From 250b43f34b0cea3daff6f718a95133b585fe1584 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 7 Jan 2023 20:46:54 +0000 Subject: [PATCH] Implement LIST command --- src/channel.rs | 8 ++++---- src/channel/response.rs | 6 +++--- src/client.rs | 24 +++++++++++++++++++----- src/messages.rs | 9 ++++++++- src/server.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- src/server/response.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 src/server/response.rs diff --git a/src/channel.rs b/src/channel.rs index f6f9249..9a12dc3 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -12,7 +12,7 @@ use crate::{ client::Client, connection::InitiatedConnection, messages::{ - Broadcast, ChannelFetchTopic, ChannelJoin, ChannelList, ChannelMessage, ChannelPart, + Broadcast, ChannelFetchTopic, ChannelJoin, ChannelMemberList, ChannelMessage, ChannelPart, ChannelUpdateTopic, ServerDisconnect, UserNickChange, }, }; @@ -42,11 +42,11 @@ impl Handler for Channel { } /// Sends back a list of users currently connected to the client -impl Handler for Channel { - type Result = MessageResult; +impl Handler for Channel { + type Result = MessageResult; #[instrument(parent = &msg.span, skip_all)] - fn handle(&mut self, msg: ChannelList, _ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: ChannelMemberList, _ctx: &mut Self::Context) -> Self::Result { MessageResult(ChannelNamesList::new(self)) } } diff --git a/src/channel/response.rs b/src/channel/response.rs index 0389e20..4549aa1 100644 --- a/src/channel/response.rs +++ b/src/channel/response.rs @@ -6,8 +6,8 @@ use crate::{ }; pub struct ChannelTopic { - channel_name: String, - topic: Option, + pub channel_name: String, + pub topic: Option, } impl ChannelTopic { @@ -65,7 +65,7 @@ impl ChannelTopic { } pub struct ChannelNamesList { - channel_name: String, + pub channel_name: String, pub nick_list: Vec, } diff --git a/src/client.rs b/src/client.rs index 8ade8d7..bf015fb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,8 +13,8 @@ use crate::{ channel::Channel, connection::{InitiatedConnection, MessageSink}, messages::{ - Broadcast, ChannelFetchTopic, ChannelJoin, ChannelList, ChannelMessage, ChannelPart, - ChannelUpdateTopic, FetchClientDetails, ServerDisconnect, UserNickChange, + Broadcast, ChannelFetchTopic, ChannelJoin, ChannelList, ChannelMemberList, ChannelMessage, + ChannelPart, ChannelUpdateTopic, FetchClientDetails, ServerDisconnect, UserNickChange, }, server::Server, SERVER_NAME, @@ -195,12 +195,12 @@ impl Handler for Client { continue; } - futures.push(handle.send(ChannelList { + futures.push(handle.send(ChannelMemberList { span: Span::current(), })); } - // await on all the `ChannelList` events to the channels, and once we get the lists back + // await on all the `ChannelMemberList` events to the channels, and once we get the lists back // write them to the client let fut = wrap_future::<_, Self>( futures::future::join_all(futures.into_iter()).instrument(Span::current()), @@ -368,7 +368,21 @@ impl StreamHandler> for Client { span: Span::current(), }); } - Command::LIST(_, _) => {} + Command::LIST(_, _) => { + let span = Span::current(); + let fut = self.server.send(ChannelList { span }).into_actor(self).map( + |result, this, _ctx| { + for message in result + .unwrap() + .into_messages(this.connection.nick.to_string()) + { + this.writer.write(message); + } + }, + ); + + ctx.spawn(fut); + } Command::INVITE(_, _) => {} Command::KICK(_, _, _) => {} Command::PRIVMSG(target, message) => { diff --git a/src/messages.rs b/src/messages.rs index ae0a330..83998a9 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -33,6 +33,13 @@ pub struct UserNickChange { pub span: Span, } +/// Fetches all the channels visible to the user. +#[derive(Message, Clone)] +#[rtype(result = "super::server::response::ChannelList")] +pub struct ChannelList { + pub span: Span, +} + /// Sent when the user attempts to join a channel. #[derive(Message)] #[rtype(result = "Result>")] @@ -55,7 +62,7 @@ pub struct ChannelPart { /// Retrieves the list of users currently in a channel. #[derive(Message)] #[rtype(result = "super::channel::response::ChannelNamesList")] -pub struct ChannelList { +pub struct ChannelMemberList { pub span: Span, } diff --git a/src/server.rs b/src/server.rs index 1970a32..834d01a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,14 +1,20 @@ +pub mod response; + use std::collections::{HashMap, HashSet}; use actix::{Actor, Addr, Context, Handler, ResponseFuture}; -use futures::TryFutureExt; +use futures::{stream::FuturesOrdered, TryFutureExt}; use irc_proto::{Command, Message, Prefix, Response}; +use tokio_stream::StreamExt; use tracing::{instrument, Span}; use crate::{ channel::Channel, client::Client, - messages::{Broadcast, ChannelJoin, ServerDisconnect, UserConnected, UserNickChange}, + messages::{ + Broadcast, ChannelFetchTopic, ChannelJoin, ChannelList, ChannelMemberList, + ServerDisconnect, UserConnected, UserNickChange, + }, SERVER_NAME, }; @@ -125,6 +131,44 @@ impl Handler for Server { } } +impl Handler for Server { + type Result = ResponseFuture<::Result>; + + #[instrument(parent = &msg.span, skip_all)] + fn handle(&mut self, msg: ChannelList, _ctx: &mut Self::Context) -> Self::Result { + let fut = self + .channels + .values() + .map(|channel| { + let fetch_topic = channel.send(ChannelFetchTopic { + span: Span::current(), + }); + + let fetch_members = channel.send(ChannelMemberList { + span: Span::current(), + }); + + futures::future::try_join(fetch_topic, fetch_members) + }) + .collect::>() + .map(|res| { + let (topic, members) = res.unwrap(); + + response::ChannelListItem { + channel_name: topic.channel_name, + client_count: members.nick_list.len(), + topic: topic.topic.map(|v| v.topic), + } + }) + .fold(response::ChannelList::default(), |mut acc, v| { + acc.members.push(v); + acc + }); + + Box::pin(fut) + } +} + impl Actor for Server { type Context = Context; } diff --git a/src/server/response.rs b/src/server/response.rs new file mode 100644 index 0000000..9a68ed8 --- /dev/null +++ b/src/server/response.rs @@ -0,0 +1,60 @@ +use crate::SERVER_NAME; +use irc_proto::{Command, Message, Prefix, Response}; + +#[derive(Default)] +pub struct ChannelList { + pub members: Vec, +} + +impl ChannelList { + #[must_use] + pub fn into_messages(self, for_user: String) -> Vec { + let mut messages = Vec::with_capacity(self.members.len() + 2); + + messages.push(Message { + tags: None, + prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())), + command: Command::Response( + Response::RPL_LISTSTART, + vec![ + for_user.to_string(), + "Channel".to_string(), + "Users Name".to_string(), + ], + ), + }); + + for item in self.members { + messages.push(Message { + tags: None, + prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())), + command: Command::Response( + Response::RPL_LIST, + vec![ + for_user.to_string(), + item.channel_name, + item.client_count.to_string(), + item.topic.unwrap_or_default(), + ], + ), + }); + } + + messages.push(Message { + tags: None, + prefix: Some(Prefix::ServerName(SERVER_NAME.to_string())), + command: Command::Response( + Response::RPL_LISTEND, + vec![for_user, "End of /LIST".to_string()], + ), + }); + + messages + } +} + +pub struct ChannelListItem { + pub channel_name: String, + pub client_count: usize, + pub topic: Option, +} -- libgit2 1.7.2