From 5bf1f1119076750afb0d4a0f38ec6f2a854e11c4 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 13 Dec 2017 15:41:25 -0800 Subject: [PATCH 01/14] Hacky first go at it -- keeping the existing VfsItem infrastructure I think this is actually a pretty reasonable flow. --- Cargo.lock | 76 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/bin.rs | 4 +++ src/web.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 182 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe831498..50e19653 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,14 @@ dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "aho-corasick" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ansi_term" version = "0.10.2" @@ -269,6 +277,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.34" @@ -292,6 +305,14 @@ dependencies = [ "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memchr" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mime" version = "0.1.3" @@ -510,18 +531,37 @@ dependencies = [ "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex-syntax" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "regex-syntax" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rojo" version = "0.3.1" dependencies = [ "clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rouille 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", @@ -694,6 +734,15 @@ dependencies = [ "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread_local" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.38" @@ -742,6 +791,14 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "0.2.38" @@ -767,6 +824,11 @@ name = "utf8-ranges" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "uuid" version = "0.1.18" @@ -781,6 +843,11 @@ name = "vec_map" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "walkdir" version = "2.0.1" @@ -810,6 +877,7 @@ dependencies = [ [metadata] "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" "checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" "checksum ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae7d751998c189c1d4468cf0a39bb2eae052a9c58d50ebb3b9591ee3813ad50" "checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860" @@ -845,10 +913,12 @@ dependencies = [ "checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c" "checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" "checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0" "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" "checksum mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ec0c2f4d901bf1d4a2192a40b4b570ae3b19c51243e549defc1de741940aa787" "checksum mime_guess 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "422acd80644209a8c8c66a20514840d8c092eb1eab2898ca7c548cc1d64c8998" "checksum miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "609ce024854aeb19a0ef7567d348aaa5a746b32fb72e336df7fcc16869d7e2b4" @@ -872,7 +942,9 @@ dependencies = [ "checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +"checksum regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ac6ab4e9218ade5b423358bbd2567d1617418403c7a512603630181813316322" "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" +"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" "checksum rouille 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d6415f261a8775bef50e9fcfb14ed73209ce637f753f9d1c8c6122559e559001" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum same-file 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "70a18720d745fb9ca6a041b37cb36d0b21066006b6cff8b5b360142d4b81fb60" @@ -893,17 +965,21 @@ dependencies = [ "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" "checksum tiny_http 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "016f040cfc9b5be610de3619eaaa57017fa0b0b678187327bde75fc146e2a41f" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum url 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)" = "cbaa8377a162d88e7d15db0cf110c8523453edcbc5bc66d2b6fffccffa34a068" "checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum uuid 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "78c590b5bd79ed10aad8fb75f078a59d8db445af6c743e55c4a53227fc01c13f" "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b6d201f4f8998a837196b6de9c73e35af14c992cbb92c4ab641d2c2dce52de" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index a1e34642..9cab5bfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,5 @@ serde_derive = "1.0" serde_json = "1.0" notify = "4.0.0" rand = "0.3" +regex = "0.2" +lazy_static = "1.0" diff --git a/src/bin.rs b/src/bin.rs index 4c42a4cf..aecb946a 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -7,10 +7,14 @@ extern crate rouille; #[macro_use] extern crate clap; +#[macro_use] +extern crate lazy_static; + extern crate notify; extern crate rand; extern crate serde; extern crate serde_json; +extern crate regex; pub mod web; pub mod core; diff --git a/src/web.rs b/src/web.rs index 12c936ae..4a8aaebe 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::io::Read; use std::sync::{Arc, Mutex}; use std::thread; @@ -5,6 +6,7 @@ use std::thread; use rouille; use serde; use serde_json; +use regex::Regex; use core::Config; use project::Project; @@ -12,6 +14,91 @@ use vfs::{Vfs, VfsChange, VfsItem}; static MAX_BODY_SIZE: usize = 25 * 1024 * 1025; // 25 MiB +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct RbxItem { + name: String, + class_name: String, + children: Vec, + properties: HashMap, +} + +impl RbxItem { + pub fn from_vfs_item(file_name: &String, item: &VfsItem) -> RbxItem { + lazy_static! { + static ref PT_MODULE: Regex = Regex::new(r"^(.*?)\.lua$").unwrap(); + } + + match item { + &VfsItem::File { ref contents } => { + let mut properties = HashMap::new(); + + properties.insert("Source".to_string(), RbxValue::String { + value: contents.clone() + }); + + let rbx_name = { + if let Some(captures) = PT_MODULE.captures(file_name) { + captures.get(1).unwrap().as_str().to_string() + } else { + file_name.clone() + } + }; + + RbxItem { + name: rbx_name, + class_name: "ModuleScript".to_string(), + children: Vec::new(), + properties, + } + }, + &VfsItem::Dir { ref children } => { + let init = children.get(&"init.lua".to_string()); + + if let Some(init) = init { + let mut rbx_children = Vec::new(); + + for (name, child_item) in children { + if name != "init.lua" { + rbx_children.push(RbxItem::from_vfs_item(&name, &child_item)); + } + } + + let init_rbx = RbxItem::from_vfs_item(&"init.lua".to_string(), &init); + + RbxItem { + name: file_name.clone(), + class_name: init_rbx.class_name, + children: rbx_children, + properties: init_rbx.properties, + } + } else { + let mut rbx_children = Vec::new(); + + for (name, child_item) in children { + rbx_children.push(RbxItem::from_vfs_item(&name, &child_item)); + } + + RbxItem { + name: file_name.clone(), + class_name: "Folder".to_string(), + children: rbx_children, + properties: HashMap::new(), + } + } + }, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase", tag = "type")] +enum RbxValue { + String { + value: String, + }, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct ServerInfo<'a> { @@ -25,7 +112,7 @@ struct ServerInfo<'a> { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct ReadResult<'a> { - items: Vec>, + items: Vec>, server_id: &'a str, current_time: f64, } @@ -110,7 +197,7 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { json(ServerInfo { server_version: env!("CARGO_PKG_VERSION"), - protocol_version: 0, + protocol_version: 1, server_id: &server_id, project: &project, current_time, @@ -152,9 +239,19 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { (items, current_time) }; + let rbx_items = items + .iter() + .map(|item| { + match *item { + Some(ref item) => Some(RbxItem::from_vfs_item(&"src".to_string(), item)), + None => None, + } + }) + .collect::>(); + json(ReadResult { server_id: &server_id, - items, + items: rbx_items, current_time, }) }, From 21e9625c36544303f55b8ea61e038b0cdbe4e83b Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 13 Dec 2017 17:50:34 -0800 Subject: [PATCH 02/14] Prototype plugin architecture, switch instance-based stuff to that --- src/bin.rs | 3 ++ src/plugin.rs | 11 ++++ src/plugins/default_plugin.rs | 49 ++++++++++++++++++ src/plugins/mod.rs | 3 ++ src/rbx.rs | 18 +++++++ src/vfs.rs | 8 ++- src/web.rs | 95 ++++------------------------------- 7 files changed, 99 insertions(+), 88 deletions(-) create mode 100644 src/plugin.rs create mode 100644 src/plugins/default_plugin.rs create mode 100644 src/plugins/mod.rs create mode 100644 src/rbx.rs diff --git a/src/bin.rs b/src/bin.rs index aecb946a..41090e1b 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -22,6 +22,9 @@ pub mod project; pub mod pathext; pub mod vfs; pub mod vfs_watch; +pub mod rbx; +pub mod plugin; +pub mod plugins; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; diff --git a/src/plugin.rs b/src/plugin.rs new file mode 100644 index 00000000..1d03d326 --- /dev/null +++ b/src/plugin.rs @@ -0,0 +1,11 @@ +use rbx::RbxItem; +use vfs::VfsItem; + +pub enum PluginResult { + Value(Option), + Pass, +} + +pub trait Plugin { + fn transform(item: &VfsItem) -> PluginResult; +} diff --git a/src/plugins/default_plugin.rs b/src/plugins/default_plugin.rs new file mode 100644 index 00000000..b5beeca6 --- /dev/null +++ b/src/plugins/default_plugin.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; + +use plugin::{Plugin, PluginResult}; +use rbx::{RbxItem, RbxValue}; +use vfs::VfsItem; + +pub struct DefaultPlugin; + +impl Plugin for DefaultPlugin { + fn transform(item: &VfsItem) -> PluginResult { + match item { + &VfsItem::File { ref contents, ref name } => { + let mut properties = HashMap::new(); + + properties.insert("Value".to_string(), RbxValue::String { + value: contents.clone(), + }); + + PluginResult::Value(Some(RbxItem { + name: name.clone(), + class_name: "StringValue".to_string(), + children: Vec::new(), + properties, + })) + }, + &VfsItem::Dir { ref children, ref name } => { + // TODO: call back into plugin list and transform there instead + + let mut rbx_children = Vec::new(); + + for (_, child_item) in children { + match Self::transform(child_item) { + PluginResult::Value(Some(rbx_item)) => { + rbx_children.push(rbx_item); + }, + _ => {}, + } + } + + PluginResult::Value(Some(RbxItem { + name: name.clone(), + class_name: "Folder".to_string(), + children: rbx_children, + properties: HashMap::new(), + })) + }, + } + } +} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs new file mode 100644 index 00000000..c3b68bad --- /dev/null +++ b/src/plugins/mod.rs @@ -0,0 +1,3 @@ +mod default_plugin; + +pub use self::default_plugin::*; diff --git a/src/rbx.rs b/src/rbx.rs new file mode 100644 index 00000000..043dc3b6 --- /dev/null +++ b/src/rbx.rs @@ -0,0 +1,18 @@ +use std::collections::HashMap; + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RbxItem { + pub name: String, + pub class_name: String, + pub children: Vec, + pub properties: HashMap, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase", tag = "type")] +pub enum RbxValue { + String { + value: String, + }, +} diff --git a/src/vfs.rs b/src/vfs.rs index 44b9165f..81fb9c04 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -37,8 +37,8 @@ pub struct VfsChange { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum VfsItem { - File { contents: String }, - Dir { children: HashMap }, + File { name: String, contents: String }, + Dir { name: String, children: HashMap }, } impl Vfs { @@ -78,6 +78,7 @@ impl Vfs { } fn read_dir>(&self, path: P) -> Result { + let path = path.as_ref(); let reader = match fs::read_dir(path) { Ok(v) => v, Err(_) => return Err(()), @@ -104,11 +105,13 @@ impl Vfs { } Ok(VfsItem::Dir { + name: path.file_name().unwrap().to_string_lossy().into_owned(), children, }) } fn read_file>(&self, path: P) -> Result { + let path = path.as_ref(); let mut file = match File::open(path) { Ok(v) => v, Err(_) => return Err(()), @@ -122,6 +125,7 @@ impl Vfs { } Ok(VfsItem::File { + name: path.file_name().unwrap().to_string_lossy().into_owned(), contents, }) } diff --git a/src/web.rs b/src/web.rs index 4a8aaebe..670b0f0c 100644 --- a/src/web.rs +++ b/src/web.rs @@ -11,94 +11,12 @@ use regex::Regex; use core::Config; use project::Project; use vfs::{Vfs, VfsChange, VfsItem}; +use rbx::{RbxItem, RbxValue}; +use plugin::{Plugin, PluginResult}; +use plugins::DefaultPlugin; static MAX_BODY_SIZE: usize = 25 * 1024 * 1025; // 25 MiB -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct RbxItem { - name: String, - class_name: String, - children: Vec, - properties: HashMap, -} - -impl RbxItem { - pub fn from_vfs_item(file_name: &String, item: &VfsItem) -> RbxItem { - lazy_static! { - static ref PT_MODULE: Regex = Regex::new(r"^(.*?)\.lua$").unwrap(); - } - - match item { - &VfsItem::File { ref contents } => { - let mut properties = HashMap::new(); - - properties.insert("Source".to_string(), RbxValue::String { - value: contents.clone() - }); - - let rbx_name = { - if let Some(captures) = PT_MODULE.captures(file_name) { - captures.get(1).unwrap().as_str().to_string() - } else { - file_name.clone() - } - }; - - RbxItem { - name: rbx_name, - class_name: "ModuleScript".to_string(), - children: Vec::new(), - properties, - } - }, - &VfsItem::Dir { ref children } => { - let init = children.get(&"init.lua".to_string()); - - if let Some(init) = init { - let mut rbx_children = Vec::new(); - - for (name, child_item) in children { - if name != "init.lua" { - rbx_children.push(RbxItem::from_vfs_item(&name, &child_item)); - } - } - - let init_rbx = RbxItem::from_vfs_item(&"init.lua".to_string(), &init); - - RbxItem { - name: file_name.clone(), - class_name: init_rbx.class_name, - children: rbx_children, - properties: init_rbx.properties, - } - } else { - let mut rbx_children = Vec::new(); - - for (name, child_item) in children { - rbx_children.push(RbxItem::from_vfs_item(&name, &child_item)); - } - - RbxItem { - name: file_name.clone(), - class_name: "Folder".to_string(), - children: rbx_children, - properties: HashMap::new(), - } - } - }, - } - } -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase", tag = "type")] -enum RbxValue { - String { - value: String, - }, -} - #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct ServerInfo<'a> { @@ -243,7 +161,12 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { .iter() .map(|item| { match *item { - Some(ref item) => Some(RbxItem::from_vfs_item(&"src".to_string(), item)), + Some(ref item) => { + match DefaultPlugin::transform(item) { + PluginResult::Value(rbx_item) => rbx_item, + _ => None, + } + }, None => None, } }) From 01325c8c7ec4a6dbe49ef0190a58954101490497 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 13 Dec 2017 23:40:32 -0800 Subject: [PATCH 03/14] Implement PluginChain --- src/plugin.rs | 25 ++++++++++++++++++++++++- src/plugins/default_plugin.rs | 18 +++++++++++------- src/web.rs | 24 +++++++++++++----------- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/plugin.rs b/src/plugin.rs index 1d03d326..6c2f6246 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -7,5 +7,28 @@ pub enum PluginResult { } pub trait Plugin { - fn transform(item: &VfsItem) -> PluginResult; + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> PluginResult; +} + +pub struct PluginChain { + plugins: Vec>, +} + +impl PluginChain { + pub fn new(plugins: Vec>) -> PluginChain { + PluginChain { + plugins, + } + } + + pub fn transform_file(&self, vfs_item: &VfsItem) -> Option { + for plugin in &self.plugins { + match plugin.transform_file(self, vfs_item) { + PluginResult::Value(rbx_item) => return rbx_item, + PluginResult::Pass => {}, + } + } + + None + } } diff --git a/src/plugins/default_plugin.rs b/src/plugins/default_plugin.rs index b5beeca6..9026f063 100644 --- a/src/plugins/default_plugin.rs +++ b/src/plugins/default_plugin.rs @@ -1,14 +1,20 @@ use std::collections::HashMap; -use plugin::{Plugin, PluginResult}; +use plugin::{Plugin, PluginChain, PluginResult}; use rbx::{RbxItem, RbxValue}; use vfs::VfsItem; pub struct DefaultPlugin; +impl DefaultPlugin { + pub fn new() -> DefaultPlugin { + DefaultPlugin + } +} + impl Plugin for DefaultPlugin { - fn transform(item: &VfsItem) -> PluginResult { - match item { + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> PluginResult { + match vfs_item { &VfsItem::File { ref contents, ref name } => { let mut properties = HashMap::new(); @@ -24,13 +30,11 @@ impl Plugin for DefaultPlugin { })) }, &VfsItem::Dir { ref children, ref name } => { - // TODO: call back into plugin list and transform there instead - let mut rbx_children = Vec::new(); for (_, child_item) in children { - match Self::transform(child_item) { - PluginResult::Value(Some(rbx_item)) => { + match plugins.transform_file(child_item) { + Some(rbx_item) => { rbx_children.push(rbx_item); }, _ => {}, diff --git a/src/web.rs b/src/web.rs index 670b0f0c..aa069f6d 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::io::Read; use std::sync::{Arc, Mutex}; use std::thread; @@ -6,13 +5,12 @@ use std::thread; use rouille; use serde; use serde_json; -use regex::Regex; use core::Config; use project::Project; -use vfs::{Vfs, VfsChange, VfsItem}; -use rbx::{RbxItem, RbxValue}; -use plugin::{Plugin, PluginResult}; +use vfs::{Vfs, VfsChange}; +use rbx::RbxItem; +use plugin::PluginChain; use plugins::DefaultPlugin; static MAX_BODY_SIZE: usize = 25 * 1024 * 1025; // 25 MiB @@ -103,6 +101,13 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { let server_id = config.server_id.to_string(); + // Fine, rouille, I'll put things in a 'static Arc... + lazy_static! { + static ref PLUGIN_CHAIN: Arc> = Arc::new(Mutex::new(PluginChain::new(vec![ + Box::new(DefaultPlugin::new()), + ]))); + } + thread::spawn(move || { rouille::start_server(address, move |request| { router!(request, @@ -135,6 +140,8 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { }, (POST) (/read) => { + let plugin_chain = PLUGIN_CHAIN.lock().unwrap(); + let read_request: Vec> = match read_json(&request) { Some(v) => v, None => return rouille::Response::empty_400(), @@ -161,12 +168,7 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { .iter() .map(|item| { match *item { - Some(ref item) => { - match DefaultPlugin::transform(item) { - PluginResult::Value(rbx_item) => rbx_item, - _ => None, - } - }, + Some(ref item) => plugin_chain.transform_file(item), None => None, } }) From 67ac6b7cec4afdd8c27bc072ce5b86800a16a71f Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 13 Dec 2017 23:58:53 -0800 Subject: [PATCH 04/14] First implementation of 'ScriptPlugin', which serves script files as scripts --- src/plugins/default_plugin.rs | 3 ++ src/plugins/mod.rs | 2 ++ src/plugins/script_plugin.rs | 57 +++++++++++++++++++++++++++++++++++ src/web.rs | 3 +- 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/plugins/script_plugin.rs diff --git a/src/plugins/default_plugin.rs b/src/plugins/default_plugin.rs index 9026f063..92ce1104 100644 --- a/src/plugins/default_plugin.rs +++ b/src/plugins/default_plugin.rs @@ -4,6 +4,9 @@ use plugin::{Plugin, PluginChain, PluginResult}; use rbx::{RbxItem, RbxValue}; use vfs::VfsItem; +/// A plugin with simple transforms: +/// * Directories become Folder instances +/// * Files become StringValue objects with 'Value' as their contents pub struct DefaultPlugin; impl DefaultPlugin { diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index c3b68bad..dd39b2db 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,3 +1,5 @@ mod default_plugin; +mod script_plugin; pub use self::default_plugin::*; +pub use self::script_plugin::*; diff --git a/src/plugins/script_plugin.rs b/src/plugins/script_plugin.rs new file mode 100644 index 00000000..2be88e25 --- /dev/null +++ b/src/plugins/script_plugin.rs @@ -0,0 +1,57 @@ +use std::collections::HashMap; + +use regex::Regex; + +use plugin::{Plugin, PluginChain, PluginResult}; +use rbx::{RbxItem, RbxValue}; +use vfs::VfsItem; + +lazy_static! { + static ref SERVER_PATTERN: Regex = Regex::new(r"^(.*?)\.server\.lua$").unwrap(); + static ref CLIENT_PATTERN: Regex = Regex::new(r"^(.*?)\.client\.lua$").unwrap(); + static ref MODULE_PATTERN: Regex = Regex::new(r"^(.*?)\.lua$").unwrap(); +} + +pub struct ScriptPlugin; + +impl ScriptPlugin { + pub fn new() -> ScriptPlugin { + ScriptPlugin + } +} + +impl Plugin for ScriptPlugin { + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> PluginResult { + match vfs_item { + &VfsItem::File { ref contents, ref name } => { + let (class_name, rbx_name) = { + if let Some(captures) = SERVER_PATTERN.captures(name) { + ("Script".to_string(), captures.get(1).unwrap().as_str().to_string()) + } else if let Some(captures) = CLIENT_PATTERN.captures(name) { + ("LocalScript".to_string(), captures.get(1).unwrap().as_str().to_string()) + } else if let Some(captures) = MODULE_PATTERN.captures(name) { + ("ModuleScript".to_string(), captures.get(1).unwrap().as_str().to_string()) + } else { + return PluginResult::Pass; + } + }; + + let mut properties = HashMap::new(); + + properties.insert("Source".to_string(), RbxValue::String { + value: contents.clone(), + }); + + PluginResult::Value(Some(RbxItem { + name: rbx_name, + class_name: class_name, + children: Vec::new(), + properties, + })) + }, + &VfsItem::Dir { ref children, ref name } => { + PluginResult::Pass + }, + } + } +} diff --git a/src/web.rs b/src/web.rs index aa069f6d..e6955c78 100644 --- a/src/web.rs +++ b/src/web.rs @@ -11,7 +11,7 @@ use project::Project; use vfs::{Vfs, VfsChange}; use rbx::RbxItem; use plugin::PluginChain; -use plugins::DefaultPlugin; +use plugins::{ScriptPlugin, DefaultPlugin}; static MAX_BODY_SIZE: usize = 25 * 1024 * 1025; // 25 MiB @@ -104,6 +104,7 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { // Fine, rouille, I'll put things in a 'static Arc... lazy_static! { static ref PLUGIN_CHAIN: Arc> = Arc::new(Mutex::new(PluginChain::new(vec![ + Box::new(ScriptPlugin::new()), Box::new(DefaultPlugin::new()), ]))); } From d365bc076e05f37e83b0af6e9044c001be080e00 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Thu, 14 Dec 2017 00:18:55 -0800 Subject: [PATCH 05/14] Add VfsItem::name to make comparisons easier --- src/vfs.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vfs.rs b/src/vfs.rs index 81fb9c04..8b39ced4 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -41,6 +41,15 @@ pub enum VfsItem { Dir { name: String, children: HashMap }, } +impl VfsItem { + pub fn name(&self) -> &String { + match self { + &VfsItem::File { ref name, .. } => name, + &VfsItem::Dir { ref name, .. } => name, + } + } +} + impl Vfs { pub fn new(config: Config) -> Vfs { Vfs { From 12bfcd7b663c35b2fe21f217e0e4eed1d7c526e7 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Thu, 14 Dec 2017 00:19:07 -0800 Subject: [PATCH 06/14] Implement init.lua support in ScriptPlugin --- src/plugins/script_plugin.rs | 41 +++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/plugins/script_plugin.rs b/src/plugins/script_plugin.rs index 2be88e25..bda36bcf 100644 --- a/src/plugins/script_plugin.rs +++ b/src/plugins/script_plugin.rs @@ -12,6 +12,10 @@ lazy_static! { static ref MODULE_PATTERN: Regex = Regex::new(r"^(.*?)\.lua$").unwrap(); } +static SERVER_INIT: &'static str = "init.server.lua"; +static CLIENT_INIT: &'static str = "init.client.lua"; +static MODULE_INIT: &'static str = "init.lua"; + pub struct ScriptPlugin; impl ScriptPlugin { @@ -50,7 +54,42 @@ impl Plugin for ScriptPlugin { })) }, &VfsItem::Dir { ref children, ref name } => { - PluginResult::Pass + let init_item = { + let maybe_item = children.get(SERVER_INIT) + .or(children.get(CLIENT_INIT)) + .or(children.get(MODULE_INIT)); + + match maybe_item { + Some(v) => v, + None => return PluginResult::Pass, + } + }; + + let mut rbx_item = match self.transform_file(plugins, init_item) { + PluginResult::Value(Some(item)) => item, + _ => { + eprintln!("Inconsistency detected in ScriptPlugin!"); + return PluginResult::Pass; + }, + }; + + rbx_item.name.clear(); + rbx_item.name.push_str(name); + + for (child_name, child_item) in children { + if child_name == init_item.name() { + continue; + } + + match plugins.transform_file(child_item) { + Some(child_rbx_item) => { + rbx_item.children.push(child_rbx_item); + }, + _ => {}, + } + } + + PluginResult::Value(Some(rbx_item)) }, } } From 6472a2cbcea47d6a08cc0db922b9dc8c11c0172a Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Sun, 17 Dec 2017 22:46:56 -0800 Subject: [PATCH 07/14] Add handle_file_change to plugin API This solves the problem I was running into with the ScriptPlugin implementation -- if foo/init.lua changes, foo needs to be requested, not 'foo/init.lua' (which would then erroneously create a ModuleScript before this commit) --- src/core.rs | 2 ++ src/plugin.rs | 15 ++++++++++---- src/plugins/default_plugin.rs | 13 ++++++++---- src/plugins/script_plugin.rs | 37 +++++++++++++++++++++++++++-------- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/core.rs b/src/core.rs index 6d83d3cd..8300fe0b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -4,3 +4,5 @@ pub struct Config { pub verbose: bool, pub server_id: u64, } + +pub type Route = Vec; diff --git a/src/plugin.rs b/src/plugin.rs index 6c2f6246..fa56a701 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,13 +1,20 @@ use rbx::RbxItem; use vfs::VfsItem; +use core::Route; -pub enum PluginResult { +pub enum TransformResult { Value(Option), Pass, } +pub enum FileChangeResult { + MarkChanged(Option>), + Pass, +} + pub trait Plugin { - fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> PluginResult; + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformResult; + fn handle_file_change(&self, route: &Route) -> FileChangeResult; } pub struct PluginChain { @@ -24,8 +31,8 @@ impl PluginChain { pub fn transform_file(&self, vfs_item: &VfsItem) -> Option { for plugin in &self.plugins { match plugin.transform_file(self, vfs_item) { - PluginResult::Value(rbx_item) => return rbx_item, - PluginResult::Pass => {}, + TransformResult::Value(rbx_item) => return rbx_item, + TransformResult::Pass => {}, } } diff --git a/src/plugins/default_plugin.rs b/src/plugins/default_plugin.rs index 92ce1104..b3ac5289 100644 --- a/src/plugins/default_plugin.rs +++ b/src/plugins/default_plugin.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use plugin::{Plugin, PluginChain, PluginResult}; +use core::Route; +use plugin::{Plugin, PluginChain, TransformResult, FileChangeResult}; use rbx::{RbxItem, RbxValue}; use vfs::VfsItem; @@ -16,7 +17,7 @@ impl DefaultPlugin { } impl Plugin for DefaultPlugin { - fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> PluginResult { + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformResult { match vfs_item { &VfsItem::File { ref contents, ref name } => { let mut properties = HashMap::new(); @@ -25,7 +26,7 @@ impl Plugin for DefaultPlugin { value: contents.clone(), }); - PluginResult::Value(Some(RbxItem { + TransformResult::Value(Some(RbxItem { name: name.clone(), class_name: "StringValue".to_string(), children: Vec::new(), @@ -44,7 +45,7 @@ impl Plugin for DefaultPlugin { } } - PluginResult::Value(Some(RbxItem { + TransformResult::Value(Some(RbxItem { name: name.clone(), class_name: "Folder".to_string(), children: rbx_children, @@ -53,4 +54,8 @@ impl Plugin for DefaultPlugin { }, } } + + fn handle_file_change(&self, route: &Route) -> FileChangeResult { + FileChangeResult::MarkChanged(Some(vec![route.clone()])) + } } diff --git a/src/plugins/script_plugin.rs b/src/plugins/script_plugin.rs index bda36bcf..75d78e42 100644 --- a/src/plugins/script_plugin.rs +++ b/src/plugins/script_plugin.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use regex::Regex; -use plugin::{Plugin, PluginChain, PluginResult}; +use core::Route; +use plugin::{Plugin, PluginChain, TransformResult, FileChangeResult}; use rbx::{RbxItem, RbxValue}; use vfs::VfsItem; @@ -25,7 +26,7 @@ impl ScriptPlugin { } impl Plugin for ScriptPlugin { - fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> PluginResult { + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformResult { match vfs_item { &VfsItem::File { ref contents, ref name } => { let (class_name, rbx_name) = { @@ -36,7 +37,7 @@ impl Plugin for ScriptPlugin { } else if let Some(captures) = MODULE_PATTERN.captures(name) { ("ModuleScript".to_string(), captures.get(1).unwrap().as_str().to_string()) } else { - return PluginResult::Pass; + return TransformResult::Pass; } }; @@ -46,7 +47,7 @@ impl Plugin for ScriptPlugin { value: contents.clone(), }); - PluginResult::Value(Some(RbxItem { + TransformResult::Value(Some(RbxItem { name: rbx_name, class_name: class_name, children: Vec::new(), @@ -61,15 +62,15 @@ impl Plugin for ScriptPlugin { match maybe_item { Some(v) => v, - None => return PluginResult::Pass, + None => return TransformResult::Pass, } }; let mut rbx_item = match self.transform_file(plugins, init_item) { - PluginResult::Value(Some(item)) => item, + TransformResult::Value(Some(item)) => item, _ => { eprintln!("Inconsistency detected in ScriptPlugin!"); - return PluginResult::Pass; + return TransformResult::Pass; }, }; @@ -89,8 +90,28 @@ impl Plugin for ScriptPlugin { } } - PluginResult::Value(Some(rbx_item)) + TransformResult::Value(Some(rbx_item)) }, } } + + fn handle_file_change(&self, route: &Route) -> FileChangeResult { + let leaf = match route.last() { + Some(v) => v, + None => return FileChangeResult::Pass, + }; + + let is_init = leaf == SERVER_INIT + || leaf == CLIENT_INIT + || leaf == MODULE_INIT; + + if is_init { + let mut changed = route.clone(); + changed.pop(); + + FileChangeResult::MarkChanged(Some(vec![changed])) + } else { + FileChangeResult::Pass + } + } } From f90c51e9237c2a20ec9f34534def2046a5f1be1b Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Mon, 18 Dec 2017 01:20:04 -0800 Subject: [PATCH 08/14] Move web server onto main thread --- src/bin.rs | 6 +-- src/web.rs | 138 ++++++++++++++++++++++++++--------------------------- 2 files changed, 69 insertions(+), 75 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index e759a753..18378c7f 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -196,13 +196,9 @@ fn main() { }); } - web::start(config.clone(), project.clone(), vfs.clone()); - println!("Server listening on port {}", port); - loop { - thread::park(); - } + web::start(config.clone(), project.clone(), vfs.clone()); }, ("pack", _) => { eprintln!("'rojo pack' is not yet implemented!"); diff --git a/src/web.rs b/src/web.rs index e6955c78..f15dc7e1 100644 --- a/src/web.rs +++ b/src/web.rs @@ -109,85 +109,83 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { ]))); } - thread::spawn(move || { - rouille::start_server(address, move |request| { - router!(request, - (GET) (/) => { - let current_time = { - let vfs = vfs.lock().unwrap(); - - vfs.current_time() - }; - - json(ServerInfo { - server_version: env!("CARGO_PKG_VERSION"), - protocol_version: 1, - server_id: &server_id, - project: &project, - current_time, - }) - }, - - (GET) (/changes/{ last_time: f64 }) => { + rouille::start_server(address, move |request| { + router!(request, + (GET) (/) => { + let current_time = { let vfs = vfs.lock().unwrap(); + + vfs.current_time() + }; + + json(ServerInfo { + server_version: env!("CARGO_PKG_VERSION"), + protocol_version: 1, + server_id: &server_id, + project: &project, + current_time, + }) + }, + + (GET) (/changes/{ last_time: f64 }) => { + let vfs = vfs.lock().unwrap(); + let current_time = vfs.current_time(); + let changes = vfs.changes_since(last_time); + + json(ChangesResult { + changes, + server_id: &server_id, + current_time, + }) + }, + + (POST) (/read) => { + let plugin_chain = PLUGIN_CHAIN.lock().unwrap(); + + let read_request: Vec> = match read_json(&request) { + Some(v) => v, + None => return rouille::Response::empty_400(), + }; + + let (items, current_time) = { + let vfs = vfs.lock().unwrap(); + let current_time = vfs.current_time(); - let changes = vfs.changes_since(last_time); - json(ChangesResult { - changes, - server_id: &server_id, - current_time, - }) - }, + let mut items = Vec::new(); - (POST) (/read) => { - let plugin_chain = PLUGIN_CHAIN.lock().unwrap(); - - let read_request: Vec> = match read_json(&request) { - Some(v) => v, - None => return rouille::Response::empty_400(), - }; - - let (items, current_time) = { - let vfs = vfs.lock().unwrap(); - - let current_time = vfs.current_time(); - - let mut items = Vec::new(); - - for route in &read_request { - match vfs.read(&route) { - Ok(v) => items.push(Some(v)), - Err(_) => items.push(None), - } + for route in &read_request { + match vfs.read(&route) { + Ok(v) => items.push(Some(v)), + Err(_) => items.push(None), } + } - (items, current_time) - }; + (items, current_time) + }; - let rbx_items = items - .iter() - .map(|item| { - match *item { - Some(ref item) => plugin_chain.transform_file(item), - None => None, - } - }) - .collect::>(); - - json(ReadResult { - server_id: &server_id, - items: rbx_items, - current_time, + let rbx_items = items + .iter() + .map(|item| { + match *item { + Some(ref item) => plugin_chain.transform_file(item), + None => None, + } }) - }, + .collect::>(); - (POST) (/write) => { - rouille::Response::empty_404() - }, + json(ReadResult { + server_id: &server_id, + items: rbx_items, + current_time, + }) + }, - _ => rouille::Response::empty_404() - ) - }); + (POST) (/write) => { + rouille::Response::empty_404() + }, + + _ => rouille::Response::empty_404() + ) }); } From 6ee9a48e20b990ec785a00f377d3f66f1c42bb4a Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Mon, 18 Dec 2017 01:52:08 -0800 Subject: [PATCH 09/14] Use plugin chain code in Vfs --- src/bin.rs | 11 ++++++++++- src/plugin.rs | 15 +++++++++++++-- src/vfs.rs | 23 +++++++++++++++++------ src/web.rs | 1 - 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 18378c7f..57520201 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -33,6 +33,8 @@ use std::thread; use core::Config; use pathext::canonicalish; use project::{Project, ProjectLoadError}; +use plugin::{PluginChain}; +use plugins::{DefaultPlugin, ScriptPlugin}; use vfs::Vfs; use vfs_watch::VfsWatcher; @@ -148,6 +150,13 @@ fn main() { } }; + lazy_static! { + static ref PLUGIN_CHAIN: PluginChain = PluginChain::new(vec![ + Box::new(ScriptPlugin::new()), + Box::new(DefaultPlugin::new()), + ]); + } + let config = Config { port, verbose, @@ -159,7 +168,7 @@ fn main() { } let vfs = { - let mut vfs = Vfs::new(config.clone()); + let mut vfs = Vfs::new(config.clone(), &PLUGIN_CHAIN); for (name, project_partition) in &project.partitions { let path = { diff --git a/src/plugin.rs b/src/plugin.rs index fa56a701..cd0c90a8 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -18,11 +18,11 @@ pub trait Plugin { } pub struct PluginChain { - plugins: Vec>, + plugins: Vec>, } impl PluginChain { - pub fn new(plugins: Vec>) -> PluginChain { + pub fn new(plugins: Vec>) -> PluginChain { PluginChain { plugins, } @@ -38,4 +38,15 @@ impl PluginChain { None } + + pub fn handle_file_change(&self, route: &Route) -> Option> { + for plugin in &self.plugins { + match plugin.handle_file_change(route) { + FileChangeResult::MarkChanged(changes) => return changes, + FileChangeResult::Pass => {}, + } + } + + None + } } diff --git a/src/vfs.rs b/src/vfs.rs index 8b39ced4..483a93b2 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; use std::time::Instant; use core::Config; +use plugin::PluginChain; /// Represents a virtual layer over multiple parts of the filesystem. /// @@ -24,6 +25,8 @@ pub struct Vfs { /// created, along with a timestamp denoting when. pub change_history: Vec, + plugin_chain: &'static PluginChain, + config: Config, } @@ -51,11 +54,12 @@ impl VfsItem { } impl Vfs { - pub fn new(config: Config) -> Vfs { + pub fn new(config: Config, plugin_chain: &'static PluginChain) -> Vfs { Vfs { partitions: HashMap::new(), start_time: Instant::now(), change_history: Vec::new(), + plugin_chain, config, } } @@ -164,13 +168,20 @@ impl Vfs { pub fn add_change(&mut self, timestamp: f64, route: Vec) { if self.config.verbose { - println!("Added change {:?}", route); + println!("Received change {:?}, running through plugins...", route); } - self.change_history.push(VfsChange { - timestamp, - route, - }); + match self.plugin_chain.handle_file_change(&route) { + Some(routes) => { + for route in routes { + self.change_history.push(VfsChange { + timestamp, + route, + }); + } + }, + None => {} + } } pub fn changes_since(&self, timestamp: f64) -> &[VfsChange] { diff --git a/src/web.rs b/src/web.rs index f15dc7e1..2a0e1118 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,6 +1,5 @@ use std::io::Read; use std::sync::{Arc, Mutex}; -use std::thread; use rouille; use serde; From 0f78eb933a3a5202ae41b80281e899ee876325b6 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 20 Dec 2017 22:00:01 -0800 Subject: [PATCH 10/14] DESIGN doc, stub out /write endpoint --- DESIGN.md | 20 ++++++++++++++++++++ src/rbx.rs | 4 ++-- src/web.rs | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 00000000..a3366429 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,20 @@ +# Rojo Design +This is a super rough draft that I'm trying to use to lay out of my thoughts. + +## API + +### POST `/read` +Accepts a `Vec` of items to read. + +Returns `Vec>`, in the same order as the request. + +### POST `/write` +Accepts a `Vec<{ Route, RbxItem }>` of items to write. + +I imagine that the `Name` attribute of the top-level `RbxItem` would be ignored in favor of the route name? + +## CLI + +### Transform Plugins + +## Roblox Studio Plugin \ No newline at end of file diff --git a/src/rbx.rs b/src/rbx.rs index 043dc3b6..234529ca 100644 --- a/src/rbx.rs +++ b/src/rbx.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RbxItem { pub name: String, @@ -9,7 +9,7 @@ pub struct RbxItem { pub properties: HashMap, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum RbxValue { String { diff --git a/src/web.rs b/src/web.rs index 2a0e1118..f4237a1e 100644 --- a/src/web.rs +++ b/src/web.rs @@ -40,6 +40,13 @@ struct ChangesResult<'a> { current_time: f64, } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct WriteSpecifier { + route: String, + item: RbxItem, +} + fn json(value: T) -> rouille::Response { let data = serde_json::to_string(&value).unwrap(); rouille::Response::from_data("application/json", data) @@ -181,6 +188,13 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { }, (POST) (/write) => { + let _plugin_chain = PLUGIN_CHAIN.lock().unwrap(); + + let _write_request: Vec = match read_json(&request) { + Some(v) => v, + None => return rouille::Response::empty_400(), + }; + rouille::Response::empty_404() }, From b885cae0864c662d604dd482efa4d9a705596418 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 20 Dec 2017 22:01:38 -0800 Subject: [PATCH 11/14] Rename TransformResult -> TransformFileResult in preparation for two-way syncing --- src/plugin.rs | 8 ++++---- src/plugins/default_plugin.rs | 8 ++++---- src/plugins/script_plugin.rs | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/plugin.rs b/src/plugin.rs index cd0c90a8..3686ffb8 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,7 +2,7 @@ use rbx::RbxItem; use vfs::VfsItem; use core::Route; -pub enum TransformResult { +pub enum TransformFileResult { Value(Option), Pass, } @@ -13,7 +13,7 @@ pub enum FileChangeResult { } pub trait Plugin { - fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformResult; + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult; fn handle_file_change(&self, route: &Route) -> FileChangeResult; } @@ -31,8 +31,8 @@ impl PluginChain { pub fn transform_file(&self, vfs_item: &VfsItem) -> Option { for plugin in &self.plugins { match plugin.transform_file(self, vfs_item) { - TransformResult::Value(rbx_item) => return rbx_item, - TransformResult::Pass => {}, + TransformFileResult::Value(rbx_item) => return rbx_item, + TransformFileResult::Pass => {}, } } diff --git a/src/plugins/default_plugin.rs b/src/plugins/default_plugin.rs index b3ac5289..11bab5a9 100644 --- a/src/plugins/default_plugin.rs +++ b/src/plugins/default_plugin.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use core::Route; -use plugin::{Plugin, PluginChain, TransformResult, FileChangeResult}; +use plugin::{Plugin, PluginChain, TransformFileResult, FileChangeResult}; use rbx::{RbxItem, RbxValue}; use vfs::VfsItem; @@ -17,7 +17,7 @@ impl DefaultPlugin { } impl Plugin for DefaultPlugin { - fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformResult { + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult { match vfs_item { &VfsItem::File { ref contents, ref name } => { let mut properties = HashMap::new(); @@ -26,7 +26,7 @@ impl Plugin for DefaultPlugin { value: contents.clone(), }); - TransformResult::Value(Some(RbxItem { + TransformFileResult::Value(Some(RbxItem { name: name.clone(), class_name: "StringValue".to_string(), children: Vec::new(), @@ -45,7 +45,7 @@ impl Plugin for DefaultPlugin { } } - TransformResult::Value(Some(RbxItem { + TransformFileResult::Value(Some(RbxItem { name: name.clone(), class_name: "Folder".to_string(), children: rbx_children, diff --git a/src/plugins/script_plugin.rs b/src/plugins/script_plugin.rs index 75d78e42..a6932392 100644 --- a/src/plugins/script_plugin.rs +++ b/src/plugins/script_plugin.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use regex::Regex; use core::Route; -use plugin::{Plugin, PluginChain, TransformResult, FileChangeResult}; +use plugin::{Plugin, PluginChain, TransformFileResult, FileChangeResult}; use rbx::{RbxItem, RbxValue}; use vfs::VfsItem; @@ -26,7 +26,7 @@ impl ScriptPlugin { } impl Plugin for ScriptPlugin { - fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformResult { + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult { match vfs_item { &VfsItem::File { ref contents, ref name } => { let (class_name, rbx_name) = { @@ -37,7 +37,7 @@ impl Plugin for ScriptPlugin { } else if let Some(captures) = MODULE_PATTERN.captures(name) { ("ModuleScript".to_string(), captures.get(1).unwrap().as_str().to_string()) } else { - return TransformResult::Pass; + return TransformFileResult::Pass; } }; @@ -47,7 +47,7 @@ impl Plugin for ScriptPlugin { value: contents.clone(), }); - TransformResult::Value(Some(RbxItem { + TransformFileResult::Value(Some(RbxItem { name: rbx_name, class_name: class_name, children: Vec::new(), @@ -62,15 +62,15 @@ impl Plugin for ScriptPlugin { match maybe_item { Some(v) => v, - None => return TransformResult::Pass, + None => return TransformFileResult::Pass, } }; let mut rbx_item = match self.transform_file(plugins, init_item) { - TransformResult::Value(Some(item)) => item, + TransformFileResult::Value(Some(item)) => item, _ => { eprintln!("Inconsistency detected in ScriptPlugin!"); - return TransformResult::Pass; + return TransformFileResult::Pass; }, }; @@ -90,7 +90,7 @@ impl Plugin for ScriptPlugin { } } - TransformResult::Value(Some(rbx_item)) + TransformFileResult::Value(Some(rbx_item)) }, } } From aaaf3ba0b9253278aa103debdb37e2395b21003f Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 20 Dec 2017 22:11:46 -0800 Subject: [PATCH 12/14] Implement handle_rbx_change API for plugins as a pass --- src/plugin.rs | 17 +++++++++++++++++ src/plugins/default_plugin.rs | 6 +++++- src/plugins/script_plugin.rs | 6 +++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/plugin.rs b/src/plugin.rs index 3686ffb8..2a6e34a6 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -7,6 +7,11 @@ pub enum TransformFileResult { Pass, } +pub enum RbxChangeResult { + Write(Option), + Pass, +} + pub enum FileChangeResult { MarkChanged(Option>), Pass, @@ -14,6 +19,7 @@ pub enum FileChangeResult { pub trait Plugin { fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult; + fn handle_rbx_change(&self, route: &Route, rbx_item: &RbxItem) -> RbxChangeResult; fn handle_file_change(&self, route: &Route) -> FileChangeResult; } @@ -39,6 +45,17 @@ impl PluginChain { None } + pub fn handle_rbx_change(&self, route: &Route, rbx_item: &RbxItem) -> Option { + for plugin in &self.plugins { + match plugin.handle_rbx_change(route, rbx_item) { + RbxChangeResult::Write(vfs_item) => return vfs_item, + RbxChangeResult::Pass => {}, + } + } + + None + } + pub fn handle_file_change(&self, route: &Route) -> Option> { for plugin in &self.plugins { match plugin.handle_file_change(route) { diff --git a/src/plugins/default_plugin.rs b/src/plugins/default_plugin.rs index 11bab5a9..b9503877 100644 --- a/src/plugins/default_plugin.rs +++ b/src/plugins/default_plugin.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use core::Route; -use plugin::{Plugin, PluginChain, TransformFileResult, FileChangeResult}; +use plugin::{Plugin, PluginChain, TransformFileResult, RbxChangeResult, FileChangeResult}; use rbx::{RbxItem, RbxValue}; use vfs::VfsItem; @@ -58,4 +58,8 @@ impl Plugin for DefaultPlugin { fn handle_file_change(&self, route: &Route) -> FileChangeResult { FileChangeResult::MarkChanged(Some(vec![route.clone()])) } + + fn handle_rbx_change(&self, route: &Route, rbx_item: &RbxItem) -> RbxChangeResult { + RbxChangeResult::Pass + } } diff --git a/src/plugins/script_plugin.rs b/src/plugins/script_plugin.rs index a6932392..70cdc059 100644 --- a/src/plugins/script_plugin.rs +++ b/src/plugins/script_plugin.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use regex::Regex; use core::Route; -use plugin::{Plugin, PluginChain, TransformFileResult, FileChangeResult}; +use plugin::{Plugin, PluginChain, TransformFileResult, RbxChangeResult, FileChangeResult}; use rbx::{RbxItem, RbxValue}; use vfs::VfsItem; @@ -114,4 +114,8 @@ impl Plugin for ScriptPlugin { FileChangeResult::Pass } } + + fn handle_rbx_change(&self, route: &Route, rbx_item: &RbxItem) -> RbxChangeResult { + RbxChangeResult::Pass + } } From 95581dbaa6a3b60ab61643105b247ae21e0687c8 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 20 Dec 2017 22:35:26 -0800 Subject: [PATCH 13/14] Pass common plugin chain into web handler --- src/bin.rs | 2 +- src/web.rs | 15 +-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 57520201..4cc0aa1c 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -207,7 +207,7 @@ fn main() { println!("Server listening on port {}", port); - web::start(config.clone(), project.clone(), vfs.clone()); + web::start(config.clone(), project.clone(), &PLUGIN_CHAIN, vfs.clone()); }, ("pack", _) => { eprintln!("'rojo pack' is not yet implemented!"); diff --git a/src/web.rs b/src/web.rs index f4237a1e..c9aef861 100644 --- a/src/web.rs +++ b/src/web.rs @@ -10,7 +10,6 @@ use project::Project; use vfs::{Vfs, VfsChange}; use rbx::RbxItem; use plugin::PluginChain; -use plugins::{ScriptPlugin, DefaultPlugin}; static MAX_BODY_SIZE: usize = 25 * 1024 * 1025; // 25 MiB @@ -102,19 +101,11 @@ where Some(parsed) } -pub fn start(config: Config, project: Project, vfs: Arc>) { +pub fn start(config: Config, project: Project, plugin_chain: &'static PluginChain, vfs: Arc>) { let address = format!("localhost:{}", config.port); let server_id = config.server_id.to_string(); - // Fine, rouille, I'll put things in a 'static Arc... - lazy_static! { - static ref PLUGIN_CHAIN: Arc> = Arc::new(Mutex::new(PluginChain::new(vec![ - Box::new(ScriptPlugin::new()), - Box::new(DefaultPlugin::new()), - ]))); - } - rouille::start_server(address, move |request| { router!(request, (GET) (/) => { @@ -146,8 +137,6 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { }, (POST) (/read) => { - let plugin_chain = PLUGIN_CHAIN.lock().unwrap(); - let read_request: Vec> = match read_json(&request) { Some(v) => v, None => return rouille::Response::empty_400(), @@ -188,8 +177,6 @@ pub fn start(config: Config, project: Project, vfs: Arc>) { }, (POST) (/write) => { - let _plugin_chain = PLUGIN_CHAIN.lock().unwrap(); - let _write_request: Vec = match read_json(&request) { Some(v) => v, None => return rouille::Response::empty_400(), From 68ba3fee6c6857192367991640ae7025fea74ba1 Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Wed, 20 Dec 2017 22:36:12 -0800 Subject: [PATCH 14/14] Fix max body size typo --- src/web.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web.rs b/src/web.rs index c9aef861..65024b18 100644 --- a/src/web.rs +++ b/src/web.rs @@ -11,7 +11,7 @@ use vfs::{Vfs, VfsChange}; use rbx::RbxItem; use plugin::PluginChain; -static MAX_BODY_SIZE: usize = 25 * 1024 * 1025; // 25 MiB +static MAX_BODY_SIZE: usize = 25 * 1024 * 1024; // 25 MiB #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")]