Flesh out crate

This commit is contained in:
Lucien Greathouse
2020-02-18 23:16:56 -08:00
parent b4963f4ff7
commit 52e1dbd846
7 changed files with 95 additions and 5 deletions

View File

@@ -17,12 +17,16 @@ mod sealed {
impl Sealed for StdBackend {}
}
/// Backend that can be used to create a
///
/// This trait is sealed and cannot not be implemented outside this crate.
pub trait VfsBackend: sealed::Sealed {
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>>;
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()>;
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir>;
fn metadata(&mut self, path: &Path) -> io::Result<Metadata>;
fn event_receiver(&mut self) -> crossbeam_channel::Receiver<VfsEvent>;
fn watch(&mut self, path: &Path) -> io::Result<()>;
fn unwatch(&mut self, path: &Path) -> io::Result<()>;
}
@@ -49,6 +53,7 @@ impl Iterator for ReadDir {
}
}
#[derive(Debug)]
pub struct Metadata {
is_file: bool,
}
@@ -63,6 +68,14 @@ impl Metadata {
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum VfsEvent {
Create(PathBuf),
Write(PathBuf),
Remove(PathBuf),
}
struct VfsLock {
backend: Box<dyn VfsBackend>,
}
@@ -90,11 +103,18 @@ impl VfsLock {
}
}
/// A virtual filesystem with a configurable backend.
pub struct Vfs {
inner: Mutex<VfsLock>,
}
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<B: VfsBackend + 'static>(backend: B) -> Self {
let lock = VfsLock {
backend: Box::new(backend),
@@ -105,12 +125,23 @@ impl Vfs {
}
}
/// 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
pub fn read<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<Vec<u8>>> {
let path = path.as_ref();
let mut inner = self.inner.lock().unwrap();
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
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&self, path: P, contents: C) -> io::Result<()> {
let path = path.as_ref();
let contents = contents.as_ref();
@@ -118,6 +149,11 @@ impl Vfs {
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
pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<ReadDir> {
let path = path.as_ref();
let mut inner = self.inner.lock().unwrap();

View File

@@ -1,8 +1,9 @@
use std::io;
use std::path::Path;
use crate::{Metadata, ReadDir, VfsBackend};
use crate::{Metadata, ReadDir, VfsBackend, VfsEvent};
/// `VfsBackend` that returns an error on every operation.
#[non_exhaustive]
pub struct NoopBackend;
@@ -41,6 +42,10 @@ impl VfsBackend for NoopBackend {
))
}
fn event_receiver(&mut self) -> crossbeam_channel::Receiver<VfsEvent> {
crossbeam_channel::never()
}
fn watch(&mut self, _path: &Path) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Other,

View File

@@ -2,21 +2,49 @@ use std::fs;
use std::io;
use std::path::Path;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use crossbeam_channel::Receiver;
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use crate::{DirEntry, Metadata, ReadDir, VfsBackend};
use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent};
/// `VfsBackend` that uses `std::fs` and the `notify` crate.
pub struct StdBackend {
watcher: RecommendedWatcher,
watcher_receiver: mpsc::Receiver<DebouncedEvent>,
watcher_receiver: Receiver<VfsEvent>,
}
impl StdBackend {
pub fn new() -> StdBackend {
let (tx, rx) = mpsc::channel();
let watcher = watcher(tx, Duration::from_millis(50)).unwrap();
let (notify_tx, notify_rx) = mpsc::channel();
let watcher = watcher(notify_tx, Duration::from_millis(50)).unwrap();
let (tx, rx) = crossbeam_channel::unbounded();
thread::spawn(move || {
for event in notify_rx {
match event {
DebouncedEvent::Create(path) => {
tx.send(VfsEvent::Create(path))?;
}
DebouncedEvent::Write(path) => {
tx.send(VfsEvent::Write(path))?;
}
DebouncedEvent::Remove(path) => {
tx.send(VfsEvent::Remove(path))?;
}
DebouncedEvent::Rename(from, to) => {
tx.send(VfsEvent::Remove(from))?;
tx.send(VfsEvent::Create(to))?;
}
_ => {}
}
}
Result::<(), crossbeam_channel::SendError<VfsEvent>>::Ok(())
});
Self {
watcher,
@@ -54,6 +82,10 @@ impl VfsBackend for StdBackend {
})
}
fn event_receiver(&mut self) -> crossbeam_channel::Receiver<VfsEvent> {
self.watcher_receiver.clone()
}
fn watch(&mut self, path: &Path) -> io::Result<()> {
self.watcher
.watch(path, RecursiveMode::NonRecursive)