summaryrefslogtreecommitdiff
path: root/src/cmd/backup.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/backup.rs')
-rw-r--r--src/cmd/backup.rs178
1 files changed, 125 insertions, 53 deletions
diff --git a/src/cmd/backup.rs b/src/cmd/backup.rs
index da7298f..70e9eac 100644
--- a/src/cmd/backup.rs
+++ b/src/cmd/backup.rs
@@ -1,65 +1,137 @@
-use crate::backup_run::BackupRun;
-use crate::client::ClientConfig;
-use crate::fsiter::FsIterator;
-use crate::generation::NascentGeneration;
+//! The `backup` subcommand.
+
+use crate::backup_run::{current_timestamp, BackupRun};
+use crate::chunk::ClientTrust;
+use crate::client::BackupClient;
+use crate::config::ClientConfig;
+use crate::dbgen::{schema_version, FileId, DEFAULT_SCHEMA_MAJOR};
+use crate::error::ObnamError;
+use crate::generation::GenId;
+use crate::performance::{Clock, Performance};
+use crate::schema::VersionComponent;
+
+use clap::Parser;
use log::info;
use std::time::SystemTime;
-use tempfile::NamedTempFile;
-
-pub fn backup(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<()> {
- let runtime = SystemTime::now();
-
- let run = BackupRun::new(config, buffer_size)?;
-
- // Create a named temporary file. We don't meed the open file
- // handle, so we discard that.
- let oldname = {
- let temp = NamedTempFile::new()?;
- let (_, dbname) = temp.keep()?;
- dbname
- };
-
- // Create a named temporary file. We don't meed the open file
- // handle, so we discard that.
- let newname = {
- let temp = NamedTempFile::new()?;
- let (_, dbname) = temp.keep()?;
- dbname
- };
-
- 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") {
- None => {
- info!("fresh backup without a previous generation");
- new.insert_iter(iter.map(|entry| run.backup_file_initially(entry)))?;
+use tempfile::tempdir;
+use tokio::runtime::Runtime;
+
+/// Make a backup.
+#[derive(Debug, Parser)]
+pub struct Backup {
+ /// Force a full backup, instead of an incremental one.
+ #[clap(long)]
+ full: bool,
+
+ /// Backup schema major version to use.
+ #[clap(long)]
+ backup_version: Option<VersionComponent>,
+}
+
+impl Backup {
+ /// Run the command.
+ pub fn run(&self, config: &ClientConfig, perf: &mut Performance) -> Result<(), ObnamError> {
+ let rt = Runtime::new()?;
+ rt.block_on(self.run_async(config, perf))
+ }
+
+ async fn run_async(
+ &self,
+ config: &ClientConfig,
+ perf: &mut Performance,
+ ) -> Result<(), ObnamError> {
+ let runtime = SystemTime::now();
+
+ let major = self.backup_version.unwrap_or(DEFAULT_SCHEMA_MAJOR);
+ let schema = schema_version(major)?;
+
+ let mut client = BackupClient::new(config)?;
+ let trust = client
+ .get_client_trust()
+ .await?
+ .or_else(|| Some(ClientTrust::new("FIXME", None, current_timestamp(), vec![])))
+ .unwrap();
+ let genlist = client.list_generations(&trust);
+
+ let temp = tempdir()?;
+ let oldtemp = temp.path().join("old.db");
+ let newtemp = temp.path().join("new.db");
+
+ let old_id = if self.full {
+ None
+ } else {
+ match genlist.resolve("latest") {
+ Err(_) => None,
+ Ok(old_id) => Some(old_id),
}
- Some(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)))?;
+ };
+
+ let (is_incremental, outcome) = if let Some(old_id) = old_id {
+ info!("incremental backup based on {}", old_id);
+ let mut run = BackupRun::incremental(config, &mut client)?;
+ let old = run.start(Some(&old_id), &oldtemp, perf).await?;
+ (
+ true,
+ run.backup_roots(config, &old, &newtemp, schema, perf)
+ .await?,
+ )
+ } else {
+ info!("fresh backup without a previous generation");
+ let mut run = BackupRun::initial(config, &mut client)?;
+ let old = run.start(None, &oldtemp, perf).await?;
+ (
+ false,
+ run.backup_roots(config, &old, &newtemp, schema, perf)
+ .await?,
+ )
+ };
+
+ perf.start(Clock::GenerationUpload);
+ let mut trust = trust;
+ trust.append_backup(outcome.gen_id.as_chunk_id());
+ trust.finalize(current_timestamp());
+ let trust = trust.to_data_chunk()?;
+ let trust_id = client.upload_chunk(trust).await?;
+ perf.stop(Clock::GenerationUpload);
+ info!("uploaded new client-trust {}", trust_id);
+
+ for w in outcome.warnings.iter() {
+ println!("warning: {}", w);
+ }
+
+ if is_incremental && !outcome.new_cachedir_tags.is_empty() {
+ println!("New CACHEDIR.TAG files since the last backup:");
+ for t in &outcome.new_cachedir_tags {
+ println!("- {:?}", t);
}
+ println!("You can configure Obnam to ignore all such files by setting `exclude_cache_tag_directories` to `false`.");
}
- run.progress().finish();
- new.file_count()
- };
- // 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, buffer_size)?;
+ report_stats(
+ &runtime,
+ outcome.files_count,
+ &outcome.gen_id,
+ outcome.warnings.len(),
+ )?;
+
+ if is_incremental && !outcome.new_cachedir_tags.is_empty() {
+ Err(ObnamError::NewCachedirTagsFound)
+ } else {
+ Ok(())
+ }
+ }
+}
+
+fn report_stats(
+ runtime: &SystemTime,
+ file_count: FileId,
+ gen_id: &GenId,
+ num_warnings: usize,
+) -> Result<(), ObnamError> {
println!("status: OK");
+ println!("warnings: {}", num_warnings);
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)?;
-
Ok(())
}