feat: define delta difference & strict difference
parent
75e9d7a9d2
commit
6e216aa88f
|
@ -4,7 +4,7 @@ use std::fmt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Represents the changes relative to the previous backup
|
/// Represents the changes relative to the previous backup
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Delta {
|
pub struct Delta {
|
||||||
/// What files were added/modified in each part of the tarball.
|
/// What files were added/modified in each part of the tarball.
|
||||||
pub added: HashMap<PathBuf, HashSet<PathBuf>>,
|
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
|
/// Calculate the union of this delta with another delta.
|
||||||
/// state.
|
///
|
||||||
|
/// 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)]
|
#[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() {
|
for (dir, added) in delta.added.iter() {
|
||||||
// Files that were removed in the current state, but added in the new state, are no
|
// Files that were removed in the current state, but added in the new state, are no
|
||||||
// longer removed
|
// 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));
|
orig_removed.retain(|k| !added.contains(k));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Newly added files are added to the state as well
|
// 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());
|
orig_added.extend(added.iter().cloned());
|
||||||
} else {
|
} else {
|
||||||
self.added.insert(dir.clone(), added.clone());
|
out.added.insert(dir.clone(), added.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (dir, removed) in delta.removed.iter() {
|
for (dir, removed) in delta.removed.iter() {
|
||||||
// Files that were originally added, but now deleted are removed from the added list
|
// 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));
|
orig_added.retain(|k| !removed.contains(k));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Newly removed files are added to the state as well
|
// 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());
|
orig_removed.extend(removed.iter().cloned());
|
||||||
} else {
|
} 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
|
/// 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 {
|
impl fmt::Display for Delta {
|
||||||
|
|
|
@ -247,6 +247,13 @@ impl<T: Clone> Backup<T> {
|
||||||
|
|
||||||
Ok(())
|
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> {
|
impl<T: Clone> fmt::Display for Backup<T> {
|
||||||
|
|
Loading…
Reference in New Issue