use std::path::{Path, PathBuf};
use bytesize::ByteSize;
use log::{debug, trace};
use crate::{
archiver::{parent::ParentResult, tree::TreeType},
backend::{decrypt::DecryptWriteBackend, node::Node},
blob::{
BlobType,
packer::{PackSizer, Packer},
tree::{Tree, TreeId},
},
error::{ErrorKind, RusticError, RusticResult},
index::{ReadGlobalIndex, indexer::SharedIndexer},
repofile::{configfile::ConfigFile, snapshotfile::SnapshotSummary},
};
pub(crate) type TreeItem = TreeType<(ParentResult<()>, u64), ParentResult<TreeId>>;
pub(crate) struct TreeArchiver<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> {
tree: Tree,
stack: Vec<(PathBuf, Node, ParentResult<TreeId>, Tree)>,
index: &'a I,
tree_packer: Packer<BE>,
summary: SnapshotSummary,
}
impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> {
pub(crate) fn new(
be: BE,
index: &'a I,
indexer: SharedIndexer<BE>,
config: &ConfigFile,
summary: SnapshotSummary,
) -> RusticResult<Self> {
let pack_sizer =
PackSizer::from_config(config, BlobType::Tree, index.total_size(BlobType::Tree));
let tree_packer = Packer::new(be, BlobType::Tree, indexer, pack_sizer)?;
Ok(Self {
tree: Tree::new(),
stack: Vec::new(),
index,
tree_packer,
summary,
})
}
pub(crate) fn add(&mut self, item: TreeItem) -> RusticResult<()> {
match item {
TreeType::NewTree((path, node, parent)) => {
trace!("entering {}", path.display());
let tree = std::mem::replace(&mut self.tree, Tree::new());
self.stack.push((path, node, parent, tree));
}
TreeType::EndTree => {
let (path, mut node, parent, tree) = self.stack.pop().ok_or_else(|| {
RusticError::new(ErrorKind::Internal, "Tree stack is empty.").ask_report()
})?;
trace!("finishing {}", path.display());
let id = self.backup_tree(&path, &parent)?;
node.subtree = Some(id);
self.tree = tree;
self.tree.add(node);
}
TreeType::Other((path, node, (parent, size))) => {
self.add_file(&path, node, &parent, size);
}
}
Ok(())
}
fn add_file(&mut self, path: &Path, node: Node, parent: &ParentResult<()>, size: u64) {
let filename = path.join(node.name());
match parent {
ParentResult::Matched(()) => {
debug!("unchanged file: {}", filename.display());
self.summary.files_unmodified += 1;
}
ParentResult::NotMatched => {
debug!("changed file: {}", filename.display());
self.summary.files_changed += 1;
}
ParentResult::NotFound => {
debug!("new file: {}", filename.display());
self.summary.files_new += 1;
}
}
self.summary.total_files_processed += 1;
self.summary.total_bytes_processed += size;
self.tree.add(node);
}
fn backup_tree(&mut self, path: &Path, parent: &ParentResult<TreeId>) -> RusticResult<TreeId> {
let (chunk, id) = self.tree.serialize().map_err(|err| {
RusticError::with_source(
ErrorKind::Internal,
"Failed to serialize tree at `{path}`",
err,
)
.attach_context("path", path.to_string_lossy())
.ask_report()
})?;
let dirsize = chunk.len() as u64;
let dirsize_bytes = ByteSize(dirsize).display().iec().to_string();
self.summary.total_dirs_processed += 1;
self.summary.total_dirsize_processed += dirsize;
match parent {
ParentResult::Matched(p_id) if id == *p_id => {
debug!("unchanged tree: {}", path.display());
self.summary.dirs_unmodified += 1;
return Ok(id);
}
ParentResult::NotFound => {
debug!("new tree: {} {dirsize_bytes}", path.display());
self.summary.dirs_new += 1;
}
_ => {
debug!("changed tree: {} {dirsize_bytes}", path.display());
self.summary.dirs_changed += 1;
}
}
if !self.index.has_tree(&id) {
self.tree_packer.add(chunk.into(), id.into())?;
}
Ok(id)
}
pub(crate) fn finalize(
mut self,
parent_tree: Option<TreeId>,
) -> RusticResult<(TreeId, SnapshotSummary)> {
let parent = parent_tree.map_or(ParentResult::NotFound, ParentResult::Matched);
let id = self.backup_tree(&PathBuf::new(), &parent)?;
let stats = self.tree_packer.finalize()?;
stats.apply(&mut self.summary, BlobType::Tree);
Ok((id, self.summary))
}
}