use crate::AppState; use crate::DmUserMethod::*; use crate::SendUserMessage::*; use crate::User; use crate::UserHandlerMessage::*; use crate::*; use serde_json::to_string; use std::net::SocketAddr; use std::sync::{Arc, RwLock}; /// Handles users pub struct UserHandler { /// Pointer to global state state: Arc, } pub enum DmUserMethod { Addr(SocketAddr), Id(String), } /// For interacting with the user handler pub enum UserHandlerMessage { NewUser(User, SocketAddr), UserLogIn(UserLogInRequest, SocketAddr), DmUser(SendUserMessage, DmUserMethod), } /// Types of messages that can be sent to a user as a DM // TODO: try to eliminate this extra step pub enum SendUserMessage { SendUserUpdate(UserUpdate), SendChatMessage(ChatMessage), SendJudgeRound(JudgeRound), } impl UserHandler { /// Returns new UserHandler pub fn new(state: Arc) -> Self { UserHandler { state } } /// Handles incoming messages pub async fn handle(&self, message: UserHandlerMessage) { match message { NewUser(user, addr) => { // TODO: make this not async self.set_up_new_user(user, addr).await } UserLogIn(request, addr) => self.login(request, addr).await, DmUser(message, method) => self.dm_user(message, method).await, } } /// Send message direct to a single user async fn dm_user(&self, message: SendUserMessage, method: DmUserMethod) { let tx; // Determine lookup method match method { Id(id) => { if let Some(user) = self.state.users_by_id.read().unwrap().get(&id) { tx = user.read().unwrap().tx.clone(); } else { tracing::error!("Attempted to send message to invalid user id!"); // TODO: User can still be offline causing send to fail. This is suppressed // below return; } } Addr(addr) => { if let Some(user) = self.state.online_users.read().unwrap().get(&addr) { tx = user.read().unwrap().tx.clone(); } else { tracing::error!("Attempted to send message to offline user!"); return; } } } // Serialize and send message // TODO: Send failures are suppressed match message { SendUserUpdate(message) => { let msg = to_string::(&message).unwrap(); let _ = tx.send(msg).await; } SendChatMessage(message) => { let msg = to_string::(&message).unwrap(); let _ = tx.send(msg).await; } SendJudgeRound(message) => { let msg = to_string::(&message).unwrap(); let _ = tx.send(msg).await; } } } /// Create, register, and hydrate new user async fn set_up_new_user(&self, user: User, addr: SocketAddr) { let tx = user.tx.clone(); let new_user = Arc::new(RwLock::new(user)); // Notify client of new username let _ = tx.send(user_client_self_update(&new_user)).await; // Register uuid self.state .users_by_id .write() .unwrap() .insert(new_user.read().unwrap().uuid.clone(), new_user.clone()); // Register online using `addr` as key until something longer lived exists self.state .online_users .write() .unwrap() .insert(addr, new_user.clone()); // Hydrate client let _ = tx.send(meta_games_browser_update(&self.state)).await; let _ = tx.send(meta_new_game_card_packs(&self.state)).await; // Broadcast new user's existence // TODO: this should probably be combined and sent as one let _ = &self .state .broadcast_tx .send(meta_announce_user_join(&self.state, &addr)); let _ = &self .state .broadcast_tx .send(meta_server_summary_update(&self.state)); let _ = &self.state.broadcast_tx.send(meta_chat_update(&self.state)); // TODO: this races the broadcasts but if it's done last it'll probably show up last... let _ = tx.send(meta_motd()).await; } /// Handle user login async fn login(&self, request: UserLogInRequest, addr: SocketAddr) { let username_max_len = 66; // This is the longest name the generator may produce right now let broadcast_tx = self.state.broadcast_tx.clone(); let new_name; if request.username.len() > username_max_len { new_name = request.username[..username_max_len].to_string(); } else { new_name = request.username; } let old_name; if let Some(user) = self.state.online_users.read().unwrap().get(&addr) { old_name = user.read().unwrap().name.clone(); } else { tracing::error!("Nonexistent user attempting login"); return; } // Resume user's old session if they exist as offline if self .state .offline_users .read() .unwrap() .contains_key(&new_name) { let buf; // Copy over new tx if let Some(online_user) = self.state.online_users.write().unwrap().remove(&addr) { if let Some(offline_user) = self.state.offline_users.write().unwrap().remove(&new_name) { offline_user.write().unwrap().tx = online_user.write().unwrap().tx.clone(); buf = offline_user; } else { tracing::error!("Error copying tx to new user: Can't find offline user!"); return; } } else { tracing::error!("Error copying tx to new user: Can't find online user!"); return; } // Move offline user object to online self.state.online_users.write().unwrap().insert(addr, buf); // Send welcome back messages let msg = format! { "{0} changed name to {1}. Welcome back!", old_name, new_name }; tracing::debug!("{}", &msg); let _ = broadcast_tx.send(to_string(&ChatMessage { text: msg }).unwrap()); } // Check if name is taken by an online user else if self .state .reserved_names .read() .unwrap() .contains(&new_name) { self.dm_user( SendChatMessage(ChatMessage { text: "Name is taken".to_string(), }), Addr(addr), ) .await; tracing::debug!("{}", old_name.clone()); self.dm_user( SendUserUpdate(UserUpdate { username: old_name.clone(), }), Addr(addr), ) .await; } else { // Reserve name self.state .reserved_names .write() .unwrap() .insert(new_name.clone()); // Change user's name if let Some(user) = self.state.online_users.write().unwrap().get_mut(&addr) { user.write().unwrap().change_name(new_name.clone()); } else { tracing::error!("Error updating username: Can't find user!"); return; } // Send chat updates let msg = format! { "{0} changed name to {1}.", old_name, new_name }; let _ = broadcast_tx.send(to_string::(&ChatMessage { text: msg }).unwrap()); } // Send the user their new name self.dm_user( SendUserUpdate(UserUpdate { username: new_name.clone(), }), Addr(addr), ) .await; // Update games this user is in if let Some(user) = &self.state.online_users.read().unwrap().get(&addr) { let user_id = user.read().unwrap().uuid.to_string(); { if self .state .games_by_user .read() .unwrap() .contains_key(&user_id) { let msg = GameHandlerMessage::SendGameMetaUpdate( self.state .games_by_user .read() .unwrap() .get(&user_id) .unwrap() .to_vec(), ); let tx = self.state.games_tx.clone(); tokio::spawn(async move { tracing::debug!("msg: {:#?}", &msg); let _ = tx.send(msg).await; }); } } } // Send client updates let _ = broadcast_tx.send(meta_games_browser_update(&self.state)); let _ = broadcast_tx.send(meta_chat_update(&self.state)); } } /// Generate message to notify client of user changes pub fn user_client_self_update(new_user: &Arc>) -> String { to_string::(&UserUpdate { username: new_user.read().unwrap().name.clone(), }) .unwrap() } /// Generate chatroom metadata update pub fn meta_chat_update(state: &Arc) -> String { // TODO: this may get expensive if there are many users let mut names = vec![]; for user in state.online_users.read().unwrap().iter() { names.push(user.1.read().unwrap().name.clone()); } to_string::(&ChatUpdate { room: "Lobby".to_string(), users: names, }) .unwrap() } /// Generate chatroom join announcement pub fn meta_announce_user_join(state: &Arc, addr: &SocketAddr) -> String { let msg = format!("{} joined.", { if let Some(user) = state.online_users.read().unwrap().get(addr) { user.read().unwrap().name.clone() } else { return Default::default(); } }); tracing::debug!("{}", &msg); to_string::(&ChatMessage { text: msg }).unwrap() } /// Generage cards meta message pub fn meta_new_game_card_packs(state: &Arc) -> String { to_string::(&state.packs_meta).unwrap() } /// Generate message-of-the-day server greeting pub fn meta_motd() -> String { to_string::(&ChatMessage { text: "Greetings from the game server!".to_string(), }) .unwrap() } /// Generate server summary update - mostly debug stuff pub fn meta_server_summary_update(state: &Arc) -> String { let online_users = state.online_users.read().unwrap().len(); let active_games = state.games.read().unwrap().len(); to_string::(&ServerStateSummary { online_users, active_games, }) .unwrap() } /// Generate games list update pub fn meta_games_browser_update(state: &Arc) -> String { // TODO: this may get expensive if there are many games let mut games = vec![]; for game in state.games.read().unwrap().values() { games.push(GameBrowserMeta { uuid: game.read().unwrap().uuid.to_string(), name: game.read().unwrap().name.clone(), host: game.read().unwrap().host.read().unwrap().name.clone(), players: game.read().unwrap().players.len(), packs: game.read().unwrap().packs.clone(), }); } to_string::(&GamesUpdate { games }).unwrap() }