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}