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 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 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}