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

@@ -11,7 +11,6 @@ use rbx_dom_weak::RbxId;
use crate::{
serve_session::ServeSession,
snapshot::{PatchSet, PatchUpdate},
vfs::VfsFetcher,
web::{
interface::{
ErrorResponse, Instance, InstanceMetadata as WebInstanceMetadata, InstanceUpdate,
@@ -22,11 +21,11 @@ use crate::{
},
};
pub struct ApiService<F> {
serve_session: Arc<ServeSession<F>>,
pub struct ApiService {
serve_session: Arc<ServeSession>,
}
impl<F: VfsFetcher> Service for ApiService<F> {
impl Service for ApiService {
type ReqBody = Body;
type ResBody = Body;
type Error = hyper::Error;
@@ -53,8 +52,8 @@ impl<F: VfsFetcher> Service for ApiService<F> {
}
}
impl<F: VfsFetcher> ApiService<F> {
pub fn new(serve_session: Arc<ServeSession<F>>) -> Self {
impl ApiService {
pub fn new(serve_session: Arc<ServeSession>) -> Self {
ApiService { serve_session }
}

View File

@@ -13,16 +13,16 @@ use futures::{
use hyper::{service::Service, Body, Request, Response, Server};
use log::trace;
use crate::{serve_session::ServeSession, vfs::VfsFetcher};
use crate::serve_session::ServeSession;
use self::{api::ApiService, ui::UiService};
pub struct RootService<F> {
api: ApiService<F>,
ui: UiService<F>,
pub struct RootService {
api: ApiService,
ui: UiService,
}
impl<F: VfsFetcher> Service for RootService<F> {
impl Service for RootService {
type ReqBody = Body;
type ResBody = Body;
type Error = hyper::Error;
@@ -39,8 +39,8 @@ impl<F: VfsFetcher> Service for RootService<F> {
}
}
impl<F: VfsFetcher> RootService<F> {
pub fn new(serve_session: Arc<ServeSession<F>>) -> Self {
impl RootService {
pub fn new(serve_session: Arc<ServeSession>) -> Self {
RootService {
api: ApiService::new(Arc::clone(&serve_session)),
ui: UiService::new(Arc::clone(&serve_session)),
@@ -48,12 +48,12 @@ impl<F: VfsFetcher> RootService<F> {
}
}
pub struct LiveServer<F> {
serve_session: Arc<ServeSession<F>>,
pub struct LiveServer {
serve_session: Arc<ServeSession>,
}
impl<F: VfsFetcher + Send + Sync + 'static> LiveServer<F> {
pub fn new(serve_session: Arc<ServeSession<F>>) -> Self {
impl LiveServer {
pub fn new(serve_session: Arc<ServeSession>) -> Self {
LiveServer { serve_session }
}

View File

@@ -1,6 +1,6 @@
//! Defines the HTTP-based UI. These endpoints generally return HTML and SVG.
use std::{borrow::Cow, path::Path, sync::Arc, time::Duration};
use std::{borrow::Cow, sync::Arc, time::Duration};
use futures::{future, Future};
use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
@@ -11,7 +11,6 @@ use ritz::{html, Fragment, HtmlContent, HtmlSelfClosingTag};
use crate::{
serve_session::ServeSession,
snapshot::RojoTree,
vfs::{Vfs, VfsDebug, VfsFetcher},
web::{
assets,
interface::{ErrorResponse, SERVER_VERSION},
@@ -19,11 +18,11 @@ use crate::{
},
};
pub struct UiService<F> {
serve_session: Arc<ServeSession<F>>,
pub struct UiService {
serve_session: Arc<ServeSession>,
}
impl<F: VfsFetcher> Service for UiService<F> {
impl Service for UiService {
type ReqBody = Body;
type ResBody = Body;
type Error = hyper::Error;
@@ -35,7 +34,6 @@ impl<F: VfsFetcher> Service for UiService<F> {
(&Method::GET, "/logo.png") => self.handle_logo(),
(&Method::GET, "/icon.png") => self.handle_icon(),
(&Method::GET, "/show-instances") => self.handle_show_instances(),
(&Method::GET, "/show-vfs") => self.handle_show_vfs(),
(_method, path) => {
return json(
ErrorResponse::not_found(format!("Route not found: {}", path)),
@@ -48,8 +46,8 @@ impl<F: VfsFetcher> Service for UiService<F> {
}
}
impl<F: VfsFetcher> UiService<F> {
pub fn new(serve_session: Arc<ServeSession<F>>) -> Self {
impl UiService {
pub fn new(serve_session: Arc<ServeSession>) -> Self {
UiService { serve_session }
}
@@ -71,7 +69,6 @@ impl<F: VfsFetcher> UiService<F> {
let page = self.normal_page(html! {
<div class="button-list">
{ Self::button("Rojo Documentation", "https://rojo.space/docs") }
{ Self::button("View virtual filesystem state", "/show-vfs") }
{ Self::button("View instance tree state", "/show-instances") }
</div>
});
@@ -96,100 +93,6 @@ impl<F: VfsFetcher> UiService<F> {
.unwrap()
}
fn handle_show_vfs(&self) -> Response<Body> {
let vfs = self.serve_session.vfs();
let orphans: Vec<_> = vfs
.debug_orphans()
.into_iter()
.map(|path| Self::render_vfs_path(&vfs, &path, true))
.collect();
let watched_list: Vec<_> = vfs
.debug_watched_paths()
.into_iter()
.map(|path| {
html! {
<li>{ format!("{}", path.display()) }</li>
}
})
.collect();
let page = self.normal_page(html! {
<>
<section class="main-section">
<h1 class="section-title">"Known FS Items"</h1>
<div>{ Fragment::new(orphans) }</div>
</section>
<section class="main-section">
<h1 class="section-title">"Watched Paths"</h1>
<ul class="path-list">{ Fragment::new(watched_list) }</ul>
</section>
</>
});
Response::builder()
.header(header::CONTENT_TYPE, "text/html")
.body(Body::from(format!("<!DOCTYPE html>{}", page)))
.unwrap()
}
fn render_vfs_path(vfs: &Vfs<F>, path: &Path, is_root: bool) -> HtmlContent<'static> {
let is_file = vfs.debug_is_file(path);
let (note, children) = if is_file {
(HtmlContent::None, Vec::new())
} else {
let (is_exhaustive, mut children) = vfs.debug_children(path).unwrap();
// Sort files above directories, then sort how Path does after that.
children.sort_unstable_by(|a, b| {
let a_is_file = vfs.debug_is_file(a);
let b_is_file = vfs.debug_is_file(b);
b_is_file.cmp(&a_is_file).then_with(|| a.cmp(b))
});
let children: Vec<_> = children
.into_iter()
.map(|child| Self::render_vfs_path(vfs, &child, false))
.collect();
let note = if is_exhaustive {
HtmlContent::None
} else {
html!({ " (not enumerated)" })
};
(note, children)
};
// For root entries, we want the full path to contextualize the path.
let mut name = if is_root {
path.to_str().unwrap().to_owned()
} else {
path.file_name().unwrap().to_str().unwrap().to_owned()
};
// Directories should end with `/` in the UI to mark them.
if !is_file && !name.ends_with('/') && !name.ends_with('\\') {
name.push('/');
}
html! {
<div class="vfs-entry">
<div>
<span class="vfs-entry-name">{ name }</span>
<span class="vfs-entry-note">{ note }</span>
</div>
<div class="vfs-entry-children">
{ Fragment::new(children) }
</div>
</div>
}
}
fn instance(tree: &RojoTree, id: RbxId) -> HtmlContent<'_> {
let instance = tree.get_instance(id).unwrap();
let children_list: Vec<_> = instance