<commit_before>//! Interface for working with IRC Servers.
//!
//! There are currently two recommended ways to work
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::error::Error as StdError;
use std::io::{BufReader, BufWriter, Error, ErrorKind, Result};
use std::path::Path;
use std::sync::{Arc, Mutex, RwLock};
use std::iter::Map;
use client::conn::{Connection, NetStream};
use client::data::{Command, Config, Message, Response, User};
use client::data::Command::{JOIN, NICK, NICKSERV, PONG, MODE};
use client::data::kinds::{IrcRead, IrcWrite};
#[cfg(feature = "ctcp")] use time::now;

pub mod utils;

/// Trait describing core Server functionality.
pub trait Server<'a, T: IrcRead, U: IrcWrite> {
    /// Gets the configuration being used with this Server.
    fn config(&self) -> &Config;
    /// Sends a Command to this Server.
    fn send<M: Into<Message>>(&self, message: M) -> Result<()> where Self: Sized;
    /// Gets an Iterator over Messages received by this Server.
    fn iter(&'a self) -> ServerIterator<'a, T, U>;
    /// Gets an Iterator over Commands received by this Server.
    fn iter_cmd(&'a self) -> ServerCmdIterator<'a, T, U>;
    /// Gets a list of Users in the specified channel. This will be none if the channel is not
    /// being tracked, or if tracking is not supported altogether. For best results, be sure to
    /// request `multi-prefix` support from the server.
    fn list_users(&self, channel: &str) -> Option<Vec<User>>;
}

/// A thread-safe implementation of an IRC Server connection.
pub struct IrcServer<T: IrcRead, U: IrcWrite> {
    state: Arc<ServerState<T, U>>,
}

/// Thread-safe internal state for an IRC server connection.
struct ServerState<T: IrcRead, U: IrcWrite> {
    /// The thread-safe IRC connection.
    conn: Connection<T, U>,
    /// The configuration used with this connection.
    config: Config,
    /// A thread-safe map of channels to the list of users in them.
    chanlists: Mutex<HashMap<String, Vec<User>>>,
    /// A thread-safe index to track the current alternative nickname being used.
    alt_nick_index: RwLock<usize>,
}

/// An IrcServer over a buffered NetStream.
pub type NetIrcServer = IrcServer<BufReader<NetStream>, BufWriter<NetStream>>;

impl IrcServer<BufReader<NetStream>, BufWriter<NetStream>> {
    /// Creates a new IRC Server connection from the configuration at the specified path,
    /// connecting immediately.
    pub fn new<P: AsRef<Path>>(config: P) -> Result<NetIrcServer> {
        IrcServer::from_config(try!(Config::load(config)))
    }

    /// Creates a new IRC server connection from the specified configuration, connecting
    /// immediately.
    pub fn from_config(config: Config) -> Result<NetIrcServer> {
        let conn = try!(if config.use_ssl() {
            Connection::connect_ssl(config.server(), config.port())
        } else {
            Connection::connect(config.server(), config.port())
        });
        let state = ServerState {
            config: config,
            conn: conn,
            chanlists: Mutex::new(HashMap::new()),
            alt_nick_index: RwLock::new(0),
        };
        Ok(IrcServer { state: Arc::new(state) })
    }

    /// Reconnects to the IRC server.
    pub fn reconnect(&self) -> Result<()> {
        self.state.conn.reconnect(self.config().server(), self.config().port())
    }
}

impl<'a, T: IrcRead, U: IrcWrite> Clone for IrcServer<T, U> {
    fn clone(&self) -> IrcServer<T, U> {
        IrcServer { state: self.state.clone() }
    }
}

impl<'a, T: IrcRead, U: IrcWrite> Server<'a, T, U> for IrcServer<T, U> {
    fn config(&self) -> &Config {
        &self.state.config
    }

    #[cfg(feature = "encode")]
    fn send<M: Into<Message>>(&self, msg: M) -> Result<()> {
        self.state.conn.send(msg, self.config().encoding())
    }

    #[cfg(not(feature = "encode"))]
    fn send<M: Into<Message>>(&self, msg: M) -> Result<()> where Self: Sized {
        self.state.conn.send(msg)
    }

