Completed channel user list tracking.

This commit is contained in:
Aaron Weiss 2014-11-12 00:51:40 -05:00
parent 4e40fd8218
commit dd6b6eebd3
3 changed files with 153 additions and 10 deletions

View file

@ -2,11 +2,13 @@
use std::from_str::FromStr;
/// IRC User data.
#[deriving(PartialEq, Clone, Show)]
#[deriving(Clone, Show)]
pub struct User {
/// The user's nickname.
name: String,
/// The user's access level.
/// For simplicity, this is not used to determine the equality of two users.
/// That is a user is equal if and only if their nickname is the same.
access_level: AccessLevel,
}
@ -23,9 +25,37 @@ impl User {
access_level: rank.unwrap(),
}
}
/// Gets the user's access level.
pub fn access_level(&self) -> AccessLevel {
self.access_level
}
/// Updates the user's access level.
pub fn update_access_level(&mut self, mode: &str) {
self.access_level = match mode {
"+q" => Owner,
"-q" => Member,
"+a" => Admin,
"-a" => Member,
"+o" => Oper,
"-o" => Member,
"+h" => HalfOp,
"-h" => Member,
"+v" => Voice,
"-v" => Member,
_ => self.access_level,
}
}
}
///
impl PartialEq for User {
fn eq(&self, other: &User) -> bool {
self.name == other.name
}
}
/// The user's access level.
#[deriving(PartialEq, Clone, Show)]
pub enum AccessLevel {
/// The channel owner (~).
@ -44,7 +74,7 @@ pub enum AccessLevel {
impl FromStr for AccessLevel {
fn from_str(s: &str) -> Option<AccessLevel> {
if s.len() == 0 { None } else {
if s.len() == 0 { Some(Member) } else {
Some(match s.char_at(0) {
'~' => Owner,
'&' => Admin,
@ -59,5 +89,53 @@ impl FromStr for AccessLevel {
#[cfg(test)]
mod test {
use super::{AccessLevel, Admin, HalfOp, Member, Oper, Owner, User, Voice};
#[test]
fn access_level_from_str() {
assert_eq!(from_str::<AccessLevel>("member").unwrap(), Member);
assert_eq!(from_str::<AccessLevel>("~owner").unwrap(), Owner);
assert_eq!(from_str::<AccessLevel>("&admin").unwrap(), Admin);
assert_eq!(from_str::<AccessLevel>("@oper").unwrap(), Oper);
assert_eq!(from_str::<AccessLevel>("%halfop").unwrap(), HalfOp);
assert_eq!(from_str::<AccessLevel>("+voice").unwrap(), Voice);
assert_eq!(from_str::<AccessLevel>("").unwrap(), Member);
}
#[test]
fn create_user() {
let user = User::new("~owner");
let exp = User {
name: format!("owner"),
access_level: Owner,
};
assert_eq!(user, exp);
assert_eq!(user.access_level, exp.access_level);
}
#[test]
fn update_user_rank() {
let mut user = User::new("user");
assert_eq!(user.access_level, Member);
user.update_access_level("+q");
assert_eq!(user.access_level, Owner);
user.update_access_level("-q");
assert_eq!(user.access_level, Member);
user.update_access_level("+a");
assert_eq!(user.access_level, Admin);
user.update_access_level("-a");
assert_eq!(user.access_level, Member);
user.update_access_level("+o");
assert_eq!(user.access_level, Oper);
user.update_access_level("-o");
assert_eq!(user.access_level, Member);
user.update_access_level("+h");
assert_eq!(user.access_level, HalfOp);
user.update_access_level("-h");
assert_eq!(user.access_level, Member);
user.update_access_level("+v");
assert_eq!(user.access_level, Voice);
user.update_access_level("-v");
assert_eq!(user.access_level, Member);
}
}

View file

@ -21,6 +21,8 @@ pub trait Server<'a, T> {
fn send(&self, _: Command) -> IoResult<()>;
/// Gets an Iterator over Messages received by this Server.
fn iter(&'a self) -> ServerIterator<'a, T>;
/// Gets a list of Users in the specified channel.
fn list_users(&self, _: &str) -> Option<Vec<User>>;
}
/// A thread-safe implementation of an IRC Server connection.
@ -71,6 +73,10 @@ impl<'a, T> Server<'a, T> for IrcServer<'a, T> where T: IrcStream {
fn iter(&'a self) -> ServerIterator<'a, T> {
ServerIterator::new(self)
}
fn list_users(&self, chan: &str) -> Option<Vec<User>> {
self.chanlists.lock().find_copy(&chan.into_string())
}
}
impl<'a, T> IrcServer<'a, T> where T: IrcStream {
@ -94,7 +100,7 @@ impl<'a, T> IrcServer<'a, T> where T: IrcStream {
for chan in self.config.channels.iter() {
self.send(JOIN(chan[], None)).unwrap();
}
} /* FIXME: it's not really clear why this stuff is broken.
}
else if message.command[] == "353" { // /NAMES
if let Some(users) = message.suffix.clone() {
if let [_, _, ref chan] = message.args[] {
@ -114,7 +120,7 @@ impl<'a, T> IrcServer<'a, T> where T: IrcStream {
None => message.args[0][],
};
if let Some(vec) = self.chanlists.lock().get_mut(&String::from_str(chan)) {
if let Some(ref source) = message.suffix {
if let Some(ref source) = message.prefix {
if let Some(i) = source.find('!') {
if message.command[] == "JOIN" {
vec.push(User::new(source[..i]));
@ -126,8 +132,13 @@ impl<'a, T> IrcServer<'a, T> where T: IrcStream {
}
}
}
} */
/* TODO: implement more message handling */
} else if let ("MODE", [ref chan, ref mode, ref user]) = (message.command[], message.args[]) {
if let Some(vec) = self.chanlists.lock().get_mut(chan) {
if let Some(n) = vec.as_slice().position_elem(&User::new(user[])) {
vec[n].update_access_level(mode[]);
}
}
}
}
}
@ -168,7 +179,7 @@ mod test {
use std::io::{MemReader, MemWriter};
use std::io::util::{NullReader, NullWriter};
use conn::{Connection, IoStream};
use data::Config;
use data::{Config, User};
use data::command::PRIVMSG;
use data::kinds::IrcReader;
@ -223,4 +234,54 @@ mod test {
assert_eq!(get_server_value(server)[],
"PRIVMSG #test :Hi there!\r\n");
}
#[test]
fn user_tracking_names() {
let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n";
let server = IrcServer::from_connection(test_config(),
Connection::new(IoStream::new(NullWriter, MemReader::new(value.as_bytes().to_vec()))));
for message in server.iter() {
println!("{}", message);
}
assert_eq!(server.list_users("#test").unwrap(),
vec![User::new("test"), User::new("~owner"), User::new("&admin")])
}
#[test]
fn user_tracking_names_join() {
let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n:test2!test@test JOIN #test\r\n";
let server = IrcServer::from_connection(test_config(),
Connection::new(IoStream::new(NullWriter, MemReader::new(value.as_bytes().to_vec()))));
for message in server.iter() {
println!("{}", message);
}
assert_eq!(server.list_users("#test").unwrap(),
vec![User::new("test"), User::new("~owner"), User::new("&admin"), User::new("test2")])
}
#[test]
fn user_tracking_names_part() {
let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n:owner!test@test PART #test\r\n";
let server = IrcServer::from_connection(test_config(),
Connection::new(IoStream::new(NullWriter, MemReader::new(value.as_bytes().to_vec()))));
for message in server.iter() {
println!("{}", message);
}
assert_eq!(server.list_users("#test").unwrap(),
vec![User::new("test"), User::new("&admin")])
}
#[test]
fn user_tracking_names_mode() {
let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n:test!test@test MODE #test +o test\r\n";
let server = IrcServer::from_connection(test_config(),
Connection::new(IoStream::new(NullWriter, MemReader::new(value.as_bytes().to_vec()))));
for message in server.iter() {
println!("{}", message);
}
assert_eq!(server.list_users("#test").unwrap(),
vec![User::new("@test"), User::new("~owner"), User::new("&admin")]);
assert_eq!(server.list_users("#test").unwrap()[0].access_level(),
User::new("@test").access_level());
}
}

View file

@ -2,9 +2,9 @@
#![experimental]
use std::io::IoResult;
use data::command::{Command, INVITE, JOIN, KILL, MODE, NICK, KICK};
use data::{Command, Config, User};
use data::command::{INVITE, JOIN, KILL, MODE, NICK, KICK};
use data::command::{OPER, PONG, PRIVMSG, SAMODE, SANICK, TOPIC, USER};
use data::config::Config;
use data::kinds::IrcStream;
use server::{Server, ServerIterator};
@ -26,6 +26,10 @@ impl<'a, T> Server<'a, T> for Wrapper<'a, T> where T: IrcStream {
fn iter(&'a self) -> ServerIterator<'a, T> {
self.server.iter()
}
fn list_users(&self, chan: &str) -> Option<Vec<User>> {
self.server.list_users(chan)
}
}
impl<'a, T> Wrapper<'a, T> where T: IrcStream {