file_hashing/
file.rs

1//! File functions
2
3use super::{DynDigest, IOError, IOErrorKind, ProgressInfo, PAGE_SIZE};
4use std::{fs::File, io::Read, path::Path};
5
6/// Get hash from **file**
7///
8/// # Example
9///
10/// ```no_run
11/// use std::path::PathBuf;
12/// use blake2::{Blake2s256, Digest};
13/// use file_hashing::get_hash_file;
14///
15/// let path = PathBuf::from("/home/gladi/test-hashing.txt");
16///
17/// let mut hash = Blake2s256::new();
18/// let result = get_hash_file(&path, &mut hash).unwrap();
19///
20/// assert_eq!(result.len(), 64); // Blake2s256 len == 64
21/// ```
22pub 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
43/// Get hash from **files**
44///
45/// # Warning
46///
47/// if you want to get the hash from a folder then it's better to use this [function](get_hash_folder)
48///
49/// If you can get all files from a folder with this [function](fs::get_all_file_from_folder)
50///
51/// # Example
52///
53/// ```no_run
54/// use std::path::PathBuf;
55/// use blake2::{Blake2s256, Digest};
56/// use file_hashing::{get_hash_files, ProgressInfo};
57/// use walkdir::WalkDir;
58///
59/// let walkdir = WalkDir::new("/home/gladi/Pictures");
60/// let mut paths: Vec<PathBuf> = Vec::new();
61///
62/// for file in walkdir.into_iter().filter_map(|file| file.ok()) {
63///     if file.metadata().unwrap().is_file() {
64///         paths.push(file.into_path());
65///     }
66/// }
67///
68/// let mut hash = Blake2s256::new();
69/// let result = get_hash_files(&paths, &mut hash, 4, |info| match info {
70///     ProgressInfo::Yield(done_files) => {
71///         println!("done files {}/{}", done_files, paths.len())
72///     }
73///     ProgressInfo::Error(error) => println!("error: {}", error),
74/// })
75/// .unwrap();
76///
77/// println!("result: {}", result);
78/// assert_eq!(result.len(), 64); // Blake2s256 len == 64
79/// ```
80///
81/// # Error
82///
83/// * if the **path** variable is empty, the error **IOErrorKind::InvalidInput** will be returned
84pub 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); // Blake2s256 len == 64
143    }
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); // Blake2s256 len == 64
171    }
172}