1mod dir_comparer;
2mod file_comparer;
3mod file_hash_cache;
4mod file_hasher;
5mod file_iterator;
6mod progress_reporter;
7
8pub use dir_comparer::{DirectoryComparer, FileComparisonMethod};
9pub use file_comparer::{Classification, FileComparer, FileComparisonResult};
10pub(crate) use file_hash_cache::FileHashCache;
11pub use file_hasher::{DuplicatedFiles, FileHasher};
12pub(crate) use file_iterator::FileIterator;
13pub(crate) use progress_reporter::ProgressReporter;
14
15use std::path::{Path, StripPrefixError};
16
17pub(crate) fn human_readable_size(size: u64) -> String {
18 const KB: u64 = 1024;
19 if size < KB {
20 return format!("{} bytes", size);
21 }
22 const KB_AS_F: f64 = KB as f64;
23 let mut size = size as f64;
24 for unit in ["KB", "MB"] {
25 size /= KB_AS_F;
26 if size < KB_AS_F {
27 return format!("{:.1}{}", size, unit);
28 }
29 }
30 format!("{:.1}GB", size / KB_AS_F)
31}
32
33pub(crate) fn strip_prefix<'a>(path: &'a Path, base: &Path) -> Result<&'a Path, StripPrefixError> {
35 let result = path.strip_prefix(base);
36 #[cfg(windows)]
37 if let Ok(result_path) = result {
38 let result_os_str = result_path.as_os_str();
39 let result_bytes = result_os_str.as_encoded_bytes();
40 if !result_bytes.is_empty() && result_bytes[0] as char == std::path::MAIN_SEPARATOR {
41 return Ok(Path::new(unsafe {
44 use std::ffi::OsStr;
45 OsStr::from_encoded_bytes_unchecked(&result_bytes[1..])
46 }));
47 }
48 }
49 result
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn human_readable_size_tests() {
58 assert_eq!(human_readable_size(0), "0 bytes");
59 assert_eq!(human_readable_size(1), "1 bytes");
60 assert_eq!(human_readable_size(1023), "1023 bytes");
61 assert_eq!(human_readable_size(1024), "1.0KB");
62 assert_eq!(human_readable_size(1024 * 1024), "1.0MB");
63 assert_eq!(human_readable_size(1024 * 1024 * 1024), "1.0GB");
64 assert_eq!(human_readable_size(1024 * 1024 * 1024 * 1024), "1024.0GB");
65 }
66
67 #[cfg(windows)]
68 #[test]
69 fn strip_prefix_share_root() -> anyhow::Result<()> {
70 let path = Path::new(r"\\server\share\dir1\dir2");
71 let base = Path::new(r"\\server\share");
72 assert_eq!(strip_prefix(path, base)?.to_str().unwrap(), r"dir1\dir2");
73 assert_eq!(path.strip_prefix(base)?.to_str().unwrap(), r"dir1\dir2");
74 Ok(())
75 }
76
77 #[cfg(windows)]
78 #[test]
79 fn strip_prefix_unc_root() -> anyhow::Result<()> {
80 let path = Path::new(r"\\?\UNC\server\share\dir1\dir2");
81 let base = Path::new(r"\\?\UNC\server\share");
82 assert_eq!(strip_prefix(path, base)?.to_str().unwrap(), r"dir1\dir2");
83 Ok(())
85 }
86}