diff --git a/server/src/commands/serve.rs b/server/src/commands/serve.rs index 3659ad87..6c0fa507 100644 --- a/server/src/commands/serve.rs +++ b/server/src/commands/serve.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::path::PathBuf; use std::process; -use std::sync::{Arc, Mutex}; use std::thread; use std::time::Instant; @@ -9,8 +8,7 @@ use rand; use project::Project; use web; -use session_config::SessionConfig; -use fs_session::FsSession; +use session::{Session, SessionConfig}; use partition::Partition; pub fn serve(project_path: &PathBuf, port: Option) { @@ -47,6 +45,7 @@ pub fn serve(project_path: &PathBuf, port: Option) { partitions.insert(partition_name.clone(), Partition { path, target, + name: partition_name.clone(), }); } @@ -56,20 +55,8 @@ pub fn serve(project_path: &PathBuf, port: Option) { println!("Using session config {:#?}", config); - let session = { - let mut session = FsSession::new(config.clone()); - session.init(); - - Arc::new(Mutex::new(session)) - }; - - // TODO: Let FsSession handle the main loop and spawn a bunch of threads - // ...since Rust stable doesn't have a channel select implementation - thread::spawn(move || { - loop { - session.lock().unwrap().step(); - } - }); + let mut session = Session::new(config.clone()); + session.start(); loop { thread::park(); diff --git a/server/src/file_route.rs b/server/src/file_route.rs index f1e500ec..30ef043d0 100644 --- a/server/src/file_route.rs +++ b/server/src/file_route.rs @@ -2,8 +2,6 @@ use std::path::{Path, PathBuf}; use partition::Partition; -// TODO: Add lifetime, switch to using Cow<'a, str> instead of String? It's -// possible that it would be too cumbersome! #[derive(Debug, Clone, PartialEq, Hash)] pub struct FileRoute { pub partition: String, @@ -43,7 +41,7 @@ impl FileRoute { // This FileRoute refers to the partition itself if partition.target.len() == 0 { - // We're targeting the game itself! + // We're targeting the game! "game".to_string() } else { partition.target.last().unwrap().clone() diff --git a/server/src/lib.rs b/server/src/lib.rs index 10d5a995..baccab96 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -12,13 +12,14 @@ extern crate tempfile; pub mod commands; pub mod file_route; -pub mod fs_session; pub mod id; pub mod partition; +pub mod partition_watcher; pub mod pathext; pub mod project; pub mod rbx; -// pub mod session; -pub mod session_config; +pub mod rbx_session; +pub mod session; +pub mod vfs_session; pub mod web; pub mod web_util; diff --git a/server/src/partition.rs b/server/src/partition.rs index 5e9d86e8..9ef66664 100644 --- a/server/src/partition.rs +++ b/server/src/partition.rs @@ -2,6 +2,9 @@ use std::path::PathBuf; #[derive(Debug, Clone, PartialEq)] pub struct Partition { + /// The unique name of this partition, used for debugging. + pub name: String, + /// The path on the filesystem that this partition maps to. pub path: PathBuf, diff --git a/server/src/partition_watcher.rs b/server/src/partition_watcher.rs new file mode 100644 index 00000000..f147954b --- /dev/null +++ b/server/src/partition_watcher.rs @@ -0,0 +1,44 @@ +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::time::Duration; +use std::thread; + +use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher, watcher}; + +use partition::Partition; + +pub struct PartitionWatcher { + pub watcher: RecommendedWatcher, + pub partition: Partition, +} + +impl PartitionWatcher { + pub fn start_new(partition: Partition, tx: Sender<(String, DebouncedEvent)>) -> PartitionWatcher { + let (watch_tx, watch_rx) = channel(); + + let mut watcher = watcher(watch_tx, Duration::from_millis(100)).unwrap(); + + watcher.watch(&partition.path, RecursiveMode::Recursive).unwrap(); + + let partition_name = partition.name.clone(); + thread::spawn(move || { + loop { + match watch_rx.recv() { + // TODO: Transform DebouncedEvent to some sort of FileChange object + Ok(event) => match tx.send((partition_name.clone(), event)) { + Ok(_) => {}, + Err(_) => break, + }, + Err(_) => break, + }; + } + }); + + PartitionWatcher { + partition, + watcher, + } + } + + pub fn stop(self) { + } +} diff --git a/server/src/rbx_session.rs b/server/src/rbx_session.rs new file mode 100644 index 00000000..df3dbfbb --- /dev/null +++ b/server/src/rbx_session.rs @@ -0,0 +1,43 @@ +use std::collections::HashMap; + +use id::Id; +use partition::Partition; +use rbx::RbxInstance; +use session::SessionConfig; + +pub struct RbxSession { + pub config: SessionConfig, + + /// The RbxInstance that represents each partition. + pub partition_instances: HashMap, + + /// The store of all instances in the session. + pub instances: HashMap, + + // pub instances_by_route: HashMap, +} + +impl RbxSession { + pub fn new(config: SessionConfig) -> RbxSession { + RbxSession { + config, + partition_instances: HashMap::new(), + instances: HashMap::new(), + } + } + + // fn load_instances(&mut self) { + // for (partition_name, file_item) in &self.partition_files { + // let partition = self.partitions.get(partition_name).unwrap(); + // let (root_id, mut instances) = file_to_instance(&file_item, partition); + + // // there has to be an std method for this + // // oh well + // for (instance_id, instance) in instances.drain() { + // self.instances.insert(instance_id, instance); + // } + + // self.partition_instances.insert(partition_name.clone(), root_id); + // } + // } +} diff --git a/server/src/session.rs b/server/src/session.rs index f711dbd1..12244b28 100644 --- a/server/src/session.rs +++ b/server/src/session.rs @@ -1,49 +1,45 @@ use std::collections::HashMap; -use std::io::Read; -use std::fs::{self, File}; +use std::sync::{mpsc, Arc, RwLock}; +use std::thread; use id::{Id, get_id}; use file_route::FileRoute; use partition::Partition; use rbx::RbxInstance; -use fs_session::FsSession; -use session_config::SessionConfig; +use rbx_session::RbxSession; +use vfs_session::{VfsSession, FileItem}; +use partition_watcher::PartitionWatcher; -pub struct Session<'a> { - pub config: &'a SessionConfig, - - /// The RbxInstance that represents each partition. - pub partition_instances: HashMap, - - /// The store of all instances in the session. - pub instances: HashMap, - - // pub instances_by_route: HashMap, +#[derive(Debug, Clone, PartialEq)] +pub struct SessionConfig { + pub partitions: HashMap, } -fn file_to_instance(file_item: &FileItem, partition: &Partition) -> (Id, HashMap) { +/// Stub trait for middleware +trait Middleware { +} + +fn file_to_instances(file_item: &FileItem, partition: &Partition, output: &mut HashMap) -> Id { match file_item { &FileItem::File { ref contents, ref route } => { let mut properties = HashMap::new(); properties.insert("Value".to_string(), contents.clone()); let primary_id = get_id(); - let mut instances = HashMap::new(); - instances.insert(primary_id, RbxInstance { + output.insert(primary_id, RbxInstance { name: route.name(partition).to_string(), class_name: "StringValue".to_string(), parent: None, properties, }); - (primary_id, instances) + primary_id }, &FileItem::Directory { ref children, ref route } => { let primary_id = get_id(); - let mut instances = HashMap::new(); - instances.insert(primary_id, RbxInstance { + output.insert(primary_id, RbxInstance { name: route.name(partition).to_string(), class_name: "Folder".to_string(), parent: None, @@ -51,115 +47,66 @@ fn file_to_instance(file_item: &FileItem, partition: &Partition) -> (Id, HashMap }); for child_file_item in children.values() { - let (child_id, mut child_instances) = file_to_instance(child_file_item, partition); + let mut child_instances = HashMap::new(); + let child_id = file_to_instances(child_file_item, partition, &mut child_instances); child_instances.get_mut(&child_id).unwrap().parent = Some(primary_id); for (instance_id, instance) in child_instances.drain() { - instances.insert(instance_id, instance); + output.insert(instance_id, instance); } } - (primary_id, instances) + primary_id } } } +pub struct Session { + config: SessionConfig, + vfs_session: Arc>, + rbx_session: Arc>, + middlewares: Vec>, + watchers: Vec, +} + impl Session { - pub fn new() -> Session { + pub fn new(config: SessionConfig) -> Session { Session { - partitions: HashMap::new(), - partition_instances: HashMap::new(), - instances: HashMap::new(), + vfs_session: Arc::new(RwLock::new(VfsSession::new(config.clone()))), + rbx_session: Arc::new(RwLock::new(RbxSession::new(config.clone()))), + middlewares: Vec::new(), + watchers: Vec::new(), + config, } } - pub fn init(&mut self) { - self.load_files(); - self.load_instances(); - } + pub fn start(&mut self) { + let (tx, rx) = mpsc::channel(); - fn load_instances(&mut self) { - for (partition_name, file_item) in &self.partition_files { - let partition = self.partitions.get(partition_name).unwrap(); - let (root_id, mut instances) = file_to_instance(&file_item, partition); + for partition in self.config.partitions.values() { + let watcher = PartitionWatcher::start_new(partition.clone(), tx.clone()); - // there has to be an std method for this - // oh well - for (instance_id, instance) in instances.drain() { - self.instances.insert(instance_id, instance); + self.watchers.push(watcher); + } + + thread::spawn(move || { + loop { + match rx.recv() { + Ok((partition_name, change)) => { + println!("Got change {:?} on partition {}", change, partition_name); + }, + Err(_) => break, + } } + }); + } - self.partition_instances.insert(partition_name.clone(), root_id); - } + pub fn get_vfs_session(&self) -> Arc> { + self.vfs_session.clone() + } + + pub fn get_rbx_session(&self) -> Arc> { + self.rbx_session.clone() } } - -#[cfg(test)] -mod tests { - use super::*; - use tempfile; - - // I'm not exactly sure how I want to structure these tests - // Essentially, I need a bunch of random files to load, and to measure: - // * What FileItems were loaded? - // * Are changes logged to those FileItems correctly? - // * What RbxInstance objects are generated from them? - // * Are changes propagated from FileItem through to those RbxInstances? - - // #[test] - // fn file_items_correct() { - // use std::io::Write; - - // let root_dir = tempfile::tempdir().unwrap(); - - // let foo_path = root_dir.path().join("foo.txt"); - // let bar_path = root_dir.path().join("bar.tsv"); - - // { - // let mut foo = File::create(foo_path).unwrap(); - // writeln!(foo, "Hello, foo!").unwrap(); - - // let mut bar = File::create(bar_path).unwrap(); - // writeln!(bar, "Hello, bar!").unwrap(); - // } - - // let mut session = Session::new(); - - // let partition = Partition { - // path: root_dir.path().to_path_buf(), - // target: vec!["ReplicatedStorage".to_string()], - // }; - - // session.partitions.insert("agh".to_string(), partition); - - // session.load_files(); - - // assert_eq!(session.partition_files.len(), 1); - - // { - // let folder = session.partition_files.values().nth(0).unwrap(); - - // let children = match folder { - // &FileItem::Directory { ref children, .. } => children, - // _ => panic!("Not a directory!"), - // }; - - // assert_eq!(children.len(), 2); - // assert!(children.get("foo.txt").is_some()); - // assert!(children.get("bar.tsv").is_some()); - // } - - // session.load_instances(); - - // assert_eq!(session.instances.len(), 3); - // assert_eq!(session.partition_instances.len(), 1); - - // { - // let folder_id = session.partition_instances.values().nth(0).unwrap(); - - // let folder = session.instances.get(folder_id).unwrap(); - // assert_eq!(folder.name, "ReplicatedStorage"); - // } - // } -} diff --git a/server/src/session_config.rs b/server/src/session_config.rs deleted file mode 100644 index 8cd68a99..00000000 --- a/server/src/session_config.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::collections::HashMap; - -use partition::Partition; - -#[derive(Debug, Clone, PartialEq)] -pub struct SessionConfig { - pub partitions: HashMap, -} diff --git a/server/src/fs_session.rs b/server/src/vfs_session.rs similarity index 66% rename from server/src/fs_session.rs rename to server/src/vfs_session.rs index 0d609a54..cf50e115 100644 --- a/server/src/fs_session.rs +++ b/server/src/vfs_session.rs @@ -1,18 +1,9 @@ use std::collections::HashMap; use std::io::Read; use std::fs::{self, File}; -use std::sync::mpsc::{channel, Receiver}; -use std::time::Duration; - -use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher, watcher}; use file_route::FileRoute; -use session_config::SessionConfig; - -struct FileWatcher { - watcher: RecommendedWatcher, - rx: Receiver, -} +use session::SessionConfig; /// Represents a file or directory that has been read from the filesystem. #[derive(Debug, Clone)] @@ -27,60 +18,33 @@ pub enum FileItem { }, } -pub struct FsSession { +#[derive(Debug, Clone, Copy)] +pub enum FileChange { + Created, + Deleted, + Modified, +} + +pub struct VfsSession { pub config: SessionConfig, /// The in-memory files associated with each partition. pub partition_files: HashMap, - - watchers: HashMap, } -impl FsSession { - pub fn new(config: SessionConfig) -> FsSession { - FsSession { +impl VfsSession { + pub fn new(config: SessionConfig) -> VfsSession { + VfsSession { config: config, partition_files: HashMap::new(), - watchers: HashMap::new(), } } - pub fn init(&mut self) { - self.load_partitions(); - self.watch_partitions(); - } - - pub fn step(&mut self) { - for (partition_name, watcher) in self.watchers.iter() { - let change_event = match watcher.rx.try_recv() { - Ok(v) => v, - Err(_) => continue, - }; - - println!("Change event on partition {}: {:?}", partition_name, change_event); - } - } - - fn watch_partitions(&mut self) { - for (partition_name, partition) in self.config.partitions.iter() { - let (tx, rx) = channel(); - - let mut watcher = watcher(tx, Duration::from_millis(300)).unwrap(); - - watcher.watch(&partition.path, RecursiveMode::Recursive).unwrap(); - - self.watchers.insert(partition_name.clone(), FileWatcher { - watcher, - rx, - }); - } - } - - fn load_partitions(&mut self) { + pub fn read_partitions(&mut self) { for partition_name in self.config.partitions.keys() { let route = FileRoute { partition: partition_name.clone(), - route: vec![], + route: Vec::new(), }; let file_item = self.read(&route).expect("Couldn't load partitions"); @@ -89,6 +53,10 @@ impl FsSession { } } + pub fn handle_change(&mut self, route: &FileRoute, change: FileChange) { + // TODO: Update in-memory files + } + fn read(&self, route: &FileRoute) -> Result { let partition_path = &self.config.partitions.get(&route.partition) .ok_or(())?.path;