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