feat: implement backup layers using meta manager
							parent
							
								
									a236c36a4f
								
							
						
					
					
						commit
						29636ffcdb
					
				|  | @ -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<Self, Self::Err> { | ||||
|         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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<T> | ||||
| where | ||||
|     T: Clone + Serialize + for<'de> Deserialize<'de>, | ||||
| { | ||||
|     backup_dir: PathBuf, | ||||
|     config_dir: PathBuf, | ||||
|     world_dir: PathBuf, | ||||
|     default_metadata: T, | ||||
|     managers: Vec<Manager<T>>, | ||||
| } | ||||
| 
 | ||||
| impl<T> MetaManager<T> | ||||
| where | ||||
|     T: Clone + Serialize + for<'de> Deserialize<'de>, | ||||
| { | ||||
|     pub fn new<P1: Into<PathBuf>, P2: Into<PathBuf>, P3: Into<PathBuf>>( | ||||
|         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<ManagerConfig>) -> io::Result<()> { | ||||
|         for config in configs { | ||||
|             self.add(config)?; | ||||
|         } | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn next_scheduled_time(&self) -> Option<chrono::DateTime<Utc>> { | ||||
|         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(()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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<P1: Into<PathBuf>, P2: Into<PathBuf>, P3: Into<PathBuf>>( | ||||
|         backup_dir: P1, | ||||
|         config_dir: P2, | ||||
|  | @ -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; | ||||
|  |  | |||
							
								
								
									
										76
									
								
								src/cli.rs
								
								
								
								
							
							
						
						
									
										76
									
								
								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<Self, Self::Err> { | ||||
|         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<ManagerConfig>, | ||||
| 
 | ||||
|     /// 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, | ||||
|  |  | |||
							
								
								
									
										43
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										43
									
								
								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<Mutex<server::ServerProcess>>) { | |||
|     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(()), | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<ManagerConfig>, | ||||
| } | ||||
| 
 | ||||
| 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<ManagerConfig>) -> 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)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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, | ||||
| } | ||||
|  |  | |||
|  | @ -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<Metadata>, | ||||
|     pub backups: MetaManager<Metadata>, | ||||
| } | ||||
| 
 | ||||
| impl ServerProcess { | ||||
|     pub fn new(manager: BackupManager<Metadata>, child: Child) -> ServerProcess { | ||||
|     pub fn new(manager: MetaManager<Metadata>, 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", | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue