Skip to main content

xtask_toolkit/
checksums.rs

1use std::collections::HashMap;
2use std::path::Path;
3
4pub use sha2;
5use sha2::{Digest, Sha256};
6use walkdir::WalkDir;
7
8#[derive(Debug, Eq, PartialEq, Hash, Clone)]
9pub struct Checksum(String);
10
11pub const UNKNOWN_FILENAME: &str = "unknown";
12
13fn strfilename(path: &std::path::Path) -> String {
14    path.file_name()
15        .map(|x| x.to_string_lossy().to_string())
16        .unwrap_or(UNKNOWN_FILENAME.to_string())
17}
18
19pub trait PathChecksum {
20    fn calculate_sha256(&self) -> Result<Checksum, std::io::Error>;
21    fn calculate_sha256_filtered(
22        &self,
23        filter: fn(&std::path::Path) -> bool,
24    ) -> Result<Checksum, std::io::Error>;
25    fn calculate_entries_sha256(&self) -> Result<HashMap<String, Checksum>, std::io::Error>;
26}
27
28impl PathChecksum for Path {
29    fn calculate_sha256_filtered(
30        &self,
31        filter: fn(&std::path::Path) -> bool,
32    ) -> Result<Checksum, std::io::Error> {
33        if self.is_file() {
34            let binary_content = std::fs::read(&self)?;
35
36            let mut hasher = Sha256::new();
37            hasher.update(&binary_content);
38            Ok(Checksum(format!("{:x}", hasher.finalize())))
39        } else {
40            let mut result = String::new();
41
42            for entry in WalkDir::new(self)
43                .sort_by_file_name()
44                .into_iter()
45                .filter(|entry| entry.as_ref().is_ok_and(|x| filter(x.path())))
46                .filter_map(Result::ok)
47            {
48                if entry.file_type().is_file() {
49                    result += &entry.path().calculate_sha256()?.0;
50                }
51            }
52
53            let mut hasher = Sha256::new();
54            hasher.update(&result);
55            Ok(Checksum(format!("{:x}", hasher.finalize())))
56        }
57    }
58
59    fn calculate_entries_sha256(&self) -> Result<HashMap<String, Checksum>, std::io::Error> {
60        if !self.is_dir() {
61            let checksum = self.calculate_sha256()?;
62            return Ok(HashMap::from([(strfilename(self), checksum)]));
63        }
64
65        let mut result = HashMap::new();
66        for entry in self.read_dir()? {
67            let entry_path = entry?.path();
68            let checksum = entry_path.calculate_sha256()?;
69            let filename = strfilename(&entry_path);
70
71            result.insert(filename, checksum);
72        }
73
74        Ok(result)
75    }
76
77    fn calculate_sha256(&self) -> Result<Checksum, std::io::Error> {
78        self.calculate_sha256_filtered(|_| true)
79    }
80}
81
82impl Checksum {
83    pub fn get(&self) -> &str {
84        self.0.as_str()
85    }
86
87    pub fn string(&self) -> String {
88        self.0.clone()
89    }
90}
91
92pub trait ChecksumsToFile {
93    fn save_checksum(&self, path: &std::path::Path) -> Result<(), std::io::Error>;
94}
95
96impl<T> ChecksumsToFile for T
97where
98    T: Iterator<Item = (String, Checksum)> + Clone
99{
100    fn save_checksum(&self, path: &std::path::Path) -> Result<(), std::io::Error> {
101        use std::io::Write;
102
103        let mut file = std::fs::File::create(path)?;
104
105        let checksum_contents = self.clone().fold(String::new(), |acc, x| {
106            format!("{}{} {}\n", acc, x.0, x.1.get())
107        });
108
109        file.write(checksum_contents.as_bytes())?;
110        Ok(())
111    }
112}