diff --git a/src/chat_pinnd.rs b/src/chat_pinnd.rs index e5be3e4..55befec 100644 --- a/src/chat_pinnd.rs +++ b/src/chat_pinnd.rs @@ -4,15 +4,14 @@ use std::{collections::HashMap, vec}; use actix::prelude::*; use actix_broker::BrokerSubscribe; -use vecmap::VecMap; +use ms::Resp; -use crate::{errors, ws_sansad, messages as ms}; +use crate::{ws_sansad, messages as ms}; #[allow(dead_code)] pub struct ChatPinnd { grih: HashMap, // kunjika, Grih vyaktigat_waitlist: Vec, - non_connected_vyakti: VecMap, // kunjika, vayakti } pub struct Grih { @@ -24,7 +23,7 @@ pub struct Loog { addr: Addr, kunjika: String, name: String, - _tags: Vec + tags: Option> } #[derive(Debug, Clone)] @@ -34,6 +33,8 @@ pub struct Vyakti { } pub struct VyaktiWatchlist { kunjika: String, + name: String, + tags: Vec, addr: Addr } @@ -43,87 +44,65 @@ impl Actor for ChatPinnd { fn started(&mut self, ctx: &mut Self::Context) { // for actix broker self.subscribe_system_async::(ctx); + self.subscribe_system_async::(ctx); self.subscribe_system_async::(ctx); } } -// Set information of user -impl Handler for ChatPinnd { - type Result = Option; - - fn handle(&mut self, msg: ms::SetInfoVyakti, _: &mut Self::Context) -> Self::Result { - // check if vayakti info is not modified and do key exist - if !msg.modify { - if self.non_connected_vyakti.key_exist(&msg.kunjika) { - return Some("Kunjika Exists".to_owned()); - } - if let Some(_) = self.grih.iter().position(|a| { - match a.1.loog.iter().position(|b| { - b.kunjika == msg.kunjika - }) { - Some(_) => true, - None => false - } - }) { - return Some("Kunjika Exists".to_owned()); - } - } - // change value - self.non_connected_vyakti.insert(msg.kunjika, Vyakti { - name: msg.name, - tags: msg.tags - }); - - None - } -} - /// Join grih impl Handler for ChatPinnd { - type Result = Result<(), errors::GrihFullError>; + type Result = Resp; fn handle(&mut self, msg: ms::JoinGrih, _: &mut Self::Context) -> Self::Result { - match self.grih.get_mut(&msg.grih_kunjika) { // check if group exist + // check if user exist + if let Some(_) = self.vyaktigat_waitlist.iter().position(|vk| vk.kunjika == msg.kunjika) { + println!("got in watchlist"); + return Resp::Err("Kunjika already exist".to_owned()); + } + + if let Some(_) = self.grih.iter().position(|(_,g)| { + match g.loog.iter().position(|a| {println!("Got in grih {:?} {:?}", a.kunjika, msg.kunjika); a.kunjika == msg.kunjika}) { + Some(_) => true, + None => false + } + }) { + return Resp::Err("Kunjika already exist".to_owned()); + } + + // check if grih exist and add user + match self.grih.get_mut(&msg.grih_kunjika) { Some(grih) =>{ // exist - // check if group have no space left + // check if grih have no space left if let Some(n) = grih.length { if grih.loog.len() >= n { - return Err(errors::GrihFullError); + return Resp::Err("Grih have no space".to_owned()); } } - let vayakti = self.non_connected_vyakti.get(&msg.kunjika).unwrap(); - let name = vayakti.name.to_owned(); - let tags = vayakti.tags.to_owned(); - - let name_tmp = name.clone(); - let kunjika_tmp = msg.kunjika.clone(); - grih.loog.iter().for_each(move |a: &Loog| { + grih.loog.iter().for_each(|a: &Loog| { a.addr.do_send(ms::WsConnected { - name: name_tmp.clone(), - kunjika: kunjika_tmp.clone() + name: msg.name.to_owned(), + kunjika: msg.kunjika.to_owned() }) }); - self.non_connected_vyakti.remove(&msg.kunjika).unwrap_or(()); - grih.loog.push(Loog::new(msg.addr, msg.kunjika,name,tags)); + + grih.loog.push(Loog::new(msg.addr, msg.kunjika,msg.name, None)); }, None => { // don't exist - // add group and notify - let vayakti = self.non_connected_vyakti.get(&msg.kunjika).unwrap(); + // add grih and notify msg.addr.do_send(ms::WsConnected { - name: vayakti.name.clone(), - kunjika: msg.kunjika.clone() + name: msg.name.to_owned(), + kunjika: msg.kunjika.to_owned() }); self.grih.insert(msg.grih_kunjika, Grih { length: msg.length, - loog: vec![Loog::new(msg.addr,msg.kunjika.clone(),vayakti.name.clone(),vayakti.tags.clone())] + loog: vec![Loog::new(msg.addr,msg.kunjika,msg.name, None)] }); - self.non_connected_vyakti.remove(&msg.kunjika).unwrap_or(()); } } - Ok(()) + Resp::Ok } } @@ -132,50 +111,165 @@ impl Handler for ChatPinnd { /// Check if watchlist is empty, if yes add the kunjika andaddr to watchlist /// if watchlist have people get 0th person an connect it impl Handler for ChatPinnd { - type Result = Option<()>; + type Result = Resp; fn handle(&mut self, msg: ms::JoinRandom, _: &mut Self::Context) -> Self::Result { + // check if user exist + if let Some(_) = self.vyaktigat_waitlist.iter().position(|vk| vk.kunjika == msg.kunjika) { + return Resp::Err("Kunjika already exist".to_owned()); + } + + if let Some(_) = self.grih.iter().position(|(_,g)| { + match g.loog.iter().position(|a| a.kunjika == msg.kunjika) { + Some(_) => true, + None => false + } + }) { + return Resp::Err("Kunjika already exist".to_owned()); + } + // Check if watch list is empty if self.vyaktigat_waitlist.len() == 0 { self.vyaktigat_waitlist.push(VyaktiWatchlist { kunjika: msg.kunjika, - addr: msg.addr + addr: msg.addr, + name: msg.name, + tags: msg.tags }); - return None; + return Resp::None; } - // connect 0th person - let vayakti_watchlist = self.vyaktigat_waitlist.remove(0); - let vayakti1_name: String; - let vayakti2_name: String; - let group_kunjika: String; - { - let vayakti1 = self.non_connected_vyakti.get(&msg.kunjika).unwrap(); - let vayakti2 = self.non_connected_vyakti.get(&vayakti_watchlist.kunjika).unwrap(); - vayakti1_name = vayakti1.name.clone(); - vayakti2_name = vayakti2.name.clone(); - group_kunjika = format!("gupt_{}>{}",msg.kunjika.clone(), vayakti_watchlist.kunjika); - self.grih.insert(group_kunjika.clone(), Grih { - length: Some(2), - loog: vec![Loog::new(msg.addr.clone(), msg.kunjika.clone(), vayakti1.name.clone(), vayakti1.tags.clone()), - Loog::new(vayakti_watchlist.addr.clone(), vayakti_watchlist.kunjika.clone(), vayakti2.name.clone(), vayakti2.tags.clone())] - }); - } - - self.non_connected_vyakti.remove(&msg.kunjika).unwrap_or(()); - self.non_connected_vyakti.remove(&vayakti_watchlist.kunjika).unwrap_or(()); + // connect person with tag or to zero + let pos = match self.vyaktigat_waitlist.iter().position(|vk| { + match vk.tags.iter().position(|t| msg.tags.contains(t)) { + Some(_) => true, + None => false + } + }) { + Some(i) => i, + None => { + self.vyaktigat_waitlist.push(VyaktiWatchlist { + kunjika: msg.kunjika, + addr: msg.addr, + name: msg.name, + tags: msg.tags + }); + return Resp::None; + } + }; + let vayakti_watchlist = self.vyaktigat_waitlist.remove(pos); + let group_kunjika = format!("gupt_{}>{}",msg.kunjika.to_owned(), vayakti_watchlist.kunjika); + self.grih.insert(group_kunjika.to_owned(), Grih { + length: Some(2), + loog: vec![Loog::new(msg.addr.clone(), msg.kunjika.to_owned(), msg.name.to_owned(), Some(msg.tags.clone())), + Loog::new(vayakti_watchlist.addr.clone(), vayakti_watchlist.kunjika.to_owned(), vayakti_watchlist.name.to_owned(), Some(vayakti_watchlist.tags.clone()))] + }); + // notify about connection msg.addr.do_send(ms::WsConnectedRandom { - ajnyat_name: vayakti2_name, - grih_kunjika: group_kunjika.clone() + name: vayakti_watchlist.name, + kunjika: vayakti_watchlist.kunjika, + grih_kunjika: group_kunjika.to_owned() }); - vayakti_watchlist.addr.do_send(ms::WsConnectedRandom { - ajnyat_name: vayakti1_name, + name: msg.name, + kunjika: msg.kunjika.to_owned(), grih_kunjika: group_kunjika }); - Some(()) + Resp::Ok + } +} + +/// Next Random user +impl Handler for ChatPinnd { + type Result = Resp; + fn handle(&mut self, msg: ms::JoinRandomNext, _: &mut Self::Context) -> Self::Result { + let grih = self.grih.get_mut(&msg.grih_kunjika).unwrap(); + + let loog_i = grih.loog.iter().position(|a| a.kunjika == msg.kunjika).unwrap(); + + let addr; + let name; + let tags; + + { + let loog = grih.loog.get(0).unwrap(); + + if let None = loog.tags { + return Resp::Err("You are not a randome vyakti!".to_owned()); + } + + addr = loog.addr.clone(); + name = loog.name.to_owned(); + tags = loog.tags.clone().unwrap(); + } + + // Check if watch list is empty + if self.vyaktigat_waitlist.len() == 0 { + self.vyaktigat_waitlist.push(VyaktiWatchlist { + kunjika: msg.kunjika, + addr, + name, + tags + }); + return Resp::None; + } + + // connect person with tag or to zero + let tags = tags.clone(); + let pos = match self.vyaktigat_waitlist.iter().position(|vk| { + match vk.tags.iter().position(|t| tags.contains(t)) { + Some(_) => true, + None => false + } + }) { + Some(i) => i, + None => { + self.vyaktigat_waitlist.push(VyaktiWatchlist { + kunjika: msg.kunjika, + addr, + name, + tags + }); + return Resp::None; + } + }; + + let vayakti_watchlist = self.vyaktigat_waitlist.remove(pos); + let group_kunjika = format!("gupt_{}>{}",msg.kunjika.to_owned(), vayakti_watchlist.kunjika); + grih.loog.remove(loog_i); + grih.loog.iter().for_each(|a| { + a.addr.do_send(ms::WsDisconnected { + kunjika: msg.kunjika.to_owned(), + name: name.to_owned() + }) + }); + let log_count = grih.loog.len(); + drop(grih); + if log_count == 0 { + self.grih.remove(&msg.grih_kunjika); + } + self.grih.insert(group_kunjika.to_owned(), Grih { + length: Some(2), + loog: vec![Loog::new(addr.clone(), msg.kunjika.to_owned(), name.to_owned(), Some(tags.clone())), + Loog::new(vayakti_watchlist.addr.clone(), vayakti_watchlist.kunjika.to_owned(), vayakti_watchlist.name.to_owned(), Some(vayakti_watchlist.tags.clone()))] + }); + + // notify about connection + addr.do_send(ms::WsConnectedRandom { + name: vayakti_watchlist.name, + kunjika: vayakti_watchlist.kunjika, + grih_kunjika: group_kunjika.to_owned() + }); + + vayakti_watchlist.addr.do_send(ms::WsConnectedRandom { + name, + kunjika: msg.kunjika.to_owned(), + grih_kunjika: group_kunjika + }); + + Resp::Ok } } @@ -187,9 +281,9 @@ impl Handler for ChatPinnd { if let Some(grih) = self.grih.get(&msg.grih_kunjika) { grih.loog.iter().for_each(|c| { c.addr.do_send(ms::WsText { - sender_kunjika: msg.kunjika.clone(), - text: msg.text.clone(), - reply: msg.reply.clone() + sender_kunjika: msg.kunjika.to_owned(), + text: msg.text.to_owned(), + reply: msg.reply.to_owned() }); }); } @@ -204,8 +298,8 @@ impl Handler for ChatPinnd { if let Some(grih) = self.grih.get(&msg.grih_kunjika) { grih.loog.iter().for_each(|c| { c.addr.do_send(ms::WsStatus { - sender_kunjika: msg.kunjika.clone(), - status: msg.status.clone(), + sender_kunjika: msg.kunjika.to_owned(), + status: msg.status.to_owned(), }); }); } @@ -220,7 +314,7 @@ impl Handler for ChatPinnd { if let Some(grih) = self.grih.get(&msg.grih_kunjika) { let mut list = Vec::new(); for x in grih.loog.iter() { - list.push((x.kunjika.clone(),x.name.clone())); + list.push((x.kunjika.to_owned(),x.name.to_owned())); } serde_json::json!(list).to_string() } else { @@ -236,23 +330,23 @@ impl Handler for ChatPinnd { fn handle(&mut self, msg: ms::LeaveUser, _: &mut Self::Context) -> Self::Result { if let Some(grih_kunjika) = &msg.grih_kunjika { if let Some(grih) = self.grih.get_mut(grih_kunjika) { - if let Some(i) = grih.loog.iter().position(|x| x.addr == msg.addr) { - grih.loog.remove(i); - } + let name = if let Some(i) = grih.loog.iter().position(|x| x.addr == msg.addr) { + grih.loog.remove(i).name + } else { "".to_owned() }; if grih.loog.len() == 0 { self.grih.remove(grih_kunjika); } else { grih.loog.iter().for_each(|a| { a.addr.do_send(ms::WsDisconnected { - kunjika: msg.kunjika.clone() + kunjika: msg.kunjika.to_owned(), + name: name.to_owned() }) }); } } } - self.non_connected_vyakti.remove(&msg.kunjika).unwrap_or(()); if let Some(i) = self.vyaktigat_waitlist.iter().position(|a| a.kunjika == msg.kunjika) { self.vyaktigat_waitlist.remove(i); } @@ -263,8 +357,7 @@ impl Default for ChatPinnd { fn default() -> Self { ChatPinnd { grih: HashMap::new(), - vyaktigat_waitlist: Vec::new(), - non_connected_vyakti: VecMap::new() + vyaktigat_waitlist: Vec::new() } } } @@ -273,12 +366,13 @@ impl Loog { fn new(addr: Addr, kunjika: String, name: String, - tags: Vec) -> Self { + tags: Option>) -> Self { + Loog { addr, kunjika, name, - _tags:tags + tags } } } diff --git a/src/errors/user_kunjika_error.rs b/src/errors/user_kunjika_error.rs index 682db3c..554fced 100644 --- a/src/errors/user_kunjika_error.rs +++ b/src/errors/user_kunjika_error.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::error::Error; #[derive(Debug, Clone)] pub struct AlreadyExistError; @@ -7,4 +8,8 @@ impl fmt::Display for AlreadyExistError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "User kunjika already exist!") } +} + +impl Error for AlreadyExistError { + } \ No newline at end of file diff --git a/src/messages.rs b/src/messages.rs index 4dcfa62..2b7bfbd 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,35 +1,37 @@ //! Messages to be sent between Actors use actix::prelude::*; +use dev::{MessageResponse, ResponseChannel}; use crate::ws_sansad::WsSansad; -use crate::errors; //################################################## For ChatPinnd ################################################## /// Request to change information of vayakti to list of vayakti im ChatPind -#[derive(Clone, Message)] -#[rtype(result = "Option")] // None if no error -pub struct SetInfoVyakti { - pub kunjika: String, - pub name: String, - pub tags: Vec, - pub modify: bool -} /// Request to Grih with its kunjika #[derive(Clone, Message)] -#[rtype(result = "Result<(), errors::GrihFullError>")] +#[rtype(result = "Resp")] pub struct JoinGrih { pub grih_kunjika: String, pub length: Option, pub addr: Addr, - pub kunjika: String + pub kunjika: String, + pub name: String, } /// Request to connect Random vayakti #[derive(Clone, Message)] -#[rtype(result = "Option<()>")] +#[rtype(result = "Resp")] pub struct JoinRandom { pub addr: Addr, + pub kunjika: String, + pub name: String, + pub tags: Vec, +} +/// Request to connect Random vayakti +#[derive(Clone, Message)] +#[rtype(result = "Resp")] +pub struct JoinRandomNext { + pub grih_kunjika: String, pub kunjika: String } @@ -93,7 +95,6 @@ pub struct WsList { pub json: String } - // Notify Someone connected #[derive(Clone, Message)] #[rtype(result = "()")] @@ -106,7 +107,8 @@ pub struct WsConnected { #[derive(Clone, Message)] #[rtype(result = "()")] pub struct WsDisconnected { - pub kunjika: String + pub kunjika: String, + pub name: String } // Give response message @@ -121,7 +123,25 @@ pub struct WsResponse { #[derive(Clone, Message)] #[rtype(result = "()")] pub struct WsConnectedRandom { - pub ajnyat_name: String, + pub name: String, + pub kunjika: String, pub grih_kunjika: String } +//################################################## Helper ################################################## +pub enum Resp { + Ok, + Err(String), + None +} +impl MessageResponse for Resp +where + A: Actor, + M: Message, +{ + fn handle>(self, _: &mut A::Context, tx: Option) { + if let Some(tx) = tx { + tx.send(self); + } + } +} \ No newline at end of file diff --git a/src/ws_sansad.rs b/src/ws_sansad.rs index 0ccbf84..843b7a5 100644 --- a/src/ws_sansad.rs +++ b/src/ws_sansad.rs @@ -2,15 +2,22 @@ use actix::prelude::*; use actix_broker::{Broker, SystemBroker}; use actix_web_actors::ws; +use ms::Resp; use serde_json::{json, Value}; +use std::time::{Duration, Instant}; use crate::{chat_pinnd::ChatPinnd, messages as ms, validator::{Validation as vl, validate}}; -use crate::errors; + +/// How often heartbeat pings are sent +const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); +/// How long before lack of client response causes a timeout +const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); pub struct WsSansad { - kunjika: Option, + kunjika: String, isthiti: Isthiti, addr: Option>, + hb: Instant } #[derive(Debug)] @@ -25,6 +32,7 @@ impl Actor for WsSansad { fn started(&mut self, ctx: &mut Self::Context) { self.addr = Some(ctx.address().clone()); // own addr + self.hb(ctx); } fn stopping(&mut self, _: &mut Self::Context) -> Running { @@ -37,14 +45,18 @@ impl Actor for WsSansad { impl StreamHandler> for WsSansad { fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { match msg { - Ok(ws::Message::Ping(msg)) => ctx.ping(&msg), - Ok(ws::Message::Text(msg)) => { + Ok(ws::Message::Ping(msg)) => { + ctx.ping(&msg); + self.hb = Instant::now(); + }, Ok(ws::Message::Pong(_)) => { + self.hb = Instant::now(); + }, Ok(ws::Message::Text(msg)) => { futures::executor::block_on(self.parse_text_handle(msg)); - }, - Ok(ws::Message::Close(msg)) => { + }, Ok(ws::Message::Close(msg)) => { ctx.close(msg); + ctx.stop(); } - _ => ctx.close(None) + _ => ctx.stop() } } } @@ -69,7 +81,7 @@ impl Handler for WsSansad { type Result = (); fn handle(&mut self, msg: ms::WsStatus, ctx: &mut Self::Context) -> Self::Result { let json = json!({ - "cmd": "text", + "cmd": "status", "status": msg.status, "kunjika": msg.sender_kunjika // Sender's kunjuka }); @@ -121,7 +133,8 @@ impl Handler for WsSansad { fn handle(&mut self, msg: ms::WsDisconnected, ctx: &mut Self::Context) -> Self::Result { let json = json!({ "cmd": "disconnected", - "name": msg.kunjika + "name": msg.name, + "kunjika": msg.kunjika }); ctx.text(json.to_string()); } @@ -133,8 +146,9 @@ impl Handler for WsSansad { fn handle(&mut self, msg: ms::WsConnectedRandom, ctx: &mut Self::Context) -> Self::Result { self.isthiti = Isthiti::Grih(msg.grih_kunjika); let json = json!({ - "cmd": "connected", - "ajnyat": msg.ajnyat_name + "cmd": "random", + "name": msg.name, + "kunjika": msg.kunjika }); ctx.text(json.to_string()); } @@ -143,20 +157,41 @@ impl Handler for WsSansad { impl WsSansad { pub fn new() -> Self { WsSansad { - kunjika: None, + kunjika: String::new(), isthiti: Isthiti::None, addr: None, + hb: Instant::now() } } + /// helper method that sends ping to client every second. + /// + /// also this method checks heartbeats from client + fn hb(&self, ctx: &mut ::Context) { + ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { + // check client heartbeats + if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { + // heartbeat timed out + println!("Websocket Client heartbeat failed, disconnecting!"); + + // stop actor + ctx.stop(); + + // don't try to send a ping + return; + } + + ctx.ping(b""); + }); + } + /// parse the request text from client 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() { - "seinfo" => { self.set_info(val).await }, "join" => { self.join_grih(val).await }, - "rand" => { self.join_random().await }, + "rand" => { self.join_random(val).await }, + "randnext" => { self.join_random_next().await }, "text" => { self.send_text(val).await }, "status" => { self.send_status(val).await }, "list" => { self.list().await }, @@ -181,10 +216,17 @@ impl WsSansad { message: text.to_owned() }); } + /// Request for joining to random person + async fn join_random(&mut self, val: Value) { + // Check is already joined + match self.isthiti { + Isthiti::None => (), + Isthiti::VraktigatWaitlist => { + self.send_ok_response("watchlist"); + return; + }, Isthiti::Grih(_) => return + } - /// send info of user and modify if needed - async fn set_info(&mut self, val: Value) { - // parse parameters let kunjika = match val.get("kunjika") { Some(val ) => val.as_str().unwrap().to_owned(), None => { @@ -222,32 +264,69 @@ impl WsSansad { return; } - // check if eing modified - let modify = self.kunjika == Some(kunjika.clone()); - - //request - let result: Option = ChatPinnd::from_registry().send(ms::SetInfoVyakti { - kunjika: kunjika.clone(), + // request + let result: Resp = ChatPinnd::from_registry().send(ms::JoinRandom{ + addr: self.addr.clone().unwrap(), + kunjika: kunjika.to_owned(), name, - tags, - modify + tags }).await.unwrap(); - if let Some(msg) = result { - self.send_err_response(&msg); - return; + match result { + Resp::Err(err) => self.send_err_response(&err), + Resp::Ok => self.kunjika = kunjika, + Resp::None => { + self.addr.clone().unwrap().do_send(ms::WsResponse{ + result: "watch".to_owned() , + message: "Watchlist".to_owned() + }); + self.isthiti = Isthiti::VraktigatWaitlist; + self.kunjika = kunjika + } } - - self.kunjika = Some(kunjika); - self.send_ok_response("info changed"); } /// Request for joining to random person - async fn join_random(&mut self) { - // check if vayakti exist - if let None = self.kunjika { - self.send_err_response("No vayakti kunjika set"); - return; + async fn join_random_next(&mut self) { + // Check is already joined + let grih_kunjika = match &self.isthiti { + Isthiti::VraktigatWaitlist => { + self.send_ok_response("watchlist"); + return; + }, + Isthiti::Grih(grih_kunjika) => grih_kunjika, + Isthiti::None => { + self.send_ok_response("Not allowed"); + return; + } + }; + + // request + let result: Resp = ChatPinnd::from_registry().send(ms::JoinRandomNext{ + kunjika: self.kunjika.to_owned(), + grih_kunjika: grih_kunjika.to_owned(), + }).await.unwrap(); + + match result { + Resp::Err(err) => self.send_err_response(&err), + Resp::None => { + self.addr.clone().unwrap().do_send(ms::WsResponse{ + result: "watch".to_owned() , + message: "Watchlist".to_owned() + }); + self.isthiti = Isthiti::VraktigatWaitlist; + self.kunjika = self.kunjika.to_owned() + } + _ => () + } + } + + /// Request to join to grih + async fn join_grih(&mut self, val: Value) { + // Check is already joined + match self.isthiti { + Isthiti::None => (), + _ => return } // is vayakti in watch list @@ -256,50 +335,27 @@ impl WsSansad { return; } - // request - let result: Option<()> = ChatPinnd::from_registry().send(ms::JoinRandom{ - addr: self.addr.clone().unwrap(), - kunjika: self.kunjika.clone().unwrap() - }).await.unwrap(); - - if let None = result { - self.send_ok_response("watchlist"); - self.isthiti = Isthiti::VraktigatWaitlist; - } - } - - /// Request to join to grih - async fn join_grih(&mut self, val: Value) { - //check user exist - if let None = self.kunjika { - self.send_err_response("No vayakti kunjika set"); - return; - } - - // Check is already joined - match self.isthiti { - Isthiti::None => (), - _ => { - return; - } - } - - // parse parameter - let grih_kunjika = match val.get("grih_kunjika") { - Some(val) => val, + let kunjika = match val.get("kunjika") { + Some(val ) => val.as_str().unwrap().to_owned(), None => { self.send_err_response("Invalid request"); return; } - }.as_str().unwrap().to_owned(); - println!("about to validate"); - // Validate - if let Some(val ) = validate(vec![vl::NonEmpty, vl::NoGupt, vl::NoSpace], &grih_kunjika, "Grih Kunjika") { - println!("{}", val); - self.send_err_response(&val); - return; - } - + }; + let name = match val.get("name") { + Some(val ) => val.as_str().unwrap().to_owned(), + None => { + self.send_err_response("Invalid request"); + return; + } + }; + let grih_kunjika = match val.get("grih_kunjika") { + Some(val ) => val.as_str().unwrap().to_owned(), + None => { + self.send_err_response("Invalid request"); + return; + } + }; let length: Option = match val.get("length") { Some(val) => match val.as_i64(){ Some(val) => Some(val as usize), @@ -308,30 +364,47 @@ impl WsSansad { None => None }; - println!("{:?} {:?} {:?}", grih_kunjika, self.kunjika, length); + // Validate + if let Some(val ) = validate(vec![vl::NonEmpty, vl::NoSpace, vl::NoHashtag], &kunjika, "Kunjika") { + self.send_err_response(&val); + return; + } + if let Some(val ) = validate(vec![vl::NonEmpty], &name, "Name") { + self.send_err_response(&val); + return; + } + if let Some(val ) = validate(vec![vl::NonEmpty, vl::NoGupt, vl::NoSpace], &grih_kunjika, "Grih Kunjika") { + self.send_err_response(&val); + return; + } + // request - let result: Result<(), errors::GrihFullError> = ChatPinnd::from_registry().send(ms::JoinGrih { - grih_kunjika: grih_kunjika.clone(), + let result: Resp = ChatPinnd::from_registry().send(ms::JoinGrih { + grih_kunjika: grih_kunjika.to_owned(), length, addr: self.addr.clone().unwrap(), - kunjika: self.kunjika.clone().unwrap() + kunjika: kunjika.to_owned(), + name }).await.unwrap(); + match result { - Ok(_) => { + Resp::Err(err) => self.send_err_response(&err), + Resp::Ok => { self.isthiti = Isthiti::Grih(grih_kunjika); + self.kunjika = kunjika; self.send_ok_response("joined") - }, - Err(e) => self.send_err_response(&format!("{}", e)) + } + _ => () } } /// Request to join to grih async fn list(&mut self) { // check if vayakti exist - if let None = self.kunjika { - self.send_err_response("No vayakti kunjika set"); + if let Isthiti::None = self.isthiti { + self.send_err_response("Not in any Grih"); return; } @@ -339,7 +412,7 @@ impl WsSansad { match &self.isthiti { Isthiti::Grih(kunjika) => { let json: String = ChatPinnd::from_registry().send(ms::List { - grih_kunjika: kunjika.clone() + grih_kunjika: kunjika.to_owned() }).await.unwrap(); self.addr.clone().unwrap().do_send(ms::WsList { @@ -356,8 +429,8 @@ impl WsSansad { /// send text to vayakti in grih async fn send_text(&mut self, val: Value) { // check if vayakti exist - if let None = self.kunjika { - self.send_err_response("No vayakti kunjika set"); + if let Isthiti::None = self.isthiti { + self.send_err_response("Not in any Grih"); return; } @@ -385,15 +458,15 @@ impl WsSansad { }; let grih_kunjika = match &self.isthiti { - Isthiti::Grih(g) => { - g.clone() + Isthiti::Grih(grih_kunjika) => { + grih_kunjika.to_owned() }, _ => { return; } }; Broker::::issue_async(ms::SendText { grih_kunjika, - kunjika: self.kunjika.clone().unwrap(), + kunjika: self.kunjika.to_owned(), text, reply }); @@ -402,8 +475,8 @@ impl WsSansad { /// send status to vayakti in grih async fn send_status(&mut self, val: Value) { // check if vayakti exist - if let None = self.kunjika { - self.send_err_response("No vayakti kunjika set"); + if let Isthiti::None = self.isthiti { + self.send_err_response("Not in any Grih"); return; } @@ -425,34 +498,32 @@ impl WsSansad { } }.as_str().unwrap().to_owned(); let grih_kunjika = match &self.isthiti { - Isthiti::Grih(g) => { - g.clone() + Isthiti::Grih(grih_kunjika) => { + grih_kunjika.to_owned() }, _ => { return; } }; Broker::::issue_async(ms::SendStatus { grih_kunjika, - kunjika: self.kunjika.clone().unwrap(), + kunjika: self.kunjika.to_owned(), status }); } - // notify leaving + /// notify leaving async fn leave_grih(&mut self) { let grih_kunjika = match &self.isthiti { Isthiti::Grih(val) => Some(val.to_owned()), _ => None }; - if let Some(ku) = &self.kunjika { - Broker::::issue_async(ms::LeaveUser { - grih_kunjika, - kunjika: - ku.to_owned(), - addr: self.addr.clone().unwrap() - }); - } + Broker::::issue_async(ms::LeaveUser { + grih_kunjika, + kunjika: self.kunjika.to_owned(), + addr: self.addr.clone().unwrap() + }); + self.isthiti = Isthiti::None; self.send_ok_response("left"); diff --git a/static/css/style.css b/static/css/style.css index 45d0d0c..fa3ba35 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -3,6 +3,7 @@ pre { font-size: 0.9em; margin-top: 0; margin-bottom: 0; + white-space: pre-wrap; } textarea { diff --git a/static/index.html b/static/index.html index ad01157..302ddce 100644 --- a/static/index.html +++ b/static/index.html @@ -30,7 +30,7 @@
Connect Grih(home) with kunjika.
-
Grih kunjika(id)
+
Grih kunjika(home id)
New group is created if no such group exists
@@ -66,7 +66,7 @@
Tags
-
Seperated by space
+
Seperated by space. Leave empty to connect anyone. Its recommended to try without tags if you don't get anyone
@@ -85,14 +85,14 @@
Lupt Chat
-
+
@@ -134,7 +134,36 @@
+ + + + diff --git a/static/js/app.js b/static/js/app.js index c5e7fd6..844c05c 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -20,18 +20,34 @@ $(document).ready(() => { }); $('.message-me, .message-other').click(function() { - activateMessage(this); + selectMessage(this); }); $('#selected_clip > .siimple-close').click(function() { - deactivateMessages(); + unselectMessages(); }); + var send_typing = false; + var timeout = null; $('#send_box').keypress(function(e) { if(e.originalEvent.charCode == 13 && !e.shiftKey) { send(); e.preventDefault(); + clearTimeout(timeout); + send_typing = false; + sendTypingEnd() + return } + if (!send_typing) { + sendTyping(); + send_typing = true; + return; + } + clearTimeout(timeout); + timeout = setTimeout(function() { + send_typing = false; + sendTypingEnd(); + },3000); }); $('#send_box').bind('input propertychange keyup', function() { @@ -47,6 +63,20 @@ $(document).ready(() => { }); }); +function sendTyping() { + socket.send(JSON.stringify({ + cmd: 'status', + status: "typing" + })); +} + +function sendTypingEnd() { + socket.send(JSON.stringify({ + cmd: 'status', + status: "typing_end" + })); +} + function calcHeight(value) { let numberOfLineBreaks = (value.match(/\n/g) || []).length; // min-height + lines x line-height + padding + border @@ -61,7 +91,9 @@ var myinfo = { kunjika: "", name: "" }; -var users = {}; +var vayaktiList = {}; +var typing = []; +var no_name_message = false; // Connection opened socket.addEventListener('open', function (event) { @@ -71,13 +103,13 @@ socket.addEventListener('open', function (event) { // Listen for messages socket.addEventListener('message', function (event) { var j = JSON.parse(event.data); - console.log(j); switch(j.cmd) { case 'resp': if(j.result == 'Err') { if($('#chat_panel').hasClass('hidden')) { $('[name="error_msg"]').text(j.message); $('[name="error_msg"]').removeClass('hidden'); + $('#progressbar').addClass('hidden'); callbacks = []; } else { pushStatus(j.message); @@ -89,24 +121,49 @@ socket.addEventListener('message', function (event) { } } break; + case 'random': + callbacks[0](); + callbacks = []; + no_name_message = true; + $('#next_btn').removeClass('hidden'); + pushStatus('Say hi to '+j.name); + vayaktiList[j.kunjika] = j.name; + break; + case 'status': + if(j.status == "typing") { + typing.push(j.kunjika); + pushTypingStatus(); + } else if(j.status == "typing_end") { + const index = typing.indexOf(j.kunjika); + if (index > -1) typing.splice(index, 1); + pushTypingStatus(); + } + break; case 'text': pushMessage(j.kunjika, j.text, j.reply); break; case 'connected': - users[j.kunjika] = j.name; + vayaktiList[j.kunjika] = j.name; pushStatus('Vyakti '+j.name+' connected as '+j.kunjika); break; case 'disconnected': - delete users[j.kunjika]; + delete vayaktiList[j.kunjika]; pushStatus('Vyakti '+j.name+' disconnected as '+j.kunjika); break; - + case 'list': + JSON.parse(j.vayakti).forEach(function(usr) { + vayaktiList[usr[0]] = usr[1]; + }); + break; } }); +var joining = false; function connect(frm) { + if(joining) return; + joining = true; var frm = $(frm); - $('#progressbar').addClass('hidden'); + $('#progressbar').removeClass('hidden'); var data = {}; frm.serializeArray().forEach(el => { data[el.name] = el.value; @@ -115,8 +172,27 @@ function connect(frm) { data['length'] = parseInt(data['length']); } callbacks.push(() => { - socket.send(JSON.stringify(Object.assign({cmd: frm.attr('cmd')}, data))); + cleanMessage(); + $('#progressbar').addClass('hidden'); + $('#send_box').text(''); + $('#connect_panel').addClass('hidden'); + $('[name="error_msg"]').addClass('hidden'); + $('#chat_panel').removeClass('hidden'); + $('#send_box').focus(); + $('#next_btn').addClass('hidden'); + myinfo.kunjika = data.kunjika; + myinfo.name = data.name; + no_name_message = false; + joining = false; + socket.send(JSON.stringify({cmd: 'list'})); }); + socket.send(JSON.stringify(Object.assign({cmd: frm.attr('cmd')}, data))); +} + +function connect_next() { + if(joining) return; + joining = true; + $('#progressbar').removeClass('hidden'); callbacks.push(() => { cleanMessage(); $('#progressbar').addClass('hidden'); @@ -125,10 +201,11 @@ function connect(frm) { $('[name="error_msg"]').addClass('hidden'); $('#chat_panel').removeClass('hidden'); $('#send_box').focus(); - myinfo.kunjika = data.kunjika; - myinfo.name = data.name; + $('#next_btn').addClass('hidden'); + joining = false; + socket.send(JSON.stringify({cmd: 'list'})); }); - socket.send(JSON.stringify(Object.assign({cmd: 'seinfo'}, data))); + socket.send(JSON.stringify({ cmd: 'randnext' })); } function leave() { @@ -138,19 +215,43 @@ function leave() { $('#selected_clip').addClass('hidden'); $('#action_clip').addClass('hidden'); $('#connect_panel').removeClass('hidden'); - myinfo.kunjika = ""; - myinfo.name = ""; + myinfo.kunjika = ''; + myinfo.name = ''; }); socket.send(JSON.stringify({ - cmd: "leave" + cmd: 'leave' })); } +function pushTypingStatus() { + var elm = $('#status_area > #typing'); + if(elm.length > 0) elm.remove(); + if(typing.length == 0) return; + var text = ''; + typing.forEach((val) => { + text += val + ',' + }) + text = text.substr(0, text.length-1); + text += ' is typing...' + $('#status_area').append($('
', { id: 'typing', + class:'siimple-label siimple--mx-2 siimple--my-0' }).append(text)); + + var scroll = $("#message_area_scroll"); + scroll.scrollTop(scroll[0].scrollHeight); +} + function pushMessage(sender, text, reply = null) { var isMe = myinfo.kunjika == sender; var area = $('#message_area'); var elm = $('
', {class: 'message '+(isMe?'message-me':'message-other')}); - elm.append($('
', {class: 'message-by'}).append(users[sender])); + if(!no_name_message) { + if(sender == myinfo.kunjika) + elm.append($('
', {class: 'message-by'}).append('me')) + else + elm.append($('
', {class: 'message-by'}).append(vayaktiList[sender]+'('+sender+')')) + } else { + elm.addClass('siimple--py-1'); + } if(reply != null && reply.length > 0) { elm.append( $('
', {class: 'message message-reply'}) @@ -159,35 +260,33 @@ function pushMessage(sender, text, reply = null) { } elm.append($('
').append(text));
     elm.click(function() {
-        activateMessage(this);
+        selectMessage(this);
     });
     area.append(elm);
 
-    //to bottom
     var scroll = $("#message_area_scroll");
     scroll.scrollTop(scroll[0].scrollHeight);
 }
 
-
+// in message area 
 function pushStatus(text) {
     var area = $('#message_area');
     var elm = $('
', {class: 'status'}); elm.append($('', {class: 'siimple-tag siimple-tag--dark'}).append(text)); area.append(elm); - //to bottom var scroll = $("#message_area_scroll"); scroll.scrollTop(scroll[0].scrollHeight); } -function deactivateMessages() { +function unselectMessages() { $('.active').each(function() { $(this).removeClass('active'); }); $('#selected_clip').addClass('hidden'); } -function activateMessage(t) { +function selectMessage(t) { var t = $(t); t.toggleClass('active'); @@ -214,7 +313,7 @@ function prepareReply() { el.removeClass('hidden'); el.attr('msg', text); $('#reply_clip > span').text(text.substr(0, 15)+ '...'); - deactivateMessages(); + unselectMessages(); } function send() { @@ -237,49 +336,22 @@ function copyMessagesToClipboard() { $temp.val(selectedMessageToText()).select(); document.execCommand("copy"); $temp.remove(); - deactivateMessages(); + unselectMessages(); } function cleanMessage() { $('#message_area').empty(); $('#action_clip').addClass('hidden'); } -// function wsend(p) { -// socket.send(p); -// } -// function join(r, l) { -// socket.send(JSON.stringify({ -// cmd: "join", -// grih_kunjika: r, -// length: l -// })); -// } - -// function leave() { -// socket.send(JSON.stringify({ -// cmd: "leave" -// })); -// } - -// function send(t) { -// socket.send(JSON.stringify({ -// cmd: "text", -// text: t -// })); -// } - -// function info(k, n, t) { -// socket.send(JSON.stringify({ -// cmd: "seinfo", -// kunjika: k, -// name: n, -// tags: t -// })); -// } - -// function joinrand() { -// socket.send(JSON.stringify({ -// cmd: "rand" -// })); -// } \ No newline at end of file +function vayaktiList() { + var v = $('#vayakti_list'); + v.empty(); + Object.keys(vayaktiList).forEach((key) => { + v.append($('
', {class: 'siimple-table-row'}) + .append($('
', {class: 'siimple-table-cell'}).append(key)) + .append($('
', {class: 'siimple-table-cell'}).append(vayaktiList[key]))); + }); + $('#vayakti_model').removeClass('hidden'); + $('#action_clip').addClass('hidden'); +}