add backup done

This commit is contained in:
qwjyh 2024-03-16 21:31:08 +09:00
parent 4f0d725b52
commit b1b174b4b3
7 changed files with 117 additions and 12 deletions

View file

@ -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

View file

@ -101,11 +101,23 @@ impl BackupCommandExt for ExternallyInvoked {
/// Backup execution log.
#[derive(Debug, Serialize, Deserialize)]
pub struct BackupLog {
datetime: DateTime<Local>,
pub datetime: DateTime<Local>,
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<()> {

View file

@ -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:<name_width$} [{dev:<dev_width$}] {src:<src_storage_width$} → {dest:<dest_storage_width$} {cmd_name}",
"{name:<name_width$} [{dev:<dev_width$}] {src:<src_storage_width$} → {dest:<dest_storage_width$} {last_backup_elapsed}",
name = backup.name(),
src = backup.source().storage,
dest = backup.destination().storage,
@ -275,9 +287,41 @@ fn write_backups_list(
" dest: {dest:<dest_width$}",
dest = dest.display()
)?;
writeln!(writer, " {note}", note = cmd_note,)?;
writeln!(
writer,
" {cmd_name:<cmd_name_width$}({note})",
note = cmd_note,
)?;
} else {
}
}
Ok(())
}
pub fn cmd_backup_done(
name: String,
exit_status: u64,
log: Option<String>,
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(())
}

View file

@ -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,
);

View file

@ -120,7 +120,7 @@ fn main() -> Result<()> {
name,
exit_status,
log,
} => todo!(),
} => cmd_backup::cmd_backup_done(name, exit_status, log, repo, &config_dir)?,
}
}
}

View file

@ -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()),

View file

@ -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<PathBuf> {
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())
}
}