1use std::path::Path;
3
4#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
6pub struct Retries {
7 pub to_create_entire_directory: usize,
11 pub on_create_directory_failure: usize,
14 pub on_interrupt: usize,
16}
17
18impl Default for Retries {
19 fn default() -> Self {
20 Retries {
21 on_interrupt: 10,
22 to_create_entire_directory: 5,
23 on_create_directory_failure: 25,
24 }
25 }
26}
27
28mod error {
29 use std::{fmt, path::Path};
30
31 use crate::dir::create::Retries;
32
33 #[allow(missing_docs)]
35 #[derive(Debug)]
36 pub enum Error<'a> {
37 Intermediate { dir: &'a Path, kind: std::io::ErrorKind },
39 Permanent {
41 dir: &'a Path,
42 err: std::io::Error,
43 retries_left: Retries,
45 retries: Retries,
47 },
48 }
49
50 impl fmt::Display for Error<'_> {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 Error::Intermediate { dir, kind } => write!(
54 f,
55 "Intermediae failure creating {:?} with error: {:?}",
56 dir.display(),
57 kind
58 ),
59 Error::Permanent {
60 err: _,
61 dir,
62 retries_left,
63 retries,
64 } => write!(
65 f,
66 "Permanently failing to create directory {dir:?} ({retries_left:?} of {retries:?})",
67 ),
68 }
69 }
70 }
71
72 impl std::error::Error for Error<'_> {
73 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
74 match self {
75 Error::Permanent { err, .. } => Some(err),
76 _ => None,
77 }
78 }
79 }
80}
81pub use error::Error;
82
83enum State {
84 CurrentlyCreatingDirectories,
85 SearchingUpwardsForExistingDirectory,
86}
87
88pub struct Iter<'a> {
94 cursors: Vec<&'a Path>,
95 retries: Retries,
96 original_retries: Retries,
97 state: State,
98}
99
100impl<'a> Iter<'a> {
102 pub fn new(target: &'a Path) -> Self {
104 Self::new_with_retries(target, Default::default())
105 }
106
107 pub fn new_with_retries(target: &'a Path, retries: Retries) -> Self {
109 Iter {
110 cursors: vec![target],
111 original_retries: retries,
112 retries,
113 state: State::SearchingUpwardsForExistingDirectory,
114 }
115 }
116}
117
118impl<'a> Iter<'a> {
119 fn permanent_failure(
120 &mut self,
121 dir: &'a Path,
122 err: impl Into<std::io::Error>,
123 ) -> Option<Result<&'a Path, Error<'a>>> {
124 self.cursors.clear();
125 Some(Err(Error::Permanent {
126 err: err.into(),
127 dir,
128 retries_left: self.retries,
129 retries: self.original_retries,
130 }))
131 }
132
133 fn intermediate_failure(&self, dir: &'a Path, err: std::io::Error) -> Option<Result<&'a Path, Error<'a>>> {
134 Some(Err(Error::Intermediate { dir, kind: err.kind() }))
135 }
136}
137
138impl<'a> Iterator for Iter<'a> {
139 type Item = Result<&'a Path, Error<'a>>;
140
141 fn next(&mut self) -> Option<Self::Item> {
142 use std::io::ErrorKind::*;
143 match self.cursors.pop() {
144 Some(dir) => match std::fs::create_dir(dir) {
145 Ok(()) => {
146 self.state = State::CurrentlyCreatingDirectories;
147 Some(Ok(dir))
148 }
149 Err(err) => match err.kind() {
150 AlreadyExists if dir.is_dir() => {
151 self.state = State::CurrentlyCreatingDirectories;
152 Some(Ok(dir))
153 }
154 AlreadyExists => self.permanent_failure(dir, err), NotFound => {
156 self.retries.on_create_directory_failure -= 1;
157 if let State::CurrentlyCreatingDirectories = self.state {
158 self.state = State::SearchingUpwardsForExistingDirectory;
159 self.retries.to_create_entire_directory -= 1;
160 if self.retries.to_create_entire_directory < 1 {
161 return self.permanent_failure(dir, NotFound);
162 }
163 self.retries.on_create_directory_failure =
164 self.original_retries.on_create_directory_failure;
165 }
166 if self.retries.on_create_directory_failure < 1 {
167 return self.permanent_failure(dir, NotFound);
168 };
169 self.cursors.push(dir);
170 self.cursors.push(match dir.parent() {
171 None => return self.permanent_failure(dir, InvalidInput),
172 Some(parent) => parent,
173 });
174 self.intermediate_failure(dir, err)
175 }
176 Interrupted => {
177 self.retries.on_interrupt -= 1;
178 if self.retries.on_interrupt <= 1 {
179 return self.permanent_failure(dir, Interrupted);
180 };
181 self.cursors.push(dir);
182 self.intermediate_failure(dir, err)
183 }
184 _unexpected_kind => self.permanent_failure(dir, err),
185 },
186 },
187 None => None,
188 }
189 }
190}
191
192pub fn all(dir: &Path, retries: Retries) -> std::io::Result<&Path> {
195 for res in Iter::new_with_retries(dir, retries) {
196 match res {
197 Err(Error::Permanent { err, .. }) => return Err(err),
198 Err(Error::Intermediate { .. }) | Ok(_) => continue,
199 }
200 }
201 Ok(dir)
202}