diff options
author | andy.boot <bootandy@gmail.com> | 2024-01-03 23:07:06 +0000 |
---|---|---|
committer | andy.boot <bootandy@gmail.com> | 2024-01-09 23:02:01 +0000 |
commit | e80892a9e7eb9e2c342328242cb51a4e1536b311 (patch) | |
tree | 4ac92da2db76addff9d06c28ffb7881ffd507c84 | |
parent | cd53fc74945010c06c6a6e0658a9c34e13f5a22c (diff) |
feat: better error messages
Provide "No such file or directory" error if file is not found.
Provide "Unknown Error" if other error found
Should reduce confusion from the generic other error
-rw-r--r-- | src/dir_walker.rs | 123 | ||||
-rw-r--r-- | src/main.rs | 31 | ||||
-rw-r--r-- | src/progress.rs | 11 | ||||
-rw-r--r-- | tests/test_flags.rs | 2 |
4 files changed, 114 insertions, 53 deletions
diff --git a/src/dir_walker.rs b/src/dir_walker.rs index 2863751..120d56c 100644 --- a/src/dir_walker.rs +++ b/src/dir_walker.rs @@ -1,9 +1,11 @@ use std::fs; use std::sync::Arc; +use std::sync::Mutex; use crate::node::Node; use crate::progress::Operation; use crate::progress::PAtomicInfo; +use crate::progress::RuntimeErrors; use crate::progress::ORDERING; use crate::utils::is_filtered_out_due_to_invert_regex; use crate::utils::is_filtered_out_due_to_regex; @@ -28,16 +30,17 @@ pub struct WalkData<'a> { pub ignore_hidden: bool, pub follow_links: bool, pub progress_data: Arc<PAtomicInfo>, + pub errors: Arc<Mutex<RuntimeErrors>>, } -pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> Vec<Node> { +pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: &WalkData) -> Vec<Node> { let mut inodes = HashSet::new(); let top_level_nodes: Vec<_> = dirs .into_iter() .filter_map(|d| { let prog_data = &walk_data.progress_data; prog_data.clear_state(&d); - let node = walk(d, &walk_data, 0)?; + let node = walk(d, walk_data, 0)?; prog_data.state.store(Operation::PREPARING, ORDERING); @@ -126,55 +129,83 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool { fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> { let prog_data = &walk_data.progress_data; - let mut children = vec![]; - - if let Ok(entries) = fs::read_dir(&dir) { - children = entries - .into_iter() - .par_bridge() - .filter_map(|entry| { - if let Ok(ref entry) = entry { - // uncommenting the below line gives simpler code but - // rayon doesn't parallelize as well giving a 3X performance drop - // hence we unravel the recursion a bit - - // return walk(entry.path(), walk_data, depth) - - if !ignore_file(entry, walk_data) { - if let Ok(data) = entry.file_type() { - if data.is_dir() || (walk_data.follow_links && data.is_symlink()) { - return walk(entry.path(), walk_data, depth + 1); - } + let errors = &walk_data.errors; - let node = build_node( - entry.path(), - vec![], - walk_data.filter_regex, - walk_data.invert_filter_regex, - walk_data.use_apparent_size, - data.is_symlink(), - data.is_file(), - walk_data.by_filecount, - depth, - ); - - prog_data.num_files.fetch_add(1, ORDERING); - if let Some(ref file) = node { - prog_data.total_file_size.fetch_add(file.size, ORDERING); - } + let children = if dir.is_dir() { + let read_dir = fs::read_dir(&dir); + match read_dir { + Ok(entries) => { + entries + .into_iter() + .par_bridge() + .filter_map(|entry| { + if let Ok(ref entry) = entry { + // uncommenting the below line gives simpler code but + // rayon doesn't parallelize as well giving a 3X performance drop + // hence we unravel the recursion a bit + + // return walk(entry.path(), walk_data, depth) + + if !ignore_file(entry, walk_data) { + if let Ok(data) = entry.file_type() { + if data.is_dir() + || (walk_data.follow_links && data.is_symlink()) + { + return walk(entry.path(), walk_data, depth + 1); + } - return node; + let node = build_node( + entry.path(), + vec![], + walk_data.filter_regex, + walk_data.invert_filter_regex, + walk_data.use_apparent_size, + data.is_symlink(), + data.is_file(), + walk_data.by_filecount, + depth, + ); + + prog_data.num_files.fetch_add(1, ORDERING); + if let Some(ref file) = node { + prog_data.total_file_size.fetch_add(file.size, ORDERING); + } + + return node; + } + } + } else { + let mut editable_error = errors.lock().unwrap(); + editable_error.no_permissions = true } + None + }) + .collect() + } + Err(failed) => { + let mut editable_error = errors.lock().unwrap(); + match failed.kind() { + std::io::ErrorKind::PermissionDenied => { + editable_error.no_permissions = true; + } + std::io::ErrorKind::NotFound => { + editable_error.file_not_found.insert(failed.to_string()); + } + _ => { + editable_error.unknown_error.insert(failed.to_string()); } - } else { - prog_data.no_permissions.store(true, ORDERING) } - None - }) - .collect(); - } else if !dir.is_file() { - walk_data.progress_data.no_permissions.store(true, ORDERING) - } + vec![] + } + } + } else { + if !dir.is_file() { + let mut editable_error = errors.lock().unwrap(); + let bad_file = dir.as_os_str().to_string_lossy().into(); + editable_error.file_not_found.insert(bad_file); + } + vec![] + }; build_node( dir, children, diff --git a/src/main.rs b/src/main.rs index 52c0a0c..0b81002 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,17 +11,19 @@ mod progress; mod utils; use crate::cli::build_cli; +use crate::progress::RuntimeErrors; use clap::parser::ValuesRef; use dir_walker::WalkData; use display::InitialDisplayData; use filter::AggregateData; use progress::PIndicator; -use progress::ORDERING; use regex::Error; use std::collections::HashSet; use std::fs::read_to_string; use std::panic; use std::process; +use std::sync::Arc; +use std::sync::Mutex; use sysinfo::{System, SystemExt}; use self::display::draw_it; @@ -195,11 +197,12 @@ fn main() { ignore_hidden, follow_links, progress_data: indicator.data.clone(), + errors: Arc::new(Mutex::new(RuntimeErrors::default())), }; let stack_size = config.get_custom_stack_size(&options); init_rayon(&stack_size); - let top_level_nodes = walk_it(simplified_dirs, walk_data); + let top_level_nodes = walk_it(simplified_dirs, &walk_data); let tree = match summarize_file_types { true => get_all_file_types(&top_level_nodes, number_of_lines), @@ -216,12 +219,32 @@ fn main() { } }; - let failed_permissions = indicator.data.no_permissions.load(ORDERING); - indicator.stop(); // Must have stopped indicator before we print to stderr + indicator.stop(); + + let final_errors = walk_data.errors.lock().unwrap(); + let failed_permissions = final_errors.no_permissions; + if !final_errors.file_not_found.is_empty() { + let err = final_errors + .file_not_found + .iter() + .map(|a| a.as_ref()) + .collect::<Vec<&str>>() + .join(", "); + eprintln!("No such file or directory: {}", err); + } if failed_permissions { eprintln!("Did not have permissions for all directories"); } + if !final_errors.unknown_error.is_empty() { + let err = final_errors + .unknown_error + .iter() + .map(|a| a.as_ref()) + .collect::<Vec<&str>>() + .join(", "); + eprintln!("Unknown Error: {}", err); + } if let Some(root_node) = tree { let idd = InitialDisplayData { diff --git a/src/progress.rs b/src/progress.rs index 15f7722..b4d7a89 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -1,8 +1,9 @@ use std::{ + collections::HashSet, io::Write, path::Path, sync::{ - atomic::{AtomicBool, AtomicU64, AtomicU8, AtomicUsize, Ordering}, + atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering}, mpsc::{self, RecvTimeoutError, Sender}, Arc, RwLock, }, @@ -55,7 +56,6 @@ pub struct PAtomicInfo { pub total_file_size: AtomicU64, pub state: AtomicU8, pub current_path: ThreadStringWrapper, - pub no_permissions: AtomicBool, } impl PAtomicInfo { @@ -68,6 +68,13 @@ impl PAtomicInfo { } } +#[derive(Default)] +pub struct RuntimeErrors { + pub no_permissions: bool, + pub file_not_found: HashSet<String>, + pub unknown_error: HashSet<String>, +} + /* -------------------------------------------------------------------------- */ fn format_preparing_str(prog_char: char, data: &PAtomicInfo, is_iso: bool) -> String { diff --git a/tests/test_flags.rs b/tests/test_flags.rs index 199ac0e..3251692 100644 --- a/tests/test_flags.rs +++ b/tests/test_flags.rs @@ -92,7 +92,7 @@ pub fn test_with_bad_param() { let mut cmd = Command::cargo_bin("dust").unwrap(); let result = cmd.arg("bad_place").unwrap(); let stderr = str::from_utf8(&result.stderr).unwrap(); - assert!(stderr.contains("Did not have permissions for all directories")); + assert!(stderr.contains("No such file or directory")); } #[test] |