From b1b174b4b3994e78a93dd582469b17ae0c02138c Mon Sep 17 00:00:00 2001 From: qwjyh Date: Sat, 16 Mar 2024 21:31:08 +0900 Subject: [PATCH] add backup done --- README.md | 4 +- src/backups.rs | 37 ++++++++++++++++- src/cmd_backup.rs | 52 ++++++++++++++++++++++-- src/cmd_storage.rs | 6 +-- src/main.rs | 2 +- src/storages/physical_drive_partition.rs | 2 +- src/util.rs | 26 ++++++++++++ 7 files changed, 117 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 566dc0d..a8dcb7a 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ - [ ] backup add - [ ] test for backup add - [ ] backup list - - [ ] status printing - - [ ] backup done + - [x] status printing + - [x] backup done - [ ] fancy display - [ ] no commit option diff --git a/src/backups.rs b/src/backups.rs index 3c55c98..a660110 100644 --- a/src/backups.rs +++ b/src/backups.rs @@ -101,11 +101,23 @@ impl BackupCommandExt for ExternallyInvoked { /// Backup execution log. #[derive(Debug, Serialize, Deserialize)] pub struct BackupLog { - datetime: DateTime, + pub datetime: DateTime, status: BackupResult, log: String, } +impl BackupLog { + pub fn new_with_current_time(status: BackupResult, log: String) -> BackupLog { + let timestamp = Local::now(); + trace!("Generating timestamp: {:?}", timestamp); + BackupLog { + datetime: timestamp, + status, + log, + } + } +} + /// Result of backup. #[derive(Debug, Serialize, Deserialize)] pub enum BackupResult { @@ -113,6 +125,16 @@ pub enum BackupResult { Failure, } +impl BackupResult { + pub fn from_exit_code(code: u64) -> Self { + if code == 0 { + Self::Success + } else { + Self::Failure + } + } +} + /// Backup source, destination, command and logs. #[derive(Debug, Serialize, Deserialize)] pub struct Backup { @@ -164,6 +186,15 @@ impl Backup { pub fn command(&self) -> &BackupCommand { &self.command } + + pub fn add_log(&mut self, newlog: BackupLog) -> () { + self.logs.push(newlog) + } + + /// Get the last backup. + pub fn last_backup(&self) -> Option<&BackupLog> { + self.logs.iter().max_by_key(|log| log.datetime) + } } #[derive(Debug, Serialize, Deserialize)] @@ -183,6 +214,10 @@ impl Backups { self.list.get(name) } + pub fn get_mut(&mut self, name: &String) -> Option<&mut Backup> { + self.list.get_mut(name) + } + /// Add new [`Backup`]. /// New `backup` must has new unique name. pub fn add(&mut self, backup: Backup) -> Result<()> { diff --git a/src/cmd_backup.rs b/src/cmd_backup.rs index e8a360a..ff51461 100644 --- a/src/cmd_backup.rs +++ b/src/cmd_backup.rs @@ -1,10 +1,11 @@ use std::{ collections::HashMap, io::{self, stdout, Write}, - path::{PathBuf, self}, + path::{self, PathBuf}, }; -use anyhow::{anyhow, Context, Result, Ok}; +use anyhow::{anyhow, Context, Ok, Result}; +use chrono::Local; use dunce::canonicalize; use git2::Repository; use unicode_width::UnicodeWidthStr; @@ -235,6 +236,7 @@ fn write_backups_list( let mut src_storage_width = 0; let mut dest_width = 0; let mut dest_storage_width = 0; + let mut cmd_name_width = 0; // get widths for ((dev, _name), backup) in &backups { let device = backup.device(devices).context(format!( @@ -250,6 +252,7 @@ fn write_backups_list( 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(); + cmd_name_width = cmd_name_width.max(cmd_name.width()); } // main printing for ((dev, _name), backup) in &backups { @@ -260,9 +263,18 @@ fn write_backups_list( let src = backup.source().path(&storages, &device)?; let dest = backup.destination().path(&storages, &device)?; let cmd_name = backup.command().name(); + let last_backup_elapsed = match backup.last_backup() { + Some(log) => { + let time = Local::now() - log.datetime; + util::format_summarized_duration(time) + } + None => { + format!("---") + } + }; writeln!( writer, - "{name:, + repo: Repository, + config_dir: &PathBuf, +) -> Result<()> { + let device = devices::get_device(&config_dir)?; + let mut backups = Backups::read(&config_dir, &device)?; + let backup = backups + .get_mut(&name) + .context(format!("Failed to get backup with name {}", name))?; + trace!("Got backup: {:?}", backup); + let backup_name = backup.name().clone(); + let status = BackupResult::from_exit_code(exit_status); + let new_log = BackupLog::new_with_current_time(status, log.unwrap_or("".to_string())); + trace!("New backup log: {:?}", new_log); + backup.add_log(new_log); + trace!("Added"); + backups.write(&config_dir, &device)?; + add_and_commit( + &repo, + &backups::backups_file(&device), + &format!("Done backup: {}", backup_name), + )?; + Ok(()) +} diff --git a/src/cmd_storage.rs b/src/cmd_storage.rs index 304b1bd..8380d91 100644 --- a/src/cmd_storage.rs +++ b/src/cmd_storage.rs @@ -50,7 +50,7 @@ pub(crate) fn cmd_storage_add( } else { manually_construct_physical_drive_partition( name, - canonicalize(util::expand_tilde(path.unwrap()))?, + canonicalize(util::expand_tilde(path.unwrap())?)?, &device, )? }; @@ -77,7 +77,7 @@ pub(crate) fn cmd_storage_add( trace!("SubDirectory arguments: path: {:?}", path); // Nightly feature std::path::absolute trace!("Canonicalize path: {:?}", path); - let path = canonicalize(util::expand_tilde(path))?; + let path = canonicalize(util::expand_tilde(path)?)?; trace!("canonicalized: path: {:?}", path); let storage = directory::Directory::try_from_device_path( @@ -99,7 +99,7 @@ pub(crate) fn cmd_storage_add( )); } trace!("Canonicalize path: {:?}", path); - let path = canonicalize(util::expand_tilde(path))?; + let path = canonicalize(util::expand_tilde(path)?)?; let storage = storages::online_storage::OnlineStorage::new( name, provider, capacity, alias, path, &device, ); diff --git a/src/main.rs b/src/main.rs index b2bd7c9..7f4c882 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,7 +120,7 @@ fn main() -> Result<()> { name, exit_status, log, - } => todo!(), + } => cmd_backup::cmd_backup_done(name, exit_status, log, repo, &config_dir)?, } } } diff --git a/src/storages/physical_drive_partition.rs b/src/storages/physical_drive_partition.rs index 7f06748..d8b9846 100644 --- a/src/storages/physical_drive_partition.rs +++ b/src/storages/physical_drive_partition.rs @@ -59,7 +59,7 @@ impl PhysicalDrivePartition { let fs = disk.file_system(); trace!("fs: {:?}", fs); let fs = std::str::from_utf8(fs)?; - let local_info = LocalInfo::new(alias, disk.mount_point().to_path_buf().canonicalize()?); + let local_info = LocalInfo::new(alias, disk.mount_point().to_path_buf()); Ok(PhysicalDrivePartition { name: name, kind: format!("{:?}", disk.kind()), diff --git a/src/util.rs b/src/util.rs index 7f7cd5b..4f98b79 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,8 @@ use std::path::{self, PathBuf}; +use anyhow::{Context, Result}; +use chrono::format; + use crate::{ devices::Device, storages::{Storage, StorageExt, Storages}, @@ -35,3 +38,26 @@ pub fn min_parent_storage<'a>( let storage = storages.get(name)?; Some((storage, pathdiff)) } + +/// Expand first `~` in path as `home_dir`. +pub fn expand_tilde(path: PathBuf) -> Result { + if path.components().next() == Some(path::Component::Normal("~".as_ref())) { + let mut expanded_path = dirs::home_dir().context("Failed to expand home directory.")?; + for c in path.components().skip(1) { + expanded_path.push(c) + } + Ok(expanded_path) + } else { + Ok(path) + } +} + +pub fn format_summarized_duration(dt: chrono::Duration) -> String { + if dt.num_days() > 0 { + format!("{}d", dt.num_days()) + } else if dt.num_hours() > 0 { + format!("{}h", dt.num_hours()) + } else { + format!("{}min", dt.num_minutes()) + } +}