use actix::{Actor, Context, Handler, Message, Recipient}; use askama::Template; use redis::Connection; use serde_json::json; use std::collections::HashMap; use uuid::Uuid; use crate::{ db::models::{Board, Post}, web::tcx::TemplateCtx, PostTemplate, }; #[derive(Message)] #[rtype(result = "()")] pub enum SessionMessage { Data(String), Stop, } #[derive(Message)] #[rtype(result = "()")] pub struct ConnectMessage { pub uuid: Uuid, pub thread: (String, i64), pub tcx: TemplateCtx, pub recv: Recipient, } #[derive(Message)] #[rtype(result = "()")] pub struct DisconnectMessage { pub uuid: Uuid, pub thread: (String, i64), } #[derive(Message)] #[rtype(result = "()")] pub struct PostCreatedMessage { pub post: Post, } #[derive(Message)] #[rtype(result = "()")] pub struct TargetedPostCreatedMessage { pub uuid: Uuid, pub post: Post, } #[derive(Message)] #[rtype(result = "()")] pub struct PostUpdatedMessage { pub post: Post, } #[derive(Message)] #[rtype(result = "()")] pub struct PostRemovedMessage { pub post: Post, } pub struct LiveHub { pub cache: Connection, pub recv_by_uuid: HashMap)>, pub recv_by_thread: HashMap<(String, i64), Vec>, } impl LiveHub { pub fn new(cache: Connection) -> Self { Self { cache, recv_by_uuid: HashMap::new(), recv_by_thread: HashMap::new(), } } } impl Actor for LiveHub { type Context = Context; } impl Handler for LiveHub { type Result = (); fn handle(&mut self, msg: ConnectMessage, _: &mut Self::Context) -> Self::Result { self.recv_by_uuid.insert(msg.uuid, (msg.tcx, msg.recv)); match self.recv_by_thread.get_mut(&msg.thread) { Some(vec) => { vec.push(msg.uuid); } None => { self.recv_by_thread.insert(msg.thread, vec![msg.uuid]); } } } } impl Handler for LiveHub { type Result = (); fn handle(&mut self, msg: DisconnectMessage, _: &mut Self::Context) -> Self::Result { self.recv_by_uuid.remove(&msg.uuid); let recv_by_thread = match self.recv_by_thread.get_mut(&msg.thread) { Some(recv_by_thread) => recv_by_thread, None => return, }; *recv_by_thread = recv_by_thread .iter() .filter(|uuid| **uuid != msg.uuid) .map(Uuid::clone) .collect(); if recv_by_thread.is_empty() { self.recv_by_thread.remove(&msg.thread); } } } impl Handler for LiveHub { type Result = (); fn handle(&mut self, msg: PostCreatedMessage, _: &mut Self::Context) -> Self::Result { let post = msg.post; let uuids = self .recv_by_thread .get(&(post.board.clone(), post.thread.unwrap_or(post.id))); let uuids = match uuids { Some(uuids) => uuids, None => return, }; let Ok(Some(board)) = Board::read_sync(&mut self.cache, post.board.clone()) else { return; }; for uuid in uuids { let Some((tcx, recv)) = self.recv_by_uuid.get_mut(uuid) else { continue; }; tcx.update_yous(&mut self.cache).ok(); let tcx = &tcx; let board = &board; let post = &post; let id = post.id; let html = PostTemplate { tcx, board, post } .render() .unwrap_or_default(); recv.do_send(SessionMessage::Data( json!({ "type": "created", "id": id, "html": html }).to_string(), )); } } } impl Handler for LiveHub { type Result = (); fn handle(&mut self, msg: TargetedPostCreatedMessage, _: &mut Self::Context) -> Self::Result { let post = msg.post; let Ok(Some(board)) = Board::read_sync(&mut self.cache, post.board.clone()) else { return; }; let Some((tcx, recv)) = self.recv_by_uuid.get(&msg.uuid) else { return; }; let id = post.id; let tcx = &tcx; let board = &board; let post = &post; let html = PostTemplate { tcx, board, post } .render() .unwrap_or_default(); recv.do_send(SessionMessage::Data( json!({ "type": "created", "id": id, "html": html }).to_string(), )); } } impl Handler for LiveHub { type Result = (); fn handle(&mut self, msg: PostUpdatedMessage, _: &mut Self::Context) -> Self::Result { let post = msg.post; let uuids = self .recv_by_thread .get(&(post.board.clone(), post.thread.unwrap_or(post.id))); let uuids = match uuids { Some(uuids) => uuids, None => return, }; let Ok(Some(board)) = Board::read_sync(&mut self.cache, post.board.clone()) else { return; }; for uuid in uuids { let Some((tcx, recv)) = self.recv_by_uuid.get_mut(uuid) else { continue; }; tcx.update_yous(&mut self.cache).ok(); let id = post.id; let tcx = &tcx; let board = &board; let post = &post; let html = PostTemplate { tcx, post, board } .render() .unwrap_or_default(); recv.do_send(SessionMessage::Data( json!({ "type": "updated", "id": id, "html": html }).to_string(), )); } } } impl Handler for LiveHub { type Result = (); fn handle(&mut self, msg: PostRemovedMessage, _: &mut Self::Context) -> Self::Result { let post = msg.post; let uuids = self .recv_by_thread .get(&(post.board.clone(), post.thread.unwrap_or(post.id))); let uuids = match uuids { Some(uuids) => uuids, None => return, }; if post.thread.is_none() { for uuid in uuids { let Some((_, recv)) = self.recv_by_uuid.get(uuid) else { continue; }; recv.do_send(SessionMessage::Stop); } return; } for uuid in uuids { let Some((_, recv)) = self.recv_by_uuid.get(uuid) else { continue; }; recv.do_send(SessionMessage::Data( json!({ "type": "removed", "id": post.id }).to_string(), )); } } }