    fn iter(&'a self) -> ServerIterator<'a, T, U> {
        ServerIterator::new(self)
    }

    fn iter_cmd(&'a self) -> ServerCmdIterator<'a, T, U> {
        self.iter().map(Command::from_message_io)
    }

    #[cfg(not(feature = "nochanlists"))]
    fn list_users(&self, chan: &str) -> Option<Vec<User>> {
        self.state.chanlists.lock().unwrap().get(&chan.to_owned()).cloned()
    }


    #[cfg(feature = "nochanlists")]
    fn list_users(&self, chan: &str) -> Option<Vec<User>> {
        None
    }
}

impl<T: IrcRead, U: IrcWrite> IrcServer<T, U> {
    /// Creates an IRC server from the specified configuration, and any arbitrary Connection.
    pub fn from_connection(config: Config, conn: Connection<T, U>) -> IrcServer<T, U> {
        let state = ServerState {
            conn: conn,
            config: config,
            chanlists: Mutex::new(HashMap::new()),
            alt_nick_index: RwLock::new(0),
        };
        IrcServer { state: Arc::new(state) }
    }

    /// Gets a reference to the IRC server's connection.
    pub fn conn(&self) -> &Connection<T, U> {
        &self.state.conn
    }

    /// Handles messages internally for basic bot functionality.
    fn handle_message(&self, msg: &Message) {
        if let Some(resp) = Response::from_message(msg) {
            if resp == Response::RPL_NAMREPLY {
                if cfg!(not(feature = "nochanlists")) {
                    if let Some(users) = msg.suffix.clone() {
                        if msg.args.len() == 3 {
                            // TODO: replace with slice pattern matching when/if stable
                            let ref chan = msg.args[2];
                            for user in users.split(" ") {
                                if match self.state.chanlists.lock().unwrap().get_mut(chan) {
                                    Some(vec) => { vec.push(User::new(user)); false },
                                    None => true,
                                } {
                                    self.state.chanlists.lock().unwrap()
                                        .insert(chan.clone(), vec!(User::new(user)));
                                }
                            }
                        }
                    }
                }
            } else if resp == Response::RPL_ENDOFMOTD || resp == Response::ERR_NOMOTD {
                if self.config().nick_password() != "" {
                    self.send(NICKSERV(
                        format!("IDENTIFY {}", self.config().nick_password())
                    )).unwrap();<commit_msg>Moved to using an internal sending channel for messages.<commit_after>//! Interface for working with IRC Servers.
//!
//! There are currently two recommended ways to work
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::error::Error as StdError;
use std::io::{BufReader, BufWriter, Error, ErrorKind, Result};
use std::iter::Map;
use std::path::Path;
use std::sync::{Arc, Mutex, RwLock};
use std::sync::mpsc::{Sender, channel};
use std::thread::{JoinHandle, spawn};
use client::conn::{Connection, NetStream};
use client::data::{Command, Config, Message, Response, User};
use client::data::Command::{JOIN, NICK, NICKSERV, PONG, MODE};
use client::data::kinds::{IrcRead, IrcWrite};
#[cfg(feature = "ctcp")] use time::now;

pub mod utils;

/// Trait describing core Server functionality.
pub trait Server<'a, T: IrcRead, U: IrcWrite> {
    /// Gets the configuration being used with this Server.
    fn config(&self) -> &Config;
    /// Sends a Command to this Server.
    fn send<M: Into<Message>>(&self, message: M) -> Result<()> where Self: Sized;
    /// Gets an Iterator over Messages received by this Server.
    fn iter(&'a self) -> ServerIterator<'a, T, U>;
    /// Gets an Iterator over Commands received by this Server.
    fn iter_cmd(&'a self) -> ServerCmdIterator<'a, T, U>;
    /// Gets a list of Users in the specified channel. This will be none if the channel is not
    /// being tracked, or if tracking is not supported altogether. For best results, be sure to
    /// request `multi-prefix` support from the server.
    fn list_users(&self, channel: &str) -> Option<Vec<User>>;
}

/// A thread-safe implementation of an IRC Server connection.
pub struct IrcServer<T: IrcRead, U: IrcWrite> {
    state: Arc<ServerState<T, U>>,
    tx: Sender<Message>,
}

/// Thread-safe internal state for an IRC server connection.
struct ServerState<T: IrcRead, U: IrcWrite> {
    /// The thread-safe IRC connection.
    conn: Connection<T, U>,
    /// The handle for the message sending thread.
    write_handle: Mutex<Option<JoinHandle<()>>>,
    /// The configuration used with this connection.
    config: Config,
    /// A thread-safe map of channels to the list of users in them.
    chanlists: Mutex<HashMap<String, Vec<User>>>,
    /// A thread-safe index to track the current alternative nickname being used.
    alt_nick_index: RwLock<usize>,
}

/// An IrcServer over a buffered NetStream.
pub type NetIrcServer = IrcServer<BufReader<NetStream>, BufWriter<NetStream>>;

impl IrcServer<BufReader<NetStream>, BufWriter<NetStream>> {
    /// Creates a new IRC Server connection from the configuration at the specified path,
    /// connecting immediately.
    pub fn new<P: AsRef<Path>>(config: P) -> Result<NetIrcServer> {
        IrcServer::from_config(try!(Config::load(config)))
    }

    /// Creates a new IRC server connection from the specified configuration, connecting
    /// immediately.
    pub fn from_config(config: Config) -> Result<NetIrcServer> {
        let conn = try!(if config.use_ssl() {
            Connection::connect_ssl(config.server(), config.port())
        } else {
            Connection::connect(config.server(), config.port())
        });
        Ok(IrcServer::from_connection(config, conn))
    }

    /// Reconnects to the IRC server.
    pub fn reconnect(&self) -> Result<()> {
        self.state.conn.reconnect(self.config().server(), self.config().port())
    }
}

impl<T: IrcRead, U: IrcWrite> Clone for IrcServer<T, U> {
    fn clone(&self) -> IrcServer<T, U> {
        IrcServer { state: self.state.clone(), tx: self.tx.clone() }
    }
}

impl<T: IrcRead, U: IrcWrite> Drop for ServerState<T, U> {
    fn drop(&mut self) {
        let mut guard = self.write_handle.lock().unwrap();
        if let Some(handle) = guard.take() {
            handle.join().unwrap()
        }
    }
}

impl<'a, T: IrcRead, U: IrcWrite> Server<'a, T, U> for IrcServer<T, U> {
    fn config(&self) -> &Config {
        &self.state.config
    }

    #[cfg(feature = "encode")]
    fn send<M: Into<Message>>(&self, msg: M) -> Result<()> {
        self.state.conn.send(msg, self.config().encoding())
    }

    #[cfg(not(feature = "encode"))]
    fn send<M: Into<Message>>(&self, msg: M) -> Result<()> where Self: Sized {
        self.state.conn.send(msg)
    }

    fn iter(&'a self) -> ServerIterator<'a, T, U> {
        ServerIterator::new(self)
    }

    fn iter_cmd(&'a self) -> ServerCmdIterator<'a, T, U> {
        self.iter().map(Command::from_message_io)
    }

    #[cfg(not(feature = "nochanlists"))]
    fn list_users(&self, chan: &str) -> Option<Vec<User>> {
        self.state.chanlists.lock().unwrap().get(&chan.to_owned()).cloned()
    }


    #[cfg(feature = "nochanlists")]
    fn list_users(&self, chan: &str) -> Option<Vec<User>> {
        None
    }
}

impl<T: IrcRead, U: IrcWrite> IrcServer<T, U> {
    /// Creates an IRC server from the specified configuration, and any arbitrary Connection.
    pub fn from_connection(config: Config, conn: Connection<T, U>) -> IrcServer<T, U> {
        let (tx, rx) = channel();
        let state = Arc::new(ServerState {
            conn: conn,
            write_handle: Mutex::new(None),
            config: config,
            chanlists: Mutex::new(HashMap::new()),
            alt_nick_index: RwLock::new(0),
        });
        let weak = Arc::downgrade(&state);
        let write_handle = spawn(move || {
            while let Some(strong) = weak.upgrade() {
                match rx.recv() {
                    Ok(msg) => { IrcServer::write(&strong, msg); },
                    Err(_) => ()
                }
            }
        });
        let state2 = state.clone();
        let mut handle = state2.write_handle.lock().unwrap();
        *handle = Some(write_handle);
        IrcServer { state: state, tx: tx }
    }

    /// Gets a reference to the IRC server's connection.
    pub fn conn(&self) -> &Connection<T, U> {
        &self.state.conn
    }

    #[cfg(feature = "encode")]
    fn write<M: Into<Message>>(state: &Arc<ServerState<T, U>>, msg: M) -> Result<()> {
        state.conn.send(msg, state.config.encoding())
    }

    #[cfg(not(feature = "encode"))]
    fn write<M: Into<Message>>(state: &Arc<ServerState<T, U>>, msg: M) -> Result<()> where Self: Sized {
        state.conn.send(msg)
    }


    /// Handles messages internally for basic bot functionality.
    fn handle_message(&self, msg: &Message) {
        if let Some(resp) = Response::from_message(msg) {
            if resp == Response::RPL_NAMREPLY {
                if cfg!(not(feature = "nochanlists")) {
                    if let Some(users) = msg.suffix.clone() {
                        if msg.args.len() == 3 {
                            // TODO: replace with slice pattern matching when/if stable
                            let ref chan = msg.args[2];
                            for user in users.split(" ") {
                                if match self.state.chanlists.lock().unwrap().get_mut(chan) {
                                    Some(vec) => { vec.push(User::new(user)); false },
                                    None => true,
                                } {
                                    self.state.chanlists.lock().unwrap()
                                        .insert(chan.clone(), vec!(User::new(user)));
                                }
                            }
                        }
                    }
                }
            } else if resp == Response::RPL_ENDOFMOTD || resp == Response::ERR_NOMOTD {
                if self.config().nick_password() != "" {
                    self.send(NICKSERV(
                        format!("IDENTIFY {}", self.config().nick_password())
                    )).unwrap();
{'fraction_non_alphanumeric': 0.14663310258023915, 'fraction_numerical': 0.0006293266205160479, 'mean_word_length': 2.301477377654663, 'pattern_counts': {'":': 0, '<': 121, '<?xml version=': 0, '>': 165, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3916412', 'n_tokens_mistral': 4310, 'n_tokens_neox': 4253, 'n_words': 1170}