tellirc 0.1.0

IRC bot - Saves messages and tell the person you are after
Documentation
extern crate rusqlite;
extern crate serde_json;

use rusqlite::types::{ToSql};
use rusqlite::{Connection, NO_PARAMS};
use irc::client::prelude::IrcClient;

#[derive(Debug, Clone)]
pub struct AliasRow {
    id: u32,
    aliases: String,
}

impl PartialEq for AliasRow {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

type Aliases = Vec<String>;

#[derive(Debug, Clone)]
pub struct MessageCacheRow {
    id: u32,
    pub to_name: String,
    pub message: String,
    pub in_chan: Option<String>,
    pub from_name: String,
}

pub trait DaoTrait {
    fn get_message_for(&self, who: &str, in_chan: &str) -> Result<Option<Vec<MessageCacheRow>>, &'static str>;
    fn add_message_for(&self, who: &str, what: &str, chan: &Option<&str>, from: &str) -> Result<(), &'static str>;
    fn del_message(&self, msg: &MessageCacheRow) -> Result<(), &'static str>;
    fn get_alias_for(&self, who: &str) -> Result<(Option<AliasRow>, Aliases), &'static str>;
    fn add_alias_for(&self, who: &str, to_add: &str) -> Result<Aliases, &'static str>;
    fn del_alias_for(&self, who: &str, to_del: &str) -> Result<Aliases, &'static str>;
}

pub struct Dao {
    connection: Connection,
}

impl Dao {
    pub fn new(conn: Connection) -> Dao {
        Dao { connection: conn }
    }

    fn _get_message_for_single(&self, who: &str, in_chan: &str) -> Result<Option<Vec<MessageCacheRow>>, &'static str> {
        let mut stmt = self.connection
                            .prepare("SELECT id, to_name, message, in_chan, from_name
                                        FROM MessageCache WHERE to_name IS ?1 AND in_chan is ?2").unwrap();

        let iter = stmt.query_map(&[&who as &ToSql, &in_chan as &ToSql], |row| Ok(MessageCacheRow {
            id: row.get(0).unwrap(),
            to_name: row.get(1).unwrap(),
            message: row.get(2).unwrap(),
            in_chan: row.get(3).unwrap(),
            from_name: row.get(4).unwrap(),
        }
        )).unwrap();

        let res: Vec<MessageCacheRow> = iter.map(|a| a.unwrap()).collect();
        
        return Ok(Some(res))
    }
}

impl DaoTrait for Dao {
    fn del_message(&self, msg: &MessageCacheRow) -> Result<(), &'static str> {
        let res = self.connection
            .execute("DELETE FROM MessageCache WHERE id = ?1", &[&msg.id]);
        match res {
            Ok(_) => Ok(()),
            Err(e) => Err("SQL error"),
        }

    }

