use std::io::ErrorKind;
use std::path::Path;
use crate::dir_entry::DirEntry;
use crate::error::Error;
type Result<T> = std::result::Result<T, Error>;
pub struct Dirscent {
stack: Vec<Result<Vec<Result<DirEntry>>>>,
deferred_dirs: Vec<Result<DirEntry>>,
min_depth: usize,
max_depth: usize,
follow_symlinks: bool,
postorder: bool,
skip_permission_denied: bool,
}
impl Dirscent {
pub(crate) fn new(path: impl AsRef<Path>) -> Result<Self> {
let entry = path.as_ref().to_path_buf().try_into()?;
let iter = Dirscent {
stack: vec![Ok(vec![Ok(entry)])],
deferred_dirs: Vec::new(),
min_depth: std::usize::MIN,
max_depth: std::usize::MAX,
follow_symlinks: false,
postorder: false,
skip_permission_denied: false,
};
Ok(iter)
}
pub fn follow_symlinks(self) -> Self {
self.with_follow_symlinks(true)
}
pub fn with_follow_symlinks(mut self, x: bool) -> Self {
self.follow_symlinks = x;
self
}
pub fn with_min_depth(mut self, x: usize) -> Self {
self.min_depth = x;
self
}
pub fn with_max_depth(mut self, x: usize) -> Self {
self.max_depth = x;
self
}
pub fn postorder(self) -> Self {
self.with_postorder(true)
}
pub fn with_postorder(mut self, x: bool) -> Self {
self.postorder = x;
self
}
pub fn skip_permission_denied(self) -> Self {
self.with_skip_permission_denied(true)
}
pub fn with_skip_permission_denied(mut self, x: bool) -> Self {
self.skip_permission_denied = x;
self
}
}
impl Iterator for Dirscent {
type Item = Result<DirEntry>;
fn next(&mut self) -> Option<Self::Item> {
match self.stack.pop() {
Some(Ok(mut entry_vec)) => {
let depth = self.stack.len();
let item = entry_vec.pop();
match &item {
Some(entry_res) => {
self.stack.push(Ok(entry_vec));
if let Ok(entry) = entry_res {
let file_type = entry.file_type();
if depth < self.max_depth
&& (!file_type.is_symlink() || self.follow_symlinks)
&& file_type.is_dir()
{
self.stack
.push(read_dir(entry.path(), self.follow_symlinks));
}
}
if depth < self.min_depth {
return self.next();
}
if self.stack.len() - depth > 1 && self.postorder {
self.deferred_dirs.push(item.unwrap());
return self.next();
}
item
}
None => self.deferred_dirs.pop().or_else(|| self.next()),
}
}
Some(Err(err))
if err.io_error().kind() == ErrorKind::PermissionDenied
&& self.skip_permission_denied =>
{
self.next()
}
Some(Err(err)) => Some(Err(err)),
None => None,
}
}
}
fn read_dir(path: &Path, follow_symlinks: bool) -> Result<Vec<Result<DirEntry>>> {
path.read_dir()
.map(|it| {
it.map(|entry_res| {
entry_res
.map_err(|err| Error::new(err, path.to_path_buf()))
.and_then(DirEntry::try_from)
.map(|entry| entry.with_follow_symlinks(follow_symlinks))
})
.collect()
})
.map_err(|err| Error::new(err, path.to_path_buf()))
}