use crate::build_thread_pool;
use clap::Args;
use pkgsrc::digest::Digest;
use rayon::prelude::*;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Args, Debug)]
pub struct DigestCmd {
#[arg(value_name = "algorithm")]
#[arg(help = "Algorithm to use")]
algorithm: String,
#[arg(short = 'j', value_name = "jobs")]
#[arg(help = "Maximum number of threads (or \"MKTOOL_JOBS\" env var)")]
jobs: Option<usize>,
#[arg(value_name = "file")]
#[arg(help = "List of files to calculate checksums for")]
files: Option<Vec<PathBuf>>,
}
struct DigestResult {
path: PathBuf,
hash: Option<String>,
error: String,
}
impl DigestCmd {
pub fn run(&self) -> Result<i32, Box<dyn std::error::Error>> {
let algorithm = Digest::from_str(&self.algorithm)?;
let Some(files) = &self.files else {
let mut stdin = io::stdin().lock();
println!("{}", algorithm.hash_file(&mut stdin)?);
return Ok(0);
};
let pool = build_thread_pool(self.jobs)?;
let mut hashfiles: Vec<DigestResult> = files
.iter()
.map(|f| DigestResult {
path: f.to_path_buf(),
hash: None,
error: String::new(),
})
.collect();
pool.install(|| {
hashfiles.par_iter_mut().for_each(|file| {
match fs::File::open(&file.path) {
Ok(mut f) => match algorithm.hash_file(&mut f) {
Ok(h) => file.hash = Some(h),
Err(e) => file.error = e.to_string(),
},
Err(e) => file.error = e.to_string(),
}
});
});
let mut rv = 0;
for file in hashfiles {
if let Some(hash) = file.hash {
println!("{} ({}) = {}", algorithm, file.path.display(), hash);
} else {
eprintln!("{}: {}", file.path.display(), file.error);
rv = 1;
}
}
Ok(rv)
}
}