use crate::{
escape::Escape, node::SureNode, progress::ScanProgress, surefs::encode_atts, suretree::AttMap,
Error, Result,
};
use log::error;
use std::{
collections::VecDeque,
fs::{self, symlink_metadata, Metadata},
os::unix::prelude::*,
path::{Path, PathBuf},
};
pub fn walk<P: AsRef<Path>>(root: P) -> Result<()> {
for entry in scan_fs(root)? {
let entry = entry?;
println!("{:?}", entry);
}
Ok(())
}
pub fn scan_fs<P: AsRef<Path>>(root: P) -> Result<ScanIterator> {
let root = root.as_ref().to_path_buf();
let meta = symlink_metadata(&root)?;
if !meta.is_dir() {
return Err(Error::RootMustBeDir);
}
let atts = encode_atts(&root, &meta);
let root_dev = meta.dev();
let mut todo = VecDeque::new();
todo.push_back(AugNode::SubDir {
path: root,
name: "__root__".to_string(),
meta,
atts,
});
let si = ScanIterator {
todo,
root_dev,
progress: ScanProgress::new(),
};
Ok(si)
}
pub struct ScanIterator {
todo: VecDeque<AugNode>,
root_dev: u64,
progress: ScanProgress,
}
impl Iterator for ScanIterator {
type Item = Result<SureNode>;
fn next(&mut self) -> Option<Result<SureNode>> {
match self.todo.pop_front() {
None => None,
Some(AugNode::Normal(e)) => Some(Ok(e)),
Some(AugNode::SubDir {
path,
name,
atts,
meta,
}) => {
if !meta.is_dir() || meta.dev() == self.root_dev {
match self.push_dir(&path) {
Ok(()) => (),
Err(e) => return Some(Err(e)),
};
} else {
self.push_empty_dir();
}
Some(Ok(SureNode::Enter { name, atts }))
}
}
}
}
impl ScanIterator {
fn push_dir(&mut self, path: &Path) -> Result<()> {
let mut entries = vec![];
match fs::read_dir(path) {
Ok(dir) => {
for entry in dir {
let entry = match entry {
Ok(ent) => ent,
Err(err) => {
error!("Unable to read from dir: {:?} ({})", path, err);
break;
}
};
entries.push(entry);
}
}
Err(e) => {
error!("Unable to read dir: {:?} ({})", path, e);
}
};
entries.sort_by_key(|a| a.ino());
let mut files: Vec<_> = entries
.iter()
.filter_map(|e| match e.metadata() {
Ok(m) => {
let path = e.path();
let atts = encode_atts(&path, &m);
Some(OneFile {
path,
meta: m,
atts,
})
}
Err(err) => {
error!("Unable to stat file: {:?} ({})", e.path(), err);
None
}
})
.collect();
files.sort_by(|a, b| a.path.file_name().cmp(&b.path.file_name()));
let (dirs, files): (Vec<_>, Vec<_>) = files.into_iter().partition(|n| n.meta.is_dir());
self.progress.update(
dirs.len() as u64,
files.len() as u64,
files.iter().map(|x| x.meta.len()).sum(),
);
self.todo.push_front(AugNode::Normal(SureNode::Leave));
for f in files.into_iter().rev() {
self.todo.push_front(AugNode::Normal(SureNode::File {
name: f.path.file_name().unwrap().as_bytes().escaped(),
atts: f.atts,
}));
}
self.todo.push_front(AugNode::Normal(SureNode::Sep));
for d in dirs.into_iter().rev() {
let name = d.path.file_name().unwrap().as_bytes().escaped();
self.todo.push_front(AugNode::SubDir {
path: d.path,
name,
meta: d.meta,
atts: d.atts,
});
}
Ok(())
}
fn push_empty_dir(&mut self) {
self.todo.push_front(AugNode::Normal(SureNode::Leave));
self.todo.push_front(AugNode::Normal(SureNode::Sep));
}
}
struct OneFile {
path: PathBuf,
meta: Metadata,
atts: AttMap,
}
enum AugNode {
Normal(SureNode),
SubDir {
path: PathBuf,
name: String,
meta: Metadata,
atts: AttMap,
},
}