backup_deduplicator/
utils.rs

1use std::io::Write;
2use std::path::{PathBuf};
3use std::time::{SystemTime, UNIX_EPOCH};
4use anyhow::{anyhow, Result};
5
6/// Trait to convert a path to a lexical absolute path.
7/// Does not require the path to exist.
8/// 
9/// # See also
10/// * <https://internals.rust-lang.org/t/path-to-lexical-absolute/14940>
11/// * [std::fs::canonicalize]
12pub trait LexicalAbsolute {
13    /// Convert a path to a lexical absolute path.
14    /// Does not require the path to exist.
15    /// 
16    /// # Errors
17    /// Returns an error if the absolute path could not be determined.
18    fn to_lexical_absolute(&self) -> std::io::Result<PathBuf>;
19}
20
21impl LexicalAbsolute for PathBuf {
22    /// Convert a path to a lexical absolute path.
23    /// Does not require the path to exist.
24    ///
25    /// # Example
26    /// ```
27    /// use std::path::PathBuf;
28    /// use backup_deduplicator::utils::LexicalAbsolute;
29    ///
30    /// let path = PathBuf::from("/a/b/../c");
31    /// let absolute = path.to_lexical_absolute().unwrap();
32    /// assert_eq!(absolute, PathBuf::from("/a/c"));
33    /// ```
34    /// 
35    /// # Errors
36    /// Returns an error if the given path is relative and the current working directory could not be determined.
37    /// * The working directory does not exist.
38    /// * Insufficient permissions to determine the working directory.
39    fn to_lexical_absolute(&self) -> std::io::Result<PathBuf> {
40        // https://internals.rust-lang.org/t/path-to-lexical-absolute/14940
41        let mut absolute = if self.is_absolute() {
42            PathBuf::new()
43        } else {
44            std::env::current_dir()?
45        };
46        for component in self.components() {
47            match component {
48                std::path::Component::CurDir => {},
49                std::path::Component::ParentDir => { absolute.pop(); },
50                component @ _ => absolute.push(component.as_os_str()),
51            }
52        }
53        Ok(absolute)
54    }
55}
56
57/// Decode a hex string to a byte vector.
58/// 
59/// # Example
60/// ```
61/// use backup_deduplicator::utils::decode_hex;
62/// 
63/// let bytes = decode_hex("deadbeef").unwrap();
64/// assert_eq!(bytes, vec![0xde, 0xad, 0xbe, 0xef]);
65/// ```
66/// 
67/// # Errors
68/// Returns an error if the given string is not a valid hex string.
69pub fn decode_hex(s: &str) -> Result<Vec<u8>> {
70    if s.len() % 2 != 0 {
71        return Err(anyhow!("Invalid hex length"));
72    }
73    (0..s.len())
74        .step_by(2)
75        .map(|i| u8::from_str_radix(&s[i..i + 2], 16)
76            .map_err(|e| anyhow!("Failed to parse hex: {}", e)))
77        .collect()
78}
79
80/// Get the current time in seconds since the Unix epoch (in seconds).
81/// 
82/// # Returns
83/// The current time in seconds since the Unix epoch. Returns 0 if the current time is before the Unix epoch.
84pub fn get_time() -> u64 {
85    SystemTime::now()
86        .duration_since(UNIX_EPOCH)
87        .map(|d| d.as_secs()).unwrap_or(0)
88}
89
90/// A writer that discards all data.
91/// 
92/// # Example
93/// ```
94/// use std::io::Write;
95/// 
96/// let mut writer = backup_deduplicator::utils::NullWriter::new();
97/// writer.write(b"Hello, world!").unwrap();
98/// ```
99pub struct NullWriter {}
100
101impl NullWriter {
102    /// Create a new NullWriter.
103    /// 
104    /// # Returns
105    /// A new NullWriter.
106    pub fn new() -> Self {
107        NullWriter {}
108    }
109}
110
111impl Write for NullWriter {
112    /// Discard all data.
113    /// 
114    /// # Arguments
115    /// * `buf` - The data to write.
116    /// 
117    /// # Returns
118    /// The number of bytes written. Always the same as the length of `buf`.
119    /// 
120    /// # Errors
121    /// Never
122    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {Ok(buf.len())}
123
124    /// Flush the writer.
125    /// 
126    /// # Errors
127    /// Never
128    fn flush(&mut self) -> std::io::Result<()> {Ok(())}
129}
130
131/// Utility functions for the main function of `backup-deduplicator`.
132pub mod main {
133    use std::env;
134    use std::path::PathBuf;
135    use crate::utils::LexicalAbsolute;
136
137    /// Changes the working directory to the given path.
138    ///
139    /// # Arguments
140    /// * `working_directory` - The new working directory.
141    ///
142    /// # Returns
143    /// The new working directory.
144    ///
145    /// # Exit
146    /// Exits the process if the working directory could not be changed.
147    pub fn change_working_directory(working_directory: Option<PathBuf>) -> PathBuf {
148        match working_directory {
149            None => {},
150            Some(working_directory) => {
151                env::set_current_dir(&working_directory).unwrap_or_else(|_| {
152                    eprintln!("IO error, could not change working directory: {}", working_directory.display());
153                    std::process::exit(exitcode::CONFIG);
154                });
155            }
156        }
157
158        env::current_dir().unwrap_or_else(|_| {
159            eprintln!("IO error, could not resolve working directory");
160            std::process::exit(exitcode::CONFIG);
161        }).canonicalize().unwrap_or_else(|_| {
162            eprintln!("IO error, could not resolve working directory");
163            std::process::exit(exitcode::CONFIG);
164        })
165    }
166
167    /// Option how to parse a path.
168    ///
169    /// # See also
170    /// * [parse_path]
171    #[derive(Debug, Clone, Copy)]
172    pub enum ParsePathKind {
173        /// Do not post-process the path.
174        Direct,
175        /// Convert the path to a absolute path. The path must exist.
176        AbsoluteExisting,
177        /// Convert the path to a absolute path. The path might not exist.
178        AbsoluteNonExisting,
179    }
180
181    /// Parse a path from a string.
182    ///
183    /// # Arguments
184    /// * `path` - The path to parse.
185    /// * `kind` - How to parse the path.
186    ///
187    /// # Returns
188    /// The parsed path.
189    pub fn parse_path(path: &str, kind: ParsePathKind) -> PathBuf {
190        let path = std::path::Path::new(path);
191
192        let path = path.to_path_buf();
193
194        let path = match kind {
195            ParsePathKind::Direct => path,
196            ParsePathKind::AbsoluteExisting => to_lexical_absolute(path, true),
197            ParsePathKind::AbsoluteNonExisting => to_lexical_absolute(path, false),
198        };
199
200        path
201    }
202
203    /// Convert a path to a absolute path.
204    ///
205    /// # Arguments
206    /// * `path` - The path to convert.
207    /// * `exists` - Whether the path must exist.
208    ///
209    /// # Returns
210    /// The absolute path.
211    ///
212    /// # Exit
213    /// Exits the process if the path could not be resolved.
214    pub fn to_lexical_absolute(path: PathBuf, exists: bool) -> PathBuf {
215        let path = match exists {
216            true => path.canonicalize(),
217            false => path.to_lexical_absolute(),
218        };
219
220        let path = match path{
221            Ok(out) => out,
222            Err(e) => {
223                eprintln!("IO error, could not resolve output file: {:?}", e);
224                std::process::exit(exitcode::CONFIG);
225            }
226        };
227
228        path
229    }
230}