diff options
Diffstat (limited to 'src/cmd/backup.rs')
-rw-r--r-- | src/cmd/backup.rs | 178 |
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(()) } |