From 8add96b39bb0050a21e4ed16d66f9781005e9479 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 15 Jun 2023 20:36:46 +0200 Subject: [PATCH] feat: persistently store backup state --- Cargo.lock | 46 ++++++++++++++++++++++++++++++ Cargo.toml | 5 ++-- src/server/backups.rs | 66 ++++++++++++++++++++++++++++++++++++++++--- src/server/command.rs | 13 +++++---- src/server/process.rs | 25 ++-------------- 5 files changed, 121 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e798dd..5ba48c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,8 @@ dependencies = [ "chrono", "clap", "flate2", + "serde", + "serde_json", "signal-hook", "tar", ] @@ -123,6 +125,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "time", "wasm-bindgen", "winapi", @@ -292,6 +295,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + [[package]] name = "js-sys" version = "0.3.63" @@ -384,6 +393,43 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "serde" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "signal-hook" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index 2c4045b..2ac6b39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,11 @@ edition = "2021" tar = "0.4.38" # Used to compress said tarballs using gzip flate2 = "1.0.26" -# Used for backup filenames -chrono = "0.4.26" +chrono = { version = "0.4.26", features = ["serde"] } clap = { version = "4.3.1", features = ["derive", "env"] } signal-hook = "0.3.15" +serde = { version = "1.0.164", features = ["derive", "rc"] } +serde_json = "1.0.96" [profile.release] lto = "fat" diff --git a/src/server/backups.rs b/src/server/backups.rs index 2620e17..8012b5f 100644 --- a/src/server/backups.rs +++ b/src/server/backups.rs @@ -1,6 +1,7 @@ use chrono::{Local, Utc}; use flate2::write::GzEncoder; use flate2::Compression; +use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io; @@ -60,7 +61,7 @@ fn not_modified_since>(time: chrono::DateTime, path: T) -> b false } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize, Deserialize)] pub enum BackupType { Full, Incremental, @@ -74,7 +75,7 @@ pub enum BackupError { type BackupResult = Result; /// Represents the changes relative to the previous backup -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct BackupDelta { /// What files were added/modified in each part of the tarball. pub added: HashMap>, @@ -146,8 +147,9 @@ impl BackupDelta { } /// Represents a successful backup -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct Backup { + #[serde(skip)] previous: Option>, /// When the backup was started (also corresponds to the name) start_time: chrono::DateTime, @@ -174,6 +176,11 @@ impl Backup { return Err(BackupError::NoFullAncestor); } } + + pub fn set_previous(&mut self, previous: Arc) { + self.previous = Some(previous); + } + /// Create a new Full backup, populated with the given directories. /// /// # Arguments @@ -282,7 +289,9 @@ pub struct BackupManager { } impl BackupManager { - pub fn open( + const METADATA_FILE: &str = "alex.json"; + + pub fn new( backup_dir: PathBuf, config_dir: PathBuf, world_dir: PathBuf, @@ -297,6 +306,18 @@ impl BackupManager { } } + pub fn open( + backup_dir: PathBuf, + config_dir: PathBuf, + world_dir: PathBuf, + max_backups: u64, + ) -> std::io::Result { + let mut manager = Self::new(backup_dir, config_dir, world_dir, max_backups); + manager.load_json()?; + + Ok(manager) + } + pub fn create_backup(&mut self) -> io::Result<()> { let dirs = vec![ (PathBuf::from("config"), self.config_dir.clone()), @@ -310,6 +331,7 @@ impl BackupManager { }; self.last_backup = Some(Arc::new(backup)); + self.write_json()?; Ok(()) } @@ -337,4 +359,40 @@ impl BackupManager { Ok(()) } + + pub fn write_json(&self) -> std::io::Result<()> { + // Put the backup chain into a list that can be serialized + let mut backups: Vec> = Vec::new(); + let mut backup_opt = &self.last_backup; + + while let Some(backup) = backup_opt { + backups.insert(0, Arc::clone(backup)); + backup_opt = &backup.previous; + } + + let json_file = File::create(self.backup_dir.join(Self::METADATA_FILE))?; + serde_json::to_writer(json_file, &backups)?; + + Ok(()) + } + + pub fn load_json(&mut self) -> std::io::Result<()> { + let json_file = File::open(self.backup_dir.join(Self::METADATA_FILE))?; + let mut backups: Vec> = serde_json::from_reader(json_file)?; + + if !backups.is_empty() { + for i in 1..backups.len() { + let previous = Arc::clone(&backups[i - 1]); + // We can unwrap here, as this function creates the first instance of each Arc, + // meaning we're definitely the only pointer. + Arc::get_mut(&mut backups[i]) + .unwrap() + .set_previous(previous); + } + + self.last_backup = Some(Arc::clone(backups.last().unwrap())); + } + + Ok(()) + } } diff --git a/src/server/command.rs b/src/server/command.rs index 641c6b8..1c44d5b 100644 --- a/src/server/command.rs +++ b/src/server/command.rs @@ -1,4 +1,4 @@ -use crate::server::ServerProcess; +use crate::server::{BackupManager, ServerProcess}; use clap::ValueEnum; use std::fmt; use std::fs::File; @@ -179,6 +179,12 @@ impl ServerCommand { } pub fn spawn(&mut self) -> std::io::Result { + let manager = BackupManager::open( + self.backup_dir.clone(), + self.config_dir.clone(), + self.world_dir.clone(), + self.max_backups, + )?; let mut cmd = self.create_cmd(); self.accept_eula()?; let child = cmd.spawn()?; @@ -186,10 +192,7 @@ impl ServerCommand { Ok(ServerProcess::new( self.type_, self.version.clone(), - self.config_dir.clone(), - self.world_dir.clone(), - self.backup_dir.clone(), - self.max_backups, + manager, child, )) } diff --git a/src/server/process.rs b/src/server/process.rs index 6edc484..fbd806a 100644 --- a/src/server/process.rs +++ b/src/server/process.rs @@ -1,18 +1,11 @@ use crate::server::BackupManager; use crate::server::ServerType; -use flate2::write::GzEncoder; -use flate2::Compression; use std::io::Write; -use std::path::{Path, PathBuf}; use std::process::Child; pub struct ServerProcess { type_: ServerType, version: String, - config_dir: PathBuf, - world_dir: PathBuf, - backup_dir: PathBuf, - max_backups: u64, child: Child, backups: BackupManager, } @@ -21,28 +14,14 @@ impl ServerProcess { pub fn new( type_: ServerType, version: String, - config_dir: PathBuf, - world_dir: PathBuf, - backup_dir: PathBuf, - max_backups: u64, + manager: BackupManager, child: Child, ) -> ServerProcess { - let backup_manager = BackupManager::open( - backup_dir.clone(), - config_dir.clone(), - world_dir.clone(), - max_backups, - ); - ServerProcess { type_, version, - config_dir, - world_dir, - backup_dir, - max_backups, child, - backups: backup_manager, + backups: manager, } }