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
This commit is contained in:
andy.boot
2024-01-03 23:07:06 +00:00
parent cd53fc7494
commit e80892a9e7
4 changed files with 112 additions and 51 deletions
+75 -44
View File
@@ -1,9 +1,11 @@
use std::fs; use std::fs;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use crate::node::Node; use crate::node::Node;
use crate::progress::Operation; use crate::progress::Operation;
use crate::progress::PAtomicInfo; use crate::progress::PAtomicInfo;
use crate::progress::RuntimeErrors;
use crate::progress::ORDERING; use crate::progress::ORDERING;
use crate::utils::is_filtered_out_due_to_invert_regex; use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex; use crate::utils::is_filtered_out_due_to_regex;
@@ -28,16 +30,17 @@ pub struct WalkData<'a> {
pub ignore_hidden: bool, pub ignore_hidden: bool,
pub follow_links: bool, pub follow_links: bool,
pub progress_data: Arc<PAtomicInfo>, 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 mut inodes = HashSet::new();
let top_level_nodes: Vec<_> = dirs let top_level_nodes: Vec<_> = dirs
.into_iter() .into_iter()
.filter_map(|d| { .filter_map(|d| {
let prog_data = &walk_data.progress_data; let prog_data = &walk_data.progress_data;
prog_data.clear_state(&d); 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); 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> { fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
let prog_data = &walk_data.progress_data; let prog_data = &walk_data.progress_data;
let mut children = vec![]; let errors = &walk_data.errors;
if let Ok(entries) = fs::read_dir(&dir) { let children = if dir.is_dir() {
children = entries let read_dir = fs::read_dir(&dir);
.into_iter() match read_dir {
.par_bridge() Ok(entries) => {
.filter_map(|entry| { entries
if let Ok(ref entry) = entry { .into_iter()
// uncommenting the below line gives simpler code but .par_bridge()
// rayon doesn't parallelize as well giving a 3X performance drop .filter_map(|entry| {
// hence we unravel the recursion a bit 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) // return walk(entry.path(), walk_data, depth)
if !ignore_file(entry, walk_data) { if !ignore_file(entry, walk_data) {
if let Ok(data) = entry.file_type() { if let Ok(data) = entry.file_type() {
if data.is_dir() || (walk_data.follow_links && data.is_symlink()) { if data.is_dir()
return walk(entry.path(), walk_data, depth + 1); || (walk_data.follow_links && data.is_symlink())
{
return walk(entry.path(), walk_data, depth + 1);
}
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 node = build_node( let mut editable_error = errors.lock().unwrap();
entry.path(), editable_error.no_permissions = true
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;
} }
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 vec![]
}) }
.collect(); }
} else if !dir.is_file() { } else {
walk_data.progress_data.no_permissions.store(true, ORDERING) 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( build_node(
dir, dir,
children, children,
+27 -4
View File
@@ -11,17 +11,19 @@ mod progress;
mod utils; mod utils;
use crate::cli::build_cli; use crate::cli::build_cli;
use crate::progress::RuntimeErrors;
use clap::parser::ValuesRef; use clap::parser::ValuesRef;
use dir_walker::WalkData; use dir_walker::WalkData;
use display::InitialDisplayData; use display::InitialDisplayData;
use filter::AggregateData; use filter::AggregateData;
use progress::PIndicator; use progress::PIndicator;
use progress::ORDERING;
use regex::Error; use regex::Error;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::panic; use std::panic;
use std::process; use std::process;
use std::sync::Arc;
use std::sync::Mutex;
use sysinfo::{System, SystemExt}; use sysinfo::{System, SystemExt};
use self::display::draw_it; use self::display::draw_it;
@@ -195,11 +197,12 @@ fn main() {
ignore_hidden, ignore_hidden,
follow_links, follow_links,
progress_data: indicator.data.clone(), progress_data: indicator.data.clone(),
errors: Arc::new(Mutex::new(RuntimeErrors::default())),
}; };
let stack_size = config.get_custom_stack_size(&options); let stack_size = config.get_custom_stack_size(&options);
init_rayon(&stack_size); 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 { let tree = match summarize_file_types {
true => get_all_file_types(&top_level_nodes, number_of_lines), 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 // 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 { if failed_permissions {
eprintln!("Did not have permissions for all directories"); 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 { if let Some(root_node) = tree {
let idd = InitialDisplayData { let idd = InitialDisplayData {
+9 -2
View File
@@ -1,8 +1,9 @@
use std::{ use std::{
collections::HashSet,
io::Write, io::Write,
path::Path, path::Path,
sync::{ sync::{
atomic::{AtomicBool, AtomicU64, AtomicU8, AtomicUsize, Ordering}, atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering},
mpsc::{self, RecvTimeoutError, Sender}, mpsc::{self, RecvTimeoutError, Sender},
Arc, RwLock, Arc, RwLock,
}, },
@@ -55,7 +56,6 @@ pub struct PAtomicInfo {
pub total_file_size: AtomicU64, pub total_file_size: AtomicU64,
pub state: AtomicU8, pub state: AtomicU8,
pub current_path: ThreadStringWrapper, pub current_path: ThreadStringWrapper,
pub no_permissions: AtomicBool,
} }
impl PAtomicInfo { 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 { fn format_preparing_str(prog_char: char, data: &PAtomicInfo, is_iso: bool) -> String {
+1 -1
View File
@@ -92,7 +92,7 @@ pub fn test_with_bad_param() {
let mut cmd = Command::cargo_bin("dust").unwrap(); let mut cmd = Command::cargo_bin("dust").unwrap();
let result = cmd.arg("bad_place").unwrap(); let result = cmd.arg("bad_place").unwrap();
let stderr = str::from_utf8(&result.stderr).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] #[test]