1use crate::UploadStore;
5use base_scheduler::{Blocking, Cancellation};
6use base_web_content::{FieldPath, UploadHead};
7use std::{
8 fmt,
9 fs::{self, File, OpenOptions},
10 io::{self, Write},
11 path::{Path, PathBuf},
12};
13
14const WRITE_SIZE: usize = 64 * 1024;
16
17pub struct FileStore<'s, S, D> {
25 scheduler: &'s S,
26 destination: D,
27 #[cfg(unix)]
28 permissions: u32,
29}
30
31impl<'s, S, D> FileStore<'s, S, D>
32where
33 S: Blocking + Sync,
34 D: FnMut(&FieldPath) -> PathBuf + Send,
35{
36 pub fn new(scheduler: &'s S, destination: D) -> Self {
38 Self {
39 scheduler,
40 destination,
41 #[cfg(unix)]
42 permissions: 0o600,
43 }
44 }
45
46 #[cfg(unix)]
48 pub fn with_permissions(mut self, permissions: u32) -> Self {
49 self.permissions = permissions & 0o777;
50 self
51 }
52}
53
54impl<S, D> fmt::Debug for FileStore<'_, S, D> {
55 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
56 formatter.debug_struct("FileStore").finish_non_exhaustive()
57 }
58}
59
60pub struct FileUpload {
62 path: PathBuf,
63 file: Option<File>,
65 pending: Vec<u8>,
67}
68
69impl FileUpload {
70 pub fn path(&self) -> &Path {
72 &self.path
73 }
74}
75
76impl fmt::Debug for FileUpload {
78 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79 formatter
80 .debug_struct("FileUpload")
81 .field("path", &self.path)
82 .field("pending", &self.pending.len())
83 .finish_non_exhaustive()
84 }
85}
86
87impl<S, D> UploadStore for FileStore<'_, S, D>
88where
89 S: Blocking + Sync,
90 D: FnMut(&FieldPath) -> PathBuf + Send,
91{
92 type Handle = PathBuf;
93 type Upload = FileUpload;
94 type Error = io::Error;
95
96 async fn create(
97 &mut self,
98 head: &UploadHead,
99 cancellation: &Cancellation,
100 ) -> io::Result<FileUpload> {
101 let path = (self.destination)(head.path());
102 let mut options = OpenOptions::new();
103 options.write(true).create_new(true);
105 #[cfg(unix)]
106 std::os::unix::fs::OpenOptionsExt::mode(&mut options, self.permissions);
107 let opened = path.clone();
108 let file = blocking(self.scheduler, move || options.open(opened), cancellation).await?;
109 Ok(FileUpload {
110 path,
111 file: Some(file),
112 pending: Vec::new(),
113 })
114 }
115
116 async fn write(
117 &mut self,
118 upload: &mut FileUpload,
119 content: &[u8],
120 cancellation: &Cancellation,
121 ) -> io::Result<()> {
122 upload.pending.extend_from_slice(content);
123 if upload.pending.len() < WRITE_SIZE {
124 return Ok(());
125 }
126 flush(self.scheduler, upload, cancellation).await
127 }
128
129 async fn finish(
130 &mut self,
131 upload: &mut FileUpload,
132 cancellation: &Cancellation,
133 ) -> io::Result<PathBuf> {
134 if !upload.pending.is_empty() {
135 flush(self.scheduler, upload, cancellation).await?;
136 }
137 let file = upload.file.take();
139 let close = move || {
140 drop(file);
141 Ok(())
142 };
143 blocking(self.scheduler, close, cancellation).await?;
144 Ok(upload.path.clone())
145 }
146
147 async fn abort(&mut self, upload: FileUpload) -> io::Result<()> {
148 let FileUpload { path, file, .. } = upload;
149 let abort = move || {
150 drop(file);
151 remove(&path)
152 };
153 blocking(self.scheduler, abort, &Cancellation::never()).await
154 }
155
156 async fn remove(&mut self, handle: PathBuf) -> io::Result<()> {
157 blocking(
158 self.scheduler,
159 move || remove(&handle),
160 &Cancellation::never(),
161 )
162 .await
163 }
164}
165
166async fn blocking<S: Blocking + Sync, T: Send + 'static>(
168 scheduler: &S,
169 operation: impl FnOnce() -> io::Result<T> + Send + 'static,
170 cancellation: &Cancellation,
171) -> io::Result<T> {
172 let handle = scheduler
173 .spawn_blocking(operation, cancellation)
174 .await
175 .map_err(io::Error::other)?;
176 handle
178 .await
179 .map_err(|error| io::Error::other(error.to_string()))?
180}
181
182async fn flush<S: Blocking + Sync>(
184 scheduler: &S,
185 upload: &mut FileUpload,
186 cancellation: &Cancellation,
187) -> io::Result<()> {
188 let mut file = upload.file.take().ok_or_else(closed)?;
190 let pending = std::mem::take(&mut upload.pending);
191 let path = upload.path.clone();
192 let (file, mut pending) = blocking(
193 scheduler,
194 move || {
195 write_all(&mut file, &pending, &path)?;
196 Ok((file, pending))
197 },
198 cancellation,
199 )
200 .await?;
201 pending.clear();
202 upload.file = Some(file);
203 upload.pending = pending;
204 Ok(())
205}
206
207fn closed() -> io::Error {
209 io::Error::other("the upload's file is closed, after it failed or finished")
210}
211
212fn write_all(file: &mut File, content: &[u8], path: &Path) -> io::Result<()> {
214 #[cfg(test)]
215 {
216 if faults::check(path, faults::Operation::Panic).is_err() {
217 panic!("injected panic");
218 }
219 faults::check(path, faults::Operation::Write)?;
220 }
221 #[cfg(not(test))]
222 let _ = path;
223 file.write_all(content)
224}
225
226fn remove(path: &Path) -> io::Result<()> {
229 #[cfg(test)]
230 faults::check(path, faults::Operation::Remove)?;
231 match fs::remove_file(path) {
232 Err(error) if error.kind() != io::ErrorKind::NotFound => Err(error),
233 _ => Ok(()),
234 }
235}
236
237#[cfg(test)]
239pub(crate) mod faults {
240 use std::{
241 io,
242 path::{Path, PathBuf},
243 sync::Mutex,
244 };
245
246 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
247 pub(crate) enum Operation {
248 Write,
249 Remove,
250 Panic,
252 }
253
254 static FAULTS: Mutex<Vec<(PathBuf, Operation)>> = Mutex::new(Vec::new());
255
256 pub(crate) fn inject(path: &Path, operation: Operation) {
258 FAULTS.lock().unwrap().push((path.to_owned(), operation));
259 }
260
261 pub(crate) fn check(path: &Path, operation: Operation) -> io::Result<()> {
263 let mut faults = FAULTS.lock().unwrap();
264 let Some(position) = faults
265 .iter()
266 .position(|(faulty, injected)| faulty == path && *injected == operation)
267 else {
268 return Ok(());
269 };
270 faults.remove(position);
271 Err(io::Error::other("injected failure"))
272 }
273}