1use anyhow::{Result, anyhow};
2use sha2::{Digest, Sha256, Sha512};
3use std::fs;
4use std::io::Read;
5use std::path::Path;
6use walkdir::WalkDir;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum HashAlgorithm {
11 Sha512,
13 Sha256,
15}
16
17impl HashAlgorithm {
18 pub fn from_len(len: usize) -> Option<Self> {
19 match len {
20 128 => Some(HashAlgorithm::Sha512),
21 64 => Some(HashAlgorithm::Sha256),
22 _ => None,
23 }
24 }
25
26 pub fn from_name(name: &str) -> Option<Self> {
27 match name.to_lowercase().as_str() {
28 "sha512" => Some(HashAlgorithm::Sha512),
29 "sha256" => Some(HashAlgorithm::Sha256),
30 _ => None,
31 }
32 }
33}
34
35pub fn calculate_file_hash(path: &Path, algo: HashAlgorithm) -> Result<String> {
37 let mut file =
38 fs::File::open(path).map_err(|e| anyhow!("Failed to open file {:?}: {}", path, e))?;
39
40 match algo {
41 HashAlgorithm::Sha512 => {
42 let mut hasher = Sha512::new();
43 let mut buffer = [0; 8192];
44 loop {
45 let bytes_read = file.read(&mut buffer)?;
46 if bytes_read == 0 {
47 break;
48 }
49 hasher.update(&buffer[..bytes_read]);
50 }
51 Ok(hex::encode(hasher.finalize()))
52 }
53 HashAlgorithm::Sha256 => {
54 let mut hasher = Sha256::new();
55 let mut buffer = [0; 8192];
56 loop {
57 let bytes_read = file.read(&mut buffer)?;
58 if bytes_read == 0 {
59 break;
60 }
61 hasher.update(&buffer[..bytes_read]);
62 }
63 Ok(hex::encode(hasher.finalize()))
64 }
65 }
66}
67
68pub fn calculate_dir_hash(path: &Path) -> Result<String> {
78 if !path.is_dir() {
79 return Err(anyhow!("Path is not a directory"));
80 }
81
82 let mut hasher = Sha512::new();
83 let mut paths = Vec::new();
84
85 for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
86 if entry.file_type().is_file() {
87 paths.push(entry.path().to_path_buf());
88 }
89 }
90
91 paths.sort();
92
93 for file_path in paths {
94 if let Ok(rel_path) = file_path.strip_prefix(path) {
95 let rel_path_str = rel_path.to_string_lossy().to_string().replace('\\', "/");
96 let path_bytes = rel_path_str.as_bytes();
97 hasher.update((path_bytes.len() as u64).to_le_bytes());
98 hasher.update(path_bytes);
99 }
100
101 let mut file = fs::File::open(&file_path)?;
102 let metadata = file.metadata()?;
103 hasher.update(metadata.len().to_le_bytes());
104
105 let mut buffer = [0; 8192];
106 loop {
107 let bytes_read = file.read(&mut buffer)?;
108 if bytes_read == 0 {
109 break;
110 }
111 hasher.update(&buffer[..bytes_read]);
112 }
113 }
114
115 Ok(hex::encode(hasher.finalize()))
116}