VFS in external crate (#297)

* vroom

* Port dir middleware

* Filter rules

* Directory metadata

* Project support

* Enable Lua support

* StringValue support

* CSV

* rbxm, rbxmx, and rbxlx

* JSON models

* Clean up some warnings

* Strip out PathMap

* Unwatch paths when they're reported as removed

* Fix 'rojo upload' behavior

* Upgrade to Insta 0.13.1

* Update dependencies

* Release 0.6.0-alpha.2

* Fix bad merge

* Replace MemoryBackend with InMemoryFs

* Sledgehammer tests into passing for now

* Txt middleware

* Update easy snapshot tests

* Lua tests

* Project middleware tests

* Try to fix test failures by sorting

* Port first set of serve session tests

* Add InMemoryFs::raise_event

* Finish porting serve session tests

* Remove UI code for introspecting VFS for now

* VFS docs
This commit is contained in:
Lucien Greathouse
2020-03-10 17:38:49 -07:00
committed by GitHub
parent a884f693ae
commit 477e0ada32
38 changed files with 846 additions and 2509 deletions

View File

@@ -1,32 +1,77 @@
use std::collections::{BTreeSet, HashMap, VecDeque};
use std::io;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use crossbeam_channel::{Receiver, Sender};
use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent, VfsSnapshot};
/// `VfsBackend` that reads from an in-memory filesystem, intended for setting
/// up testing scenarios quickly.
#[derive(Debug)]
pub struct MemoryBackend {
entries: HashMap<PathBuf, Entry>,
orphans: BTreeSet<PathBuf>,
/// In-memory filesystem that can be used as a VFS backend.
///
/// Internally reference counted to enable giving a copy to
/// [`Vfs`](struct.Vfs.html) and keeping the original to mutate the filesystem's
/// state with.
#[derive(Debug, Clone)]
pub struct InMemoryFs {
inner: Arc<Mutex<InMemoryFsInner>>,
}
impl MemoryBackend {
impl InMemoryFs {
/// Create a new empty `InMemoryFs`.
pub fn new() -> Self {
Self {
entries: HashMap::new(),
orphans: BTreeSet::new(),
inner: Arc::new(Mutex::new(InMemoryFsInner::new())),
}
}
/// Load a [`VfsSnapshot`](enum.VfsSnapshot.html) into a subtree of the
/// in-memory filesystem.
///
/// This function will return an error if the operations required to apply
/// the snapshot result in errors, like trying to create a file inside a
/// file.
pub fn load_snapshot<P: Into<PathBuf>>(
&mut self,
path: P,
snapshot: VfsSnapshot,
) -> io::Result<()> {
let path = path.into();
let mut inner = self.inner.lock().unwrap();
inner.load_snapshot(path.into(), snapshot)
}
/// Raises a filesystem change event.
///
/// If this `InMemoryFs` is being used as the backend of a
/// [`Vfs`](struct.Vfs.html), then any listeners be notified of this event.
pub fn raise_event(&mut self, event: VfsEvent) {
let inner = self.inner.lock().unwrap();
inner.event_sender.send(event).unwrap();
}
}
#[derive(Debug)]
struct InMemoryFsInner {
entries: HashMap<PathBuf, Entry>,
orphans: BTreeSet<PathBuf>,
event_receiver: Receiver<VfsEvent>,
event_sender: Sender<VfsEvent>,
}
impl InMemoryFsInner {
fn new() -> Self {
let (event_sender, event_receiver) = crossbeam_channel::unbounded();
Self {
entries: HashMap::new(),
orphans: BTreeSet::new(),
event_receiver,
event_sender,
}
}
fn load_snapshot(&mut self, path: PathBuf, snapshot: VfsSnapshot) -> io::Result<()> {
if let Some(parent_path) = path.parent() {
if let Some(parent_entry) = self.entries.get_mut(parent_path) {
if let Entry::Dir { children } = parent_entry {
@@ -84,9 +129,11 @@ enum Entry {
Dir { children: BTreeSet<PathBuf> },
}
impl VfsBackend for MemoryBackend {
impl VfsBackend for InMemoryFs {
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>> {
match self.entries.get(path) {
let inner = self.inner.lock().unwrap();
match inner.entries.get(path) {
Some(Entry::File { contents }) => Ok(contents.clone()),
Some(Entry::Dir { .. }) => must_be_file(path),
None => not_found(path),
@@ -94,8 +141,10 @@ impl VfsBackend for MemoryBackend {
}
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
self.load_snapshot(
path,
let mut inner = self.inner.lock().unwrap();
inner.load_snapshot(
path.to_path_buf(),
VfsSnapshot::File {
contents: data.to_owned(),
},
@@ -103,7 +152,9 @@ impl VfsBackend for MemoryBackend {
}
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
match self.entries.get(path) {
let inner = self.inner.lock().unwrap();
match inner.entries.get(path) {
Some(Entry::Dir { children }) => {
let iter = children
.clone()
@@ -120,9 +171,11 @@ impl VfsBackend for MemoryBackend {
}
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
match self.entries.get(path) {
let mut inner = self.inner.lock().unwrap();
match inner.entries.get(path) {
Some(Entry::File { .. }) => {
self.remove(path.to_owned());
inner.remove(path.to_owned());
Ok(())
}
Some(Entry::Dir { .. }) => must_be_file(path),
@@ -131,9 +184,11 @@ impl VfsBackend for MemoryBackend {
}
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
match self.entries.get(path) {
let mut inner = self.inner.lock().unwrap();
match inner.entries.get(path) {
Some(Entry::Dir { .. }) => {
self.remove(path.to_owned());
inner.remove(path.to_owned());
Ok(())
}
Some(Entry::File { .. }) => must_be_dir(path),
@@ -142,7 +197,9 @@ impl VfsBackend for MemoryBackend {
}
fn metadata(&mut self, path: &Path) -> io::Result<Metadata> {
match self.entries.get(path) {
let inner = self.inner.lock().unwrap();
match inner.entries.get(path) {
Some(Entry::File { .. }) => Ok(Metadata { is_file: true }),
Some(Entry::Dir { .. }) => Ok(Metadata { is_file: false }),
None => not_found(path),
@@ -150,7 +207,9 @@ impl VfsBackend for MemoryBackend {
}
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
crossbeam_channel::never()
let inner = self.inner.lock().unwrap();
inner.event_receiver.clone()
}
fn watch(&mut self, _path: &Path) -> io::Result<()> {

View File

@@ -1,4 +1,4 @@
mod memory_backend;
mod in_memory_fs;
mod noop_backend;
mod snapshot;
mod std_backend;
@@ -7,7 +7,7 @@ use std::io;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, MutexGuard};
pub use memory_backend::MemoryBackend;
pub use in_memory_fs::InMemoryFs;
pub use noop_backend::NoopBackend;
pub use snapshot::VfsSnapshot;
pub use std_backend::StdBackend;
@@ -18,9 +18,9 @@ mod sealed {
/// Sealing trait for VfsBackend.
pub trait Sealed {}
impl Sealed for MemoryBackend {}
impl Sealed for NoopBackend {}
impl Sealed for StdBackend {}
impl Sealed for InMemoryFs {}
}
/// Trait that transforms `io::Result<T>` into `io::Result<Option<T>>`.
@@ -107,6 +107,8 @@ impl Metadata {
}
}
/// Represents an event that a filesystem can raise that might need to be
/// handled.
#[derive(Debug)]
#[non_exhaustive]
pub enum VfsEvent {

View File

@@ -1,5 +1,8 @@
use std::collections::BTreeMap;
/// A slice of a tree of files. Can be loaded into an
/// [`InMemoryFs`](struct.InMemoryFs.html).
#[derive(Debug)]
#[non_exhaustive]
pub enum VfsSnapshot {
File {
@@ -26,4 +29,16 @@ impl VfsSnapshot {
.collect(),
}
}
pub fn empty_file() -> Self {
Self::File {
contents: Vec::new(),
}
}
pub fn empty_dir() -> Self {
Self::Dir {
children: BTreeMap::new(),
}
}
}

View File

@@ -63,11 +63,14 @@ impl VfsBackend for StdBackend {
}
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
let inner = fs::read_dir(path)?.map(|entry| {
Ok(DirEntry {
path: entry?.path(),
})
});
let entries: Result<Vec<_>, _> = fs::read_dir(path)?.collect();
let mut entries = entries?;
entries.sort_by_cached_key(|entry| entry.file_name());
let inner = entries
.into_iter()
.map(|entry| Ok(DirEntry { path: entry.path() }));
Ok(ReadDir {
inner: Box::new(inner),