ccsync_core/comparison/
hash.rs1use std::fs::File;
4use std::io::{BufReader, Read};
5use std::path::Path;
6
7use anyhow::Context;
8use sha2::{Digest, Sha256};
9
10use crate::error::Result;
11
12pub type FileHash = [u8; 32];
14
15pub struct FileHasher;
17
18impl Default for FileHasher {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl FileHasher {
25 #[must_use]
27 pub const fn new() -> Self {
28 Self
29 }
30
31 pub fn hash(path: &Path) -> Result<FileHash> {
37 let file = File::open(path)
38 .with_context(|| format!("Failed to open file for hashing: {}", path.display()))?;
39
40 let mut reader = BufReader::new(file);
41 let mut hasher = Sha256::new();
42 let mut buffer = [0; 8192]; loop {
45 let bytes_read = reader
46 .read(&mut buffer)
47 .with_context(|| format!("Failed to read file: {}", path.display()))?;
48
49 if bytes_read == 0 {
50 break;
51 }
52
53 hasher.update(&buffer[..bytes_read]);
54 }
55
56 Ok(hasher.finalize().into())
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use std::fs;
64 use tempfile::TempDir;
65
66 #[test]
67 fn test_hash_identical_files() {
68 let tmp = TempDir::new().unwrap();
69 let file1 = tmp.path().join("file1.txt");
70 let file2 = tmp.path().join("file2.txt");
71
72 fs::write(&file1, "same content").unwrap();
73 fs::write(&file2, "same content").unwrap();
74
75 let _hasher = FileHasher::new();
76 let hash1 = FileHasher::hash(&file1).unwrap();
77 let hash2 = FileHasher::hash(&file2).unwrap();
78
79 assert_eq!(hash1, hash2);
80 }
81
82 #[test]
83 fn test_hash_different_files() {
84 let tmp = TempDir::new().unwrap();
85 let file1 = tmp.path().join("file1.txt");
86 let file2 = tmp.path().join("file2.txt");
87
88 fs::write(&file1, "content 1").unwrap();
89 fs::write(&file2, "content 2").unwrap();
90
91 let _hasher = FileHasher::new();
92 let hash1 = FileHasher::hash(&file1).unwrap();
93 let hash2 = FileHasher::hash(&file2).unwrap();
94
95 assert_ne!(hash1, hash2);
96 }
97
98 #[test]
99 fn test_hash_large_file() {
100 let tmp = TempDir::new().unwrap();
101 let file = tmp.path().join("large.bin");
102
103 let content = vec![0u8; 1024 * 1024];
105 fs::write(&file, &content).unwrap();
106
107 let _hasher = FileHasher::new();
108 let hash = FileHasher::hash(&file);
109
110 assert!(hash.is_ok());
111 }
112
113 #[test]
114 fn test_hash_empty_file() {
115 let tmp = TempDir::new().unwrap();
116 let file = tmp.path().join("empty.txt");
117 fs::write(&file, "").unwrap();
118
119 let _hasher = FileHasher::new();
120 let hash = FileHasher::hash(&file);
121
122 assert!(hash.is_ok());
123 }
124}