Implement LIST command
Diff
src/channel.rs | 8 ++++----
src/client.rs | 24 +++++++++++++++++++++---
src/messages.rs | 9 ++++++++-
src/server.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
src/channel/response.rs | 6 +++---
src/server/response.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 140 insertions(+), 15 deletions(-)
@@ -12,7 +12,7 @@
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<ChannelList> for Channel {
type Result = MessageResult<ChannelList>;
impl Handler<ChannelMemberList> for Channel {
type Result = MessageResult<ChannelMemberList>;
#[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))
}
}
@@ -13,8 +13,8 @@
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 @@
continue;
}
futures.push(handle.send(ChannelList {
futures.push(handle.send(ChannelMemberList {
span: Span::current(),
}));
}
let fut = wrap_future::<_, Self>(
futures::future::join_all(futures.into_iter()).instrument(Span::current()),
@@ -368,7 +368,21 @@
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) => {
@@ -33,6 +33,13 @@
pub span: Span,
}
#[derive(Message, Clone)]
#[rtype(result = "super::server::response::ChannelList")]
pub struct ChannelList {
pub span: Span,
}
#[derive(Message)]
#[rtype(result = "Result<Addr<Channel>>")]
@@ -55,7 +62,7 @@
#[derive(Message)]
#[rtype(result = "super::channel::response::ChannelNamesList")]
pub struct ChannelList {
pub struct ChannelMemberList {
pub span: Span,
}
@@ -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,
};
@@ -122,6 +128,44 @@
for client in &self.clients {
client.do_send(msg.clone());
}
}
}
impl Handler<ChannelList> for Server {
type Result = ResponseFuture<<ChannelList as actix::Message>::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::<FuturesOrdered<_>>()
.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)
}
}
@@ -6,8 +6,8 @@
};
pub struct ChannelTopic {
channel_name: String,
topic: Option<CurrentChannelTopic>,
pub channel_name: String,
pub topic: Option<CurrentChannelTopic>,
}
impl ChannelTopic {
@@ -65,7 +65,7 @@
}
pub struct ChannelNamesList {
channel_name: String,
pub channel_name: String,
pub nick_list: Vec<String>,
}
@@ -1,0 +1,60 @@
use crate::SERVER_NAME;
use irc_proto::{Command, Message, Prefix, Response};
#[derive(Default)]
pub struct ChannelList {
pub members: Vec<ChannelListItem>,
}
impl ChannelList {
#[must_use]
pub fn into_messages(self, for_user: String) -> Vec<Message> {
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<String>,
}