1use std::path::{Path, PathBuf};
3
4pub struct Iter<'a> {
9 cursor: Option<&'a Path>,
10 boundary: &'a Path,
11}
12
13impl<'a> Iter<'a> {
15 pub fn new(target: &'a Path, boundary: &'a Path) -> std::io::Result<Self> {
20 if !target.starts_with(boundary) {
21 return Err(std::io::Error::new(
22 std::io::ErrorKind::InvalidInput,
23 format!("Removal target {target:?} must be contained in boundary {boundary:?}"),
24 ));
25 }
26 let cursor = if target == boundary {
27 None
28 } else if target.exists() {
29 Some(target)
30 } else {
31 None
32 };
33 Ok(Iter { cursor, boundary })
34 }
35}
36
37impl<'a> Iterator for Iter<'a> {
38 type Item = std::io::Result<&'a Path>;
39
40 fn next(&mut self) -> Option<Self::Item> {
41 match self.cursor.take() {
42 Some(dir) => {
43 let next = match std::fs::remove_dir(dir) {
44 Ok(()) => Some(Ok(dir)),
45 Err(err) => match err.kind() {
46 std::io::ErrorKind::NotFound => Some(Ok(dir)),
47 _other_error_kind => return Some(Err(err)),
48 },
49 };
50 self.cursor = match dir.parent() {
51 Some(parent) => (parent != self.boundary).then_some(parent),
52 None => {
53 unreachable!("directory {:?} ran out of parents, this really shouldn't happen before hitting the boundary {:?}", dir, self.boundary)
54 }
55 };
56 next
57 }
58 None => None,
59 }
60 }
61}
62
63pub fn empty_upward_until_boundary<'a>(delete_dir: &'a Path, boundary_dir: &'a Path) -> std::io::Result<&'a Path> {
67 for item in Iter::new(delete_dir, boundary_dir)? {
68 match item {
69 Ok(_dir) => continue,
70 Err(err) => return Err(err),
71 }
72 }
73 Ok(delete_dir)
74}
75
76pub fn empty_depth_first(delete_dir: PathBuf) -> std::io::Result<()> {
82 if let Ok(()) = std::fs::remove_dir(&delete_dir) {
83 return Ok(());
84 }
85
86 let mut stack = vec![delete_dir];
87 let mut next_to_push = Vec::new();
88 while let Some(dir_to_delete) = stack.pop() {
89 let mut num_entries = 0;
90 for entry in std::fs::read_dir(&dir_to_delete)? {
91 num_entries += 1;
92 let entry = entry?;
93 if entry.file_type()?.is_dir() {
94 next_to_push.push(entry.path());
95 } else {
96 return Err(std::io::Error::new(std::io::ErrorKind::Other, "Directory not empty"));
97 }
98 }
99 if num_entries == 0 {
100 std::fs::remove_dir(&dir_to_delete)?;
101 } else {
102 stack.push(dir_to_delete);
103 stack.append(&mut next_to_push);
104 }
105 }
106 Ok(())
107}