From d6cfbf5cf561453be7ca50a2d727c7a78e319eb4 Mon Sep 17 00:00:00 2001 From: Casper Rogild Storm Date: Tue, 4 Jul 2023 15:43:57 +0200 Subject: [PATCH] broadcast quit and nickname --- data/src/client.rs | 60 +++++++++++++++--- data/src/history/manager.rs | 49 ++++++++++++--- data/src/message.rs | 122 ++++++++++++++++++++++-------------- data/src/user.rs | 6 ++ src/main.rs | 43 +++++++++++-- src/screen/dashboard.rs | 42 ++++++++++++- 6 files changed, 252 insertions(+), 70 deletions(-) diff --git a/data/src/client.rs b/data/src/client.rs index 5b75c28f..14fd6443 100644 --- a/data/src/client.rs +++ b/data/src/client.rs @@ -25,6 +25,18 @@ pub enum State { Ready(Connection), } +#[derive(Debug)] +pub enum Brodcast { + Quit(User, Option), + Nickname(String, String, bool), +} + +#[derive(Debug)] +pub enum Event { + Single(Message), + Brodcast(Brodcast), +} + #[derive(Debug)] pub struct Connection { client: Client, @@ -60,15 +72,13 @@ impl Connection { } } - fn receive(&mut self, message: message::Encoded) -> Option { + fn receive(&mut self, message: message::Encoded) -> Option { log::trace!("Message received => {:?}", *message); - self.handle(&message); - - Message::received(message, self.nickname()) + self.handle(message) } - fn handle(&mut self, message: &message::Encoded) { + fn handle(&mut self, message: message::Encoded) -> Option { use irc::proto::{Command, Response}; match &message.command { @@ -77,20 +87,34 @@ impl Connection { irc::proto::Prefix::ServerName(_) => None, irc::proto::Prefix::Nickname(nick, _, _) => Some(nick), }) else { - return; + return None; }; - if self.resolved_nick.as_ref() == Some(old_nick) { - self.resolved_nick = Some(nick.clone()) + let changed_own_nickname = self.resolved_nick.as_ref() == Some(old_nick); + if changed_own_nickname { + self.resolved_nick = Some(nick.clone()); } + + return Some(Event::Brodcast(Brodcast::Nickname( + nick.clone(), + old_nick.clone(), + changed_own_nickname, + ))); } Command::Response(Response::RPL_WELCOME, args) => { if let Some(nick) = args.first() { self.resolved_nick = Some(nick.to_string()); } } + Command::QUIT(comment) => { + let user = message.user()?; + + return Some(Event::Brodcast(Brodcast::Quit(user, comment.clone()))); + } _ => {} } + + Some(Event::Single(Message::received(message, self.nickname())?)) } fn sync(&mut self) { @@ -183,7 +207,7 @@ impl Map { self.connection(server).map(Connection::nickname) } - pub fn receive(&mut self, server: &Server, message: message::Encoded) -> Option { + pub fn receive(&mut self, server: &Server, message: message::Encoded) -> Option { self.connection_mut(server) .and_then(|connection| connection.receive(message)) } @@ -208,6 +232,24 @@ impl Map { .unwrap_or_default() } + pub fn get_user_channels(&self, server: &Server, nick: NickRef) -> Vec { + self.connection(server) + .map(|connection| { + connection + .channels() + .iter() + .filter(|channel| { + connection + .users(channel) + .iter() + .any(|user| user.nickname() == nick) + }) + .cloned() + .collect::>() + }) + .unwrap_or_default() + } + pub fn iter(&self) -> std::collections::btree_map::Iter { self.0.iter() } diff --git a/data/src/history/manager.rs b/data/src/history/manager.rs index b74ee47f..212043a6 100644 --- a/data/src/history/manager.rs +++ b/data/src/history/manager.rs @@ -9,7 +9,7 @@ use crate::history::{self, History}; use crate::message::{self, Limit}; use crate::time::Posix; use crate::user::{Nick, NickRef}; -use crate::{server, Buffer, Input, Server}; +use crate::{server, Buffer, Input, Server, User}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Resource { @@ -260,7 +260,7 @@ impl Manager { } }) .cloned(); - let queries = map + let mut queries = map .keys() .filter_map(|kind| { if let history::Kind::Query(nick) = kind { @@ -272,11 +272,35 @@ impl Manager { .cloned(); let messages = match broadcast { - Broadcast::Disconnected => { - message::broadcast::disconnected(channels, queries).collect::>() + Broadcast::Disconnected => message::broadcast::disconnected(channels, queries), + Broadcast::Reconnected => message::broadcast::reconnected(channels, queries), + Broadcast::Quit { + user, + comment, + user_channels, + } => { + let user_query = queries.find(|nick| user.nickname() == *nick); + + message::broadcast::quit(user_channels, user_query, &user, &comment) } - Broadcast::Reconnected => { - message::broadcast::reconnected(channels, queries).collect::>() + Broadcast::Nickname { + new_nick, + old_nick, + changed_own_nickname, + user_channels, + } => { + let user_query = queries.find(|nick| { + let old_nick = NickRef::from(old_nick.as_str()); + old_nick == *nick + }); + + message::broadcast::nickname( + user_channels, + user_query, + &new_nick, + &old_nick, + changed_own_nickname, + ) } }; @@ -438,8 +462,19 @@ impl Data { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum Broadcast { Disconnected, Reconnected, + Quit { + user: User, + comment: Option, + user_channels: Vec, + }, + Nickname { + new_nick: String, + old_nick: String, + changed_own_nickname: bool, + user_channels: Vec, + }, } diff --git a/data/src/message.rs b/data/src/message.rs index 1cdaa43a..e8ad613b 100644 --- a/data/src/message.rs +++ b/data/src/message.rs @@ -12,6 +12,24 @@ pub type Channel = String; #[derive(Debug, Clone)] pub struct Encoded(proto::Message); +impl Encoded { + pub fn user(&self) -> Option { + fn not_empty(s: &str) -> Option<&str> { + (!s.is_empty()).then_some(s) + } + + let prefix = self.prefix.as_ref()?; + match prefix { + proto::Prefix::Nickname(nickname, username, hostname) => Some(User::new( + Nick::from(nickname.as_str()), + not_empty(username), + not_empty(hostname), + )), + _ => None, + } + } +} + impl std::ops::Deref for Encoded { type Target = proto::Message; @@ -122,24 +140,8 @@ impl Message { } } -fn user(message: &Encoded) -> Option { - fn not_empty(s: &str) -> Option<&str> { - (!s.is_empty()).then_some(s) - } - - let prefix = message.prefix.as_ref()?; - match prefix { - proto::Prefix::Nickname(nickname, username, hostname) => Some(User::new( - Nick::from(nickname.as_str()), - not_empty(username), - not_empty(hostname), - )), - _ => None, - } -} - fn source(message: Encoded, our_nick: NickRef) -> Option { - let user = user(&message); + let user = message.user(); match message.0.command { // Channel @@ -252,7 +254,7 @@ fn server_time(message: &Encoded) -> DateTime { } fn text(message: &Encoded, our_nick: NickRef) -> Option { - let user = user(message); + let user = message.user(); match &message.command { proto::Command::TOPIC(_, topic) => { let user = user?; @@ -267,7 +269,7 @@ fn text(message: &Encoded, our_nick: NickRef) -> Option { .map(|text| format!(" ({text})")) .unwrap_or_default(); - Some(format!("⟵ {user}{text} has left the channel")) + Some(format!("⟵ {user} has left the channel{text}")) } proto::Command::JOIN(_, _, _) | proto::Command::SAJOIN(_, _) => { let user = user?; @@ -365,54 +367,80 @@ pub(crate) mod broadcast { use super::{Direction, Message, Sender, Source}; use crate::time::Posix; use crate::user::Nick; + use crate::User; fn expand( channels: impl IntoIterator, queries: impl IntoIterator, - f: fn(source: Source) -> Message, - ) -> impl Iterator { + include_server: bool, + text: String, + ) -> Vec { + let message = |source, text| -> Message { + Message { + received_at: Posix::now(), + server_time: Utc::now(), + direction: Direction::Received, + source, + text, + } + }; + channels .into_iter() - .map(move |channel| f(Source::Channel(channel, Sender::Server))) + .map(|channel| message(Source::Channel(channel, Sender::Server), text.clone())) .chain( queries .into_iter() - .map(move |nick| f(Source::Query(nick, Sender::Server))), + .map(|nick| message(Source::Query(nick, Sender::Server), text.clone())), ) - .chain(Some(f(Source::Server))) + .chain(include_server.then(|| message(Source::Server, text.clone()))) + .collect() } pub fn disconnected( channels: impl IntoIterator, queries: impl IntoIterator, - ) -> impl Iterator { - fn message(source: Source) -> Message { - Message { - received_at: Posix::now(), - server_time: Utc::now(), - direction: Direction::Received, - source, - text: " ∙ connection to server lost".into(), - } - } - - expand(channels, queries, message) + ) -> Vec { + let text = " ∙ connection to server lost".into(); + expand(channels, queries, true, text) } pub fn reconnected( channels: impl IntoIterator, queries: impl IntoIterator, - ) -> impl Iterator { - fn message(source: Source) -> Message { - Message { - received_at: Posix::now(), - server_time: Utc::now(), - direction: Direction::Received, - source, - text: " ∙ connection to server restored".into(), - } - } + ) -> Vec { + let text = " ∙ connection to server restored".into(); + expand(channels, queries, true, text) + } + + pub fn quit( + channels: impl IntoIterator, + queries: impl IntoIterator, + user: &User, + comment: &Option, + ) -> Vec { + let comment = comment + .as_ref() + .map(|comment| format!(" ({comment})")) + .unwrap_or_default(); + let text = format!("⟵ {user} has quit{comment}"); + + expand(channels, queries, false, text) + } + + pub fn nickname( + channels: impl IntoIterator, + queries: impl IntoIterator, + new_nick: &str, + old_nick: &str, + changed_own_nickname: bool, + ) -> Vec { + let text = if changed_own_nickname { + format!(" ∙ You're now known as {new_nick}") + } else { + format!(" ∙ {old_nick} is now known as {new_nick}") + }; - expand(channels, queries, message) + expand(channels, queries, false, text) } } diff --git a/data/src/user.rs b/data/src/user.rs index ae290f17..cd7f1ba2 100644 --- a/data/src/user.rs +++ b/data/src/user.rs @@ -184,6 +184,12 @@ impl<'a> Ord for NickRef<'a> { } } +impl<'a> PartialEq for NickRef<'a> { + fn eq(&self, other: &Nick) -> bool { + self.0.eq(other.0.as_str()) + } +} + #[derive(Debug, Clone)] pub struct AccessLevel(data::AccessLevel); diff --git a/src/main.rs b/src/main.rs index d2b2e6f4..fdd2ca8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -290,7 +290,7 @@ impl Application for Halloy { return Command::none() }; - dashboard.disconnected(&server); + dashboard.broadcast_disconnected(&server); } Command::none() @@ -307,7 +307,7 @@ impl Application for Halloy { return Command::none() }; - dashboard.reconnected(&server); + dashboard.broadcast_reconnected(&server); } Command::none() @@ -318,8 +318,43 @@ impl Application for Halloy { }; messages.into_iter().for_each(|encoded| { - if let Some(message) = self.clients.receive(&server, encoded) { - dashboard.record_message(&server, message); + if let Some(event) = self.clients.receive(&server, encoded) { + match event { + data::client::Event::Single(message) => { + dashboard.record_message(&server, message); + } + data::client::Event::Brodcast(brodcast) => match brodcast { + data::client::Brodcast::Quit(user, comment) => { + let user_channels = self + .clients + .get_user_channels(&server, user.nickname()); + + dashboard.broadcast_quit( + &server, + user, + comment, + user_channels, + ); + } + data::client::Brodcast::Nickname( + new_nick, + old_nick, + changed_own_nickname, + ) => { + let user_channels = self + .clients + .get_user_channels(&server, old_nick.as_str().into()); + + dashboard.broadcast_nickname( + &server, + new_nick, + old_nick, + changed_own_nickname, + user_channels, + ); + } + }, + } } }); diff --git a/src/screen/dashboard.rs b/src/screen/dashboard.rs index 6e0d16ba..cb0d3a32 100644 --- a/src/screen/dashboard.rs +++ b/src/screen/dashboard.rs @@ -4,7 +4,7 @@ pub mod side_menu; use std::time::{Duration, Instant}; use data::history::manager::Broadcast; -use data::{dashboard, history, Config, Server}; +use data::{dashboard, history, Config, Server, User}; use iced::widget::pane_grid::{self, PaneGrid}; use iced::widget::{container, row}; use iced::{clipboard, window, Command, Length, Subscription}; @@ -496,11 +496,47 @@ impl Dashboard { self.history.record_message(server, message); } - pub fn disconnected(&mut self, server: &Server) { + pub fn broadcast_quit( + &mut self, + server: &Server, + user: User, + comment: Option, + user_channels: Vec, + ) { + self.history.broadcast( + server, + Broadcast::Quit { + user, + comment, + user_channels, + }, + ); + } + + pub fn broadcast_nickname( + &mut self, + server: &Server, + new_nick: String, + old_nick: String, + changed_own_nickname: bool, + user_channels: Vec, + ) { + self.history.broadcast( + server, + Broadcast::Nickname { + new_nick, + old_nick, + changed_own_nickname, + user_channels, + }, + ); + } + + pub fn broadcast_disconnected(&mut self, server: &Server) { self.history.broadcast(server, Broadcast::Disconnected); } - pub fn reconnected(&mut self, server: &Server) { + pub fn broadcast_reconnected(&mut self, server: &Server) { self.history.broadcast(server, Broadcast::Reconnected); }