virtual_filesystem/
mountable_fs.rs1use crate::file::{DirEntry, File, Metadata, OpenOptions};
2use crate::tree::{normalize_and_relativize, Entry, FilesystemTree};
3use crate::util::{already_exists, invalid_path, not_found, not_supported};
4use crate::FileSystem;
5use itertools::Itertools;
6use std::collections::hash_map;
7use std::ffi::OsStr;
8use std::path::Path;
9
10type FS = Box<dyn FileSystem + Send + Sync>;
11
12#[derive(Default)]
14pub struct MountableFS {
15 inner: FilesystemTree<FS>,
16}
17
18impl MountableFS {
19 pub fn mount<P: AsRef<Path>>(
25 &self,
26 path: P,
27 fs: Box<dyn FileSystem + Send + Sync>,
28 ) -> crate::Result<()> {
29 let normalized_path = normalize_and_relativize(path);
31 let parent_path = normalized_path.parent().ok_or_else(invalid_path)?;
32 let child_path = normalized_path
33 .file_name()
34 .and_then(OsStr::to_str)
35 .ok_or_else(invalid_path)?;
36
37 self.inner.create_dir_all(parent_path, |dir| {
39 if let hash_map::Entry::Vacant(vac) = dir.entry(child_path.to_owned()) {
40 vac.insert(Entry::UserData(fs));
41 Ok(())
42 } else {
43 Err(already_exists())
44 }
45 })??;
46
47 Ok(())
48 }
49}
50
51impl<'a> FromIterator<(&'a str, Box<dyn FileSystem + Send + Sync>)> for MountableFS {
52 fn from_iter<T: IntoIterator<Item = (&'a str, Box<dyn FileSystem + Send + Sync>)>>(
53 iter: T,
54 ) -> Self {
55 let mountable_fs = Self::default();
56 for (path, fs) in iter {
57 mountable_fs.mount(path, fs).unwrap();
58 }
59 mountable_fs
60 }
61}
62
63impl FileSystem for MountableFS {
64 fn create_dir(&self, _path: &str) -> crate::Result<()> {
65 Err(not_supported())
66 }
67
68 fn metadata(&self, path: &str) -> crate::Result<Metadata> {
69 self.inner.with_entry(path, |maybe_directory| {
70 match maybe_directory {
71 Ok(_dir) => Ok(Metadata::directory()),
72 Err((fs, remaining_path)) => {
73 if remaining_path.as_os_str().is_empty() {
74 Ok(Metadata::directory())
76 } else {
77 fs.metadata(remaining_path.to_str().unwrap())
79 }
80 }
81 }
82 })
83 }
84
85 fn open_file_options(&self, path: &str, options: &OpenOptions) -> crate::Result<Box<dyn File>> {
86 self.inner.with_entry(path, |maybe_directory| {
87 maybe_directory
88 .err()
89 .map(|(fs, remaining_path)| {
90 fs.open_file_options(remaining_path.to_str().unwrap(), options)
92 })
93 .ok_or_else(not_found)
94 })?
95 }
96
97 fn read_dir(
98 &self,
99 path: &str,
100 ) -> crate::Result<Box<dyn Iterator<Item = crate::Result<DirEntry>>>> {
101 self.inner
102 .with_entry(path, |maybe_entry| match maybe_entry {
103 Ok(dir) => {
104 let entries = dir.keys().map(|path| {
106 Ok(DirEntry {
108 path: path.into(),
109 metadata: Metadata::directory(),
110 })
111 })
112 .collect_vec();
113
114 Ok::<Box<dyn Iterator<Item = crate::Result<DirEntry>>>, _>(Box::new(
115 entries.into_iter(),
116 ))
117 }
118 Err((fs, remaining_path)) => {
119 fs.read_dir(remaining_path.to_str().unwrap())
121 }
122 })
123 }
124
125 fn remove_dir(&self, _path: &str) -> crate::Result<()> {
126 Err(not_supported())
127 }
128
129 fn remove_file(&self, _path: &str) -> crate::Result<()> {
130 Err(not_supported())
131 }
132}
133
134#[cfg(test)]
135mod test {
136 use crate::file::Metadata;
137 use crate::memory_fs::MemoryFS;
138 use crate::mountable_fs::MountableFS;
139 use crate::util::test::read_directory;
140 use crate::{FileSystem, MockFileSystem};
141 use std::io::Write;
142
143 const TEST_PATHS: [&str; 4] = [
144 "test/abc",
145 "/test/abc",
146 "./test//abc",
147 "//test\\def//../abc",
148 ];
149
150 #[test]
151 fn mount() {
152 for mount_point in TEST_PATHS {
153 let fs = MountableFS::default();
154 assert!(!fs.exists("test/abc").unwrap());
155
156 fs.mount(mount_point, Box::new(MockFileSystem::new()))
157 .unwrap();
158 assert!(fs.exists("test/abc").unwrap());
159 }
160 }
161
162 #[test]
163 fn double_mount() {
164 for mount_point in TEST_PATHS {
165 let fs = MountableFS::default();
166 fs.mount(mount_point, Box::new(MockFileSystem::new()))
167 .unwrap();
168 assert!(fs
169 .mount(mount_point, Box::new(MockFileSystem::new()))
170 .is_err())
171 }
172 }
173
174 fn mounted_fs() -> MountableFS {
175 let fs = MountableFS::default();
176
177 let memory_fs = MemoryFS::default();
178 write!(memory_fs.create_file("abc").unwrap(), "file").unwrap();
179 memory_fs.create_dir_all("folder/and/it").unwrap();
180 fs.mount("test", Box::new(memory_fs)).unwrap();
181
182 fs
183 }
184
185 #[test]
186 fn metadata() {
187 let fs = mounted_fs();
188
189 for path in TEST_PATHS {
190 assert_eq!(fs.metadata(path).unwrap(), Metadata::file(4));
191 }
192
193 assert_eq!(fs.metadata("test/folder").unwrap(), Metadata::directory());
194 }
195
196 #[test]
197 fn open_file() {
198 let fs = mounted_fs();
199
200 for path in TEST_PATHS {
201 assert_eq!(
202 fs.open_file(path).unwrap().read_into_string().unwrap(),
203 "file"
204 );
205 }
206
207 assert!(fs.open_file("folder").is_err());
208 }
209
210 #[test]
211 fn read_dir() {
212 let fs = mounted_fs();
213
214 for path in ["/", "//", "", ".", "./", "test/something/else/../../../"] {
215 let dir = read_directory(&fs, path);
216 itertools::assert_equal(dir.keys(), vec!["test"]);
217 itertools::assert_equal(dir.values(), vec![&Metadata::directory()])
218 }
219
220 for path in ["/test", "./test/", "\\test/\\", "test/../test//"] {
221 let dir = read_directory(&fs, path);
222 itertools::assert_equal(dir.keys(), vec!["abc", "folder"]);
223 itertools::assert_equal(
224 dir.values(),
225 vec![&Metadata::file(4), &Metadata::directory()],
226 )
227 }
228 }
229
230 #[test]
231 fn exists() {
232 let fs = mounted_fs();
233
234 for path in ["/", "//", "", ".", "./", "test/something/else/../../../"] {
235 assert!(fs.exists(path).unwrap());
236 }
237
238 for path in TEST_PATHS {
239 assert!(fs.exists(path).unwrap());
240 }
241
242 assert!(!fs.exists("nonsense").unwrap());
243 assert!(!fs.exists("test/nonsense").unwrap());
244 assert!(fs.exists("test/folder").unwrap());
245 assert!(fs.exists("test/folder/and/").unwrap());
246 }
247}