use std::path::Path;
use walkdir::WalkDir;
pub const SKIP_WALK_DIRS: &[&str] = &[
".git",
"target",
"node_modules",
"Library",
".npm",
".cache",
".local",
".rustup",
".cargo",
".wine",
];
pub fn skip_walk_dir(name: &str) -> bool {
SKIP_WALK_DIRS.iter().any(|s| *s == name)
}
pub fn exists_named_within(roots: &[impl AsRef<Path>], file_name: &str, max_depth: usize) -> bool {
for root in roots {
let root = root.as_ref();
if !root.is_dir() {
continue;
}
if root.join(file_name).is_file() || root.join(file_name).is_dir() {
return true;
}
for entry in WalkDir::new(root)
.follow_links(false)
.max_depth(max_depth)
.into_iter()
.filter_entry(|e| {
if !e.file_type().is_dir() {
return true;
}
let name = e.file_name().to_string_lossy();
if name == file_name {
return true;
}
!skip_walk_dir(&name)
})
.filter_map(|e| e.ok())
{
if entry.file_name() == file_name {
return true;
}
}
}
false
}
pub fn dir_size(path: &Path) -> u64 {
walk_size(path, None).bytes
}
pub struct SizeReport {
pub bytes: u64,
pub issues: Vec<crate::core::ScanIssue>,
}
pub fn dir_size_in(path: &Path, ctx: &crate::core::ScanContext) -> SizeReport {
walk_size(path, Some(ctx))
}
fn walk_size(path: &Path, ctx: Option<&crate::core::ScanContext>) -> SizeReport {
let mut bytes = 0u64;
let mut issues = Vec::new();
if !path.exists() {
return SizeReport { bytes: 0, issues };
}
let mut counter: u32 = 0;
for entry in WalkDir::new(path).follow_links(false).into_iter() {
counter = counter.wrapping_add(1);
if counter % 2048 == 0 {
if let Some(ctx) = ctx {
if ctx.cancelled() {
return SizeReport { bytes, issues };
}
}
}
match entry {
Ok(e) if e.file_type().is_file() => {
bytes += e.metadata().map(|m| m.len()).unwrap_or(0);
}
Ok(_) => {}
Err(err) => {
if issues.len() >= 8 {
continue;
}
let p = err
.path()
.map(Path::to_path_buf)
.unwrap_or_else(|| path.to_path_buf());
issues.push(crate::core::ScanIssue::permission(p, err.to_string()));
}
}
}
SizeReport { bytes, issues }
}