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::File(f) => new(f.path.parent().unwrap_or(Path::new("."))),
62        }
63    }
64}
65
66impl Default for Output {
67    fn default() -> Output {
68        let dir = Path::new(".").join("cleaned");
69        Output {
70            dir: new_dir(&dir).unwrap_or(dir.into()),
71            len_created_dir_chain: 0,
72        }
73    }
74}
75
76fn remove_created_dirs(mut dir: &Path, len_created_dir_chain: usize) {
77    for _ in 0..len_created_dir_chain {
78        let _ = fs::remove_dir(dir);
79        match dir.parent() {
80            Some(parent) => dir = parent,
81            None => break,
82        }
83    }
84}
85
86fn new_dir(path: &Path) -> Result<Box<Path>> {
87    if path.is_file() {
88        Err("Is not a directory".into())
89    } else {
90        let dir: PathBuf = try_absolutize(path.into())?.components().collect();
91        Ok(ensure_long_path_prefix(dir).into())
92    }
93}
94
95fn try_absolutize(path: PathBuf) -> Result<PathBuf> {
96    #[cfg(unix)]
97    {
98        if path.starts_with("~") {
99            return Ok(path);
100        }
101    }
102
103    if path.is_absolute() {
104        Ok(path)
105    } else {
106        let mut new = env::current_dir()?;
107        new.push(path);
108        Ok(new)
109    }
110}
111
112fn ensure_long_path_prefix(path: impl Into<PathBuf>) -> PathBuf {
113    #[cfg(unix)]
114    {
115        path.into()
116    }
117
118    #[cfg(windows)]
119    {
120        let path = path.into();
121
122        if path.as_os_str().as_encoded_bytes().starts_with(b"\\\\?\\") {
123            return path;
124        }
125
126        let mut prf_path = std::ffi::OsString::from("\\\\?\\");
127        prf_path.push(path.as_os_str());
128        prf_path.into()
129    }
130}