qubit_fs/temp/
managed_temp_file.rs1use std::sync::Arc;
13
14use log::warn;
15
16use crate::{
17 AtomicityRequirement,
18 CopyConflictPolicy,
19 CopyOptions,
20 DeleteOptions,
21 FileSystem,
22 FsErrorKind,
23 FsPath,
24 FsResult,
25 PersistOptions,
26 RenameOptions,
27 TempFile,
28 TempResource,
29 WriteOutcome,
30};
31
32#[derive(Debug)]
34pub struct ManagedTempFile {
35 fs: Arc<dyn FileSystem>,
37 path: FsPath,
39 cleanup_on_drop: bool,
41}
42
43impl ManagedTempFile {
44 #[inline]
53 #[must_use]
54 pub fn new(fs: Arc<dyn FileSystem>, path: FsPath) -> Self {
55 Self {
56 fs,
57 path,
58 cleanup_on_drop: true,
59 }
60 }
61
62 #[inline]
67 fn detach(&mut self) -> FsPath {
68 self.cleanup_on_drop = false;
69 self.path.clone()
70 }
71}
72
73impl TempResource for ManagedTempFile {
74 #[inline]
75 fn fs(&self) -> Arc<dyn FileSystem> {
76 self.fs.clone()
77 }
78
79 #[inline]
80 fn path(&self) -> &FsPath {
81 &self.path
82 }
83
84 fn cleanup(mut self: Box<Self>) -> FsResult<()> {
85 let result = self.fs.delete(
86 &self.path,
87 &DeleteOptions {
88 missing_ok: true,
89 ..DeleteOptions::default()
90 },
91 );
92 if result.is_ok() {
93 self.cleanup_on_drop = false;
94 }
95 result
96 }
97
98 fn keep(mut self: Box<Self>) -> FsResult<FsPath> {
99 Ok(self.detach())
100 }
101}
102
103impl TempFile for ManagedTempFile {
104 fn persist(mut self: Box<Self>, target: &FsPath, options: &PersistOptions) -> FsResult<WriteOutcome> {
105 let rename_options = RenameOptions {
106 overwrite: options.overwrite,
107 atomic: options.atomic,
108 };
109 match self.fs.rename(&self.path, target, &rename_options) {
110 Ok(()) => {
111 self.cleanup_on_drop = false;
112 Ok(WriteOutcome::default())
113 }
114 Err(error) if error.kind() == FsErrorKind::UnsupportedOperation && options.allow_copy_delete => {
115 let mut copy_options = CopyOptions::file();
116 copy_options.conflict = if options.overwrite {
117 CopyConflictPolicy::Overwrite
118 } else {
119 CopyConflictPolicy::Fail
120 };
121 copy_options.preserve_metadata = options.preserve_metadata;
122 if matches!(options.atomic, AtomicityRequirement::Required) {
123 copy_options.server_side = crate::ServerSidePreference::Require;
124 }
125 let outcome = self.fs.copy(&self.path, target, ©_options)?;
126 self.fs.delete(
127 &self.path,
128 &DeleteOptions {
129 missing_ok: true,
130 ..DeleteOptions::default()
131 },
132 )?;
133 self.cleanup_on_drop = false;
134 Ok(WriteOutcome {
135 bytes_written: Some(outcome.stats.bytes),
136 etag: None,
137 diagnostics: outcome.diagnostics,
138 })
139 }
140 Err(error) => Err(error),
141 }
142 }
143}
144
145impl Drop for ManagedTempFile {
146 fn drop(&mut self) {
147 if self.cleanup_on_drop {
148 let result = self.fs.delete(
149 &self.path,
150 &DeleteOptions {
151 missing_ok: true,
152 ..DeleteOptions::default()
153 },
154 );
155 if let Err(error) = result {
156 warn!("failed to cleanup temporary file '{}': {error}", self.path);
157 }
158 }
159 }
160}