feat: define delta difference & strict difference
parent
75e9d7a9d2
commit
6e216aa88f
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Reference in New Issue