/ src / checksummer.rs
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  }