From 53dc3783ca2696e35f5dbd956c3b9632dd043d7d Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 20 Jun 2023 19:31:50 +0200 Subject: [PATCH] feat: store server info in metadata file; change cli flags --- CHANGELOG.md | 2 ++ src/backup/delta.rs | 1 + src/backup/manager.rs | 60 ++++++++++++++++++++++++++----------------- src/backup/mod.rs | 38 +++++++++++++++++---------- src/cli.rs | 20 +++++---------- src/main.rs | 8 +++++- src/server/command.rs | 23 ++++++++++++----- src/server/mod.rs | 2 +- src/server/process.rs | 15 +++-------- 9 files changed, 97 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8157f9e..9d35929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Chain length descibres how many incremental backups to create from the same full backup * "backups to keep" has been replaced by "chains to keep" +* Server type & version is now stored as metadata in the metadata file ### Changed * Running the server now uses the `run` CLI subcommand +* `server_type` and `server_version` arguments are now optional flags ### Removed diff --git a/src/backup/delta.rs b/src/backup/delta.rs index 15f233b..34971f0 100644 --- a/src/backup/delta.rs +++ b/src/backup/delta.rs @@ -24,6 +24,7 @@ impl Delta { /// Update the current state so that its result becomes the merge of itself and the other /// state. + #[allow(dead_code)] pub fn merge(&mut self, delta: &Self) { for (dir, added) in delta.added.iter() { // Files that were removed in the current state, but added in the new state, are no diff --git a/src/backup/manager.rs b/src/backup/manager.rs index 71633a3..ce3dbe1 100644 --- a/src/backup/manager.rs +++ b/src/backup/manager.rs @@ -1,18 +1,27 @@ use super::Backup; +use serde::de::DeserializeOwned; +use serde::Serialize; use std::fs::File; use std::io; use std::path::PathBuf; -pub struct Manager { +pub struct Manager +where + T: Clone + Serialize + DeserializeOwned, +{ backup_dir: PathBuf, config_dir: PathBuf, world_dir: PathBuf, + default_metadata: T, chain_len: u64, chains_to_keep: u64, - chains: Vec>, + chains: Vec>>, } -impl Manager { +impl Manager +where + T: Clone + Serialize + DeserializeOwned, +{ const METADATA_FILE: &str = "alex.json"; /// Initialize a new instance of a `BackupManager`. @@ -20,6 +29,7 @@ impl Manager { backup_dir: PathBuf, config_dir: PathBuf, world_dir: PathBuf, + metadata: T, chain_len: u64, chains_to_keep: u64, ) -> Self { @@ -27,6 +37,7 @@ impl Manager { backup_dir, config_dir, world_dir, + default_metadata: metadata, chain_len, chains_to_keep, chains: Vec::new(), @@ -40,32 +51,32 @@ impl Manager { (PathBuf::from("worlds"), self.world_dir.clone()), ]; - // I kinda hate this statement, please just let me combine let statements in if statements - // already - let backup = if let Some(current_chain) = self.chains.last() { + // We start a new chain if the current chain is complete, or if there isn't a first chain + // yet + if let Some(current_chain) = self.chains.last() { let current_chain_len: u64 = current_chain.len().try_into().unwrap(); - if current_chain_len < self.chain_len { - if let Some(previous_backup) = current_chain.last() { - let state = Backup::state(current_chain); - - Backup::create_from(state, previous_backup.start_time, &self.backup_dir, dirs)? - } else { - Backup::create(&self.backup_dir, dirs)? - } - } else { + if current_chain_len >= self.chain_len { self.chains.push(Vec::new()); - - Backup::create(&self.backup_dir, dirs)? } } else { self.chains.push(Vec::new()); + } + let current_chain = self.chains.last_mut().unwrap(); + + let mut backup = if !current_chain.is_empty() { + let previous_backup = current_chain.last().unwrap(); + let state = Backup::state(current_chain); + + Backup::create_from(state, previous_backup.start_time, &self.backup_dir, dirs)? + } else { Backup::create(&self.backup_dir, dirs)? }; - // The above statement always creates this element, so this unwrap is safe - self.chains.last_mut().unwrap().push(backup); + backup.set_metadata(self.default_metadata.clone()); + + current_chain.push(backup); self.save()?; @@ -73,7 +84,7 @@ impl Manager { } /// Delete all backups associated with outdated chains, and forget those chains. - pub fn remove_old_backups(&mut self) -> std::io::Result<()> { + pub fn remove_old_backups(&mut self) -> io::Result<()> { let chains_to_store: usize = self.chains_to_keep.try_into().unwrap(); if chains_to_store < self.chains.len() { @@ -91,15 +102,15 @@ impl Manager { std::fs::remove_file(path)?; } } - } - self.save()?; + self.save()?; + } Ok(()) } /// Write the in-memory state to disk. - pub fn save(&self) -> std::io::Result<()> { + pub fn save(&self) -> io::Result<()> { let json_file = File::create(self.backup_dir.join(Self::METADATA_FILE))?; serde_json::to_writer(json_file, &self.chains)?; @@ -107,7 +118,7 @@ impl Manager { } /// Overwrite the in-memory state with the on-disk state. - pub fn load(&mut self) -> std::io::Result<()> { + pub fn load(&mut self) -> io::Result<()> { let json_file = match File::open(self.backup_dir.join(Self::METADATA_FILE)) { Ok(f) => f, Err(e) => { @@ -121,6 +132,7 @@ impl Manager { } } }; + self.chains = serde_json::from_reader(json_file)?; Ok(()) diff --git a/src/backup/mod.rs b/src/backup/mod.rs index 600ef6d..5b12b8c 100644 --- a/src/backup/mod.rs +++ b/src/backup/mod.rs @@ -22,21 +22,37 @@ pub enum BackupType { } /// Represents a successful backup -#[derive(Debug, Serialize, Deserialize)] -pub struct Backup { +#[derive(Serialize, Deserialize)] +pub struct Backup { /// When the backup was started (also corresponds to the name) pub start_time: chrono::DateTime, /// Type of the backup pub type_: BackupType, pub delta: Delta, + /// Additional metadata that can be associated with a given backup + pub metadata: Option, } -impl Backup { +impl Backup<()> { + /// Return the path to a backup file by properly formatting the data. + pub fn path>(backup_dir: P, start_time: chrono::DateTime) -> PathBuf { + let backup_dir = backup_dir.as_ref(); + + let filename = format!("{}", start_time.format(Self::FILENAME_FORMAT)); + backup_dir.join(filename) + } +} + +impl Backup { const FILENAME_FORMAT: &str = "%Y-%m-%d_%H-%M-%S.tar.gz"; + pub fn set_metadata(&mut self, metadata: T) { + self.metadata = Some(metadata); + } + /// Resolve the state of the list of backups by applying their deltas in-order to an initially /// empty state. - pub fn state(backups: &Vec) -> HashMap> { + pub fn state(backups: &Vec) -> HashMap> { let mut state: HashMap> = HashMap::new(); for backup in backups { @@ -46,14 +62,6 @@ impl Backup { state } - /// Return the path to a backup file by properly formatting the data. - pub fn path>(backup_dir: P, start_time: chrono::DateTime) -> PathBuf { - let backup_dir = backup_dir.as_ref(); - - let filename = format!("{}", start_time.format(Self::FILENAME_FORMAT)); - backup_dir.join(filename) - } - /// Create a new Full backup, populated with the given directories. /// /// # Arguments @@ -71,7 +79,7 @@ impl Backup { ) -> io::Result { let start_time = chrono::offset::Utc::now(); - let path = Self::path(backup_dir, start_time); + let path = Backup::path(backup_dir, start_time); let tar_gz = File::create(path)?; let enc = GzEncoder::new(tar_gz, Compression::default()); let mut ar = tar::Builder::new(enc); @@ -96,6 +104,7 @@ impl Backup { type_: BackupType::Full, start_time, delta, + metadata: None, }) } @@ -108,7 +117,7 @@ impl Backup { ) -> io::Result { let start_time = chrono::offset::Utc::now(); - let path = Self::path(backup_dir, start_time); + let path = Backup::path(backup_dir, start_time); let tar_gz = File::create(path)?; let enc = GzEncoder::new(tar_gz, Compression::default()); let mut ar = tar::Builder::new(enc); @@ -145,6 +154,7 @@ impl Backup { type_: BackupType::Incremental, start_time, delta, + metadata: None, }) } } diff --git a/src/cli.rs b/src/cli.rs index 613dfa1..166fa82 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -53,6 +53,12 @@ pub struct Cli { global = true )] pub chains: u64, + /// Type of server + #[arg(long, default_value = "unknown", env = "ALEX_SERVER")] + pub server: ServerType, + /// Version string for the server, e.g. 1.19.4-545 + #[arg(long, default_value = "", env = "ALEX_SERVER_VERSION")] + pub server_version: String, } #[derive(Subcommand)] @@ -66,12 +72,6 @@ pub enum Commands { #[derive(Args)] pub struct RunArgs { - /// Type of server - pub type_: ServerType, - /// Version string for the server, e.g. 1.19.4-545 - #[arg(env = "ALEX_SERVER_VERSION")] - pub server_version: String, - /// Server jar to execute #[arg( long, @@ -103,10 +103,4 @@ pub struct RunArgs { } #[derive(Args)] -pub struct BackupArgs { - /// Type of server - pub type_: ServerType, - /// Version string for the server, e.g. 1.19.4-545 - #[arg(env = "ALEX_SERVER_VERSION")] - pub server_version: String, -} +pub struct BackupArgs {} diff --git a/src/main.rs b/src/main.rs index 40d112c..ffb0c93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ fn backups_thread(counter: Arc>, frequency: u64) { fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { let (_, mut signals) = signals::install_signal_handlers()?; - let mut cmd = server::ServerCommand::new(args.type_, &args.server_version) + let mut cmd = server::ServerCommand::new(cli.server, &cli.server_version) .java(&args.java) .jar(args.jar.clone()) .config(cli.config.clone()) @@ -60,10 +60,16 @@ fn command_run(cli: &Cli, args: &RunArgs) -> io::Result<()> { } 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 manager = backup::Manager::new( cli.backup.clone(), cli.config.clone(), cli.world.clone(), + metadata, cli.chain_len, cli.chains, ); diff --git a/src/server/command.rs b/src/server/command.rs index 808d6bf..d06f8bc 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -1,14 +1,16 @@ use crate::backup::Manager as BackupManager; use crate::server::ServerProcess; use clap::ValueEnum; +use serde::{Deserialize, Serialize}; 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)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)] pub enum ServerType { + Unknown, Paper, Forge, Vanilla, @@ -17,6 +19,7 @@ pub enum ServerType { 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", @@ -26,6 +29,12 @@ impl fmt::Display for ServerType { } } +#[derive(Clone, Serialize, Deserialize)] +pub struct Metadata { + pub server_type: ServerType, + pub server_version: String, +} + pub struct ServerCommand { type_: ServerType, version: String, @@ -187,10 +196,15 @@ impl ServerCommand { } pub fn spawn(&mut self) -> std::io::Result { + let metadata = Metadata { + server_type: self.type_, + server_version: self.version.clone(), + }; let mut manager = BackupManager::new( self.backup_dir.clone(), self.config_dir.clone(), self.world_dir.clone(), + metadata, self.chain_len, self.chains_to_keep, ); @@ -200,12 +214,7 @@ impl ServerCommand { self.accept_eula()?; let child = cmd.spawn()?; - Ok(ServerProcess::new( - self.type_, - self.version.clone(), - manager, - child, - )) + Ok(ServerProcess::new(manager, child)) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index e3e3131..0f01c9a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,5 @@ mod command; mod process; -pub use command::{ServerCommand, ServerType}; +pub use command::{Metadata, ServerCommand, ServerType}; pub use process::ServerProcess; diff --git a/src/server/process.rs b/src/server/process.rs index 3ac7beb..0a09ab0 100644 --- a/src/server/process.rs +++ b/src/server/process.rs @@ -1,25 +1,16 @@ use crate::backup::Manager as BackupManager; -use crate::server::ServerType; +use crate::server::Metadata; use std::io::Write; use std::process::Child; pub struct ServerProcess { - type_: ServerType, - version: String, child: Child, - backups: BackupManager, + backups: BackupManager, } impl ServerProcess { - pub fn new( - type_: ServerType, - version: String, - manager: BackupManager, - child: Child, - ) -> ServerProcess { + pub fn new(manager: BackupManager, child: Child) -> ServerProcess { ServerProcess { - type_, - version, child, backups: manager, }