Skip to main content

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    /// Length of a created directory chain up to [`Output::dir`].
13    pub len_created_dir_chain: usize,
14}
15
16impl Output {
17    /// Creates non-exists directories in the directory chain up to [`Output::dir`],
18    /// storing the count of created directories to [`Output::len_created_dir_chain`].
19    pub fn create_dirs(&mut self) -> Result<()> {
20        let mut dirs: Vec<&Path> = Vec::new();
21        let mut dir = &*self.dir;
22
23        while !dir.exists() {
24            dirs.push(dir);
25            match dir.parent() {
26                Some(parent) => dir = parent,
27                None => break,
28            }
29        }
30
31        for dir in dirs.iter().rev() {
32            if let Err(err) = fs::create_dir(dir) {
33                if !dir.exists() {
34                    remove_created_dirs(&self.dir, dirs.len());
35                    return Err(err.into());
36                }
37            }
38        }
39
40        self.len_created_dir_chain = dirs.len();
41        Ok(())
42    }
43
44    /// Removes all created empty directories.
45    pub fn remove_created_dirs(&self) {
46        remove_created_dirs(&self.dir, self.len_created_dir_chain);
47    }
48
49    pub fn new(path: impl AsRef<Path>) -> Result<Output> {
50        let dir = new_dir(path.as_ref())?;
51        Ok(Output {
52            dir,
53            len_created_dir_chain: 0,
54        })
55    }
56
57    pub(crate) fn try_from_input(input: &Input) -> Result<Output> {
58        let new = |base: &Path| Output::new(base.join("cleaned"));
59        match input {
60            Input::Dir(d) => new(d),
61            Input::Files(xs) => new(xs
62                .get(0)
63                .and_then(|f| f.path.parent())
64                .unwrap_or(Path::new("."))),
65        }
66    }
67}
68
69impl Default for Output {
70    fn default() -> Output {
71        let dir = Path::new(".").join("cleaned");
72        Output {
73            dir: new_dir(&dir).unwrap_or(dir.into()),
74            len_created_dir_chain: 0,
75        }
76    }
77}
78
79fn remove_created_dirs(mut dir: &Path, len_created_dir_chain: usize) {
80    for _ in 0..len_created_dir_chain {
81        let _ = fs::remove_dir(dir);
82        match dir.parent() {
83            Some(parent) => dir = parent,
84            None => break,
85        }
86    }
87}
88
89fn new_dir(path: &Path) -> Result<Box<Path>> {
90    if path.is_file() {
91        Err("Is not a directory".into())
92    } else {
93        let dir: PathBuf = try_absolutize(path.into())?.components().collect();
94        Ok(ensure_long_path_prefix(dir).into())
95    }
96}
97
98fn try_absolutize(path: PathBuf) -> Result<PathBuf> {
99    #[cfg(unix)]
100    {
101        if path.starts_with("~") {
102            return Ok(path);
103        }
104    }
105
106    if path.is_absolute() {
107        Ok(path)
108    } else {
109        let mut new = env::current_dir()?;
110        new.push(path);
111        Ok(new)
112    }
113}
114
115fn ensure_long_path_prefix(path: impl Into<PathBuf>) -> PathBuf {
116    #[cfg(unix)]
117    {
118        path.into()
119    }
120
121    #[cfg(windows)]
122    {
123        let path = path.into();
124
125        if path.as_os_str().as_encoded_bytes().starts_with(b"\\\\?\\") {
126            return path;
127        }
128
129        let mut prf_path = std::ffi::OsString::from("\\\\?\\");
130        prf_path.push(path.as_os_str());
131        prf_path.into()
132    }
133}