diff --git a/crates/rustic_core/src/commands/restore.rs b/crates/rustic_core/src/commands/restore.rs index a8fa1c7..6d6dd29 100644 --- a/crates/rustic_core/src/commands/restore.rs +++ b/crates/rustic_core/src/commands/restore.rs @@ -5,7 +5,6 @@ use log::{debug, error, info, trace, warn}; use std::{ cmp::Ordering, collections::BTreeMap, - io::Read, num::NonZeroU32, path::{Path, PathBuf}, sync::Mutex, @@ -18,10 +17,9 @@ use rayon::ThreadPoolBuilder; use crate::{ error::CommandErrorKind, - hash, repository::{IndexedFull, IndexedTree}, - DecryptReadBackend, FileType, Id, IndexedBackend, LocalDestination, Node, NodeType, Open, - Progress, ProgressBars, ReadBackend, Repository, RusticResult, + BlobType, DecryptReadBackend, FileType, Id, LocalDestination, Node, NodeType, Open, Progress, + ProgressBars, ReadBackend, Repository, RusticResult, }; pub(crate) mod constants { @@ -167,7 +165,7 @@ impl RestoreOpts { match ( exists, restore_infos - .add_file(dest, node, path.clone(), repo.index(), self.verify_existing) + .add_file(dest, node, path.clone(), repo, self.verify_existing) .map_err(|err| { CommandErrorKind::ErrorCollecting(path.to_path_buf(), Box::new(err)) })?, @@ -502,12 +500,12 @@ enum AddFileResult { impl RestoreInfos { /// Add the file to [`FileInfos`] using `index` to get blob information. - fn add_file( + fn add_file( &mut self, dest: &LocalDestination, file: &Node, name: PathBuf, - index: &impl IndexedBackend, + repo: &Repository, ignore_mtime: bool, ) -> RusticResult { let mut open_file = dest.get_matching_file(&name, file.meta.size); @@ -543,9 +541,7 @@ impl RestoreInfos { let mut file_pos = 0; let mut has_unmatched = false; for id in file.content.iter().flatten() { - let ie = index - .get_data(id) - .ok_or_else(|| CommandErrorKind::IdNotFound(*id))?; + let ie = repo.get_index_entry(BlobType::Data, id)?; let bl = BlobLocation { offset: ie.offset, length: ie.length, @@ -553,11 +549,9 @@ impl RestoreInfos { }; let length = bl.data_length(); - let matches = open_file.as_mut().map_or(false, |file| { - // Existing file content; check if SHA256 matches - let mut vec = vec![0; length as usize]; - file.read_exact(&mut vec).is_ok() && id == &hash(&vec) - }); + let matches = open_file + .as_mut() + .map_or(false, |file| id.blob_matches_reader(length as usize, file)); let blob_location = self.r.entry((ie.pack, bl)).or_insert_with(Vec::new); blob_location.push(FileLocation { diff --git a/crates/rustic_core/src/error.rs b/crates/rustic_core/src/error.rs index 0053bc1..3e6926d 100644 --- a/crates/rustic_core/src/error.rs +++ b/crates/rustic_core/src/error.rs @@ -185,8 +185,6 @@ pub enum CommandErrorKind { ErrorCollecting(PathBuf, Box), /// error setting length for {0:?}: {1:?} ErrorSettingLength(PathBuf, Box), - /// did not find id {0} in index - IdNotFound(Id), /// {0:?} FromRayonError(#[from] rayon::ThreadPoolBuildError), /// conversion to `u64` failed: `{0:?}` @@ -274,6 +272,8 @@ pub enum RepositoryErrorKind { ReadingPasswordFromPromptFailed(std::io::Error), /// Config file already exists. Aborting. ConfigFileExists, + /// did not find id {0} in index + IdNotFound(Id), } /// [`IndexErrorKind`] describes the errors that can be returned by processing Indizes diff --git a/crates/rustic_core/src/id.rs b/crates/rustic_core/src/id.rs index 4aa6039..ac1f05c 100644 --- a/crates/rustic_core/src/id.rs +++ b/crates/rustic_core/src/id.rs @@ -1,11 +1,11 @@ -use std::{fmt, ops::Deref, path::Path}; +use std::{fmt, io::Read, ops::Deref, path::Path}; use binrw::{BinRead, BinWrite}; use derive_more::{Constructor, Display}; use rand::{thread_rng, RngCore}; use serde::{Deserialize, Serialize}; -use crate::{error::IdErrorKind, RusticResult}; +use crate::{error::IdErrorKind, hash, RusticResult}; pub(super) mod constants { pub(super) const LEN: usize = 32; @@ -63,6 +63,12 @@ impl Id { pub fn is_null(&self) -> bool { self == &Self::default() } + + pub fn blob_matches_reader(&self, length: usize, r: &mut impl Read) -> bool { + // check if SHA256 matches + let mut vec = vec![0; length]; + r.read_exact(&mut vec).is_ok() && self == &hash(&vec) + } } impl fmt::Debug for Id { diff --git a/crates/rustic_core/src/lib.rs b/crates/rustic_core/src/lib.rs index fc1828f..761dc90 100644 --- a/crates/rustic_core/src/lib.rs +++ b/crates/rustic_core/src/lib.rs @@ -142,5 +142,5 @@ pub use crate::{ SnapshotOptions, StringList, }, }, - repository::{Open, OpenStatus, Repository, RepositoryOptions}, + repository::{IndexedFull, Open, OpenStatus, Repository, RepositoryOptions}, }; diff --git a/crates/rustic_core/src/repository.rs b/crates/rustic_core/src/repository.rs index 770b138..5545e82 100644 --- a/crates/rustic_core/src/repository.rs +++ b/crates/rustic_core/src/repository.rs @@ -46,13 +46,14 @@ use crate::{ }, crypto::aespoly1305::Key, error::{KeyFileErrorKind, RepositoryErrorKind, RusticErrorKind}, + index::IndexEntry, repofile::{ configfile::ConfigFile, keyfile::find_key_in_backend, snapshotfile::SnapshotSummary, RepoFile, }, BlobType, Id, IndexBackend, IndexedBackend, LocalDestination, NoProgressBars, Node, - NodeStreamer, PathList, ProgressBars, PruneOpts, PrunePlan, RusticResult, SnapshotFile, - SnapshotGroup, SnapshotGroupCriterion, Tree, TreeStreamerOptions, + NodeStreamer, PathList, ProgressBars, PruneOpts, PrunePlan, ReadIndex, RusticResult, + SnapshotFile, SnapshotGroup, SnapshotGroupCriterion, Tree, TreeStreamerOptions, }; mod warm_up; @@ -502,6 +503,16 @@ impl Repository { commands::snapshots::get_snapshot_group(self, ids, group_by, filter) } + pub fn get_snapshot_from_str( + &self, + id: &str, + filter: impl FnMut(&SnapshotFile) -> bool + Send + Sync, + ) -> RusticResult { + let p = self.pb.progress_counter("getting snapshot..."); + let snap = SnapshotFile::from_str(self.dbe(), id, filter, &p)?; + Ok(snap) + } + pub fn get_snapshots(&self, ids: &[String]) -> RusticResult> { let p = self.pb.progress_counter("getting snapshots..."); SnapshotFile::from_ids(self.dbe(), ids, &p) @@ -670,6 +681,16 @@ impl Open for IndexedStatus { } } +impl Repository { + pub fn get_index_entry(&self, tpe: BlobType, id: &Id) -> RusticResult { + let ie = self + .index() + .get_id(tpe, id) + .ok_or_else(|| RepositoryErrorKind::IdNotFound(*id))?; + Ok(ie) + } +} + impl Repository { pub fn node_from_snapshot_path( &self, @@ -684,6 +705,14 @@ impl Repository { Tree::node_from_path(self.index(), snap.tree, Path::new(path)) } + pub fn node_from_snapshot_and_path( + &self, + snap: &SnapshotFile, + path: &str, + ) -> RusticResult { + Tree::node_from_path(self.index(), snap.tree, Path::new(path)) + } + pub fn cat_tree( &self, snap: &str, diff --git a/src/commands/diff.rs b/src/commands/diff.rs index 3f3ea69..8f1b73e 100644 --- a/src/commands/diff.rs +++ b/src/commands/diff.rs @@ -6,15 +6,14 @@ use crate::{commands::open_repository, status_err, Application, RUSTIC_APP}; use abscissa_core::{Command, Runnable, Shutdown}; -use std::io::Read; use std::path::{Path, PathBuf}; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{bail, Context, Result}; use rustic_core::{ - hash, IndexBackend, LocalDestination, LocalSource, LocalSourceFilterOptions, - LocalSourceSaveOptions, Node, NodeStreamer, NodeType, Open, Progress, ProgressBars, ReadIndex, - ReadSourceEntry, RusticResult, SnapshotFile, Tree, + BlobType, IndexedFull, LocalDestination, LocalSource, LocalSourceFilterOptions, + LocalSourceSaveOptions, Node, NodeType, ReadSourceEntry, Repository, RusticResult, + TreeStreamerOptions, }; /// `diff` subcommand @@ -53,31 +52,25 @@ impl DiffCmd { fn inner_run(&self) -> Result<()> { let config = RUSTIC_APP.config(); - let repo = open_repository(&config)?; + let repo = open_repository(&config)?.to_indexed()?; - let be = repo.dbe(); let (id1, path1) = arg_to_snap_path(&self.snap1, ""); let (id2, path2) = arg_to_snap_path(&self.snap2, path1); - let progress_options = &config.global.progress_options; - _ = match (id1, id2) { (Some(id1), Some(id2)) => { // diff between two snapshots - let p = progress_options.progress_spinner("getting snapshots..."); - let snaps = SnapshotFile::from_ids(be, &[id1.to_string(), id2.to_string()], &p)?; - p.finish(); + let snaps = repo.get_snapshots(&[id1.to_string(), id2.to_string()])?; let snap1 = &snaps[0]; let snap2 = &snaps[1]; - let index = IndexBackend::new(be, &progress_options.progress_counter(""))?; - let node1 = Tree::node_from_path(&index, snap1.tree, Path::new(path1))?; - let node2 = Tree::node_from_path(&index, snap2.tree, Path::new(path2))?; + let node1 = repo.node_from_snapshot_and_path(snap1, path1)?; + let node2 = repo.node_from_snapshot_and_path(snap2, path2)?; diff( - NodeStreamer::new(index.clone(), &node1)?, - NodeStreamer::new(index, &node2)?, + repo.ls(&node1, &TreeStreamerOptions::default(), true)?, + repo.ls(&node2, &TreeStreamerOptions::default(), true)?, self.no_content, |_path, node1, node2| Ok(node1.content == node2.content), self.metadata, @@ -85,13 +78,10 @@ impl DiffCmd { } (Some(id1), None) => { // diff between snapshot and local path - let p = progress_options.progress_spinner("getting snapshot..."); let snap1 = - SnapshotFile::from_str(be, id1, |sn| config.snapshot_filter.matches(sn), &p)?; - p.finish(); + repo.get_snapshot_from_str(id1, |sn| config.snapshot_filter.matches(sn))?; - let index = IndexBackend::new(be, &progress_options.progress_counter(""))?; - let node1 = Tree::node_from_path(&index, snap1.tree, Path::new(path1))?; + let node1 = repo.node_from_snapshot_and_path(&snap1, path1)?; let local = LocalDestination::new(path2, false, !node1.is_dir())?; let path2 = PathBuf::from(path2); let is_dir = path2 @@ -103,24 +93,11 @@ impl DiffCmd { &self.ignore_opts, &[&path2], )? - .map(|item| { - let ReadSourceEntry { path, node, .. } = match item { - Ok(it) => it, - Err(err) => { - status_err!("{}", err); - RUSTIC_APP.shutdown(Shutdown::Crash); - } - }; + .map(|item| -> RusticResult<_> { + let ReadSourceEntry { path, node, .. } = item?; let path = if is_dir { // remove given path prefix for dirs as local path - match path.strip_prefix(&path2) { - Ok(it) => it, - Err(err) => { - status_err!("{}", err); - RUSTIC_APP.shutdown(Shutdown::Crash); - } - } - .to_path_buf() + path.strip_prefix(&path2).unwrap().to_path_buf() } else { // ensure that we really get the filename if local path is a file path2.file_name().unwrap().into() @@ -129,10 +106,10 @@ impl DiffCmd { }); diff( - NodeStreamer::new(index.clone(), &node1)?, + repo.ls(&node1, &TreeStreamerOptions::default(), true)?, src, self.no_content, - |path, node1, _node2| identical_content_local(&local, &index, path, node1), + |path, node1, _node2| identical_content_local(&local, &repo, path, node1), self.metadata, ) } @@ -158,26 +135,20 @@ fn arg_to_snap_path<'a>(arg: &'a str, default_path: &'a str) -> (Option<&'a str> } } -fn identical_content_local( +fn identical_content_local( local: &LocalDestination, - index: &impl ReadIndex, + repo: &Repository, path: &Path, node: &Node, ) -> Result { let Some(mut open_file) = local.get_matching_file(path, node.meta.size) else { return Ok(false) }; for id in node.content.iter().flatten() { - let ie = index - .get_data(id) - .ok_or_else(|| anyhow!("did not find id {} in index", id))?; + let ie = repo.get_index_entry(BlobType::Data, id)?; let length = ie.data_length(); - - // check if SHA256 matches - let mut vec = vec![0; length as usize]; - if open_file.read_exact(&mut vec).is_ok() && id == &hash(&vec) { - continue; + if !id.blob_matches_reader(length as usize, &mut open_file) { + return Ok(false); } - return Ok(false); } Ok(true) }