🏡 index : ~doyle/titanirc.git

author Jordan Doyle <jordan@doyle.la> 2023-01-07 20:46:54.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2023-01-07 20:46:54.0 +00:00:00
commit
250b43f34b0cea3daff6f718a95133b585fe1584 [patch]
tree
a8dfcacf1f8bbbffc5a916f3cfbacb0c8222f055
parent
6c91669a4c0f5c1de920b4a3ef711d309cf65a6c
download
250b43f34b0cea3daff6f718a95133b585fe1584.tar.gz

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(-)

diff --git a/src/channel.rs b/src/channel.rs
index f6f9249..9a12dc3 100644
--- a/src/channel.rs
+++ a/src/channel.rs
@@ -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 @@
}

/// Sends back a list of users currently connected to the client

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))
    }
}
diff --git a/src/client.rs b/src/client.rs
index 8ade8d7..bf015fb 100644
--- a/src/client.rs
+++ a/src/client.rs
@@ -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(),
            }));
        }

        // 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 @@
                    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
+++ a/src/messages.rs
@@ -33,6 +33,13 @@
    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<Addr<Channel>>")]
@@ -55,7 +62,7 @@
/// 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
+++ a/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,
};

@@ -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)
    }
}

diff --git a/src/channel/response.rs b/src/channel/response.rs
index 0389e20..4549aa1 100644
--- a/src/channel/response.rs
+++ a/src/channel/response.rs
@@ -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>,
}

diff --git a/src/server/response.rs b/src/server/response.rs
new file mode 100644
index 0000000..9a68ed8 100644
--- /dev/null
+++ a/src/server/response.rs
@@ -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>,
}