victor_db/filesystem/
memory.rs

1//! "in-memory" filesystem for use in tests or when persistence isn't necessary
2
3use std::{cell::RefCell, collections::HashMap, rc::Rc};
4
5use async_trait::async_trait;
6
7use crate::filesystem;
8
9/// An entry in a virtual directory in the in-memory filesystem.
10#[derive(Debug, Clone)]
11pub enum DirectoryEntry {
12    #[allow(dead_code)]
13    Directory(DirectoryHandle),
14    File(FileHandle),
15}
16
17/// A virtual directory in the in-memory filesystem.
18#[derive(Debug, Clone)]
19pub struct DirectoryHandle(Rc<RefCell<HashMap<String, DirectoryEntry>>>);
20
21/// A virtual file in the in-memory filesystem.
22#[derive(Debug, Clone)]
23pub struct FileHandle(WritableFileStream);
24
25/// A writable file stream in the in-memory filesystem.
26#[derive(Debug, Clone)]
27pub struct WritableFileStream {
28    cursor_pos: usize,
29    stream: Rc<RefCell<Vec<u8>>>,
30}
31
32#[async_trait(?Send)]
33impl filesystem::DirectoryHandle for DirectoryHandle {
34    type Error = String;
35    type FileHandleT = FileHandle;
36
37    async fn get_file_handle_with_options(
38        &self,
39        name: &str,
40        options: &filesystem::GetFileHandleOptions,
41    ) -> Result<Self::FileHandleT, Self::Error> {
42        let mut directory = self.0.borrow_mut();
43        let entry = match directory.entry(name.to_string()) {
44            std::collections::hash_map::Entry::Occupied(entry) => entry.get().clone(),
45            std::collections::hash_map::Entry::Vacant(entry) => {
46                if options.create {
47                    let file_handle = FileHandle::new();
48                    entry.insert(DirectoryEntry::File(file_handle.clone()));
49                    DirectoryEntry::File(file_handle)
50                } else {
51                    return Err(format!("'{name}' does not exist"));
52                }
53            }
54        };
55
56        match entry {
57            DirectoryEntry::Directory(_) => Err(format!("'{name}' is a directory")),
58            DirectoryEntry::File(file) => Ok(file),
59        }
60    }
61
62    async fn remove_entry(&mut self, name: &str) -> Result<(), Self::Error> {
63        let mut directory = self.0.borrow_mut();
64        directory.remove(name);
65        Ok(())
66    }
67}
68impl Default for DirectoryHandle {
69    fn default() -> Self {
70        Self(Rc::new(RefCell::new(HashMap::new())))
71    }
72}
73
74#[async_trait(?Send)]
75impl filesystem::FileHandle for FileHandle {
76    type Error = String;
77    type WritableFileStreamT = WritableFileStream;
78
79    async fn create_writable_with_options(
80        &mut self,
81        options: &filesystem::CreateWritableOptions,
82    ) -> Result<Self::WritableFileStreamT, Self::Error> {
83        if !options.keep_existing_data {
84            self.0.stream.borrow_mut().clear();
85        }
86        Ok(WritableFileStream {
87            cursor_pos: 0,
88            ..self.0.clone()
89        })
90    }
91
92    async fn read(&self) -> Result<Vec<u8>, Self::Error> {
93        let stream = self.0.stream.clone();
94        let data = stream.borrow().clone();
95        Ok(data)
96    }
97
98    async fn size(&self) -> Result<usize, Self::Error> {
99        Ok(self.0.len())
100    }
101}
102
103#[async_trait(?Send)]
104impl filesystem::WritableFileStream for WritableFileStream {
105    type Error = String;
106
107    async fn write_at_cursor_pos(&mut self, data: Vec<u8>) -> Result<(), Self::Error> {
108        let data_len = data.len();
109
110        let mut stream = self.stream.borrow_mut();
111        *stream = stream[0..self.cursor_pos]
112            .iter()
113            .cloned()
114            .chain(data)
115            .collect::<Vec<u8>>();
116
117        self.cursor_pos += data_len;
118
119        Ok(())
120    }
121
122    async fn close(&mut self) -> Result<(), Self::Error> {
123        // no op
124        Ok(())
125    }
126
127    async fn seek(&mut self, offset: usize) -> Result<(), Self::Error> {
128        if offset > self.len() {
129            return Err(format!(
130                "cannot seek to {offset} because the file is only {len} bytes long",
131                len = self.len()
132            ));
133        }
134        self.cursor_pos = offset;
135        Ok(())
136    }
137}
138
139impl FileHandle {
140    fn new() -> Self {
141        Self(WritableFileStream::new())
142    }
143}
144
145impl WritableFileStream {
146    fn new() -> Self {
147        Self {
148            cursor_pos: 0,
149            stream: Rc::new(RefCell::new(Vec::new())),
150        }
151    }
152
153    fn len(&self) -> usize {
154        self.stream.borrow().len()
155    }
156}