nd_util/
lib.rs

1#[cfg(feature = "download-to-file")]
2mod download_to_file;
3#[cfg(feature = "download-to-file")]
4pub use self::download_to_file::download_to_file;
5
6#[cfg(feature = "drop-remove-path")]
7mod drop_remove_path;
8#[cfg(feature = "drop-remove-path")]
9pub use self::drop_remove_path::DropRemovePath;
10
11#[cfg(feature = "download-to-path")]
12mod download_to_path;
13#[cfg(feature = "download-to-path")]
14pub use self::download_to_path::download_to_path;
15
16#[cfg(feature = "arc-anyhow-error")]
17mod arc_anyhow_error;
18#[cfg(feature = "arc-anyhow-error")]
19pub use self::arc_anyhow_error::ArcAnyhowError;
20
21use std::mem::ManuallyDrop;
22use std::ops::Deref;
23use std::path::Path;
24use std::path::PathBuf;
25
26/// Try to create a dir at the given path.
27///
28/// # Returns
29/// Returns `Ok(true)` if the dir was created.
30/// Returns `Ok(false)` if the dir already exists.
31/// Returns and error if there was an error creating the dir.
32pub fn try_create_dir<P>(path: P) -> std::io::Result<bool>
33where
34    P: AsRef<Path>,
35{
36    match std::fs::create_dir(path) {
37        Ok(()) => Ok(true),
38        Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => Ok(false),
39        Err(error) => Err(error),
40    }
41}
42
43/// Try to remove a dir at the given path.
44///
45/// # Returns
46/// Returns `Ok(true)` if the dir was removed.
47/// Returns `Ok(false)` if the dir did not exist.
48/// Returns and error if there was an error removing the dir.
49pub fn try_remove_dir<P>(path: P) -> std::io::Result<bool>
50where
51    P: AsRef<Path>,
52{
53    match std::fs::remove_dir(path) {
54        Ok(()) => Ok(true),
55        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
56        Err(error) => Err(error),
57    }
58}
59
60/// Syncronously remove a file at a path on drop.
61///
62/// Currently, this only supports files, NOT directories.
63#[derive(Debug)]
64pub struct DropRemovePathBlocking {
65    /// The path
66    path: PathBuf,
67
68    /// Whether dropping this should remove the file.
69    should_remove: bool,
70}
71
72impl DropRemovePathBlocking {
73    /// Make a new [`DropRemovePathBlocking`].
74    pub fn new<P>(path: P) -> Self
75    where
76        P: AsRef<Path>,
77    {
78        Self {
79            path: path.as_ref().into(),
80            should_remove: true,
81        }
82    }
83
84    /// Persist the file at this path.
85    pub fn persist(&mut self) {
86        self.should_remove = false;
87    }
88
89    /// Try to drop this file path, removing it if needed.
90    ///
91    /// # Return
92    /// Returns an error if the file could not be removed.
93    /// Returns Ok(true) if the file was removed.
94    /// Returns Ok(false) if the file was not removed.
95    pub fn try_drop(self) -> Result<bool, (Self, std::io::Error)> {
96        let wrapper = ManuallyDrop::new(self);
97        let should_remove = wrapper.should_remove;
98
99        if should_remove {
100            std::fs::remove_file(&wrapper.path)
101                .map_err(|e| (ManuallyDrop::into_inner(wrapper), e))?;
102        }
103
104        Ok(should_remove)
105    }
106}
107
108impl AsRef<Path> for DropRemovePathBlocking {
109    fn as_ref(&self) -> &Path {
110        self.path.as_ref()
111    }
112}
113
114impl Deref for DropRemovePathBlocking {
115    type Target = Path;
116
117    fn deref(&self) -> &Self::Target {
118        &self.path
119    }
120}
121
122impl Drop for DropRemovePathBlocking {
123    fn drop(&mut self) {
124        // Try to remove the path.
125        if self.should_remove {
126            if let Err(error) = std::fs::remove_file(self.path.clone()) {
127                let message = format!("failed to delete file: '{error}'");
128                if std::thread::panicking() {
129                    eprintln!("{message}");
130                } else {
131                    panic!("{message}");
132                }
133            }
134        }
135    }
136}
137
138#[cfg(test)]
139mod test {
140    use super::*;
141    use std::io::Write;
142
143    #[test]
144    fn try_create_dir_works() {
145        let path = "test_tmp/try_create_dir";
146
147        std::fs::create_dir_all("test_tmp").expect("failed to create tmp dir");
148
149        try_remove_dir(path).expect("failed to remove dir");
150        assert!(try_create_dir(path).expect("failed to create dir"));
151        assert!(!try_create_dir(path).expect("failed to create dir"));
152        assert!(try_remove_dir(path).expect("failed to remove dir"));
153        assert!(!try_remove_dir(path).expect("failed to remove dir"));
154    }
155
156    #[test]
157    fn drop_remove_file_blocking_sanity_check() {
158        std::fs::create_dir_all("test_tmp").expect("failed to create tmp dir");
159
160        let file_path: &Path = "test_tmp/drop_remove_file_blocking.txt".as_ref();
161        let file_data = b"testing 1 2 3";
162
163        {
164            let mut file = std::fs::File::create(file_path).expect("failed to create file");
165            let drop_remove_path = DropRemovePathBlocking::new(file_path);
166
167            file.write_all(file_data).expect("failed to write data");
168
169            drop(file);
170            drop_remove_path.try_drop().expect("failed to close file");
171        }
172        let exists = file_path.exists();
173        assert!(!exists, "nonpersisted file exists");
174
175        {
176            let mut file = std::fs::File::create(file_path).expect("failed to create file");
177            let mut drop_remove_path = DropRemovePathBlocking::new(file_path);
178
179            file.write_all(file_data).expect("failed to write data");
180
181            drop_remove_path.persist();
182
183            drop(file);
184            drop_remove_path.try_drop().expect("failed to close file");
185        }
186
187        let exists = file_path.exists();
188        assert!(exists, "persisted file does not exist");
189
190        // Failed cleanup does not matter
191        let _ = std::fs::remove_file(file_path).is_ok();
192    }
193}