Skip to content

Commit

Permalink
Improve channel support
Browse files Browse the repository at this point in the history
Topics now have time stamps and user masks.
Topic replies no longer sent when there is no topic set.
Echo JOIN/PART commands to user so clients e.g. weechat know they're
joined.
Its pretty neeeeat :D

Signed-off-by: Joanna Doyle <jjadoyle@gmail.com>
  • Loading branch information
aoeixsz4 committed Oct 17, 2020
1 parent 36b5f4c commit 6f2ea15
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 22 deletions.
17 changes: 11 additions & 6 deletions src/irc.rs
Expand Up @@ -21,7 +21,7 @@ pub mod rfc_defs;
use crate::{USER_MODES, CHAN_MODES};
use crate::client;
use crate::client::{Client, ClientType, ClientReply, ClientReplies, GenError, Host};
use crate::irc::chan::{ChanFlags, Channel};
use crate::irc::chan::{ChanFlags, Channel, ChanTopic};
use crate::irc::error::Error as ircError;
use crate::irc::reply::Reply as ircReply;
use crate::irc::rfc_defs as rfc;
Expand Down Expand Up @@ -413,11 +413,11 @@ impl Core {
}; ret
}

pub fn get_list_reply(&self) -> Vec<(String, String)> {
pub fn get_list_reply(&self) -> Vec<(Arc<Channel>, Option<ChanTopic>)> {
let vector = self.list_chans_ptr();
let mut out_vect = Vec::new();
for item in vector {
out_vect.push((item.get_name(), item.get_topic()));
out_vect.push((Arc::clone(&item), item.get_topic()));
} out_vect
}

Expand Down Expand Up @@ -589,7 +589,7 @@ pub async fn list(irc: &Core) -> Result<ClientReplies, GenError> {
let tuple_vector = irc.get_list_reply();
let mut replies = Vec::new();
for (chan, topic) in tuple_vector.iter() {
replies.push(Ok(ircReply::ListReply(chan.to_string(), topic.to_string())));
replies.push(Ok(ircReply::ListReply(chan.get_name(), chan.get_n_users(), topic.clone())));
}
replies.push(Ok(ircReply::EndofList));
Ok(replies)
Expand All @@ -612,13 +612,18 @@ pub async fn topic(irc: &Core, user: &User, mut params: ParsedMsg) -> Result<Cli

/* just want to receive topic? */
if params.opt_params.is_empty() {
replies.push(Ok(ircReply::Topic(chanmask, chan.get_topic())));
if let Some(topic) = chan.get_topic() {
replies.push(Ok(ircReply::Topic(chanmask.clone(), topic.text)));
replies.push(Ok(ircReply::TopicSetBy(chanmask, topic.usermask, topic.timestamp)));
} else {
replies.push(Ok(ircReply::NoTopic(chanmask)));
}
return Ok(replies);
};

/* set topic IF permissions allow */
if chan.is_op(user) {
chan.set_topic(&params.opt_params.remove(0));
chan.set_topic(&params.opt_params.remove(0), &user);
} else {
replies.push(Err(ircError::ChanOPrivsNeeded(chanmask)));
}
Expand Down
60 changes: 48 additions & 12 deletions src/irc/chan.rs
@@ -1,9 +1,11 @@
extern crate log;
extern crate chrono;
use crate::client::{ClientReply, ClientReplies, GenError};
use crate::irc::error::Error as ircError;
use crate::irc::reply::Reply as ircReply;
use crate::irc::{Core, User};

use chrono::Utc;
use std::clone::Clone;
use std::collections::BTreeMap;
use std::{error, fmt};
Expand Down Expand Up @@ -49,10 +51,27 @@ impl ChanUser {
}
}

#[derive(Debug)]
pub struct ChanTopic {
pub text: String,
pub usermask: String,
pub timestamp: i64
}

impl Clone for ChanTopic {
fn clone(&self) -> Self {
ChanTopic {
text: self.text.clone(),
usermask: self.usermask.clone(),
timestamp: self.timestamp
}
}
}

#[derive(Debug)]
pub struct Channel {
name: String,
topic: Mutex<String>,
topic: Mutex<Option<ChanTopic>>,
users: Mutex<BTreeMap<String, ChanUser>>,
banmasks: Mutex<Vec<String>>,
irc: Arc<Core>,
Expand All @@ -61,7 +80,7 @@ pub struct Channel {
impl Channel {
pub fn new(irc: &Arc<Core>, chanmask: &str) -> Channel {
let name = chanmask.to_string();
let topic = Mutex::new(String::from(""));
let topic = Mutex::new(None);
let users = Mutex::new(BTreeMap::new());
let banmasks = Mutex::new(Vec::new());
Channel {
Expand Down Expand Up @@ -126,12 +145,24 @@ impl Channel {
}).collect::<Vec<_>>()
}

pub fn get_topic(&self) -> String {
self.topic.lock().unwrap().to_string()
pub fn get_n_users(&self) -> usize {
self.users.lock().unwrap().len()
}

pub fn set_topic(&self, topic: &str) {
*self.topic.lock().unwrap() = topic.to_string()
pub fn get_topic(&self) -> Option<ChanTopic> {
match self.topic.lock().unwrap().clone() {
Some(topic) => Some(topic.clone()),
None => None
}
}

pub fn set_topic(&self, topic_text: &str, user: &User) {
let topic = ChanTopic {
text: topic_text.to_string(),
usermask: user.get_prefix(),
timestamp: Utc::now().timestamp()
};
*self.topic.lock().unwrap() = Some(topic);
}

pub fn get_name(&self) -> String {
Expand Down Expand Up @@ -180,7 +211,10 @@ impl Channel {

/* also self.notify_join() */
replies.push(self.notify_join(new_user, &chan).await?);
replies.push(Ok(ircReply::Topic(chan.to_string(), self.get_topic())));
if let Some(topic) = self.get_topic() {
replies.push(Ok(ircReply::Topic(chan.to_string(), topic.text)));
replies.push(Ok(ircReply::TopicSetBy(chan.to_string(), topic.usermask, topic.timestamp)))
}
replies.push(Ok(ircReply::NameReply(chan.to_string(), self.get_nick_list())));
replies.push(Ok(ircReply::EndofNames(chan.to_string())));
Ok(replies)
Expand All @@ -195,6 +229,11 @@ impl Channel {
* that in one place, both for User and Chan side - plus, mutex lock
* everything for the entire fn call */
pub async fn rm_user(&self, user: &User, msg: &str) -> Result<(), ChanError> {
/* Notify part msg */
if !self.is_empty() {
let _res = self.notify_part(user, &self.get_name(), msg).await;
}

let retval = {
let mut chan_mutex_lock = self.users.lock().unwrap();
let mut user_mutex_lock = user.channel_list.lock().unwrap();
Expand All @@ -214,10 +253,6 @@ impl Channel {
}
}; /* de-scope Mutex */

/* Notify part msg */
if !self.is_empty() {
let _res = self.notify_part(user, &self.get_name(), msg).await;
}
retval
}

Expand Down Expand Up @@ -254,7 +289,8 @@ impl Channel {
// we're forwarding messages, but this keeps us thread safe
let users = self.gen_user_ptr_vec();
for user in users.iter() {
if user.id != source.id {
// if you're parting or joining, your own echoed message confirms success
if user.id != source.id || command_str == "JOIN" || command_str == "PART" {
if let Err(err) = user.send_line(&line).await {
debug!("another tasks's client died: {}, note dead key {}", err, &user.get_nick());
//user.clear_chans_and_exit();
Expand Down
33 changes: 29 additions & 4 deletions src/irc/reply.rs
Expand Up @@ -45,17 +45,21 @@
use std::fmt;
use std::str;
use crate::irc::rfc_defs as rfc;
use crate::irc::chan::ChanTopic;

pub enum Reply {
None,
Welcome(String, String, String),
YourHost(String, String),
Created(String),
MyInfo(String, String, String, String),
NoTopic(String),
Topic(String, String),
TopicSetBy(String, String, i64),
NameReply(String, Vec<String>),
EndofNames(String),
ListReply(String, String),
ListStart,
ListReply(String, usize, Option<ChanTopic>),
EndofList,
}

Expand All @@ -71,9 +75,12 @@ impl Reply {
Reply::Created(_t) => 003,
Reply::MyInfo(_s, _v, _um, _cm) => 004,
Reply::None => 300,
Reply::ListReply(_ch, _top) => 322,
Reply::ListStart => 321,
Reply::ListReply(_ch, _nu, _top) => 322,
Reply::EndofList => 323,
Reply::NoTopic(_ch) => 331,
Reply::Topic(_ch, _top) => 332,
Reply::TopicSetBy(_ch, _umask, _stamp) => 333,
Reply::NameReply(_ch, _ns) => 353,
Reply::EndofNames(_ch) => 366
}
Expand All @@ -92,9 +99,18 @@ impl Reply {
Reply::YourHost(serv, ver) => Some(format!(":Your host is {}, running version {}", serv, ver)),
Reply::Created(time) => Some(format!(":This server was created {}", time)),
Reply::MyInfo(serv, ver, umodes, chanmodes) => Some(format!(":{} {} {} {}", serv, ver, umodes, chanmodes)),
Reply::ListReply(chan, topic) => Some(format!(":{} {}", chan, topic)),
Reply::ListStart => Some(format!("Channel Users :Topic")),
Reply::ListReply(chan, n_users, topic_opt) => {
if let Some(topic) = topic_opt {
Some(format!("{} {} :{}", chan, n_users, topic.text))
} else {
Some(format!("{} {}", chan, n_users))
}
},
Reply::EndofList => Some(format!(":End of /LIST")),
Reply::NoTopic(chan) => Some(format!("{} :No topic is set.", chan)),
Reply::Topic(chan, topic_msg) => Some(format!("{} :{}", chan, topic_msg)),
Reply::TopicSetBy(chan, usermask, timestamp) => Some(format!("{} {} {}", chan, usermask, timestamp)),
Reply::NameReply(chan, nicks) => Some(format!("{} :{}", chan, nicks.join(" "))),
Reply::EndofNames(chan) => Some(format!("{} :End of /NAMES list", chan)),
}
Expand Down Expand Up @@ -169,9 +185,18 @@ impl fmt::Display for Reply {
Reply::YourHost(serv, ver) => write!(f, "002 :Your host is {}, running version {}", serv, ver),
Reply::Created(time) => write!(f, "003 :This server was created {}", time),
Reply::MyInfo(serv, ver, umodes, chanmodes) => write!(f, "004 :{} {} {} {}", serv, ver, umodes, chanmodes),
Reply::ListReply(chan, topic) => write!(f, "322 :{} {}", chan, topic),
Reply::ListStart => write!(f, "321 Chan Users :Topic"),
Reply::ListReply(chan, n_users, topic_opt) => {
if let Some(topic) = topic_opt {
write!(f, "322 {} {} :{}", chan, n_users, topic.text)
} else {
write!(f, "322 {} {}", chan, n_users)
}
},
Reply::EndofList => write!(f, "323 :End of /LIST"),
Reply::NoTopic(chan) => write!(f, "331 {} :No topic is set", chan),
Reply::Topic(chan, topic_msg) => write!(f, "332 {} :{}", chan, topic_msg),
Reply::TopicSetBy(chan, usermask, timestamp) => write!(f, "333 {} {} {}", chan, usermask, timestamp),
Reply::NameReply(chan, nicks) => write!(f, "353 {} :{}", chan, nicks.join(" ")),
Reply::EndofNames(chan) => write!(f, "366 {} :End of /NAMES list", chan),
}
Expand Down

0 comments on commit 6f2ea15

Please sign in to comment.