diff --git a/Cargo.lock b/Cargo.lock index 54425c46..fa8723f8 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.2" 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 bc492d2b..cab5b988 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/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/bin.rs b/src/bin.rs index b04500a9..4cc0aa1c 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; @@ -18,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}; @@ -26,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; @@ -141,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, @@ -152,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 = { @@ -189,13 +205,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(), &PLUGIN_CHAIN, vfs.clone()); }, ("pack", _) => { eprintln!("'rojo pack' is not yet implemented!"); 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 new file mode 100644 index 00000000..2a6e34a6 --- /dev/null +++ b/src/plugin.rs @@ -0,0 +1,69 @@ +use rbx::RbxItem; +use vfs::VfsItem; +use core::Route; + +pub enum TransformFileResult { + Value(Option), + Pass, +} + +pub enum RbxChangeResult { + Write(Option), + Pass, +} + +pub enum FileChangeResult { + MarkChanged(Option>), + Pass, +} + +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; +} + +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) { + TransformFileResult::Value(rbx_item) => return rbx_item, + TransformFileResult::Pass => {}, + } + } + + 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) { + FileChangeResult::MarkChanged(changes) => return changes, + FileChangeResult::Pass => {}, + } + } + + None + } +} diff --git a/src/plugins/default_plugin.rs b/src/plugins/default_plugin.rs new file mode 100644 index 00000000..b9503877 --- /dev/null +++ b/src/plugins/default_plugin.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use core::Route; +use plugin::{Plugin, PluginChain, TransformFileResult, RbxChangeResult, FileChangeResult}; +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 { + pub fn new() -> DefaultPlugin { + DefaultPlugin + } +} + +impl Plugin for DefaultPlugin { + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult { + match vfs_item { + &VfsItem::File { ref contents, ref name } => { + let mut properties = HashMap::new(); + + properties.insert("Value".to_string(), RbxValue::String { + value: contents.clone(), + }); + + TransformFileResult::Value(Some(RbxItem { + name: name.clone(), + class_name: "StringValue".to_string(), + children: Vec::new(), + properties, + })) + }, + &VfsItem::Dir { ref children, ref name } => { + let mut rbx_children = Vec::new(); + + for (_, child_item) in children { + match plugins.transform_file(child_item) { + Some(rbx_item) => { + rbx_children.push(rbx_item); + }, + _ => {}, + } + } + + TransformFileResult::Value(Some(RbxItem { + name: name.clone(), + class_name: "Folder".to_string(), + children: rbx_children, + properties: HashMap::new(), + })) + }, + } + } + + 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/mod.rs b/src/plugins/mod.rs new file mode 100644 index 00000000..dd39b2db --- /dev/null +++ b/src/plugins/mod.rs @@ -0,0 +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..70cdc059 --- /dev/null +++ b/src/plugins/script_plugin.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; + +use regex::Regex; + +use core::Route; +use plugin::{Plugin, PluginChain, TransformFileResult, RbxChangeResult, FileChangeResult}; +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(); +} + +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 { + pub fn new() -> ScriptPlugin { + ScriptPlugin + } +} + +impl Plugin for ScriptPlugin { + fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult { + 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 TransformFileResult::Pass; + } + }; + + let mut properties = HashMap::new(); + + properties.insert("Source".to_string(), RbxValue::String { + value: contents.clone(), + }); + + TransformFileResult::Value(Some(RbxItem { + name: rbx_name, + class_name: class_name, + children: Vec::new(), + properties, + })) + }, + &VfsItem::Dir { ref children, ref name } => { + 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 TransformFileResult::Pass, + } + }; + + let mut rbx_item = match self.transform_file(plugins, init_item) { + TransformFileResult::Value(Some(item)) => item, + _ => { + eprintln!("Inconsistency detected in ScriptPlugin!"); + return TransformFileResult::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); + }, + _ => {}, + } + } + + TransformFileResult::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 + } + } + + fn handle_rbx_change(&self, route: &Route, rbx_item: &RbxItem) -> RbxChangeResult { + RbxChangeResult::Pass + } +} diff --git a/src/rbx.rs b/src/rbx.rs new file mode 100644 index 00000000..234529ca --- /dev/null +++ b/src/rbx.rs @@ -0,0 +1,18 @@ +use std::collections::HashMap; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RbxItem { + pub name: String, + pub class_name: String, + pub children: Vec, + pub properties: HashMap, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type")] +pub enum RbxValue { + String { + value: String, + }, +} diff --git a/src/vfs.rs b/src/vfs.rs index 44b9165f..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, } @@ -37,16 +40,26 @@ 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 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 { + 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, } } @@ -78,6 +91,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 +118,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 +138,7 @@ impl Vfs { } Ok(VfsItem::File { + name: path.file_name().unwrap().to_string_lossy().into_owned(), contents, }) } @@ -151,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 12c936ae..65024b18 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; @@ -8,9 +7,11 @@ use serde_json; use core::Config; use project::Project; -use vfs::{Vfs, VfsChange, VfsItem}; +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")] @@ -25,7 +26,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, } @@ -38,6 +39,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) @@ -93,78 +101,91 @@ 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(); - 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: 0, - 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 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 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) + }; - json(ReadResult { - server_id: &server_id, - 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) => { + let _write_request: Vec = match read_json(&request) { + Some(v) => v, + None => return rouille::Response::empty_400(), + }; + + rouille::Response::empty_404() + }, + + _ => rouille::Response::empty_404() + ) }); }