1use std::{
2 collections::BTreeMap,
3 ffi::OsString,
4 fmt,
5 io::Cursor,
6 path::{Path, PathBuf},
7 sync::Arc,
8};
9
10use crate::{utils::PathUtil, Error, LogixVfs, LogixVfsDirEntry};
11
12#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13enum FileData {
14 Static(&'static [u8]),
15 Arc(Arc<[u8]>),
16}
17
18impl fmt::Debug for FileData {
19 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20 match self {
21 Self::Static(buf) => f.debug_struct("Static").field("size", &buf.len()).finish(),
22 Self::Arc(buf) => f.debug_struct("Arc").field("size", &buf.len()).finish(),
23 }
24 }
25}
26
27#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
28enum Entry {
29 #[default]
30 Empty,
31 File(FileData),
32 Dir(BTreeMap<OsString, Entry>),
33}
34
35#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
36pub struct MemFs {
37 root: Entry,
38}
39
40impl MemFs {
41 fn resolve_node_mut(&mut self, path: PathBuf, create_path: bool) -> Result<&mut Entry, Error> {
42 use std::path::Component;
43
44 let mut cur = &mut self.root;
45
46 for (i, component) in path.components().enumerate() {
47 match component {
48 Component::RootDir => (),
49 Component::Prefix(_) | Component::CurDir | Component::ParentDir => {
50 debug_assert!(false, "Should be unreachable ({path:?})");
51 return Err(Error::Other(format!(
52 "Internal error: path {path:?} is not canonicalized",
53 )));
54 }
55 Component::Normal(name) => 'retry_cur: loop {
56 match cur {
57 Entry::Empty => {
58 if create_path {
59 *cur = Entry::Dir([(name.to_owned(), Entry::Empty)].into());
60 continue 'retry_cur;
61 } else {
62 return Err(Error::NotFound { path });
63 }
64 }
65 Entry::File(_) => {
66 let dir: PathBuf = path.components().take(i).collect();
67 return Err(Error::Other(format!(
68 "Cannot create directory {dir:?} as it is a file for {path:?}"
69 )));
70 }
71 Entry::Dir(map) => cur = map.entry(name.to_owned()).or_default(),
72 }
73 break;
74 },
75 }
76 }
77
78 Ok(cur)
79 }
80
81 fn resolve_node(&self, path: PathBuf) -> Result<(PathBuf, &Entry), Error> {
82 use std::path::Component;
83
84 let mut cur = &self.root;
85
86 for (i, component) in path.components().enumerate() {
87 match component {
88 Component::RootDir => (),
89 Component::Prefix(_) | Component::CurDir | Component::ParentDir => {
90 debug_assert!(false, "Should be unreachable ({path:?})");
91 return Err(Error::Other(format!(
92 "Internal error: path {path:?} is not canonicalized",
93 )));
94 }
95 Component::Normal(name) => match cur {
96 Entry::Empty => return Err(Error::NotFound { path }),
97 Entry::File(_) => {
98 let dir: PathBuf = path.components().take(i).collect();
99 return Err(Error::NotADirectory { path: dir });
100 }
101 Entry::Dir(map) => {
102 if let Some(entry) = map.get(name) {
103 cur = entry;
104 } else {
105 return Err(Error::NotFound { path });
106 }
107 }
108 },
109 }
110 }
111
112 Ok((path, cur))
113 }
114
115 fn resolve_path(&self, path: impl AsRef<Path>) -> Result<PathBuf, Error> {
116 PathUtil {
117 root: "/".as_ref(),
118 cur_dir: "/".as_ref(),
119 }
120 .resolve_path(false, path.as_ref())
121 }
122
123 pub fn set_static_file(
124 &mut self,
125 path: impl AsRef<Path>,
126 data: &'static [u8],
127 create_dir: bool,
128 ) -> Result<(), Error> {
129 let path = path.as_ref();
130 let node = self.resolve_node_mut(self.resolve_path(path)?, create_dir)?;
131 match node {
132 Entry::Empty | Entry::File(_) => {
133 *node = Entry::File(FileData::Static(data));
134 Ok(())
135 }
136 Entry::Dir(_) => Err(Error::Other(format!(
137 "Can't overwrite directory with a file at {path:?}"
138 ))),
139 }
140 }
141
142 pub fn set_file(
143 &mut self,
144 path: impl AsRef<Path>,
145 data: impl Into<Arc<[u8]>>,
146 create_dir: bool,
147 ) -> Result<(), Error> {
148 let path = path.as_ref();
149 let node = self.resolve_node_mut(self.resolve_path(path)?, create_dir)?;
150 match node {
151 Entry::Empty | Entry::File(_) => {
152 *node = Entry::File(FileData::Arc(data.into()));
153 Ok(())
154 }
155 Entry::Dir(_) => Err(Error::Other(format!(
156 "Can't overwrite directory with a file at {path:?}"
157 ))),
158 }
159 }
160}
161
162#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
163pub struct MemFileData(FileData);
164
165impl AsRef<[u8]> for MemFileData {
166 fn as_ref(&self) -> &[u8] {
167 match &self.0 {
168 FileData::Static(buf) => buf,
169 FileData::Arc(buf) => buf,
170 }
171 }
172}
173
174pub type MemFile = Cursor<MemFileData>;
175
176#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
177enum DirEntryType {
178 File,
179 Dir,
180}
181
182#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
183pub struct DirEntry {
184 path: PathBuf,
185 ty: DirEntryType,
186}
187
188impl LogixVfsDirEntry for DirEntry {
189 fn path(&self) -> &Path {
190 &self.path
191 }
192
193 fn is_dir(&self) -> bool {
194 match self.ty {
195 DirEntryType::File => false,
196 DirEntryType::Dir => true,
197 }
198 }
199
200 fn is_file(&self) -> bool {
201 match self.ty {
202 DirEntryType::File => true,
203 DirEntryType::Dir => false,
204 }
205 }
206
207 fn is_symlink(&self) -> bool {
208 false
209 }
210}
211
212#[derive(Clone, Debug)]
213pub struct ReadDir {
214 it: std::vec::IntoIter<DirEntry>,
215}
216
217impl ReadDir {
218 fn new(base: &Path, map: &BTreeMap<OsString, Entry>) -> Self {
219 let list: Vec<_> = map
220 .iter()
221 .filter_map(|(k, v)| {
222 let ty = match v {
223 Entry::Empty => return None,
224 Entry::File(_) => DirEntryType::File,
225 Entry::Dir(_) => DirEntryType::Dir,
226 };
227
228 Some(DirEntry {
229 path: base.join(k),
230 ty,
231 })
232 })
233 .collect();
234 ReadDir {
235 it: list.into_iter(),
236 }
237 }
238}
239
240impl Iterator for ReadDir {
241 type Item = Result<DirEntry, Error>;
242
243 fn next(&mut self) -> Option<Self::Item> {
244 self.it.next().map(Ok)
245 }
246}
247
248impl LogixVfs for MemFs {
249 type RoFile = Cursor<MemFileData>;
250 type DirEntry = DirEntry;
251 type ReadDir = ReadDir;
252
253 fn canonicalize_path(&self, path: &Path) -> Result<PathBuf, crate::Error> {
254 self.resolve_path(path)
255 }
256
257 fn open_file(&self, path: &Path) -> Result<Self::RoFile, crate::Error> {
258 match self.resolve_node(self.resolve_path(path)?)? {
259 (_, Entry::Empty) => Err(Error::NotFound {
260 path: path.to_path_buf(),
261 }),
262 (_, Entry::File(data)) => Ok(Cursor::new(MemFileData(data.clone()))),
263 (_, Entry::Dir(_)) => Err(Error::Other(format!("The path {path:?} is not a file"))),
264 }
265 }
266
267 fn read_dir(&self, path: &Path) -> Result<Self::ReadDir, crate::Error> {
268 match self.resolve_node(self.resolve_path(path)?)? {
269 (_, Entry::Empty) => Err(Error::NotFound {
270 path: path.to_path_buf(),
271 }),
272 (path, Entry::File(_)) => Err(Error::NotADirectory { path }),
273 (path, Entry::Dir(map)) => Ok(ReadDir::new(&path, map)),
274 }
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn basics() {
284 let mut fs = MemFs::default();
285 let hello_rs = b"fn hello() -> i32 {{\n42}}\n".as_slice();
286 let world_rs = b"fn world() -> i32 {{\n1337}}\n".as_slice();
287
288 assert_eq!(
289 fs.set_static_file("/src/hello.rs", hello_rs, false)
290 .unwrap_err(),
291 Error::NotFound {
292 path: "/src/hello.rs".into()
293 }
294 );
295
296 fs.set_static_file("/src/hello.rs", hello_rs, true).unwrap();
297 fs.set_static_file("/src/world.rs", world_rs, true).unwrap();
298
299 assert_eq!(
300 fs.set_static_file("/src/hello.rs/world.rs", hello_rs, false)
301 .unwrap_err(),
302 Error::Other(
303 "Cannot create directory \"/src/hello.rs\" as it is a file for \"/src/hello.rs/world.rs\"".into()
304 )
305 );
306
307 assert_eq!(
308 fs.open_file("/src/hello.rs".as_ref()).unwrap().get_ref().0,
309 FileData::Static(hello_rs)
310 );
311
312 assert_eq!(
313 fs.open_file("/src".as_ref()).unwrap_err(),
314 Error::Other("The path \"/src\" is not a file".to_owned())
315 );
316
317 assert_eq!(
318 fs.open_file("/src/hello.rs/world.rs".as_ref()).unwrap_err(),
319 Error::NotADirectory {
320 path: "/src/hello.rs".into()
321 }
322 );
323
324 {
325 let mut it = fs.read_dir("/".as_ref()).unwrap();
326 let entry = it.next().unwrap().unwrap();
327 assert!(it.next().is_none());
328
329 assert_eq!(entry.path(), Path::new("/src"),);
330 assert!(entry.is_dir());
331 assert!(!entry.is_file());
332 }
333
334 {
335 let mut it = fs.read_dir("/src".as_ref()).unwrap();
336 let file1 = it.next().unwrap().unwrap();
337 let file2 = it.next().unwrap().unwrap();
338 assert!(it.next().is_none());
339
340 assert_eq!(file1.path(), Path::new("/src/hello.rs"),);
341 assert_eq!(file2.path(), Path::new("/src/world.rs"),);
342
343 assert!(!file1.is_dir());
344 assert!(!file2.is_dir());
345
346 assert!(file1.is_file());
347 assert!(file2.is_file());
348 }
349 }
350}