From 3c77efb1810ebb7b8f42caba91e991c4fd6df88b Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Sat, 3 Sep 2022 04:09:13 +0200 Subject: [PATCH] backup: Allow multiple sources --- src/backend/ignore.rs | 2 +- src/commands/backup.rs | 275 +++++++++++++++++++++-------------------- 2 files changed, 145 insertions(+), 132 deletions(-) diff --git a/src/backend/ignore.rs b/src/backend/ignore.rs index ee5c162..5d7b0df 100644 --- a/src/backend/ignore.rs +++ b/src/backend/ignore.rs @@ -18,7 +18,7 @@ pub struct LocalSource { cache: UsersCache, } -#[derive(Parser)] +#[derive(Clone, Parser)] pub struct LocalSourceOptions { /// Save access time for files and directories #[clap(long)] diff --git a/src/commands/backup.rs b/src/commands/backup.rs index d8d504f..83dbcb5 100644 --- a/src/commands/backup.rs +++ b/src/commands/backup.rs @@ -60,7 +60,8 @@ pub(super) struct Opts { ignore_opts: LocalSourceOptions, /// Backup source, use - for stdin - source: String, + #[clap(value_name = "SOURCE")] + sources: Vec, } pub(super) async fn execute( @@ -74,146 +75,158 @@ pub(super) async fn execute( let mut be = DryRunBackend::new(be.clone(), opts.dry_run); be.set_zstd(zstd); - let backup_stdin = opts.source == "-"; - let backup_path = if backup_stdin { - PathBuf::from(&opts.stdin_filename) - } else { - PathBuf::from(&opts.source).absolutize()?.to_path_buf() - }; - let backup_path_str = backup_path - .to_str() - .ok_or_else(|| anyhow!("non-unicode path {:?}", backup_path))? - .to_string(); + if opts.sources.is_empty() { + v1!("no backup source given."); + return Ok(()); + } - let hostname = gethostname(); - let hostname = hostname - .to_str() - .ok_or_else(|| anyhow!("non-unicode hostname {:?}", hostname))? - .to_string(); + let index = IndexBackend::only_full_trees(&be.clone(), progress_counter()).await?; - let parent = match (backup_stdin, opts.force, opts.parent) { - (true, _, _) | (false, true, _) => None, - (false, false, None) => SnapshotFile::latest( - &be, - |snap| snap.hostname == hostname && snap.paths.contains(&backup_path_str), - progress_counter(), - ) - .await - .ok(), - (false, false, Some(parent)) => SnapshotFile::from_id(&be, &parent).await.ok(), - }; - - let parent_tree = match &parent { - Some(snap) => { - v1!("using parent {}", snap.id); - Some(snap.tree) - } - None => { - v1!("using no parent"); - None - } - }; - - let delete = match (opts.delete_never, opts.delete_after) { - (true, _) => DeleteOption::Never, - (_, Some(d)) => DeleteOption::After(time + Duration::from_std(*d)?), - (false, None) => DeleteOption::NotSet, - }; - - let mut snap = SnapshotFile { - time, - parent: parent.map(|sn| sn.id), - hostname, - delete, - summary: Some(SnapshotSummary { - command, - ..Default::default() - }), - ..Default::default() - }; - snap.paths.add(backup_path_str.clone()); - snap.set_tags(opts.tag); - - let index = IndexBackend::only_full_trees(&be, progress_counter()).await?; - - let parent = Parent::new(&index, parent_tree, opts.ignore_ctime, opts.ignore_inode).await; - - let snap = if backup_stdin { - v1!("starting backup from stdin..."); - let mut archiver = Archiver::new(be, index, &config, parent, snap)?; - let p = progress_bytes(); - archiver - .backup_reader( - std::io::stdin(), - Node::new( - backup_path_str, - NodeType::File, - Metadata::default(), - None, - None, - ), - p, - ) - .await?; - - archiver.finalize_snapshot().await? - } else { - let src = LocalSource::new(opts.ignore_opts, backup_path)?; - - let size = if get_verbosity_level() == 1 { - v1!("determining size of backup source..."); - src.size()? + for source in opts.sources { + v1!("\nbacking up {source}..."); + let be = be.clone(); + let index = index.clone(); + let backup_stdin = source == "-"; + let backup_path = if backup_stdin { + PathBuf::from(&opts.stdin_filename) } else { - 0 + PathBuf::from(&source).absolutize()?.to_path_buf() }; - v1!("starting backup..."); - let mut archiver = Archiver::new(be, index, &config, parent, snap)?; - let p = progress_bytes(); - p.set_length(size); - for item in src { - match item { - Err(e) => { - eprintln!("ignoring error {}\n", e) - } - Ok((path, node)) => { - if let Err(e) = archiver.add_entry(&path, node, p.clone()).await { - eprintln!("ignoring error {} for {:?}\n", e, path); + let backup_path_str = backup_path + .to_str() + .ok_or_else(|| anyhow!("non-unicode path {:?}", backup_path))? + .to_string(); + + let hostname = gethostname(); + let hostname = hostname + .to_str() + .ok_or_else(|| anyhow!("non-unicode hostname {:?}", hostname))? + .to_string(); + + let parent = match (backup_stdin, opts.force, opts.parent.clone()) { + (true, _, _) | (false, true, _) => None, + (false, false, None) => SnapshotFile::latest( + &be, + |snap| snap.hostname == hostname && snap.paths.contains(&backup_path_str), + progress_counter(), + ) + .await + .ok(), + (false, false, Some(parent)) => SnapshotFile::from_id(&be, &parent).await.ok(), + }; + + let parent_tree = match &parent { + Some(snap) => { + v1!("using parent {}", snap.id); + Some(snap.tree) + } + None => { + v1!("using no parent"); + None + } + }; + + let delete = match (opts.delete_never, opts.delete_after) { + (true, _) => DeleteOption::Never, + (_, Some(d)) => DeleteOption::After(time + Duration::from_std(*d)?), + (false, None) => DeleteOption::NotSet, + }; + + let mut snap = SnapshotFile { + time, + parent: parent.map(|sn| sn.id), + hostname, + delete, + summary: Some(SnapshotSummary { + command: command.clone(), + ..Default::default() + }), + ..Default::default() + }; + snap.paths.add(backup_path_str.clone()); + snap.set_tags(opts.tag.clone()); + + let parent = Parent::new(&index, parent_tree, opts.ignore_ctime, opts.ignore_inode).await; + + let snap = if backup_stdin { + v1!("starting backup from stdin..."); + let mut archiver = Archiver::new(be, index, &config, parent, snap)?; + let p = progress_bytes(); + archiver + .backup_reader( + std::io::stdin(), + Node::new( + backup_path_str, + NodeType::File, + Metadata::default(), + None, + None, + ), + p, + ) + .await?; + + archiver.finalize_snapshot().await? + } else { + let src = LocalSource::new(opts.ignore_opts.clone(), backup_path)?; + + let size = if get_verbosity_level() == 1 { + v1!("determining size of backup source..."); + src.size()? + } else { + 0 + }; + v1!("starting backup..."); + let mut archiver = Archiver::new(be, index.clone(), &config, parent, snap)?; + let p = progress_bytes(); + p.set_length(size); + for item in src { + match item { + Err(e) => { + eprintln!("ignoring error {}\n", e) + } + Ok((path, node)) => { + if let Err(e) = archiver.add_entry(&path, node, p.clone()).await { + eprintln!("ignoring error {} for {:?}\n", e, path); + } } } } - } - p.finish_with_message("done"); - archiver.finalize_snapshot().await? - }; + p.finish_with_message("done"); + archiver.finalize_snapshot().await? + }; - let summary = snap.summary.unwrap(); + let summary = snap.summary.unwrap(); - v1!( - "Files: {} new, {} changed, {} unchanged", - summary.files_new, - summary.files_changed, - summary.files_unmodified - ); - v1!( - "Dirs: {} new, {} changed, {} unchanged", - summary.dirs_new, - summary.dirs_changed, - summary.dirs_unmodified - ); - v2!("Data Blobs: {} new", summary.data_blobs); - v2!("Tree Blobs: {} new", summary.tree_blobs); - v1!( - "Added to the repo: {} (raw: {})", - bytes(summary.data_added_packed), - bytes(summary.data_added) - ); + v1!( + "Files: {} new, {} changed, {} unchanged", + summary.files_new, + summary.files_changed, + summary.files_unmodified + ); + v1!( + "Dirs: {} new, {} changed, {} unchanged", + summary.dirs_new, + summary.dirs_changed, + summary.dirs_unmodified + ); + v2!("Data Blobs: {} new", summary.data_blobs); + v2!("Tree Blobs: {} new", summary.tree_blobs); + v1!( + "Added to the repo: {} (raw: {})", + bytes(summary.data_added_packed), + bytes(summary.data_added) + ); - v1!( - "processed {} files, {}", - summary.total_files_processed, - bytes(summary.total_bytes_processed) - ); - v1!("snapshot {} successfully saved.", snap.id); + v1!( + "processed {} files, {}", + summary.total_files_processed, + bytes(summary.total_bytes_processed) + ); + v1!("snapshot {} successfully saved.", snap.id); + + v1!("backup of {source} done."); + } Ok(()) }