1use crate::{
4 error::{Error, Operation, Result},
5 path::{Path, PathBuf},
6};
7
8#[derive(Debug)]
10pub struct File {
11 inner: tempfile::NamedTempFile,
12 path: PathBuf,
13}
14
15impl File {
16 pub fn with_affixes(prefix: &str, suffix: &str) -> Result<Self> {
25 let inner = tempfile::Builder::new()
26 .prefix(prefix)
27 .suffix(suffix)
28 .tempfile()
29 .map_err(|source| {
30 Error::new(
31 Operation::CreateFile,
32 Path::new(std::env::temp_dir().as_os_str()),
33 source,
34 )
35 })?;
36 let path = PathBuf::from(inner.path());
37
38 Ok(Self { inner, path })
39 }
40
41 #[must_use]
43 pub fn path(&self) -> &Path {
44 &self.path
45 }
46
47 pub fn retain(self) -> Result<crate::file::File> {
57 let Self { inner, path } = self;
58 let (file, retained_path) = inner
59 .keep()
60 .map_err(|error| Error::new(Operation::RetainFile, &path, error.error))?;
61 let retained_path = PathBuf::from(retained_path);
62
63 debug_assert_eq!(
64 retained_path, path,
65 "retaining a temporary file must preserve its unique path"
66 );
67
68 Ok(crate::file::File::from_open_file(file, retained_path))
69 }
70}
71
72#[derive(Debug)]
74pub struct Directory {
75 inner: tempfile::TempDir,
76 path: PathBuf,
77}
78
79impl Directory {
80 pub fn new() -> Result<Self> {
86 Self::with_prefix("wowlab-")
87 }
88
89 pub fn with_prefix(prefix: &str) -> Result<Self> {
95 let inner = tempfile::Builder::new()
96 .prefix(prefix)
97 .tempdir()
98 .map_err(|source| {
99 Error::new(
100 Operation::CreateDirectory,
101 Path::new(std::env::temp_dir().as_os_str()),
102 source,
103 )
104 })?;
105 let path = PathBuf::from(inner.path());
106
107 Ok(Self { inner, path })
108 }
109
110 #[must_use]
112 pub fn path(&self) -> &Path {
113 &self.path
114 }
115
116 #[must_use]
120 pub fn retain(self) -> PathBuf {
121 let path = PathBuf::from(self.inner.keep());
122
123 debug_assert_eq!(
124 path, self.path,
125 "retaining a temporary directory must preserve its unique path"
126 );
127
128 path
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use std::io::Write as _;
135
136 use googletest::prelude::*;
137
138 use super::{Directory, File};
139 use crate::{directory, file};
140
141 #[gtest]
142 fn temporary_file_is_removed_when_its_guard_is_dropped() -> Result<()> {
143 let path = {
144 let temporary = File::with_affixes("wowlab-fs-scoped-", ".log").or_fail()?;
145 let path = temporary.path().to_path_buf();
146
147 verify_that!(directory::inspect(&path).or_fail()?, some(anything()))?;
148
149 path
150 };
151
152 verify_that!(directory::inspect(&path).or_fail()?, none())
153 }
154
155 #[gtest]
156 fn retained_files_are_unique_and_remain_readable_after_handles_close() -> Result<()> {
157 let temporary_left = File::with_affixes("wowlab-fs-retained-", ".log").or_fail()?;
158 let temporary_right = File::with_affixes("wowlab-fs-retained-", ".log").or_fail()?;
159 let mut left = temporary_left.retain().or_fail()?;
160 let right = temporary_right.retain().or_fail()?;
161 let left_path = left.path().to_path_buf();
162 let right_path = right.path().to_path_buf();
163
164 left.write_all(b"trace").or_fail()?;
165 left.sync().or_fail()?;
166
167 verify_that!(left_path, not(eq(&right_path)))?;
168
169 drop(left);
170 drop(right);
171
172 verify_that!(file::read_bytes(&left_path).or_fail()?, eq(b"trace"))?;
173 verify_that!(directory::inspect(&right_path).or_fail()?, some(anything()))?;
174
175 directory::remove_file_if_exists(&left_path).or_fail()?;
176 directory::remove_file_if_exists(&right_path).or_fail()?;
177
178 Ok(())
179 }
180
181 #[gtest]
182 fn retained_directory_remains_until_its_owner_removes_it() -> Result<()> {
183 let retained = Directory::new().or_fail()?.retain();
184
185 verify_that!(directory::inspect(&retained).or_fail()?, some(anything()))?;
186
187 verify_true!(directory::remove_tree_if_exists(&retained).or_fail()?)
188 }
189}