checksummer.rs
1 //! Compute cryptographic checksums. 2 3 use std::{ 4 fs::File, 5 io::Read, 6 path::{Path, PathBuf}, 7 }; 8 9 use indicatif::{ProgressBar, ProgressStyle}; 10 use sha2::{Digest, Sha256}; 11 12 /// A computed checksum. 13 #[derive(Debug, Eq, PartialEq)] 14 pub enum Checksum { 15 /// A SHA-256 checksum. 16 Sha256(String), 17 } 18 19 impl std::fmt::Display for Checksum { 20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 let s = match self { 22 Self::Sha256(s) => s, 23 }; 24 write!(f, "{s}") 25 } 26 } 27 28 /// Compute a checksum. 29 pub struct Checksummer { 30 filename: PathBuf, 31 } 32 33 const BUF_SIZE: usize = 1025 * 1024; 34 35 impl Checksummer { 36 /// Create a new checsum computer for a file. 37 pub fn new(filename: &Path) -> Self { 38 Self { 39 filename: filename.into(), 40 } 41 } 42 43 /// Compute checksum for a file using SHA-256. 44 pub fn sha256(&self) -> Result<Checksum, ChecksummerError> { 45 let metadata = self 46 .filename 47 .metadata() 48 .map_err(|err| ChecksummerError::GetMetadata(self.filename.clone(), err))?; 49 let bar = ProgressBar::new(metadata.len()).with_style( 50 ProgressStyle::with_template( 51 "read {binary_bytes}/{binary_total_bytes} speed {binary_bytes_per_sec} {wide_bar} ETA {eta}", 52 ) 53 .map_err(ChecksummerError::ProgressStyle)?, 54 ); 55 56 let mut sha256 = Sha256::new(); 57 let mut file = File::open(&self.filename) 58 .map_err(|err| ChecksummerError::Open(self.filename.clone(), err))?; 59 60 let mut buf = vec![0; BUF_SIZE]; 61 loop { 62 let mut n = file 63 .read(&mut buf) 64 .map_err(|err| ChecksummerError::Read(self.filename.clone(), err))?; 65 66 if n == 0 { 67 break; 68 } 69 70 sha256.update(&buf[..n]); 71 72 while n >= u64::MAX as usize { 73 bar.inc(u64::MAX); 74 n -= u64::MAX as usize; 75 } 76 bar.inc(n as u64); // this safe as we've ensured n fits into u64. 77 } 78 79 let checksum = Checksum::Sha256(hex::encode(sha256.finalize())); 80 Ok(checksum) 81 } 82 } 83 84 /// Possible errors when computing checksums. 85 #[derive(Debug, thiserror::Error)] 86 pub enum ChecksummerError { 87 /// Can't get file metadata. 88 #[error("failed to get file metadata for {0}")] 89 GetMetadata(PathBuf, #[source] std::io::Error), 90 91 /// Can't create a progress bar. 92 #[error("failed to create a progress bar")] 93 ProgressStyle(#[source] indicatif::style::TemplateError), 94 95 /// Can't open file for reading. 96 #[error("failed to open file for reading: {0}")] 97 Open(PathBuf, #[source] std::io::Error), 98 99 /// Can't read from file. 100 #[error("failed to read from file: {0}")] 101 Read(PathBuf, #[source] std::io::Error), 102 }