1use crate::async_vfs::{AsyncFileSystem, SeekAndRead};
3use crate::error::VfsErrorKind;
4use crate::path::VfsFileType;
5use crate::{VfsMetadata, VfsResult};
6
7use async_std::io::{prelude::SeekExt, Cursor, Read, Seek, SeekFrom, Write};
8use async_std::sync::{Arc, RwLock};
9use async_trait::async_trait;
10use futures::task::{Context, Poll};
11use futures::{Stream, StreamExt};
12use std::collections::hash_map::Entry;
13use std::collections::HashMap;
14use std::fmt;
15use std::fmt::{Debug, Formatter};
16use std::mem::swap;
17use std::pin::Pin;
18
19type AsyncMemoryFsHandle = Arc<RwLock<AsyncMemoryFsImpl>>;
20
21pub struct AsyncMemoryFS {
23 handle: AsyncMemoryFsHandle,
24}
25
26impl Debug for AsyncMemoryFS {
27 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
28 f.write_str("In Memory File System")
29 }
30}
31
32impl AsyncMemoryFS {
33 pub fn new() -> Self {
35 AsyncMemoryFS {
36 handle: Arc::new(RwLock::new(AsyncMemoryFsImpl::new())),
37 }
38 }
39
40 async fn ensure_has_parent(&self, path: &str) -> VfsResult<()> {
41 let separator = path.rfind('/');
42 if let Some(index) = separator {
43 if self.exists(&path[..index]).await? {
44 return Ok(());
45 }
46 }
47 Err(VfsErrorKind::Other("Parent path does not exist".into()).into())
48 }
49}
50
51impl Default for AsyncMemoryFS {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57struct AsyncWritableFile {
58 content: Cursor<Vec<u8>>,
59 destination: String,
60 fs: AsyncMemoryFsHandle,
61}
62
63impl Write for AsyncWritableFile {
64 fn poll_write(
65 self: Pin<&mut Self>,
66 cx: &mut Context<'_>,
67 buf: &[u8],
68 ) -> Poll<Result<usize, async_std::io::Error>> {
69 let this = self.get_mut();
70 let file = Pin::new(&mut this.content);
71 file.poll_write(cx, buf)
72 }
73 fn poll_flush(
75 self: Pin<&mut Self>,
76 cx: &mut Context<'_>,
77 ) -> Poll<Result<(), async_std::io::Error>> {
78 let this = self.get_mut();
79 let file = Pin::new(&mut this.content);
80 file.poll_flush(cx)
81 }
82 fn poll_close(
83 self: Pin<&mut Self>,
84 cx: &mut Context<'_>,
85 ) -> Poll<Result<(), async_std::io::Error>> {
86 let this = self.get_mut();
87 let file = Pin::new(&mut this.content);
88 file.poll_close(cx)
89 }
90}
91
92impl Drop for AsyncWritableFile {
93 fn drop(&mut self) {
94 let mut content = vec![];
95 swap(&mut content, self.content.get_mut());
96 futures::executor::block_on(self.fs.write()).files.insert(
97 self.destination.clone(),
98 AsyncMemoryFile {
99 file_type: VfsFileType::File,
100 content: Arc::new(content),
101 },
102 );
103 }
104}
105
106struct AsyncReadableFile {
107 #[allow(clippy::rc_buffer)] content: Arc<Vec<u8>>,
109 cursor_pos: u64,
111}
112
113impl AsyncReadableFile {
114 fn len(&self) -> u64 {
115 self.content.len() as u64
116 }
117}
118
119impl Read for AsyncReadableFile {
120 fn poll_read(
121 self: Pin<&mut Self>,
122 _cx: &mut Context<'_>,
123 buf: &mut [u8],
124 ) -> Poll<Result<usize, async_std::io::Error>> {
125 let this = self.get_mut();
126 let bytes_left = this.len() - this.cursor_pos;
127 let bytes_read = std::cmp::min(buf.len() as u64, bytes_left);
128 if bytes_left == 0 {
129 return Poll::Ready(Ok(0));
130 }
131 buf[..bytes_read as usize].copy_from_slice(
132 &this.content[this.cursor_pos as usize..(this.cursor_pos + bytes_read) as usize],
133 );
134 this.cursor_pos += bytes_read;
135 Poll::Ready(Ok(bytes_read as usize))
136 }
137}
138
139impl Seek for AsyncReadableFile {
140 fn poll_seek(
141 self: Pin<&mut Self>,
142 _cx: &mut Context<'_>,
143 pos: SeekFrom,
144 ) -> Poll<Result<u64, async_std::io::Error>> {
145 let this = self.get_mut();
146 let new_pos = match pos {
147 SeekFrom::Start(offset) => offset as i64,
148 SeekFrom::End(offset) => this.cursor_pos as i64 - offset,
149 SeekFrom::Current(offset) => this.cursor_pos as i64 + offset,
150 };
151 if new_pos < 0 || new_pos >= this.len() as i64 {
152 Poll::Ready(Err(async_std::io::Error::new(
153 async_std::io::ErrorKind::InvalidData,
154 "Requested offset is outside the file!",
155 )))
156 } else {
157 this.cursor_pos = new_pos as u64;
158 Poll::Ready(Ok(new_pos as u64))
159 }
160 }
161}
162
163#[async_trait]
164impl AsyncFileSystem for AsyncMemoryFS {
165 async fn read_dir(
166 &self,
167 path: &str,
168 ) -> VfsResult<Box<dyn Unpin + Stream<Item = String> + Send>> {
169 let prefix = format!("{}/", path);
170 let handle = self.handle.read().await;
171 let mut found_directory = false;
172 #[allow(clippy::needless_collect)] let entries: Vec<String> = handle
174 .files
175 .iter()
176 .filter_map(|(candidate_path, _)| {
177 if candidate_path == path {
178 found_directory = true;
179 }
180 if candidate_path.starts_with(&prefix) {
181 let rest = &candidate_path[prefix.len()..];
182 if !rest.contains('/') {
183 return Some(rest.to_string());
184 }
185 }
186 None
187 })
188 .collect();
189 if !found_directory {
190 return Err(VfsErrorKind::FileNotFound.into());
191 }
192 Ok(Box::new(futures::stream::iter(entries)))
193 }
194
195 async fn create_dir(&self, path: &str) -> VfsResult<()> {
196 self.ensure_has_parent(path).await?;
197 let map = &mut self.handle.write().await.files;
198 let entry = map.entry(path.to_string());
199 match entry {
200 Entry::Occupied(file) => {
201 return match file.get().file_type {
202 VfsFileType::File => Err(VfsErrorKind::FileExists.into()),
203 VfsFileType::Directory => Err(VfsErrorKind::DirectoryExists.into()),
204 }
205 }
206 Entry::Vacant(_) => {
207 map.insert(
208 path.to_string(),
209 AsyncMemoryFile {
210 file_type: VfsFileType::Directory,
211 content: Default::default(),
212 },
213 );
214 }
215 }
216 Ok(())
217 }
218
219 async fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead + Send + Unpin>> {
220 let handle = self.handle.read().await;
221 let file = handle.files.get(path).ok_or(VfsErrorKind::FileNotFound)?;
222 ensure_file(file)?;
223 Ok(Box::new(AsyncReadableFile {
224 content: file.content.clone(),
225 cursor_pos: 0,
226 }))
227 }
228
229 async fn create_file(&self, path: &str) -> VfsResult<Box<dyn Write + Send + Unpin>> {
230 self.ensure_has_parent(path).await?;
231 let content = Arc::new(Vec::<u8>::new());
232 self.handle.write().await.files.insert(
233 path.to_string(),
234 AsyncMemoryFile {
235 file_type: VfsFileType::File,
236 content,
237 },
238 );
239 let writer = AsyncWritableFile {
240 content: Cursor::new(vec![]),
241 destination: path.to_string(),
242 fs: self.handle.clone(),
243 };
244 Ok(Box::new(writer))
245 }
246
247 async fn append_file(&self, path: &str) -> VfsResult<Box<dyn Write + Send + Unpin>> {
248 let handle = self.handle.write().await;
249 let file = handle.files.get(path).ok_or(VfsErrorKind::FileNotFound)?;
250 let mut content = Cursor::new(file.content.as_ref().clone());
251 content.seek(SeekFrom::End(0)).await?;
252 let writer = AsyncWritableFile {
253 content,
254 destination: path.to_string(),
255 fs: self.handle.clone(),
256 };
257 Ok(Box::new(writer))
258 }
259
260 async fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
261 let guard = self.handle.read().await;
262 let files = &guard.files;
263 let file = files.get(path).ok_or(VfsErrorKind::FileNotFound)?;
264 Ok(VfsMetadata {
265 file_type: file.file_type,
266 len: file.content.len() as u64,
267 modified: None,
268 created: None,
269 accessed: None,
270 })
271 }
272
273 async fn exists(&self, path: &str) -> VfsResult<bool> {
274 Ok(self.handle.read().await.files.contains_key(path))
275 }
276
277 async fn remove_file(&self, path: &str) -> VfsResult<()> {
278 let mut handle = self.handle.write().await;
279 handle
280 .files
281 .remove(path)
282 .ok_or(VfsErrorKind::FileNotFound)?;
283 Ok(())
284 }
285
286 async fn remove_dir(&self, path: &str) -> VfsResult<()> {
287 if self.read_dir(path).await?.next().await.is_some() {
288 return Err(VfsErrorKind::Other("Directory to remove is not empty".into()).into());
289 }
290 let mut handle = self.handle.write().await;
291 handle
292 .files
293 .remove(path)
294 .ok_or(VfsErrorKind::FileNotFound)?;
295 Ok(())
296 }
297}
298
299#[derive(Debug)]
300struct AsyncMemoryFsImpl {
301 files: HashMap<String, AsyncMemoryFile>,
302}
303
304impl AsyncMemoryFsImpl {
305 pub fn new() -> Self {
306 let mut files = HashMap::new();
307 files.insert(
309 "".to_string(),
310 AsyncMemoryFile {
311 file_type: VfsFileType::Directory,
312 content: Arc::new(vec![]),
313 },
314 );
315 Self { files }
316 }
317}
318
319#[derive(Debug)]
320struct AsyncMemoryFile {
321 file_type: VfsFileType,
322 #[allow(clippy::rc_buffer)] content: Arc<Vec<u8>>,
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 use crate::async_vfs::AsyncVfsPath;
330 use async_std::io::{ReadExt, WriteExt};
331 test_async_vfs!(AsyncMemoryFS::new());
332
333 #[tokio::test]
334 async fn write_and_read_file() -> VfsResult<()> {
335 let root = AsyncVfsPath::new(AsyncMemoryFS::new());
336 let path = root.join("foobar.txt").unwrap();
337 let _send = &path as &dyn Send;
338 {
339 let mut file = path.create_file().await.unwrap();
340 write!(file, "Hello world").await.unwrap();
341 write!(file, "!").await.unwrap();
342 }
343 {
344 let mut file = path.open_file().await.unwrap();
345 let mut string: String = String::new();
346 file.read_to_string(&mut string).await.unwrap();
347 assert_eq!(string, "Hello world!");
348 }
349 assert!(path.exists().await?);
350 assert!(!root.join("foo").unwrap().exists().await?);
351 let metadata = path.metadata().await.unwrap();
352 assert_eq!(metadata.len, 12);
353 assert_eq!(metadata.file_type, VfsFileType::File);
354 Ok(())
355 }
356
357 #[tokio::test]
358 async fn append_file() {
359 let root = AsyncVfsPath::new(AsyncMemoryFS::new());
360 let _string = String::new();
361 let path = root.join("test_append.txt").unwrap();
362 path.create_file()
363 .await
364 .unwrap()
365 .write_all(b"Testing 1")
366 .await
367 .unwrap();
368 path.append_file()
369 .await
370 .unwrap()
371 .write_all(b"Testing 2")
372 .await
373 .unwrap();
374 {
375 let mut file = path.open_file().await.unwrap();
376 let mut string: String = String::new();
377 file.read_to_string(&mut string).await.unwrap();
378 assert_eq!(string, "Testing 1Testing 2");
379 }
380 }
381
382 #[tokio::test]
383 async fn create_dir() {
384 let root = AsyncVfsPath::new(AsyncMemoryFS::new());
385 let _string = String::new();
386 let path = root.join("foo").unwrap();
387 path.create_dir().await.unwrap();
388 let metadata = path.metadata().await.unwrap();
389 assert_eq!(metadata.file_type, VfsFileType::Directory);
390 }
391
392 #[tokio::test]
393 async fn remove_dir_error_message() {
394 let root = AsyncVfsPath::new(AsyncMemoryFS::new());
395 let path = root.join("foo").unwrap();
396 let result = path.remove_dir().await;
397 assert_eq!(
398 format!("{}", result.unwrap_err()),
399 "Could not remove directory for '/foo': The file or directory could not be found"
400 );
401 }
402
403 #[tokio::test]
404 async fn read_dir_error_message() {
405 let root = AsyncVfsPath::new(AsyncMemoryFS::new());
406 let path = root.join("foo").unwrap();
407 let result = path.read_dir().await;
408 match result {
409 Ok(_) => panic!("Error expected"),
410 Err(err) => {
411 assert_eq!(
412 format!("{}", err),
413 "Could not read directory for '/foo': The file or directory could not be found"
414 );
415 }
416 }
417 }
418
419 #[tokio::test]
420 async fn copy_file_across_filesystems() -> VfsResult<()> {
421 let root_a = AsyncVfsPath::new(AsyncMemoryFS::new());
422 let root_b = AsyncVfsPath::new(AsyncMemoryFS::new());
423 let src = root_a.join("a.txt")?;
424 let dest = root_b.join("b.txt")?;
425 src.create_file().await?.write_all(b"Hello World").await?;
426 src.copy_file(&dest).await?;
427 assert_eq!(&dest.read_to_string().await?, "Hello World");
428 Ok(())
429 }
430}
431
432fn ensure_file(file: &AsyncMemoryFile) -> VfsResult<()> {
433 if file.file_type != VfsFileType::File {
434 return Err(VfsErrorKind::Other("Not a file".into()).into());
435 }
436 Ok(())
437}