acme_disk_use/
disk_use.rs1use std::{io, path::Path};
4
5use crate::cache::CacheManager;
6use crate::scanner::{self, DirStat};
7
8pub struct DiskUse {
10 cache_manager: CacheManager,
11}
12
13impl DiskUse {
14 pub fn new(cache_path: impl AsRef<Path>) -> Self {
16 Self {
17 cache_manager: CacheManager::new(cache_path),
18 }
19 }
20
21 pub fn new_with_default_cache() -> Self {
23 Self::new(crate::get_default_cache_path())
24 }
25
26 pub fn scan(&mut self, path: impl AsRef<Path>) -> io::Result<u64> {
33 self.scan_with_options(path, false)
34 }
35
36 pub fn scan_with_options(
42 &mut self,
43 path: impl AsRef<Path>,
44 ignore_cache: bool,
45 ) -> io::Result<u64> {
46 let path = path.as_ref();
47
48 let path_buf = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
50
51 let old_entry = if ignore_cache {
53 None
54 } else {
55 self.cache_manager.get(&path_buf)
56 };
57
58 let new_entry = scanner::scan_directory(&path_buf, old_entry)?;
60
61 let total_size = new_entry.total_size();
63
64 if !ignore_cache {
66 self.cache_manager.update(&path_buf, new_entry);
67 }
69
70 Ok(total_size)
71 }
72
73 pub fn get_stats(&self, path: impl AsRef<Path>) -> Option<&DirStat> {
75 self.cache_manager.get(path.as_ref())
76 }
77
78 pub fn get_file_count(&self, path: impl AsRef<Path>, ignore_cache: bool) -> io::Result<u64> {
84 if ignore_cache {
85 scanner::count_files(path.as_ref())
86 } else {
87 Ok(self
88 .get_stats(path)
89 .map(|stats| stats.file_count())
90 .unwrap_or(0))
91 }
92 }
93
94 pub fn save_cache(&mut self) -> io::Result<()> {
96 self.cache_manager.save()
97 }
98
99 pub fn clear_cache(&mut self) -> io::Result<()> {
101 self.cache_manager.clear()
102 }
103
104 pub fn delete_cache(&self) -> io::Result<()> {
106 self.cache_manager.delete()
107 }
108
109 pub fn cache_path(&self) -> &Path {
111 self.cache_manager.path()
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use std::fs;
119 use tempfile::TempDir;
120
121 fn create_test_directory_structure(base: &Path) -> io::Result<()> {
122 fs::create_dir_all(base.join("subdir1"))?;
123 fs::create_dir_all(base.join("subdir2/nested"))?;
124
125 fs::write(base.join("file1.txt"), "Hello World")?;
126 fs::write(base.join("file2.txt"), "Test content")?;
127 fs::write(base.join("subdir1/nested_file.txt"), "Nested content here")?;
128 fs::write(base.join("subdir2/another.txt"), "More content")?;
129 fs::write(base.join("subdir2/nested/deep.txt"), "Deep file content")?;
130
131 Ok(())
132 }
133
134 #[test]
135 fn test_disk_use_with_cache() -> io::Result<()> {
136 let temp_dir = TempDir::new()?;
142 let test_dir = temp_dir.path().join("test");
143 let cache_file = temp_dir.path().join("cache.bin");
144
145 fs::create_dir(&test_dir)?;
146 create_test_directory_structure(&test_dir)?;
147
148 let canonical_test_dir = test_dir.canonicalize()?;
149
150 {
151 let mut disk_use = DiskUse::new(&cache_file);
152 let size1 = disk_use.scan(&canonical_test_dir)?;
153 assert!(size1 >= 71);
154
155 disk_use.save_cache()?;
157 } assert!(cache_file.exists());
160
161 {
162 let mut disk_use = DiskUse::new(&cache_file);
163 let _size2 = disk_use.scan(&canonical_test_dir)?;
164 assert!(_size2 >= 71);
165
166 let file_count = disk_use.get_file_count(&canonical_test_dir, false)?;
167 assert_eq!(file_count, 5);
168 }
169
170 Ok(())
171 }
172
173 #[test]
174 fn test_disk_use_ignore_cache() -> io::Result<()> {
175 let temp_dir = TempDir::new()?;
181 let test_dir = temp_dir.path().join("test");
182 let cache_file = temp_dir.path().join("cache.json");
183
184 fs::create_dir(&test_dir)?;
185 create_test_directory_structure(&test_dir)?;
186
187 let mut disk_use = DiskUse::new(&cache_file);
188
189 let size1 = disk_use.scan(&test_dir)?;
190 assert!(size1 >= 71);
191
192 fs::write(test_dir.join("new_file.txt"), "New content")?;
193
194 let _size2 = disk_use.scan(&test_dir)?;
195
196 let size3 = disk_use.scan_with_options(&test_dir, true)?;
197 assert!(size3 >= 82);
198
199 Ok(())
200 }
201
202 #[test]
203 fn test_cache_management() -> io::Result<()> {
204 let temp_dir = TempDir::new()?;
209 let test_dir = temp_dir.path().join("test");
210 let cache_file = temp_dir.path().join("cache.bin");
211
212 fs::create_dir(&test_dir)?;
213 create_test_directory_structure(&test_dir)?;
214
215 {
216 let mut disk_use = DiskUse::new(&cache_file);
217
218 disk_use.scan(&test_dir)?;
219 disk_use.save_cache()?; } assert!(cache_file.exists());
223
224 {
225 let mut disk_use = DiskUse::new(&cache_file);
226 disk_use.clear_cache()?;
227
228 disk_use.delete_cache()?;
229 }
230
231 assert!(!cache_file.exists());
232
233 Ok(())
234 }
235
236 #[test]
237 fn test_get_file_count_subdirectory() -> io::Result<()> {
238 let temp_dir = TempDir::new()?;
242 let test_dir = temp_dir.path().join("test");
243 let cache_file = temp_dir.path().join("cache.bin");
244
245 fs::create_dir(&test_dir)?;
246 fs::create_dir(test_dir.join("sub"))?;
247 fs::write(test_dir.join("sub/file.txt"), "content")?;
248
249 let mut disk_use = DiskUse::new(&cache_file);
250 disk_use.scan(&test_dir)?; let count = disk_use.get_file_count(test_dir.join("sub"), false)?;
254 assert_eq!(count, 1);
255
256 Ok(())
257 }
258
259 #[test]
260 #[cfg(unix)]
261 fn test_compare_with_du() -> io::Result<()> {
262 use std::process::Command;
265
266 let temp_dir = TempDir::new()?;
267 let test_dir = temp_dir.path().join("test_du");
268 fs::create_dir(&test_dir)?;
269
270 fs::write(test_dir.join("file1.txt"), "Hello World")?; let large_content = vec![0u8; 8192]; fs::write(test_dir.join("file2.bin"), &large_content)?;
275
276 let mut disk_use = DiskUse::new_with_default_cache();
277 let lib_size = disk_use.scan_with_options(&test_dir, true)?;
278
279 let output = Command::new("du")
291 .arg("-s")
292 .arg("-k") .arg(&test_dir)
294 .output()?;
295
296 if !output.status.success() {
297 return Ok(());
299 }
300
301 let stdout = String::from_utf8_lossy(&output.stdout);
302 let du_kblocks: u64 = stdout.split_whitespace().next().unwrap().parse().unwrap();
303
304 let du_bytes = du_kblocks * 1024;
305
306 println!("Library size: {}, du size: {}", lib_size, du_bytes);
313
314 let diff = lib_size.abs_diff(du_bytes);
315
316 assert!(
317 diff <= 4096,
318 "Library size {} differs significantly from du size {}",
319 lib_size,
320 du_bytes
321 );
322
323 Ok(())
324 }
325}