use std::{
io::{Result as IoResult, Write},
path::{Path, PathBuf},
};
use anyhow::Result;
use clap::{Parser, Subcommand};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use wholesum::{hashfile::HashFile, Algorithm, Mode};
#[derive(Parser)]
#[clap(about, author, version)]
struct Opt {
#[clap(short, long, arg_enum, default_value_t = Mode::Text)]
mode: Mode,
#[clap(short, long, arg_enum, default_value_t = Algorithm::Blake3)]
algorithm: Algorithm,
#[clap(short, long)]
prefix: bool,
#[clap(parse(from_os_str))]
files: Vec<PathBuf>,
#[clap(subcommand)]
cmd: Option<Command>,
}
#[derive(Subcommand)]
enum Command {
Check { file: PathBuf },
}
const IS_A_DIRECTORY: i32 = 21;
fn main() -> Result<()> {
let opt = Opt::parse();
if let Some(Command::Check { file }) = opt.cmd {
verify_files(file)
} else {
hash_files(opt)
}
}
fn verify_files(file: PathBuf) -> Result<()> {
let hash_file = HashFile::parse(file)?;
let matches = hash_file
.entries
.into_par_iter()
.map(|entry| {
let hasher = entry
.algorithm
.or(hash_file.algorithm)
.unwrap_or(Algorithm::Blake3)
.into_hasher();
let hash = hasher(Path::new(&entry.file))?;
Ok((entry.file, hash == entry.hash))
})
.collect::<Result<Vec<_>>>()?;
let width = matches
.iter()
.map(|(file, _)| file.len())
.max()
.unwrap_or_default();
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
for (file, valid) in matches {
writeln!(
stdout,
"{:width$} {}",
file,
if valid { "OK" } else { "ERR" },
width = width
)?;
}
stdout.flush()?;
Ok(())
}
fn hash_files(opt: Opt) -> Result<()> {
let hasher = opt.algorithm.into_hasher();
let mut hashes = opt
.files
.into_par_iter()
.filter_map(|file| match hasher(&file) {
Ok(hash) => Some(Ok((hash, file))),
Err(e) if e.raw_os_error() == Some(IS_A_DIRECTORY) => None,
Err(e) => Some(Err(e)),
})
.collect::<IoResult<Vec<_>>>()?;
hashes.sort_unstable_by(|(_, a), (_, b)| a.cmp(b));
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
for (hash, path) in hashes {
if opt.prefix {
write!(stdout, "{} ", opt.algorithm)?;
}
writeln!(stdout, "{} {}{}", hash, opt.mode, path.display())?;
}
stdout.flush()?;
Ok(())
}