diff --git a/src/backups.rs b/src/backups.rs index fbb4c1f..896fcd9 100644 --- a/src/backups.rs +++ b/src/backups.rs @@ -44,10 +44,12 @@ impl BackupTarget { } } - pub fn path(&self, storages: &Storages, device: &Device) -> Result { + /// Get full path of the [`BackupTarget`]. + pub fn path(&self, storages: &Storages, device: &Device) -> Option { let parent = storages.get(&self.storage).unwrap(); - let parent_path = parent.mount_path(device)?; - Ok(parent_path.join(self.path.clone())) + let parent_path = parent + .mount_path(device)?; + Some(parent_path.join(self.path.clone())) } } diff --git a/src/cmd_backup.rs b/src/cmd_backup.rs index 7ae9767..df6d13d 100644 --- a/src/cmd_backup.rs +++ b/src/cmd_backup.rs @@ -188,10 +188,16 @@ fn write_backups_list( ))?; name_width = name_width.max(backup.name().width()); dev_width = dev_width.max(dev.width()); - let src = backup.source().path(storages, device)?; + let src = backup + .source() + .path(storages, device) + .context("Couldn't get path for source")?; src_width = src_width.max(format!("{}", src.display()).width()); src_storage_width = src_storage_width.max(backup.source().storage.width()); - let dest = backup.destination().path(storages, device)?; + let dest = backup + .destination() + .path(storages, device) + .context("Couldn't get path for destination")?; dest_width = dest_width.max(format!("{}", dest.display()).width()); dest_storage_width = dest_storage_width.max(backup.destination().storage.width()); let cmd_name = backup.command().name(); @@ -203,8 +209,14 @@ fn write_backups_list( "Couldn't find the device specified in the backup config: {}", backup.name() ))?; - let src = backup.source().path(storages, device)?; - let dest = backup.destination().path(storages, device)?; + let src = backup + .source() + .path(storages, device) + .context("Couldn't get path for source")?; + let dest = backup + .destination() + .path(storages, device) + .context("Couldn't get path for destination")?; let cmd_name = backup.command().name(); let (last_backup_elapsed, style_on_time_elapsed) = match backup.last_backup() { Some(log) => { diff --git a/src/cmd_status.rs b/src/cmd_status.rs index eebc2cc..cd90860 100644 --- a/src/cmd_status.rs +++ b/src/cmd_status.rs @@ -6,9 +6,9 @@ use std::{ }; use crate::{ - backups::Backups, + backups::{Backup, Backups}, devices::{self, Device}, - storages::{self, StorageExt, Storages}, + storages::{self, Storage, StorageExt, Storages}, util, }; @@ -20,12 +20,14 @@ pub(crate) fn cmd_status( config_dir: &Path, ) -> Result<()> { let path = path.unwrap_or(env::current_dir().context("Failed to get current directory.")?); - let device = devices::get_device(config_dir)?; + let currrent_device = devices::get_device(config_dir)?; if show_storage { let storages = storages::Storages::read(config_dir)?; - let storage = util::min_parent_storage(&path, &storages, &device); + let storage = util::min_parent_storage(&path, &storages, &currrent_device); + trace!("storage {:?}", storage); + // TODO: recursively trace all storages for subdirectory? match storage { Some(storage) => { println!("Storage: {}", storage.0.name()) @@ -38,44 +40,217 @@ pub(crate) fn cmd_status( if show_backup { let devices = devices::get_devices(config_dir)?; let storages = storages::Storages::read(config_dir)?; - let backups = Backups::read(config_dir, &device)?; - let covering_backup = devices + let backups = Backups::read(config_dir, &currrent_device)?; + + let (target_storage, target_diff_from_storage) = + util::min_parent_storage(&path, &storages, &currrent_device) + .context("Target path is not covered in any storage")?; + + let covering_backup: Vec<_> = devices .iter() - .map(|device| (device, parent_backups(&path, &backups, &storages, device))); + .map(|device| { + ( + device, + parent_backups( + &target_diff_from_storage, + target_storage, + &backups, + &storages, + device, + ), + ) + }) + .collect(); + trace!("{:?}", covering_backup.first()); + + let name_len = &covering_backup + .iter() + .map(|(_, backups)| { + backups + .iter() + .map(|(backup, _path)| backup.name().len()) + .max() + .unwrap_or(0) + }) + .max() + .unwrap_or(5); for (backup_device, covering_backups) in covering_backup { println!("Device: {}", backup_device.name()); - for backup in covering_backups { - println!(" {}", console::style(backup.0).bold()); + for (backup, path_from_backup) in covering_backups { + println!( + " {:( - target_path: &'a PathBuf, + target_path_from_storage: &'a Path, + target_storage: &'a Storage, backups: &'a Backups, storages: &'a Storages, device: &'a Device, -) -> Vec<(&'a String, PathBuf)> { +) -> Vec<(&'a Backup, PathBuf)> { + trace!("Dev {:?}", device.name()); + let target_path = match target_storage.mount_path(device) { + Some(target_path) => target_path.join(target_path_from_storage), + None => return vec![], + }; + trace!("Path on the device {:?}", target_path); backups .list .iter() - .filter_map(|(k, v)| { - let backup_path = match v.source().path(storages, device) { - Ok(path) => path, - Err(e) => { - error!("Error while getting backup source path: {}", e); - return None; - } - }; - let diff = pathdiff::diff_paths(target_path, backup_path)?; + .filter_map(|(_k, backup)| { + let backup_path = backup.source().path(storages, device)?; + let diff = pathdiff::diff_paths(&target_path, backup_path)?; if diff.components().any(|c| c == path::Component::ParentDir) { None } else { - Some((k, diff)) + Some((backup, diff)) } }) .collect() } + +#[cfg(test)] +mod test { + use std::path::PathBuf; + + use crate::{ + backups::{self, ExternallyInvoked}, + devices, + storages::{self, online_storage::OnlineStorage, StorageExt}, + util, + }; + + use super::parent_backups; + + #[test] + fn test_parent_backups() { + let device1 = devices::Device::new("device_1".to_string()); + let mut storage1 = storages::Storage::Online(OnlineStorage::new( + "storage_1".to_string(), + "smb".to_string(), + 1_000_000, + "str1".to_string(), + PathBuf::from("/home/foo/"), + &device1, + )); + let storage2 = storages::Storage::Online(OnlineStorage::new( + "storage_2".to_string(), + "smb".to_string(), + 1_000_000_000, + "str2".to_string(), + PathBuf::from("/"), + &device1, + )); + let device2 = devices::Device::new("device_2".to_string()); + storage1 + .bound_on_device("alias".to_string(), PathBuf::from("/mnt/dev"), &device2) + .unwrap(); + let storage3 = storages::Storage::Online(OnlineStorage::new( + "storage_3".to_string(), + "smb".to_string(), + 2_000_000_000, + "str2".to_string(), + PathBuf::from("/"), + &device2, + )); + let storages = { + let mut storages = storages::Storages::new(); + storages.add(storage1).unwrap(); + storages.add(storage2).unwrap(); + storages.add(storage3).unwrap(); + storages + }; + + let backup1 = backups::Backup::new( + "backup_1".to_string(), + device1.name().to_string(), + backups::BackupTarget { + storage: "storage_1".to_string(), + path: PathBuf::from("bar"), + }, + backups::BackupTarget { + storage: "storage_1".to_string(), + path: PathBuf::from("hoge"), + }, + backups::BackupCommand::ExternallyInvoked(ExternallyInvoked::new( + "cmd".to_string(), + "".to_string(), + )), + ); + let backup2 = backups::Backup::new( + "backup_2".to_string(), + device2.name().to_string(), + backups::BackupTarget { + storage: "storage_1".to_string(), + path: PathBuf::from(""), + }, + backups::BackupTarget { + storage: "storage_3".to_string(), + path: PathBuf::from("foo"), + }, + backups::BackupCommand::ExternallyInvoked(ExternallyInvoked::new( + "cmd".to_string(), + "".to_string(), + )), + ); + + let backups = { + let mut backups = backups::Backups::new(); + backups.add(backup1).unwrap(); + backups.add(backup2).unwrap(); + backups + }; + + let target_path1 = PathBuf::from("/home/foo/bar/hoo"); + let (target_storage1, target_path_from_storage1) = + util::min_parent_storage(&target_path1, &storages, &device1) + .expect("Failed to get storage"); + let covering_backups_1 = parent_backups( + &target_path_from_storage1, + target_storage1, + &backups, + &storages, + &device1, + ); + assert_eq!(covering_backups_1.len(), 2); + + let target_path2 = PathBuf::from("/mnt/"); + let (target_storage2, target_path_from_storage2) = + util::min_parent_storage(&target_path2, &storages, &device2) + .expect("Failed to get storage"); + let covering_backups_2 = parent_backups( + &target_path_from_storage2, + target_storage2, + &backups, + &storages, + &device2, + ); + assert_eq!(covering_backups_2.len(), 0); + + let target_path3 = PathBuf::from("/mnt/dev/foo"); + let (target_storage3, target_path_from_storage3) = + util::min_parent_storage(&target_path3, &storages, &device2) + .expect("Failed to get storage"); + let covering_backups_3 = parent_backups( + &target_path_from_storage3, + target_storage3, + &backups, + &storages, + &device2, + ); + assert_eq!(covering_backups_3.len(), 1); + 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!(covering_backup_names_3.next().is_none()); + } +} diff --git a/src/cmd_storage.rs b/src/cmd_storage.rs index 8c39646..2cb9414 100644 --- a/src/cmd_storage.rs +++ b/src/cmd_storage.rs @@ -215,8 +215,8 @@ fn write_storages_list( "" }; let path = storage.mount_path(device).map_or_else( - |e| { - info!("Not found: {}", e); + || { + info!("Mount path not found"); "".to_string() }, |v| v.display().to_string(), diff --git a/src/storages.rs b/src/storages.rs index b80536e..d4b9dd4 100644 --- a/src/storages.rs +++ b/src/storages.rs @@ -78,7 +78,7 @@ impl StorageExt for Storage { } } - fn mount_path(&self, device: &devices::Device) -> Result { + fn mount_path(&self, device: &devices::Device) -> Option { match self { Self::Physical(s) => s.mount_path(device), Self::SubDirectory(s) => s.mount_path(device), @@ -144,7 +144,8 @@ pub trait StorageExt { fn local_info(&self, device: &devices::Device) -> Option<&local_info::LocalInfo>; /// Get mount path of `self` on `device`. - fn mount_path(&self, device: &devices::Device) -> Result; + /// Return [`None`] if the storage([`self`]) is not configured for the `device`. + fn mount_path(&self, device: &devices::Device) -> Option; /// Add local info of `device` to `self`. fn bound_on_device( diff --git a/src/storages/directory.rs b/src/storages/directory.rs index 4cd7803..f1e460c 100644 --- a/src/storages/directory.rs +++ b/src/storages/directory.rs @@ -19,7 +19,7 @@ pub struct Directory { /// Relative path to the parent storage. relative_path: path::PathBuf, pub notes: String, - /// Device and localinfo pairs. + /// [`devices::Device`] name and localinfo pairs. local_infos: BTreeMap, } @@ -80,7 +80,8 @@ impl Directory { let parent_mount_path = self .parent(storages) .context("Can't find parent storage")? - .mount_path(device)?; + .mount_path(device) + .context("Can't find mount path")?; Ok(parent_mount_path.join(self.relative_path.clone())) } } @@ -98,12 +99,10 @@ impl StorageExt for Directory { self.local_infos.get(&device.name()) } - fn mount_path(&self, device: &devices::Device) -> Result { - Ok(self - .local_infos + fn mount_path(&self, device: &devices::Device) -> Option { + self.local_infos .get(&device.name()) - .context(format!("LocalInfo for storage: {} not found", &self.name()))? - .mount_path()) + .map(|info| info.mount_path()) } /// This method doesn't use `mount_path`. diff --git a/src/storages/online_storage.rs b/src/storages/online_storage.rs index 4ce2459..6c4d62c 100644 --- a/src/storages/online_storage.rs +++ b/src/storages/online_storage.rs @@ -61,12 +61,10 @@ impl StorageExt for OnlineStorage { self.local_infos.get(&device.name()) } - fn mount_path(&self, device: &devices::Device) -> Result { - Ok(self - .local_infos + fn mount_path(&self, device: &devices::Device) -> Option { + self.local_infos .get(&device.name()) - .context(format!("LocalInfo for storage: {} not found", &self.name()))? - .mount_path()) + .map(|info| info.mount_path()) } fn bound_on_device( diff --git a/src/storages/physical_drive_partition.rs b/src/storages/physical_drive_partition.rs index 95dec3f..14a35a6 100644 --- a/src/storages/physical_drive_partition.rs +++ b/src/storages/physical_drive_partition.rs @@ -21,6 +21,7 @@ pub struct PhysicalDrivePartition { fs: String, is_removable: bool, // system_names: BTreeMap, + /// [`Device`] name and [`LocalInfo`] mapping. local_infos: BTreeMap, } @@ -112,12 +113,10 @@ impl StorageExt for PhysicalDrivePartition { self.local_infos.get(&device.name()) } - fn mount_path(&self, device: &devices::Device) -> Result { - Ok(self - .local_infos + fn mount_path(&self, device: &devices::Device) -> Option { + self.local_infos .get(&device.name()) - .context(format!("LocalInfo for storage: {} not found", &self.name()))? - .mount_path()) + .map(|info| info.mount_path()) } fn bound_on_device( diff --git a/src/util.rs b/src/util.rs index 820de33..8f27bc4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -18,10 +18,7 @@ pub fn min_parent_storage<'a>( .list .iter() .filter_map(|(k, storage)| { - let storage_path = match storage.mount_path(device) { - Ok(path) => path, - Err(_) => return None, - }; + let storage_path = storage.mount_path(device)?; let diff = pathdiff::diff_paths(path, storage_path)?; if diff.components().any(|c| c == path::Component::ParentDir) { None