1use super::{DynDigest, IOError, IOErrorKind, ProgressInfo, PAGE_SIZE};
4use std::{fs::File, io::Read, path::Path};
5
6pub fn get_hash_file<HashType, P>(
23 path: P,
24 hash: &mut HashType,
25) -> Result<String, IOError>
26where
27 HashType: DynDigest + Clone,
28 P: AsRef<Path>,
29{
30 let mut file = File::open(path)?;
31 let mut buf = [0u8; PAGE_SIZE];
32
33 loop {
34 let i = file.read(&mut buf)?;
35 hash.update(&buf[0..i]);
36
37 if i == 0 {
38 return Ok(crate::encoding::get_lowerhex(hash));
39 }
40 }
41}
42
43pub fn get_hash_files<HashType, P>(
85 paths: &Vec<P>,
86 hash: &mut HashType,
87 num_threads: usize,
88 progress: impl Fn(ProgressInfo),
89) -> Result<String, IOError>
90where
91 HashType: DynDigest + Clone + std::marker::Send,
92 P: AsRef<Path> + std::marker::Sync,
93{
94 if paths.is_empty() {
95 return Err(IOError::from(IOErrorKind::InvalidInput));
96 }
97
98 let pool = rayon::ThreadPoolBuilder::new()
99 .num_threads(num_threads)
100 .build()
101 .unwrap();
102 let mut jobs = Vec::with_capacity(paths.len());
103
104 for path in paths.iter() {
105 jobs.push(pool.install(|| -> Result<(), std::io::Error> {
106 let file_hash = get_hash_file(path, hash)?;
107 hash.update(file_hash.as_bytes());
108 Ok(())
109 }));
110 }
111
112 let mut done_files = 0;
113 for job in jobs.into_iter() {
114 done_files += 1;
115
116 match job {
117 Err(error) => progress(ProgressInfo::Error(error)),
118 Ok(_) => progress(ProgressInfo::Yield(done_files)),
119 }
120 }
121
122 Ok(crate::encoding::get_lowerhex(hash))
123}
124
125#[cfg(test)]
126mod tests {
127 use super::ProgressInfo;
128 use crate::fs::extra;
129 use blake2::{Blake2s256, Digest};
130 use std::path::PathBuf;
131 use walkdir::WalkDir;
132
133 #[test]
134 fn get_hash_file() {
135 let (_temp_dir, path) = extra::generate_random_file(32);
136
137 let mut hash = Blake2s256::new();
138 let result =
139 super::get_hash_file(&path.to_path_buf(), &mut hash).unwrap();
140
141 println!("result: {}", result);
142 assert_eq!(result.len(), 64); }
144
145 #[test]
146 fn get_hash_files() {
147 let (temp_dir, _path) =
148 extra::generate_random_folder_with_files(325, 32);
149
150 let walkdir = WalkDir::new(temp_dir.to_path_buf());
151 let mut paths: Vec<PathBuf> = Vec::new();
152
153 for file in walkdir.into_iter().filter_map(|file| file.ok()) {
154 if file.metadata().unwrap().is_file() {
155 paths.push(file.into_path());
156 }
157 }
158
159 let mut hash = Blake2s256::new();
160 let result =
161 super::get_hash_files(&paths, &mut hash, 4, |info| match info {
162 ProgressInfo::Yield(done_files) => {
163 println!("done files {}/{}", done_files, paths.len())
164 }
165 ProgressInfo::Error(error) => println!("error: {}", error),
166 })
167 .unwrap();
168
169 println!("result: {}", result);
170 assert_eq!(result.len(), 64); }
172}