145 lines
4.5 KiB
Rust
145 lines
4.5 KiB
Rust
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
#![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::files::{scan_directory, scan_from_walkdir};
|
|
use fif::formats::Format;
|
|
use fif::parameters::OutputFormat;
|
|
use fif::utils::{clap_long_version, os_name};
|
|
use fif::{formats, init_db, parameters};
|
|
|
|
#[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");
|
|
}
|