1#![deny(warnings)]
50use std::any::Any;
51use std::collections::LinkedList;
52use std::io::{Cursor, Error, ErrorKind, Read, Result, Seek, SeekFrom};
53use std::path::{Path, PathBuf};
54use std::rc::Rc;
55use std::{env, fs};
56
57pub use caseless::CaselessFs;
58pub use store::{Entries, Entry, EntryKind, Store, StoreExt};
60#[cfg(feature = "tar")]
61pub use tar::TarFs;
62#[cfg(feature = "zip")]
63pub use zip::ZipFs;
64
65include!("macros.rs");
66
67pub mod caseless;
68#[doc(hidden)]
70pub mod index;
71mod store;
72#[cfg(feature = "tar")]
74pub mod tar;
75#[cfg(feature = "zip")]
77pub mod zip;
78pub mod prelude {
80 pub use crate::store::{Store, StoreExt};
81}
82
83impl_file! {
84 pub enum File {
86 Local(fs::File),
87 Ram(RamFile),
88 #[cfg(feature = "zip")]
89 Zip(zip::ZipFsFile),
90 #[cfg(feature = "tar")]
91 Tar(tar::TarFsFile),
92 User(Box<dyn UserFile>),
94 }
95}
96
97pub trait UserFile: Any + Read + Seek + Send {}
99
100impl<T: UserFile> From<T> for File {
101 fn from(file: T) -> Self {
102 File::User(Box::new(file))
103 }
104}
105
106struct Mount {
107 path: PathBuf,
108 store: Box<dyn Store<File = File>>,
109}
110
111pub struct MiniFs {
113 mount: LinkedList<Mount>,
114}
115
116impl Store for MiniFs {
117 type File = File;
118
119 fn open_path(&self, path: &Path) -> Result<File> {
120 let next = self.mount.iter().rev().find_map(|mnt| {
121 if let Ok(np) = path.strip_prefix(&mnt.path) {
122 Some((np, &mnt.store))
123 } else {
124 None
125 }
126 });
127 if let Some((np, store)) = next {
128 store.open_path(np)
129 } else {
130 Err(Error::from(ErrorKind::NotFound))
131 }
132 }
133
134 fn entries_path(&self, path: &Path) -> Result<Entries> {
135 let path = path.to_path_buf();
137
138 Ok(Entries::new(
139 self.mount
140 .iter()
141 .rev()
142 .find(|m| path.strip_prefix(&m.path).is_ok())
143 .into_iter()
144 .flat_map(move |m| match path.strip_prefix(&m.path) {
145 Ok(np) => m.store.entries_path(np).unwrap(),
146 Err(_) => Entries::new(None),
147 }),
148 ))
149 }
150}
151
152impl MiniFs {
153 pub fn new() -> Self {
154 Self {
155 mount: LinkedList::new(),
156 }
157 }
158
159 pub fn mount<P, S, T>(mut self, path: P, store: S) -> Self
160 where
161 P: Into<PathBuf>,
162 S: Store<File = T> + 'static,
163 T: Into<File>,
164 {
165 let path = path.into();
166 let store = Box::new(store::MapFile::new(store, |file: T| file.into()));
167 self.mount.push_back(Mount { path, store });
168 self
169 }
170
171 pub fn umount<P>(&mut self, path: P) -> Option<Box<dyn Store<File = File>>>
172 where
173 P: AsRef<Path>,
174 {
175 let path = path.as_ref();
176 if let Some(p) = self.mount.iter().rposition(|p| p.path == path) {
177 let mut tail = self.mount.split_off(p);
178 let fs = tail.pop_front().map(|m| m.store);
179 self.mount.append(&mut tail);
180 fs
181 } else {
182 None
183 }
184 }
185}
186
187pub struct LocalFs {
189 root: PathBuf,
190}
191
192impl Store for LocalFs {
193 type File = fs::File;
194
195 fn open_path(&self, path: &Path) -> Result<fs::File> {
196 fs::OpenOptions::new()
197 .create(false)
198 .read(true)
199 .write(false)
200 .open(self.root.join(path))
201 }
202
203 fn entries_path(&self, path: &Path) -> Result<Entries> {
204 let entries = fs::read_dir(self.root.join(path))?.map(move |ent| {
208 let entry = ent?;
209 let path = entry
210 .path()
211 .strip_prefix(&self.root)
212 .map(Path::to_path_buf)
213 .expect("Error striping path suffix.");
214 let file_type = entry.file_type()?;
215
216 let kind = if file_type.is_dir() {
218 EntryKind::Dir
219 } else if file_type.is_symlink() {
220 EntryKind::File
221 } else {
222 EntryKind::File
223 };
224
225 Ok(Entry {
226 name: path.into_os_string(),
227 kind,
228 })
229 });
230
231 Ok(Entries::new(entries))
232 }
233}
234
235impl LocalFs {
236 pub fn new<P: Into<PathBuf>>(root: P) -> Self {
237 Self { root: root.into() }
238 }
239
240 pub fn pwd() -> Result<Self> {
242 Ok(Self::new(env::current_dir()?))
243 }
244}
245
246pub struct RamFs {
248 index: index::Index<Rc<[u8]>>,
249}
250
251pub struct RamFile(Cursor<Rc<[u8]>>);
253
254impl Read for RamFile {
255 fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
256 self.0.read(buf)
257 }
258}
259
260impl Seek for RamFile {
261 fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
262 self.0.seek(pos)
263 }
264}
265
266impl Store for RamFs {
267 type File = RamFile;
268
269 fn open_path(&self, path: &Path) -> Result<Self::File> {
270 match self.index.get(path) {
271 Some(file) => Ok(RamFile(Cursor::new(Rc::clone(file)))),
272 None => Err(Error::from(ErrorKind::NotFound)),
273 }
274 }
275
276 fn entries_path(&self, path: &Path) -> Result<Entries> {
277 Ok(Entries::new(self.index.entries(path).map(|ent| {
278 Ok(Entry {
279 name: ent.name.to_os_string(),
280 kind: ent.kind,
281 })
282 })))
283 }
284}
285
286impl RamFs {
287 pub fn new() -> Self {
288 Self {
289 index: index::Index::new(),
290 }
291 }
292
293 pub fn clear(&mut self) {
294 self.index.clear();
295 }
296
297 pub fn rm<P: AsRef<Path>>(&mut self, path: P) -> Option<Rc<[u8]>> {
298 self.index.remove(path)
299 }
300
301 pub fn touch<P, F>(&mut self, path: P, file: F)
302 where
303 P: Into<PathBuf>,
304 F: Into<Rc<[u8]>>,
305 {
306 self.index.insert(path.into(), file.into());
307 }
308
309 pub fn index(self) -> Self {
310 self
311 }
312}