nd_util/
drop_remove_path.rs1use std::mem::ManuallyDrop;
2use std::ops::Deref;
3use std::path::Path;
4use std::path::PathBuf;
5
6#[derive(Debug)]
10pub struct DropRemovePath {
11 path: PathBuf,
13
14 should_remove: bool,
16}
17
18impl DropRemovePath {
19 pub fn new<P>(path: P) -> Self
21 where
22 P: AsRef<Path>,
23 {
24 Self {
25 path: path.as_ref().into(),
26 should_remove: true,
27 }
28 }
29
30 pub fn persist(&mut self) {
32 self.should_remove = false;
33 }
34
35 pub async fn try_drop(self) -> Result<bool, (Self, std::io::Error)> {
42 let wrapper = ManuallyDrop::new(self);
43 let should_remove = wrapper.should_remove;
44
45 if should_remove {
46 tokio::fs::remove_file(&wrapper.path)
47 .await
48 .map_err(|e| (ManuallyDrop::into_inner(wrapper), e))?;
49 }
50
51 Ok(should_remove)
52 }
53}
54
55impl AsRef<Path> for DropRemovePath {
56 fn as_ref(&self) -> &Path {
57 self.path.as_ref()
58 }
59}
60
61impl Deref for DropRemovePath {
62 type Target = Path;
63
64 fn deref(&self) -> &Self::Target {
65 &self.path
66 }
67}
68
69impl Drop for DropRemovePath {
70 fn drop(&mut self) {
71 let should_remove = self.should_remove;
72 let path = std::mem::take(&mut self.path);
73
74 tokio::spawn(async move {
76 if should_remove {
77 if let Err(error) = tokio::fs::remove_file(path).await {
78 let message = format!("failed to delete file: '{error}'");
79 if std::thread::panicking() {
80 eprintln!("{message}");
81 } else {
82 panic!("{message}");
83 }
84 }
85 }
86 });
87 }
88}
89
90#[cfg(test)]
91mod test {
92 use super::*;
93 use tokio::io::AsyncWriteExt;
94
95 #[tokio::test]
96 async fn drop_remove_tokio_file_sanity_check() {
97 tokio::fs::create_dir_all("test_tmp")
98 .await
99 .expect("failed to create tmp dir");
100
101 let file_path: &Path = "test_tmp/test.txt".as_ref();
102 let file_data = b"testing 1 2 3";
103
104 {
105 let mut file = tokio::fs::File::create(&file_path)
106 .await
107 .expect("failed to create file");
108 let drop_remove_path = DropRemovePath::new(file_path);
109
110 file.write_all(file_data)
111 .await
112 .expect("failed to write data");
113
114 drop(file);
115 drop_remove_path
116 .try_drop()
117 .await
118 .expect("failed to close file");
119 }
120 let exists = file_path.exists();
121 assert!(!exists, "nonpersisted file exists");
122
123 {
124 let mut file = tokio::fs::File::create(&file_path)
125 .await
126 .expect("failed to create file");
127 let mut drop_remove_path = DropRemovePath::new(file_path);
128
129 file.write_all(file_data)
130 .await
131 .expect("failed to write data");
132
133 drop_remove_path.persist();
134
135 drop(file);
136 drop_remove_path
137 .try_drop()
138 .await
139 .expect("failed to close file");
140 }
141
142 let exists = file_path.exists();
143 assert!(exists, "persisted file does not exist");
144
145 let _ = tokio::fs::remove_file(file_path).await.is_ok();
147 }
148}