From 6e216aa88f86923faa8e10dc7ee24dc7fb6e635b Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 4 Jul 2023 18:51:31 +0200 Subject: [PATCH] feat: define delta difference & strict difference --- src/backup/delta.rs | 95 ++++++++++++++++++++++++++++++++++++++++----- src/backup/mod.rs | 7 ++++ 2 files changed, 92 insertions(+), 10 deletions(-) diff --git a/src/backup/delta.rs b/src/backup/delta.rs index 6e37e96..b90ddfb 100644 --- a/src/backup/delta.rs +++ b/src/backup/delta.rs @@ -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>, @@ -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) -> Vec { + 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 { diff --git a/src/backup/mod.rs b/src/backup/mod.rs index a4081f9..4515ba1 100644 --- a/src/backup/mod.rs +++ b/src/backup/mod.rs @@ -247,6 +247,13 @@ impl Backup { Ok(()) } + + pub fn open>(&self, backup_dir: P) -> io::Result>> { + 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 fmt::Display for Backup {