mod commands; pub mod events; use crate::server::Server; use std::{collections::HashMap, hash::Hash, sync::Arc}; use actix::{ io::{FramedWrite, WriteHandler}, prelude::*, }; use derive_more::Deref; use std::time::{Duration, Instant}; use titanirc_types::{ protocol::commands::{JoinCommand, PrivmsgCommand}, protocol::primitives::{Channel, FreeText, Nick, Receiver}, protocol::replies::Source, protocol::ServerMessage, RegisteredNick, }; use tokio::{io::WriteHalf, net::TcpStream}; use uuid::Uuid; use super::channel::ChannelName; #[derive(Debug, Deref, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] #[allow(clippy::module_name_repetitions)] pub struct UserUuid(Uuid); pub struct User { pub session_id: UserUuid, pub server: Addr, pub writer: FramedWrite< WriteHalf, titanirc_codec::Encoder, >>::Error, >, pub last_active: Instant, pub nick: RegisteredNick, pub password_auth_in_progress: Option, pub channels: HashMap, } // TODO: broadcast a leave to all the user's channels on actor shutdown impl User { pub fn new( server: Addr, writer: FramedWrite, titanirc_codec::Encoder>, nick: RegisteredNick, ) -> Self { Self { session_id: UserUuid(Uuid::new_v4()), server, writer, password_auth_in_progress: None, last_active: Instant::now(), nick, channels: HashMap::new(), } } } /// Sends a ping to the user every 30 seconds, if we haven't received a protocol /// message from the user in 240 seconds then disconnect. fn schedule_ping(ctx: &mut ::Context) { ctx.run_later(Duration::from_secs(30), |act, ctx| { if Instant::now().duration_since(act.last_active) > Duration::from_secs(240) { // send `QUIT :Ping timeout: 120 seconds` & `ERROR :Closing Link: {ip} (Ping timeout: 120 seconds)` ctx.stop(); } act.writer .write(titanirc_types::protocol::ServerMessage::Ping); schedule_ping(ctx); }); } impl Actor for User { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { schedule_ping(ctx); } } /// Handles errors from our socket Writer. impl WriteHandler for User {} impl actix::Handler for User { type Result = (); fn handle( &mut self, msg: crate::server::events::UserAuthResponse, ctx: &mut Self::Context, ) -> Self::Result { todo!() } } impl actix::Handler for User { type Result = (); fn handle( &mut self, msg: crate::entities::channel::Handle, _ctx: &mut Self::Context, ) -> Self::Result { self.channels.insert(msg.channel_name.clone(), msg); } } /// Handles `Join`s sent by a channel the user is in, and forwards a /// `JOIN` command to the user. impl actix::Handler> for User { type Result = (); fn handle( &mut self, msg: Arc, _ctx: &mut Self::Context, ) -> Self::Result { self.writer.write(ServerMessage::Command( Source::User(Nick((*msg.nick.load().unwrap()).clone().into())), JoinCommand { _phantom: std::marker::PhantomData, channel: Channel((&msg.channel_name[..]).into()), } .into(), )); } } /// Handles messages that have been forwarded from a channel to this user. impl actix::Handler> for User { type Result = (); fn handle( &mut self, msg: Arc, _ctx: &mut Self::Context, ) -> Self::Result { self.writer.write(ServerMessage::Command( Source::User(Nick((*msg.0.from.load().unwrap()).clone().into())), PrivmsgCommand { _phantom: std::marker::PhantomData, free_text: FreeText(msg.0.message.as_bytes().into()), receiver: Receiver::Channel(msg.0.to.clone()), } .into(), )); } } /// Handles messages that have been sent directly to this user. impl actix::Handler> for User { type Result = (); fn handle( &mut self, msg: Arc, _ctx: &mut Self::Context, ) -> Self::Result { self.writer.write(ServerMessage::Command( Source::User(Nick((*msg.0.from.load().unwrap()).clone().into())), PrivmsgCommand { _phantom: std::marker::PhantomData, free_text: FreeText(msg.0.message.as_bytes().into()), receiver: Receiver::User(msg.0.to.clone()), } .into(), )); } }