folder_declutter/
process_dir.rs

1use std::fs;
2use std::path::Path;
3use std::time::SystemTime;
4
5// Recursively process the directory, delete old files and empty folders (for subdirectories)
6pub fn process_dir(dir: &Path, cutoff: SystemTime) {
7    let mut removed_files = 0;
8    if let Ok(entries) = fs::read_dir(dir) {
9        for entry in entries.flatten() {
10            let path = entry.path();
11            if path.is_dir() {
12                process_dir(&path, cutoff);
13                // After processing, try to remove empty subdirectories.
14                if let Ok(mut entries) = fs::read_dir(&path) {
15                    if entries.next().is_none() {
16                        match fs::remove_dir(&path) {
17                            Ok(_) => println!("\n🗑️ Empty folder deleted: {:?}", path),
18                            Err(e) => eprintln!("Error deleting folder {:?}: {}", path, e),
19                        }
20                    }
21                }
22            } else if let Ok(metadata) = fs::metadata(&path) {
23                if let Ok(modified) = metadata.modified() {
24                    if modified < cutoff {
25                        match fs::remove_file(&path) {
26                            Ok(_) => {
27                                removed_files += 1;
28                                println!("\n🗑️ File deleted: {:?}", path)
29                            }
30                            Err(e) => eprintln!("Error deleting {:?}: {}", path, e),
31                        }
32                    }
33                }
34            }
35        }
36    }
37    if removed_files == 0 {
38        println!("\n✅ Done! No files to delete.");
39        return;
40    }
41    println!("\n✅ Done! {} files removed.", removed_files);
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use std::fs::{self, File};
48    use std::io::Write;
49    use std::path::PathBuf;
50    use std::time::{Duration, SystemTime};
51    use tempfile::tempdir;
52
53    #[test]
54    fn test_removes_old_files() {
55        // Create a temporary directory and a file inside it.
56        let temp_dir = tempdir().unwrap();
57        let file_path: PathBuf = temp_dir.path().join("old_file.txt");
58        let mut file = File::create(&file_path).unwrap();
59        writeln!(file, "Some test content").unwrap();
60
61        // Set cutoff to a time in the future so that the file is considered old.
62        let cutoff = SystemTime::now() + Duration::from_secs(3600);
63
64        // Process the directory; expected to remove the file.
65        process_dir(temp_dir.path(), cutoff);
66
67        // Assert that the file no longer exists.
68        assert!(!file_path.exists(), "File should have been deleted");
69    }
70
71    #[test]
72    fn test_preserves_new_files() {
73        // Create a temporary directory and a file inside it.
74        let temp_dir = tempdir().unwrap();
75        let file_path: PathBuf = temp_dir.path().join("new_file.txt");
76        let mut file = File::create(&file_path).unwrap();
77        writeln!(file, "Fresh content").unwrap();
78
79        // Set cutoff to a time in the past so that the file is considered new.
80        let cutoff = SystemTime::now() - Duration::from_secs(3600);
81
82        // Process the directory; expected to keep the file.
83        process_dir(temp_dir.path(), cutoff);
84
85        // Assert that the file still exists.
86        assert!(file_path.exists(), "File should not have been deleted");
87    }
88
89    #[test]
90    fn test_recursive_processing() {
91        // Create a temporary directory with a subdirectory.
92        let temp_dir = tempdir().unwrap();
93        let sub_dir = temp_dir.path().join("subdir");
94        fs::create_dir(&sub_dir).unwrap();
95
96        // Create files inside the subdirectory.
97        let old_file: PathBuf = sub_dir.join("old_file.txt");
98        let new_file: PathBuf = sub_dir.join("new_file.txt");
99        {
100            let mut file = File::create(&old_file).unwrap();
101            writeln!(file, "Old content").unwrap();
102        }
103        {
104            let mut file = File::create(&new_file).unwrap();
105            writeln!(file, "New content").unwrap();
106        }
107
108        // For this test, use a cutoff in the future so that both files (in subdirectory) are deleted.
109        let cutoff = SystemTime::now() + Duration::from_secs(3600);
110        process_dir(temp_dir.path(), cutoff);
111
112        // Assert that files in the subdirectory have been removed.
113        assert!(!old_file.exists(), "old_file should have been deleted recursively");
114        assert!(!new_file.exists(), "new_file should have been deleted recursively");
115
116        // Now test preservation in a recursive scenario.
117        // Recreate a subdirectory with a file.
118        let sub_dir2 = temp_dir.path().join("subdir2");
119        fs::create_dir(&sub_dir2).unwrap();
120        let preserved_file: PathBuf = sub_dir2.join("preserved.txt");
121        {
122            let mut file = File::create(&preserved_file).unwrap();
123            writeln!(file, "I am still new").unwrap();
124        }
125
126        // Use a cutoff in the past to ensure the file is not deleted.
127        let cutoff_old = SystemTime::now() - Duration::from_secs(3600);
128        process_dir(temp_dir.path(), cutoff_old);
129
130        // Assert preserved_file still exists.
131        assert!(preserved_file.exists(), "File should not have been deleted recursively");
132    }
133
134    #[test]
135    fn test_empty_folder_deletion() {
136        // Create a temporary directory with an empty subdirectory.
137        let temp_dir = tempdir().unwrap();
138        let empty_subdir = temp_dir.path().join("empty_folder");
139        fs::create_dir(&empty_subdir).unwrap();
140
141        // Set a cutoff that doesn't affect folders.
142        let cutoff = SystemTime::now() + Duration::from_secs(3600);
143        process_dir(temp_dir.path(), cutoff);
144
145        // Assert that the empty subdirectory has been deleted.
146        assert!(!empty_subdir.exists(), "Empty folder should have been deleted");
147    }
148}