(WIP): implementing multi device backup search

This commit is contained in:
qwjyh 2024-11-08 06:09:04 +09:00
parent a409a43906
commit bc3939c9bc
4 changed files with 61 additions and 35 deletions

View file

@ -27,7 +27,7 @@ pub fn backups_file(device: &Device) -> PathBuf {
} }
/// Targets for backup source or destination. /// Targets for backup source or destination.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupTarget { pub struct BackupTarget {
/// `name()` of [`crate::storages::Storage`]. /// `name()` of [`crate::storages::Storage`].
/// Use `String` for serialization/deserialization. /// Use `String` for serialization/deserialization.
@ -47,14 +47,13 @@ impl BackupTarget {
/// Get full path of the [`BackupTarget`]. /// Get full path of the [`BackupTarget`].
pub fn path(&self, storages: &Storages, device: &Device) -> Option<PathBuf> { pub fn path(&self, storages: &Storages, device: &Device) -> Option<PathBuf> {
let parent = storages.get(&self.storage).unwrap(); let parent = storages.get(&self.storage).unwrap();
let parent_path = parent let parent_path = parent.mount_path(device)?;
.mount_path(device)?;
Some(parent_path.join(self.path.clone())) Some(parent_path.join(self.path.clone()))
} }
} }
/// Type of backup commands. /// Type of backup commands.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BackupCommand { pub enum BackupCommand {
ExternallyInvoked(ExternallyInvoked), ExternallyInvoked(ExternallyInvoked),
} }
@ -81,7 +80,7 @@ impl BackupCommandExt for BackupCommand {
/// Backup commands which is not invoked from xdbm itself. /// Backup commands which is not invoked from xdbm itself.
/// Call xdbm externally to record backup datetime and status. /// Call xdbm externally to record backup datetime and status.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternallyInvoked { pub struct ExternallyInvoked {
name: String, name: String,
pub note: String, pub note: String,
@ -104,7 +103,7 @@ impl BackupCommandExt for ExternallyInvoked {
} }
/// Backup execution log. /// Backup execution log.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupLog { pub struct BackupLog {
pub datetime: DateTime<Local>, pub datetime: DateTime<Local>,
status: BackupResult, status: BackupResult,
@ -124,7 +123,7 @@ impl BackupLog {
} }
/// Result of backup. /// Result of backup.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BackupResult { pub enum BackupResult {
Success, Success,
Failure, Failure,
@ -141,7 +140,7 @@ impl BackupResult {
} }
/// Backup source, destination, command and logs. /// Backup source, destination, command and logs.
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Backup { pub struct Backup {
/// must be unique /// must be unique
name: String, name: String,
@ -202,7 +201,7 @@ impl Backup {
} }
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Backups { pub struct Backups {
pub list: BTreeMap<String, Backup>, pub list: BTreeMap<String, Backup>,
} }

View file

@ -5,7 +5,7 @@ use std::{
}; };
use anyhow::{anyhow, Context, Ok, Result}; use anyhow::{anyhow, Context, Ok, Result};
use chrono::{Local, TimeDelta}; use chrono::Local;
use console::Style; use console::Style;
use dunce::canonicalize; use dunce::canonicalize;
use git2::Repository; use git2::Repository;
@ -154,17 +154,6 @@ pub fn cmd_backup_list(
Ok(()) Ok(())
} }
fn duration_style(time: TimeDelta) -> Style {
match time {
x if x < TimeDelta::days(7) => Style::new().green(),
x if x < TimeDelta::days(14) => Style::new().yellow(),
x if x < TimeDelta::days(28) => Style::new().magenta(),
x if x < TimeDelta::days(28 * 3) => Style::new().red(),
x if x < TimeDelta::days(180) => Style::new().red().bold(),
_ => Style::new().on_red().black(),
}
}
/// TODO: status printing /// TODO: status printing
fn write_backups_list( fn write_backups_list(
mut writer: impl io::Write, mut writer: impl io::Write,
@ -222,7 +211,7 @@ fn write_backups_list(
Some(log) => { Some(log) => {
let time = Local::now() - log.datetime; let time = Local::now() - log.datetime;
let s = util::format_summarized_duration(time); let s = util::format_summarized_duration(time);
let style = duration_style(time); let style = util::duration_style(time);
(style.apply_to(s), style) (style.apply_to(s), style)
} }
None => { None => {

View file

@ -1,5 +1,5 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use console::Style; use chrono::Local;
use std::{ use std::{
env, env,
path::{self, Path, PathBuf}, path::{self, Path, PathBuf},
@ -40,7 +40,11 @@ pub(crate) fn cmd_status(
if show_backup { if show_backup {
let devices = devices::get_devices(config_dir)?; let devices = devices::get_devices(config_dir)?;
let storages = storages::Storages::read(config_dir)?; let storages = storages::Storages::read(config_dir)?;
let backups = Backups::read(config_dir, &currrent_device)?; let backups = devices.iter().map(|device| {
Backups::read(config_dir, device)
.context("Backups were not found")
.unwrap()
});
let (target_storage, target_diff_from_storage) = let (target_storage, target_diff_from_storage) =
util::min_parent_storage(&path, &storages, &currrent_device) util::min_parent_storage(&path, &storages, &currrent_device)
@ -48,13 +52,27 @@ pub(crate) fn cmd_status(
let covering_backup: Vec<_> = devices let covering_backup: Vec<_> = devices
.iter() .iter()
.map(|device| { .zip(backups)
.map(|(device, backups)| {
debug!(
"dev {}, storage {:?}",
device.name(),
backups
.list
.iter()
.map(|(backup_name, backup)| format!(
"{} {}",
backup_name,
backup.source().storage
))
.collect::<Vec<_>>()
);
( (
device, device,
parent_backups( parent_backups(
&target_diff_from_storage, &target_diff_from_storage,
target_storage, target_storage,
&backups, backups,
&storages, &storages,
device, device,
), ),
@ -78,9 +96,14 @@ pub(crate) fn cmd_status(
for (backup_device, covering_backups) in covering_backup { for (backup_device, covering_backups) in covering_backup {
println!("Device: {}", backup_device.name()); println!("Device: {}", backup_device.name());
for (backup, path_from_backup) in covering_backups { for (backup, path_from_backup) in covering_backups {
let last_backup = match backup.last_backup() {
Some(log) => util::format_summarized_duration(Local::now() - log.datetime),
None => "---".to_string(),
};
println!( println!(
" {:<name_len$} {}", " {:<name_len$} {} {}",
console::style(backup.name()).bold(), console::style(backup.name()).bold(),
last_backup,
path_from_backup.display(), path_from_backup.display(),
); );
} }
@ -94,10 +117,10 @@ pub(crate) fn cmd_status(
fn parent_backups<'a>( fn parent_backups<'a>(
target_path_from_storage: &'a Path, target_path_from_storage: &'a Path,
target_storage: &'a Storage, target_storage: &'a Storage,
backups: &'a Backups, backups: Backups,
storages: &'a Storages, storages: &'a Storages,
device: &'a Device, device: &'a Device,
) -> Vec<(&'a Backup, PathBuf)> { ) -> Vec<(Backup, PathBuf)> {
trace!("Dev {:?}", device.name()); trace!("Dev {:?}", device.name());
let target_path = match target_storage.mount_path(device) { let target_path = match target_storage.mount_path(device) {
Some(target_path) => target_path.join(target_path_from_storage), Some(target_path) => target_path.join(target_path_from_storage),
@ -106,10 +129,12 @@ fn parent_backups<'a>(
trace!("Path on the device {:?}", target_path); trace!("Path on the device {:?}", target_path);
backups backups
.list .list
.iter() .into_iter()
.filter_map(|(_k, backup)| { .filter_map(|(_k, backup)| {
let backup_path = backup.source().path(storages, device)?; let backup_path = backup.source().path(storages, device)?;
let diff = pathdiff::diff_paths(&target_path, backup_path)?; trace!("{:?}", backup_path.components());
let diff = pathdiff::diff_paths(&target_path, backup_path.clone())?;
trace!("Backup: {:?}, Diff: {:?}", backup_path, diff);
if diff.components().any(|c| c == path::Component::ParentDir) { if diff.components().any(|c| c == path::Component::ParentDir) {
None None
} else { } else {
@ -218,7 +243,7 @@ mod test {
let covering_backups_1 = parent_backups( let covering_backups_1 = parent_backups(
&target_path_from_storage1, &target_path_from_storage1,
target_storage1, target_storage1,
&backups, backups.clone(),
&storages, &storages,
&device1, &device1,
); );
@ -231,7 +256,7 @@ mod test {
let covering_backups_2 = parent_backups( let covering_backups_2 = parent_backups(
&target_path_from_storage2, &target_path_from_storage2,
target_storage2, target_storage2,
&backups, backups.clone(),
&storages, &storages,
&device2, &device2,
); );
@ -244,12 +269,13 @@ mod test {
let covering_backups_3 = parent_backups( let covering_backups_3 = parent_backups(
&target_path_from_storage3, &target_path_from_storage3,
target_storage3, target_storage3,
&backups, backups,
&storages, &storages,
&device2, &device2,
); );
assert_eq!(covering_backups_3.len(), 1); assert_eq!(covering_backups_3.len(), 1);
let mut covering_backup_names_3 = covering_backups_3.iter().map(|(backup, _)| backup.name()); let mut covering_backup_names_3 =
covering_backups_3.iter().map(|(backup, _)| backup.name());
assert_eq!(covering_backup_names_3.next().unwrap(), "backup_2"); assert_eq!(covering_backup_names_3.next().unwrap(), "backup_2");
assert!(covering_backup_names_3.next().is_none()); assert!(covering_backup_names_3.next().is_none());
} }

View file

@ -1,6 +1,7 @@
use std::path::{self, PathBuf}; use std::path::{self, PathBuf};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use chrono::TimeDelta;
use console::Style; use console::Style;
use crate::{ use crate::{
@ -59,6 +60,17 @@ pub fn format_summarized_duration(dt: chrono::Duration) -> String {
} }
} }
pub fn duration_style(time: TimeDelta) -> Style {
match time {
x if x < TimeDelta::days(7) => Style::new().green(),
x if x < TimeDelta::days(14) => Style::new().yellow(),
x if x < TimeDelta::days(28) => Style::new().magenta(),
x if x < TimeDelta::days(28 * 3) => Style::new().red(),
x if x < TimeDelta::days(180) => Style::new().red().bold(),
_ => Style::new().on_red().black(),
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use anyhow::Result; use anyhow::Result;