feat: persistently store backup state
parent
d204c68400
commit
8add96b39b
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<T: AsRef<Path>>(time: chrono::DateTime<Utc>, 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<T> = Result<T, BackupError>;
|
||||
|
||||
/// 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<PathBuf, HashSet<PathBuf>>,
|
||||
|
@ -146,8 +147,9 @@ impl BackupDelta {
|
|||
}
|
||||
|
||||
/// Represents a successful backup
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Backup {
|
||||
#[serde(skip)]
|
||||
previous: Option<Arc<Backup>>,
|
||||
/// When the backup was started (also corresponds to the name)
|
||||
start_time: chrono::DateTime<Utc>,
|
||||
|
@ -174,6 +176,11 @@ impl Backup {
|
|||
return Err(BackupError::NoFullAncestor);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_previous(&mut self, previous: Arc<Self>) {
|
||||
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<Self> {
|
||||
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<Arc<Backup>> = 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<Arc<Backup>> = 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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ServerProcess> {
|
||||
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,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue