diff --git a/Cargo.toml b/Cargo.toml index c338639..9e487fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,4 @@ clap = "2.33.3" serde = "1.0.123" serde_json = "1.0.62" rand = "0.8.3" -futures = "0.3.12" \ No newline at end of file +futures = "0.3.12" diff --git a/api.txt b/api.txt new file mode 100644 index 0000000..ce648e8 --- /dev/null +++ b/api.txt @@ -0,0 +1,25 @@ +Set name: +{ + cmd: "name", + name: name +} + +Join Grih: +{ + cmd: "join", + kunjika: id, + length: number of members allowed (optional) +} + +Leave: +{ + cmd: "leave" +} + +Send Message: +{ + cmd: "text", + text: message +} + + diff --git a/src/chat_pinnd.rs b/src/chat_pinnd.rs index 3fc15c5..0b983e4 100644 --- a/src/chat_pinnd.rs +++ b/src/chat_pinnd.rs @@ -4,95 +4,92 @@ use std::collections::HashMap; use actix::prelude::*; use actix_broker::BrokerSubscribe; +use ws_sansad::WsSansad; -use crate::ws_sansad; -use crate::messages as ms; +use crate::{errors, ws_sansad, messages as ms}; #[allow(dead_code)] pub struct ChatPinnd { - grih: HashMap, - vyaktigat_waitlist: Vec> + grih: HashMap, // id, Grih + vyaktigat_waitlist: Vec>, + vyakti: Vec // id, tags } pub struct Grih { - name: Option, length: Option, - clients: Vec> + loog: Vec> +} + +pub struct Vyakti { + kunjika: String, + tags: Vec } impl Actor for ChatPinnd { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { - self.subscribe_system_async::(ctx); + self.subscribe_system_async::(ctx); self.subscribe_system_async::(ctx); } } -impl Handler for ChatPinnd { - type Result = Option; +impl Handler for ChatPinnd { + type Result = Result<(), errors::AlreadyExistError>; - fn handle(&mut self, msg: ms::JoinUser, _: &mut Self::Context) -> Self::Result { - match msg.grih { - ms::JoinUserGrihType::Name(name) => { - let mat = Some(name.clone()); - if let Some((kunjika, grih)) = - self.grih.iter_mut().find(|(_, g)| g.name == mat) { - if let Some(val) = grih.length { - if grih.clients.len()+1 == val { - return None; - } - } - grih.clients.push(msg.addr.clone()); - return Some(*kunjika); - } - - let mut kunjika: i32 = rand::random::(); - while self.grih.contains_key(&kunjika) { - kunjika = rand::random::(); - } - self.grih.insert(kunjika, Grih { - name: Some(name), - length: msg.length, - clients: vec![msg.addr] - }); - - return Some(kunjika); - }, ms::JoinUserGrihType::Kunjika(kunjika) => { - match self.grih.get_mut(&kunjika) { - Some(grih) => { - if let Some(val) = grih.length { - if grih.clients.len()+1 == val { - return None; - } - } - grih.clients.push(msg.addr.clone()); - }, None => { - self.grih.insert(kunjika, Grih { - name: None, - length: msg.length, - clients: vec![msg.addr] - }); - } - } - return Some(kunjika); - } - } + fn handle(&mut self, msg: ms::SetKunjikaUser, ctx: &mut Self::Context) -> Self::Result { + let kunjika = msg.kunjika; + let vyakti = self.vyakti.iter().find(|a| a.kunjika == kunjika); + Ok(()) } } -impl Handler for ChatPinnd { +impl Handler for ChatPinnd { + type Result = Result<(), errors::GrihFullError>; + + fn handle(&mut self, msg: ms::JoinUser, _: &mut Self::Context) -> Self::Result { + match self.grih.get_mut(&msg.grih_kunjika) { + Some(grih) =>{ + if let Some(n) = grih.length { + println!("length check {}, {}, {}", grih.loog.len(), n,grih.loog.len() >= n); + if grih.loog.len() >= n { + return Err(errors::GrihFullError); + } + } + + grih.loog.push(msg.addr); + let username = msg.name.clone(); + grih.loog.iter().for_each(move |a: &Addr| { + a.do_send(ms::WsConnected { + user: username.clone() + }) + }); + }, None => { + self.grih.insert(msg.grih_kunjika, Grih { + length: msg.length, + loog: vec![msg.addr.clone()] + }); + msg.addr.do_send(ms::WsConnected { + user: msg.name + }); + } + } + + Ok(()) + } +} + +impl Handler for ChatPinnd { type Result = (); - fn handle(&mut self, msg: ms::ReciveText, _: &mut Self::Context) -> Self::Result { - println!("Here to text"); + fn handle(&mut self, msg: ms::SendText, _: &mut Self::Context) -> Self::Result { if let Some(grih) = self.grih.get(&msg.grih_kunjika) { - for client in grih.clients.iter() { - client.do_send(ms::WsMessage { + grih.loog.iter().for_each(|c| { + c.do_send(ms::WsMessage { sender: msg.sender_name.clone(), text: msg.text.clone(), }); - } + }); } } } @@ -102,12 +99,18 @@ impl Handler for ChatPinnd { fn handle(&mut self, msg: ms::LeaveUser, _: &mut Self::Context) -> Self::Result { if let Some(grih) = self.grih.get_mut(&msg.grih_kunjika) { - if let Some(i) = grih.clients.iter().position(|x| x == &msg.addr) { - grih.clients.remove(i); + if let Some(i) = grih.loog.iter().position(|x| x == &msg.addr) { + grih.loog.remove(i); } - if grih.clients.len() == 0 { + if grih.loog.len() == 0 { self.grih.remove(&msg.grih_kunjika); + } else { + grih.loog.iter().for_each(|a| { + a.do_send(ms::WsDisconnected { + user: "u".to_owned() + }) + }); } } } @@ -117,7 +120,8 @@ impl Default for ChatPinnd { fn default() -> Self { ChatPinnd { grih: HashMap::new(), - vyaktigat_waitlist: Vec::new() + vyaktigat_waitlist: Vec::new(), + vyakti: Vec::new() } } } diff --git a/src/errors/grih_full_error.rs b/src/errors/grih_full_error.rs new file mode 100644 index 0000000..513a7b9 --- /dev/null +++ b/src/errors/grih_full_error.rs @@ -0,0 +1,10 @@ +use std::fmt; +#[derive(Debug, Clone)] +pub struct GrihFullError; + +impl fmt::Display for GrihFullError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "No space left for more user!") + } +} + diff --git a/src/errors/mod.rs b/src/errors/mod.rs new file mode 100644 index 0000000..04a155e --- /dev/null +++ b/src/errors/mod.rs @@ -0,0 +1,6 @@ +mod grih_full_error; +mod user_kunjika_error; +pub mod vayakti_list_error; + +pub use grih_full_error::GrihFullError; +pub use user_kunjika_error::AlreadyExistError; \ No newline at end of file diff --git a/src/errors/user_kunjika_error.rs b/src/errors/user_kunjika_error.rs new file mode 100644 index 0000000..682db3c --- /dev/null +++ b/src/errors/user_kunjika_error.rs @@ -0,0 +1,10 @@ +use std::fmt; + +#[derive(Debug, Clone)] +pub struct AlreadyExistError; + +impl fmt::Display for AlreadyExistError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "User kunjika already exist!") + } +} \ No newline at end of file diff --git a/src/errors/vayakti_list_error.rs b/src/errors/vayakti_list_error.rs new file mode 100644 index 0000000..81411c8 --- /dev/null +++ b/src/errors/vayakti_list_error.rs @@ -0,0 +1,24 @@ +use std::fmt::{Display, Formatter, Result}; +use std::error::Error; + +#[derive(Debug)] +pub struct ElementNotFount; + +#[derive(Debug)] +pub struct KeyAlreadyExist; + +impl Display for ElementNotFount { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "Element not found") + } +} + +impl Error for ElementNotFount {} + +impl Display for KeyAlreadyExist { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "Key already do exist") + } +} + +impl Error for KeyAlreadyExist {} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 007ffa0..ce8e14b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,11 @@ use actix_web_actors::ws; use ws_sansad::WsSansad; mod config; +mod errors; mod messages; mod ws_sansad; mod chat_pinnd; +mod vyakti_list; #[actix_web::main] async fn main() -> std::io::Result<()> { diff --git a/src/messages.rs b/src/messages.rs index cd66407..8a14135 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -2,27 +2,28 @@ use actix::prelude::*; use crate::ws_sansad::WsSansad; +use crate::errors; // For ChatPinnd #[derive(Clone, Message)] -#[rtype(result = "Option")] -pub struct JoinUser { - pub grih: JoinUserGrihType, - pub length: Option, - pub addr: Addr +#[rtype(result = "Result<(), errors::AlreadyExistError>")] +pub struct SetKunjikaUser { + pub kunjika: String } -#[allow(dead_code)] -#[derive(Clone)] -pub enum JoinUserGrihType { - Name(String), - Kunjika(i32) +#[derive(Clone, Message)] +#[rtype(result = "Result<(), errors::GrihFullError>")] +pub struct JoinUser { + pub grih_kunjika: String, + pub length: Option, + pub addr: Addr, + pub name: String } #[derive(Clone, Message)] #[rtype(result = "()")] -pub struct ReciveText { - pub grih_kunjika: i32, +pub struct SendText { + pub grih_kunjika: String, pub sender_name: String, pub text: String } @@ -30,14 +31,40 @@ pub struct ReciveText { #[derive(Clone, Message)] #[rtype(result = "()")] pub struct LeaveUser { - pub grih_kunjika: i32, + pub grih_kunjika: String, pub addr: Addr } +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct AddUserKunjika { + pub old_kunjika: Option, + pub kunjika: String +} + // For WsSansad #[derive(Clone, Message)] #[rtype(result = "()")] pub struct WsMessage { pub text: String, pub sender: String -} \ No newline at end of file +} + +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsConnected { + pub user: String +} + +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsDisconnected { + pub user: String +} + +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsResponse { + pub result: String, + pub message: String +} diff --git a/src/vyakti_list.rs b/src/vyakti_list.rs new file mode 100644 index 0000000..a7c58a8 --- /dev/null +++ b/src/vyakti_list.rs @@ -0,0 +1,98 @@ +use std::error::Error; + +use crate::errors::vayakti_list_error as er; + +#[derive(Debug, Clone)] +pub struct VecMap(Vec>); + +#[derive(Debug, Clone)] +pub struct VecMapElement { + key: K, + value: V +} + +impl IntoIterator for VecMap { + type IntoIter = std::vec::IntoIter>; + type Item = VecMapElement; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl VecMap { + pub fn new() -> VecMap { + VecMap(Vec::new()) + } + + pub fn insert(&mut self, key: K, value: V) { + let key_tmp = key.clone(); + match self.0.iter_mut().find(move |a| { + a.key == key_tmp + }) { + Some(i) => { + i.value = value.clone(); + } + None => { + self.0.push(VecMapElement { + key, + value + }); + } + } + } + + pub fn get(&self, key: K) -> Option<&V> { + match self.0.iter().find(|a| { + a.key == key + }) { + Some(v) => Some(&v.value), + None => None + } + } + + pub fn get_mut(&mut self, key: K) -> Option<&mut V> { + match self.0.iter_mut().find(move |a| { + a.key == key + }) { + Some(v) => Some(&mut v.value), + None => None + } + } + + pub fn remove(&mut self, key: K) -> Result<(), er::ElementNotFount> { + match self.0.iter().position(move |a| { + a.key == key + }) { + Some(i) => { + self.0.remove(i); + } + None => { + return Err(er::ElementNotFount); + } + } + Ok(()) + } + + pub fn change_key(&mut self, key: K, new_key: K) -> Result<(), &dyn Error> { + let key_tmp = key.clone(); + if let Some(_) = self.0.iter().position(move |a| { + a.key == key_tmp + }) { + return Err(&er::KeyAlreadyExist); + } + + + match self.0.iter_mut().find(move |a| { + a.key == key + }) { + Some(i) => { + i.key = new_key; + } + None => { + return Err(&er::ElementNotFount); + } + } + Ok(()) + } +} diff --git a/src/ws_sansad.rs b/src/ws_sansad.rs index c8a4d9a..b77837a 100644 --- a/src/ws_sansad.rs +++ b/src/ws_sansad.rs @@ -4,27 +4,23 @@ use actix_broker::{Broker, SystemBroker}; use actix_web_actors::ws; use serde_json::{json, Value}; -use crate::chat_pinnd::ChatPinnd; -use crate::messages as ms; +use crate::{chat_pinnd::ChatPinnd, messages as ms}; +use crate::errors; pub struct WsSansad { name: String, + kunjika: Option, isthiti: Isthiti, - addr: Option> + addr: Option>, } #[allow(dead_code)] enum Isthiti { None, - Grih(Grih), + Grih(String), // VraktigatWaitlist } -pub struct Grih { - kunjika: i32, - // name: String -} - impl Actor for WsSansad { type Context = ws::WebsocketContext; @@ -33,7 +29,7 @@ impl Actor for WsSansad { } fn stopping(&mut self, _: &mut Self::Context) -> Running { - futures::executor::block_on(self.end()); + futures::executor::block_on(self.leave_grih()); Running::Stop } } @@ -65,73 +61,153 @@ impl Handler for WsSansad { } } +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::WsResponse, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "resp", + "result": msg.result, + "message": msg.message + }); + ctx.text(json.to_string()); + } +} + +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::WsConnected, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "connected", + "user": msg.user + }); + ctx.text(json.to_string()); + } +} + +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::WsDisconnected, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "disconnected", + "user": msg.user + }); + ctx.text(json.to_string()); + } +} + + impl WsSansad { pub fn new() -> Self { WsSansad { name: "()".to_owned(), + kunjika: None, isthiti: Isthiti::None, - addr: None + addr: None, } } async fn parse_text_handle(&mut self, msg: String) { + println!("{}", msg); if let Ok(val) = serde_json::from_str::(&msg) { match val.get("cmd").unwrap().as_str().unwrap() { - "name" => { self.name(val).await }, - "join" => { self.join(val).await }, - "text" => { self.text(val).await }, - "end" => { self.end().await }, + "name" => { self.set_name(val).await }, + "kunjika" => { self.set_kunjika(val).await }, + "join" => { self.join_grih(val).await }, + "text" => { self.send_text(val).await }, + "leave" => { self.leave_grih().await }, _ => () } } } - async fn name(&mut self, val: Value) { - self.name = val.get("name").unwrap().as_str().unwrap().to_owned(); + fn send_ok_response(&self) { + self.addr.clone().unwrap().do_send(ms::WsResponse { + result: "Ok".to_owned(), + message: "".to_owned() + }); } - async fn text(&mut self, val: Value) { - let text = val.get("text").unwrap().as_str().unwrap().to_owned(); + fn send_err_response(&self, text: &str) { + self.addr.clone().unwrap().do_send(ms::WsResponse { + result: "Err".to_owned(), + message: text.to_owned() + }); + } + + async fn set_name(&mut self, val: Value) { + self.name = val.get("name").unwrap().as_str().unwrap().to_owned(); + self.send_ok_response(); + } + + async fn set_kunjika(&mut self, val: Value) { + self.name = val.get("name").unwrap().as_str().unwrap().to_owned(); + self.send_ok_response(); + } + + async fn join_grih(&mut self, val: Value) { + if let None = self.kunjika { + self.send_err_response("No user kunjika set"); + return; + } + + let kunjika = match val.get("kunjika") { + Some(val) => val, + None => { + self.send_err_response("Invalid request"); + return; + } + }.as_str().unwrap().to_owned(); + let length: Option = match val.get("length") { + Some(val) => Some(val.as_i64().unwrap() as usize), + None => None + }; + + let result: Result<(), errors::GrihFullError> = ChatPinnd::from_registry().send(ms::JoinUser { + grih_kunjika: kunjika.clone(), + length, + addr: self.addr.clone().unwrap(), + name: self.name.clone() + }).await.unwrap(); + + match result { + Ok(_) => { + self.isthiti = Isthiti::Grih(kunjika); + self.send_ok_response() + }, + Err(e) => self.send_err_response(&format!("{}", e)) + } + } + + async fn send_text(&mut self, val: Value) { + let text = match val.get("text") { + Some(val) => val, + None => { + self.send_err_response("Invalid request"); + return; + } + }.as_str().unwrap().to_owned(); let grih_kunjika = match &self.isthiti { Isthiti::Grih(g) => { - g.kunjika + g.clone() }, Isthiti::None => { return; } }; - - Broker::::issue_async(ms::ReciveText { + Broker::::issue_async(ms::SendText { grih_kunjika, sender_name: self.name.clone(), text }); } - async fn join(&mut self, val: Value) { - let name = val.get("name").unwrap().as_str().unwrap().to_owned(); - let length: Option = match val.get("length") { - Some(val) => Some(val.as_i64().unwrap() as usize), - None => None - }; - - let kunjika = ChatPinnd::from_registry().send(ms::JoinUser{ - grih: ms::JoinUserGrihType::Name(name.clone()), - length, - addr: self.addr.clone().unwrap() - }).await.unwrap().unwrap(); - - self.isthiti = Isthiti::Grih(Grih { - kunjika, - // name - }) - } - - async fn end(&mut self) { + async fn leave_grih(&mut self) { if let Isthiti::Grih(val) = &mut self.isthiti { Broker::::issue_async(ms::LeaveUser { - grih_kunjika: val.kunjika, + grih_kunjika: val.clone(), addr: self.addr.clone().unwrap() }); + + self.send_ok_response(); } } } diff --git a/static/index.html b/static/index.html index e79ed40..24effc2 100644 --- a/static/index.html +++ b/static/index.html @@ -25,10 +25,17 @@ function wsend(p) { socket.send(p); } -function join(r) { +function join(r, l) { socket.send(JSON.stringify({ cmd: "join", - name: r + kunjika: r, + length: l + })); +} + +function leave() { + socket.send(JSON.stringify({ + cmd: "leave" })); }