Skip to main content

ambient_ci/
checksummer.rs

1//! Compute cryptographic checksums.
2
3use std::{
4    fs::File,
5    io::Read,
6    path::{Path, PathBuf},
7};
8
9use indicatif::{ProgressBar, ProgressStyle};
10use sha2::{Digest, Sha256};
11
12/// A computed checksum.
13#[derive(Debug, Eq, PartialEq)]
14pub enum Checksum {
15    /// A SHA-256 checksum.
16    Sha256(String),
17}
18
19impl 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.
29pub struct Checksummer {
30    filename: PathBuf,
31}
32
33const BUF_SIZE: usize = 1025 * 1024;
34
35impl 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)]
86pub 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}