Skip to content

Commit 81dde70

Browse files
committed
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
1 parent cd53fc7 commit 81dde70

File tree

4 files changed

+114
-53
lines changed

4 files changed

+114
-53
lines changed

src/dir_walker.rs

+77-46
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use std::fs;
22
use std::sync::Arc;
3+
use std::sync::Mutex;
34

45
use crate::node::Node;
56
use crate::progress::Operation;
67
use crate::progress::PAtomicInfo;
8+
use crate::progress::RuntimeErrors;
79
use crate::progress::ORDERING;
810
use crate::utils::is_filtered_out_due_to_invert_regex;
911
use crate::utils::is_filtered_out_due_to_regex;
@@ -28,16 +30,17 @@ pub struct WalkData<'a> {
2830
pub ignore_hidden: bool,
2931
pub follow_links: bool,
3032
pub progress_data: Arc<PAtomicInfo>,
33+
pub errors: Arc<Mutex<RuntimeErrors>>,
3134
}
3235

33-
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> Vec<Node> {
36+
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: &WalkData) -> Vec<Node> {
3437
let mut inodes = HashSet::new();
3538
let top_level_nodes: Vec<_> = dirs
3639
.into_iter()
3740
.filter_map(|d| {
3841
let prog_data = &walk_data.progress_data;
3942
prog_data.clear_state(&d);
40-
let node = walk(d, &walk_data, 0)?;
43+
let node = walk(d, walk_data, 0)?;
4144

4245
prog_data.state.store(Operation::PREPARING, ORDERING);
4346

@@ -126,55 +129,83 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
126129

127130
fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
128131
let prog_data = &walk_data.progress_data;
129-
let mut children = vec![];
130-
131-
if let Ok(entries) = fs::read_dir(&dir) {
132-
children = entries
133-
.into_iter()
134-
.par_bridge()
135-
.filter_map(|entry| {
136-
if let Ok(ref entry) = entry {
137-
// uncommenting the below line gives simpler code but
138-
// rayon doesn't parallelize as well giving a 3X performance drop
139-
// hence we unravel the recursion a bit
140-
141-
// return walk(entry.path(), walk_data, depth)
142-
143-
if !ignore_file(entry, walk_data) {
144-
if let Ok(data) = entry.file_type() {
145-
if data.is_dir() || (walk_data.follow_links && data.is_symlink()) {
146-
return walk(entry.path(), walk_data, depth + 1);
147-
}
132+
let errors = &walk_data.errors;
148133

149-
let node = build_node(
150-
entry.path(),
151-
vec![],
152-
walk_data.filter_regex,
153-
walk_data.invert_filter_regex,
154-
walk_data.use_apparent_size,
155-
data.is_symlink(),
156-
data.is_file(),
157-
walk_data.by_filecount,
158-
depth,
159-
);
160-
161-
prog_data.num_files.fetch_add(1, ORDERING);
162-
if let Some(ref file) = node {
163-
prog_data.total_file_size.fetch_add(file.size, ORDERING);
164-
}
134+
let children = if dir.is_dir() {
135+
let read_dir = fs::read_dir(&dir);
136+
match read_dir {
137+
Ok(entries) => {
138+
entries
139+
.into_iter()
140+
.par_bridge()
141+
.filter_map(|entry| {
142+
if let Ok(ref entry) = entry {
143+
// uncommenting the below line gives simpler code but
144+
// rayon doesn't parallelize as well giving a 3X performance drop
145+
// hence we unravel the recursion a bit
146+
147+
// return walk(entry.path(), walk_data, depth)
148+
149+
if !ignore_file(entry, walk_data) {
150+
if let Ok(data) = entry.file_type() {
151+
if data.is_dir()
152+
|| (walk_data.follow_links && data.is_symlink())
153+
{
154+
return walk(entry.path(), walk_data, depth + 1);
155+
}
165156

166-
return node;
157+
let node = build_node(
158+
entry.path(),
159+
vec![],
160+
walk_data.filter_regex,
161+
walk_data.invert_filter_regex,
162+
walk_data.use_apparent_size,
163+
data.is_symlink(),
164+
data.is_file(),
165+
walk_data.by_filecount,
166+
depth,
167+
);
168+
169+
prog_data.num_files.fetch_add(1, ORDERING);
170+
if let Some(ref file) = node {
171+
prog_data.total_file_size.fetch_add(file.size, ORDERING);
172+
}
173+
174+
return node;
175+
}
176+
}
177+
} else {
178+
let mut editable_error = errors.lock().unwrap();
179+
editable_error.no_permissions = true
167180
}
181+
None
182+
})
183+
.collect()
184+
}
185+
Err(failed) => {
186+
let mut editable_error = errors.lock().unwrap();
187+
match failed.kind() {
188+
std::io::ErrorKind::PermissionDenied => {
189+
editable_error.no_permissions = true;
190+
}
191+
std::io::ErrorKind::NotFound => {
192+
editable_error.file_not_found.insert(failed.to_string());
193+
}
194+
_ => {
195+
editable_error.unknown_error.insert(failed.to_string());
168196
}
169-
} else {
170-
prog_data.no_permissions.store(true, ORDERING)
171197
}
172-
None
173-
})
174-
.collect();
175-
} else if !dir.is_file() {
176-
walk_data.progress_data.no_permissions.store(true, ORDERING)
177-
}
198+
vec![]
199+
}
200+
}
201+
} else {
202+
if !dir.is_file() {
203+
let mut editable_error = errors.lock().unwrap();
204+
let bad_file = dir.as_os_str().to_string_lossy().into();
205+
editable_error.file_not_found.insert(bad_file);
206+
}
207+
vec![]
208+
};
178209
build_node(
179210
dir,
180211
children,

src/main.rs

+27-4
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ mod progress;
1111
mod utils;
1212

1313
use crate::cli::build_cli;
14+
use crate::progress::RuntimeErrors;
1415
use clap::parser::ValuesRef;
1516
use dir_walker::WalkData;
1617
use display::InitialDisplayData;
1718
use filter::AggregateData;
1819
use progress::PIndicator;
19-
use progress::ORDERING;
2020
use regex::Error;
2121
use std::collections::HashSet;
2222
use std::fs::read_to_string;
2323
use std::panic;
2424
use std::process;
25+
use std::sync::Arc;
26+
use std::sync::Mutex;
2527
use sysinfo::{System, SystemExt};
2628

2729
use self::display::draw_it;
@@ -195,11 +197,12 @@ fn main() {
195197
ignore_hidden,
196198
follow_links,
197199
progress_data: indicator.data.clone(),
200+
errors: Arc::new(Mutex::new(RuntimeErrors::default())),
198201
};
199202
let stack_size = config.get_custom_stack_size(&options);
200203
init_rayon(&stack_size);
201204

202-
let top_level_nodes = walk_it(simplified_dirs, walk_data);
205+
let top_level_nodes = walk_it(simplified_dirs, &walk_data);
203206

204207
let tree = match summarize_file_types {
205208
true => get_all_file_types(&top_level_nodes, number_of_lines),
@@ -216,12 +219,32 @@ fn main() {
216219
}
217220
};
218221

219-
let failed_permissions = indicator.data.no_permissions.load(ORDERING);
220-
indicator.stop();
221222
// Must have stopped indicator before we print to stderr
223+
indicator.stop();
224+
225+
let final_errors = walk_data.errors.lock().unwrap();
226+
let failed_permissions = final_errors.no_permissions;
227+
if !final_errors.file_not_found.is_empty() {
228+
let err = final_errors
229+
.file_not_found
230+
.iter()
231+
.map(|a| a.as_ref())
232+
.collect::<Vec<&str>>()
233+
.join(", ");
234+
eprintln!("No such file or directory: {}", err);
235+
}
222236
if failed_permissions {
223237
eprintln!("Did not have permissions for all directories");
224238
}
239+
if !final_errors.unknown_error.is_empty() {
240+
let err = final_errors
241+
.unknown_error
242+
.iter()
243+
.map(|a| a.as_ref())
244+
.collect::<Vec<&str>>()
245+
.join(", ");
246+
eprintln!("Unknown Error: {}", err);
247+
}
225248

226249
if let Some(root_node) = tree {
227250
let idd = InitialDisplayData {

src/progress.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::{
2+
collections::HashSet,
23
io::Write,
34
path::Path,
45
sync::{
5-
atomic::{AtomicBool, AtomicU64, AtomicU8, AtomicUsize, Ordering},
6+
atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering},
67
mpsc::{self, RecvTimeoutError, Sender},
78
Arc, RwLock,
89
},
@@ -55,7 +56,6 @@ pub struct PAtomicInfo {
5556
pub total_file_size: AtomicU64,
5657
pub state: AtomicU8,
5758
pub current_path: ThreadStringWrapper,
58-
pub no_permissions: AtomicBool,
5959
}
6060

6161
impl PAtomicInfo {
@@ -68,6 +68,13 @@ impl PAtomicInfo {
6868
}
6969
}
7070

71+
#[derive(Default)]
72+
pub struct RuntimeErrors {
73+
pub no_permissions: bool,
74+
pub file_not_found: HashSet<String>,
75+
pub unknown_error: HashSet<String>,
76+
}
77+
7178
/* -------------------------------------------------------------------------- */
7279

7380
fn format_preparing_str(prog_char: char, data: &PAtomicInfo, is_iso: bool) -> String {

tests/test_flags.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ pub fn test_with_bad_param() {
9292
let mut cmd = Command::cargo_bin("dust").unwrap();
9393
let result = cmd.arg("bad_place").unwrap();
9494
let stderr = str::from_utf8(&result.stderr).unwrap();
95-
assert!(stderr.contains("Did not have permissions for all directories"));
95+
assert!(stderr.contains("No such file or directory"));
9696
}
9797

9898
#[test]

0 commit comments

Comments
 (0)