Skip to main content
← dvs documentation Rust API reference

dvs/
hashes.rs

1use std::fmt::Display;
2use std::io::Read;
3use std::path::Path;
4
5use anyhow::Result;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Hash)]
9#[serde(rename_all = "lowercase")]
10pub enum HashAlg {
11    Blake3,
12    Md5,
13}
14
15#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
16pub struct Hashes {
17    pub blake3: String,
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub md5: Option<String>,
20}
21
22impl Hashes {
23    /// Stream-hash a file in one pass. Blake3 is always computed;
24    /// `extra` controls which additional algorithms to include.
25    /// Returns `(Hashes, file_size)`.
26    pub fn compute_from_path(path: &Path, extra: &[HashAlg]) -> Result<(Self, u64)> {
27        let mut file = fs_err::File::open(path)?;
28        let mut size: u64 = 0;
29        let mut buf = [0u8; 64 * 1024];
30
31        let mut blake3_hasher = blake3::Hasher::new();
32        let mut md5_context = if extra.contains(&HashAlg::Md5) {
33            Some(md5::Context::new())
34        } else {
35            None
36        };
37
38        loop {
39            let n = file.read(&mut buf)?;
40            if n == 0 {
41                break;
42            }
43            size += n as u64;
44            blake3_hasher.update(&buf[..n]);
45            if let Some(c) = &mut md5_context {
46                c.consume(&buf[..n]);
47            }
48        }
49
50        Ok((
51            Hashes {
52                blake3: blake3_hasher.finalize().to_string(),
53                md5: md5_context.map(|c| format!("{:x}", c.finalize())),
54            },
55            size,
56        ))
57    }
58
59    pub fn get_blake3(&self) -> &str {
60        self.blake3.as_str()
61    }
62
63    pub fn get_by_alg(&self, alg: HashAlg) -> Option<&str> {
64        match alg {
65            HashAlg::Blake3 => Some(&self.blake3),
66            HashAlg::Md5 => self.md5.as_deref(),
67        }
68    }
69}
70
71impl Display for Hashes {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        match &self.md5 {
74            Some(m) => write!(f, "Hashes(md5={}, blake3={})", m, self.blake3),
75            None => write!(f, "Hashes(blake3={})", self.blake3),
76        }
77    }
78}