use crate::{node::SureNode, Error, Result};
use log::error;
use std::{collections::HashSet, path::Path};
struct State<IA, IB> {
left: SureNode,
right: SureNode,
left_iter: IA,
right_iter: IB,
adds: HashSet<String>,
missings: HashSet<String>,
ignore: HashSet<String>,
}
pub fn compare_trees<P: AsRef<Path>, IA, IB>(
mut left: IA,
mut right: IB,
dir: P,
ignore: &[&str],
) -> Result<()>
where
IA: Iterator<Item = Result<SureNode>>,
IB: Iterator<Item = Result<SureNode>>,
{
let mut ignore: HashSet<String> = ignore.iter().map(|x| (*x).to_owned()).collect();
ignore.insert("ctime".to_owned());
ignore.insert("ino".to_owned());
let ln = match left.next() {
None => return Err(Error::EmptyLeftIterator),
Some(Err(e)) => return Err(e),
Some(Ok(node)) => node,
};
let rn = match right.next() {
None => return Err(Error::EmptyRightIterator),
Some(Err(e)) => return Err(e),
Some(Ok(node)) => node,
};
let mut state = State {
left: ln,
right: rn,
left_iter: left,
right_iter: right,
adds: HashSet::new(),
missings: HashSet::new(),
ignore,
};
state.walk_root(dir.as_ref())
}
impl<IA, IB> State<IA, IB>
where
IA: Iterator<Item = Result<SureNode>>,
IB: Iterator<Item = Result<SureNode>>,
{
fn next_left(&mut self) -> Result<()> {
let next = match self.left_iter.next() {
None => SureNode::Leave,
Some(Ok(node)) => node,
Some(Err(e)) => return Err(e),
};
self.left = next;
Ok(())
}
fn next_right(&mut self) -> Result<()> {
let next = match self.right_iter.next() {
None => SureNode::Leave,
Some(Ok(node)) => node,
Some(Err(e)) => return Err(e),
};
self.right = next;
Ok(())
}
fn walk_root(&mut self, dir: &Path) -> Result<()> {
if !self.left.is_enter() {
Err(Error::UnexpectedLeftNode)
} else if !self.right.is_enter() {
Err(Error::UnexpectedRightNode)
} else if self.left.name() != "__root__" || self.right.name() != "__root__" {
Err(Error::IncorrectName)
} else {
self.compare_enter(dir)?;
self.next_left()?;
self.next_right()?;
self.walk_samedir(dir)
}
}
fn walk_samedir(&mut self, dir: &Path) -> Result<()> {
loop {
match (self.left.is_sep(), self.right.is_sep()) {
(true, true) => {
self.next_left()?;
self.next_right()?;
return self.walk_samefiles(dir);
}
(false, true) => {
self.show_delete(dir);
self.next_left()?;
self.walk_leftdir()?;
}
(true, false) => {
self.show_add(dir);
self.next_right()?;
self.walk_rightdir()?;
}
_ if self.left.name() < self.right.name() => {
self.show_delete(dir);
self.next_left()?;
self.walk_leftdir()?;
}
_ if self.left.name() > self.right.name() => {
self.show_add(dir);
self.next_right()?;
self.walk_rightdir()?;
}
_ => {
let dirname = dir.join(self.left.name());
self.compare_enter(&dirname)?;
self.next_left()?;
self.next_right()?;
self.walk_samedir(&dirname)?;
}
}
}
}
fn walk_samefiles(&mut self, dir: &Path) -> Result<()> {
loop {
match (self.left.is_leave(), self.right.is_leave()) {
(true, true) => {
self.next_left()?;
self.next_right()?;
return Ok(());
}
(false, true) => {
self.show_delete(dir);
self.next_left()?;
}
(true, false) => {
self.show_add(dir);
self.next_right()?;
}
_ if self.left.name() < self.right.name() => {
self.show_delete(dir);
self.next_left()?;
}
_ if self.left.name() > self.right.name() => {
self.show_add(dir);
self.next_right()?;
}
_ => {
let nodename = dir.join(self.left.name());
self.compare_file(&nodename)?;
self.next_left()?;
self.next_right()?;
}
}
}
}
fn walk_leftdir(&mut self) -> Result<()> {
loop {
if self.left.is_enter() {
self.next_left()?;
self.walk_leftdir()?;
} else if self.left.is_leave() {
self.next_left()?;
return Ok(());
} else {
self.next_left()?;
}
}
}
fn walk_rightdir(&mut self) -> Result<()> {
loop {
if self.right.is_enter() {
self.next_right()?;
self.walk_rightdir()?;
} else if self.right.is_leave() {
self.next_right()?;
return Ok(());
} else {
self.next_right()?;
}
}
}
fn show_add(&self, dir: &Path) {
println!(
"+ {:22} {:?}",
self.right.kind(),
dir.join(self.right.name())
);
}
fn show_delete(&self, dir: &Path) {
println!("- {:22} {:?}", self.left.kind(), dir.join(self.left.name()));
}
fn compare_enter(&mut self, dir: &Path) -> Result<()> {
self.compare_atts('d', dir)
}
fn compare_file(&mut self, dir: &Path) -> Result<()> {
self.compare_atts('f', dir)
}
fn compare_atts(&mut self, _kind: char, dir: &Path) -> Result<()> {
let mut old = self.left.atts().unwrap().clone();
let mut new = self.right.atts().unwrap().clone();
let mut diffs = vec![];
for att in self.ignore.iter() {
old.remove(att);
new.remove(att);
}
for (k, v) in &new {
match old.get(k) {
None => {
if !self.adds.contains(k) {
error!("Added attribute: {}", k);
self.adds.insert(k.clone());
}
}
Some(ov) => {
if v != ov {
diffs.push(k.clone());
}
}
}
old.remove(k);
}
for k in old.keys() {
if !self.missings.contains(k) {
error!("Missing attribute: {}", k);
self.missings.insert(k.clone());
}
}
if !diffs.is_empty() {
let mut buf = String::new();
diffs.sort();
for d in &diffs {
if !buf.is_empty() {
buf.push(',');
}
buf.push_str(&d);
}
println!(" [{:<20}] {:?}", buf, dir);
}
Ok(())
}
}