2024-08-07 18:39:58 +09:00
|
|
|
use anyhow::{Context, Result};
|
2024-11-08 06:09:04 +09:00
|
|
|
use chrono::Local;
|
2024-12-02 02:52:53 +09:00
|
|
|
use console::Style;
|
2024-08-07 18:39:58 +09:00
|
|
|
use std::{
|
|
|
|
env,
|
|
|
|
path::{self, Path, PathBuf},
|
|
|
|
};
|
|
|
|
|
|
|
|
use crate::{
|
2024-08-10 04:45:44 +09:00
|
|
|
backups::{Backup, Backups},
|
2024-08-07 18:39:58 +09:00
|
|
|
devices::{self, Device},
|
2024-08-10 04:45:44 +09:00
|
|
|
storages::{self, Storage, StorageExt, Storages},
|
2024-08-07 18:39:58 +09:00
|
|
|
util,
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: fine styling like `backup list`, or should I just use the same style?
|
|
|
|
pub(crate) fn cmd_status(
|
|
|
|
path: Option<PathBuf>,
|
|
|
|
show_storage: bool,
|
|
|
|
show_backup: bool,
|
|
|
|
config_dir: &Path,
|
|
|
|
) -> Result<()> {
|
|
|
|
let path = path.unwrap_or(env::current_dir().context("Failed to get current directory.")?);
|
2024-12-02 03:07:48 +09:00
|
|
|
let current_device = devices::get_device(config_dir)?;
|
2024-08-07 18:39:58 +09:00
|
|
|
|
|
|
|
if show_storage {
|
|
|
|
let storages = storages::Storages::read(config_dir)?;
|
2024-12-02 03:07:48 +09:00
|
|
|
let storage = util::min_parent_storage(&path, &storages, ¤t_device);
|
2024-08-10 04:45:44 +09:00
|
|
|
trace!("storage {:?}", storage);
|
2024-08-07 18:39:58 +09:00
|
|
|
|
2024-08-10 04:45:44 +09:00
|
|
|
// TODO: recursively trace all storages for subdirectory?
|
2024-08-07 18:39:58 +09:00
|
|
|
match storage {
|
|
|
|
Some(storage) => {
|
|
|
|
println!("Storage: {}", storage.0.name())
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
println!("Storage: None");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if show_backup {
|
|
|
|
let devices = devices::get_devices(config_dir)?;
|
|
|
|
let storages = storages::Storages::read(config_dir)?;
|
2024-11-08 06:09:04 +09:00
|
|
|
let backups = devices.iter().map(|device| {
|
|
|
|
Backups::read(config_dir, device)
|
|
|
|
.context("Backups were not found")
|
|
|
|
.unwrap()
|
|
|
|
});
|
2024-08-10 04:45:44 +09:00
|
|
|
|
|
|
|
let (target_storage, target_diff_from_storage) =
|
2024-12-02 03:07:48 +09:00
|
|
|
util::min_parent_storage(&path, &storages, ¤t_device)
|
2024-08-10 04:45:44 +09:00
|
|
|
.context("Target path is not covered in any storage")?;
|
|
|
|
|
|
|
|
let covering_backup: Vec<_> = devices
|
|
|
|
.iter()
|
2024-11-08 06:09:04 +09:00
|
|
|
.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<_>>()
|
|
|
|
);
|
2024-08-10 04:45:44 +09:00
|
|
|
(
|
|
|
|
device,
|
|
|
|
parent_backups(
|
|
|
|
&target_diff_from_storage,
|
|
|
|
target_storage,
|
2024-11-08 06:09:04 +09:00
|
|
|
backups,
|
2024-08-10 04:45:44 +09:00
|
|
|
&storages,
|
|
|
|
device,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
trace!("{:?}", covering_backup.first());
|
|
|
|
|
|
|
|
let name_len = &covering_backup
|
2024-08-07 18:39:58 +09:00
|
|
|
.iter()
|
2024-08-10 04:45:44 +09:00
|
|
|
.map(|(_, backups)| {
|
|
|
|
backups
|
|
|
|
.iter()
|
|
|
|
.map(|(backup, _path)| backup.name().len())
|
|
|
|
.max()
|
|
|
|
.unwrap_or(0)
|
|
|
|
})
|
|
|
|
.max()
|
|
|
|
.unwrap_or(5);
|
2024-08-07 18:39:58 +09:00
|
|
|
|
|
|
|
for (backup_device, covering_backups) in covering_backup {
|
2024-12-02 02:53:30 +09:00
|
|
|
if covering_backups.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-08-07 18:39:58 +09:00
|
|
|
println!("Device: {}", backup_device.name());
|
2024-08-10 04:45:44 +09:00
|
|
|
for (backup, path_from_backup) in covering_backups {
|
2024-12-02 02:52:53 +09:00
|
|
|
let (last_backup, style) = match backup.last_backup() {
|
|
|
|
Some(log) => {
|
|
|
|
let timediff = Local::now() - log.datetime;
|
|
|
|
(
|
|
|
|
util::format_summarized_duration(timediff),
|
|
|
|
util::duration_style(timediff),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
None => ("---".to_string(), Style::new().red()),
|
2024-11-08 06:09:04 +09:00
|
|
|
};
|
2024-08-10 04:45:44 +09:00
|
|
|
println!(
|
2024-11-08 06:09:04 +09:00
|
|
|
" {:<name_len$} {} {}",
|
2024-08-10 04:45:44 +09:00
|
|
|
console::style(backup.name()).bold(),
|
2024-12-02 02:52:53 +09:00
|
|
|
style.apply_to(last_backup),
|
2024-08-10 04:45:44 +09:00
|
|
|
path_from_backup.display(),
|
|
|
|
);
|
2024-08-07 18:39:58 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-12-01 23:14:50 +09:00
|
|
|
|
|
|
|
Ok(())
|
2024-08-07 18:39:58 +09:00
|
|
|
}
|
|
|
|
|
2024-08-10 04:45:44 +09:00
|
|
|
/// Get [`Backup`]s for `device` which covers `target_path`.
|
|
|
|
/// Returns [`Vec`] of tuple of [`Backup`] and relative path from the backup root.
|
2024-08-07 18:39:58 +09:00
|
|
|
fn parent_backups<'a>(
|
2024-08-10 04:45:44 +09:00
|
|
|
target_path_from_storage: &'a Path,
|
|
|
|
target_storage: &'a Storage,
|
2024-11-08 06:09:04 +09:00
|
|
|
backups: Backups,
|
2024-08-07 18:39:58 +09:00
|
|
|
storages: &'a Storages,
|
|
|
|
device: &'a Device,
|
2024-11-08 06:09:04 +09:00
|
|
|
) -> Vec<(Backup, PathBuf)> {
|
2024-08-10 04:45:44 +09:00
|
|
|
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);
|
2024-08-07 18:39:58 +09:00
|
|
|
backups
|
|
|
|
.list
|
2024-11-08 06:09:04 +09:00
|
|
|
.into_iter()
|
2024-08-10 04:45:44 +09:00
|
|
|
.filter_map(|(_k, backup)| {
|
|
|
|
let backup_path = backup.source().path(storages, device)?;
|
2024-11-08 06:09:04 +09:00
|
|
|
trace!("{:?}", backup_path.components());
|
|
|
|
let diff = pathdiff::diff_paths(&target_path, backup_path.clone())?;
|
|
|
|
trace!("Backup: {:?}, Diff: {:?}", backup_path, diff);
|
2024-12-01 20:37:23 +09:00
|
|
|
// note: Should `RootDir` is included in this list?
|
|
|
|
if diff
|
|
|
|
.components()
|
|
|
|
.any(|c| matches!(c, path::Component::ParentDir | path::Component::Prefix(_)))
|
|
|
|
{
|
2024-08-07 18:39:58 +09:00
|
|
|
None
|
|
|
|
} else {
|
2024-08-10 04:45:44 +09:00
|
|
|
Some((backup, diff))
|
2024-08-07 18:39:58 +09:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
2024-08-10 04:45:44 +09:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2024-12-01 19:14:57 +09:00
|
|
|
use std::{path::PathBuf, vec};
|
2024-08-10 04:45:44 +09:00
|
|
|
|
|
|
|
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(),
|
2024-12-01 19:14:57 +09:00
|
|
|
path: vec!["bar".to_string()],
|
2024-08-10 04:45:44 +09:00
|
|
|
},
|
|
|
|
backups::BackupTarget {
|
|
|
|
storage: "storage_1".to_string(),
|
2024-12-01 19:14:57 +09:00
|
|
|
path: vec!["hoge".to_string()],
|
2024-08-10 04:45:44 +09:00
|
|
|
},
|
|
|
|
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(),
|
2024-12-01 19:14:57 +09:00
|
|
|
path: vec!["".to_string()],
|
2024-08-10 04:45:44 +09:00
|
|
|
},
|
|
|
|
backups::BackupTarget {
|
|
|
|
storage: "storage_3".to_string(),
|
2024-12-01 19:14:57 +09:00
|
|
|
path: vec!["foo".to_string()],
|
2024-08-10 04:45:44 +09:00
|
|
|
},
|
|
|
|
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,
|
2024-11-08 06:09:04 +09:00
|
|
|
backups.clone(),
|
2024-08-10 04:45:44 +09:00
|
|
|
&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,
|
2024-11-08 06:09:04 +09:00
|
|
|
backups.clone(),
|
2024-08-10 04:45:44 +09:00
|
|
|
&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,
|
2024-11-08 06:09:04 +09:00
|
|
|
backups,
|
2024-08-10 04:45:44 +09:00
|
|
|
&storages,
|
|
|
|
&device2,
|
|
|
|
);
|
|
|
|
assert_eq!(covering_backups_3.len(), 1);
|
2024-11-08 06:09:04 +09:00
|
|
|
let mut covering_backup_names_3 =
|
|
|
|
covering_backups_3.iter().map(|(backup, _)| backup.name());
|
2024-08-10 04:45:44 +09:00
|
|
|
assert_eq!(covering_backup_names_3.next().unwrap(), "backup_2");
|
|
|
|
assert!(covering_backup_names_3.next().is_none());
|
|
|
|
}
|
|
|
|
}
|