feat: define delta difference & strict difference

export-backup
Jef Roosens 2023-07-04 18:51:31 +02:00
parent 75e9d7a9d2
commit 6e216aa88f
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
2 changed files with 92 additions and 10 deletions

View File

@ -4,7 +4,7 @@ use std::fmt;
use std::path::PathBuf;
/// Represents the changes relative to the previous backup
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Delta {
/// What files were added/modified in each part of the tarball.
pub added: HashMap<PathBuf, HashSet<PathBuf>>,
@ -23,38 +23,44 @@ impl Delta {
}
}
/// Update the current state so that its result becomes the merge of itself and the other
/// state.
/// Calculate the union of this delta with another delta.
///
/// The union of two deltas is a delta that produces the same state as if you were to apply
/// both deltas in-order. Note that this operation is not commutative.
#[allow(dead_code)]
pub fn merge(&mut self, delta: &Self) {
pub fn union(&self, delta: &Self) -> Self {
let mut out = self.clone();
for (dir, added) in delta.added.iter() {
// Files that were removed in the current state, but added in the new state, are no
// longer removed
if let Some(orig_removed) = self.removed.get_mut(dir) {
if let Some(orig_removed) = out.removed.get_mut(dir) {
orig_removed.retain(|k| !added.contains(k));
}
// Newly added files are added to the state as well
if let Some(orig_added) = self.added.get_mut(dir) {
if let Some(orig_added) = out.added.get_mut(dir) {
orig_added.extend(added.iter().cloned());
} else {
self.added.insert(dir.clone(), added.clone());
out.added.insert(dir.clone(), added.clone());
}
}
for (dir, removed) in delta.removed.iter() {
// Files that were originally added, but now deleted are removed from the added list
if let Some(orig_added) = self.added.get_mut(dir) {
if let Some(orig_added) = out.added.get_mut(dir) {
orig_added.retain(|k| !removed.contains(k));
}
// Newly removed files are added to the state as well
if let Some(orig_removed) = self.removed.get_mut(dir) {
if let Some(orig_removed) = out.removed.get_mut(dir) {
orig_removed.extend(removed.iter().cloned());
} else {
self.removed.insert(dir.clone(), removed.clone());
out.removed.insert(dir.clone(), removed.clone());
}
}
out
}
/// Modify the given state by applying this delta's changes to it
@ -74,6 +80,75 @@ impl Delta {
}
}
}
// Calculate the difference between this delta and the other delta.
//
// The difference simply means removing all adds and removes that are also performed in the
// other delta.
pub fn difference(&self, other: &Self) -> Self {
let mut out = self.clone();
for (dir, added) in out.added.iter_mut() {
// If files are added in the other delta, we don't add them in this delta
if let Some(other_added) = other.added.get(dir) {
added.retain(|k| !other_added.contains(k));
};
}
for (dir, removed) in out.removed.iter_mut() {
// If files are removed in the other delta, we don't remove them in this delta either
if let Some(other_removed) = other.removed.get(dir) {
removed.retain(|k| !other_removed.contains(k));
}
}
out
}
// Calculate the strict difference between this delta and the other delta.
//
// The strict difference is a difference where all operations that would be overwritten by the
// other delta are also removed (a.k.a. adding a file after removing it, or vice versa)
pub fn strict_difference(&self, other: &Self) -> Self {
let mut out = self.difference(other);
for (dir, added) in out.added.iter_mut() {
// Remove additions that are removed in the other delta
if let Some(other_removed) = other.removed.get(dir) {
added.retain(|k| !other_removed.contains(k));
}
}
for (dir, removed) in out.removed.iter_mut() {
// Remove removals that are re-added in the other delta
if let Some(other_added) = other.added.get(dir) {
removed.retain(|k| !other_added.contains(k));
}
}
out
}
/// Given a list of deltas, return a new list of the same length containing each delta, but
/// with all its successive deltas subtracted from it. This resulting list would produce the
/// same delta if merged, but with each delta only representing the parts of the final state
/// that it is the sole contributor to.
pub fn disjunct(deltas: &Vec<Self>) -> Vec<Delta> {
let mut contributions = Vec::with_capacity(deltas.len());
for (i, delta) in deltas.iter().enumerate() {
// The base case for the contributions is every added file
let mut contribution = delta.clone();
for other_delta in &deltas[i + 1..] {
contribution = contribution.difference(other_delta);
}
contributions.push(contribution);
}
contributions
}
}
impl fmt::Display for Delta {

View File

@ -247,6 +247,13 @@ impl<T: Clone> Backup<T> {
Ok(())
}
pub fn open<P: AsRef<Path>>(&self, backup_dir: P) -> io::Result<tar::Archive<GzDecoder<File>>> {
let path = Backup::path(backup_dir, self.start_time);
let tar_gz = File::open(path)?;
let enc = GzDecoder::new(tar_gz);
Ok(tar::Archive::new(enc))
}
}
impl<T: Clone> fmt::Display for Backup<T> {