From 1a2ee07d37e9862d64084097d7dbb2b5a07ea71e Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Sun, 19 Aug 2018 17:43:11 -0700 Subject: [PATCH] Begin epiphany rewrite --- server/src/bin.rs | 15 +- server/src/commands/serve.rs | 32 +- server/src/file_route.rs | 166 ------- server/src/lib.rs | 8 +- server/src/partition.rs | 13 - server/src/partition_watcher.rs | 67 --- server/src/project.rs | 391 +++++++-------- server/src/rbx_session.rs | 306 ------------ server/src/session.rs | 95 ---- server/src/vfs_session.rs | 242 ---------- server/tests/read_projects.rs | 38 ++ server/tests/web.rs | 483 ------------------- test-projects/empty/roblox-project.json | 4 + test-projects/empty/rojo.json | 5 - test-projects/foo.json | 26 + test-projects/one-partition/lib/a.lua | 1 - test-projects/one-partition/lib/a.server.lua | 1 - test-projects/one-partition/lib/b.client.lua | 1 - test-projects/one-partition/rojo.json | 10 - test-projects/partition-to-file/foo.lua | 1 - test-projects/partition-to-file/rojo.json | 10 - 21 files changed, 245 insertions(+), 1670 deletions(-) delete mode 100644 server/src/file_route.rs delete mode 100644 server/src/partition.rs delete mode 100644 server/src/partition_watcher.rs delete mode 100644 server/src/rbx_session.rs delete mode 100644 server/src/session.rs delete mode 100644 server/src/vfs_session.rs create mode 100644 server/tests/read_projects.rs delete mode 100644 server/tests/web.rs create mode 100644 test-projects/empty/roblox-project.json delete mode 100644 test-projects/empty/rojo.json create mode 100644 test-projects/foo.json delete mode 100644 test-projects/one-partition/lib/a.lua delete mode 100644 test-projects/one-partition/lib/a.server.lua delete mode 100644 test-projects/one-partition/lib/b.client.lua delete mode 100644 test-projects/one-partition/rojo.json delete mode 100644 test-projects/partition-to-file/foo.lua delete mode 100644 test-projects/partition-to-file/rojo.json diff --git a/server/src/bin.rs b/server/src/bin.rs index bf20429e..82c568a7 100644 --- a/server/src/bin.rs +++ b/server/src/bin.rs @@ -42,20 +42,7 @@ fn main() { None => std::env::current_dir().unwrap(), }; - let port = { - match sub_matches.value_of("port") { - Some(source) => match source.parse::() { - Ok(value) => Some(value), - Err(_) => { - eprintln!("Invalid port '{}'", source); - process::exit(1); - }, - }, - None => None, - } - }; - - librojo::commands::serve(&project_path, port); + librojo::commands::serve(&project_path); }, _ => { eprintln!("Please specify a subcommand!"); diff --git a/server/src/commands/serve.rs b/server/src/commands/serve.rs index 243ee335..6b867ab7 100644 --- a/server/src/commands/serve.rs +++ b/server/src/commands/serve.rs @@ -1,40 +1,36 @@ -use std::path::PathBuf; +use std::path::Path; use std::process; use std::fs; use rand; use project::Project; -use web::{self, WebConfig}; -use session::Session; +// use web::{self, WebConfig}; +// use session::Session; use roblox_studio; -pub fn serve(project_dir: &PathBuf, override_port: Option) { +pub fn serve(fuzzy_project_location: &Path) { let server_id = rand::random::(); - let project = match Project::load(project_dir) { - Ok(v) => { - println!("Using project from {}", fs::canonicalize(project_dir).unwrap().display()); - v + let project = match Project::load_fuzzy(fuzzy_project_location) { + Ok(project) => { + println!("Using project from {}", fs::canonicalize(&project.file_location).unwrap().display()); + project }, - Err(err) => { - eprintln!("{}", err); + Err(error) => { + eprintln!("{}", error); process::exit(1); }, }; - let port = override_port.unwrap_or(project.serve_port); - println!("Using project {:#?}", project); roblox_studio::install_bundled_plugin().unwrap(); - let mut session = Session::new(project.clone()); - session.start(); + // let mut session = Session::new(project.clone()); + // session.start(); - let web_config = WebConfig::from_session(server_id, port, &session); + // let web_config = WebConfig::from_session(server_id, port, &session); - println!("Server listening on port {}", port); - - web::start(web_config); + // web::start(web_config); } diff --git a/server/src/file_route.rs b/server/src/file_route.rs deleted file mode 100644 index a691034e..00000000 --- a/server/src/file_route.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::path::{Path, PathBuf, Component}; - -use partition::Partition; - -// TODO: Change backing data structure to use a single allocation with slices -// taken out of it for each portion -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FileRoute { - pub partition: String, - pub route: Vec, -} - -impl FileRoute { - pub fn from_path(path: &Path, partition: &Partition) -> Option { - assert!(path.is_absolute()); - assert!(path.starts_with(&partition.path)); - - let relative_path = path.strip_prefix(&partition.path).ok()?; - let mut route = Vec::new(); - - for component in relative_path.components() { - match component { - Component::Normal(piece) => { - route.push(piece.to_string_lossy().into_owned()); - }, - _ => panic!("Unexpected path component: {:?}", component), - } - } - - Some(FileRoute { - partition: partition.name.clone(), - route, - }) - } - - pub fn parent(&self) -> Option { - if self.route.len() == 0 { - return None; - } - - let mut new_route = self.route.clone(); - new_route.pop(); - - Some(FileRoute { - partition: self.partition.clone(), - route: new_route, - }) - } - - /// Creates a PathBuf out of the `FileRoute` based on the given partition - /// `Path`. - pub fn to_path_buf(&self, partition_path: &Path) -> PathBuf { - let mut result = partition_path.to_path_buf(); - - for route_piece in &self.route { - result.push(route_piece); - } - - result - } - - /// Creates a version of the FileRoute with the given extra pieces appended - /// to the end. - pub fn extended_with(&self, pieces: &[&str]) -> FileRoute { - let mut result = self.clone(); - - for piece in pieces { - result.route.push(piece.to_string()); - } - - result - } - - pub fn file_name(&self, partition: &Partition) -> String { - if self.route.len() == 0 { - partition.path.file_name().unwrap().to_str().unwrap().to_string() - } else { - self.route.last().unwrap().clone() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(windows)] - const ROOT_PATH: &'static str = "C:\\"; - - #[cfg(not(windows))] - const ROOT_PATH: &'static str = "/"; - - #[test] - fn from_path_empty() { - let path = Path::new(ROOT_PATH).join("a/b/c"); - - let partition = Partition { - name: "foo".to_string(), - path: path.clone(), - target: vec![], - }; - - let route = FileRoute::from_path(&path, &partition); - - assert_eq!(route, Some(FileRoute { - partition: "foo".to_string(), - route: vec![], - })); - } - - #[test] - fn from_path_non_empty() { - let partition_path = Path::new(ROOT_PATH).join("a/b/c"); - - let inside_path = partition_path.join("d"); - - let partition = Partition { - name: "bar".to_string(), - path: partition_path, - target: vec![], - }; - - let route = FileRoute::from_path(&inside_path, &partition); - - assert_eq!(route, Some(FileRoute { - partition: "bar".to_string(), - route: vec!["d".to_string()], - })); - } - - #[test] - fn file_name_empty_route() { - let partition_path = Path::new(ROOT_PATH).join("a/b/c"); - - let partition = Partition { - name: "bar".to_string(), - path: partition_path, - target: vec![], - }; - - let route = FileRoute { - partition: "bar".to_string(), - route: vec![], - }; - - assert_eq!(route.file_name(&partition), "c"); - } - - #[test] - fn file_name_non_empty_route() { - let partition_path = Path::new(ROOT_PATH).join("a/b/c"); - - let partition = Partition { - name: "bar".to_string(), - path: partition_path, - target: vec![], - }; - - let route = FileRoute { - partition: "bar".to_string(), - route: vec!["foo".to_string(), "hello.lua".to_string()], - }; - - assert_eq!(route.file_name(&partition), "hello.lua"); - } -} \ No newline at end of file diff --git a/server/src/lib.rs b/server/src/lib.rs index 2f806548..f584328b 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -11,17 +11,11 @@ extern crate regex; extern crate tempfile; pub mod commands; -pub mod file_route; pub mod id; pub mod message_session; -pub mod partition; -pub mod partition_watcher; pub mod pathext; pub mod project; pub mod rbx; -pub mod rbx_session; -pub mod session; -pub mod vfs_session; -pub mod web; +// pub mod web; pub mod web_util; pub mod roblox_studio; diff --git a/server/src/partition.rs b/server/src/partition.rs deleted file mode 100644 index 9ef66664..00000000 --- a/server/src/partition.rs +++ /dev/null @@ -1,13 +0,0 @@ -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, - - /// The route to the Roblox instance that this partition maps to. - pub target: Vec, -} diff --git a/server/src/partition_watcher.rs b/server/src/partition_watcher.rs deleted file mode 100644 index 5885bb74..00000000 --- a/server/src/partition_watcher.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::sync::mpsc::{channel, Sender}; -use std::time::Duration; -use std::thread; - -use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher, watcher}; - -use partition::Partition; -use vfs_session::FileChange; -use file_route::FileRoute; - -const WATCH_TIMEOUT_MS: u64 = 100; - -pub struct PartitionWatcher { - pub watcher: RecommendedWatcher, -} - -impl PartitionWatcher { - pub fn start_new(partition: Partition, tx: Sender) -> PartitionWatcher { - let (watch_tx, watch_rx) = channel(); - - let mut watcher = watcher(watch_tx, Duration::from_millis(WATCH_TIMEOUT_MS)).unwrap(); - - watcher.watch(&partition.path, RecursiveMode::Recursive).unwrap(); - - thread::spawn(move || { - loop { - match watch_rx.recv() { - Ok(event) => { - let file_change = match event { - DebouncedEvent::Create(path) => { - let route = FileRoute::from_path(&path, &partition).unwrap(); - FileChange::Created(route) - }, - DebouncedEvent::Write(path) => { - let route = FileRoute::from_path(&path, &partition).unwrap(); - FileChange::Updated(route) - }, - DebouncedEvent::Remove(path) => { - let route = FileRoute::from_path(&path, &partition).unwrap(); - FileChange::Deleted(route) - }, - DebouncedEvent::Rename(from_path, to_path) => { - let from_route = FileRoute::from_path(&from_path, &partition).unwrap(); - let to_route = FileRoute::from_path(&to_path, &partition).unwrap(); - FileChange::Moved(from_route, to_route) - }, - _ => continue, - }; - - match tx.send(file_change) { - Ok(_) => {}, - Err(_) => break, - } - }, - Err(_) => break, - }; - } - }); - - PartitionWatcher { - watcher, - } - } - - pub fn stop(self) { - } -} diff --git a/server/src/project.rs b/server/src/project.rs index b0b95de7..6cc3a5ba 100644 --- a/server/src/project.rs +++ b/server/src/project.rs @@ -1,246 +1,177 @@ -use std::collections::HashMap; -use std::fmt; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::path::{Path, PathBuf}; - -use rand::{self, Rng}; - +use std::{ + collections::HashMap, + fmt, + fs, + io, + path::{Path, PathBuf}, +}; use serde_json; -use partition::Partition; +pub static PROJECT_FILENAME: &'static str = "roblox-project.json"; -pub static PROJECT_FILENAME: &'static str = "rojo.json"; +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +enum SourceProjectNode { + Regular { + #[serde(rename = "$className")] + class_name: String, -#[derive(Debug)] -pub enum ProjectLoadError { - DidNotExist(PathBuf), - FailedToOpen(PathBuf), - FailedToRead(PathBuf), - InvalidJson(PathBuf, serde_json::Error), + // #[serde(rename = "$ignoreUnknown", default = "false")] + // ignore_unknown: bool, + + #[serde(flatten)] + children: HashMap, + }, + SyncPoint { + #[serde(rename = "$path")] + path: String, + } } -impl fmt::Display for ProjectLoadError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl SourceProjectNode { + pub fn into_project_node(self, project_file_location: &Path) -> ProjectNode { match self { - &ProjectLoadError::InvalidJson(ref project_path, ref serde_err) => { - write!(f, "Found invalid JSON reading project: {}\nError: {}", project_path.display(), serde_err) + SourceProjectNode::Regular { class_name, mut children } => { + let mut new_children = HashMap::new(); + + for (node_name, node) in children.drain() { + new_children.insert(node_name, node.into_project_node(project_file_location)); + } + + ProjectNode::Regular { + class_name, + children: new_children, + } }, - &ProjectLoadError::FailedToOpen(ref project_path) | - &ProjectLoadError::FailedToRead(ref project_path) => { - write!(f, "Found project file, but failed to read it: {}", project_path.display()) - }, - &ProjectLoadError::DidNotExist(ref project_path) => { - write!(f, "Could not locate a project file at {}.\nUse 'rojo init' to create one.", project_path.display()) - }, - } - } -} + SourceProjectNode::SyncPoint { path: source_path } => { + let path = if Path::new(&source_path).is_absolute() { + PathBuf::from(source_path) + } else { + let project_folder_location = project_file_location.parent().unwrap(); + project_folder_location.join(source_path) + }; -#[derive(Debug)] -pub enum ProjectSaveError { - FailedToCreate, -} - -#[derive(Debug)] -pub enum ProjectInitError { - AlreadyExists, - FailedToCreate, - FailedToWrite, -} - -impl fmt::Display for ProjectInitError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &ProjectInitError::AlreadyExists => { - write!(f, "A project already exists at that location.") - }, - &ProjectInitError::FailedToCreate | - &ProjectInitError::FailedToWrite => { - write!(f, "Failed to write to the given location.") - }, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SourceProjectPartition { - /// A slash-separated path to a file or folder, relative to the project's - /// directory. - pub path: String, - - /// A dot-separated route to a Roblox instance, relative to game. - pub target: String, -} - -/// Represents a Rojo project in the format that's most convenient for users to -/// edit. This should generally line up with `Project`, but can diverge when -/// there's either compatibility shims or when the data structures that Rojo -/// want are too verbose to write in JSON but easy to convert from something -/// else. -// -/// Holds anything that can be configured with `rojo.json`. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase")] -pub struct SourceProject { - pub name: String, - pub serve_port: u64, - pub partitions: HashMap, -} - -impl Default for SourceProject { - fn default() -> SourceProject { - SourceProject { - name: "new-project".to_string(), - serve_port: 8000, - partitions: HashMap::new(), - } - } -} - -/// Represents a Rojo project in the format that's convenient for Rojo to work -/// with. -#[derive(Debug, Clone)] -pub struct Project { - /// The path to the project file that this project is associated with. - pub project_path: PathBuf, - - /// The name of this project, used for user-facing labels. - pub name: String, - - /// The port that this project will run a web server on. - pub serve_port: u64, - - /// All of the project's partitions, laid out in an expanded way. - pub partitions: HashMap, -} - -impl Project { - fn from_source_project(source_project: SourceProject, project_path: PathBuf) -> Project { - let mut partitions = HashMap::new(); - - { - let project_directory = project_path.parent().unwrap(); - - for (partition_name, partition) in source_project.partitions.into_iter() { - let path = project_directory.join(&partition.path); - let target = partition.target - .split(".") - .map(String::from) - .collect::>(); - - partitions.insert(partition_name.clone(), Partition { + ProjectNode::SyncPoint { path, - target, - name: partition_name, - }); - } + } + }, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SourceProject { + name: String, + tree: HashMap, +} + +impl SourceProject { + pub fn into_project(mut self, project_file_location: &Path) -> Project { + let mut tree = HashMap::new(); + + for (node_name, node) in self.tree.drain() { + tree.insert(node_name, node.into_project_node(project_file_location)); } Project { - project_path, - name: source_project.name, - serve_port: source_project.serve_port, - partitions, + name: self.name, + tree: tree, + file_location: PathBuf::from(project_file_location), } } - - fn as_source_project(&self) -> SourceProject { - let mut partitions = HashMap::new(); - - for partition in self.partitions.values() { - let path = partition.path.strip_prefix(&self.project_path) - .unwrap_or_else(|_| &partition.path) - .to_str() - .unwrap() - .to_string(); - - let target = partition.target.join("."); - - partitions.insert(partition.name.clone(), SourceProjectPartition { - path, - target, - }); - } - - SourceProject { - partitions, - name: self.name.clone(), - serve_port: self.serve_port, - } - } - - /// Initializes a new project inside the given folder path. - pub fn init>(location: T) -> Result { - let location = location.as_ref(); - let project_path = location.join(PROJECT_FILENAME); - - // We abort if the project file already exists. - fs::metadata(&project_path) - .map_err(|_| ProjectInitError::AlreadyExists)?; - - let mut file = File::create(&project_path) - .map_err(|_| ProjectInitError::FailedToCreate)?; - - // Try to give the project a meaningful name. - // If we can't, we'll just fall back to a default. - let name = match location.file_name() { - Some(v) => v.to_string_lossy().into_owned(), - None => "new-project".to_string(), - }; - - // Generate a random port to run the server on. - let serve_port = rand::thread_rng().gen_range(2000, 49151); - - // Configure the project with all of the values we know so far. - let source_project = SourceProject { - name, - serve_port, - partitions: HashMap::new(), - }; - let serialized = serde_json::to_string_pretty(&source_project).unwrap(); - - file.write(serialized.as_bytes()) - .map_err(|_| ProjectInitError::FailedToWrite)?; - - Ok(Project::from_source_project(source_project, project_path)) - } - - /// Attempts to load a project from the file named PROJECT_FILENAME from the - /// given folder. - pub fn load>(location: T) -> Result { - let project_path = location.as_ref().join(Path::new(PROJECT_FILENAME)); - - fs::metadata(&project_path) - .map_err(|_| ProjectLoadError::DidNotExist(project_path.clone()))?; - - let mut file = File::open(&project_path) - .map_err(|_| ProjectLoadError::FailedToOpen(project_path.clone()))?; - - let mut contents = String::new(); - - file.read_to_string(&mut contents) - .map_err(|_| ProjectLoadError::FailedToRead(project_path.clone()))?; - - let source_project = serde_json::from_str(&contents) - .map_err(|e| ProjectLoadError::InvalidJson(project_path.clone(), e))?; - - Ok(Project::from_source_project(source_project, project_path)) - } - - /// Saves the given project file to the given folder with the appropriate name. - pub fn save>(&self, location: T) -> Result<(), ProjectSaveError> { - let project_path = location.as_ref().join(Path::new(PROJECT_FILENAME)); - - let mut file = File::create(&project_path) - .map_err(|_| ProjectSaveError::FailedToCreate)?; - - let source_project = self.as_source_project(); - let serialized = serde_json::to_string_pretty(&source_project).unwrap(); - - file.write(serialized.as_bytes()).unwrap(); - - Ok(()) - } } + +#[derive(Debug)] +pub enum ProjectLoadExactError { + IoError(io::Error), + JsonError(serde_json::Error), +} + +impl fmt::Display for ProjectLoadExactError { + fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result { + match self { + ProjectLoadExactError::IoError(inner) => write!(output, "{}", inner), + ProjectLoadExactError::JsonError(inner) => write!(output, "{}", inner), + } + } +} + +#[derive(Debug)] +pub enum ProjectInitError {} + +impl fmt::Display for ProjectInitError { + fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result { + write!(output, "ProjectInitError") + } +} + +#[derive(Debug)] +pub enum ProjectLoadFuzzyError {} + +impl fmt::Display for ProjectLoadFuzzyError { + fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result { + write!(output, "ProjectLoadFuzzyError") + } +} + +#[derive(Debug)] +pub enum ProjectSaveError {} + +impl fmt::Display for ProjectSaveError { + fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result { + write!(output, "ProjectSaveError") + } +} + +#[derive(Debug)] +pub enum ProjectNode { + Regular { + class_name: String, + children: HashMap, + + // ignore_unknown: bool, + }, + SyncPoint { + path: PathBuf, + }, +} + +#[derive(Debug)] +pub struct Project { + pub name: String, + pub tree: HashMap, + pub file_location: PathBuf, +} + +impl Project { + pub fn init(_project_folder_location: &Path) -> Result<(), ProjectInitError> { + unimplemented!(); + } + + pub fn locate(_start_location: &Path) -> Option { + unimplemented!(); + } + + pub fn load_fuzzy(_fuzzy_project_location: &Path) -> Result { + unimplemented!(); + } + + pub fn load_exact(project_file_location: &Path) -> Result { + let contents = fs::read_to_string(project_file_location) + .map_err(ProjectLoadExactError::IoError)?; + + let parsed: SourceProject = serde_json::from_str(&contents) + .map_err(ProjectLoadExactError::JsonError)?; + + Ok(parsed.into_project(project_file_location)) + } + + pub fn save(&self) -> Result<(), ProjectSaveError> { + unimplemented!(); + } + + fn to_source_project(&self) -> SourceProject { + unimplemented!(); + } +} \ No newline at end of file diff --git a/server/src/rbx_session.rs b/server/src/rbx_session.rs deleted file mode 100644 index 0abd25e8..00000000 --- a/server/src/rbx_session.rs +++ /dev/null @@ -1,306 +0,0 @@ -use std::collections::HashMap; -use std::sync::{Arc, RwLock}; - -use file_route::FileRoute; -use id::{Id, get_id}; -use message_session::{Message, MessageSession}; -use partition::Partition; -use project::Project; -use rbx::{RbxInstance, RbxTree, RbxValue}; -use vfs_session::{VfsSession, FileItem, FileChange}; - -static SERVICES: &'static [&'static str] = &[ - "Chat", - "Lighting", - "LocalizationService", - "Players", - "ReplicatedFirst", - "ReplicatedStorage", - "ServerScriptService", - "ServerStorage", - "SoundService", - "StarterGui", - "StarterPack", - "StarterPlayer", - "TestService", - "Workspace", -]; - -fn get_partition_target_class_name(target: &[String]) -> &'static str { - match target.len() { - 1 => { - let target_name = &target[0]; - - for &service in SERVICES { - if service == target_name { - return service; - } - } - - "Folder" - }, - 2 => { - "Folder" - }, - _ => "Folder", - } -} - -// TODO: Rethink data structure and insertion/update behavior. Maybe break some -// pieces off into a new object? -fn file_to_instances( - file_item: &FileItem, - partition: &Partition, - tree: &mut RbxTree, - instances_by_route: &mut HashMap, - parent_id: Option, -) -> (Id, Vec) { - match file_item { - FileItem::File { contents, route } => { - let primary_id = match instances_by_route.get(&file_item.get_route()) { - Some(&id) => id, - None => { - let id = get_id(); - instances_by_route.insert(route.clone(), id); - - id - }, - }; - - // This is placeholder logic; this whole function is! - let (class_name, property_key, name) = { - let file_name = match route.route.last() { - Some(v) => v.to_string(), - None => partition.path.file_name().unwrap().to_str().unwrap().to_string() - }; - - let use_partition_name = route.route.len() == 0; - - let partition_name = partition.target.last().unwrap(); - - fn strip_suffix<'a>(source: &'a str, suffix: &'static str) -> String { - source[..source.len() - suffix.len()].to_string() - } - - if file_name.ends_with(".client.lua") { - let name = if use_partition_name { - partition_name.clone() - } else { - strip_suffix(&file_name, ".client.lua") - }; - - ("LocalScript", "Source", name) - } else if file_name.ends_with(".server.lua") { - let name = if use_partition_name { - partition_name.clone() - } else { - strip_suffix(&file_name, ".server.lua") - }; - - ("Script", "Source", name) - } else if file_name.ends_with(".lua") { - let name = if use_partition_name { - partition_name.clone() - } else { - strip_suffix(&file_name, ".lua") - }; - - ("ModuleScript", "Source", name) - } else { - let name = if use_partition_name { - partition_name.clone() - } else { - file_name - }; - - // TODO: Error/warn/skip instead of falling back - ("StringValue", "Value", name) - } - }; - - let mut properties = HashMap::new(); - properties.insert(property_key.to_string(), RbxValue::String { value: contents.clone() }); - - tree.insert_instance(primary_id, RbxInstance { - name, - class_name: class_name.to_string(), - properties, - children: Vec::new(), - parent: parent_id, - }); - - (primary_id, vec![primary_id]) - }, - FileItem::Directory { children, route } => { - let primary_id = match instances_by_route.get(&file_item.get_route()) { - Some(&id) => id, - None => { - let id = get_id(); - instances_by_route.insert(route.clone(), id); - - id - }, - }; - - let mut child_ids = Vec::new(); - - let mut changed_ids = vec![primary_id]; - - for child_file_item in children.values() { - let (child_id, mut child_changed_ids) = file_to_instances(child_file_item, partition, tree, instances_by_route, Some(primary_id)); - - child_ids.push(child_id); - changed_ids.push(child_id); - - // TODO: Should I stop using drain on Vecs of Copyable types? - for id in child_changed_ids.drain(..) { - changed_ids.push(id); - } - } - - let class_name = get_partition_target_class_name(&route.route).to_string(); - - let name = if route.route.len() == 0 { - partition.target.last().unwrap().clone() - } else { - route.file_name(partition) - }; - - tree.insert_instance(primary_id, RbxInstance { - name, - class_name, - properties: HashMap::new(), - children: child_ids, - parent: parent_id, - }); - - (primary_id, changed_ids) - }, - } -} - -pub struct RbxSession { - project: Project, - - vfs_session: Arc>, - - message_session: MessageSession, - - /// The RbxInstance that represents each partition. - // TODO: Can this be removed in favor of instances_by_route? - pub partition_instances: HashMap, - - /// Keeps track of all of the instances in the tree - pub tree: RbxTree, - - /// A map from files in the VFS to instances loaded in the session. - instances_by_route: HashMap, -} - -impl RbxSession { - pub fn new(project: Project, vfs_session: Arc>, message_session: MessageSession) -> RbxSession { - RbxSession { - project, - vfs_session, - message_session, - partition_instances: HashMap::new(), - tree: RbxTree::new(), - instances_by_route: HashMap::new(), - } - } - - pub fn read_partitions(&mut self) { - let vfs_session_arc = self.vfs_session.clone(); - let vfs_session = vfs_session_arc.read().unwrap(); - - for partition in self.project.partitions.values() { - let route = FileRoute { - partition: partition.name.clone(), - route: Vec::new(), - }; - let file_item = vfs_session.get_by_route(&route).unwrap(); - - let parent_id = match route.parent() { - Some(parent_route) => match self.instances_by_route.get(&parent_route) { - Some(&parent_id) => Some(parent_id), - None => None, - }, - None => None, - }; - - let (root_id, _) = file_to_instances(file_item, partition, &mut self.tree, &mut self.instances_by_route, parent_id); - - self.partition_instances.insert(partition.name.clone(), root_id); - } - } - - pub fn handle_change(&mut self, change: &FileChange) { - let vfs_session_arc = self.vfs_session.clone(); - let vfs_session = vfs_session_arc.read().unwrap(); - - match change { - FileChange::Created(route) | FileChange::Updated(route) => { - let file_item = vfs_session.get_by_route(route).unwrap(); - let partition = self.project.partitions.get(&route.partition).unwrap(); - - let parent_id = match route.parent() { - Some(parent_route) => match self.instances_by_route.get(&parent_route) { - Some(&parent_id) => Some(parent_id), - None => None, - }, - None => None, - }; - - let (_, changed_ids) = file_to_instances(file_item, partition, &mut self.tree, &mut self.instances_by_route, parent_id); - - let messages = changed_ids - .iter() - .map(|&id| Message::InstanceChanged { id }) - .collect::>(); - - self.message_session.push_messages(&messages); - }, - FileChange::Deleted(route) => { - match self.instances_by_route.get(route) { - Some(&id) => { - self.tree.delete_instance(id); - self.instances_by_route.remove(route); - self.message_session.push_messages(&[Message::InstanceChanged { id }]); - }, - None => (), - } - }, - FileChange::Moved(from_route, to_route) => { - let mut messages = Vec::new(); - - match self.instances_by_route.get(from_route) { - Some(&id) => { - self.tree.delete_instance(id); - self.instances_by_route.remove(from_route); - messages.push(Message::InstanceChanged { id }); - }, - None => (), - } - - let file_item = vfs_session.get_by_route(to_route).unwrap(); - let partition = self.project.partitions.get(&to_route.partition).unwrap(); - - let parent_id = match to_route.parent() { - Some(parent_route) => match self.instances_by_route.get(&parent_route) { - Some(&parent_id) => Some(parent_id), - None => None, - }, - None => None, - }; - - let (_, changed_ids) = file_to_instances(file_item, partition, &mut self.tree, &mut self.instances_by_route, parent_id); - - for id in changed_ids { - messages.push(Message::InstanceChanged { id }); - } - - self.message_session.push_messages(&messages); - }, - } - } -} diff --git a/server/src/session.rs b/server/src/session.rs deleted file mode 100644 index 55341bb3..00000000 --- a/server/src/session.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::sync::{mpsc, Arc, RwLock}; -use std::thread; - -use message_session::MessageSession; -use partition_watcher::PartitionWatcher; -use project::Project; -use rbx_session::RbxSession; -use vfs_session::VfsSession; - -/// Stub trait for middleware -trait Middleware { -} - -pub struct Session { - pub project: Project, - vfs_session: Arc>, - rbx_session: Arc>, - message_session: MessageSession, - watchers: Vec, -} - -impl Session { - pub fn new(project: Project) -> Session { - let message_session = MessageSession::new(); - let vfs_session = Arc::new(RwLock::new(VfsSession::new(project.clone()))); - let rbx_session = Arc::new(RwLock::new(RbxSession::new(project.clone(), vfs_session.clone(), message_session.clone()))); - - Session { - vfs_session, - rbx_session, - watchers: Vec::new(), - message_session, - project, - } - } - - pub fn start(&mut self) { - { - let mut vfs_session = self.vfs_session.write().unwrap(); - vfs_session.read_partitions(); - } - - { - let mut rbx_session = self.rbx_session.write().unwrap(); - rbx_session.read_partitions(); - } - - let (tx, rx) = mpsc::channel(); - - for partition in self.project.partitions.values() { - let watcher = PartitionWatcher::start_new(partition.clone(), tx.clone()); - - self.watchers.push(watcher); - } - - { - let vfs_session = self.vfs_session.clone(); - let rbx_session = self.rbx_session.clone(); - - thread::spawn(move || { - loop { - match rx.recv() { - Ok(change) => { - { - let mut vfs_session = vfs_session.write().unwrap(); - vfs_session.handle_change(&change); - } - - { - let mut rbx_session = rbx_session.write().unwrap(); - rbx_session.handle_change(&change); - } - }, - Err(_) => break, - } - } - }); - } - } - - pub fn stop(self) { - } - - pub fn get_vfs_session(&self) -> Arc> { - self.vfs_session.clone() - } - - pub fn get_rbx_session(&self) -> Arc> { - self.rbx_session.clone() - } - - pub fn get_message_session(&self) -> MessageSession { - self.message_session.clone() - } -} diff --git a/server/src/vfs_session.rs b/server/src/vfs_session.rs deleted file mode 100644 index c4dc3e0b..00000000 --- a/server/src/vfs_session.rs +++ /dev/null @@ -1,242 +0,0 @@ -use std::collections::HashMap; -use std::io::Read; -use std::fs::{self, File}; -use std::mem; - -use file_route::FileRoute; -use project::Project; - -/// Represents a file or directory that has been read from the filesystem. -#[derive(Debug, Clone)] -pub enum FileItem { - File { - contents: String, - route: FileRoute, - }, - Directory { - children: HashMap, - route: FileRoute, - }, -} - -impl FileItem { - pub fn get_route(&self) -> &FileRoute { - match self { - FileItem::File { route, .. } => route, - FileItem::Directory { route, .. } => route, - } - } -} - -#[derive(Debug, Clone)] -pub enum FileChange { - Created(FileRoute), - Deleted(FileRoute), - Updated(FileRoute), - Moved(FileRoute, FileRoute), -} - -pub struct VfsSession { - pub project: Project, - - /// The in-memory files associated with each partition. - pub partition_files: HashMap, -} - -impl VfsSession { - pub fn new(project: Project) -> VfsSession { - VfsSession { - project, - partition_files: HashMap::new(), - } - } - - pub fn read_partitions(&mut self) { - for partition_name in self.project.partitions.keys() { - let route = FileRoute { - partition: partition_name.clone(), - route: Vec::new(), - }; - - let file_item = self.read(&route).expect("Couldn't load partitions"); - - self.partition_files.insert(partition_name.clone(), file_item); - } - } - - pub fn handle_change(&mut self, change: &FileChange) -> Option<()> { - match change { - FileChange::Created(route) | FileChange::Updated(route) => { - let new_item = self.read(&route).ok()?; - self.set_file_item(new_item); - }, - FileChange::Deleted(route) => { - self.delete_route(&route); - }, - FileChange::Moved(from_route, to_route) => { - let new_item = self.read(&to_route).ok()?; - self.delete_route(&from_route); - self.set_file_item(new_item); - }, - } - - None - } - - pub fn get_by_route(&self, route: &FileRoute) -> Option<&FileItem> { - let partition = self.partition_files.get(&route.partition)?; - let mut current = partition; - - for piece in &route.route { - match current { - FileItem::File { .. } => return None, - FileItem::Directory { children, .. } => { - current = children.get(piece)?; - }, - } - } - - Some(current) - } - - pub fn get_by_route_mut(&mut self, route: &FileRoute) -> Option<&mut FileItem> { - let mut current = self.partition_files.get_mut(&route.partition)?; - - for piece in &route.route { - let mut next = match { current } { - FileItem::File { .. } => return None, - FileItem::Directory { children, .. } => { - children.get_mut(piece)? - }, - }; - - current = next; - } - - Some(current) - } - - pub fn set_file_item(&mut self, item: FileItem) { - match self.get_by_route_mut(item.get_route()) { - Some(existing) => { - mem::replace(existing, item); - return; - }, - None => {}, - } - - if item.get_route().route.len() > 0 { - let mut parent_route = item.get_route().clone(); - let child_name = parent_route.route.pop().unwrap(); - - let mut parent_children = HashMap::new(); - parent_children.insert(child_name, item); - - let parent_item = FileItem::Directory { - route: parent_route, - children: parent_children, - }; - - self.set_file_item(parent_item); - } else { - self.partition_files.insert(item.get_route().partition.clone(), item); - } - } - - pub fn delete_route(&mut self, route: &FileRoute) -> Option<()> { - if route.route.len() == 0 { - self.partition_files.remove(&route.partition); - return Some(()); - } - - let mut current = self.partition_files.get_mut(&route.partition)?; - - for i in 0..(route.route.len() - 1) { - let piece = &route.route[i]; - - let mut next = match { current } { - FileItem::File { .. } => return None, - FileItem::Directory { children, .. } => { - children.get_mut(piece)? - }, - }; - - current = next; - } - - match current { - FileItem::Directory { children, .. } => { - children.remove(route.route.last().unwrap().as_str()); - }, - _ => {}, - } - - Some(()) - } - - fn read(&self, route: &FileRoute) -> Result { - let partition_path = &self.project.partitions.get(&route.partition) - .ok_or(())?.path; - let path = route.to_path_buf(partition_path); - - let metadata = fs::metadata(path) - .map_err(|_| ())?; - - if metadata.is_dir() { - self.read_directory(route) - } else if metadata.is_file() { - self.read_file(route) - } else { - Err(()) - } - } - - fn read_file(&self, route: &FileRoute) -> Result { - let partition_path = &self.project.partitions.get(&route.partition) - .ok_or(())?.path; - let path = route.to_path_buf(partition_path); - - let mut file = File::open(path) - .map_err(|_| ())?; - - let mut contents = String::new(); - - file.read_to_string(&mut contents) - .map_err(|_| ())?; - - Ok(FileItem::File { - contents, - route: route.clone(), - }) - } - - fn read_directory(&self, route: &FileRoute) -> Result { - let partition_path = &self.project.partitions.get(&route.partition) - .ok_or(())?.path; - let path = route.to_path_buf(partition_path); - - let reader = fs::read_dir(path) - .map_err(|_| ())?; - - let mut children = HashMap::new(); - - for entry in reader { - let entry = entry - .map_err(|_| ())?; - - let path = entry.path(); - let name = path.file_name().unwrap().to_string_lossy().into_owned(); - - let child_route = route.extended_with(&[&name]); - - let child_item = self.read(&child_route)?; - - children.insert(name, child_item); - } - - Ok(FileItem::Directory { - children, - route: route.clone(), - }) - } -} diff --git a/server/tests/read_projects.rs b/server/tests/read_projects.rs new file mode 100644 index 00000000..216ece8d --- /dev/null +++ b/server/tests/read_projects.rs @@ -0,0 +1,38 @@ +#[macro_use] extern crate lazy_static; + +extern crate librojo; + +use std::{ + collections::HashMap, + path::PathBuf, +}; + +use librojo::{ + project::Project, +}; + +lazy_static! { + static ref TEST_PROJECTS_ROOT: PathBuf = { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("../test-projects"); + path + }; +} + +#[test] +fn foo() { + let project_file_location = TEST_PROJECTS_ROOT.join("foo.json"); + let project = Project::load_exact(&project_file_location).unwrap(); + + assert_eq!(project.name, "foo"); + assert_eq!(project.tree.len(), 1); +} + +#[test] +fn empty() { + let project_file_location = TEST_PROJECTS_ROOT.join("empty/roblox-project.json"); + let project = Project::load_exact(&project_file_location).unwrap(); + + assert_eq!(project.name, "empty"); + assert_eq!(project.tree.len(), 0); +} \ No newline at end of file diff --git a/server/tests/web.rs b/server/tests/web.rs deleted file mode 100644 index c9ae6259..00000000 --- a/server/tests/web.rs +++ /dev/null @@ -1,483 +0,0 @@ -#[macro_use] extern crate lazy_static; - -extern crate rouille; -extern crate serde_json; -extern crate serde; -extern crate tempfile; -extern crate walkdir; - -extern crate librojo; - -mod test_util; -use test_util::*; - -use std::borrow::Cow; -use std::collections::HashMap; -use std::fs::{File, remove_file}; -use std::io::Write; -use std::path::PathBuf; - -use librojo::{ - session::Session, - project::Project, - web::{Server, WebConfig, ServerInfoResponse, ReadResponse, ReadAllResponse, SubscribeResponse}, - rbx::RbxValue, -}; - -lazy_static! { - static ref TEST_PROJECTS_ROOT: PathBuf = { - let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("../test-projects"); - path - }; -} - -#[test] -fn empty() { - let original_project_path = TEST_PROJECTS_ROOT.join("empty"); - - let project_tempdir = tempfile::tempdir().unwrap(); - let project_path = project_tempdir.path(); - - copy_recursive(&original_project_path, &project_path).unwrap(); - - let project = Project::load(&project_path).unwrap(); - let mut session = Session::new(project.clone()); - session.start(); - - let web_config = WebConfig::from_session(0, project.serve_port, &session); - let server = Server::new(web_config); - - { - let body = server.get_string("/api/rojo"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.protocol_version, 2); - assert_eq!(response.partitions.len(), 0); - } - - { - let body = server.get_string("/api/read_all"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, -1); - assert_eq!(response.instances.len(), 0); - } - - { - let body = server.get_string("/api/read/0"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, -1); - assert_eq!(response.instances.len(), 0); - } -} - -#[test] -fn one_partition() { - let original_project_path = TEST_PROJECTS_ROOT.join("one-partition"); - - let project_tempdir = tempfile::tempdir().unwrap(); - let project_path = project_tempdir.path(); - - copy_recursive(&original_project_path, &project_path).unwrap(); - - let project = Project::load(&project_path).unwrap(); - let mut session = Session::new(project.clone()); - session.start(); - - let web_config = WebConfig::from_session(0, project.serve_port, &session); - let server = Server::new(web_config); - - { - let body = server.get_string("/api/rojo"); - let response = serde_json::from_str::(&body).unwrap(); - - let mut partitions = HashMap::new(); - partitions.insert("lib".to_string(), vec!["ReplicatedStorage".to_string(), "OnePartition".to_string()]); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.protocol_version, 2); - assert_eq!(response.partitions, partitions); - } - - let initial_body = server.get_string("/api/read_all"); - let initial_response = { - let response = serde_json::from_str::(&initial_body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, -1); - assert_eq!(response.instances.len(), 4); - - let partition_id = *response.partition_instances.get("lib").unwrap(); - - let mut root_id = None; - let mut module_id = None; - let mut client_id = None; - let mut server_id = None; - - for (id, instance) in response.instances.iter() { - match (instance.name.as_str(), instance.class_name.as_str()) { - // TOOD: Should partition roots (and other directories) be some - // magical object instead of Folder? - // TODO: Should this name actually equal the last part of the - // partition's target? - ("OnePartition", "Folder") => { - assert!(root_id.is_none()); - root_id = Some(*id); - - assert_eq!(*id, partition_id); - - assert_eq!(instance.properties.len(), 0); - assert_eq!(instance.parent, None); - assert_eq!(instance.children.len(), 3); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - ("a", "ModuleScript") => { - assert!(module_id.is_none()); - module_id = Some(*id); - - let mut properties = HashMap::new(); - properties.insert("Source".to_string(), RbxValue::String { value: "-- a.lua".to_string() }); - - assert_eq!(instance.properties, properties); - assert_eq!(instance.parent, Some(partition_id)); - assert_eq!(instance.children.len(), 0); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - ("b", "LocalScript") => { - assert!(client_id.is_none()); - client_id = Some(*id); - - let mut properties = HashMap::new(); - properties.insert("Source".to_string(), RbxValue::String { value: "-- b.client.lua".to_string() }); - - assert_eq!(instance.properties, properties); - assert_eq!(instance.parent, Some(partition_id)); - assert_eq!(instance.children.len(), 0); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - ("a", "Script") => { - assert!(server_id.is_none()); - server_id = Some(*id); - - let mut properties = HashMap::new(); - properties.insert("Source".to_string(), RbxValue::String { value: "-- a.server.lua".to_string() }); - - assert_eq!(instance.properties, properties); - assert_eq!(instance.parent, Some(partition_id)); - assert_eq!(instance.children.len(), 0); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - _ => { - panic!("Unexpected instance named {} of class {}", instance.name, instance.class_name); - }, - } - } - - let root_id = root_id.unwrap(); - let module_id = module_id.unwrap(); - let client_id = client_id.unwrap(); - let server_id = server_id.unwrap(); - - { - let root_instance = response.instances.get(&root_id).unwrap(); - - assert!(root_instance.children.contains(&module_id)); - assert!(root_instance.children.contains(&client_id)); - assert!(root_instance.children.contains(&server_id)); - } - - response - }; - - { - let temp_name = project_path.join("lib/c.client.lua"); - - { - let mut file = File::create(&temp_name).unwrap(); - file.write_all(b"-- c.client.lua").unwrap(); - } - - { - // Block until Rojo detects the addition of our temp file - let body = server.get_string("/api/subscribe/-1"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, 0); - assert_eq!(response.messages.len(), 1); - - // TODO: Read which instance was changed and try to access it with - // /read - } - - let body = server.get_string("/api/read_all"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, 0); - assert_eq!(response.instances.len(), 5); - - let partition_id = *response.partition_instances.get("lib").unwrap(); - - let mut root_id = None; - let mut module_id = None; - let mut client_id = None; - let mut server_id = None; - let mut new_id = None; - - for (id, instance) in response.instances.iter() { - match (instance.name.as_str(), instance.class_name.as_str()) { - // TOOD: Should partition roots (and other directories) be some - // magical object instead of Folder? - // TODO: Should this name actually equal the last part of the - // partition's target? - ("OnePartition", "Folder") => { - assert!(root_id.is_none()); - root_id = Some(*id); - - assert_eq!(*id, partition_id); - - assert_eq!(instance.properties.len(), 0); - assert_eq!(instance.parent, None); - assert_eq!(instance.children.len(), 4); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - ("a", "ModuleScript") => { - assert!(module_id.is_none()); - module_id = Some(*id); - - let mut properties = HashMap::new(); - properties.insert("Source".to_string(), RbxValue::String { value: "-- a.lua".to_string() }); - - assert_eq!(instance.properties, properties); - assert_eq!(instance.parent, Some(partition_id)); - assert_eq!(instance.children.len(), 0); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - ("b", "LocalScript") => { - assert!(client_id.is_none()); - client_id = Some(*id); - - let mut properties = HashMap::new(); - properties.insert("Source".to_string(), RbxValue::String { value: "-- b.client.lua".to_string() }); - - assert_eq!(instance.properties, properties); - assert_eq!(instance.parent, Some(partition_id)); - assert_eq!(instance.children.len(), 0); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - ("a", "Script") => { - assert!(server_id.is_none()); - server_id = Some(*id); - - let mut properties = HashMap::new(); - properties.insert("Source".to_string(), RbxValue::String { value: "-- a.server.lua".to_string() }); - - assert_eq!(instance.properties, properties); - assert_eq!(instance.parent, Some(partition_id)); - assert_eq!(instance.children.len(), 0); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - ("c", "LocalScript") => { - assert!(new_id.is_none()); - new_id = Some(*id); - - let mut properties = HashMap::new(); - properties.insert("Source".to_string(), RbxValue::String { value: "-- c.client.lua".to_string() }); - - assert_eq!(instance.properties, properties); - assert_eq!(instance.parent, Some(partition_id)); - assert_eq!(instance.children.len(), 0); - - let single_body = server.get_string(&format!("/api/read/{}", id)); - let single_response = serde_json::from_str::(&single_body).unwrap(); - - let single_instance = single_response.instances.get(id).unwrap(); - - assert_eq!(single_instance, &Cow::Borrowed(instance)); - }, - _ => {}, - } - } - - let root_id = root_id.unwrap(); - let module_id = module_id.unwrap(); - let client_id = client_id.unwrap(); - let server_id = server_id.unwrap(); - let new_id = new_id.unwrap(); - - let root_instance = response.instances.get(&root_id).unwrap(); - - assert!(root_instance.children.contains(&module_id)); - assert!(root_instance.children.contains(&client_id)); - assert!(root_instance.children.contains(&server_id)); - assert!(root_instance.children.contains(&new_id)); - - remove_file(&temp_name).unwrap(); - } - - { - // Block until Rojo detects the removal of our temp file - let body = server.get_string("/api/subscribe/0"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, 1); - assert_eq!(response.messages.len(), 1); - } - - { - // Everything should be back to the initial state! - let body = server.get_string("/api/read_all"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, 1); - assert_eq!(response.instances.len(), 4); - - assert_eq!(response.instances, initial_response.instances); - } - - // TODO: Test to change existing instance -} - -#[test] -fn partition_to_file() { - let original_project_path = TEST_PROJECTS_ROOT.join("partition-to-file"); - - let project_tempdir = tempfile::tempdir().unwrap(); - let project_path = project_tempdir.path(); - - copy_recursive(&original_project_path, &project_path).unwrap(); - - let project = Project::load(&project_path).unwrap(); - let mut session = Session::new(project.clone()); - session.start(); - - let web_config = WebConfig::from_session(0, project.serve_port, &session); - let server = Server::new(web_config); - - { - let body = server.get_string("/api/rojo"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.protocol_version, 2); - assert_eq!(response.partitions.len(), 1); - } - - { - let body = server.get_string("/api/read_all"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, -1); - assert_eq!(response.instances.len(), 1); - - let (id, instance) = response.instances.iter().next().unwrap(); - - assert_eq!(instance.name, "bar"); - assert_eq!(instance.class_name, "ModuleScript"); - assert_eq!(instance.properties.get("Source"), Some(&RbxValue::String { value: "-- foo.lua".to_string() })); - assert_eq!(instance.children.len(), 0); - assert_eq!(instance.parent, None); - - let body = server.get_string(&format!("/api/read/{}", id)); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, -1); - assert_eq!(response.instances.len(), 1); - - let single_instance = response.instances.values().next().unwrap(); - - assert_eq!(&Cow::Borrowed(instance), single_instance); - } - - let file_path = project_path.join("foo.lua"); - - { - let mut file = File::create(file_path).unwrap(); - file.write_all(b"-- modified").unwrap(); - } - - { - // Block until Rojo detects our file being modified - let body = server.get_string("/api/subscribe/-1"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, 0); - assert_eq!(response.messages.len(), 1); - } - - { - let body = server.get_string("/api/read/0"); - let response = serde_json::from_str::(&body).unwrap(); - - assert_eq!(response.server_id, "0"); - assert_eq!(response.message_cursor, 0); - assert_eq!(response.instances.len(), 1); - - let instance = response.instances.values().next().unwrap(); - - assert_eq!(instance.name, "bar"); - assert_eq!(instance.class_name, "ModuleScript"); - assert_eq!(instance.properties.get("Source"), Some(&RbxValue::String { value: "-- modified".to_string() })); - assert_eq!(instance.children.len(), 0); - assert_eq!(instance.parent, None); - } -} diff --git a/test-projects/empty/roblox-project.json b/test-projects/empty/roblox-project.json new file mode 100644 index 00000000..5844e00e --- /dev/null +++ b/test-projects/empty/roblox-project.json @@ -0,0 +1,4 @@ +{ + "name": "empty", + "tree": {} +} \ No newline at end of file diff --git a/test-projects/empty/rojo.json b/test-projects/empty/rojo.json deleted file mode 100644 index 51c7917c..00000000 --- a/test-projects/empty/rojo.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "empty", - "servePort": 23456, - "partitions": {} -} \ No newline at end of file diff --git a/test-projects/foo.json b/test-projects/foo.json new file mode 100644 index 00000000..f12be6a8 --- /dev/null +++ b/test-projects/foo.json @@ -0,0 +1,26 @@ +{ + "name": "foo", + "tree": { + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "Rojo": { + "$className": "Folder", + "Plugin": { + "$path": "lib" + }, + "Roact": { + "$path": "modules/roact/lib" + }, + "Rodux": { + "$path": "modules/rodux/lib" + }, + "RoactRodux": { + "$path": "modules/roact-rodux/lib" + }, + "Promise": { + "$path": "modules/promise/lib" + } + } + } + } +} \ No newline at end of file diff --git a/test-projects/one-partition/lib/a.lua b/test-projects/one-partition/lib/a.lua deleted file mode 100644 index f23fd06a..00000000 --- a/test-projects/one-partition/lib/a.lua +++ /dev/null @@ -1 +0,0 @@ --- a.lua \ No newline at end of file diff --git a/test-projects/one-partition/lib/a.server.lua b/test-projects/one-partition/lib/a.server.lua deleted file mode 100644 index 08491e94..00000000 --- a/test-projects/one-partition/lib/a.server.lua +++ /dev/null @@ -1 +0,0 @@ --- a.server.lua \ No newline at end of file diff --git a/test-projects/one-partition/lib/b.client.lua b/test-projects/one-partition/lib/b.client.lua deleted file mode 100644 index 12d034fa..00000000 --- a/test-projects/one-partition/lib/b.client.lua +++ /dev/null @@ -1 +0,0 @@ --- b.client.lua \ No newline at end of file diff --git a/test-projects/one-partition/rojo.json b/test-projects/one-partition/rojo.json deleted file mode 100644 index d774a8f8..00000000 --- a/test-projects/one-partition/rojo.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "one-partition", - "servePort": 23456, - "partitions": { - "lib": { - "path": "lib", - "target": "ReplicatedStorage.OnePartition" - } - } -} \ No newline at end of file diff --git a/test-projects/partition-to-file/foo.lua b/test-projects/partition-to-file/foo.lua deleted file mode 100644 index 4c3ac65c..00000000 --- a/test-projects/partition-to-file/foo.lua +++ /dev/null @@ -1 +0,0 @@ --- foo.lua \ No newline at end of file diff --git a/test-projects/partition-to-file/rojo.json b/test-projects/partition-to-file/rojo.json deleted file mode 100644 index 0e7367f1..00000000 --- a/test-projects/partition-to-file/rojo.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "partition-to-file", - "servePort": 23456, - "partitions": { - "lib": { - "path": "foo.lua", - "target": "ReplicatedStorage.bar" - } - } -} \ No newline at end of file