summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-02-06 19:22:10 +0200
committerLars Wirzenius <liw@liw.fi>2021-02-06 20:00:04 +0200
commit7cfa4142bc1859f9084a35e7e7fd5f67d3a655a3 (patch)
tree1eabf91078c2be52b719b78110af1630a3fbf375 /src
parentaa75f0d5709fb4062900db5ab5b6e2598b6af667 (diff)
downloadobnam2-7cfa4142bc1859f9084a35e7e7fd5f67d3a655a3.tar.gz
feat! back up multiple roots
This changes the client configuration file "root" field (with a single string) to "roots" (a list of strings).
Diffstat (limited to 'src')
-rw-r--r--src/client.rs10
-rw-r--r--src/cmd/backup.rs70
2 files changed, 52 insertions, 28 deletions
diff --git a/src/client.rs b/src/client.rs
index 7a4ce21..e4d9be8 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -25,7 +25,7 @@ struct TentativeClientConfig {
server_url: String,
verify_tls_cert: Option<bool>,
chunk_size: Option<usize>,
- root: PathBuf,
+ roots: Vec<PathBuf>,
log: Option<PathBuf>,
}
@@ -34,7 +34,7 @@ pub struct ClientConfig {
pub server_url: String,
pub verify_tls_cert: bool,
pub chunk_size: usize,
- pub root: PathBuf,
+ pub roots: Vec<PathBuf>,
pub log: PathBuf,
}
@@ -43,7 +43,7 @@ pub enum ClientConfigError {
#[error("server_url is empty")]
ServerUrlIsEmpty,
- #[error("backup root is unset or empty")]
+ #[error("No backup roots in config; at least one is needed")]
NoBackupRoot,
#[error("server URL doesn't use https: {0}")]
@@ -66,7 +66,7 @@ impl ClientConfig {
let config = ClientConfig {
server_url: tentative.server_url,
- root: tentative.root,
+ roots: tentative.roots,
verify_tls_cert: tentative.verify_tls_cert.or(Some(false)).unwrap(),
chunk_size: tentative.chunk_size.or(Some(DEFAULT_CHUNK_SIZE)).unwrap(),
log: tentative.log.or(Some(PathBuf::from(DEVNULL))).unwrap(),
@@ -83,7 +83,7 @@ impl ClientConfig {
if !self.server_url.starts_with("https://") {
return Err(ClientConfigError::NotHttps(self.server_url.to_string()));
}
- if self.root.to_string_lossy().is_empty() {
+ if self.roots.is_empty() {
return Err(ClientConfigError::NoBackupRoot);
}
Ok(())
diff --git a/src/cmd/backup.rs b/src/cmd/backup.rs
index fd1d876..cb2e9af 100644
--- a/src/cmd/backup.rs
+++ b/src/cmd/backup.rs
@@ -1,9 +1,11 @@
use crate::backup_run::BackupRun;
+use crate::chunkid::ChunkId;
use crate::client::ClientConfig;
use crate::error::ObnamError;
use crate::fsiter::FsIterator;
use crate::generation::NascentGeneration;
use log::info;
+use std::path::{Path, PathBuf};
use std::time::SystemTime;
use tempfile::NamedTempFile;
@@ -31,40 +33,62 @@ pub fn backup(config: &ClientConfig) -> Result<(), ObnamError> {
};
let genlist = run.client().list_generations()?;
- let file_count = {
- let iter = FsIterator::new(&config.root);
- let mut new = NascentGeneration::create(&newname)?;
-
- match genlist.resolve("latest") {
- Err(_) => {
- info!("fresh backup without a previous generation");
- new.insert_iter(iter.map(|entry| run.backup_file_initially(entry)))?;
- }
- Ok(old) => {
- info!("incremental backup based on {}", old);
- let old = run.client().fetch_generation(&old, &oldname)?;
- run.progress()
- .files_in_previous_generation(old.file_count()? as u64);
- new.insert_iter(iter.map(|entry| run.backup_file_incrementally(entry, &old)))?;
- }
- }
- run.progress().finish();
- new.file_count()
+ let file_count = match genlist.resolve("latest") {
+ Err(_) => initial_backup(&config.roots, &newname, &run)?,
+ Ok(old) => incremental_backup(&old, &config.roots, &newname, &oldname, &run)?,
};
+ run.progress().finish();
// Upload the SQLite file, i.e., the named temporary file, which
// still exists, since we persisted it above.
let gen_id = run
.client()
.upload_generation(&newname, SQLITE_CHUNK_SIZE)?;
- println!("status: OK");
- println!("duration: {}", runtime.elapsed()?.as_secs());
- println!("file-count: {}", file_count);
- println!("generation-id: {}", gen_id);
// Delete the temporary file.q
std::fs::remove_file(&newname)?;
std::fs::remove_file(&oldname)?;
+ report_stats(&runtime, file_count, &gen_id)?;
+
Ok(())
}
+
+fn report_stats(runtime: &SystemTime, file_count: i64, gen_id: &ChunkId) -> Result<(), ObnamError> {
+ println!("status: OK");
+ println!("duration: {}", runtime.elapsed()?.as_secs());
+ println!("file-count: {}", file_count);
+ println!("generation-id: {}", gen_id);
+ Ok(())
+}
+
+fn initial_backup(roots: &[PathBuf], newname: &Path, run: &BackupRun) -> Result<i64, ObnamError> {
+ info!("fresh backup without a previous generation");
+
+ let mut new = NascentGeneration::create(&newname)?;
+ for root in roots {
+ let iter = FsIterator::new(root);
+ new.insert_iter(iter.map(|entry| run.backup_file_initially(entry)))?;
+ }
+ Ok(new.file_count())
+}
+
+fn incremental_backup(
+ old: &str,
+ roots: &[PathBuf],
+ newname: &Path,
+ oldname: &Path,
+ run: &BackupRun,
+) -> Result<i64, ObnamError> {
+ info!("incremental backup based on {}", old);
+
+ let old = run.client().fetch_generation(&old, &oldname)?;
+ let mut new = NascentGeneration::create(&newname)?;
+ for root in roots {
+ let iter = FsIterator::new(root);
+ run.progress()
+ .files_in_previous_generation(old.file_count()? as u64);
+ new.insert_iter(iter.map(|entry| run.backup_file_incrementally(entry, &old)))?;
+ }
+ Ok(new.file_count())
+}