diff --git a/src/backend/local.rs b/src/backend/local.rs index f0ea307..c4e9bc5 100644 --- a/src/backend/local.rs +++ b/src/backend/local.rs @@ -139,7 +139,7 @@ impl WriteBackend for LocalBackend { v3!("writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); let mut file = fs::OpenOptions::new() - .create_new(true) + .create(true) .write(true) .open(&filename)?; copy(&mut f, &mut file)?; @@ -151,7 +151,7 @@ impl WriteBackend for LocalBackend { v3!("writing tpe: {:?}, id: {}", &tpe, &id); let filename = self.path(tpe, id); let mut file = fs::OpenOptions::new() - .create_new(true) + .create(true) .write(true) .open(&filename)?; file.write_all(&buf)?; diff --git a/src/commands/backup.rs b/src/commands/backup.rs index b8885ea..b602479 100644 --- a/src/commands/backup.rs +++ b/src/commands/backup.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use chrono::{Duration, Local}; use clap::Parser; use gethostname::gethostname; @@ -57,11 +57,7 @@ pub(super) async fn execute( ) -> Result<()> { let time = Local::now(); let poly = config.poly()?; - let zstd = match config.version { - 1 => None, - 2 => Some(0), - _ => bail!("config version not supported!"), - }; + let zstd = config.zstd()?; let mut be = DryRunBackend::new(be.clone(), opts.dry_run); be.set_zstd(zstd); diff --git a/src/commands/cat.rs b/src/commands/cat.rs index e085ad3..a151bfd 100644 --- a/src/commands/cat.rs +++ b/src/commands/cat.rs @@ -21,14 +21,14 @@ pub(super) struct Opts { enum Command { TreeBlob(IdOpt), DataBlob(IdOpt), - Config(IdOpt), + Config, Index(IdOpt), Snapshot(IdOpt), /// display a tree within a snapshot Tree(TreeOpts), } -#[derive(Parser)] +#[derive(Default, Parser)] struct IdOpt { /// id to cat id: String, @@ -45,7 +45,7 @@ struct TreeOpts { pub(super) async fn execute(be: &impl DecryptReadBackend, opts: Opts) -> Result<()> { match opts.command { - Command::Config(opt) => cat_file(be, FileType::Config, opt).await, + Command::Config => cat_file(be, FileType::Config, IdOpt::default()).await, Command::Index(opt) => cat_file(be, FileType::Index, opt).await, Command::Snapshot(opt) => cat_file(be, FileType::Snapshot, opt).await, // special treatment for catingg blobs: read the index and use it to locate the blob diff --git a/src/commands/config.rs b/src/commands/config.rs new file mode 100644 index 0000000..8e63758 --- /dev/null +++ b/src/commands/config.rs @@ -0,0 +1,76 @@ +use anyhow::{bail, Result}; +use clap::Parser; + +use crate::backend::DecryptFullBackend; +use crate::repo::ConfigFile; + +#[derive(Parser)] +pub(super) struct Opts { + #[clap(flatten)] + config_opts: ConfigOpts, +} + +pub(super) async fn execute( + be: &impl DecryptFullBackend, + opts: Opts, + config: ConfigFile, +) -> Result<()> { + let mut new_config = config.clone(); + opts.config_opts.apply(&mut new_config)?; + if new_config != config { + be.save_file(&new_config).await?; + println!("saved new config"); + } else { + println!("config is unchanged"); + } + + Ok(()) +} + +#[derive(Parser)] +pub(super) struct ConfigOpts { + /// set compression level, 0 equals no compression + #[clap(long, value_name = "LEVEL")] + pub set_compression: Option, + + /// set repository version + #[clap(long, value_name = "VERSION")] + pub set_version: Option, +} + +impl ConfigOpts { + pub fn apply(&self, config: &mut ConfigFile) -> Result<()> { + if let Some(version) = self.set_version { + let range = 1..=2; + if !range.contains(&version) { + bail!( + "version {version} is not supported. Allowed values: {}..{}", + range.start(), + range.end() + ); + } else if version < config.version { + bail!( + "cannot downgrade version from {} to {version}", + config.version + ); + } + config.version = version; + } + + if let Some(compression) = self.set_compression { + if config.version == 1 && compression != 0 { + bail!("compression level {compression} is not supported for repo v1"); + } + let range = zstd::compression_level_range(); + if !range.contains(&compression) { + bail!( + "compression level {compression} is not supported. Allowed values: 0..{}", + range.end() + ); + } + config.compression = Some(compression); + } + + Ok(()) + } +} diff --git a/src/commands/init.rs b/src/commands/init.rs index 984cfec..bae7421 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -5,6 +5,7 @@ use anyhow::{bail, Result}; use clap::Parser; use rpassword::{prompt_password, read_password_from_bufread}; +use super::config::ConfigOpts; use super::key::AddOpts; use crate::backend::{DecryptBackend, DecryptWriteBackend, FileType, WriteBackend}; use crate::chunker; @@ -16,6 +17,9 @@ use crate::repo::{ConfigFile, KeyFile}; pub(super) struct Opts { #[clap(flatten)] key_opts: AddOpts, + + #[clap(flatten)] + config_opts: ConfigOpts, } pub(super) async fn execute( @@ -28,6 +32,17 @@ pub(super) async fn execute( bail!("Config file already exists. Aborting."); } + // Create config first to allow catching errors from here without writing anything + let repo_id = Id::random(); + let chunker_poly = chunker::random_poly()?; + let version = match opts.config_opts.set_version { + None => 2, + Some(_) => 1, // will be changed later + }; + let mut config = ConfigFile::new(version, repo_id, chunker_poly); + opts.config_opts.apply(&mut config)?; + + // generate key let key = Key::new(); let key_opts = opts.key_opts; @@ -56,10 +71,7 @@ pub(super) async fn execute( } println!("key {} successfully added.", id); - let repo_id = Id::random(); - let chunker_poly = chunker::random_poly()?; - let mut config = ConfigFile::new(2, repo_id, chunker_poly); - + // save config let dbe = DecryptBackend::new(be, key.clone()); dbe.save_file(&config).await?; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 24e8603..40321d8 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -12,6 +12,7 @@ use crate::repo::ConfigFile; mod backup; mod cat; mod check; +mod config; mod diff; mod forget; mod helpers; @@ -76,6 +77,9 @@ enum Command { /// Cat repository files and blobs Cat(cat::Opts), + /// Change repo configuration + Config(config::Opts), + /// Check repository Check(check::Opts), @@ -171,6 +175,7 @@ pub async fn execute() -> Result<()> { match cmd { Command::Backup(opts) => backup::execute(&dbe, opts, config, command).await?, + Command::Config(opts) => config::execute(&dbe, opts, config).await?, Command::Cat(opts) => cat::execute(&dbe, opts).await?, Command::Check(opts) => check::execute(&dbe, &cache, &be_hot, &be, opts).await?, Command::Diff(opts) => diff::execute(&dbe, opts).await?, diff --git a/src/commands/prune.rs b/src/commands/prune.rs index dbb3575..ed32fc6 100644 --- a/src/commands/prune.rs +++ b/src/commands/prune.rs @@ -715,11 +715,7 @@ impl Pruner { opts: Opts, config: ConfigFile, ) -> Result<()> { - let zstd = match config.version { - 1 => None, - 2 => Some(0), - _ => bail!("config version not supported!"), - }; + let zstd = config.zstd()?; let mut be = be.clone(); be.set_zstd(zstd); diff --git a/src/repo/configfile.rs b/src/repo/configfile.rs index 480a82d..9e9d913 100644 --- a/src/repo/configfile.rs +++ b/src/repo/configfile.rs @@ -1,16 +1,18 @@ -use anyhow::Result; +use anyhow::{bail, Result}; use serde::{Deserialize, Serialize}; use crate::backend::{FileType, RepoFile}; use crate::id::Id; -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct ConfigFile { pub version: u32, pub id: Id, pub chunker_polynomial: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub is_hot: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub compression: Option, // note that Some(0) means no compression. } impl RepoFile for ConfigFile { @@ -24,10 +26,20 @@ impl ConfigFile { id, chunker_polynomial: format!("{:x}", poly), is_hot: None, + compression: None, } } pub fn poly(&self) -> Result { Ok(u64::from_str_radix(&self.chunker_polynomial, 16)?) } + + pub fn zstd(&self) -> Result> { + match (self.version, self.compression) { + (1, _) | (2, Some(0)) => Ok(None), + (2, None) => Ok(Some(0)), // use default (=0) zstd compression + (2, Some(c)) => Ok(Some(c)), + _ => bail!("config version not supported!"), + } + } }