use futures::{SinkExt, StreamExt, stream::SplitSink};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::mpsc::UnboundedSender;
use warp::Filter;
use warp::filters::ws;
use warp::filters::ws::{Message, WebSocket};
use crate::app::app_event::AppEventServer;
use crate::app::event::{BasicEvent, BasicEventSenderExt};
use crate::app::models::Maid;
use crate::cli::ServerArgs;
use crate::server::types::{History, Room, RoomId, RoomUser, Rooms, UserId, UserMessage};
#[derive(Debug)]
struct Forbidden;
impl warp::reject::Reject for Forbidden {}
pub async fn main(maid: Maid, args: ServerArgs) -> color_eyre::Result<()> {
let rooms: Rooms = Rooms::default();
let maid = warp::any().map(move || maid.clone());
let rooms = warp::any().map(move || rooms.clone());
let room_route = warp::path("room".to_string())
.and(warp::ws())
.and(warp::query::<HashMap<String, String>>())
.and(maid)
.and(rooms)
.and_then(
|ws: ws::Ws, query: HashMap<String, String>, maid: Maid, rooms: Rooms| async move {
if let Some(room_id) = query.get("room") {
let room_id: String = room_id.clone();
let reply = ws.on_upgrade(move |socket| {
connect(socket, maid.clone(), rooms.clone(), room_id.clone())
});
Ok(reply)
} else {
Err(warp::reject::custom(Forbidden))
}
},
);
warp::serve(room_route).run(args.address).await;
log::info!("Server started at ws://{}/room", args.address);
Ok(())
}
#[allow(unused_assignments)]
async fn connect(ws: WebSocket, maid: Maid, rooms: Rooms, room_id: RoomId) {
let mut user: Option<Arc<RoomUser>> = None;
let (mut user_tx, mut user_rx) = ws.split();
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<Message>(); {
user = join_room(maid.event_tx.clone(), rooms.clone(), &room_id, tx).await;
if let Some(user) = user.clone() {
{
let room_lock = rooms.lock().await;
let room = room_lock.get(&room_id);
if let Some(room) = room {
send_history(room.history.clone(), &mut user_tx).await;
}
}
let token = maid.token.child_token();
tokio::spawn(async move {
let token = token.clone();
loop {
tokio::select! {
_ = token.cancelled() => {},
msg = rx.recv() => { if let Some(msg) = msg {
if user_tx.send(msg.clone()).await.is_err() { break; }
} else {
break;
}
}
}
}
});
maid.event_tx
.send_event(AppEventServer::AddRoomUser((*user).clone()))
.await; }
}
if let Some(user) = user {
while let Some(result) = user_rx.next().await {
if let Ok(result) = result {
broadcast_msg(maid.event_tx.clone(), rooms.clone(), user.clone(), result).await; }
}
disconnect(maid.event_tx.clone(), rooms.clone(), user.clone()).await;
}
}
async fn get_room(rooms: Rooms, room_id: &RoomId) -> Option<Arc<Room>> {
rooms.lock().await.get(room_id).cloned()
}
async fn join_room(
sender: UnboundedSender<BasicEvent>,
rooms: Rooms,
room_id: &RoomId,
tx: UnboundedSender<Message>,
) -> Option<Arc<RoomUser>> {
let mut result: Option<Arc<RoomUser>> = None;
let mut create_flag = false;
let mut room_lock = rooms.lock().await;
let room = room_lock.entry(room_id.clone()).or_insert_with(|| {
create_flag = true;
Arc::new(Room::new(room_id))
});
let mut users_lock = room.users.lock().await;
if users_lock.len() < room.capacity {
let user = Arc::new(RoomUser::new(
petname::petname(2, "-")?,
room_id.clone(),
tx,
));
users_lock.insert(user.id, user.clone());
result = Some(user);
}
if create_flag {
sender
.send_event(AppEventServer::AddRoom(room_id.clone()))
.await; }
result
}
async fn send_history(history: History, user_tx: &mut SplitSink<WebSocket, Message>) {
let history_guard = history.lock().await;
for user_msg in history_guard.iter() {
if user_tx
.send(Message::text(user_msg.msg.clone()))
.await
.is_err()
{
return;
}
}
}
async fn append_to_history(
sender: UnboundedSender<BasicEvent>,
room_id: &RoomId,
user_id: &UserId,
msg: Message,
history: History,
) {
if msg.is_text()
&& let Ok(msg_text) = msg.to_str()
{
let msg_text = msg_text.to_string();
let user_msg = UserMessage::new(room_id.clone(), *user_id, msg_text);
{
let mut history_guard = history.lock().await;
history_guard.push(user_msg.clone());
}
sender
.send_event(AppEventServer::AddMessage(user_msg))
.await; }
}
async fn broadcast_msg(
sender: UnboundedSender<BasicEvent>,
rooms: Rooms,
user: Arc<RoomUser>,
msg: Message,
) {
if msg.is_text() {
let room = get_room(rooms, &user.room_id).await;
if let Some(room) = room {
for (uid, ru) in room.users.lock().await.iter() {
if user.id != *uid {
let tx = &ru.tx;
tx.send(msg.clone()).ok(); }
}
append_to_history(
sender.clone(),
&room.id,
&user.id,
msg.clone(),
room.history.clone(),
)
.await;
}
}
}
async fn disconnect(sender: UnboundedSender<BasicEvent>, rooms: Rooms, user: Arc<RoomUser>) {
let room = get_room(rooms.clone(), &user.room_id).await;
if let Some(room) = room {
room.users.lock().await.remove(&user.id);
sender
.send_event(AppEventServer::RemoveRoomUser((*user).clone()))
.await;
if room.users.lock().await.is_empty() {
rooms.lock().await.remove(&room.id);
sender
.send_event(AppEventServer::RemoveRoom(room.id.clone()))
.await; }
}
}