feat: further generalize backup code
parent
03e21fda87
commit
d5cea49c8b
|
@ -6,13 +6,13 @@ use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Manages a collection of backup layers, allowing them to be utilized as a single object.
|
||||||
pub struct MetaManager<T>
|
pub struct MetaManager<T>
|
||||||
where
|
where
|
||||||
T: Clone + Serialize + for<'de> Deserialize<'de>,
|
T: Clone + Serialize + for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
backup_dir: PathBuf,
|
backup_dir: PathBuf,
|
||||||
config_dir: PathBuf,
|
dirs: Vec<(PathBuf, PathBuf)>,
|
||||||
world_dir: PathBuf,
|
|
||||||
default_metadata: T,
|
default_metadata: T,
|
||||||
managers: HashMap<String, Manager<T>>,
|
managers: HashMap<String, Manager<T>>,
|
||||||
}
|
}
|
||||||
|
@ -21,21 +21,20 @@ impl<T> MetaManager<T>
|
||||||
where
|
where
|
||||||
T: Clone + Serialize + for<'de> Deserialize<'de>,
|
T: Clone + Serialize + for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
pub fn new<P1: Into<PathBuf>, P2: Into<PathBuf>, P3: Into<PathBuf>>(
|
pub fn new<P: Into<PathBuf>>(
|
||||||
backup_dir: P1,
|
backup_dir: P,
|
||||||
config_dir: P2,
|
dirs: Vec<(PathBuf, PathBuf)>,
|
||||||
world_dir: P3,
|
|
||||||
default_metadata: T,
|
default_metadata: T,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
MetaManager {
|
MetaManager {
|
||||||
backup_dir: backup_dir.into(),
|
backup_dir: backup_dir.into(),
|
||||||
config_dir: config_dir.into(),
|
dirs,
|
||||||
world_dir: world_dir.into(),
|
|
||||||
default_metadata,
|
default_metadata,
|
||||||
managers: HashMap::new(),
|
managers: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a new manager to track, initializing it first.
|
||||||
pub fn add(&mut self, config: &ManagerConfig) -> io::Result<()> {
|
pub fn add(&mut self, config: &ManagerConfig) -> io::Result<()> {
|
||||||
// Backup dir itself should exist, but we control its contents, so we can create
|
// Backup dir itself should exist, but we control its contents, so we can create
|
||||||
// separate directories for each layer
|
// separate directories for each layer
|
||||||
|
@ -52,8 +51,7 @@ where
|
||||||
|
|
||||||
let mut manager = Manager::new(
|
let mut manager = Manager::new(
|
||||||
path,
|
path,
|
||||||
self.config_dir.clone(),
|
self.dirs.clone(),
|
||||||
self.world_dir.clone(),
|
|
||||||
self.default_metadata.clone(),
|
self.default_metadata.clone(),
|
||||||
config.chain_len,
|
config.chain_len,
|
||||||
config.chains,
|
config.chains,
|
||||||
|
@ -65,6 +63,7 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenient wrapper for `add`.
|
||||||
pub fn add_all(&mut self, configs: &Vec<ManagerConfig>) -> io::Result<()> {
|
pub fn add_all(&mut self, configs: &Vec<ManagerConfig>) -> io::Result<()> {
|
||||||
for config in configs {
|
for config in configs {
|
||||||
self.add(config)?;
|
self.add(config)?;
|
||||||
|
@ -73,6 +72,7 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the name of the next scheduled layer, if one or more managers are present.
|
||||||
pub fn next_scheduled_layer(&self) -> Option<&str> {
|
pub fn next_scheduled_layer(&self) -> Option<&str> {
|
||||||
self.managers
|
self.managers
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -80,6 +80,7 @@ where
|
||||||
.map(|(k, _)| k.as_str())
|
.map(|(k, _)| k.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the earliest scheduled time for the underlying managers.
|
||||||
pub fn next_scheduled_time(&self) -> Option<chrono::DateTime<Utc>> {
|
pub fn next_scheduled_time(&self) -> Option<chrono::DateTime<Utc>> {
|
||||||
self.managers
|
self.managers
|
||||||
.values()
|
.values()
|
||||||
|
@ -87,6 +88,7 @@ where
|
||||||
.min()
|
.min()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform a backup cycle for the earliest scheduled manager.
|
||||||
pub fn perform_backup_cycle(&mut self) -> io::Result<()> {
|
pub fn perform_backup_cycle(&mut self) -> io::Result<()> {
|
||||||
if let Some(manager) = self
|
if let Some(manager) = self
|
||||||
.managers
|
.managers
|
||||||
|
|
|
@ -12,13 +12,13 @@ use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Manages a single backup layer consisting of one or more chains of backups.
|
||||||
pub struct Manager<T>
|
pub struct Manager<T>
|
||||||
where
|
where
|
||||||
T: Clone + Serialize + for<'de> Deserialize<'de>,
|
T: Clone + Serialize + for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
backup_dir: PathBuf,
|
backup_dir: PathBuf,
|
||||||
config_dir: PathBuf,
|
dirs: Vec<(PathBuf, PathBuf)>,
|
||||||
world_dir: PathBuf,
|
|
||||||
default_metadata: T,
|
default_metadata: T,
|
||||||
chain_len: u64,
|
chain_len: u64,
|
||||||
chains_to_keep: u64,
|
chains_to_keep: u64,
|
||||||
|
@ -32,10 +32,9 @@ where
|
||||||
{
|
{
|
||||||
const METADATA_FILE: &str = "alex.json";
|
const METADATA_FILE: &str = "alex.json";
|
||||||
|
|
||||||
pub fn new<P1: Into<PathBuf>, P2: Into<PathBuf>, P3: Into<PathBuf>>(
|
pub fn new<P: Into<PathBuf>>(
|
||||||
backup_dir: P1,
|
backup_dir: P,
|
||||||
config_dir: P2,
|
dirs: Vec<(PathBuf, PathBuf)>,
|
||||||
world_dir: P3,
|
|
||||||
metadata: T,
|
metadata: T,
|
||||||
chain_len: u64,
|
chain_len: u64,
|
||||||
chains_to_keep: u64,
|
chains_to_keep: u64,
|
||||||
|
@ -43,8 +42,7 @@ where
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
backup_dir: backup_dir.into(),
|
backup_dir: backup_dir.into(),
|
||||||
config_dir: config_dir.into(),
|
dirs,
|
||||||
world_dir: world_dir.into(),
|
|
||||||
default_metadata: metadata,
|
default_metadata: metadata,
|
||||||
chain_len,
|
chain_len,
|
||||||
chains_to_keep,
|
chains_to_keep,
|
||||||
|
@ -55,11 +53,6 @@ where
|
||||||
|
|
||||||
/// Create a new backup with the expected type.
|
/// Create a new backup with the expected type.
|
||||||
pub fn create_backup(&mut self) -> io::Result<()> {
|
pub fn create_backup(&mut self) -> io::Result<()> {
|
||||||
let dirs = vec![
|
|
||||||
(PathBuf::from("config"), self.config_dir.clone()),
|
|
||||||
(PathBuf::from("worlds"), self.world_dir.clone()),
|
|
||||||
];
|
|
||||||
|
|
||||||
// We start a new chain if the current chain is complete, or if there isn't a first chain
|
// We start a new chain if the current chain is complete, or if there isn't a first chain
|
||||||
// yet
|
// yet
|
||||||
if let Some(current_chain) = self.chains.last() {
|
if let Some(current_chain) = self.chains.last() {
|
||||||
|
@ -78,9 +71,14 @@ where
|
||||||
let previous_backup = current_chain.last().unwrap();
|
let previous_backup = current_chain.last().unwrap();
|
||||||
let state = Backup::state(current_chain);
|
let state = Backup::state(current_chain);
|
||||||
|
|
||||||
Backup::create_from(state, previous_backup.start_time, &self.backup_dir, dirs)?
|
Backup::create_from(
|
||||||
|
state,
|
||||||
|
previous_backup.start_time,
|
||||||
|
&self.backup_dir,
|
||||||
|
&self.dirs,
|
||||||
|
)?
|
||||||
} else {
|
} else {
|
||||||
Backup::create(&self.backup_dir, dirs)?
|
Backup::create(&self.backup_dir, &self.dirs)?
|
||||||
};
|
};
|
||||||
|
|
||||||
backup.set_metadata(self.default_metadata.clone());
|
backup.set_metadata(self.default_metadata.clone());
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl<T: Clone> Backup<T> {
|
||||||
/// The `Backup` instance describing this new backup.
|
/// The `Backup` instance describing this new backup.
|
||||||
pub fn create<P: AsRef<Path>>(
|
pub fn create<P: AsRef<Path>>(
|
||||||
backup_dir: P,
|
backup_dir: P,
|
||||||
dirs: Vec<(PathBuf, PathBuf)>,
|
dirs: &Vec<(PathBuf, PathBuf)>,
|
||||||
) -> io::Result<Self> {
|
) -> io::Result<Self> {
|
||||||
let start_time = chrono::offset::Utc::now();
|
let start_time = chrono::offset::Utc::now();
|
||||||
|
|
||||||
|
@ -93,13 +93,13 @@ impl<T: Clone> Backup<T> {
|
||||||
|
|
||||||
for entry in src_dir.read_dir_recursive()?.ignored("cache").files() {
|
for entry in src_dir.read_dir_recursive()?.ignored("cache").files() {
|
||||||
let path = entry?.path();
|
let path = entry?.path();
|
||||||
let stripped = path.strip_prefix(&src_dir).unwrap();
|
let stripped = path.strip_prefix(src_dir).unwrap();
|
||||||
|
|
||||||
ar.append_path_with_name(&path, dir_in_tar.join(stripped))?;
|
ar.append_path_with_name(&path, dir_in_tar.join(stripped))?;
|
||||||
added_files.insert(stripped.to_path_buf());
|
added_files.insert(stripped.to_path_buf());
|
||||||
}
|
}
|
||||||
|
|
||||||
delta.added.insert(dir_in_tar, added_files);
|
delta.added.insert(dir_in_tar.to_path_buf(), added_files);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Backup {
|
Ok(Backup {
|
||||||
|
@ -115,7 +115,7 @@ impl<T: Clone> Backup<T> {
|
||||||
previous_state: HashMap<PathBuf, HashSet<PathBuf>>,
|
previous_state: HashMap<PathBuf, HashSet<PathBuf>>,
|
||||||
previous_start_time: chrono::DateTime<Utc>,
|
previous_start_time: chrono::DateTime<Utc>,
|
||||||
backup_dir: P,
|
backup_dir: P,
|
||||||
dirs: Vec<(PathBuf, PathBuf)>,
|
dirs: &Vec<(PathBuf, PathBuf)>,
|
||||||
) -> io::Result<Self> {
|
) -> io::Result<Self> {
|
||||||
let start_time = chrono::offset::Utc::now();
|
let start_time = chrono::offset::Utc::now();
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ impl<T: Clone> Backup<T> {
|
||||||
|
|
||||||
for entry in src_dir.read_dir_recursive()?.ignored("cache").files() {
|
for entry in src_dir.read_dir_recursive()?.ignored("cache").files() {
|
||||||
let path = entry?.path();
|
let path = entry?.path();
|
||||||
let stripped = path.strip_prefix(&src_dir).unwrap();
|
let stripped = path.strip_prefix(src_dir).unwrap();
|
||||||
|
|
||||||
if !path.not_modified_since(previous_start_time) {
|
if !path.not_modified_since(previous_start_time) {
|
||||||
ar.append_path_with_name(&path, dir_in_tar.join(stripped))?;
|
ar.append_path_with_name(&path, dir_in_tar.join(stripped))?;
|
||||||
|
@ -144,9 +144,9 @@ impl<T: Clone> Backup<T> {
|
||||||
|
|
||||||
delta.added.insert(dir_in_tar.clone(), added_files);
|
delta.added.insert(dir_in_tar.clone(), added_files);
|
||||||
|
|
||||||
if let Some(previous_files) = previous_state.get(&dir_in_tar) {
|
if let Some(previous_files) = previous_state.get(dir_in_tar) {
|
||||||
delta.removed.insert(
|
delta.removed.insert(
|
||||||
dir_in_tar,
|
dir_in_tar.to_path_buf(),
|
||||||
previous_files.difference(&all_files).cloned().collect(),
|
previous_files.difference(&all_files).cloned().collect(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,12 +166,13 @@ impl ServerCommand {
|
||||||
server_type: self.type_,
|
server_type: self.type_,
|
||||||
server_version: self.version.clone(),
|
server_version: self.version.clone(),
|
||||||
};
|
};
|
||||||
let mut meta = MetaManager::new(
|
|
||||||
self.backup_dir.clone(),
|
let dirs = vec![
|
||||||
self.config_dir.clone(),
|
(PathBuf::from("config"), self.config_dir.clone()),
|
||||||
self.world_dir.clone(),
|
(PathBuf::from("worlds"), self.world_dir.clone()),
|
||||||
metadata,
|
];
|
||||||
);
|
|
||||||
|
let mut meta = MetaManager::new(self.backup_dir.clone(), dirs, metadata);
|
||||||
meta.add_all(&self.managers)?;
|
meta.add_all(&self.managers)?;
|
||||||
|
|
||||||
let mut cmd = self.create_cmd();
|
let mut cmd = self.create_cmd();
|
||||||
|
|
Loading…
Reference in New Issue