mirror of
https://github.com/qwjyh/xdbm
synced 2024-11-25 08:01:04 +09:00
(WIP) feat: stat for backup with name
- replace path funcs return from Result to Option - add tests for `parent_backups`
This commit is contained in:
parent
37782c934c
commit
a409a43906
9 changed files with 236 additions and 53 deletions
|
@ -44,10 +44,12 @@ impl BackupTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self, storages: &Storages, device: &Device) -> Result<PathBuf> {
|
/// Get full path of the [`BackupTarget`].
|
||||||
|
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.mount_path(device)?;
|
let parent_path = parent
|
||||||
Ok(parent_path.join(self.path.clone()))
|
.mount_path(device)?;
|
||||||
|
Some(parent_path.join(self.path.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -188,10 +188,16 @@ fn write_backups_list(
|
||||||
))?;
|
))?;
|
||||||
name_width = name_width.max(backup.name().width());
|
name_width = name_width.max(backup.name().width());
|
||||||
dev_width = dev_width.max(dev.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_width = src_width.max(format!("{}", src.display()).width());
|
||||||
src_storage_width = src_storage_width.max(backup.source().storage.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_width = dest_width.max(format!("{}", dest.display()).width());
|
||||||
dest_storage_width = dest_storage_width.max(backup.destination().storage.width());
|
dest_storage_width = dest_storage_width.max(backup.destination().storage.width());
|
||||||
let cmd_name = backup.command().name();
|
let cmd_name = backup.command().name();
|
||||||
|
@ -203,8 +209,14 @@ fn write_backups_list(
|
||||||
"Couldn't find the device specified in the backup config: {}",
|
"Couldn't find the device specified in the backup config: {}",
|
||||||
backup.name()
|
backup.name()
|
||||||
))?;
|
))?;
|
||||||
let src = backup.source().path(storages, device)?;
|
let src = backup
|
||||||
let dest = backup.destination().path(storages, device)?;
|
.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 cmd_name = backup.command().name();
|
||||||
let (last_backup_elapsed, style_on_time_elapsed) = match backup.last_backup() {
|
let (last_backup_elapsed, style_on_time_elapsed) = match backup.last_backup() {
|
||||||
Some(log) => {
|
Some(log) => {
|
||||||
|
|
|
@ -6,9 +6,9 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backups::Backups,
|
backups::{Backup, Backups},
|
||||||
devices::{self, Device},
|
devices::{self, Device},
|
||||||
storages::{self, StorageExt, Storages},
|
storages::{self, Storage, StorageExt, Storages},
|
||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,12 +20,14 @@ pub(crate) fn cmd_status(
|
||||||
config_dir: &Path,
|
config_dir: &Path,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let path = path.unwrap_or(env::current_dir().context("Failed to get current directory.")?);
|
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 {
|
if show_storage {
|
||||||
let storages = storages::Storages::read(config_dir)?;
|
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 {
|
match storage {
|
||||||
Some(storage) => {
|
Some(storage) => {
|
||||||
println!("Storage: {}", storage.0.name())
|
println!("Storage: {}", storage.0.name())
|
||||||
|
@ -38,44 +40,217 @@ 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, &device)?;
|
let backups = Backups::read(config_dir, &currrent_device)?;
|
||||||
let covering_backup = devices
|
|
||||||
|
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()
|
.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 {
|
for (backup_device, covering_backups) in covering_backup {
|
||||||
println!("Device: {}", backup_device.name());
|
println!("Device: {}", backup_device.name());
|
||||||
for backup in covering_backups {
|
for (backup, path_from_backup) in covering_backups {
|
||||||
println!(" {}", console::style(backup.0).bold());
|
println!(
|
||||||
|
" {:<name_len$} {}",
|
||||||
|
console::style(backup.name()).bold(),
|
||||||
|
path_from_backup.display(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get [`Backup`]s for `device` which covers `target_path`.
|
||||||
|
/// Returns [`Vec`] of tuple of [`Backup`] and relative path from the backup root.
|
||||||
fn parent_backups<'a>(
|
fn parent_backups<'a>(
|
||||||
target_path: &'a PathBuf,
|
target_path_from_storage: &'a Path,
|
||||||
|
target_storage: &'a Storage,
|
||||||
backups: &'a Backups,
|
backups: &'a Backups,
|
||||||
storages: &'a Storages,
|
storages: &'a Storages,
|
||||||
device: &'a Device,
|
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
|
backups
|
||||||
.list
|
.list
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(k, v)| {
|
.filter_map(|(_k, backup)| {
|
||||||
let backup_path = match v.source().path(storages, device) {
|
let backup_path = backup.source().path(storages, device)?;
|
||||||
Ok(path) => path,
|
let diff = pathdiff::diff_paths(&target_path, backup_path)?;
|
||||||
Err(e) => {
|
|
||||||
error!("Error while getting backup source path: {}", e);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let diff = pathdiff::diff_paths(target_path, backup_path)?;
|
|
||||||
if diff.components().any(|c| c == path::Component::ParentDir) {
|
if diff.components().any(|c| c == path::Component::ParentDir) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some((k, diff))
|
Some((backup, diff))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -215,8 +215,8 @@ fn write_storages_list(
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
let path = storage.mount_path(device).map_or_else(
|
let path = storage.mount_path(device).map_or_else(
|
||||||
|e| {
|
|| {
|
||||||
info!("Not found: {}", e);
|
info!("Mount path not found");
|
||||||
"".to_string()
|
"".to_string()
|
||||||
},
|
},
|
||||||
|v| v.display().to_string(),
|
|v| v.display().to_string(),
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl StorageExt for Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_path(&self, device: &devices::Device) -> Result<path::PathBuf> {
|
fn mount_path(&self, device: &devices::Device) -> Option<path::PathBuf> {
|
||||||
match self {
|
match self {
|
||||||
Self::Physical(s) => s.mount_path(device),
|
Self::Physical(s) => s.mount_path(device),
|
||||||
Self::SubDirectory(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>;
|
fn local_info(&self, device: &devices::Device) -> Option<&local_info::LocalInfo>;
|
||||||
|
|
||||||
/// Get mount path of `self` on `device`.
|
/// Get mount path of `self` on `device`.
|
||||||
fn mount_path(&self, device: &devices::Device) -> Result<path::PathBuf>;
|
/// Return [`None`] if the storage([`self`]) is not configured for the `device`.
|
||||||
|
fn mount_path(&self, device: &devices::Device) -> Option<path::PathBuf>;
|
||||||
|
|
||||||
/// Add local info of `device` to `self`.
|
/// Add local info of `device` to `self`.
|
||||||
fn bound_on_device(
|
fn bound_on_device(
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub struct Directory {
|
||||||
/// Relative path to the parent storage.
|
/// Relative path to the parent storage.
|
||||||
relative_path: path::PathBuf,
|
relative_path: path::PathBuf,
|
||||||
pub notes: String,
|
pub notes: String,
|
||||||
/// Device and localinfo pairs.
|
/// [`devices::Device`] name and localinfo pairs.
|
||||||
local_infos: BTreeMap<String, LocalInfo>,
|
local_infos: BTreeMap<String, LocalInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,8 @@ impl Directory {
|
||||||
let parent_mount_path = self
|
let parent_mount_path = self
|
||||||
.parent(storages)
|
.parent(storages)
|
||||||
.context("Can't find parent storage")?
|
.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()))
|
Ok(parent_mount_path.join(self.relative_path.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,12 +99,10 @@ impl StorageExt for Directory {
|
||||||
self.local_infos.get(&device.name())
|
self.local_infos.get(&device.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_path(&self, device: &devices::Device) -> Result<path::PathBuf> {
|
fn mount_path(&self, device: &devices::Device) -> Option<std::path::PathBuf> {
|
||||||
Ok(self
|
self.local_infos
|
||||||
.local_infos
|
|
||||||
.get(&device.name())
|
.get(&device.name())
|
||||||
.context(format!("LocalInfo for storage: {} not found", &self.name()))?
|
.map(|info| info.mount_path())
|
||||||
.mount_path())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method doesn't use `mount_path`.
|
/// This method doesn't use `mount_path`.
|
||||||
|
|
|
@ -61,12 +61,10 @@ impl StorageExt for OnlineStorage {
|
||||||
self.local_infos.get(&device.name())
|
self.local_infos.get(&device.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_path(&self, device: &devices::Device) -> Result<std::path::PathBuf> {
|
fn mount_path(&self, device: &devices::Device) -> Option<std::path::PathBuf> {
|
||||||
Ok(self
|
self.local_infos
|
||||||
.local_infos
|
|
||||||
.get(&device.name())
|
.get(&device.name())
|
||||||
.context(format!("LocalInfo for storage: {} not found", &self.name()))?
|
.map(|info| info.mount_path())
|
||||||
.mount_path())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bound_on_device(
|
fn bound_on_device(
|
||||||
|
|
|
@ -21,6 +21,7 @@ pub struct PhysicalDrivePartition {
|
||||||
fs: String,
|
fs: String,
|
||||||
is_removable: bool,
|
is_removable: bool,
|
||||||
// system_names: BTreeMap<String, String>,
|
// system_names: BTreeMap<String, String>,
|
||||||
|
/// [`Device`] name and [`LocalInfo`] mapping.
|
||||||
local_infos: BTreeMap<String, LocalInfo>,
|
local_infos: BTreeMap<String, LocalInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,12 +113,10 @@ impl StorageExt for PhysicalDrivePartition {
|
||||||
self.local_infos.get(&device.name())
|
self.local_infos.get(&device.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_path(&self, device: &devices::Device) -> Result<path::PathBuf> {
|
fn mount_path(&self, device: &devices::Device) -> Option<path::PathBuf> {
|
||||||
Ok(self
|
self.local_infos
|
||||||
.local_infos
|
|
||||||
.get(&device.name())
|
.get(&device.name())
|
||||||
.context(format!("LocalInfo for storage: {} not found", &self.name()))?
|
.map(|info| info.mount_path())
|
||||||
.mount_path())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bound_on_device(
|
fn bound_on_device(
|
||||||
|
|
|
@ -18,10 +18,7 @@ pub fn min_parent_storage<'a>(
|
||||||
.list
|
.list
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(k, storage)| {
|
.filter_map(|(k, storage)| {
|
||||||
let storage_path = match storage.mount_path(device) {
|
let storage_path = storage.mount_path(device)?;
|
||||||
Ok(path) => path,
|
|
||||||
Err(_) => return None,
|
|
||||||
};
|
|
||||||
let diff = pathdiff::diff_paths(path, storage_path)?;
|
let diff = pathdiff::diff_paths(path, storage_path)?;
|
||||||
if diff.components().any(|c| c == path::Component::ParentDir) {
|
if diff.components().any(|c| c == path::Component::ParentDir) {
|
||||||
None
|
None
|
||||||
|
|
Loading…
Reference in a new issue