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 dir = dir.display(),
68 ),
69 }
70 }
71 }
72
73 impl std::error::Error for Error<'_> {
74 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
75 match self {
76 Error::Permanent { err, .. } => Some(err),
77 _ => None,
78 }
79 }
80 }
81}
82pub use error::Error;
83
84enum State {
85 CurrentlyCreatingDirectories,
86 SearchingUpwardsForExistingDirectory,
87}
88
89pub struct Iter<'a> {
95 cursors: Vec<&'a Path>,
96 retries: Retries,
97 original_retries: Retries,
98 state: State,
99}
100
101impl<'a> Iter<'a> {
103 pub fn new(target: &'a Path) -> Self {
105 Self::new_with_retries(target, Default::default())
106 }
107
108 pub fn new_with_retries(target: &'a Path, retries: Retries) -> Self {
110 Iter {
111 cursors: vec![target],
112 original_retries: retries,
113 retries,
114 state: State::SearchingUpwardsForExistingDirectory,
115 }
116 }
117}
118
119impl<'a> Iter<'a> {
120 fn permanent_failure(
121 &mut self,
122 dir: &'a Path,
123 err: impl Into<std::io::Error>,
124 ) -> Option<Result<&'a Path, Error<'a>>> {
125 self.cursors.clear();
126 Some(Err(Error::Permanent {
127 err: err.into(),
128 dir,
129 retries_left: self.retries,
130 retries: self.original_retries,
131 }))
132 }
133
134 fn intermediate_failure(&self, dir: &'a Path, err: std::io::Error) -> Option<Result<&'a Path, Error<'a>>> {
135 Some(Err(Error::Intermediate { dir, kind: err.kind() }))
136 }
137}
138
139impl<'a> Iterator for Iter<'a> {
140 type Item = Result<&'a Path, Error<'a>>;
141
142 fn next(&mut self) -> Option<Self::Item> {
143 use std::io::ErrorKind::*;
144 match self.cursors.pop() {
145 Some(dir) => match std::fs::create_dir(dir) {
146 Ok(()) => {
147 self.state = State::CurrentlyCreatingDirectories;
148 Some(Ok(dir))
149 }
150 Err(err) => match err.kind() {
151 AlreadyExists if dir.is_dir() => {
152 self.state = State::CurrentlyCreatingDirectories;
153 Some(Ok(dir))
154 }
155 AlreadyExists => self.permanent_failure(dir, err), NotFound => {
157 self.retries.on_create_directory_failure -= 1;
158 if let State::CurrentlyCreatingDirectories = self.state {
159 self.state = State::SearchingUpwardsForExistingDirectory;
160 self.retries.to_create_entire_directory -= 1;
161 if self.retries.to_create_entire_directory < 1 {
162 return self.permanent_failure(dir, NotFound);
163 }
164 self.retries.on_create_directory_failure =
165 self.original_retries.on_create_directory_failure;
166 }
167 if self.retries.on_create_directory_failure < 1 {
168 return self.permanent_failure(dir, NotFound);
169 }
170 self.cursors.push(dir);
171 self.cursors.push(match dir.parent() {
172 None => return self.permanent_failure(dir, InvalidInput),
173 Some(parent) => parent,
174 });
175 self.intermediate_failure(dir, err)
176 }
177 Interrupted => {
178 self.retries.on_interrupt -= 1;
179 if self.retries.on_interrupt <= 1 {
180 return self.permanent_failure(dir, Interrupted);
181 }
182 self.cursors.push(dir);
183 self.intermediate_failure(dir, err)
184 }
185 _unexpected_kind => self.permanent_failure(dir, err),
186 },
187 },
188 None => None,
189 }
190 }
191}
192
193pub fn all(dir: &Path, retries: Retries) -> std::io::Result<&Path> {
196 for res in Iter::new_with_retries(dir, retries) {
197 match res {
198 Err(Error::Permanent { err, .. }) => return Err(err),
199 Err(Error::Intermediate { .. }) | Ok(_) => continue,
200 }
201 }
202 Ok(dir)
203}