use std::{
cmp::Ordering,
ffi::{OsStr, OsString},
};
use log::warn;
use crate::{
archiver::{TreeStackEmptyError, tree::TreeType},
backend::{decrypt::DecryptReadBackend, node::Node},
blob::tree::{Tree, TreeId},
index::ReadGlobalIndex,
};
pub(crate) type ItemWithParent<O> = TreeType<(O, ParentResult<()>), ParentResult<TreeId>>;
#[derive(Debug)]
pub struct Parent {
tree_ids: Vec<TreeId>,
trees: Vec<(Tree, usize)>,
stack: Vec<Vec<(Tree, usize)>>,
ignore_ctime: bool,
ignore_inode: bool,
}
#[derive(Clone, Debug)]
pub(crate) enum ParentResult<T> {
Matched(T),
NotFound,
NotMatched,
}
impl<T> ParentResult<T> {
fn map<R>(self, f: impl FnOnce(T) -> R) -> ParentResult<R> {
match self {
Self::Matched(t) => ParentResult::Matched(f(t)),
Self::NotFound => ParentResult::NotFound,
Self::NotMatched => ParentResult::NotMatched,
}
}
}
impl Parent {
pub(crate) fn new(
be: &impl DecryptReadBackend,
index: &impl ReadGlobalIndex,
tree_id: impl IntoIterator<Item = TreeId>,
ignore_ctime: bool,
ignore_inode: bool,
) -> Self {
let (trees, tree_ids) = tree_id
.into_iter()
.filter_map(|tree_id| match Tree::from_backend(be, index, tree_id) {
Ok(tree) => Some(((tree, 0), tree_id)),
Err(err) => {
warn!(
"ignoring error when loading parent tree {tree_id:?}: {}",
err.display_log()
);
None
}
})
.unzip();
Self {
tree_ids,
trees,
stack: Vec::new(),
ignore_ctime,
ignore_inode,
}
}
fn p_node(&mut self, name: &OsStr) -> impl Iterator<Item = &Node> {
self.trees.iter_mut().filter_map(|(tree, idx)| {
let p_nodes = &tree.nodes;
loop {
match p_nodes.get(*idx) {
None => break None,
Some(p_node) => match (*p_node.name()).cmp(name) {
Ordering::Less => *idx += 1,
Ordering::Equal => {
break Some(p_node);
}
Ordering::Greater => {
break None;
}
},
}
}
})
}
fn is_parent(&mut self, node: &Node, name: &OsStr) -> ParentResult<&Node> {
let ignore_ctime = self.ignore_ctime;
let ignore_inode = self.ignore_inode;
let mut p_node = self.p_node(name).peekable();
if p_node.peek().is_none() {
return ParentResult::NotFound;
}
p_node
.find(|p_node| {
let p_meta = &p_node.meta;
let meta = &node.meta;
let match_ctime =
ignore_ctime || p_meta.ctime.zip(meta.ctime).is_none_or(|(x, y)| x == y);
let match_inode = !ignore_inode
|| p_meta.inode == 0
|| meta.inode == 0
|| p_meta.inode == meta.inode;
p_node.node_type == node.node_type
&& p_meta.size == meta.size
&& p_meta.mtime == meta.mtime
&& match_ctime
&& match_inode
})
.map_or(ParentResult::NotMatched, ParentResult::Matched)
}
fn set_dir(
&mut self,
be: &impl DecryptReadBackend,
index: &impl ReadGlobalIndex,
name: &OsStr,
) {
let mut new_ids: Vec<_> = self
.p_node(name)
.filter_map(|p_node| {
p_node.subtree.or_else(|| {
warn!("ignoring parent node {}: is no tree!", p_node.name);
None
})
})
.collect();
new_ids.sort();
new_ids.dedup();
let new_tree = new_ids
.into_iter()
.filter_map(|tree_id| match Tree::from_backend(be, index, tree_id) {
Ok(tree) => Some((tree, 0)),
Err(err) => {
warn!(
"ignoring error when loading parent tree {tree_id}: {}",
err.display_log()
);
None
}
})
.collect();
let old_tree = std::mem::replace(&mut self.trees, new_tree);
self.stack.push(old_tree);
}
fn finish_dir(&mut self) -> Result<(), TreeStackEmptyError> {
let tree = self.stack.pop().ok_or(TreeStackEmptyError)?;
self.trees = tree;
Ok(())
}
pub(crate) fn tree_id(&self) -> Option<TreeId> {
self.tree_ids.first().copied()
}
pub(crate) fn process<O>(
&mut self,
be: &impl DecryptReadBackend,
index: &impl ReadGlobalIndex,
item: TreeType<O, OsString>,
) -> Result<ItemWithParent<O>, TreeStackEmptyError> {
let result = match item {
TreeType::NewTree((path, node, tree)) => {
let parent_result = self
.is_parent(&node, &tree)
.map(|node| node.subtree.unwrap());
self.set_dir(be, index, &tree);
TreeType::NewTree((path, node, parent_result))
}
TreeType::EndTree => {
self.finish_dir()?;
TreeType::EndTree
}
TreeType::Other((path, mut node, open)) => {
let parent = self.is_parent(&node, &node.name());
let parent = match parent {
ParentResult::Matched(p_node) => {
if p_node.content.iter().flatten().all(|id| index.has_data(id)) {
node.content.clone_from(&p_node.content);
ParentResult::Matched(())
} else {
warn!(
"missing blobs in index for unchanged file {}; re-reading file",
path.display()
);
ParentResult::NotFound
}
}
parent_result => parent_result.map(|_| ()),
};
TreeType::Other((path, node, (open, parent)))
}
};
Ok(result)
}
}