mirror of
https://github.com/qwjyh/xdbm
synced 2024-11-24 23:51:04 +09:00
new subcommand: backup list
- todo: fancy print
This commit is contained in:
parent
d947dd35e0
commit
ff32996360
6 changed files with 221 additions and 11 deletions
|
@ -18,9 +18,11 @@
|
|||
- [x] use subcommand
|
||||
- [ ] backup subcommands
|
||||
- [ ] backup add
|
||||
- [?] test for backup add
|
||||
- [ ] test for backup add
|
||||
- [ ] backup list
|
||||
- [ ] status printing
|
||||
- [ ] backup done
|
||||
- [ ] fancy display
|
||||
- [ ] no commit option
|
||||
|
||||
<!-- vim: set sw=2 ts=2: -->
|
||||
|
|
|
@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::{
|
||||
devices::Device,
|
||||
storages::{self, Storage},
|
||||
storages::{self, Storage, StorageExt, Storages},
|
||||
};
|
||||
|
||||
/// Directory to store backup configs for each devices.
|
||||
|
@ -28,9 +28,9 @@ pub fn backups_file(device: &Device) -> PathBuf {
|
|||
pub struct BackupTarget {
|
||||
/// `name()` of [`Storage`].
|
||||
/// Use `String` for serialization/deserialization.
|
||||
storage: String,
|
||||
pub storage: String,
|
||||
/// Relative path to the `storage`.
|
||||
path: PathBuf,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl BackupTarget {
|
||||
|
@ -40,6 +40,12 @@ impl BackupTarget {
|
|||
path: relative_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self, storages: &Storages, device: &Device) -> Result<PathBuf> {
|
||||
let parent = storages.get(&self.storage).unwrap();
|
||||
let parent_path = parent.mount_path(device, storages)?;
|
||||
Ok(parent_path.join(self.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of backup commands.
|
||||
|
@ -48,6 +54,26 @@ pub enum BackupCommand {
|
|||
ExternallyInvoked(ExternallyInvoked),
|
||||
}
|
||||
|
||||
pub trait BackupCommandExt {
|
||||
fn name(&self) -> &String;
|
||||
|
||||
fn note(&self) -> &String;
|
||||
}
|
||||
|
||||
impl BackupCommandExt for BackupCommand {
|
||||
fn name(&self) -> &String {
|
||||
match self {
|
||||
BackupCommand::ExternallyInvoked(cmd) => cmd.name(),
|
||||
}
|
||||
}
|
||||
|
||||
fn note(&self) -> &String {
|
||||
match self {
|
||||
BackupCommand::ExternallyInvoked(cmd) => cmd.note(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Backup commands which is not invoked from xdbm itself.
|
||||
/// Call xdbm externally to record backup datetime and status.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -62,6 +88,16 @@ impl ExternallyInvoked {
|
|||
}
|
||||
}
|
||||
|
||||
impl BackupCommandExt for ExternallyInvoked {
|
||||
fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn note(&self) -> &String {
|
||||
&self.note
|
||||
}
|
||||
}
|
||||
|
||||
/// Backup execution log.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BackupLog {
|
||||
|
@ -112,6 +148,22 @@ impl Backup {
|
|||
pub fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn device<'a>(&'a self, devices: &'a Vec<Device>) -> Option<&Device> {
|
||||
devices.iter().find(|dev| dev.name() == self.device)
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &BackupTarget {
|
||||
&self.from
|
||||
}
|
||||
|
||||
pub fn destination(&self) -> &BackupTarget {
|
||||
&self.to
|
||||
}
|
||||
|
||||
pub fn command(&self) -> &BackupCommand {
|
||||
&self.command
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
|
|
@ -154,7 +154,17 @@ pub(crate) enum BackupSubCommands {
|
|||
cmd: BackupAddCommands,
|
||||
},
|
||||
/// Print configured backups.
|
||||
List {},
|
||||
/// Filter by src/dest storage or device.
|
||||
List {
|
||||
#[arg(long)]
|
||||
src: Option<String>,
|
||||
#[arg(long)]
|
||||
dest: Option<String>,
|
||||
#[arg(long)]
|
||||
device: Option<String>,
|
||||
#[arg(short, long)]
|
||||
long: bool,
|
||||
},
|
||||
/// Record xdbm that the backup with the name has finished right now.
|
||||
Done {
|
||||
/// Name of the backup config.
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
use std::{io::stdout, path::{Path, PathBuf}};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, stdout, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use git2::Repository;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
add_and_commit,
|
||||
backups::{self, Backup, BackupCommand, BackupTarget, Backups, ExternallyInvoked},
|
||||
backups::{
|
||||
self, Backup, BackupCommand, BackupCommandExt, BackupTarget, Backups, ExternallyInvoked,
|
||||
},
|
||||
cmd_args::BackupAddCommands,
|
||||
devices::{self, Device},
|
||||
storages::{StorageExt, Storages},
|
||||
|
@ -79,3 +86,132 @@ fn new_backup(
|
|||
command,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use anyhow::Result;
|
||||
#[test]
|
||||
fn test_new_backup() -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmd_backup_list(
|
||||
src_storage: Option<String>,
|
||||
dest_storage: Option<String>,
|
||||
device_name: Option<String>,
|
||||
longprint: bool,
|
||||
config_dir: &PathBuf,
|
||||
storages: &Storages,
|
||||
) -> Result<()> {
|
||||
let devices = devices::get_devices(&config_dir)?;
|
||||
let backups: HashMap<(String, String), Backup> = match device_name {
|
||||
Some(device_name) => {
|
||||
let device = devices
|
||||
.iter()
|
||||
.find(|dev| dev.name() == device_name)
|
||||
.context(format!("Device with name {} doesn't exist", device_name))?;
|
||||
let backups = Backups::read(&config_dir, device)?;
|
||||
let mut allbackups = HashMap::new();
|
||||
for (name, backup) in backups.list {
|
||||
if allbackups.insert((device.name(), name), backup).is_some() {
|
||||
return Err(anyhow!("unexpected duplication in backups hashmap"));
|
||||
};
|
||||
}
|
||||
allbackups
|
||||
}
|
||||
None => {
|
||||
let mut allbackups = HashMap::new();
|
||||
for device in &devices {
|
||||
let backups = Backups::read(&config_dir, &device)?;
|
||||
for (name, backup) in backups.list {
|
||||
if allbackups.insert((device.name(), name), backup).is_some() {
|
||||
return Err(anyhow!("unexpected duplication in backups hashmap"));
|
||||
};
|
||||
}
|
||||
}
|
||||
allbackups
|
||||
}
|
||||
};
|
||||
// source/destination filtering
|
||||
let backups: HashMap<(String, String), Backup> = backups
|
||||
.into_iter()
|
||||
.filter(|((_dev, _name), backup)| {
|
||||
let src_matched = match &src_storage {
|
||||
Some(src_storage) => &backup.source().storage == src_storage,
|
||||
None => true,
|
||||
};
|
||||
let dest_matched = match &dest_storage {
|
||||
Some(dest_storage) => &backup.destination().storage == dest_storage,
|
||||
None => true,
|
||||
};
|
||||
src_matched && dest_matched
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut stdout = io::BufWriter::new(io::stdout());
|
||||
write_backups_list(&mut stdout, backups, longprint, storages, &devices)?;
|
||||
stdout.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// TODO: status printing
|
||||
fn write_backups_list(
|
||||
mut writer: impl io::Write,
|
||||
backups: HashMap<(String, String), Backup>,
|
||||
longprint: bool,
|
||||
storages: &Storages,
|
||||
devices: &Vec<Device>,
|
||||
) -> Result<()> {
|
||||
let mut name_width = 0;
|
||||
let mut dev_width = 0;
|
||||
let mut src_width = 0;
|
||||
let mut src_storage_width = 0;
|
||||
let mut dest_width = 0;
|
||||
let mut dest_storage_width = 0;
|
||||
// get widths
|
||||
for ((dev, _name), backup) in &backups {
|
||||
let device = backup.device(devices).context(format!(
|
||||
"Couldn't find device specified in backup config {}",
|
||||
backup.name()
|
||||
))?;
|
||||
name_width = name_width.max(backup.name().width());
|
||||
dev_width = dev_width.max(dev.width());
|
||||
let src = backup.source().path(&storages, &device)?;
|
||||
src_width = src_width.max(format!("{}", src.display()).width());
|
||||
src_storage_width = src_storage_width.max(backup.source().storage.width());
|
||||
let dest = backup.destination().path(&storages, &device)?;
|
||||
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();
|
||||
}
|
||||
// main printing
|
||||
for ((dev, _name), backup) in &backups {
|
||||
let device = backup.device(devices).context(format!(
|
||||
"Couldn't find device specified in backup config {}",
|
||||
backup.name()
|
||||
))?;
|
||||
let src = backup.source().path(&storages, &device)?;
|
||||
let dest = backup.destination().path(&storages, &device)?;
|
||||
let cmd_name = backup.command().name();
|
||||
writeln!(
|
||||
writer,
|
||||
"{name:<name_width$} [{dev:<dev_width$}] {src:<src_storage_width$} → {dest:<dest_storage_width$} {cmd_name}",
|
||||
name = backup.name(),
|
||||
src = backup.source().storage,
|
||||
dest = backup.destination().storage,
|
||||
)?;
|
||||
if longprint {
|
||||
let cmd_note = backup.command().note();
|
||||
writeln!(writer, " src : {src:<src_width$}", src = src.display())?;
|
||||
writeln!(
|
||||
writer,
|
||||
" dest: {dest:<dest_width$}",
|
||||
dest = dest.display()
|
||||
)?;
|
||||
writeln!(writer, " {note}", note = cmd_note,)?;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use inquire::{
|
||||
autocompletion::{Autocomplete, Replacement},
|
||||
CustomUserError,
|
||||
};
|
||||
use std::io::ErrorKind;
|
||||
use inquire::{autocompletion::{Autocomplete, Replacement}, CustomUserError};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FilePathCompleter {
|
||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -107,8 +107,15 @@ fn main() -> Result<()> {
|
|||
src,
|
||||
dest,
|
||||
cmd,
|
||||
} => cmd_backup::cmd_backup_add(name, src, dest, cmd, repo, &config_dir, &storages)?,
|
||||
BackupSubCommands::List {} => todo!(),
|
||||
} => {
|
||||
cmd_backup::cmd_backup_add(name, src, dest, cmd, repo, &config_dir, &storages)?
|
||||
}
|
||||
BackupSubCommands::List {
|
||||
src,
|
||||
dest,
|
||||
device,
|
||||
long,
|
||||
} => cmd_backup::cmd_backup_list(src, dest, device, long, &config_dir, &storages)?,
|
||||
BackupSubCommands::Done {
|
||||
name,
|
||||
exit_status,
|
||||
|
|
Loading…
Reference in a new issue