refactor: have fun with rust's functional stuff
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details

export-backup
Jef Roosens 2023-07-08 13:30:11 +02:00
parent fc8e8d37d3
commit 1acfc9c422
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
3 changed files with 67 additions and 50 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased](https://git.rustybever.be/Chewing_Bever/alex/src/branch/dev)
### Added
* Export command to export any backup as a new full backup
## [0.3.0](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.3.0)
### Added

View File

@ -4,7 +4,7 @@ mod meta;
pub use config::ManagerConfig;
pub use meta::MetaManager;
use super::{Backup, Delta, State};
use super::{Backup, BackupType, Delta, State};
use crate::other;
use chrono::SubsecRound;
use chrono::Utc;
@ -162,6 +162,25 @@ where
chrono::offset::Utc::now()
}
/// Search for a chain containing a backup with the specified start time.
///
/// # Returns
///
/// A tuple (chain, index) with index being the index of the found backup in the returned
/// chain.
fn find(&self, start_time: chrono::DateTime<Utc>) -> Option<(&Vec<Backup<T>>, usize)> {
for chain in &self.chains {
if let Some(index) = chain
.iter()
.position(|b| b.start_time.trunc_subsecs(0) == start_time)
{
return Some((chain, index));
}
}
None
}
/// Restore the backup with the given start time by restoring its chain up to and including the
/// backup, in order.
pub fn restore_backup(
@ -169,62 +188,57 @@ where
start_time: chrono::DateTime<Utc>,
dirs: &Vec<(PathBuf, PathBuf)>,
) -> io::Result<()> {
// Iterate over each chain, skipping elements until the element with the given start time
// is possibly found.
for chain in &self.chains {
// If we find the element in the chain, restore the entire chain up to and including
// the element
if let Some(index) = chain
.iter()
.position(|b| b.start_time.trunc_subsecs(0) == start_time)
{
self.find(start_time)
.ok_or_else(|| other("Unknown layer."))
.and_then(|(chain, index)| {
for backup in chain.iter().take(index + 1) {
backup.restore(&self.backup_dir, dirs)?;
}
return Ok(());
}
}
Err(other("Unknown backup."))
Ok(())
})
}
/// Export the backup with the given start time as a new full archive.
pub fn export_backup<P: AsRef<Path>>(
&self,
start_time: chrono::DateTime<Utc>,
output_path: P,
) -> io::Result<()> {
// Iterate over each chain, skipping elements until the element with the given start time
// is possibly found.
for chain in &self.chains {
// If we find the element in the chain, restore the entire chain up to and including
// the element
if let Some(index) = chain
.iter()
.position(|b| b.start_time.trunc_subsecs(0) == start_time)
{
let contributions =
Delta::contributions(chain.iter().take(index + 1).map(|b| &b.delta).rev());
self.find(start_time)
.ok_or_else(|| other("Unknown layer."))
.and_then(|(chain, index)| {
match chain[index].type_ {
// A full backup is simply copied to the output path
BackupType::Full => std::fs::copy(
Backup::path(&self.backup_dir, chain[index].start_time),
output_path,
)
.map(|_| ()),
// Incremental backups are exported one by one according to their contribution
BackupType::Incremental => {
let contributions = Delta::contributions(
chain.iter().take(index + 1).map(|b| &b.delta).rev(),
);
let tar_gz = OpenOptions::new()
.write(true)
.create(true)
.open(output_path.as_ref())?;
let enc = GzEncoder::new(tar_gz, Compression::default());
let mut ar = tar::Builder::new(enc);
let tar_gz = OpenOptions::new()
.write(true)
.create(true)
.open(output_path.as_ref())?;
let enc = GzEncoder::new(tar_gz, Compression::default());
let mut ar = tar::Builder::new(enc);
for (contribution, backup) in
contributions.iter().rev().zip(chain.iter().take(index + 1))
{
backup.append(&self.backup_dir, contribution, &mut ar)?;
for (contribution, backup) in
contributions.iter().rev().zip(chain.iter().take(index + 1))
{
backup.append(&self.backup_dir, contribution, &mut ar)?;
}
let mut enc = ar.into_inner()?;
enc.try_finish()
}
}
let mut enc = ar.into_inner()?;
return enc.try_finish();
}
}
Err(other("Unknown backup."))
})
}
/// Get a reference to the underlying chains

View File

@ -129,16 +129,15 @@ pub trait PathExt {
impl PathExt for Path {
fn not_modified_since(&self, timestamp: chrono::DateTime<Utc>) -> bool {
if let Ok(metadata) = self.metadata() {
if let Ok(last_modified) = metadata.modified() {
self.metadata()
.and_then(|m| m.modified())
.map(|last_modified| {
let t: chrono::DateTime<Utc> = last_modified.into();
let t = t.with_timezone(&Local);
return t < timestamp;
}
}
false
t < timestamp
})
.unwrap_or(false)
}
fn read_dir_recursive(&self) -> io::Result<ReadDirRecursive> {