diff --git a/src/configt.rs b/src/configt.rs new file mode 100644 index 0000000000000000000000000000000000000000..37ce9182d2c2d2768186fc9358e30a39ee67e130 --- /dev/null +++ b/src/configt.rs @@ -0,0 +1,418 @@ +use crate::commit; +use crate::prune::{new_prune, prune_launcher}; +use clap::Args; +use colored::Colorize; +use regex::Regex; +use serde_derive::{Deserialize, Serialize}; +use std::{path::PathBuf, process::exit}; +use toml::{self, Value}; + +#[derive(Debug, Args)] +pub(crate) struct PartialPrune { + /// Do not change the repository + #[clap( + short = 'n', + long = "dry-run", + takes_value = false, + help_heading = "Filtering options", + display_order = 1 + )] + pub(crate) dry_run: bool, + /// Output verbose list of archive + #[clap( + long = "--list", + takes_value = false, + help_heading = "Filtering options", + display_order = 2 + )] + pub(crate) list: bool, + /// Print statistics for the deleted archive + #[clap( + short = 's', + long = "stats", + takes_value = false, + help_heading = "Filtering options", + display_order = 3 + )] + pub(crate) stats: bool, + /// Force deletion of corrupted archives, use `--force --force` in case `--force` does not work. + #[clap( + long, + parse(from_occurrences), + takes_value = false, + help_heading = "Filtering options", + display_order = 4 + )] + pub(crate) force: usize, +} + +// ================================ toml structure of borg config + +/// Structure containing the global definition of the borg config file +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Config { + repository: Repository, + gblk_prune: Option<GblkConfig>, +} + +/// Structure that store the current borg configuration for the project +#[derive(Debug, Deserialize, Serialize, Clone)] +struct Repository { + version: Value, + segments_per_dir: usize, + max_segment_size: usize, + append_only: usize, + storage_quota: usize, + additional_free_space: usize, + id: String, +} + +/// Structure corresponding to the gblk prune config to automatically use +/// inside a project +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct GblkConfig { + pub save_space: Option<bool>, + pub keep_within: Option<String>, + pub keep_last: Option<usize>, + pub keep_minutely: Option<usize>, + pub keep_hourly: Option<usize>, + pub keep_daily: Option<usize>, + pub keep_weekly: Option<usize>, + pub keep_monthly: Option<usize>, + pub keep_yearly: Option<usize>, + pub prefix: Option<String>, + pub glob_archives: Option<String>, +} + +impl GblkConfig { + fn empty(&self) -> bool { + if self.save_space != None { + return false; + }; + if self.keep_within != None { + return false; + }; + if self.keep_last != None { + return false; + }; + if self.keep_minutely != None { + return false; + }; + if self.keep_hourly != None { + return false; + }; + if self.keep_daily != None { + return false; + }; + if self.keep_weekly != None { + return false; + }; + if self.keep_monthly != None { + return false; + }; + if self.keep_yearly != None { + return false; + }; + if self.prefix != None { + return false; + }; + if self.glob_archives != None { + return false; + }; + true + } +} + +// =================== Functions + +/// Get the config file inside borg folder +/// # Return +/// A pathbuf variable containing the location of borg config file for the +/// current project +fn get_borgconfig() -> PathBuf { + let (mut config_path, _) = commit::check_path(); + config_path.push("config"); + if !config_path.is_file() { + eprintln!("{} not found !", config_path.to_str().unwrap()); + exit(1); + } + config_path +} + +fn parse_toml() -> Config { + let config_file = get_borgconfig(); + let rcontent = std::fs::read_to_string(&config_file); + let content = match rcontent { + Err(e) => { + eprintln!("Unable to read {} - {}", &config_file.to_str().unwrap(), e); + exit(101); + } + Ok(s) => s, + }; + let re = Regex::new("id = (?P<first>[0-9a-z]+)\\W").unwrap(); + let nc = re.replace(&content, "id = '$first'"); + let config_str: Config = toml::from_str(&nc).unwrap(); + config_str +} + +/// Function that show the current gblk config of the borg folder +pub fn show() -> () { + let config = parse_toml(); + let gblk_config: GblkConfig = match config.gblk_prune { + Some(data) => data, + None => { + println!("{}", "No configuration defined for this project".yellow()); + exit(0) + } + }; + let gblk_str = toml::to_string_pretty(&gblk_config).unwrap(); + println!("{}", gblk_str); +} + +/// Function that update the current gblk config file for the project folder +/// # Arguments +/// - `key` : The key to update +/// - `value`: The value of the key to update +/// # Return +/// The updated prune keys +fn update_val(input: &mut GblkConfig, key: &str, value: &str) -> () { + match key { + "save_space" | "save-space" => { + input.save_space = Some(str::parse::<bool>(value).unwrap()); + } + "keep_within" | "keep-within" => { + input.keep_within = Some(value.to_owned()); + } + "keep_last" | "keep-last" => { + input.keep_last = Some(str::parse::<usize>(value).unwrap()); + } + "keep_minutely" | "keep-minutely" => { + input.keep_minutely = Some(str::parse::<usize>(value).unwrap()); + } + "keep_hourly" | "keep-hourly" => { + input.keep_hourly = Some(str::parse::<usize>(value).unwrap()); + } + "keep_daily" | "keep-daily" => { + input.keep_daily = Some(str::parse::<usize>(value).unwrap()); + } + "keep_weekly" | "keep-weekly" => { + input.keep_weekly = Some(str::parse::<usize>(value).unwrap()); + } + "keep_monthly" | "keep-monthly" => { + input.keep_monthly = Some(str::parse::<usize>(value).unwrap()); + } + "keep_yearly" | "keep-yearly" => { + input.keep_yearly = Some(str::parse::<usize>(value).unwrap()); + } + "prefix" => { + input.prefix = Some(value.to_owned()); + } + "glob_archives" | "glob-archives" => { + input.glob_archives = Some(value.to_owned()); + } + &_ => { + eprintln!( + "{} {} '{}' {}\n{}\n- {}\n- {}", + "error:".red(), + "The key", + key, + "doesn't exist !", + "choose from:", + "keep_within, keep_last, keep_minutely, keep_hourly, keep_daily".blue(), + "keep_weekly, keep_monthly, keep_yearly, prefix, glob_archives, save_space".blue() + ); + exit(1); + } + } +} + +/// Function that checks if the parameter input param is already defined +/// # Description: +/// If input_param is already defined, returns None but if not, return an error +/// # Arguments: +/// - input_param: A parameter of the gblk config structure +/// - key: The key to remove +/// # Return +/// None if input_param is defined, error else +fn check_and_warn<T>(input_param: &Option<T>, key: &str) -> Option<T> { + match input_param { + None => { + eprintln!( + "{} {} {} {}", + "error:".red(), + "The key", + key, + "is not defined !" + ); + exit(103); + } + Some(_) => None, + } +} + +/// Function that remove a key from the current gblk config file for the project folder +/// # Arguments +/// - `key` : The key to remove +/// # Return +/// The updated prune keys +fn remove_val(input: &mut GblkConfig, key: &str) -> () { + match key { + "save_space" | "save-space" => { + input.save_space = check_and_warn(&input.save_space, &key); + } + "keep_within" | "keep-within" => { + input.keep_within = check_and_warn(&input.keep_within, &key); + } + "keep_last" | "keep-last" => { + input.keep_last = check_and_warn(&input.keep_last, &key); + } + "keep_minutely" | "keep-minutely" => { + input.keep_minutely = check_and_warn(&input.keep_minutely, &key); + } + "keep_hourly" | "keep-hourly" => { + input.keep_hourly = check_and_warn(&input.keep_hourly, &key); + } + "keep_daily" | "keep-daily" => { + input.keep_daily = check_and_warn(&input.keep_daily, &key); + } + "keep_weekly" | "keep-weekly" => { + input.keep_weekly = check_and_warn(&input.keep_weekly, &key); + } + "keep_monthly" | "keep-monthly" => { + input.keep_monthly = check_and_warn(&input.keep_monthly, &key); + } + "keep_yearly" | "keep-yearly" => { + input.keep_yearly = check_and_warn(&input.keep_yearly, &key); + } + "prefix" => { + input.prefix = check_and_warn(&input.prefix, &key); + } + "glob_archives" | "glob-archives" => { + input.glob_archives = check_and_warn(&input.glob_archives, &key); + } + &_ => { + eprintln!( + "{} {} '{}' {}\n{}\n- {}\n- {}", + "error:".red(), + "The key", + key, + "doesn't exist !", + "choose from:", + "keep_within, keep_last, keep_minutely, keep_hourly, keep_daily".blue(), + "keep_weekly, keep_monthly, keep_yearly, prefix, glob_archives, save_space".blue() + ); + exit(1); + } + } +} + +/// Get the object conrresponding to gblk prune config +/// # Arguments +/// - full_config: The entire configuration file +/// # Return +/// The configuration for gblk prune object +fn get_gblkconfig(full_config: &Config) -> GblkConfig { + let gblk_config: GblkConfig = match &full_config.gblk_prune { + Some(data) => data.clone(), + None => GblkConfig { + save_space: None, + keep_within: None, + keep_last: None, + keep_minutely: None, + keep_hourly: None, + keep_daily: None, + keep_weekly: None, + keep_monthly: None, + keep_yearly: None, + prefix: None, + glob_archives: None, + }, + }; + gblk_config +} + +/// Function that turn toml into string +/// # Argument +/// - config: The full toml file parsed +/// # Return +/// The string corresponding to toml file +fn toml_to_string(config: &Config) -> String { + let gblk_str = toml::to_string_pretty(&config).unwrap(); + let re = Regex::new("id = '(?P<first>[0-9a-z]+)'").unwrap(); + let nc = re.replace(&gblk_str, "id = $first").to_string(); + nc +} + +/// Function that update the current gblk config file for the project folder +/// # Arguments +/// - `key` : The key to update +/// - `value`: The value of the key to update +pub fn update_config(key: &str, value: &str) -> () { + let mut config = parse_toml(); + let mut gblk_config: GblkConfig = get_gblkconfig(&config); + update_val(&mut gblk_config, key, value); + config.gblk_prune = Some(gblk_config); + let gblk_str = toml_to_string(&config); + let config_file = get_borgconfig(); + match std::fs::write(&config_file, gblk_str) { + Err(_) => { + eprintln!( + "{} {} {} {}", + "error:".red(), + "Unable to write the config file", + config_file.to_str().unwrap(), + "!" + ); + exit(102); + } + Ok(_) => (), + }; +} + +/// Function that remove the current gblk config file for the project folder +/// for a given arguments +/// # Arguments +/// - `key` : The key to update +pub fn remove_config(key: &str) -> () { + let mut config = parse_toml(); + let mut gblk_config: GblkConfig = get_gblkconfig(&config); + remove_val(&mut gblk_config, key); + config.gblk_prune = if gblk_config.empty() { + None + } else { + Some(gblk_config) + }; + let gblk_str = toml_to_string(&config); + let config_file = get_borgconfig(); + match std::fs::write(&config_file, gblk_str) { + Err(_) => { + eprintln!( + "{} {} {} {}", + "error:".red(), + "Unable to write the config file", + config_file.to_str().unwrap(), + "!" + ); + exit(102); + } + Ok(_) => (), + }; +} + +/// Execute a prune based on gblk configuration +/// # Arguments +/// - config_prune: contains partial configuration for prune that cannot be +/// used in the configuration file +pub(crate) fn launch_config_prune(config_prune: PartialPrune) { + let config = parse_toml(); + let gblk_config: GblkConfig = get_gblkconfig(&config); + if gblk_config.empty() { + eprintln!( + "{} No configuration defined for pruning!", + "error:".red() + ); + exit(104); + } + let prune_obj = new_prune(gblk_config, config_prune); + prune_launcher(prune_obj); +} diff --git a/src/main.rs b/src/main.rs index f31a6866473dab1bfd25b3f6f2c00221c6ba9c82..607a990d5f265ee88dae82e1304e03ab088e015b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,11 @@ use crate::delete::Delete; use crate::prune::Prune; use clap::{Args, Parser, Subcommand}; use colored::Colorize; +use configt::PartialPrune; mod checkout; mod commit; mod compact; +mod configt; mod create_hooks; mod delete; mod diff; @@ -58,7 +60,7 @@ enum Commands { DeleteHooks, /// Show differences between two commits of the `results` folder Diff(Diff), - /// Mount an old file/directory from one or multiple archive named afert + /// Mount an old file/directory from one or multiple archive named after /// git commits into the .mount folder inside de project directory. Mount(Mount), /// Unmount everything in the folder .mount @@ -68,7 +70,7 @@ enum Commands { /// This is basically a wrapper of the borg delete command /// /// You can visit: - /// https://borgbackup.readthedocs.io/en/stable/usage/delete.html for more details + /// <https://borgbackup.readthedocs.io/en/stable/usage/prune.html> for more details /// /// This function don't free disk space until gblk compact is used Delete(Delete), @@ -78,7 +80,7 @@ enum Commands { /// This is basically a wrapper of the borg prune command /// /// You can visit: - /// https://borgbackup.readthedocs.io/en/stable/usage/prune.html for more details + /// <https://borgbackup.readthedocs.io/en/stable/usage/prune.html> for more details /// /// This function don't free disk space until gblk compact is used Prune(Prune), @@ -87,6 +89,12 @@ enum Commands { /// It is especially useful after deleting archives because compaction will /// free repository space Compact(Compact), + /// This command can bed used to add gblk configuration + /// + /// It's useful when you want to defined to always keep archive in given + /// time interval without typing always the same prune command + #[clap(subcommand)] + Config(Config), } #[derive(Debug, Args)] @@ -206,6 +214,33 @@ struct Mount { last: Option<u8>, } +#[derive(Debug, Subcommand)] +enum Config { + /// Add a new parameter in the gblk config file to be able to automatically + /// prune some commits + Add(Add), + /// Display the current gblk configuration + Show, + /// Remove the configuration of a given key in prune + Rm(Rm), + /// Prune using the project configuration + Prune(PartialPrune), +} + +#[derive(Debug, Args)] +struct Add { + /// The name of the argument to add, see optional arguments of the prune subcommands + key: String, + /// The value of the argument to add in the configuration file + value: String, +} + +#[derive(Debug, Args)] +struct Rm { + /// The name of the argument to remove, see optional arguments of the prune subcommands + key: String, +} + fn main() { let args = Cli::parse(); @@ -273,5 +308,11 @@ fn main() { Commands::Compact(my_compact) => { compact::launch_compact(my_compact); } + Commands::Config(conf) => match conf { + Config::Add(e) => configt::update_config(&e.key, &e.value), + Config::Show => configt::show(), + Config::Rm(e) => configt::remove_config(&e.key), + Config::Prune(pp) => configt::launch_config_prune(pp), + }, } } diff --git a/src/prune.rs b/src/prune.rs index a8a2629313ed635f9ab8b1a6ebe5b252e2df26e0..e3dc37e2d55595dd4440398edc6a84057bdf156d 100644 --- a/src/prune.rs +++ b/src/prune.rs @@ -1,4 +1,5 @@ use crate::commit; +use crate::configt::{GblkConfig, PartialPrune}; use clap::Args; use std::{ collections::HashMap as HM, @@ -148,6 +149,30 @@ pub(crate) struct Prune { pub(crate) glob_archives: Option<String>, } +pub(crate) fn new_prune(gblk_config: GblkConfig, partial_prune: PartialPrune) -> Prune { + let ss = match gblk_config.save_space { + None => false, + Some(bool) => bool, + }; + Prune { + dry_run: partial_prune.dry_run, + list: partial_prune.list, + stats: partial_prune.stats, + force: partial_prune.force, + save_space: ss, + keep_within: gblk_config.keep_within, + keep_last: gblk_config.keep_last, + keep_minutely: gblk_config.keep_minutely, + keep_hourly: gblk_config.keep_hourly, + keep_daily: gblk_config.keep_daily, + keep_weekly: gblk_config.keep_weekly, + keep_monthly: gblk_config.keep_monthly, + keep_yearly: gblk_config.keep_yearly, + prefix: gblk_config.prefix, + glob_archives: gblk_config.glob_archives, + } +} + /// Function that add flags arguments in the delete commands /// # Arguments /// * `sd`: Struct containing all the data that can be used to build the delete command