    fn add_message_for(&self, who: &str, what: &str, chan: &Option<&str>, from: &str) -> Result<(), &'static str> {
        let result = self.connection
            .execute("INSERT INTO MessageCache (to_name, message, in_chan, from_name)
                            VALUES (?1, ?2, ?3, ?4)",
            &[&who as &ToSql, &what as &ToSql, chan, &from as &ToSql]);
        match result {
            Ok(_) => Ok(()),
            Err(e) => Err("SQL error"),
        }
    }

    fn get_message_for(&self, who: &str, in_chan: &str) -> Result<Option<Vec<MessageCacheRow>>, &'static str> {
        let aliases = self.get_alias_for(who).unwrap();
        let mut results: Vec<MessageCacheRow> = vec!();
        for alias in aliases.1.iter() {
            let msgs = self._get_message_for_single(&alias, &in_chan);
            if let Ok(opt) = msgs {
                if let Some(mut data) = opt {
                    results.append(&mut data);
                }
            } else if let Err(e) = msgs {
                return Err(e)
            }
        }
        if results.len() == 0 {
            Ok(None)
        } else {
            Ok(Some(results))
        }
    }

    fn get_alias_for(&self, who: &str) -> Result<(Option<AliasRow>, Aliases), &'static str> {
        let mut stmt = self.connection
            .prepare("SELECT id, aliases FROM Aliases").unwrap();
        let iter = stmt.query_map(NO_PARAMS, |row| Ok(AliasRow {
            id: row.get(0).unwrap(),
            aliases: row.get(1).unwrap(),
        })).unwrap();
        let mapped: Vec<(AliasRow, Aliases)> = iter
                                .map(|item| {
                                    let temp = item.unwrap();
                                    let als = serde_json::from_str::<Aliases>(&temp.aliases).unwrap();
                                     (temp, als) 
                                    })
                                .filter(|v| { v.1.contains(&who.to_string()) } )
                                .collect();
        if mapped.len() == 0 {
            Ok((None, vec!(String::from(who))))
        } else {
            Ok((Some(mapped.get(0).unwrap().0.clone()), mapped.get(0).unwrap().1.to_vec()))
        }
    }

    fn add_alias_for(&self, who: &str, to_add: &str) -> Result<Aliases, &'static str> {
        let res = self.get_alias_for(who);
        let res_to_add = self.get_alias_for(to_add);
        if let Ok((Some(ref row0), _)) = res_to_add { 
            if let Ok((Some(ref row1), _)) = res {
                if row0 == row1 {
                    return Err("Already exists")
                }
            } else {
                return Err("Attempting to assign alias to another group of aliases!")
            }
        }
        if let Ok((Some(row), mut als)) = res {
            // Found existing 
            let mut stmt = self.connection
                            .prepare("UPDATE Aliases SET aliases = ?1 WHERE id = ?2").unwrap();
            als.push(String::from(to_add));
            stmt.execute(&[serde_json::to_string(&als).unwrap(), row.id.to_string()]).unwrap();
            return Ok(als)
        } else if let Ok((None, mut als)) = res {
            let mut add_stmt = self.connection
                            .prepare("INSERT INTO Aliases (aliases) VALUES (?1)").unwrap();
            als.push(String::from(to_add));
            add_stmt.execute(&[serde_json::to_string(&als).unwrap()]).unwrap();
            return Ok(als)
        } else {
            Err("SQL error")
        }
    }

    fn del_alias_for(&self, who: &str, to_del: &str) -> Result<Aliases, &'static str> {
        let res = self.get_alias_for(who);
        let res_to_del = self.get_alias_for(to_del);
        if let Ok((Some(ref row0), ref al_del)) = res_to_del { 
            if let Ok((Some(ref row1), _)) = res {
                if row0 == row1 {
                    let new_vec: Aliases = al_del.iter().filter(|e| { **e != String::from(to_del)}).cloned().collect();
                    let mut stmt = self.connection
                                    .prepare("UPDATE Aliases SET aliases = ?1 WHERE id = ?2").unwrap();
                    stmt.execute(&[serde_json::to_string(&new_vec).unwrap(), row0.id.to_string()]).unwrap();
                    return Ok(new_vec)
                } else {
                    return Err("The two are not aliases!");
                }
            } else {
                return Err("Attempting to assign alias to another group of aliases!")
            }
        }
        return Err("The two are not aliases!");
    }
}

pub fn get_actual_message(what: &str) -> &str {
    let mut counter = 0;
    let index = what.find(move|c: char| {
        if c.is_whitespace() {
            counter = counter + 1;
        }
        counter >= 2 
    });
    match slice_index(&what, &index) {
        Some(s) => s,
        None => &what[0..0],
    }
}

fn slice_index<'a>(what: &'a str, index: &Option<usize>) -> Option<&'a str> {
    match index {
        Some(i) => Some(&what[*i+1..]),
        None => None,
    }
}

pub fn new_config_from_file(file_name: &str) -> Result<irc::client::prelude::Config, irc::error::IrcError> {
    irc::client::prelude::Config::load(&file_name)
}