xtask_toolkit/
checksums.rs1use 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}