1use std::{io, path::Path};
3
4use tempfile::{NamedTempFile, TempPath};
5
6use crate::{AutoRemove, ContainingDirectory, ForksafeTempfile, Handle, NEXT_MAP_INDEX, REGISTER};
7
8#[derive(Debug)]
10pub struct Writable;
11
12#[derive(Debug)]
16pub struct Closed;
17
18pub(crate) enum Mode {
19 Writable,
20 Closed,
21}
22
23impl Handle<()> {
25 fn at_path(
26 path: impl AsRef<Path>,
27 directory: ContainingDirectory,
28 cleanup: AutoRemove,
29 mode: Mode,
30 ) -> io::Result<usize> {
31 let path = path.as_ref();
32 let tempfile = {
33 let mut builder = tempfile::Builder::new();
34 let dot_ext_storage;
35 match path.file_stem() {
36 Some(stem) => builder.prefix(stem),
37 None => builder.prefix(""),
38 };
39 if let Some(ext) = path.extension() {
40 dot_ext_storage = format!(".{}", ext.to_string_lossy());
41 builder.suffix(&dot_ext_storage);
42 }
43 let parent_dir = path.parent().expect("parent directory is present");
44 let parent_dir = directory.resolve(parent_dir)?;
45 ForksafeTempfile::new(builder.rand_bytes(0).tempfile_in(parent_dir)?, cleanup, mode)
46 };
47 let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
48 expect_none(REGISTER.insert(id, Some(tempfile)));
49 Ok(id)
50 }
51
52 fn new_writable_inner(
53 containing_directory: impl AsRef<Path>,
54 directory: ContainingDirectory,
55 cleanup: AutoRemove,
56 mode: Mode,
57 ) -> io::Result<usize> {
58 let containing_directory = directory.resolve(containing_directory.as_ref())?;
59 let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
60 expect_none(REGISTER.insert(
61 id,
62 Some(ForksafeTempfile::new(
63 NamedTempFile::new_in(containing_directory)?,
64 cleanup,
65 mode,
66 )),
67 ));
68 Ok(id)
69 }
70}
71
72impl Handle<Closed> {
74 pub fn at(path: impl AsRef<Path>, directory: ContainingDirectory, cleanup: AutoRemove) -> io::Result<Self> {
79 Ok(Handle {
80 id: Handle::<()>::at_path(path, directory, cleanup, Mode::Closed)?,
81 _marker: Default::default(),
82 })
83 }
84
85 pub fn take(self) -> Option<TempPath> {
89 let res = REGISTER.remove(&self.id);
90 std::mem::forget(self);
91 res.and_then(|(_k, v)| v.map(|v| v.into_temppath()))
92 }
93}
94
95impl Handle<Writable> {
97 pub fn at(path: impl AsRef<Path>, directory: ContainingDirectory, cleanup: AutoRemove) -> io::Result<Self> {
102 Ok(Handle {
103 id: Handle::<()>::at_path(path, directory, cleanup, Mode::Writable)?,
104 _marker: Default::default(),
105 })
106 }
107
108 pub fn new(
112 containing_directory: impl AsRef<Path>,
113 directory: ContainingDirectory,
114 cleanup: AutoRemove,
115 ) -> io::Result<Self> {
116 Ok(Handle {
117 id: Handle::<()>::new_writable_inner(containing_directory, directory, cleanup, Mode::Writable)?,
118 _marker: Default::default(),
119 })
120 }
121
122 pub fn take(self) -> Option<NamedTempFile> {
126 let res = REGISTER.remove(&self.id);
127 std::mem::forget(self);
128 res.and_then(|(_k, v)| v.map(|v| v.into_tempfile().expect("correct runtime typing")))
129 }
130
131 pub fn close(self) -> std::io::Result<Handle<Closed>> {
137 match REGISTER.remove(&self.id) {
138 Some((id, Some(t))) => {
139 std::mem::forget(self);
140 expect_none(REGISTER.insert(id, Some(t.close())));
141 Ok(Handle::<Closed> {
142 id,
143 _marker: Default::default(),
144 })
145 }
146 None | Some((_, None)) => Err(std::io::Error::new(
147 std::io::ErrorKind::Interrupted,
148 format!("The tempfile with id {} wasn't available anymore", self.id),
149 )),
150 }
151 }
152}
153
154impl Handle<Writable> {
156 pub fn with_mut<T>(&mut self, once: impl FnOnce(&mut NamedTempFile) -> T) -> std::io::Result<T> {
165 match REGISTER.remove(&self.id) {
166 Some((id, Some(mut t))) => {
167 let res = once(t.as_mut_tempfile().expect("correct runtime typing"));
168 expect_none(REGISTER.insert(id, Some(t)));
169 Ok(res)
170 }
171 None | Some((_, None)) => Err(std::io::Error::new(
172 std::io::ErrorKind::Interrupted,
173 format!("The tempfile with id {} wasn't available anymore", self.id),
174 )),
175 }
176 }
177}
178
179mod io_impls {
180 use std::{io, io::SeekFrom};
181
182 use super::{Handle, Writable};
183
184 impl io::Write for Handle<Writable> {
185 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
186 self.with_mut(|f| f.write(buf))?
187 }
188
189 fn flush(&mut self) -> io::Result<()> {
190 self.with_mut(|f| f.flush())?
191 }
192 }
193
194 impl io::Seek for Handle<Writable> {
195 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
196 self.with_mut(|f| f.seek(pos))?
197 }
198 }
199
200 impl io::Read for Handle<Writable> {
201 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
202 self.with_mut(|f| f.read(buf))?
203 }
204 }
205}
206
207pub mod persist {
209 use std::path::Path;
210
211 use crate::{
212 handle::{expect_none, Closed, Writable},
213 Handle, REGISTER,
214 };
215
216 mod error {
217 use std::fmt::{self, Debug, Display};
218
219 use crate::Handle;
220
221 #[derive(Debug)]
223 pub struct Error<T: Debug> {
224 pub error: std::io::Error,
226 pub handle: Handle<T>,
228 }
229
230 impl<T: Debug> Display for Error<T> {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 Display::fmt(&self.error, f)
233 }
234 }
235
236 impl<T: Debug> std::error::Error for Error<T> {
237 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
238 self.error.source()
239 }
240 }
241 }
242 pub use error::Error;
243
244 impl Handle<Writable> {
245 pub fn persist(self, path: impl AsRef<Path>) -> Result<Option<std::fs::File>, Error<Writable>> {
250 let res = REGISTER.remove(&self.id);
251
252 match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) {
253 Some(Ok(Some(file))) => {
254 std::mem::forget(self);
255 Ok(Some(file))
256 }
257 None => {
258 std::mem::forget(self);
259 Ok(None)
260 }
261 Some(Err((err, tempfile))) => {
262 expect_none(REGISTER.insert(self.id, Some(tempfile)));
263 Err(Error::<Writable> {
264 error: err,
265 handle: self,
266 })
267 }
268 Some(Ok(None)) => unreachable!("no open files in an open handle"),
269 }
270 }
271 }
272
273 impl Handle<Closed> {
274 pub fn persist(self, path: impl AsRef<Path>) -> Result<(), Error<Closed>> {
277 let res = REGISTER.remove(&self.id);
278
279 match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) {
280 None | Some(Ok(None)) => {
281 std::mem::forget(self);
282 Ok(())
283 }
284 Some(Err((err, tempfile))) => {
285 expect_none(REGISTER.insert(self.id, Some(tempfile)));
286 Err(Error::<Closed> {
287 error: err,
288 handle: self,
289 })
290 }
291 Some(Ok(Some(_file))) => unreachable!("no open files in a closed handle"),
292 }
293 }
294 }
295}
296
297impl ContainingDirectory {
298 fn resolve(self, dir: &Path) -> std::io::Result<&Path> {
299 match self {
300 ContainingDirectory::Exists => Ok(dir),
301 ContainingDirectory::CreateAllRaceProof(retries) => crate::create_dir::all(dir, retries),
302 }
303 }
304}
305
306fn expect_none<T>(v: Option<T>) {
307 assert!(
308 v.is_none(),
309 "there should never be conflicts or old values as ids are never reused."
310 );
311}
312
313impl<T: std::fmt::Debug> Drop for Handle<T> {
314 fn drop(&mut self) {
315 if let Some((_id, Some(tempfile))) = REGISTER.remove(&self.id) {
316 tempfile.drop_impl();
317 }
318 }
319}