Skip to content

Commit

Permalink
feat: Add Repository method to update snapshot collections (#260)
Browse files Browse the repository at this point in the history
This PR adds the `update_snapshots`, `update_all_snapshots` and
`update_matching_snapshots` methods to `Repository`.

They yield the identical result as the corresponding `get_*` methods,
but allow to give a list of existing `SnapshotFile`s and won't read
those again. In interactive UIs (like rustic interactive mode) this
allows for very snapshot refreshing - which is almost costless if no or
few snapshots have changed in the repository.
  • Loading branch information
aawsome authored Sep 30, 2024
1 parent d5f5f54 commit 7668364
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 9 deletions.
95 changes: 89 additions & 6 deletions crates/core/src/repofile/snapshotfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use serde_with::{serde_as, skip_serializing_none, DisplayFromStr};
use crate::{
backend::{decrypt::DecryptReadBackend, FileType, FindInBackend},
blob::tree::TreeId,
error::{RusticError, RusticResult, SnapshotFileErrorKind},
error::{RusticError, RusticErrorKind, RusticResult, SnapshotFileErrorKind},
impl_repofile,
progress::Progress,
repofile::RepoFile,
Expand Down Expand Up @@ -550,14 +550,65 @@ impl SnapshotFile {
be: &B,
ids: &[T],
p: &impl Progress,
) -> RusticResult<Vec<Self>> {
Self::update_from_ids(be, Vec::new(), ids, p)
}

/// Update a list of [`SnapshotFile`]s from the backend by supplying a list of/parts of their Ids
///
/// # Arguments
///
/// * `be` - The backend to use
/// * `ids` - The list of (parts of the) ids of the snapshots
/// * `p` - A progress bar to use
///
/// # Errors
///
/// * [`IdErrorKind::HexError`] - If the string is not a valid hexadecimal string
/// * [`BackendAccessErrorKind::NoSuitableIdFound`] - If no id could be found.
/// * [`BackendAccessErrorKind::IdNotUnique`] - If the id is not unique.
///
/// [`IdErrorKind::HexError`]: crate::error::IdErrorKind::HexError
/// [`BackendAccessErrorKind::NoSuitableIdFound`]: crate::error::BackendAccessErrorKind::NoSuitableIdFound
/// [`BackendAccessErrorKind::IdNotUnique`]: crate::error::BackendAccessErrorKind::IdNotUnique
pub(crate) fn update_from_ids<B: DecryptReadBackend, T: AsRef<str>>(
be: &B,
current: Vec<Self>,
ids: &[T],
p: &impl Progress,
) -> RusticResult<Vec<Self>> {
let ids = be.find_ids(FileType::Snapshot, ids)?;
let mut list: BTreeMap<_, _> =
be.stream_list::<Self>(&ids, p)?.into_iter().try_collect()?;
Self::fill_missing(be, current, &ids, |_| true, p)
}

// helper func
fn fill_missing<B, F>(
be: &B,
current: Vec<Self>,
ids: &[Id],
mut filter: F,
p: &impl Progress,
) -> RusticResult<Vec<Self>>
where
B: DecryptReadBackend,
F: FnMut(&Self) -> bool,
{
let mut snaps: BTreeMap<_, _> = current.into_iter().map(|snap| (snap.id, snap)).collect();
let missing_ids: Vec<_> = ids
.iter()
.filter(|id| !snaps.contains_key(&SnapshotId::from(**id)))
.copied()
.collect();
for res in be.stream_list::<Self>(&missing_ids, p)? {
let (id, snap) = res?;
if filter(&snap) {
let _ = snaps.insert(id, snap);
}
}
// sort back to original order
Ok(ids
.into_iter()
.filter_map(|id| list.remove_entry(&SnapshotId::from(id)))
.iter()
.filter_map(|id| snaps.remove_entry(&SnapshotId::from(*id)))
.map(Self::set_id)
.collect())
}
Expand Down Expand Up @@ -637,7 +688,22 @@ impl SnapshotFile {
B: DecryptReadBackend,
F: FnMut(&Self) -> bool,
{
let mut snaps = Self::all_from_backend(be, filter, p)?;
Self::update_group_from_backend(be, Vec::new(), filter, crit, p)
}

pub(crate) fn update_group_from_backend<B, F>(
be: &B,
current: Vec<(SnapshotGroup, Vec<Self>)>,
filter: F,
crit: SnapshotGroupCriterion,
p: &impl Progress,
) -> RusticResult<Vec<(SnapshotGroup, Vec<Self>)>>
where
B: DecryptReadBackend,
F: FnMut(&Self) -> bool,
{
let current = current.into_iter().flat_map(|(_, snaps)| snaps).collect();
let mut snaps = Self::update_from_backend(be, current, filter, p)?;
snaps.sort_unstable_by(|sn1, sn2| sn1.cmp_group(crit, sn2));

let mut result = Vec::new();
Expand Down Expand Up @@ -668,6 +734,23 @@ impl SnapshotFile {
.try_collect()
}

// TODO: add documentation!
pub(crate) fn update_from_backend<B, F>(
be: &B,
current: Vec<Self>,
filter: F,
p: &impl Progress,
) -> RusticResult<Vec<Self>>
where
B: DecryptReadBackend,
F: FnMut(&Self) -> bool,
{
let ids = be
.list(FileType::Snapshot)
.map_err(RusticErrorKind::Backend)?;
Self::fill_missing(be, current, &ids, filter, p)
}

/// Add tag lists to snapshot.
///
/// # Arguments
Expand Down
64 changes: 62 additions & 2 deletions crates/core/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -949,8 +949,31 @@ impl<P: ProgressBars, S: Open> Repository<P, S> {
///
// TODO: Document errors
pub fn get_snapshots<T: AsRef<str>>(&self, ids: &[T]) -> RusticResult<Vec<SnapshotFile>> {
self.update_snapshots(Vec::new(), ids)
}

/// Update the given snapshots.
///
/// # Arguments
///
/// * `current` - The existing snapshots
/// * `ids` - The ids of the snapshots to get
///
/// # Notes
///
/// `ids` may contain part of snapshots id which will be resolved.
/// However, "latest" is not supported in this function.
///
/// # Errors
///
// TODO: Document errors
pub fn update_snapshots<T: AsRef<str>>(
&self,
current: Vec<SnapshotFile>,
ids: &[T],
) -> RusticResult<Vec<SnapshotFile>> {
let p = self.pb.progress_counter("getting snapshots...");
let result = SnapshotFile::from_ids(self.dbe(), ids, &p);
let result = SnapshotFile::update_from_ids(self.dbe(), current, ids, &p);
p.finish();
result
}
Expand All @@ -964,6 +987,22 @@ impl<P: ProgressBars, S: Open> Repository<P, S> {
self.get_matching_snapshots(|_| true)
}

/// Update existing snapshots to all from the repository
///
/// # Arguments
///
/// * `current` - The existing snapshots
///
/// # Errors
///
// TODO: Document errors
pub fn update_all_snapshots(
&self,
current: Vec<SnapshotFile>,
) -> RusticResult<Vec<SnapshotFile>> {
self.update_matching_snapshots(current, |_| true)
}

/// Get all snapshots from the repository respecting the given `filter`
///
/// # Arguments
Expand All @@ -979,9 +1018,30 @@ impl<P: ProgressBars, S: Open> Repository<P, S> {
pub fn get_matching_snapshots(
&self,
filter: impl FnMut(&SnapshotFile) -> bool,
) -> RusticResult<Vec<SnapshotFile>> {
self.update_matching_snapshots(Vec::new(), filter)
}

/// Update existing snapshots to all from the repository respecting the given `filter`
///
/// # Arguments
///
/// * `current` - The existing snapshots
/// * `filter` - The filter to use
///
/// # Errors
///
/// # Note
/// The result is not sorted and may come in random order!
///
// TODO: Document errors
pub fn update_matching_snapshots(
&self,
current: Vec<SnapshotFile>,
filter: impl FnMut(&SnapshotFile) -> bool,
) -> RusticResult<Vec<SnapshotFile>> {
let p = self.pb.progress_counter("getting snapshots...");
let result = SnapshotFile::all_from_backend(self.dbe(), filter, &p);
let result = SnapshotFile::update_from_backend(self.dbe(), current, filter, &p);
p.finish();
result
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ fn test_backup_with_tar_gz_passes(
// reverse order
all_snapshots.reverse();
ids.reverse();
let snaps = repo.get_snapshots(&ids)?;
let snaps = repo.update_snapshots(snaps, &ids)?;
assert_eq!(snaps, all_snapshots);

// get snapshot group
Expand Down

0 comments on commit 7668364

Please sign in to comment.