qubit_fs/temp/
temp_file.rs1use std::fmt::Debug;
11use std::fmt::Formatter;
12use std::fmt::Result as FmtResult;
13
14use crate::FileSystem;
15use crate::error::FsError;
16use crate::error::FsErrorKind;
17use crate::error::FsOperation;
18use crate::error::FsResult;
19use crate::metadata::AchievedAtomicity;
20use crate::metadata::AtomicityRequirement;
21use crate::path::Path;
22use crate::spi::PersistRequest;
23use crate::spi::SpiPersistFailure;
24use crate::spi::TempResourceSpi;
25use crate::temp::PersistFailure;
26use crate::temp::PersistFailureState;
27use crate::temp::PersistOptions;
28use crate::temp::PersistOutcome;
29use crate::temp::TempResourceState;
30use crate::temp::internal::TempLifecycle;
31
32pub struct TempFile {
51 filesystem: FileSystem,
53 path: Path,
55 session: Box<dyn TempResourceSpi>,
57 lifecycle: TempLifecycle,
59}
60
61impl TempFile {
62 pub(crate) fn new(filesystem: FileSystem, path: Path, session: Box<dyn TempResourceSpi>) -> Self {
64 Self {
65 filesystem,
66 path,
67 session,
68 lifecycle: TempLifecycle::new(),
69 }
70 }
71 #[inline]
73 #[must_use]
74 pub const fn path(&self) -> &Path {
75 &self.path
76 }
77 #[inline]
79 #[must_use]
80 pub const fn state(&self) -> TempResourceState {
81 self.lifecycle.state()
82 }
83 #[allow(clippy::result_large_err)]
96 pub fn persist(&mut self, target: &Path, options: PersistOptions) -> Result<PersistOutcome, PersistFailure> {
97 if self.lifecycle.state() != TempResourceState::Owned {
98 return Err(PersistFailure::new(
99 self.invalid_state(FsOperation::PersistTemp),
100 self.lifecycle.failure_state(),
101 )
102 .with_publication_target(self.lifecycle.publication_target()));
103 }
104 if let Err(error) = self.filesystem.preflight_temp_persist(&self.path, target, &options) {
105 return Err(PersistFailure::new(error, PersistFailureState::NotPublished));
106 }
107 match self.session.persist(PersistRequest::new(target, options.clone())) {
108 Ok(outcome) => {
109 if outcome.target() != target {
110 self.lifecycle
111 .record_failure(PersistFailureState::Indeterminate, Some(target.clone()), false);
112 return Err(PersistFailure::new(
113 FsError::new(
114 FsErrorKind::ProviderContractViolation,
115 FsOperation::PersistTemp,
116 "provider reported a persistence target different from the request",
117 )
118 .with_path(self.path.clone())
119 .with_target(target.clone()),
120 PersistFailureState::Indeterminate,
121 ));
122 }
123 if options.atomicity() == AtomicityRequirement::Required
124 && outcome.atomicity() != AchievedAtomicity::Atomic
125 {
126 self.lifecycle.record_failure(
127 PersistFailureState::PublishedSourceRetained,
128 Some(target.clone()),
129 false,
130 );
131 return Err(PersistFailure::new(
132 FsError::new(
133 FsErrorKind::ProviderContractViolation,
134 FsOperation::PersistTemp,
135 "provider reported non-atomic success for atomic-required persist",
136 )
137 .with_path(self.path.clone())
138 .with_target(target.clone()),
139 PersistFailureState::PublishedSourceRetained,
140 )
141 .with_publication_target(self.lifecycle.publication_target()));
142 }
143 self.lifecycle.record_success(false, outcome.target().clone());
144 Ok(outcome)
145 }
146 Err(failure) => Err(self.record_persist_failure(failure, target, FsOperation::PersistTemp)),
147 }
148 }
149 #[allow(clippy::result_large_err)]
158 pub fn keep(&mut self) -> Result<PersistOutcome, PersistFailure> {
159 if let Err(error) = self.ensure_owned(FsOperation::KeepTemp) {
160 return Err(PersistFailure::new(error, self.lifecycle.failure_state())
161 .with_publication_target(self.lifecycle.publication_target()));
162 }
163 match self.session.keep() {
164 Ok(outcome) => {
165 if let Err(error) = self.filesystem.validate_temp_keep_target(&self.path, outcome.target()) {
166 self.lifecycle.record_failure(
167 PersistFailureState::Indeterminate,
168 Some(outcome.target().clone()),
169 true,
170 );
171 return Err(PersistFailure::new(error, PersistFailureState::Indeterminate));
172 }
173 self.path = outcome.target().clone();
174 self.lifecycle.record_success(true, outcome.target().clone());
175 Ok(outcome)
176 }
177 Err(failure) => Err(self.record_persist_failure(failure, &self.path.clone(), FsOperation::KeepTemp)),
178 }
179 }
180 pub fn cleanup(&mut self) -> FsResult<()> {
186 if !matches!(
187 self.lifecycle.state(),
188 TempResourceState::Owned | TempResourceState::CleanupRequired
189 ) {
190 return Err(self.invalid_state(FsOperation::CleanupTemp));
191 }
192 self.session
193 .cleanup()
194 .map(|()| self.lifecycle.record_cleanup_success())
195 .map_err(|error| self.record_lifecycle_error(error, FsOperation::CleanupTemp))
196 }
197 fn record_persist_failure(
199 &mut self,
200 failure: SpiPersistFailure,
201 target: &Path,
202 operation: FsOperation,
203 ) -> PersistFailure {
204 let (error, state) = failure.into_parts();
205 let publication_target = if operation == FsOperation::KeepTemp {
206 error.target().cloned()
207 } else {
208 Some(target.clone())
209 };
210 self.lifecycle
211 .record_failure(state, publication_target, operation == FsOperation::KeepTemp);
212 PersistFailure::new(
213 error.with_operation(operation).with_missing_context(
214 &self.path,
215 Some(target),
216 self.filesystem.properties().info().provider_id(),
217 ),
218 state,
219 )
220 .with_publication_target(self.lifecycle.publication_target())
221 }
222 fn ensure_owned(&self, operation: FsOperation) -> FsResult<()> {
224 if self.lifecycle.state() == TempResourceState::Owned {
225 Ok(())
226 } else {
227 Err(self.invalid_state(operation))
228 }
229 }
230 fn record_lifecycle_error(&mut self, error: FsError, operation: FsOperation) -> FsError {
232 self.lifecycle.record_cleanup_error(&error);
233 error.with_operation(operation).with_missing_context(
234 &self.path,
235 None,
236 self.filesystem.properties().info().provider_id(),
237 )
238 }
239 fn invalid_state(&self, operation: FsOperation) -> FsError {
241 FsError::new(
242 FsErrorKind::InvalidState,
243 operation,
244 "temporary file cannot perform this lifecycle operation",
245 )
246 .with_path(self.path.clone())
247 }
248}
249
250impl Debug for TempFile {
251 #[inline]
252 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
253 f.debug_struct("TempFile")
254 .field("path", &self.path)
255 .field("state", &self.lifecycle.state())
256 .finish_non_exhaustive()
257 }
258}
259impl Drop for TempFile {
260 fn drop(&mut self) {
261 if matches!(
262 self.lifecycle.state(),
263 TempResourceState::Owned | TempResourceState::CleanupRequired
264 ) {
265 let _ = self.session.cleanup();
266 }
267 }
268}