1use rayon::prelude::*;
6use std::{fs, io, path::Path};
7
8const KIBIBYTE: u64 = 1 << 10;
9const MEBIBYTE: u64 = 1 << 20;
10const GIBIBYTE: u64 = 1 << 30;
11const TEBIBYTE: u64 = 1 << 40;
12const PEBIBYTE: u64 = 1 << 50;
13const EXBIBYTE: u64 = 1 << 60;
14
15pub fn get_size_in_bytes(path: &Path) -> io::Result<u64> {
23 match fs::symlink_metadata(path) {
24 Ok(meta) if meta.is_file() => Ok(meta.len()),
25 Ok(meta) if meta.is_dir() => get_dir_size(path),
26 Ok(_) => Ok(0),
27 Err(e) => Err(e),
28 }
29}
30
31pub fn get_size_in_human_bytes(path: &Path) -> io::Result<String> {
39 Ok(convert_to_human_bytes(get_size_in_bytes(path)?, false))
40}
41
42pub fn get_size_in_abbr_human_bytes(path: &Path) -> io::Result<String> {
50 Ok(convert_to_human_bytes(get_size_in_bytes(path)?, true))
51}
52
53fn get_dir_size(path: &Path) -> io::Result<u64> {
54 let entries: Vec<_> = fs::read_dir(path)?.collect();
55
56 let total = entries
57 .par_iter()
58 .filter_map(|entry| match entry {
59 Ok(entry) => match entry.metadata() {
60 Ok(meta) if meta.is_file() => Some(meta.len()),
61 Ok(meta) if meta.is_dir() => get_dir_size(&entry.path()).ok(),
62 _ => None,
63 },
64 _ => None,
65 })
66 .sum();
67
68 Ok(total)
69}
70
71fn convert_to_human_bytes(size_in_bytes: u64, abbr: bool) -> String {
72 for ((min_bytes, max_bytes), abbr_unit, full_unit) in [
73 ((1, KIBIBYTE), "B", "Bytes"), ((KIBIBYTE, MEBIBYTE), "K", "KiB"), ((MEBIBYTE, GIBIBYTE), "M", "MiB"), ((GIBIBYTE, TEBIBYTE), "G", "GiB"), ((TEBIBYTE, PEBIBYTE), "T", "TiB"), ((PEBIBYTE, EXBIBYTE), "P", "PiB"), ] {
80 if size_in_bytes < max_bytes {
81 return format!(
82 "{} {}",
83 size_in_bytes / min_bytes,
84 if abbr { abbr_unit } else { full_unit }
85 );
86 }
87 }
88
89 format!(
90 "{} {}",
91 size_in_bytes / EXBIBYTE,
92 if abbr { "E" } else { "EiB" }
93 )
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_convert_to_human_bytes() {
102 for (size_in_bytes, human_bytes) in [
103 (0, "0 Bytes"),
104 (KIBIBYTE - 1, "1023 Bytes"),
105 (KIBIBYTE, "1 KiB"),
106 (MEBIBYTE - 1, "1023 KiB"),
107 (MEBIBYTE, "1 MiB"),
108 (GIBIBYTE - 1, "1023 MiB"),
109 (GIBIBYTE, "1 GiB"),
110 (TEBIBYTE - 1, "1023 GiB"),
111 (TEBIBYTE, "1 TiB"),
112 (PEBIBYTE - 1, "1023 TiB"),
113 (PEBIBYTE, "1 PiB"),
114 (EXBIBYTE - 1, "1023 PiB"),
115 (EXBIBYTE, "1 EiB"),
116 ] {
117 println!("{size_in_bytes} bytes -> {human_bytes}");
118 assert_eq!(convert_to_human_bytes(size_in_bytes, false), human_bytes);
119 }
120 }
121
122 #[test]
123 fn test_convert_to_abbr_human_bytes() {
124 for (size_in_bytes, abbr_human_bytes) in [
125 (0, "0 B"),
126 (KIBIBYTE - 1, "1023 B"),
127 (KIBIBYTE, "1 K"),
128 (MEBIBYTE - 1, "1023 K"),
129 (MEBIBYTE, "1 M"),
130 (GIBIBYTE - 1, "1023 M"),
131 (GIBIBYTE, "1 G"),
132 (TEBIBYTE - 1, "1023 G"),
133 (TEBIBYTE, "1 T"),
134 (PEBIBYTE - 1, "1023 T"),
135 (PEBIBYTE, "1 P"),
136 (EXBIBYTE - 1, "1023 P"),
137 (EXBIBYTE, "1 E"),
138 ] {
139 println!("{size_in_bytes} bytes -> {abbr_human_bytes}");
140 assert_eq!(
141 convert_to_human_bytes(size_in_bytes, true),
142 abbr_human_bytes
143 );
144 }
145 }
146}