1#![allow(clippy::empty_docs)]
3use std::{io, path::Path};
4
5use tempfile::{NamedTempFile, TempPath};
6
7use crate::{AutoRemove, ContainingDirectory, ForksafeTempfile, Handle, NEXT_MAP_INDEX, REGISTRY};
8
9#[derive(Debug)]
11pub struct Writable;
12
13#[derive(Debug)]
17pub struct Closed;
18
19pub(crate) enum Mode {
20 Writable,
21 Closed,
22}
23
24impl Handle<()> {
26 fn at_path(
27 path: &Path,
28 directory: ContainingDirectory,
29 cleanup: AutoRemove,
30 mode: Mode,
31 permissions: Option<std::fs::Permissions>,
32 ) -> io::Result<usize> {
33 let tempfile = {
34 let mut builder = tempfile::Builder::new();
35 let dot_ext_storage;
36 match path.file_stem() {
37 Some(stem) => builder.prefix(stem),
38 None => builder.prefix(""),
39 };
40 if let Some(ext) = path.extension() {
41 dot_ext_storage = format!(".{}", ext.to_string_lossy());
42 builder.suffix(&dot_ext_storage);
43 }
44 if let Some(permissions) = permissions {
45 builder.permissions(permissions);
46 }
47 let parent_dir = path.parent().expect("parent directory is present");
48 let parent_dir = directory.resolve(parent_dir)?;
49 ForksafeTempfile::new(builder.rand_bytes(0).tempfile_in(parent_dir)?, cleanup, mode)
50 };
51 let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
52 expect_none(REGISTRY.insert(id, Some(tempfile)));
53 Ok(id)
54 }
55
56 fn new_writable_inner(
57 containing_directory: &Path,
58 directory: ContainingDirectory,
59 cleanup: AutoRemove,
60 mode: Mode,
61 ) -> io::Result<usize> {
62 let containing_directory = directory.resolve(containing_directory)?;
63 let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
64 expect_none(REGISTRY.insert(
65 id,
66 Some(ForksafeTempfile::new(
67 NamedTempFile::new_in(containing_directory)?,
68 cleanup,
69 mode,
70 )),
71 ));
72 Ok(id)
73 }
74}
75
76impl Handle<Closed> {
78 pub fn at(path: impl AsRef<Path>, directory: ContainingDirectory, cleanup: AutoRemove) -> io::Result<Self> {
88 Ok(Handle {
89 id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Closed, None)?,
90 _marker: Default::default(),
91 })
92 }
93
94 pub fn at_with_permissions(
96 path: impl AsRef<Path>,
97 directory: ContainingDirectory,
98 cleanup: AutoRemove,
99 permissions: std::fs::Permissions,
100 ) -> io::Result<Self> {
101 Ok(Handle {
102 id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Closed, Some(permissions))?,
103 _marker: Default::default(),
104 })
105 }
106
107 pub fn take(self) -> Option<TempPath> {
111 let res = REGISTRY.remove(&self.id);
112 std::mem::forget(self);
113 res.and_then(|(_k, v)| v.map(ForksafeTempfile::into_temppath))
114 }
115}
116
117impl Handle<Writable> {
119 pub fn at(path: impl AsRef<Path>, directory: ContainingDirectory, cleanup: AutoRemove) -> io::Result<Self> {
129 Ok(Handle {
130 id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Writable, None)?,
131 _marker: Default::default(),
132 })
133 }
134
135 pub fn at_with_permissions(
137 path: impl AsRef<Path>,
138 directory: ContainingDirectory,
139 cleanup: AutoRemove,
140 permissions: std::fs::Permissions,
141 ) -> io::Result<Self> {
142 Ok(Handle {
143 id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Writable, Some(permissions))?,
144 _marker: Default::default(),
145 })
146 }
147
148 pub fn new(
157 containing_directory: impl AsRef<Path>,
158 directory: ContainingDirectory,
159 cleanup: AutoRemove,
160 ) -> io::Result<Self> {
161 Ok(Handle {
162 id: Handle::<()>::new_writable_inner(containing_directory.as_ref(), directory, cleanup, Mode::Writable)?,
163 _marker: Default::default(),
164 })
165 }
166
167 pub fn take(self) -> Option<NamedTempFile> {
171 let res = REGISTRY.remove(&self.id);
172 std::mem::forget(self);
173 res.and_then(|(_k, v)| v.map(|v| v.into_tempfile().expect("correct runtime typing")))
174 }
175
176 pub fn close(self) -> std::io::Result<Handle<Closed>> {
182 match REGISTRY.remove(&self.id) {
183 Some((id, Some(t))) => {
184 std::mem::forget(self);
185 expect_none(REGISTRY.insert(id, Some(t.close())));
186 Ok(Handle::<Closed> {
187 id,
188 _marker: Default::default(),
189 })
190 }
191 None | Some((_, None)) => Err(std::io::Error::new(
192 std::io::ErrorKind::NotFound,
193 format!("The tempfile with id {} wasn't available anymore", self.id),
194 )),
195 }
196 }
197}
198
199impl Handle<Writable> {
201 pub fn with_mut<T>(&mut self, once: impl FnOnce(&mut NamedTempFile) -> T) -> std::io::Result<T> {
210 match REGISTRY.remove(&self.id) {
211 Some((id, Some(mut t))) => {
212 let res = once(t.as_mut_tempfile().expect("correct runtime typing"));
213 expect_none(REGISTRY.insert(id, Some(t)));
214 Ok(res)
215 }
216 None | Some((_, None)) => Err(std::io::Error::new(
217 std::io::ErrorKind::NotFound,
218 format!("The tempfile with id {} wasn't available anymore", self.id),
219 )),
220 }
221 }
222}
223
224mod io_impls {
225 use std::{io, io::SeekFrom};
226
227 use super::{Handle, Writable};
228
229 impl io::Write for Handle<Writable> {
230 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
231 self.with_mut(|f| f.write(buf))?
232 }
233
234 fn flush(&mut self) -> io::Result<()> {
235 self.with_mut(io::Write::flush)?
236 }
237 }
238
239 impl io::Seek for Handle<Writable> {
240 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
241 self.with_mut(|f| f.seek(pos))?
242 }
243 }
244
245 impl io::Read for Handle<Writable> {
246 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
247 self.with_mut(|f| f.read(buf))?
248 }
249 }
250}
251
252pub mod persist {
254 use std::path::Path;
255
256 use crate::{
257 handle::{expect_none, Closed, Writable},
258 Handle, REGISTRY,
259 };
260
261 mod error {
262 use std::fmt::{self, Debug, Display};
263
264 use crate::Handle;
265
266 #[derive(Debug)]
268 pub struct Error<T: Debug> {
269 pub error: std::io::Error,
271 pub handle: Handle<T>,
273 }
274
275 impl<T: Debug> Display for Error<T> {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 Display::fmt(&self.error, f)
278 }
279 }
280
281 impl<T: Debug> std::error::Error for Error<T> {
282 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
283 self.error.source()
284 }
285 }
286 }
287 pub use error::Error;
288
289 impl Handle<Writable> {
290 pub fn persist(self, path: impl AsRef<Path>) -> Result<Option<std::fs::File>, Error<Writable>> {
295 let res = REGISTRY.remove(&self.id);
296
297 match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) {
298 Some(Ok(Some(file))) => {
299 std::mem::forget(self);
300 Ok(Some(file))
301 }
302 None => {
303 std::mem::forget(self);
304 Ok(None)
305 }
306 Some(Err((err, tempfile))) => {
307 expect_none(REGISTRY.insert(self.id, Some(tempfile)));
308 Err(Error::<Writable> {
309 error: err,
310 handle: self,
311 })
312 }
313 Some(Ok(None)) => unreachable!("no open files in an open handle"),
314 }
315 }
316 }
317
318 impl Handle<Closed> {
319 pub fn persist(self, path: impl AsRef<Path>) -> Result<(), Error<Closed>> {
322 let res = REGISTRY.remove(&self.id);
323
324 match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) {
325 None | Some(Ok(None)) => {
326 std::mem::forget(self);
327 Ok(())
328 }
329 Some(Err((err, tempfile))) => {
330 expect_none(REGISTRY.insert(self.id, Some(tempfile)));
331 Err(Error::<Closed> {
332 error: err,
333 handle: self,
334 })
335 }
336 Some(Ok(Some(_file))) => unreachable!("no open files in a closed handle"),
337 }
338 }
339 }
340}
341
342impl ContainingDirectory {
343 fn resolve(self, dir: &Path) -> std::io::Result<&Path> {
344 match self {
345 ContainingDirectory::Exists => Ok(dir),
346 ContainingDirectory::CreateAllRaceProof(retries) => crate::create_dir::all(dir, retries),
347 }
348 }
349}
350
351fn expect_none<T>(v: Option<T>) {
352 assert!(
353 v.is_none(),
354 "there should never be conflicts or old values as ids are never reused."
355 );
356}
357
358impl<T: std::fmt::Debug> Drop for Handle<T> {
359 fn drop(&mut self) {
360 if let Some((_id, Some(tempfile))) = REGISTRY.remove(&self.id) {
361 tempfile.drop_impl();
362 }
363 }
364}