mirror of
https://github.com/qwjyh/xdbm
synced 2024-11-22 06:40:12 +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
|
- [x] use subcommand
|
||||||
- [ ] backup subcommands
|
- [ ] backup subcommands
|
||||||
- [ ] backup add
|
- [ ] backup add
|
||||||
- [?] test for backup add
|
- [ ] test for backup add
|
||||||
- [ ] backup list
|
- [ ] backup list
|
||||||
|
- [ ] status printing
|
||||||
- [ ] backup done
|
- [ ] backup done
|
||||||
|
- [ ] fancy display
|
||||||
- [ ] no commit option
|
- [ ] no commit option
|
||||||
|
|
||||||
<!-- vim: set sw=2 ts=2: -->
|
<!-- vim: set sw=2 ts=2: -->
|
||||||
|
|
|
@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
devices::Device,
|
devices::Device,
|
||||||
storages::{self, Storage},
|
storages::{self, Storage, StorageExt, Storages},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Directory to store backup configs for each devices.
|
/// Directory to store backup configs for each devices.
|
||||||
|
@ -28,9 +28,9 @@ pub fn backups_file(device: &Device) -> PathBuf {
|
||||||
pub struct BackupTarget {
|
pub struct BackupTarget {
|
||||||
/// `name()` of [`Storage`].
|
/// `name()` of [`Storage`].
|
||||||
/// Use `String` for serialization/deserialization.
|
/// Use `String` for serialization/deserialization.
|
||||||
storage: String,
|
pub storage: String,
|
||||||
/// Relative path to the `storage`.
|
/// Relative path to the `storage`.
|
||||||
path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackupTarget {
|
impl BackupTarget {
|
||||||
|
@ -40,6 +40,12 @@ impl BackupTarget {
|
||||||
path: relative_path,
|
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.
|
/// Type of backup commands.
|
||||||
|
@ -48,6 +54,26 @@ pub enum BackupCommand {
|
||||||
ExternallyInvoked(ExternallyInvoked),
|
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.
|
/// 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, 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.
|
/// Backup execution log.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct BackupLog {
|
pub struct BackupLog {
|
||||||
|
@ -112,6 +148,22 @@ impl Backup {
|
||||||
pub fn name(&self) -> &String {
|
pub fn name(&self) -> &String {
|
||||||
&self.name
|
&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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
|
|
@ -154,7 +154,17 @@ pub(crate) enum BackupSubCommands {
|
||||||
cmd: BackupAddCommands,
|
cmd: BackupAddCommands,
|
||||||
},
|
},
|
||||||
/// Print configured backups.
|
/// 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.
|
/// Record xdbm that the backup with the name has finished right now.
|
||||||
Done {
|
Done {
|
||||||
/// Name of the backup config.
|
/// 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 git2::Repository;
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
add_and_commit,
|
add_and_commit,
|
||||||
backups::{self, Backup, BackupCommand, BackupTarget, Backups, ExternallyInvoked},
|
backups::{
|
||||||
|
self, Backup, BackupCommand, BackupCommandExt, BackupTarget, Backups, ExternallyInvoked,
|
||||||
|
},
|
||||||
cmd_args::BackupAddCommands,
|
cmd_args::BackupAddCommands,
|
||||||
devices::{self, Device},
|
devices::{self, Device},
|
||||||
storages::{StorageExt, Storages},
|
storages::{StorageExt, Storages},
|
||||||
|
@ -79,3 +86,132 @@ fn new_backup(
|
||||||
command,
|
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 std::io::ErrorKind;
|
||||||
use inquire::{autocompletion::{Autocomplete, Replacement}, CustomUserError};
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct FilePathCompleter {
|
pub struct FilePathCompleter {
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -107,8 +107,15 @@ fn main() -> Result<()> {
|
||||||
src,
|
src,
|
||||||
dest,
|
dest,
|
||||||
cmd,
|
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 {
|
BackupSubCommands::Done {
|
||||||
name,
|
name,
|
||||||
exit_status,
|
exit_status,
|
||||||
|
|
Loading…
Reference in a new issue