// fif - a command-line tool for detecting and optionally correcting files with incorrect extensions. // Copyright (C) 2021 Lynnesbian // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #![forbid(unsafe_code)] #![warn(trivial_casts, unused_lifetimes, unused_qualifications)] use std::io::{stdout, BufWriter, Write}; use std::process::exit; use clap::Clap; use log::{debug, error, info, trace, warn, Level}; use fif::formats::Format; use fif::parameters::{OutputFormat}; use fif::utils::{clap_long_version, os_name}; use fif::{init_db, parameters, formats}; use fif::files::{scan_directory, scan_from_walkdir}; #[cfg(test)] mod tests; #[doc(hidden)] #[allow(clippy::cognitive_complexity)] fn main() { let args: parameters::Parameters = parameters::Parameters::parse(); let mut builder = env_logger::Builder::new(); builder .filter_level(args.default_verbosity()) // set default log level .parse_default_env() // set log level from RUST_LOG .parse_env("FIF_LOG") // set log level from FIF_LOG .format(|buf, r| { let mut style = buf.default_level_style(r.level()); // use bold for warnings and errors style.set_bold(r.level() <= Level::Warn); // only use the first character of the log level name let abbreviation = style.value(r.level().to_string().chars().next().unwrap()); // e.g. [D] Debug message writeln!(buf, "[{}] {}", abbreviation, r.args()) }) .init(); trace!( "fif {}, running on {} {}", clap_long_version(), std::env::consts::ARCH, os_name() ); trace!("Initialising mimetype database"); init_db(); debug!("Iterating directory: {:?}", args.dir); let extensions = args.extensions(); let excludes = args.excluded_extensions(); if let Some(extensions) = &extensions { debug!("Checking files with extensions: {:?}", extensions); } else if let Some(excludes) = &excludes { debug!("Skipping files with extensions: {:?}", excludes); } else { debug!("Checking files regardless of extensions"); } let entries = match scan_directory(&args.dir, extensions.as_ref(), excludes.as_ref(), &args.get_scan_opts()) { // 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. None => exit(exitcode::NOINPUT), Some(e) => e, }; if entries.is_empty() { warn!("No files matching requested options found."); exit(exitcode::OK); } trace!("Found {} items to check", entries.len()); let results: Vec<_> = scan_from_walkdir(&entries, args.canonical_paths) .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!("{:?} ok", result.as_ref().unwrap().file); false } else { true } ) .collect(); trace!("Scanning complete"); for result in &results { match result { Ok(r) => { debug!( "{:?} is of type {}, should have extension \"{}\"", r.file, r.mime, r.recommended_extension().unwrap_or_else(|| "???".into()) ); } Err(f) => warn!("{}", f), } } if results.is_empty() { info!("All files have valid extensions!"); exit(exitcode::OK); } let mut buffered_stdout = BufWriter::new(stdout()); let result = match args.output_format { OutputFormat::Sh => formats::Shell.write_all(&mut buffered_stdout, &results), OutputFormat::PowerShell => formats::PowerShell.write_all(&mut buffered_stdout, &results), #[cfg(feature = "json")] OutputFormat::Json => formats::Json.write_all(&mut buffered_stdout, &results), OutputFormat::Text => formats::Text.write_all(&mut buffered_stdout, &results), }; if result.is_err() { error!("Failed to write to stdout."); exit(exitcode::IOERR); } if buffered_stdout.flush().is_err() { error!("Failed to flush stdout."); exit(exitcode::IOERR); } debug!("Done"); }