369 lines
12 KiB
Rust
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()
|
|
}
|