feat: persistently store backup state
Some checks failed
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/clippy Pipeline failed
ci/woodpecker/push/build Pipeline was successful

This commit is contained in:
Jef Roosens 2023-06-15 20:36:46 +02:00
parent d204c68400
commit 8add96b39b
Signed by: Jef Roosens
GPG key ID: B75D4F293C7052DB
5 changed files with 121 additions and 34 deletions

View file

@ -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(())
}
}

View file

@ -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,
))
}

View file

@ -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,
}
}