aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs9
-rw-r--r--src/plugins/factoids/database.rs120
-rw-r--r--src/plugins/factoids/mod.rs146
3 files changed, 210 insertions, 65 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 1f5514e..4407e6a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -35,8 +35,10 @@
#[macro_use]
extern crate diesel;
#[cfg(feature = "mysql")]
+extern crate diesel_infer_schema;
+#[cfg(feature = "mysql")]
#[macro_use]
-extern crate diesel_codegen;
+extern crate diesel_migrations;
#[macro_use]
extern crate log;
@@ -48,12 +50,15 @@ extern crate frippy_derive;
extern crate irc;
extern crate futures;
extern crate tokio_core;
+extern crate regex;
+extern crate chrono;
+extern crate time;
pub mod plugin;
pub mod plugins;
-use std::fmt;
use std::collections::HashMap;
+use std::fmt;
use std::thread::spawn;
use std::sync::Arc;
diff --git a/src/plugins/factoids/database.rs b/src/plugins/factoids/database.rs
index dbf136c..522c9d0 100644
--- a/src/plugins/factoids/database.rs
+++ b/src/plugins/factoids/database.rs
@@ -9,66 +9,112 @@ use diesel::prelude::*;
#[cfg(feature = "mysql")]
use diesel::mysql::MysqlConnection;
+use chrono::NaiveDateTime;
+
+pub enum DbResponse {
+ Success,
+ Failed(&'static str),
+}
+
+#[cfg_attr(feature = "mysql", derive(Queryable))]
+#[derive(Clone, Debug)]
+pub struct Factoid {
+ pub name: String,
+ pub idx: i32,
+ pub content: String,
+ pub author: String,
+ pub created: NaiveDateTime,
+}
+
+#[cfg_attr(feature = "mysql", derive(Insertable))]
+#[cfg_attr(feature = "mysql", table_name="factoids")]
+pub struct NewFactoid<'a> {
+ pub name: &'a str,
+ pub idx: i32,
+ pub content: &'a str,
+ pub author: &'a str,
+ pub created: NaiveDateTime,
+}
+
+
pub trait Database: Send {
- fn insert(&mut self, name: &str, content: &str) -> Option<()>;
- fn get(&self, name: &str) -> Option<String>;
+ fn insert(&mut self, factoid: &NewFactoid) -> DbResponse;
+ fn get(&self, name: &str, idx: i32) -> Option<Factoid>;
+ fn count(&self, name: &str) -> Result<i32, &'static str>;
}
-impl Database for HashMap<String, String> {
- fn insert(&mut self, name: &str, content: &str) -> Option<()> {
- self.insert(String::from(name), String::from(content)).map(|_| ())
+// HashMap
+impl Database for HashMap<(String, i32), Factoid> {
+ fn insert(&mut self, factoid: &NewFactoid) -> DbResponse {
+ let factoid = Factoid {
+ name: String::from(factoid.name),
+ idx: factoid.idx,
+ content: factoid.content.to_string(),
+ author: factoid.author.to_string(),
+ created: factoid.created,
+ };
+
+ let name = String::from(factoid.name.clone());
+ match self.insert((name, factoid.idx), factoid) {
+ None => DbResponse::Success,
+ Some(_) => DbResponse::Failed("Factoid was overwritten"),
+ }
}
- fn get(&self, name: &str) -> Option<String> {
- self.get(name).cloned()
+ fn get(&self, name: &str, idx: i32) -> Option<Factoid> {
+ self.get(&(String::from(name), idx)).map(|f| f.clone())
}
-}
-#[cfg(feature = "mysql")]
-#[derive(Queryable)]
-struct Factoid {
- pub name: String,
- pub content: String,
+ fn count(&self, name: &str) -> Result<i32, &'static str> {
+ Ok(self.iter()
+ .filter(|&(&(ref n, _), _)| n == name)
+ .count() as i32)
+ }
}
+// MySql
#[cfg(feature = "mysql")]
table! {
- factoids (name) {
+ factoids (name, idx) {
name -> Varchar,
- content -> Varchar,
+ idx -> Integer,
+ content -> Text,
+ author -> Varchar,
+ created -> Timestamp,
}
}
#[cfg(feature = "mysql")]
-#[derive(Insertable)]
-#[table_name="factoids"]
-struct NewFactoid<'a> {
- pub name: &'a str,
- pub content: &'a str,
-}
-
-
-#[cfg(feature = "mysql")]
impl Database for MysqlConnection {
- fn insert(&mut self, name: &str, content: &str) -> Option<()> {
- let factoid = NewFactoid {
- name: name,
- content: content,
- };
+ fn insert(&mut self, factoid: &NewFactoid) -> DbResponse {
+ use diesel;
- ::diesel::insert(&factoid)
- .into(factoids::table)
- .execute(self)
- .ok()
- .map(|_| ())
+ match diesel::insert_into(factoids::table)
+ .values(factoid)
+ .execute(self) {
+ Ok(_) => DbResponse::Success,
+ Err(_) => DbResponse::Failed("Database error - possible duplicate"),
+ }
}
- fn get(&self, name: &str) -> Option<String> {
+ fn get(&self, name: &str, idx: i32) -> Option<Factoid> {
factoids::table
- .filter(factoids::columns::name.eq(name))
+ .find((name, idx))
.limit(1)
.load::<Factoid>(self)
.ok()
- .and_then(|v| v.first().map(|f| f.content.clone()))
+ .and_then(|v| v.into_iter().next())
+ }
+
+ fn count(&self, name: &str) -> Result<i32, &'static str> {
+ let count: Result<i64, _> = factoids::table
+ .filter(factoids::columns::name.eq(name))
+ .count()
+ .first(self);
+
+ match count {
+ Ok(c) => Ok(c as i32),
+ Err(_) => Err("Database Error"),
+ }
}
}
diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs
index 5f9f99a..bb83700 100644
--- a/src/plugins/factoids/mod.rs
+++ b/src/plugins/factoids/mod.rs
@@ -1,15 +1,18 @@
extern crate rlua;
use std::fmt;
+use std::str::FromStr;
+use std::sync::Mutex;
use self::rlua::prelude::*;
use irc::client::prelude::*;
use irc::error::Error as IrcError;
-use std::sync::Mutex;
+use time;
+use chrono::NaiveDateTime;
use plugin::*;
mod database;
-use self::database::Database;
+use self::database::{Database, DbResponse};
static LUA_SANDBOX: &'static str = include_str!("sandbox.lua");
@@ -33,50 +36,140 @@ impl<T: Database> Factoids<T> {
}
fn add(&self, server: &IrcServer, command: &mut PluginCommand) -> Result<(), IrcError> {
-
if command.tokens.len() < 2 {
return self.invalid_command(server, command);
}
let name = command.tokens.remove(0);
-
- try_lock!(self.factoids)
- .insert(&name, &command.tokens.join(" "));
-
- server.send_notice(&command.source, "Successfully added")
+ let content = command.tokens.join(" ");
+ let count = match try_lock!(self.factoids).count(&name) {
+ Ok(c) => c,
+ Err(e) => return server.send_notice(&command.source, e),
+ };
+
+ let tm = time::now().to_timespec();
+
+ let factoid = database::NewFactoid {
+ name: &name,
+ idx: count,
+ content: &content,
+ author: &command.source,
+ created: NaiveDateTime::from_timestamp(tm.sec, tm.nsec as u32),
+ };
+
+ match try_lock!(self.factoids).insert(&factoid) {
+ DbResponse::Success => server.send_notice(&command.source, "Successfully added"),
+ DbResponse::Failed(e) => server.send_notice(&command.source, &e),
+ }
}
fn get(&self, server: &IrcServer, command: &PluginCommand) -> Result<(), IrcError> {
- if command.tokens.len() < 1 {
- self.invalid_command(server, command)
+ let (name, idx) = match command.tokens.len() {
+ 0 => return self.invalid_command(server, command),
+ 1 => {
+ let name = &command.tokens[0];
+ let count = match try_lock!(self.factoids).count(name) {
+ Ok(c) => c,
+ Err(e) => return server.send_notice(&command.source, e),
+ };
+
+ if count < 1 {
+ return server.send_notice(&command.source, &format!("{} does not exist", name));
+ }
- } else {
- let name = &command.tokens[0];
- let factoids = try_lock!(self.factoids);
- let factoid = match factoids.get(name) {
- Some(v) => v,
- None => return self.invalid_command(server, command),
- };
+ (name, count - 1)
+ }
+ _ => {
+ let name = &command.tokens[0];
+ let idx = match i32::from_str(&command.tokens[1]) {
+ Ok(i) => i,
+ Err(_) => return server.send_notice(&command.source, "Invalid index"),
+ };
+
+ (name, idx)
+ }
+ };
+
+ let factoid = match try_lock!(self.factoids).get(name, idx) {
+ Some(v) => v,
+ None => return server.send_notice(&command.source, &format!("{}~{} does not exist", name, idx)),
+ };
+
+ server.send_privmsg(&command.target,
+ &format!("{}: {}", factoid.name, factoid.content))
+ }
+
+ fn info(&self, server: &IrcServer, command: &PluginCommand) -> Result<(), IrcError> {
+
+ match command.tokens.len() {
+ 0 => self.invalid_command(server, command),
+ 1 => {
+ let name = &command.tokens[0];
+ let count = match try_lock!(self.factoids).count(name) {
+ Ok(c) => c,
+ Err(e) => return server.send_notice(&command.source, e),
+ };
+
+ match count {
+ 0 => server.send_notice(&command.source, &format!("{} does not exist", name)),
+ 1 => {
+ server.send_privmsg(&command.target,
+ &format!("There is 1 version of {}", name))
+ }
+ _ => {
+ server.send_privmsg(&command.target,
+ &format!("There are {} versions of {}", count, name))
+ }
+ }
+ }
+ _ => {
+ let name = &command.tokens[0];
+ let idx = match i32::from_str(&command.tokens[1]) {
+ Ok(i) => i,
+ Err(_) => return server.send_notice(&command.source, "Invalid index"),
+ };
+
+ let factoid = match try_lock!(self.factoids).get(name, idx) {
+ Some(v) => v,
+ None => {
+ return server.send_notice(&command.source,
+ &format!("{}~{} does not exist", name, idx))
+ }
+ };
+
+ server.send_privmsg(&command.target,
+ &format!("{}: Added by {} at {} UTC",
+ name,
+ factoid.author,
+ factoid.created))
+ }
- server.send_privmsg(&command.target, &format!("{}: {}", name, factoid))
}
}
- fn exec(&self, server: &IrcServer, mut command: PluginCommand) -> Result<(), IrcError> {
+ fn exec(&self,
+ server: &IrcServer,
+ mut command: PluginCommand,
+ error: bool)
+ -> Result<(), IrcError> {
if command.tokens.len() < 1 {
self.invalid_command(server, &command)
} else {
let name = command.tokens.remove(0);
+ let count = match try_lock!(self.factoids).count(&name) {
+ Ok(c) => c,
+ Err(e) => return server.send_notice(&command.source, e),
+ };
- let factoids = try_lock!(self.factoids);
- let factoid = match factoids.get(&name) {
- Some(v) => v,
- None => return self.invalid_command(server, &command),
+ let factoid = match try_lock!(self.factoids).get(&name, count - 1) {
+ Some(v) => v.content,
+ None if error => return self.invalid_command(server, &command),
+ None => return Ok(()),
};
- let value = if factoid.starts_with(">") {
+ let value = &if factoid.starts_with(">") {
let factoid = String::from(&factoid[1..]);
if factoid.starts_with(">") {
@@ -149,7 +242,7 @@ impl<T: Database> Plugin for Factoids<T> {
tokens: t,
};
- self.exec(server, c)
+ self.exec(server, c, false)
} else {
Ok(())
@@ -165,7 +258,8 @@ impl<T: Database> Plugin for Factoids<T> {
match sub_command.as_ref() {
"add" => self.add(server, &mut command),
"get" => self.get(server, &command),
- "exec" => self.exec(server, command),
+ "info" => self.info(server, &command),
+ "exec" => self.exec(server, command, true),
_ => self.invalid_command(server, &command),
}
}