virtual_filesystem/memory_fs/
mod.rs1mod entry;
2mod file;
3
4use crate::file::{DirEntry, Metadata, OpenOptions};
5use crate::memory_fs::file::{FileHandle, FileMode};
6use crate::tree::{Directory, Entry, FilesystemTree};
7use crate::util::{already_exists, invalid_path, not_found};
8use crate::FileSystem;
9use itertools::Itertools;
10use parking_lot::Mutex;
11use std::collections::{hash_map, HashMap};
12use std::ffi::OsStr;
13use std::path::Path;
14use std::sync::Arc;
15
16type File = Arc<Mutex<Vec<u8>>>;
18
19#[derive(Default)]
21pub struct MemoryFS {
22 inner: FilesystemTree<File>,
23}
24
25impl MemoryFS {
26 fn with_parent_and_child_name<R, P: AsRef<Path>, F: FnOnce(&mut Directory<File>, &str) -> R>(
27 &self,
28 path: P,
29 f: F,
30 ) -> crate::Result<R> {
31 let parent_directory = path.as_ref().parent().ok_or_else(invalid_path)?;
32 let child_name = path
33 .as_ref()
34 .file_name()
35 .and_then(OsStr::to_str)
36 .ok_or_else(invalid_path)?;
37
38 self.inner
40 .with_directory(parent_directory, |dir| f(dir, child_name))
41 }
42}
43
44impl FileSystem for MemoryFS {
45 fn create_dir(&self, path: &str) -> crate::Result<()> {
46 self.with_parent_and_child_name(path, |dir, directory_name| {
48 match dir.entry(directory_name.to_owned()) {
49 hash_map::Entry::Vacant(vac) => {
50 vac.insert(Entry::Directory(HashMap::default()));
51 Ok(())
52 }
53 _ => Err(already_exists()),
54 }
55 })?
56 }
57
58 fn metadata(&self, path: &str) -> crate::Result<Metadata> {
59 self.with_parent_and_child_name(path, |dir, file_name| match dir.get(file_name) {
61 Some(Entry::Directory(_)) => Ok(Metadata::directory()),
62 Some(Entry::UserData(file)) => Ok(Metadata::file(file.lock().len() as u64)),
63 None => Err(not_found()),
64 })?
65 }
66
67 fn open_file_options(
68 &self,
69 path: &str,
70 options: &OpenOptions,
71 ) -> crate::Result<Box<dyn crate::File>> {
72 let mut file = self.with_parent_and_child_name(path, |dir, file_name| {
74 let file = match dir.entry(file_name.to_owned()) {
75 hash_map::Entry::Occupied(entry) => {
76 if let Entry::UserData(file) = entry.get() {
78 file.clone()
79 } else {
80 return Err(not_found());
81 }
82 }
83 hash_map::Entry::Vacant(vacant) => {
84 if options.create {
85 let file = File::new(Mutex::default());
87 vacant.insert(Entry::UserData(file.clone()));
88 file
89 } else {
90 return Err(not_found());
91 }
92 }
93 };
94
95 let mode = FileMode::from_options(options);
96 Ok(FileHandle::new(file, mode))
97 })??;
98
99 if options.truncate {
101 file.clear();
102 }
103
104 Ok(Box::new(file))
105 }
106
107 fn read_dir(
108 &self,
109 path: &str,
110 ) -> crate::Result<Box<dyn Iterator<Item = crate::Result<DirEntry>>>> {
111 self.inner.with_directory(path, |dir| {
112 let iter: Box<dyn Iterator<Item = crate::Result<DirEntry>>> = Box::new(
113 dir.iter()
114 .map(|(name, entry)| {
115 Ok(DirEntry {
116 path: name.into(),
117 metadata: entry.into(),
118 })
119 })
120 .collect_vec()
121 .into_iter(),
122 );
123 iter
124 })
125 }
126
127 fn remove_dir(&self, path: &str) -> crate::Result<()> {
128 self.with_parent_and_child_name(path, |parent, dir| match parent.entry(dir.to_owned()) {
129 hash_map::Entry::Occupied(occ) if matches!(occ.get(), Entry::Directory(_)) => {
130 occ.remove();
131 Ok(())
132 }
133 _ => Err(not_found()),
134 })?
135 }
136
137 fn remove_file(&self, path: &str) -> crate::Result<()> {
138 self.with_parent_and_child_name(path, |parent, dir| match parent.entry(dir.to_owned()) {
139 hash_map::Entry::Occupied(occ) if matches!(occ.get(), Entry::UserData(_)) => {
140 occ.remove();
141 Ok(())
142 }
143 _ => Err(not_found()),
144 })?
145 }
146
147 fn create_dir_all(&self, path: &str) -> crate::Result<()> {
148 self.inner.create_dir_all(path, |_| ())
149 }
150}
151
152#[cfg(test)]
153mod test {
154 use crate::file::{FileType, Metadata};
155 use crate::memory_fs::MemoryFS;
156 use crate::FileSystem;
157 use std::collections::BTreeMap;
158 use std::io::Write;
159
160 fn memory_fs() -> MemoryFS {
161 let fs = MemoryFS::default();
162
163 write!(fs.create_file("file").unwrap(), "something interesting").unwrap();
164 fs.create_dir_all("folder/and/it/goes/deeper").unwrap();
165 write!(fs.create_file("folder/and/it/goes/desc").unwrap(), "goes").unwrap();
166
167 fs
168 }
169
170 fn read_directory(fs: &MemoryFS, dir: &str) -> BTreeMap<String, Metadata> {
171 fs.read_dir(dir)
172 .unwrap()
173 .map(|entry| {
174 let entry = entry.unwrap();
175 (entry.path.to_str().unwrap().to_owned(), entry.metadata)
176 })
177 .collect()
178 }
179
180 #[test]
181 fn create_file() {
182 memory_fs();
183 }
184
185 #[test]
186 fn metadata() {
187 let fs = memory_fs();
188
189 for name in ["file", "/file", "./file", "test/../file"] {
191 let md = fs.metadata(name).unwrap();
192 assert_eq!(md.file_type, FileType::File);
193 assert_eq!(md.len, 21);
194 }
195
196 for name in ["folder", "/folder", "./folder", "test/../folder"] {
198 let md = fs.metadata(name).unwrap();
199 assert_eq!(md.file_type, FileType::Directory);
200 assert_eq!(md.len, 0);
201 }
202
203 for name in [
205 "folder/and/it/goes/desc",
206 "/folder/and/it/goes/desc",
207 "./folder/and/it/goes/desc",
208 "test/../folder/and/it/goes/desc",
209 ] {
210 let md = fs.metadata(name).unwrap();
211 assert_eq!(md.file_type, FileType::File);
212 assert_eq!(md.len, 4);
213 }
214 }
215
216 #[test]
217 fn read_dir() {
218 let fs = memory_fs();
219
220 for name in ["", "/", "./", "//", "\\"] {
222 let files = read_directory(&fs, name);
223 itertools::assert_equal(files.keys(), vec!["file", "folder"]);
224 itertools::assert_equal(
225 files.values(),
226 vec![&Metadata::file(21), &Metadata::directory()],
227 )
228 }
229
230 for name in [
232 "folder/and/it/goes",
233 "/folder/and/it/goes",
234 "./folder/and/it/goes/",
235 "///folder/and/it/goes///",
236 "\\folder\\and\\it\\goes\\",
237 ] {
238 let files = read_directory(&fs, name);
239 itertools::assert_equal(files.keys(), vec!["deeper", "desc"]);
240 itertools::assert_equal(
241 files.values(),
242 vec![&Metadata::directory(), &Metadata::file(4)],
243 )
244 }
245
246 for name in [
248 "folder/and/../..",
249 "./folder/and/../..",
250 ".//folder/and//../..",
251 "\\folder//../folder/and/../..",
252 ] {
253 let files = read_directory(&fs, name);
254 itertools::assert_equal(files.keys(), vec!["file", "folder"]);
255 itertools::assert_equal(
256 files.values(),
257 vec![&Metadata::file(21), &Metadata::directory()],
258 )
259 }
260 }
261
262 #[test]
263 fn remove_dir() {
264 let fs = memory_fs();
265
266 assert!(fs.exists("folder/and/it/goes").unwrap());
267 fs.remove_dir("folder/and/it").unwrap();
268 assert!(!fs.exists("folder/and/it/goes").unwrap());
269 assert!(!fs.exists("/folder/and/it").unwrap());
270 assert!(!fs.exists("/folder/and/it/goes/desc").unwrap());
271 }
272
273 #[test]
274 fn remove_file() {
275 let fs = memory_fs();
276
277 assert!(fs.exists("folder/and/it/goes/desc").unwrap());
278 fs.remove_file("folder/and/it/goes/desc").unwrap();
279 assert!(fs.exists("folder/and/it/goes/deeper").unwrap());
280 assert!(!fs.exists("folder/and/it/goes/desc").unwrap());
281 }
282}