1use jwalk::DirEntry;
28use rayon::iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator};
29use rusty_pool::ThreadPool;
30use std::{
31 collections::BTreeMap,
32 path::{Path, PathBuf},
33};
34use thiserror::Error;
35
36#[derive(Error, Debug)]
37pub enum RdError {
38 #[error("Failed to read metadata for {0}: {1}")]
39 MetadataError(PathBuf, std::io::Error),
40
41 #[error("Failed to set permissions for: {0}: {1}")]
42 PermissionError(PathBuf, std::io::Error),
43
44 #[error("Failed to remove item {0}: {1}")]
45 RemoveError(PathBuf, std::io::Error),
46
47 #[error("Failed to walk directory: {0}: {1}")]
48 WalkdirError(PathBuf, String),
49
50 #[error("IO error: {0}")]
51 Io(#[from] std::io::Error),
52}
53
54fn set_writable(path: &Path) -> Result<(), RdError> {
66 let mut perms = std::fs::metadata(path)
67 .map_err(|err| RdError::MetadataError(path.to_path_buf(), err))?
68 .permissions();
69
70 perms.set_readonly(false);
71 std::fs::set_permissions(path, perms)
72 .map_err(|err| RdError::PermissionError(path.to_path_buf(), err))?;
73
74 Ok(())
75}
76
77fn set_folder_writable(path: &Path) -> Result<(), RdError> {
92 let entries: Vec<DirEntry<((), ())>> = jwalk::WalkDir::new(&path)
93 .skip_hidden(false)
94 .into_iter()
95 .filter_map(|i| match i {
96 Ok(entry) if entry.file_type().is_file() => Some(Ok(entry)),
97 Ok(_) => None,
98 Err(e) => Some(Err(e)),
99 })
100 .collect::<Result<Vec<_>, _>>()
101 .map_err(|err| RdError::WalkdirError(path.to_path_buf(), err.to_string()))?;
102
103 let errors: Vec<_> = entries
104 .par_iter()
105 .filter_map(|entry| set_writable(&entry.path()).err())
106 .collect();
107
108 if let Some(err) = errors.into_iter().next() {
109 return Err(err);
110 }
111
112 Ok(())
113}
114
115pub fn delete_folder(dpath: &Path) -> Result<(), RdError> {
146 let mut tree: BTreeMap<u64, Vec<PathBuf>> = BTreeMap::new();
147
148 let entries: Vec<DirEntry<((), ())>> = jwalk::WalkDir::new(&dpath)
149 .skip_hidden(false)
150 .into_iter()
151 .par_bridge()
152 .filter_map(|i| match i {
153 Ok(entry) if entry.path().is_dir() => Some(Ok(entry)),
154 Ok(_) => None,
155 Err(e) => Some(Err(e)),
156 })
157 .collect::<Result<Vec<_>, _>>()
158 .map_err(|err| RdError::WalkdirError(dpath.to_path_buf(), err.to_string()))?;
159
160 for entry in entries {
161 tree.entry(entry.depth as u64)
162 .or_insert_with(Vec::new)
163 .push(entry.path());
164 }
165
166 let pool = ThreadPool::default();
167
168 let mut handles = vec![];
169
170 for (_, entries) in tree.into_iter().rev() {
171 handles.push(pool.evaluate(move || {
172 entries.par_iter().for_each(|entry| {
173 let _ = std::fs::remove_dir_all(entry);
174 });
175 }));
176 }
177
178 for handle in handles {
179 handle.await_complete();
180 }
181
182 if dpath.exists() {
183 set_folder_writable(&dpath)?;
185
186 std::fs::remove_dir_all(dpath)
187 .map_err(|err| RdError::RemoveError(dpath.to_path_buf(), err))?;
188 }
189
190 Ok(())
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use std::fs;
197 use tempfile::TempDir;
198
199 fn create_test_structure(base: &Path) -> std::io::Result<()> {
201 fs::create_dir_all(base.join("dir1/subdir1"))?;
203 fs::create_dir_all(base.join("dir1/subdir2"))?;
204 fs::create_dir_all(base.join("dir2"))?;
205
206 fs::write(base.join("file1.txt"), "content1")?;
208 fs::write(base.join("dir1/file2.txt"), "content2")?;
209 fs::write(base.join("dir1/subdir1/file3.txt"), "content3")?;
210 fs::write(base.join("dir2/file4.txt"), "content4")?;
211
212 Ok(())
213 }
214
215 fn make_readonly(path: &Path) -> std::io::Result<()> {
217 let mut perms = fs::metadata(path)?.permissions();
218 perms.set_readonly(true);
219 fs::set_permissions(path, perms)?;
220 Ok(())
221 }
222
223 #[test]
224 fn test_delete_empty_directory() {
225 let temp_dir = TempDir::new().unwrap();
226 let test_path = temp_dir.path().join("empty_dir");
227 fs::create_dir(&test_path).unwrap();
228
229 assert!(test_path.exists());
230 delete_folder(&test_path).unwrap();
231 assert!(!test_path.exists());
232 }
233
234 #[test]
235 fn test_delete_directory_with_files() {
236 let temp_dir = TempDir::new().unwrap();
237 let test_path = temp_dir.path().join("dir_with_files");
238 fs::create_dir(&test_path).unwrap();
239 fs::write(test_path.join("file1.txt"), "content").unwrap();
240 fs::write(test_path.join("file2.txt"), "content").unwrap();
241
242 assert!(test_path.exists());
243 delete_folder(&test_path).unwrap();
244 assert!(!test_path.exists());
245 }
246
247 #[test]
248 fn test_delete_nested_directory_structure() {
249 let temp_dir = TempDir::new().unwrap();
250 let test_path = temp_dir.path().join("nested");
251 create_test_structure(&test_path).unwrap();
252
253 assert!(test_path.exists());
254 assert!(test_path.join("dir1/subdir1/file3.txt").exists());
255
256 delete_folder(&test_path).unwrap();
257
258 assert!(!test_path.exists());
259 assert!(!test_path.join("dir1").exists());
260 }
261
262 #[test]
263 fn test_delete_directory_with_readonly_files() {
264 let temp_dir = TempDir::new().unwrap();
265 let test_path = temp_dir.path().join("readonly_test");
266 create_test_structure(&test_path).unwrap();
267
268 make_readonly(&test_path.join("file1.txt")).unwrap();
270 make_readonly(&test_path.join("dir1/file2.txt")).unwrap();
271
272 assert!(test_path.exists());
273 delete_folder(&test_path).unwrap();
274 assert!(!test_path.exists());
275 }
276
277 #[test]
278 fn test_delete_nonexistent_directory() {
279 let temp_dir = TempDir::new().unwrap();
280 let test_path = temp_dir.path().join("does_not_exist");
281
282 let result = delete_folder(&test_path);
284
285 match result {
288 Ok(_) => assert!(!test_path.exists()),
289 Err(RdError::WalkdirError(_, _)) => {}
290 Err(e) => panic!("Unexpected error: {:?}", e),
291 }
292 }
293
294 #[test]
295 fn test_delete_directory_with_symlinks() {
296 let temp_dir = TempDir::new().unwrap();
297
298 let external_dir = temp_dir.path().join("external");
300 fs::create_dir(&external_dir).unwrap();
301 fs::write(external_dir.join("important.txt"), "don't delete me").unwrap();
302
303 let test_path = temp_dir.path().join("with_symlink");
305 fs::create_dir(&test_path).unwrap();
306
307 #[cfg(unix)]
308 {
309 use std::os::unix::fs::symlink;
310 symlink(&external_dir, test_path.join("link_to_external")).unwrap();
311 }
312
313 #[cfg(windows)]
314 {
315 use std::os::windows::fs::symlink_dir;
316 symlink_dir(&external_dir, test_path.join("link_to_external")).unwrap();
317 }
318
319 delete_folder(&test_path).unwrap();
321
322 assert!(!test_path.exists());
324
325 assert!(external_dir.exists());
327 assert!(external_dir.join("important.txt").exists());
328 }
329
330 #[test]
331 fn test_set_writable_on_readonly_file() {
332 let temp_dir = TempDir::new().unwrap();
333 let file_path = temp_dir.path().join("readonly_file.txt");
334 fs::write(&file_path, "content").unwrap();
335
336 make_readonly(&file_path).unwrap();
338 let perms = fs::metadata(&file_path).unwrap().permissions();
339 assert!(perms.readonly());
340
341 set_writable(&file_path).unwrap();
343
344 let perms = fs::metadata(&file_path).unwrap().permissions();
345 assert!(!perms.readonly());
346 }
347
348 #[test]
349 fn test_delete_large_directory_structure() {
350 let temp_dir = TempDir::new().unwrap();
351 let test_path = temp_dir.path().join("large_structure");
352 fs::create_dir(&test_path).unwrap();
353
354 for i in 0..10 {
356 let dir = test_path.join(format!("dir_{}", i));
357 fs::create_dir(&dir).unwrap();
358
359 for j in 0..5 {
360 fs::write(dir.join(format!("file_{}.txt", j)), "content").unwrap();
361 }
362
363 let subdir = dir.join("subdir");
365 fs::create_dir(&subdir).unwrap();
366 for k in 0..3 {
367 fs::write(subdir.join(format!("subfile_{}.txt", k)), "content").unwrap();
368 }
369 }
370
371 assert!(test_path.exists());
372 delete_folder(&test_path).unwrap();
373 assert!(!test_path.exists());
374 }
375
376 #[test]
377 fn test_error_on_invalid_path() {
378 let temp_dir = TempDir::new().unwrap();
380 let file_path = temp_dir.path().join("not_a_dir.txt");
381 fs::write(&file_path, "content").unwrap();
382
383 let result = delete_folder(&file_path);
384
385 match result {
388 Ok(_) => assert!(!file_path.exists()),
389 Err(_) => {} }
391 }
392}