203 lines · 7.5 KB · Rust
RawDownload
//!use std::path::Path; /// The amount of retries to do during various aspects of the directory creation.#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]pub struct Retries {    /// How many times the whole directory can be created in the light of racy interference.    /// This count combats racy situations where another process is trying to remove a directory that we want to create,    /// and is deliberately higher than those who do deletion. That way, creation usually wins.    pub to_create_entire_directory: usize,    /// The amount of times we can try to create a directory because we couldn't as the parent didn't exist.    /// This amounts to the maximum subdirectory depth we allow to be created. Counts once per attempt to create the entire directory.    pub on_create_directory_failure: usize,    /// How often to retry to create a single directory if an interrupt happens, as caused by signals.    pub on_interrupt: usize,} impl Default for Retries {    fn default() -> Self {        Retries {            on_interrupt: 10,            to_create_entire_directory: 5,            on_create_directory_failure: 25,        }    }} mod error {    use std::{fmt, path::Path};     use crate::fs::create_dir::Retries;     /// The error returned by [all()][super::all()].    #[allow(missing_docs)]    #[derive(Debug)]    pub enum Error<'a> {        /// A failure we will probably recover from by trying again.        Intermediate { dir: &'a Path, kind: std::io::ErrorKind },        /// A failure that ends the operation.        Permanent {            dir: &'a Path,            err: std::io::Error,            /// The retries left after running the operation            retries_left: Retries,            /// The original amount of retries to allow determining how many were actually used            retries: Retries,        },    }     impl<'a> fmt::Display for Error<'a> {        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {            match self {                Error::Intermediate { dir, kind } => write!(                    f,                    "Intermediae failure creating {:?} with error: {:?}",                    dir.display(),                    kind                ),                Error::Permanent {                    err: _,                    dir,                    retries_left,                    retries,                } => write!(                    f,                    "Permanently failing to create directory {:?} ({:?} of {:?})",                    dir, retries_left, retries                ),            }        }    }     impl<'a> std::error::Error for Error<'a> {        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {            match self {                Error::Permanent { err, .. } => Some(err),                _ => None,            }        }    }}pub use error::Error; enum State {    CurrentlyCreatingDirectories,    SearchingUpwardsForExistingDirectory,} /// A special iterator which communicates its operation through results where…////// * `Some(Ok(created_directory))` is yielded once or more success, followed by `None`/// * `Some(Err(Error::Intermediate))` is yielded zero or more times while trying to create the directory./// * `Some(Err(Error::Permanent))` is yielded exactly once on failure.pub struct Iter<'a> {    cursors: Vec<&'a Path>,    retries: Retries,    original_retries: Retries,    state: State,} /// Constructionimpl<'a> Iter<'a> {    /// Create a new instance that creates `target` when iterated with the default amount of [`Retries`].    pub fn new(target: &'a Path) -> Self {        Self::new_with_retries(target, Default::default())    }     /// Create a new instance that creates `target` when iterated with the specified amount of `retries`.    pub fn new_with_retries(target: &'a Path, retries: Retries) -> Self {        Iter {            cursors: vec![target],            original_retries: retries,            retries,            state: State::SearchingUpwardsForExistingDirectory,        }    }} impl<'a> Iter<'a> {    fn pernanent_failure(        &mut self,        dir: &'a Path,        err: impl Into<std::io::Error>,    ) -> Option<Result<&'a Path, Error<'a>>> {        self.cursors.clear();        Some(Err(Error::Permanent {            err: err.into(),            dir,            retries_left: self.retries,            retries: self.original_retries,        }))    }     fn intermediate_failure(&self, dir: &'a Path, err: std::io::Error) -> Option<Result<&'a Path, Error<'a>>> {        Some(Err(Error::Intermediate { dir, kind: err.kind() }))    }} impl<'a> Iterator for Iter<'a> {    type Item = Result<&'a Path, Error<'a>>;     fn next(&mut self) -> Option<Self::Item> {        use std::io::ErrorKind::*;        match self.cursors.pop() {            Some(dir) => match std::fs::create_dir(dir) {                Ok(()) => {                    self.state = State::CurrentlyCreatingDirectories;                    Some(Ok(dir))                }                Err(err) => match err.kind() {                    AlreadyExists if dir.is_dir() => {                        self.state = State::CurrentlyCreatingDirectories;                        Some(Ok(dir))                    }                    AlreadyExists => self.pernanent_failure(dir, err), // is non-directory                    NotFound => {                        self.retries.on_create_directory_failure -= 1;                        if let State::CurrentlyCreatingDirectories = self.state {                            self.state = State::SearchingUpwardsForExistingDirectory;                            self.retries.to_create_entire_directory -= 1;                            if self.retries.to_create_entire_directory < 1 {                                return self.pernanent_failure(dir, NotFound);                            }                            self.retries.on_create_directory_failure =                                self.original_retries.on_create_directory_failure;                        }                        if self.retries.on_create_directory_failure < 1 {                            return self.pernanent_failure(dir, NotFound);                        };                        self.cursors.push(dir);                        self.cursors.push(match dir.parent() {                            None => return self.pernanent_failure(dir, InvalidInput),                            Some(parent) => parent,                        });                        self.intermediate_failure(dir, err)                    }                    Interrupted => {                        self.retries.on_interrupt -= 1;                        if self.retries.on_interrupt <= 1 {                            return self.pernanent_failure(dir, Interrupted);                        };                        self.cursors.push(dir);                        self.intermediate_failure(dir, err)                    }                    _unexpected_kind => self.pernanent_failure(dir, err),                },            },            None => None,        }    }} /// Create all directories leading to `dir` including `dir` itself with the specified amount of `retries`./// Returns the input `dir` on success that make it useful in expressions.pub fn all(dir: &Path, retries: Retries) -> std::io::Result<&Path> {    for res in Iter::new_with_retries(dir, retries) {        match res {            Err(Error::Permanent { err, .. }) => return Err(err),            Err(Error::Intermediate { .. }) | Ok(_) => continue,        }    }    Ok(dir)}