mirror of
https://github.com/qwjyh/xdbm
synced 2024-11-24 07:31:05 +09:00
(WIP): implementing multi device backup search
This commit is contained in:
parent
a409a43906
commit
bc3939c9bc
4 changed files with 61 additions and 35 deletions
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
12
src/util.rs
12
src/util.rs
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue