feat: persistently store backup state
parent
d204c68400
commit
8add96b39b
|
@ -15,6 +15,8 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
"tar",
|
"tar",
|
||||||
]
|
]
|
||||||
|
@ -123,6 +125,7 @@ dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"time",
|
"time",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
@ -292,6 +295,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.63"
|
version = "0.3.63"
|
||||||
|
@ -384,6 +393,43 @@ dependencies = [
|
||||||
"windows-sys",
|
"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]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
|
|
|
@ -12,10 +12,11 @@ edition = "2021"
|
||||||
tar = "0.4.38"
|
tar = "0.4.38"
|
||||||
# Used to compress said tarballs using gzip
|
# Used to compress said tarballs using gzip
|
||||||
flate2 = "1.0.26"
|
flate2 = "1.0.26"
|
||||||
# Used for backup filenames
|
chrono = { version = "0.4.26", features = ["serde"] }
|
||||||
chrono = "0.4.26"
|
|
||||||
clap = { version = "4.3.1", features = ["derive", "env"] }
|
clap = { version = "4.3.1", features = ["derive", "env"] }
|
||||||
signal-hook = "0.3.15"
|
signal-hook = "0.3.15"
|
||||||
|
serde = { version = "1.0.164", features = ["derive", "rc"] }
|
||||||
|
serde_json = "1.0.96"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use chrono::{Local, Utc};
|
use chrono::{Local, Utc};
|
||||||
use flate2::write::GzEncoder;
|
use flate2::write::GzEncoder;
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
@ -60,7 +61,7 @@ fn not_modified_since<T: AsRef<Path>>(time: chrono::DateTime<Utc>, path: T) -> b
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum BackupType {
|
pub enum BackupType {
|
||||||
Full,
|
Full,
|
||||||
Incremental,
|
Incremental,
|
||||||
|
@ -74,7 +75,7 @@ pub enum BackupError {
|
||||||
type BackupResult<T> = Result<T, BackupError>;
|
type BackupResult<T> = Result<T, BackupError>;
|
||||||
|
|
||||||
/// Represents the changes relative to the previous backup
|
/// Represents the changes relative to the previous backup
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct BackupDelta {
|
pub struct BackupDelta {
|
||||||
/// What files were added/modified in each part of the tarball.
|
/// What files were added/modified in each part of the tarball.
|
||||||
pub added: HashMap<PathBuf, HashSet<PathBuf>>,
|
pub added: HashMap<PathBuf, HashSet<PathBuf>>,
|
||||||
|
@ -146,8 +147,9 @@ impl BackupDelta {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a successful backup
|
/// Represents a successful backup
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Backup {
|
pub struct Backup {
|
||||||
|
#[serde(skip)]
|
||||||
previous: Option<Arc<Backup>>,
|
previous: Option<Arc<Backup>>,
|
||||||
/// When the backup was started (also corresponds to the name)
|
/// When the backup was started (also corresponds to the name)
|
||||||
start_time: chrono::DateTime<Utc>,
|
start_time: chrono::DateTime<Utc>,
|
||||||
|
@ -174,6 +176,11 @@ impl Backup {
|
||||||
return Err(BackupError::NoFullAncestor);
|
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.
|
/// Create a new Full backup, populated with the given directories.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
@ -282,7 +289,9 @@ pub struct BackupManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackupManager {
|
impl BackupManager {
|
||||||
pub fn open(
|
const METADATA_FILE: &str = "alex.json";
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
backup_dir: PathBuf,
|
backup_dir: PathBuf,
|
||||||
config_dir: PathBuf,
|
config_dir: PathBuf,
|
||||||
world_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<()> {
|
pub fn create_backup(&mut self) -> io::Result<()> {
|
||||||
let dirs = vec![
|
let dirs = vec![
|
||||||
(PathBuf::from("config"), self.config_dir.clone()),
|
(PathBuf::from("config"), self.config_dir.clone()),
|
||||||
|
@ -310,6 +331,7 @@ impl BackupManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.last_backup = Some(Arc::new(backup));
|
self.last_backup = Some(Arc::new(backup));
|
||||||
|
self.write_json()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -337,4 +359,40 @@ impl BackupManager {
|
||||||
|
|
||||||
Ok(())
|
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 clap::ValueEnum;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -179,6 +179,12 @@ impl ServerCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(&mut self) -> std::io::Result<ServerProcess> {
|
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();
|
let mut cmd = self.create_cmd();
|
||||||
self.accept_eula()?;
|
self.accept_eula()?;
|
||||||
let child = cmd.spawn()?;
|
let child = cmd.spawn()?;
|
||||||
|
@ -186,10 +192,7 @@ impl ServerCommand {
|
||||||
Ok(ServerProcess::new(
|
Ok(ServerProcess::new(
|
||||||
self.type_,
|
self.type_,
|
||||||
self.version.clone(),
|
self.version.clone(),
|
||||||
self.config_dir.clone(),
|
manager,
|
||||||
self.world_dir.clone(),
|
|
||||||
self.backup_dir.clone(),
|
|
||||||
self.max_backups,
|
|
||||||
child,
|
child,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
use crate::server::BackupManager;
|
use crate::server::BackupManager;
|
||||||
use crate::server::ServerType;
|
use crate::server::ServerType;
|
||||||
use flate2::write::GzEncoder;
|
|
||||||
use flate2::Compression;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process::Child;
|
use std::process::Child;
|
||||||
|
|
||||||
pub struct ServerProcess {
|
pub struct ServerProcess {
|
||||||
type_: ServerType,
|
type_: ServerType,
|
||||||
version: String,
|
version: String,
|
||||||
config_dir: PathBuf,
|
|
||||||
world_dir: PathBuf,
|
|
||||||
backup_dir: PathBuf,
|
|
||||||
max_backups: u64,
|
|
||||||
child: Child,
|
child: Child,
|
||||||
backups: BackupManager,
|
backups: BackupManager,
|
||||||
}
|
}
|
||||||
|
@ -21,28 +14,14 @@ impl ServerProcess {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
type_: ServerType,
|
type_: ServerType,
|
||||||
version: String,
|
version: String,
|
||||||
config_dir: PathBuf,
|
manager: BackupManager,
|
||||||
world_dir: PathBuf,
|
|
||||||
backup_dir: PathBuf,
|
|
||||||
max_backups: u64,
|
|
||||||
child: Child,
|
child: Child,
|
||||||
) -> ServerProcess {
|
) -> ServerProcess {
|
||||||
let backup_manager = BackupManager::open(
|
|
||||||
backup_dir.clone(),
|
|
||||||
config_dir.clone(),
|
|
||||||
world_dir.clone(),
|
|
||||||
max_backups,
|
|
||||||
);
|
|
||||||
|
|
||||||
ServerProcess {
|
ServerProcess {
|
||||||
type_,
|
type_,
|
||||||
version,
|
version,
|
||||||
config_dir,
|
|
||||||
world_dir,
|
|
||||||
backup_dir,
|
|
||||||
max_backups,
|
|
||||||
child,
|
child,
|
||||||
backups: backup_manager,
|
backups: manager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue