Skip to main content

pict_rs_tmp_dir/
lib.rs

1use std::{
2    ops::Deref,
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use pict_rs_process::Extras;
8use uuid::Uuid;
9
10pub type ArcTmpDir = Arc<TmpDir>;
11
12#[derive(Debug)]
13pub struct TmpDir {
14    path: Option<PathBuf>,
15}
16
17impl TmpDir {
18    pub async fn init<P: AsRef<Path>>(path: P, cleanup: bool) -> std::io::Result<Arc<Self>> {
19        let base_path = path.as_ref().join("pict-rs");
20
21        if cleanup && tokio::fs::metadata(&base_path).await.is_ok() {
22            tokio::fs::remove_dir_all(&base_path).await?;
23        }
24
25        let path = base_path.join(Uuid::now_v7().to_string());
26
27        tokio::fs::create_dir_all(&path).await?;
28
29        Ok(Arc::new(TmpDir { path: Some(path) }))
30    }
31
32    fn build_tmp_file(&self, ext: Option<&str>) -> PathBuf {
33        if let Some(ext) = ext {
34            self.path
35                .as_ref()
36                .expect("tmp path exists")
37                .join(format!("{}{}", Uuid::now_v7(), ext))
38        } else {
39            self.path
40                .as_ref()
41                .expect("tmp path exists")
42                .join(Uuid::now_v7().to_string())
43        }
44    }
45
46    pub fn tmp_file(&self, ext: Option<&str>) -> TmpFile {
47        TmpFile(Some(self.build_tmp_file(ext)))
48    }
49
50    pub async fn tmp_folder(&self) -> std::io::Result<TmpFolder> {
51        let path = self.build_tmp_file(None);
52        tokio::fs::create_dir(&path).await?;
53        Ok(TmpFolder(Some(path)))
54    }
55
56    pub async fn cleanup(self: Arc<Self>) -> std::io::Result<()> {
57        if let Some(mut path) = Arc::into_inner(self).and_then(|mut this| this.path.take()) {
58            tokio::fs::remove_dir_all(&path).await?;
59
60            if path.pop() {
61                // attempt to remove parent directory if it is empty
62                let _ = tokio::fs::remove_dir(path).await;
63            }
64        }
65
66        Ok(())
67    }
68}
69
70impl Drop for TmpDir {
71    fn drop(&mut self) {
72        if let Some(mut path) = self.path.take() {
73            tracing::warn!("TmpDir - Blocking remove of {path:?}");
74            std::fs::remove_dir_all(&path).expect("Removed directory");
75            if path.pop() {
76                // attempt to remove parent directory if it is empty
77                let _ = std::fs::remove_dir(path);
78            }
79        }
80    }
81}
82
83#[must_use]
84pub struct TmpFolder(Option<PathBuf>);
85
86impl TmpFolder {
87    pub async fn cleanup(mut self) -> std::io::Result<()> {
88        self.consume().await
89    }
90}
91
92#[async_trait::async_trait(?Send)]
93impl Extras for TmpFolder {
94    async fn consume(&mut self) -> std::io::Result<()> {
95        tokio::fs::remove_dir_all(&self).await?;
96        self.0.take();
97        Ok(())
98    }
99}
100
101impl AsRef<Path> for TmpFolder {
102    fn as_ref(&self) -> &Path {
103        self.0.as_deref().unwrap()
104    }
105}
106
107impl Deref for TmpFolder {
108    type Target = Path;
109
110    fn deref(&self) -> &Self::Target {
111        self.0.as_deref().unwrap()
112    }
113}
114
115impl Drop for TmpFolder {
116    fn drop(&mut self) {
117        if let Some(path) = self.0.take() {
118            tracing::warn!("TmpFolder - Blocking remove of directory {path:?}");
119            if let Err(e) = std::fs::remove_dir_all(path) {
120                tracing::error!("Failed removing directory {e}");
121            }
122        }
123    }
124}
125
126#[must_use]
127pub struct TmpFile(Option<PathBuf>);
128
129impl TmpFile {
130    pub async fn cleanup(mut self) -> std::io::Result<()> {
131        self.consume().await
132    }
133}
134
135#[async_trait::async_trait(?Send)]
136impl Extras for TmpFile {
137    async fn consume(&mut self) -> std::io::Result<()> {
138        tokio::fs::remove_file(&self).await?;
139        self.0.take();
140        Ok(())
141    }
142}
143
144impl AsRef<Path> for TmpFile {
145    fn as_ref(&self) -> &Path {
146        self.0.as_deref().unwrap()
147    }
148}
149
150impl Deref for TmpFile {
151    type Target = Path;
152
153    fn deref(&self) -> &Self::Target {
154        self.0.as_deref().unwrap()
155    }
156}
157
158impl Drop for TmpFile {
159    fn drop(&mut self) {
160        if let Some(path) = self.0.take() {
161            tracing::warn!("TmpFile - Blocking remove of file {path:?}");
162            if let Err(e) = std::fs::remove_file(path) {
163                tracing::error!("Failed removing file {e}");
164            }
165        }
166    }
167}