cards/server/src/user_handler.rs

369 lines
12 KiB
Rust

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<AppState>,
}
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<AppState>) -> 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::<UserUpdate>(&message).unwrap();
let _ = tx.send(msg).await;
}
SendChatMessage(message) => {
let msg = to_string::<ChatMessage>(&message).unwrap();
let _ = tx.send(msg).await;
}
SendJudgeRound(message) => {
let msg = to_string::<JudgeRound>(&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>(&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<RwLock<User>>) -> String {
to_string::<UserUpdate>(&UserUpdate {
username: new_user.read().unwrap().name.clone(),
})
.unwrap()
}
/// Generate chatroom metadata update
pub fn meta_chat_update(state: &Arc<AppState>) -> 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>(&ChatUpdate {
room: "Lobby".to_string(),
users: names,
})
.unwrap()
}
/// Generate chatroom join announcement
pub fn meta_announce_user_join(state: &Arc<AppState>, 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>(&ChatMessage { text: msg }).unwrap()
}
/// Generage cards meta message
pub fn meta_new_game_card_packs(state: &Arc<AppState>) -> String {
to_string::<CardPacksMeta>(&state.packs_meta).unwrap()
}
/// Generate message-of-the-day server greeting
pub fn meta_motd() -> String {
to_string::<ChatMessage>(&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<AppState>) -> String {
let online_users = state.online_users.read().unwrap().len();
let active_games = state.games.read().unwrap().len();
to_string::<ServerStateSummary>(&ServerStateSummary {
online_users,
active_games,
})
.unwrap()
}
/// Generate games list update
pub fn meta_games_browser_update(state: &Arc<AppState>) -> 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>(&GamesUpdate { games }).unwrap()
}