use crate::MKTOOL_DEFAULT_THREADS;
use clap::Args;
use pkgsrc::digest::Digest;
use rayon::prelude::*;
use std::env;
use std::fs;
use std::io::{self, Cursor, Read};
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 input = Vec::new();
io::stdin().read_to_end(&mut input)?;
let mut cursor = Cursor::new(input);
println!("{}", algorithm.hash_file(&mut cursor)?);
return Ok(0);
};
let nthreads = match self.jobs {
Some(n) => n,
None => match env::var("MKTOOL_JOBS") {
Ok(n) => match n.parse::<usize>() {
Ok(n) => n,
Err(e) => {
eprintln!(
"WARNING: invalid MKTOOL_JOBS '{n}': {e}, using default"
);
MKTOOL_DEFAULT_THREADS
}
},
Err(_) => MKTOOL_DEFAULT_THREADS,
},
};
rayon::ThreadPoolBuilder::new()
.num_threads(nthreads)
.build_global()
.unwrap();
let mut hashfiles: Vec<DigestResult> = files
.iter()
.map(|f| DigestResult {
path: f.to_path_buf(),
hash: None,
error: String::new(),
})
.collect();
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)
}
}