forked from
ptr.pet/faunu
endpoint 2.0
dysnomia.ptr.pet
1//! An ephemeral in-memory file system, intended mainly for unit tests
2
3use core::cmp;
4use std::fmt;
5use std::fmt::{Debug, Formatter};
6use std::io::{Cursor, Read, Seek, SeekFrom, Write};
7use std::mem::swap;
8use std::sync::Arc;
9use std::time::SystemTime;
10use vfs::error::VfsErrorKind;
11use vfs::{FileSystem, VfsFileType};
12use vfs::{SeekAndRead, VfsMetadata};
13use vfs::{SeekAndWrite, VfsResult};
14
15use crate::globals::current_time;
16
17type MemoryFsHandle = Arc<MemoryFsImpl>;
18
19/// An ephemeral in-memory file system, intended mainly for unit tests
20pub struct MemoryFS {
21 handle: MemoryFsHandle,
22}
23
24impl Debug for MemoryFS {
25 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
26 f.write_str("In Memory File System")
27 }
28}
29
30impl MemoryFS {
31 /// Create a new in-memory filesystem
32 pub fn new() -> Self {
33 MemoryFS {
34 handle: Arc::new(MemoryFsImpl::new()),
35 }
36 }
37
38 fn ensure_has_parent(&self, path: &str) -> VfsResult<()> {
39 let separator = path.rfind('/');
40 if let Some(index) = separator {
41 if self.exists(&path[..index])? {
42 return Ok(());
43 }
44 }
45 Err(VfsErrorKind::Other("Parent path does not exist".into()).into())
46 }
47}
48
49impl Default for MemoryFS {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55struct WritableFile {
56 content: Cursor<Vec<u8>>,
57 destination: String,
58 fs: MemoryFsHandle,
59}
60
61impl Seek for WritableFile {
62 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
63 self.content.seek(pos)
64 }
65}
66
67impl Write for WritableFile {
68 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
69 self.content.write(buf)
70 }
71
72 fn flush(&mut self) -> std::io::Result<()> {
73 self.content.flush()?;
74 let mut content = self.content.get_ref().clone();
75 swap(&mut content, self.content.get_mut());
76 let content = Arc::new(content);
77
78 let new_file = self
79 .fs
80 .files
81 .peek_with(&self.destination, |_, previous_file| MemoryFile {
82 file_type: VfsFileType::File,
83 content: content.clone(),
84 created: previous_file.created,
85 modified: current_time(),
86 accessed: previous_file.accessed,
87 })
88 .unwrap_or_else(|| {
89 let time = current_time();
90 MemoryFile {
91 file_type: VfsFileType::File,
92 content,
93 created: time,
94 modified: time,
95 accessed: None,
96 }
97 });
98
99 // Remove old entry if it exists, then insert new one
100 self.fs.files.remove_sync(&self.destination);
101 let _ = self
102 .fs
103 .files
104 .insert_sync(self.destination.clone(), new_file);
105 Ok(())
106 }
107}
108
109impl Drop for WritableFile {
110 fn drop(&mut self) {
111 self.flush()
112 .expect("Flush failed while dropping in-memory file");
113 }
114}
115
116struct ReadableFile {
117 #[allow(clippy::rc_buffer)] // to allow accessing the same object as writable
118 content: Arc<Vec<u8>>,
119 position: u64,
120}
121
122impl ReadableFile {
123 fn len(&self) -> u64 {
124 self.content.len() as u64 - self.position
125 }
126}
127
128impl Read for ReadableFile {
129 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
130 let amt = cmp::min(buf.len(), self.len() as usize);
131
132 if amt == 1 {
133 buf[0] = self.content[self.position as usize];
134 } else {
135 buf[..amt].copy_from_slice(
136 &self.content.as_slice()[self.position as usize..self.position as usize + amt],
137 );
138 }
139 self.position += amt as u64;
140 Ok(amt)
141 }
142}
143
144impl Seek for ReadableFile {
145 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
146 match pos {
147 SeekFrom::Start(offset) => self.position = offset,
148 SeekFrom::Current(offset) => self.position = (self.position as i64 + offset) as u64,
149 SeekFrom::End(offset) => self.position = (self.content.len() as i64 + offset) as u64,
150 }
151 Ok(self.position)
152 }
153}
154
155impl FileSystem for MemoryFS {
156 fn read_dir(&self, path: &str) -> VfsResult<Box<dyn Iterator<Item = String> + Send>> {
157 let prefix = format!("{}/", path);
158
159 // Ensure the directory exists
160 if !self.handle.files.contains(path) {
161 return Err(VfsErrorKind::FileNotFound.into());
162 }
163
164 let guard = scc::Guard::new();
165 let mut entries = Vec::new();
166
167 let range_start = prefix.clone();
168 for (candidate_path, _) in self.handle.files.range(range_start.., &guard) {
169 if candidate_path.starts_with(&prefix) {
170 let rest = &candidate_path[prefix.len()..];
171 if !rest.contains('/') {
172 entries.push(rest.to_string());
173 }
174 } else {
175 // we are in a different directory if the prefix doesnt match
176 break;
177 }
178 }
179
180 Ok(Box::new(entries.into_iter()))
181 }
182
183 fn create_dir(&self, path: &str) -> VfsResult<()> {
184 self.ensure_has_parent(path)?;
185
186 // Check if path already exists and return appropriate error
187 if let Some(result) = self
188 .handle
189 .files
190 .peek_with(path, |_, file| match file.file_type {
191 VfsFileType::File => Err(VfsErrorKind::FileExists.into()),
192 VfsFileType::Directory => Err(VfsErrorKind::DirectoryExists.into()),
193 })
194 {
195 return result;
196 }
197
198 let new_dir = MemoryFile {
199 file_type: VfsFileType::Directory,
200 content: Default::default(),
201 created: current_time(),
202 modified: current_time(),
203 accessed: current_time(),
204 };
205
206 self.handle
207 .files
208 .insert_sync(path.to_string(), new_dir)
209 .map_err(|_| VfsErrorKind::DirectoryExists.into())
210 }
211
212 fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead + Send>> {
213 if let Some(time) = current_time() {
214 let _ = self.set_access_time(path, time);
215 }
216
217 let content = self
218 .handle
219 .files
220 .peek_with(path, |_, file| {
221 ensure_file(file)?;
222 VfsResult::Ok(file.content.clone())
223 })
224 .ok_or(VfsErrorKind::FileNotFound)??;
225
226 Ok(Box::new(ReadableFile {
227 content,
228 position: 0,
229 }))
230 }
231
232 fn create_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndWrite + Send>> {
233 self.ensure_has_parent(path)?;
234 let content = Arc::new(Vec::<u8>::new());
235 let new_file = MemoryFile {
236 file_type: VfsFileType::File,
237 content,
238 created: current_time(),
239 modified: current_time(),
240 accessed: current_time(),
241 };
242
243 // Remove old entry if it exists, then insert new one
244 self.handle.files.remove_sync(path);
245 let _ = self.handle.files.insert_sync(path.to_string(), new_file);
246
247 let writer = WritableFile {
248 content: Cursor::new(vec![]),
249 destination: path.to_string(),
250 fs: self.handle.clone(),
251 };
252 Ok(Box::new(writer))
253 }
254
255 fn append_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndWrite + Send>> {
256 let content = self
257 .handle
258 .files
259 .peek_with(path, |_, file| file.content.clone())
260 .ok_or(VfsErrorKind::FileNotFound)?;
261 let mut content = Cursor::new(content.as_slice().to_vec());
262 content.seek(SeekFrom::End(0))?;
263 let writer = WritableFile {
264 content,
265 destination: path.to_string(),
266 fs: self.handle.clone(),
267 };
268 Ok(Box::new(writer))
269 }
270
271 fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
272 self.handle
273 .files
274 .peek_with(path, |_, file| VfsMetadata {
275 file_type: file.file_type,
276 len: file.content.len() as u64,
277 modified: file.modified,
278 created: file.created,
279 accessed: file.accessed,
280 })
281 .ok_or(VfsErrorKind::FileNotFound.into())
282 }
283
284 fn set_creation_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
285 let updated = self
286 .handle
287 .files
288 .peek_with(path, |_, file| MemoryFile {
289 created: Some(time),
290 ..file.clone()
291 })
292 .ok_or(VfsErrorKind::FileNotFound)?;
293
294 self.handle.files.remove_sync(path);
295 let _ = self.handle.files.insert_sync(path.to_string(), updated);
296 Ok(())
297 }
298
299 fn set_modification_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
300 let updated = self
301 .handle
302 .files
303 .peek_with(path, |_, file| MemoryFile {
304 modified: Some(time),
305 ..file.clone()
306 })
307 .ok_or(VfsErrorKind::FileNotFound)?;
308
309 self.handle.files.remove_sync(path);
310 let _ = self.handle.files.insert_sync(path.to_string(), updated);
311 Ok(())
312 }
313
314 fn set_access_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
315 let updated = self
316 .handle
317 .files
318 .peek_with(path, |_, file| MemoryFile {
319 accessed: Some(time),
320 ..file.clone()
321 })
322 .ok_or(VfsErrorKind::FileNotFound)?;
323
324 self.handle.files.remove_sync(path);
325 let _ = self.handle.files.insert_sync(path.to_string(), updated);
326 Ok(())
327 }
328
329 fn exists(&self, path: &str) -> VfsResult<bool> {
330 Ok(self.handle.files.contains(path))
331 }
332
333 fn remove_file(&self, path: &str) -> VfsResult<()> {
334 self.handle
335 .files
336 .remove_sync(path)
337 .then_some(Ok(()))
338 .unwrap_or_else(|| Err(VfsErrorKind::FileNotFound.into()))
339 }
340
341 fn remove_dir(&self, path: &str) -> VfsResult<()> {
342 if self.read_dir(path)?.next().is_some() {
343 return Err(VfsErrorKind::Other("Directory to remove is not empty".into()).into());
344 }
345 self.handle
346 .files
347 .remove_sync(path)
348 .then_some(Ok(()))
349 .unwrap_or_else(|| Err(VfsErrorKind::FileNotFound.into()))
350 }
351}
352
353struct MemoryFsImpl {
354 files: scc::TreeIndex<String, MemoryFile>,
355}
356
357impl MemoryFsImpl {
358 pub fn new() -> Self {
359 let files = scc::TreeIndex::new();
360 // Add root directory
361 let _ = files.insert_sync(
362 "".to_string(),
363 MemoryFile {
364 file_type: VfsFileType::Directory,
365 content: Arc::new(vec![]),
366 created: current_time(),
367 modified: None,
368 accessed: None,
369 },
370 );
371 Self { files }
372 }
373}
374
375#[derive(Clone)]
376struct MemoryFile {
377 file_type: VfsFileType,
378 content: Arc<Vec<u8>>,
379
380 created: Option<SystemTime>,
381 modified: Option<SystemTime>,
382 accessed: Option<SystemTime>,
383}
384
385fn ensure_file(file: &MemoryFile) -> VfsResult<()> {
386 if file.file_type != VfsFileType::File {
387 return Err(VfsErrorKind::Other("Not a file".into()).into());
388 }
389 Ok(())
390}