fb2_clean/config/
output.rs

1use crate::{Input, Result};
2use std::{
3    env, fs,
4    path::{Path, PathBuf},
5};
6
7/// Output configuration.
8#[derive(Clone, Debug, PartialEq)]
9pub struct Output {
10    /// Output directory.
11    pub dir: Box<Path>,
12    /// The descending directory chain created in real time to the [`Output::dir`].
13    pub created_dirs: Vec<Box<Path>>,
14}
15
16impl Output {
17    /// Creates non-exists directories in the directory chain up to [`Output::dir`],
18    /// storing the created directories in [`Output::created_dirs`].
19    pub fn create_dirs(&mut self) -> Result<()> {
20        let mut dirs = Vec::<Box<Path>>::new();
21        let mut dir = &*self.dir;
22
23        while !dir.exists() {
24            dirs.push(dir.into());
25
26            if let Some(parent) = dir.parent() {
27                dir = parent;
28            } else {
29                break;
30            }
31        }
32
33        for dir in dirs.iter().rev() {
34            if let Err(err) = fs::create_dir(dir) {
35                if !dir.exists() {
36                    remove_dir_chain(&dirs);
37                    return Err(err.into());
38                }
39            }
40        }
41
42        self.created_dirs = dirs;
43        Ok(())
44    }
45
46    /// Removes all created empty directories.
47    pub fn remove_created_dirs(&self) {
48        remove_dir_chain(&self.created_dirs)
49    }
50
51    pub(crate) fn new(path: impl AsRef<Path>) -> Result<Output> {
52        let dir = new_dir(path.as_ref())?;
53        Ok(Output {
54            dir,
55            created_dirs: Vec::new(),
56        })
57    }
58
59    pub(crate) fn try_from_input(input: &Input) -> Result<Output> {
60        let new = |base: &Path| Output::new(base.join("cleaned"));
61        match input {
62            Input::Dir(d) => new(d),
63            Input::File(f) => new(f.path.parent().unwrap_or(Path::new("."))),
64        }
65    }
66}
67
68fn remove_dir_chain(dirs: &Vec<Box<Path>>) {
69    for dir in dirs {
70        let _ = fs::remove_dir(dir);
71    }
72}
73
74fn new_dir(path: &Path) -> Result<Box<Path>> {
75    if path.is_file() {
76        Err("Is not a directory".into())
77    } else {
78        let dir: PathBuf = try_absolutize(path.into())?.components().collect();
79        Ok(ensure_long_path_prefix(dir).into())
80    }
81}
82
83fn try_absolutize(path: PathBuf) -> Result<PathBuf> {
84    #[cfg(unix)]
85    {
86        if path.starts_with("~") {
87            return Ok(path);
88        }
89    }
90
91    if path.is_absolute() {
92        Ok(path)
93    } else {
94        let mut new = env::current_dir()?;
95        new.push(path);
96        Ok(new)
97    }
98}
99
100fn ensure_long_path_prefix(path: impl Into<PathBuf>) -> PathBuf {
101    #[cfg(unix)]
102    {
103        path.into()
104    }
105
106    #[cfg(windows)]
107    {
108        let path = path.into();
109
110        if path.as_os_str().as_encoded_bytes().starts_with(b"\\\\?\\") {
111            return path;
112        }
113
114        let mut prf_path = std::ffi::OsString::from("\\\\?\\");
115        prf_path.push(path.as_os_str());
116        prf_path.into()
117    }
118}