use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use crate::notice::Tally;
pub(crate) struct Candidate {
pub relative: PathBuf,
pub absolute: PathBuf,
}
#[derive(Debug)]
pub(crate) struct Walker {
root: PathBuf,
recursive: bool,
stack: Vec<Vec<fs::DirEntry>>,
}
impl Walker {
pub(crate) fn open(root: &Path, recursive: bool) -> io::Result<Self> {
let entries = list(root)?;
Ok(Self {
root: root.to_path_buf(),
recursive,
stack: vec![entries],
})
}
pub(crate) fn next(&mut self, tally: &mut Tally) -> Option<Candidate> {
loop {
let entry = loop {
let top = self.stack.last_mut()?;
match top.pop() {
Some(entry) => break entry,
None => {
self.stack.pop();
}
}
};
let absolute = entry.path();
let relative = absolute
.strip_prefix(&self.root)
.map_or_else(|_| absolute.clone(), Path::to_path_buf);
let Ok(kind) = fs::metadata(&absolute) else {
tally.skip(relative, "cannot read");
continue;
};
if kind.is_dir() {
let is_link = entry.file_type().is_ok_and(|t| t.is_symlink());
if self.recursive && !is_link {
match list(&absolute) {
Ok(entries) => self.stack.push(entries),
Err(e) => tally.skip(relative, format!("cannot list directory: {e}")),
}
}
continue;
}
let is_container = absolute
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("slpc"));
if kind.is_file() && is_container {
return Some(Candidate { relative, absolute });
}
}
}
}
fn list(dir: &Path) -> io::Result<Vec<fs::DirEntry>> {
let mut entries = fs::read_dir(dir)?.collect::<io::Result<Vec<_>>>()?;
entries.sort_by_key(|e| std::cmp::Reverse(e.file_name()));
Ok(entries)
}