use std::{
fs::File,
io::Read,
path::{Path, PathBuf},
};
use indicatif::{ProgressBar, ProgressStyle};
use sha2::{Digest, Sha256};
#[derive(Debug, Eq, PartialEq)]
pub enum Checksum {
Sha256(String),
}
impl std::fmt::Display for Checksum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Sha256(s) => s,
};
write!(f, "{s}")
}
}
pub struct Checksummer {
filename: PathBuf,
}
const BUF_SIZE: usize = 1025 * 1024;
impl Checksummer {
pub fn new(filename: &Path) -> Self {
Self {
filename: filename.into(),
}
}
pub fn sha256(&self) -> Result<Checksum, ChecksummerError> {
let metadata = self
.filename
.metadata()
.map_err(|err| ChecksummerError::GetMetadata(self.filename.clone(), err))?;
let bar = ProgressBar::new(metadata.len()).with_style(
ProgressStyle::with_template(
"read {binary_bytes}/{binary_total_bytes} speed {binary_bytes_per_sec} {wide_bar} ETA {eta}",
)
.map_err(ChecksummerError::ProgressStyle)?,
);
let mut sha256 = Sha256::new();
let mut file = File::open(&self.filename)
.map_err(|err| ChecksummerError::Open(self.filename.clone(), err))?;
let mut buf = vec![0; BUF_SIZE];
loop {
let mut n = file
.read(&mut buf)
.map_err(|err| ChecksummerError::Read(self.filename.clone(), err))?;
if n == 0 {
break;
}
sha256.update(&buf[..n]);
while n >= u64::MAX as usize {
bar.inc(u64::MAX);
n -= u64::MAX as usize;
}
bar.inc(n as u64); }
let checksum = Checksum::Sha256(hex::encode(sha256.finalize()));
Ok(checksum)
}
}
#[derive(Debug, thiserror::Error)]
pub enum ChecksummerError {
#[error("failed to get file metadata for {0}")]
GetMetadata(PathBuf, #[source] std::io::Error),
#[error("failed to create a progress bar")]
ProgressStyle(#[source] indicatif::style::TemplateError),
#[error("failed to open file for reading: {0}")]
Open(PathBuf, #[source] std::io::Error),
#[error("failed to read from file: {0}")]
Read(PathBuf, #[source] std::io::Error),
}