diff --git a/Cargo.lock b/Cargo.lock index 4903e2b..a0e7773 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "exitcode" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" + [[package]] name = "fif" version = "0.2.2" @@ -174,6 +180,7 @@ dependencies = [ "cached", "clap", "env_logger", + "exitcode", "infer", "log", "mime_guess", diff --git a/Cargo.toml b/Cargo.toml index 3b35e52..5c6de47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ snailquote = "0.3.0" once_cell = "1.5.2" rayon = { version = "1.5.0", optional = true } infer = { version = "0.3.4", optional = true } +exitcode = "1.1.2" # use git version while waiting on a release incorporating https://github.com/ebassi/xdg-mime-rs/commit/de5a6dd [target.'cfg(not(target_os = "windows"))'.dependencies] diff --git a/src/main.rs b/src/main.rs index b3a64e9..89e66b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use std::io::{stdout, BufWriter}; use std::path::{Path, PathBuf}; use clap::Clap; -use log::{debug, info, trace, warn}; +use log::{debug, info, trace, warn, error}; use once_cell::sync::OnceCell; #[cfg(feature = "multi-threaded")] use rayon::prelude::*; @@ -30,6 +30,7 @@ use crate::formats::{Format, Script}; use crate::mimedb::MimeDb; use crate::parameters::OutputFormat; use crate::scanerror::ScanError; +use std::process::exit; mod findings; mod formats; @@ -154,6 +155,7 @@ fn scan_from_walkdir(entries: Vec) -> Vec = stepper .filter_entry(|e| wanted_file(&args, &extensions, e)) // filter out unwanted files - .filter_map(|e| e.ok()) // ignore anything that fails, e.g. files we don't have read access on + .filter_map(|e| { + if let Err(err) = &e { + debug!("uh oh spaghettio!! {:#?}", e); + // log errors to stdout, and remove them from the iterator + let path = err + .path() + .map_or("General error".into(), Path::to_string_lossy); + + if err.depth() == 0 { + // if something goes wrong while trying to read the root directory, we're probably not going to get much done + probably_fatal_error = true; + } + + // TODO: is there a way to just say `map_or(x, |y| y).thing()` instead of `map_or(x.thing(), |y| y.thing())`? + // i don't care whether i'm returning a walkdir error or an io error, i just care about whether or not it + // implements ToString (which they both do). map_or doesn't work on trait objects though :( + error!("{}: {}", path, err.io_error().map_or(err.to_string(), |e|e.to_string())); + return None + } + e.ok() + }) .filter(|e| !e.file_type().is_dir()) // remove directories from the final list .collect(); + if entries.is_empty() { + if probably_fatal_error { + // no need to log anything for fatal errors - fif will already have printed something obvious like + // "[ERROR] /fake/path: No such file or directory (os error 2)". we can assume that if this has happened, the dir + // given as input doesn't exist or is otherwise unreadable. + exit(exitcode::NOINPUT); + } + + warn!("No files matching requested options found."); + exit(exitcode::DATAERR); + } + trace!("Found {} items to check", entries.len()); - let results = scan_from_walkdir(entries); + let results: Vec<_> = scan_from_walkdir(entries) + .into_iter() + .filter(|result| + result.is_err() + || !result.as_ref().unwrap().valid + // TODO: find a way to trace! the valid files without doing ↓ + // || if result.as_ref().unwrap().valid { trace!("{:?} is fine", result.as_ref().unwrap().file); false } else { true } + ) + .collect(); for result in &results { match result { Ok(r) => { - if !r.valid { - info!( - "{:?} should have file extension {}", - r.file, - r.recommended_extension().unwrap_or("???".into()) - ) - } else { - trace!("{:?} is totally fine", r.file) - } + info!( + "{:?} should have file extension {}", + r.file, + r.recommended_extension().unwrap_or_else(|| "???".into()) + ) } Err(f) => warn!("{:#?}: Error 0uo - {}", f.1, f.0), } } + if results.is_empty() { info!("All files have valid extensions!") } + match args.output_format { OutputFormat::Script => { let s = Script::new(); - s.write_all(&results, &mut BufWriter::new(stdout().lock())) - .expect("failed to output"); + if s.write_all( + &results, + &mut BufWriter::new(stdout().lock()) + ).is_err() { + exit(exitcode::IOERR); + } } OutputFormat::Text => todo!(), } diff --git a/src/scanerror.rs b/src/scanerror.rs index ccb61ae..df8d710 100644 --- a/src/scanerror.rs +++ b/src/scanerror.rs @@ -1,5 +1,6 @@ use std::fmt::{Display, Formatter, Result}; +#[derive(Debug)] pub enum ScanError { File, Mime,