diff --git a/src/chat.rs b/src/chat.rs index 367447f..b0ea538 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -15,6 +15,7 @@ */ use crate::client::*; +#[derive(Debug)] pub struct Message { username: String, content: String, @@ -31,6 +32,7 @@ impl Message { } } +#[derive(Debug)] pub struct Chat { messages: Vec, // messages in this file } diff --git a/src/file.rs b/src/file.rs index b938f9a..a782768 100644 --- a/src/file.rs +++ b/src/file.rs @@ -15,12 +15,14 @@ */ use crate::chat::*; use crate::user::*; +use crate::util::*; use std::collections::HashMap; +#[derive(Debug)] pub struct File { - path: String, // absolute path - chat: Chat, // messages in this file - view: String, // the file view + path: String, // absolute path + chat: Chat, // messages in this file + view: Option, // the file view } impl File { @@ -28,7 +30,7 @@ impl File { Self { path: _path, chat: Chat::new(), - view: String::default(), + view: None, } } @@ -42,8 +44,29 @@ impl File { &mut self.chat } + fn load_file(&mut self) { + if !self.view.is_none() { + return; + } + self.view = Some(read_to_string(&self.path)); + } + + pub fn update(&mut self, add_or_delete: &String, beg: u64, end: u64, content: &String) { + self.load_file(); // ensure read + + match add_or_delete.clone().as_str() { + "add" => { + //self.view.insert(content, beg); + } + "delete" => {} + _ => { + unreachable!() + } + } + } + /// Write the content to file. - pub async fn save(&self) { - // TODO: .. + pub fn save(&self) { + let _ = std::fs::write(&self.path, &self.view.clone().unwrap()); } } diff --git a/src/handler/file.rs b/src/handler/file.rs index 8adbfc1..2aad2ea 100644 --- a/src/handler/file.rs +++ b/src/handler/file.rs @@ -14,9 +14,10 @@ * limitations under the License. */ -/// Return a list of users in the file. -pub mod users { +// Addition and Deletion to the file. +pub mod update { use crate::channel::*; + use crate::handler::file::*; use crate::handler::room::*; use crate::room::*; use crate::util::*; @@ -24,63 +25,70 @@ pub mod users { use std::sync::Arc; use tokio::sync::Mutex; - const METHOD: &str = "file::users"; + const METHOD: &str = "file::update"; pub async fn handle(channel: &mut Channel, room: &Arc>, json: &Value) { let addr = &channel.get_connection().addr; - let room = room.lock().await; - let client = room.get_client(addr).unwrap(); + let mut room = room.lock().await; - if !check_entered(channel, &client, METHOD).await { - return; - } + let path = data_str(json, "path").unwrap(); + let add_or_delete = data_str(json, "add_or_delete").unwrap(); + let beg = data_u64(json, "beg").unwrap(); + let end = data_u64(json, "end").unwrap(); + let content = data_str(json, "content").unwrap(); - let this_user = client.user().unwrap(); + let path = to_room_path(&addr, &room, &path); + let file = room.get_file_mut(&path); - // If user is not in the file, ignore it. - if this_user.path.is_none() { + if file.is_none() { + tracing::debug!("Updating an non-existence file: {}", path); + // TODO: Create one? return; } - // Prepare data to send. - let mut users = Vec::new(); + let file = file.unwrap(); - for _client in room.get_clients().iter() { - let user = _client.user(); + file.update(&add_or_delete, beg, end, &content); + } +} - if user.is_none() { - continue; - } +/// Save file. +pub mod save { + use crate::channel::*; + use crate::handler::file::*; + use crate::handler::room::*; + use crate::room::*; + use crate::util::*; + use serde_json::Value; + use std::sync::Arc; + use tokio::sync::Mutex; - let user = user.unwrap(); + const METHOD: &str = "file::save"; - // Ignore the sender client. - if this_user == user { - continue; - } - - // Ignore when user not visiting any project files. - if user.path.is_none() { - continue; - } + pub async fn handle(channel: &mut Channel, room: &Arc>, json: &Value) { + let addr = &channel.get_connection().addr; + let mut room = room.lock().await; - // Ignore if not in the same file. - if client.user_relative_path() != _client.user_relative_path() { - continue; - } + let path = data_str(json, "path").unwrap(); + let path = to_room_path(&addr, &room, &path); + let file = room.get_file_mut(&path); - users.push(user.clone()); + if file.is_none() { + tracing::debug!("Updating an non-existence file: {}", path); + // TODO: Create one? + return; } - let users = serde_json::to_string(&users).unwrap(); + let file = file.unwrap(); + file.save(); - channel - .send_json(&serde_json::json!({ - "method": METHOD, - "clients": users, - "status": "success", - })) - .await; + let relative_path = no_room_path(&room, &path); + + room.broadcast_json(&serde_json::json!({ + "method": METHOD, + "file": relative_path, + "status": "success", + })); } } @@ -125,6 +133,76 @@ pub mod sync { } } +/// Return a list of users in the file. +pub mod users { + use crate::channel::*; + use crate::handler::room::*; + use crate::room::*; + use crate::util::*; + use serde_json::Value; + use std::sync::Arc; + use tokio::sync::Mutex; + + const METHOD: &str = "file::users"; + + pub async fn handle(channel: &mut Channel, room: &Arc>, json: &Value) { + let addr = &channel.get_connection().addr; + let room = room.lock().await; + let client = room.get_client(addr).unwrap(); + + if !check_entered(channel, &client, METHOD).await { + return; + } + + let this_user = client.user().unwrap(); + + // If user is not in the file, ignore it. + if this_user.path.is_none() { + return; + } + + // Prepare data to send. + let mut users = Vec::new(); + + for _client in room.get_clients().iter() { + let user = _client.user(); + + if user.is_none() { + continue; + } + + let user = user.unwrap(); + + // Ignore the sender client. + if this_user == user { + continue; + } + + // Ignore when user not visiting any project files. + if user.path.is_none() { + continue; + } + + // Ignore if not in the same file. + if client.user_relative_path() != _client.user_relative_path() { + continue; + } + + users.push(user.clone()); + } + + let users = serde_json::to_string(&users).unwrap(); + + channel + .send_json(&serde_json::json!({ + "method": METHOD, + "clients": users, + "status": "success", + })) + .await; + } +} + /// Say pub mod say { use crate::channel::*; diff --git a/src/handler/mod.rs b/src/handler/mod.rs index 314fa8d..70404dc 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -23,7 +23,6 @@ use crate::room::*; use serde_json::Value; use std::sync::Arc; use tokio::sync::Mutex; - pub async fn handle(channel: &mut Channel, room: &Arc>, json: &str) { let v = serde_json::from_str(json); let val: Value = v.unwrap(); @@ -40,9 +39,11 @@ pub async fn handle(channel: &mut Channel, room: &Arc>, json: &str) "room::users" => room::users::handle(channel, room, &val).await, "room::sync" => room::sync::handle(channel, room, &val).await, "room::update_client" => room::update_client::handle(channel, room, &val).await, + "file::update" => file::update::handle(channel, room, &val).await, + "file::save" => file::save::handle(channel, room, &val).await, + "file::sync" => file::sync::handle(channel, room, &val).await, "file::users" => file::users::handle(channel, room, &val).await, "file::say" => file::say::handle(channel, room, &val).await, - "file::sync" => file::sync::handle(channel, room, &val).await, _ => { tracing::error!("Unkown method request: {:?}", method); } diff --git a/src/handler/room.rs b/src/handler/room.rs index b9a2d99..020ea74 100644 --- a/src/handler/room.rs +++ b/src/handler/room.rs @@ -16,9 +16,6 @@ use crate::channel::*; use crate::client::*; use crate::room::*; -use serde_json::Value; -use std::fs; -use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::Mutex; @@ -87,20 +84,6 @@ pub async fn check_admin(channel: &mut Channel, client: &Client, method: &str) - return false; } -/// Convert path's absolute project path to this room path. -/// -/// # Arguments -/// -/// * `addr` - Socket address used to get the client's project path. -/// * `room` - Used to get client and room path. -/// * `path` - Path we want to convert. -pub fn to_room_path(addr: &SocketAddr, room: &Room, path: &str) -> String { - let server_path = room.get_path().clone(); - let client = room.get_client(addr).unwrap(); - let project_path = client.get_path(); - path.replace(project_path, &server_path) -} - /// Enter room pub mod enter { use crate::channel::*; @@ -202,10 +185,9 @@ pub mod exit { pub mod kick { use crate::channel::*; use crate::handler::room::*; - use crate::room::*; use crate::util::*; use serde_json::Value; - use std::sync::{Arc, MutexGuard}; + use std::sync::Arc; use tokio::sync::Mutex; const METHOD: &str = "room::kick"; @@ -259,10 +241,9 @@ pub mod kick { pub mod broadcast { use crate::channel::*; use crate::handler::room::*; - use crate::room::*; use crate::util::*; use serde_json::Value; - use std::sync::{Arc, MutexGuard}; + use std::sync::Arc; use tokio::sync::Mutex; const METHOD: &str = "room::broadcast"; @@ -293,7 +274,6 @@ pub mod broadcast { pub mod update_client { use crate::channel::*; use crate::handler::room::*; - use crate::room::*; use crate::util::*; use serde_json::Value; use std::sync::Arc; @@ -329,7 +309,6 @@ pub mod update_client { pub mod users { use crate::channel::*; use crate::handler::room::*; - use crate::room::*; use serde_json::Value; use std::sync::Arc; use tokio::sync::Mutex; @@ -369,7 +348,6 @@ pub mod users { pub mod sync { use crate::channel::*; use crate::handler::room::*; - use crate::room::*; use crate::util::*; use serde_json::Value; use std::sync::Arc; @@ -389,7 +367,7 @@ pub mod sync { let project_path = data_str(json, "path").unwrap(); let room_path = room.get_path().clone(); - let files = room.get_files(); + let files = room.get_path_files(); for file in files.into_iter() { let abs_path = file; diff --git a/src/room.rs b/src/room.rs index 58f844b..655203f 100644 --- a/src/room.rs +++ b/src/room.rs @@ -127,12 +127,12 @@ impl Room { /// # Arguments /// /// * `path` - The file path. - pub fn get_file(&mut self, path: &String) -> Option<&mut File> { + pub fn get_file_mut(&mut self, path: &String) -> Option<&mut File> { self.files.get_mut(path) } /// Return a list of files need to be sync. - pub fn get_files(&self) -> Vec<&String> { + pub fn get_path_files(&self) -> Vec<&String> { self.files.keys().clone().collect::>() } diff --git a/src/util.rs b/src/util.rs index 5585f23..e0225a0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +use crate::room::*; use crate::user::*; use path_slash::PathBufExt as _; use serde_json::Value; +use std::net::SocketAddr; use std::path::PathBuf; /// Get data as string. @@ -81,6 +83,31 @@ pub fn read_to_string(path: &String) -> String { std::fs::read_to_string(path).expect(format!("Unable to read file: {}", path).as_str()) } +/// Convert path's absolute project path to this room path. +/// +/// # Arguments +/// +/// * `addr` - Socket address used to get the client's project path. +/// * `room` - Used to get client and room path. +/// * `path` - Path we want to convert. +pub fn to_room_path(addr: &SocketAddr, room: &Room, path: &str) -> String { + let room_path = room.get_path().clone(); + let client = room.get_client(addr).unwrap(); + let project_path = client.get_path(); + path.replace(project_path, &room_path) +} + +/// Remove room path. +/// +/// # Arguments +/// +/// * `room` - Room object. +/// * `path` - Path we want to remove room path. +pub fn no_room_path(room: &Room, path: &str) -> String { + let room_path = room.get_path().clone(); + path.replace(&room_path, "") +} + /// Convert backslash to slash. /// /// # Arguments