use dumbeq::DumbEq;
use thread_groups::ThreadGroup;
use crate::{traceback, Error, Path};
pub type MaxDepth = usize;
pub type Depth = usize;
fn iocore_walk_dir(
path: &Path,
mut handler: impl WalkProgressHandler,
max_depth: Option<MaxDepth>,
depth: Option<Depth>,
) -> Result<Vec<Path>, Error> {
let path = Into::<Path>::into(path);
let max_depth = max_depth.unwrap_or(usize::MAX);
let depth = depth.unwrap_or(0) + 1;
if !path.exists() {
return Err(traceback!(
WalkDirError,
"path {:#?} does not exist [depth: {}]",
path.to_string(),
depth
));
}
if !path.is_directory() {
return Err(traceback!(
WalkDirError,
"path {:#?} not a directory [depth: {}]",
path.to_string(),
depth
));
}
let mut result = Vec::<Path>::new();
let mut threads: ThreadGroup<Result<Vec<Path>, Error>> =
ThreadGroup::with_id(format!("walk_dir:{}", path));
if depth > max_depth {
return Ok(result);
}
if !path.exists() {
match handler.error(&path, traceback!(PathDoesNotExist, path.to_string())) {
Some(e) => Err(traceback!(WalkDirError, "{} [depth:{}]", e, depth))?,
None => {},
}
}
let path = path.absolute()?;
for path in path.list()? {
handler
.progress_in(&path, depth)
.map_err(|e| traceback!(WalkDirError, "{} [depth:{}]", e, depth))?;
if path.is_directory() {
let mut handler = handler.clone();
match handler.should_scan_directory(&path) {
Ok(should_scan_path) =>
if should_scan_path {
let sub_path = path.clone();
let handler = handler.clone();
threads.spawn(move || {
iocore_walk_dir(
&sub_path,
handler,
Some(max_depth.clone()),
Some(depth.clone()),
)
})?;
},
Err(error) => match handler.error(&path, error) {
Some(e) => Err(traceback!(WalkDirError, "{} [depth:{}]", e, depth))?,
None => {},
},
}
}
match handler.path_matching(&path) {
Ok(should_aggregate_result) =>
if should_aggregate_result {
if !result.contains(&path) {
result.push(path);
}
},
Err(error) => match handler.error(&path, error) {
Some(e) => Err(traceback!(WalkDirError, "{} [depth:{}]", e, depth))?,
None => {},
},
}
}
for paths in threads
.results()
.iter()
.filter(|path| path.is_ok())
.map(|path| path.clone().unwrap())
.flatten()
{
for path in paths.iter() {
handler
.progress_out(path)
.map_err(|e| traceback!(WalkDirError, "{} [depth:{}]", e, depth))?;
match handler.path_matching(path) {
Ok(should_aggregate_result) =>
if should_aggregate_result {
if !result.contains(&path) {
result.push(path.clone());
}
},
Err(error) => match handler.error(&path, error) {
Some(e) => Err(traceback!(WalkDirError, "{} [depth:{}]", e, depth))?,
None => {},
},
}
}
}
Ok(result)
}
pub fn walk_dir(
path: impl Into<Path>,
handler: impl WalkProgressHandler,
max_depth: Option<usize>,
) -> Result<Vec<Path>, Error> {
let path = Into::<Path>::into(path);
let mut result = Vec::<Path>::from_iter(
iocore_walk_dir(&path, handler, max_depth, None)?
.iter()
.map(|path| path.clone()),
);
if result.len() > 2 {
result.sort();
}
Ok(result)
}
#[cfg(not(feature = "walk-glob"))]
pub fn walk_globs(
_globs_: Vec<impl std::fmt::Display>,
_handle_: impl WalkProgressHandler,
_max_depth_: Option<MaxDepth>,
) -> Result<Vec<Path>, Error> {
Err(Error::CrateError(format!("walk_globs requires the feature `walk-glob'")))
}
#[cfg(feature = "walk-glob")]
pub fn walk_globs(
globs: Vec<impl std::fmt::Display>,
handle: impl WalkProgressHandler,
max_depth: Option<MaxDepth>,
) -> Result<Vec<Path>, Error> {
let mut result = Vec::<Path>::new();
let filenames = globs.iter().map(|pattern| pattern.to_string());
if filenames.len() == 0 {
result.extend_from_slice(&walk_dir(&Path::cwd(), handle.clone(), max_depth)?)
} else {
for pattern in filenames {
for path in glob(pattern)? {
if path.is_directory() {
result.extend_from_slice(&walk_dir(&path, handle.clone(), max_depth)?);
} else {
result.push(path);
}
}
}
}
if result.len() > 2 {
result.sort();
}
Ok(result)
}
#[cfg(not(feature = "walk-glob"))]
pub fn glob(_pattern_: impl std::fmt::Display) -> Result<Vec<Path>, Error> {
Err(Error::CrateError(format!("iocore::glob requires the feature `walk-glob'")))
}
#[cfg(feature = "walk-glob")]
pub fn glob(pattern: impl std::fmt::Display) -> Result<Vec<Path>, Error> {
let mut result = Vec::<Path>::new();
let pattern = pattern.to_string();
for filename in match ::glob::glob(&pattern) {
Err(e) => return Err(Error::MalformedGlobPattern(format!("{}: {}", pattern, e))),
Ok(paths) => paths,
} {
let path = match filename {
Ok(filename) => Path::from(filename),
Err(e) => return Err(Error::FileSystemError(format!("{}: {}", pattern, e))),
};
result.push(path);
}
Ok(result)
}
pub trait WalkProgressHandler: Send + Sync + 'static + Clone {
fn path_matching(&mut self, path: &Path) -> std::result::Result<bool, Error>;
fn should_scan_directory(&mut self, path: &Path) -> std::result::Result<bool, Error> {
Ok(path.is_directory())
}
fn error(&mut self, _path_: &Path, error: Error) -> Option<Error> {
Some(error)
}
fn progress_in(&mut self, _path_: &Path, _depth_: Depth) -> std::result::Result<(), Error> {
Ok(())
}
fn progress_out(&mut self, _path_: &Path) -> std::result::Result<(), Error> {
Ok(())
}
}
#[derive(Clone, DumbEq)]
pub struct NoopProgressHandler;
impl WalkProgressHandler for NoopProgressHandler {
fn path_matching(&mut self, path: &Path) -> std::result::Result<bool, Error> {
Ok(path.exists())
}
fn should_scan_directory(&mut self, path: &Path) -> std::result::Result<bool, Error> {
Ok(path.is_directory())
}
}