mirror of
https://github.com/qwjyh/xdbm
synced 2024-11-24 15:41:05 +09:00
add backup done
This commit is contained in:
parent
4f0d725b52
commit
b1b174b4b3
7 changed files with 117 additions and 12 deletions
|
@ -20,8 +20,8 @@
|
||||||
- [ ] backup add
|
- [ ] backup add
|
||||||
- [ ] test for backup add
|
- [ ] test for backup add
|
||||||
- [ ] backup list
|
- [ ] backup list
|
||||||
- [ ] status printing
|
- [x] status printing
|
||||||
- [ ] backup done
|
- [x] backup done
|
||||||
- [ ] fancy display
|
- [ ] fancy display
|
||||||
- [ ] no commit option
|
- [ ] no commit option
|
||||||
|
|
||||||
|
|
|
@ -101,11 +101,23 @@ impl BackupCommandExt for ExternallyInvoked {
|
||||||
/// Backup execution log.
|
/// Backup execution log.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct BackupLog {
|
pub struct BackupLog {
|
||||||
datetime: DateTime<Local>,
|
pub datetime: DateTime<Local>,
|
||||||
status: BackupResult,
|
status: BackupResult,
|
||||||
log: String,
|
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.
|
/// Result of backup.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub enum BackupResult {
|
pub enum BackupResult {
|
||||||
|
@ -113,6 +125,16 @@ pub enum BackupResult {
|
||||||
Failure,
|
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.
|
/// Backup source, destination, command and logs.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Backup {
|
pub struct Backup {
|
||||||
|
@ -164,6 +186,15 @@ impl Backup {
|
||||||
pub fn command(&self) -> &BackupCommand {
|
pub fn command(&self) -> &BackupCommand {
|
||||||
&self.command
|
&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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -183,6 +214,10 @@ impl Backups {
|
||||||
self.list.get(name)
|
self.list.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, name: &String) -> Option<&mut Backup> {
|
||||||
|
self.list.get_mut(name)
|
||||||
|
}
|
||||||
|
|
||||||
/// Add new [`Backup`].
|
/// Add new [`Backup`].
|
||||||
/// New `backup` must has new unique name.
|
/// New `backup` must has new unique name.
|
||||||
pub fn add(&mut self, backup: Backup) -> Result<()> {
|
pub fn add(&mut self, backup: Backup) -> Result<()> {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{self, stdout, Write},
|
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 dunce::canonicalize;
|
||||||
use git2::Repository;
|
use git2::Repository;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
@ -235,6 +236,7 @@ fn write_backups_list(
|
||||||
let mut src_storage_width = 0;
|
let mut src_storage_width = 0;
|
||||||
let mut dest_width = 0;
|
let mut dest_width = 0;
|
||||||
let mut dest_storage_width = 0;
|
let mut dest_storage_width = 0;
|
||||||
|
let mut cmd_name_width = 0;
|
||||||
// get widths
|
// get widths
|
||||||
for ((dev, _name), backup) in &backups {
|
for ((dev, _name), backup) in &backups {
|
||||||
let device = backup.device(devices).context(format!(
|
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_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();
|
||||||
|
cmd_name_width = cmd_name_width.max(cmd_name.width());
|
||||||
}
|
}
|
||||||
// main printing
|
// main printing
|
||||||
for ((dev, _name), backup) in &backups {
|
for ((dev, _name), backup) in &backups {
|
||||||
|
@ -260,9 +263,18 @@ fn write_backups_list(
|
||||||
let src = backup.source().path(&storages, &device)?;
|
let src = backup.source().path(&storages, &device)?;
|
||||||
let dest = backup.destination().path(&storages, &device)?;
|
let dest = backup.destination().path(&storages, &device)?;
|
||||||
let cmd_name = backup.command().name();
|
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!(
|
writeln!(
|
||||||
writer,
|
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(),
|
name = backup.name(),
|
||||||
src = backup.source().storage,
|
src = backup.source().storage,
|
||||||
dest = backup.destination().storage,
|
dest = backup.destination().storage,
|
||||||
|
@ -275,9 +287,41 @@ fn write_backups_list(
|
||||||
" dest: {dest:<dest_width$}",
|
" dest: {dest:<dest_width$}",
|
||||||
dest = dest.display()
|
dest = dest.display()
|
||||||
)?;
|
)?;
|
||||||
writeln!(writer, " {note}", note = cmd_note,)?;
|
writeln!(
|
||||||
|
writer,
|
||||||
|
" {cmd_name:<cmd_name_width$}({note})",
|
||||||
|
note = cmd_note,
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub(crate) fn cmd_storage_add(
|
||||||
} else {
|
} else {
|
||||||
manually_construct_physical_drive_partition(
|
manually_construct_physical_drive_partition(
|
||||||
name,
|
name,
|
||||||
canonicalize(util::expand_tilde(path.unwrap()))?,
|
canonicalize(util::expand_tilde(path.unwrap())?)?,
|
||||||
&device,
|
&device,
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
@ -77,7 +77,7 @@ pub(crate) fn cmd_storage_add(
|
||||||
trace!("SubDirectory arguments: path: {:?}", path);
|
trace!("SubDirectory arguments: path: {:?}", path);
|
||||||
// Nightly feature std::path::absolute
|
// Nightly feature std::path::absolute
|
||||||
trace!("Canonicalize path: {:?}", path);
|
trace!("Canonicalize path: {:?}", path);
|
||||||
let path = canonicalize(util::expand_tilde(path))?;
|
let path = canonicalize(util::expand_tilde(path)?)?;
|
||||||
trace!("canonicalized: path: {:?}", path);
|
trace!("canonicalized: path: {:?}", path);
|
||||||
|
|
||||||
let storage = directory::Directory::try_from_device_path(
|
let storage = directory::Directory::try_from_device_path(
|
||||||
|
@ -99,7 +99,7 @@ pub(crate) fn cmd_storage_add(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
trace!("Canonicalize path: {:?}", path);
|
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(
|
let storage = storages::online_storage::OnlineStorage::new(
|
||||||
name, provider, capacity, alias, path, &device,
|
name, provider, capacity, alias, path, &device,
|
||||||
);
|
);
|
||||||
|
|
|
@ -120,7 +120,7 @@ fn main() -> Result<()> {
|
||||||
name,
|
name,
|
||||||
exit_status,
|
exit_status,
|
||||||
log,
|
log,
|
||||||
} => todo!(),
|
} => cmd_backup::cmd_backup_done(name, exit_status, log, repo, &config_dir)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl PhysicalDrivePartition {
|
||||||
let fs = disk.file_system();
|
let fs = disk.file_system();
|
||||||
trace!("fs: {:?}", fs);
|
trace!("fs: {:?}", fs);
|
||||||
let fs = std::str::from_utf8(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 {
|
Ok(PhysicalDrivePartition {
|
||||||
name: name,
|
name: name,
|
||||||
kind: format!("{:?}", disk.kind()),
|
kind: format!("{:?}", disk.kind()),
|
||||||
|
|
26
src/util.rs
26
src/util.rs
|
@ -1,5 +1,8 @@
|
||||||
use std::path::{self, PathBuf};
|
use std::path::{self, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use chrono::format;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
devices::Device,
|
devices::Device,
|
||||||
storages::{Storage, StorageExt, Storages},
|
storages::{Storage, StorageExt, Storages},
|
||||||
|
@ -35,3 +38,26 @@ pub fn min_parent_storage<'a>(
|
||||||
let storage = storages.get(name)?;
|
let storage = storages.get(name)?;
|
||||||
Some((storage, pathdiff))
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue