From 29636ffcdb5344ce36dcc013878bc86045fa3905 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 23 Jun 2023 12:30:10 +0200 Subject: [PATCH] feat: implement backup layers using meta manager --- src/backup/manager/config.rs | 46 +++++++++++++ src/backup/manager/meta.rs | 83 +++++++++++++++++++++++ src/backup/{manager.rs => manager/mod.rs} | 7 +- src/backup/mod.rs | 4 +- src/cli.rs | 76 +-------------------- src/main.rs | 43 +++++------- src/server/command.rs | 64 +++-------------- src/server/mod.rs | 33 ++++++++- src/server/process.rs | 12 ++-- 9 files changed, 201 insertions(+), 167 deletions(-) create mode 100644 src/backup/manager/config.rs create mode 100644 src/backup/manager/meta.rs rename src/backup/{manager.rs => manager/mod.rs} (98%) diff --git a/src/backup/manager/config.rs b/src/backup/manager/config.rs new file mode 100644 index 0000000..c5ba217 --- /dev/null +++ b/src/backup/manager/config.rs @@ -0,0 +1,46 @@ +use std::error::Error; +use std::fmt; +use std::str::FromStr; + +#[derive(Clone, Debug)] +pub struct ManagerConfig { + pub name: String, + pub frequency: u32, + pub chain_len: u64, + pub chains: u64, +} + +#[derive(Debug)] +pub struct ParseManagerConfigErr; + +impl Error for ParseManagerConfigErr {} + +impl fmt::Display for ParseManagerConfigErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "parse manager config err") + } +} + +impl FromStr for ManagerConfig { + type Err = ParseManagerConfigErr; + + fn from_str(s: &str) -> Result { + let splits: Vec<&str> = s.split(',').collect(); + + if let [name, frequency, chains, chain_len] = splits[..] { + let name: String = name.parse().map_err(|_| ParseManagerConfigErr)?; + let frequency: u32 = frequency.parse().map_err(|_| ParseManagerConfigErr)?; + let chains: u64 = chains.parse().map_err(|_| ParseManagerConfigErr)?; + let chain_len: u64 = chain_len.parse().map_err(|_| ParseManagerConfigErr)?; + + Ok(ManagerConfig { + name, + chains, + chain_len, + frequency, + }) + } else { + Err(ParseManagerConfigErr) + } + } +} diff --git a/src/backup/manager/meta.rs b/src/backup/manager/meta.rs new file mode 100644 index 0000000..5878f34 --- /dev/null +++ b/src/backup/manager/meta.rs @@ -0,0 +1,83 @@ +use super::{Manager, ManagerConfig}; +use chrono::Utc; +use serde::Deserialize; +use serde::Serialize; +use std::io; +use std::path::PathBuf; + +pub struct MetaManager +where + T: Clone + Serialize + for<'de> Deserialize<'de>, +{ + backup_dir: PathBuf, + config_dir: PathBuf, + world_dir: PathBuf, + default_metadata: T, + managers: Vec>, +} + +impl MetaManager +where + T: Clone + Serialize + for<'de> Deserialize<'de>, +{ + pub fn new, P2: Into, P3: Into>( + backup_dir: P1, + config_dir: P2, + world_dir: P3, + default_metadata: T, + ) -> Self { + MetaManager { + backup_dir: backup_dir.into(), + config_dir: config_dir.into(), + world_dir: world_dir.into(), + default_metadata, + managers: Vec::new(), + } + } + + pub fn add(&mut self, config: &ManagerConfig) -> io::Result<()> { + // Backup dir itself should exist, but we control its contents, so we can create + // separate directories for each layer + let path = self.backup_dir.join(&config.name); + std::fs::create_dir(&path)?; + + let mut manager = Manager::new( + path, + self.config_dir.clone(), + self.world_dir.clone(), + self.default_metadata.clone(), + config.chain_len, + config.chains, + chrono::Duration::minutes(config.frequency.into()), + ); + manager.load()?; + self.managers.push(manager); + + Ok(()) + } + + pub fn add_all(&mut self, configs: &Vec) -> io::Result<()> { + for config in configs { + self.add(config)?; + } + + Ok(()) + } + + pub fn next_scheduled_time(&self) -> Option> { + self.managers.iter().map(|m| m.next_scheduled_time()).min() + } + + pub fn perform_backup_cycle(&mut self) -> io::Result<()> { + if let Some(manager) = self + .managers + .iter_mut() + .min_by_key(|m| m.next_scheduled_time()) + { + manager.create_backup()?; + manager.remove_old_backups() + } else { + Ok(()) + } + } +} diff --git a/src/backup/manager.rs b/src/backup/manager/mod.rs similarity index 98% rename from src/backup/manager.rs rename to src/backup/manager/mod.rs index cc6dd0c..6c991c0 100644 --- a/src/backup/manager.rs +++ b/src/backup/manager/mod.rs @@ -1,3 +1,9 @@ +mod config; +mod meta; + +pub use config::ManagerConfig; +pub use meta::MetaManager; + use super::Backup; use chrono::Utc; use serde::Deserialize; @@ -26,7 +32,6 @@ where { const METADATA_FILE: &str = "alex.json"; - /// Initialize a new instance of a `BackupManager`. pub fn new, P2: Into, P3: Into>( backup_dir: P1, config_dir: P2, diff --git a/src/backup/mod.rs b/src/backup/mod.rs index 5b12b8c..780c557 100644 --- a/src/backup/mod.rs +++ b/src/backup/mod.rs @@ -1,9 +1,11 @@ mod delta; -mod manager; +pub mod manager; mod path; use delta::Delta; pub use manager::Manager; +pub use manager::ManagerConfig; +pub use manager::MetaManager; use chrono::Utc; use flate2::write::GzEncoder; diff --git a/src/cli.rs b/src/cli.rs index adf1013..8d44af3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,52 +1,7 @@ +use crate::backup::ManagerConfig; use crate::server::ServerType; use clap::{Args, Parser, Subcommand}; -use std::error::Error; -use std::fmt; use std::path::PathBuf; -use std::str::FromStr; - -#[derive(Clone, Debug)] -pub struct ManagerConfig { - name: String, - frequency: u32, - chain_len: u64, - chains: u64, -} - -#[derive(Debug)] -pub struct ParseManagerConfigErr; - -impl Error for ParseManagerConfigErr {} - -impl fmt::Display for ParseManagerConfigErr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "parse manager config err") - } -} - -impl FromStr for ManagerConfig { - type Err = ParseManagerConfigErr; - - fn from_str(s: &str) -> Result { - let splits: Vec<&str> = s.split(',').collect(); - - if let [name, frequency, chains, chain_len] = splits[..] { - let name: String = name.parse().map_err(|_| ParseManagerConfigErr)?; - let frequency: u32 = frequency.parse().map_err(|_| ParseManagerConfigErr)?; - let chains: u64 = chains.parse().map_err(|_| ParseManagerConfigErr)?; - let chain_len: u64 = chain_len.parse().map_err(|_| ParseManagerConfigErr)?; - - Ok(ManagerConfig { - name, - chains, - chain_len, - frequency, - }) - } else { - Err(ParseManagerConfigErr) - } - } -} #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -86,35 +41,6 @@ pub struct Cli { #[arg(long, env = "ALEX_LAYERS", global = true, value_delimiter = ';')] pub layers: Vec, - /// Length of a backup chain - #[arg( - short = 'l', - long, - default_value_t = 4, - env = "ALEX_CHAIN_LEN", - global = true - )] - pub chain_len: u64, - /// How many backup chains to keep - #[arg( - short = 'n', - long, - default_value_t = 7, - env = "ALEX_CHAINS", - global = true - )] - pub chains: u64, - - /// How frequently to perform a backup, in minutes; 0 to disable. - #[arg( - short = 't', - long, - default_value_t = 0, - env = "ALEX_FREQUENCY", - global = true - )] - pub frequency: u32, - /// Type of server #[arg(long, default_value = "unknown", env = "ALEX_SERVER", global = true)] pub server: ServerType, diff --git a/src/main.rs b/src/main.rs index f4b9ef2..b7187e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod signals; mod stdin; use clap::Parser; -use cli::{BackupArgs, Cli, Commands, RunArgs}; +use cli::{Cli, Commands, RunArgs}; use std::io; use std::sync::{Arc, Mutex}; @@ -13,7 +13,7 @@ fn backups_thread(counter: Arc>) { loop { let next_scheduled_time = { let server = counter.lock().unwrap(); - server.backups.next_scheduled_time() + server.backups.next_scheduled_time().unwrap() }; let now = chrono::offset::Utc::now(); @@ -39,11 +39,9 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { .config(cli.config.clone()) .world(cli.world.clone()) .backup(cli.backup.clone()) + .managers(cli.layers.clone()) .xms(args.xms) - .xmx(args.xmx) - .chain_len(cli.chain_len) - .chains_to_keep(cli.chains) - .frequency(cli.frequency); + .xmx(args.xmx); cmd.canonicalize()?; if args.dry { @@ -54,7 +52,7 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { let counter = Arc::new(Mutex::new(cmd.spawn()?)); - if cli.frequency > 0 { + if !cli.layers.is_empty() { let clone = Arc::clone(&counter); std::thread::spawn(move || backups_thread(clone)); } @@ -67,32 +65,23 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { signals::handle_signals(&mut signals, counter) } -fn commands_backup(cli: &Cli, _args: &BackupArgs) -> io::Result<()> { - let metadata = server::Metadata { - server_type: cli.server, - server_version: cli.server_version.clone(), - }; +// fn commands_backup(cli: &Cli, _args: &BackupArgs) -> io::Result<()> { +// let metadata = server::Metadata { +// server_type: cli.server, +// server_version: cli.server_version.clone(), +// }; +// let mut meta = MetaManager::new(cli.backup, cli.config, cli.world, metadata); +// meta.add_all(&cli.layers)?; - let mut manager = backup::Manager::new( - cli.backup.clone(), - cli.config.clone(), - cli.world.clone(), - metadata, - cli.chain_len, - cli.chains, - chrono::Duration::minutes(cli.frequency.into()), - ); - manager.load()?; - - manager.create_backup()?; - manager.remove_old_backups() -} +// manager.create_backup()?; +// manager.remove_old_backups() +// } fn main() -> io::Result<()> { let cli = Cli::parse(); match &cli.command { Commands::Run(args) => command_run(&cli, args), - Commands::Backup(args) => commands_backup(&cli, args), + Commands::Backup(_) => Ok(()), } } diff --git a/src/server/command.rs b/src/server/command.rs index 1ce5ce0..ec1b302 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -1,40 +1,12 @@ -use crate::backup::Manager as BackupManager; -use crate::server::ServerProcess; -use clap::ValueEnum; -use serde::{Deserialize, Serialize}; +use crate::backup::ManagerConfig; +use crate::backup::MetaManager; +use crate::server::{Metadata, ServerProcess, ServerType}; use std::fmt; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)] -pub enum ServerType { - Unknown, - Paper, - Forge, - Vanilla, -} - -impl fmt::Display for ServerType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - ServerType::Unknown => "Unknown", - ServerType::Paper => "PaperMC", - ServerType::Forge => "Forge", - ServerType::Vanilla => "Vanilla", - }; - - write!(f, "{}", s) - } -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct Metadata { - pub server_type: ServerType, - pub server_version: String, -} - pub struct ServerCommand { type_: ServerType, version: String, @@ -45,9 +17,7 @@ pub struct ServerCommand { backup_dir: PathBuf, xms: u64, xmx: u64, - chain_len: u64, - chains_to_keep: u64, - frequency: u32, + managers: Vec, } impl ServerCommand { @@ -62,9 +32,7 @@ impl ServerCommand { backup_dir: PathBuf::from("backups"), xms: 1024, xmx: 2048, - chain_len: 4, - chains_to_keep: 7, - frequency: 0, + managers: Vec::new(), } } @@ -105,18 +73,9 @@ impl ServerCommand { self } - pub fn chain_len(mut self, v: u64) -> Self { - self.chain_len = v; - self - } + pub fn managers(mut self, configs: Vec) -> Self { + self.managers = configs; - pub fn chains_to_keep(mut self, v: u64) -> Self { - self.chains_to_keep = v; - self - } - - pub fn frequency(mut self, v: u32) -> Self { - self.frequency = v; self } @@ -207,22 +166,19 @@ impl ServerCommand { server_type: self.type_, server_version: self.version.clone(), }; - let mut manager = BackupManager::new( + let mut meta = MetaManager::new( self.backup_dir.clone(), self.config_dir.clone(), self.world_dir.clone(), metadata, - self.chain_len, - self.chains_to_keep, - chrono::Duration::minutes(self.frequency.into()), ); - manager.load()?; + meta.add_all(&self.managers)?; let mut cmd = self.create_cmd(); self.accept_eula()?; let child = cmd.spawn()?; - Ok(ServerProcess::new(manager, child)) + Ok(ServerProcess::new(meta, child)) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 0f01c9a..09f90a5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,36 @@ mod command; mod process; -pub use command::{Metadata, ServerCommand, ServerType}; +pub use command::ServerCommand; pub use process::ServerProcess; + +use clap::ValueEnum; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)] +pub enum ServerType { + Unknown, + Paper, + Forge, + Vanilla, +} + +impl fmt::Display for ServerType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + ServerType::Unknown => "Unknown", + ServerType::Paper => "PaperMC", + ServerType::Forge => "Forge", + ServerType::Vanilla => "Vanilla", + }; + + write!(f, "{}", s) + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Metadata { + pub server_type: ServerType, + pub server_version: String, +} diff --git a/src/server/process.rs b/src/server/process.rs index 17b86b1..ef6822e 100644 --- a/src/server/process.rs +++ b/src/server/process.rs @@ -1,15 +1,15 @@ -use crate::backup::Manager as BackupManager; +use crate::backup::MetaManager; use crate::server::Metadata; use std::io::Write; use std::process::Child; pub struct ServerProcess { child: Child, - pub backups: BackupManager, + pub backups: MetaManager, } impl ServerProcess { - pub fn new(manager: BackupManager, child: Child) -> ServerProcess { + pub fn new(manager: MetaManager, child: Child) -> ServerProcess { ServerProcess { child, backups: manager, @@ -59,16 +59,12 @@ impl ServerProcess { std::thread::sleep(std::time::Duration::from_secs(10)); let start_time = chrono::offset::Utc::now(); - let res = self.backups.create_backup(); + let res = self.backups.perform_backup_cycle(); // The server's save feature needs to be enabled again even if the archive failed to create self.custom("save-on")?; self.custom("save-all")?; - if res.is_ok() { - self.backups.remove_old_backups()?; - } - let duration = chrono::offset::Utc::now() - start_time; let duration_str = format!( "{}m{}s",