mod memory_backend; mod noop_backend; mod snapshot; mod std_backend; use std::io; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, MutexGuard}; pub use memory_backend::MemoryBackend; pub use noop_backend::NoopBackend; pub use snapshot::VfsSnapshot; pub use std_backend::StdBackend; mod sealed { use super::*; /// Sealing trait for VfsBackend. pub trait Sealed {} impl Sealed for MemoryBackend {} impl Sealed for NoopBackend {} impl Sealed for StdBackend {} } /// Trait that transforms `io::Result` into `io::Result>`. /// /// `Ok(None)` takes the place of IO errors whose `io::ErrorKind` is `NotFound`. pub trait IoResultExt { fn with_not_found(self) -> io::Result>; } impl IoResultExt for io::Result { fn with_not_found(self) -> io::Result> { match self { Ok(v) => Ok(Some(v)), Err(err) => { if err.kind() == io::ErrorKind::NotFound { Ok(None) } else { Err(err) } } } } } /// Backend that can be used to create a `Vfs`. /// /// This trait is sealed and cannot not be implemented outside this crate. pub trait VfsBackend: sealed::Sealed + Send + 'static { fn read(&mut self, path: &Path) -> io::Result>; fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()>; fn read_dir(&mut self, path: &Path) -> io::Result; fn metadata(&mut self, path: &Path) -> io::Result; fn remove_file(&mut self, path: &Path) -> io::Result<()>; fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>; fn event_receiver(&self) -> crossbeam_channel::Receiver; fn watch(&mut self, path: &Path) -> io::Result<()>; fn unwatch(&mut self, path: &Path) -> io::Result<()>; } /// Vfs equivalent to [`std::fs::DirEntry`][std::fs::DirEntry]. /// /// [std::fs::DirEntry]: https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html pub struct DirEntry { pub(crate) path: PathBuf, } impl DirEntry { pub fn path(&self) -> &Path { &self.path } } /// Vfs equivalent to [`std::fs::ReadDir`][std::fs::ReadDir]. /// /// [std::fs::ReadDir]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html pub struct ReadDir { pub(crate) inner: Box>>, } impl Iterator for ReadDir { type Item = io::Result; fn next(&mut self) -> Option { self.inner.next() } } /// Vfs equivalent to [`std::fs::Metadata`][std::fs::Metadata]. /// /// [std::fs::Metadata]: https://doc.rust-lang.org/stable/std/fs/struct.Metadata.html #[derive(Debug)] pub struct Metadata { pub(crate) is_file: bool, } impl Metadata { pub fn is_file(&self) -> bool { self.is_file } pub fn is_dir(&self) -> bool { !self.is_file } } #[derive(Debug)] #[non_exhaustive] pub enum VfsEvent { Create(PathBuf), Write(PathBuf), Remove(PathBuf), } /// Contains implementation details of the Vfs, wrapped by `Vfs` and `VfsLock`, /// the public interfaces to this type. struct VfsInner { backend: Box, } impl VfsInner { fn read>(&mut self, path: P) -> io::Result>> { let path = path.as_ref(); let contents = self.backend.read(path)?; self.backend.watch(path)?; Ok(Arc::new(contents)) } fn write, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> { let path = path.as_ref(); let contents = contents.as_ref(); self.backend.write(path, contents) } fn read_dir>(&mut self, path: P) -> io::Result { let path = path.as_ref(); let dir = self.backend.read_dir(path)?; self.backend.watch(path)?; Ok(dir) } fn remove_file>(&mut self, path: P) -> io::Result<()> { let path = path.as_ref(); let _ = self.backend.unwatch(path); self.backend.remove_file(path) } fn remove_dir_all>(&mut self, path: P) -> io::Result<()> { let path = path.as_ref(); let _ = self.backend.unwatch(path); self.backend.remove_dir_all(path) } fn metadata>(&mut self, path: P) -> io::Result { let path = path.as_ref(); self.backend.metadata(path) } fn event_receiver(&self) -> crossbeam_channel::Receiver { self.backend.event_receiver() } fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> { match event { VfsEvent::Remove(path) => { let _ = self.backend.unwatch(&path); } _ => {} } Ok(()) } } /// A virtual filesystem with a configurable backend. pub struct Vfs { inner: Mutex, } impl Vfs { /// Creates a new `Vfs` with the default backend, `StdBackend`. pub fn new_default() -> Self { Self::new(StdBackend::new()) } /// Creates a new `Vfs` with the given backend. pub fn new(backend: B) -> Self { let lock = VfsInner { backend: Box::new(backend), }; Self { inner: Mutex::new(lock), } } pub fn lock(&self) -> VfsLock<'_> { VfsLock { inner: self.inner.lock().unwrap(), } } /// Read a file from the VFS, or the underlying backend if it isn't /// resident. /// /// Roughly equivalent to [`std::fs::read`][std::fs::read]. /// /// [std::fs::read]: https://doc.rust-lang.org/stable/std/fs/fn.read.html #[inline] pub fn read>(&self, path: P) -> io::Result>> { let path = path.as_ref(); self.inner.lock().unwrap().read(path) } /// Write a file to the VFS and the underlying backend. /// /// Roughly equivalent to [`std::fs::write`][std::fs::write]. /// /// [std::fs::write]: https://doc.rust-lang.org/stable/std/fs/fn.write.html #[inline] pub fn write, C: AsRef<[u8]>>(&self, path: P, contents: C) -> io::Result<()> { let path = path.as_ref(); let contents = contents.as_ref(); self.inner.lock().unwrap().write(path, contents) } /// Read all of the children of a directory. /// /// Roughly equivalent to [`std::fs::read_dir`][std::fs::read_dir]. /// /// [std::fs::read_dir]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html #[inline] pub fn read_dir>(&self, path: P) -> io::Result { let path = path.as_ref(); self.inner.lock().unwrap().read_dir(path) } /// Remove a file. /// /// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file]. /// /// [std::fs::remove_file]: https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html #[inline] pub fn remove_file>(&self, path: P) -> io::Result<()> { let path = path.as_ref(); self.inner.lock().unwrap().remove_file(path) } /// Remove a directory and all of its descendants. /// /// Roughly equivalent to [`std::fs::remove_dir_all`][std::fs::remove_dir_all]. /// /// [std::fs::remove_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html #[inline] pub fn remove_dir_all>(&self, path: P) -> io::Result<()> { let path = path.as_ref(); self.inner.lock().unwrap().remove_dir_all(path) } /// Query metadata about the given path. /// /// Roughly equivalent to [`std::fs::metadata`][std::fs::metadata]. /// /// [std::fs::metadata]: https://doc.rust-lang.org/stable/std/fs/fn.metadata.html #[inline] pub fn metadata>(&self, path: P) -> io::Result { let path = path.as_ref(); self.inner.lock().unwrap().metadata(path) } /// Retrieve a handle to the event receiver for this `Vfs`. #[inline] pub fn event_receiver(&self) -> crossbeam_channel::Receiver { self.inner.lock().unwrap().event_receiver() } /// Commit an event to this `Vfs`. #[inline] pub fn commit_event(&self, event: &VfsEvent) -> io::Result<()> { self.inner.lock().unwrap().commit_event(event) } } /// A locked handle to a `Vfs`, created by `Vfs::lock`. pub struct VfsLock<'a> { inner: MutexGuard<'a, VfsInner>, } impl VfsLock<'_> { /// Read a file from the VFS, or the underlying backend if it isn't /// resident. /// /// Roughly equivalent to [`std::fs::read`][std::fs::read]. /// /// [std::fs::read]: https://doc.rust-lang.org/stable/std/fs/fn.read.html #[inline] pub fn read>(&mut self, path: P) -> io::Result>> { let path = path.as_ref(); self.inner.read(path) } /// Write a file to the VFS and the underlying backend. /// /// Roughly equivalent to [`std::fs::write`][std::fs::write]. /// /// [std::fs::write]: https://doc.rust-lang.org/stable/std/fs/fn.write.html #[inline] pub fn write, C: AsRef<[u8]>>( &mut self, path: P, contents: C, ) -> io::Result<()> { let path = path.as_ref(); let contents = contents.as_ref(); self.inner.write(path, contents) } /// Read all of the children of a directory. /// /// Roughly equivalent to [`std::fs::read_dir`][std::fs::read_dir]. /// /// [std::fs::read_dir]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html #[inline] pub fn read_dir>(&mut self, path: P) -> io::Result { let path = path.as_ref(); self.inner.read_dir(path) } /// Remove a file. /// /// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file]. /// /// [std::fs::remove_file]: https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html #[inline] pub fn remove_file>(&mut self, path: P) -> io::Result<()> { let path = path.as_ref(); self.inner.remove_file(path) } /// Remove a directory and all of its descendants. /// /// Roughly equivalent to [`std::fs::remove_dir_all`][std::fs::remove_dir_all]. /// /// [std::fs::remove_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html #[inline] pub fn remove_dir_all>(&mut self, path: P) -> io::Result<()> { let path = path.as_ref(); self.inner.remove_dir_all(path) } /// Query metadata about the given path. /// /// Roughly equivalent to [`std::fs::metadata`][std::fs::metadata]. /// /// [std::fs::metadata]: https://doc.rust-lang.org/stable/std/fs/fn.metadata.html #[inline] pub fn metadata>(&mut self, path: P) -> io::Result { let path = path.as_ref(); self.inner.metadata(path) } /// Retrieve a handle to the event receiver for this `Vfs`. #[inline] pub fn event_receiver(&self) -> crossbeam_channel::Receiver { self.inner.event_receiver() } /// Commit an event to this `Vfs`. #[inline] pub fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> { self.inner.commit_event(event) } }