From 5928afc3bf83661cd3b11130a31a2d97ef135a9e Mon Sep 17 00:00:00 2001
From: Jokler <jokler.contact@gmail.com>
Date: Sat, 4 Nov 2017 22:46:39 +0100
Subject: Add MySql as a possible database for the Factoids plugin

---
 migrations/2017-11-03-164322_create_factoids/up.sql | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 migrations/2017-11-03-164322_create_factoids/up.sql

(limited to 'migrations/2017-11-03-164322_create_factoids/up.sql')

diff --git a/migrations/2017-11-03-164322_create_factoids/up.sql b/migrations/2017-11-03-164322_create_factoids/up.sql
new file mode 100644
index 0000000..84e9f9d
--- /dev/null
+++ b/migrations/2017-11-03-164322_create_factoids/up.sql
@@ -0,0 +1,4 @@
+CREATE TABLE factoids (
+    name VARCHAR(32) PRIMARY KEY,
+    content VARCHAR(5000) NOT NULL
+)
-- 
cgit v1.2.3-70-g09d2


From 4b5693d3c6781a5ca4ef32f43f3994a65020c933 Mon Sep 17 00:00:00 2001
From: Jokler <jokler.contact@gmail.com>
Date: Sat, 16 Dec 2017 18:13:02 +0100
Subject: Add more info to factoids and save old versions

---
 Cargo.lock                                         |  78 +++++++++--
 Cargo.toml                                         |  19 ++-
 bin/main.rs                                        |  30 ++++-
 migrations/.gitkeep                                |   0
 .../2017-11-03-164322_create_factoids/up.sql       |   8 +-
 src/lib.rs                                         |   9 +-
 src/plugins/factoids/database.rs                   | 120 +++++++++++------
 src/plugins/factoids/mod.rs                        | 146 +++++++++++++++++----
 8 files changed, 322 insertions(+), 88 deletions(-)
 delete mode 100644 migrations/.gitkeep

(limited to 'migrations/2017-11-03-164322_create_factoids/up.sql')

diff --git a/Cargo.lock b/Cargo.lock
index 91693d5..936d8a4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -220,32 +220,40 @@ dependencies = [
 
 [[package]]
 name = "diesel"
-version = "0.16.0"
+version = "1.0.0-rc1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "diesel_derives 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
  "mysqlclient-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "diesel_codegen"
-version = "0.16.0"
+name = "diesel_derives"
+version = "1.0.0-rc1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "diesel_infer_schema 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "dotenv 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "diesel_infer_schema"
-version = "0.16.0"
+version = "1.0.0-rc1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "infer_schema_macros 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "diesel_migrations"
+version = "1.0.0-rc1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "migrations_internals 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "migrations_macros 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -350,9 +358,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 name = "frippy"
 version = "0.3.1"
 dependencies = [
+ "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "clippy 0.0.177 (registry+https://github.com/rust-lang/crates.io-index)",
- "diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "diesel_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "diesel 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "diesel_infer_schema 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "diesel_migrations 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
  "dotenv 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "frippy_derive 0.1.0",
  "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -502,6 +512,25 @@ name = "if_chain"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "infer_schema_internals"
+version = "1.0.0-rc1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "diesel 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "infer_schema_macros"
+version = "1.0.0-rc1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dotenv 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "infer_schema_internals 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "iovec"
 version = "0.1.1"
@@ -628,6 +657,24 @@ dependencies = [
  "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "migrations_internals"
+version = "1.0.0-rc1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "diesel 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "migrations_macros"
+version = "1.0.0-rc1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "migrations_internals 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "mime"
 version = "0.3.5"
@@ -1389,9 +1436,10 @@ dependencies = [
 "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
 "checksum debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a032eac705ca39214d169f83e3d3da290af06d8d1d344d1baad2fd002dca4b3"
 "checksum derive-error-chain 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c9ca9ade651388daad7c993f005d0d20c4f6fe78c1cdc93e95f161c6f5ede4a"
-"checksum diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "304226fa7a3982b0405f6bb95dd9c10c3e2000709f194038a60ec2c277150951"
-"checksum diesel_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18a42ca5c9b660add51d58bc5a50a87123380e1e458069c5504528a851ed7384"
-"checksum diesel_infer_schema 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf1957ff5cd3b04772e43c162c2f69c2aa918080ff9b020276792d236be8be52"
+"checksum diesel 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6b9e512c7fbcc7240848252ff620ed5f0e996e25d5c694b2b535d27db7465c6"
+"checksum diesel_derives 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)" = "a631faea061a7b5ab85e842866da57dbc31cc5fe715397a5e5a5b0effd7146b9"
+"checksum diesel_infer_schema 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cee2c5104f0258a4461c4c8694e870161cb974f4de8609fae3d496e26336590"
+"checksum diesel_migrations 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)" = "e1fd168d237ae0bd9e8bfdcb82ba87a4b72e4c1d9edd864242a67149c7f3c960"
 "checksum dotenv 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d6f0e2bb24d163428d8031d3ebd2d2bd903ad933205a97d0f18c7c1aade380f3"
 "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
 "checksum either 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "740178ddf48b1a9e878e6d6509a1442a2d42fd2928aae8e7a6f8a36fb01981b3"
@@ -1419,6 +1467,8 @@ dependencies = [
 "checksum hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c81fa95203e2a6087242c38691a0210f23e9f3f8f944350bd676522132e2985"
 "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
 "checksum if_chain 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "61bb90bdd39e3af69b0172dfc6130f6cd6332bf040fbb9bdd4401d37adbd48b8"
+"checksum infer_schema_internals 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2af8e23352c51aff14633500bfff361e9ea310375044b40dd8939e2defccd5c"
+"checksum infer_schema_macros 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa05e92983b3f9af1e7508f7d4981452542d505640d1b328fe74f4466c82b953"
 "checksum iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6e8b9c2247fcf6c6a1151f1156932be5606c9fd6f55a2d7f9fc1cb29386b2f7"
 "checksum irc 0.12.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c5053c25509042e963baa67d0d648add9f72b7487784505a5d5a3054d136a615"
 "checksum itertools 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3f2be4da1690a039e9ae5fd575f706a63ad5a2120f161b1d653c9da3930dd21"
@@ -1435,6 +1485,8 @@ dependencies = [
 "checksum markup5ever 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ff834ac7123c6a37826747e5ca09db41fd7a83126792021c2e636ad174bb77d3"
 "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
 "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
+"checksum migrations_internals 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)" = "c39531d07a48b5920d19310f9ba644667aac56edd933a6e7649893551756ad50"
+"checksum migrations_macros 1.0.0-rc1 (registry+https://github.com/rust-lang/crates.io-index)" = "12d6c72b7dae5fb40009c6313ef5a2075aa25f479a8a51f6a487abef887a7a2c"
 "checksum mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e00e17be181010a91dbfefb01660b17311059dc8c7f48b9017677721e732bd"
 "checksum mime_guess 2.0.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)" = "013572795763289e14710c7b279461295f2673b2b338200c235082cd7ca9e495"
 "checksum mio 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0e8411968194c7b139e9105bc4ae7db0bae232af087147e72f0616ebf5fdb9cb"
diff --git a/Cargo.toml b/Cargo.toml
index 5ff779f..5f535e4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,7 @@ path = "bin/main.rs"
 doc = false
 
 [features]
-mysql = ["diesel", "diesel_codegen", "dotenv"]
+mysql = ["diesel", "diesel_infer_schema", "diesel_migrations", "dotenv"]
 
 [dependencies]
 irc             = "0.12.5"
@@ -27,13 +27,14 @@ tokio-core      = "0.1.10"
 futures         = "0.1.16"
 log             = "0.3.8"
 time            = "0.1"
-rlua            = "0.9.2"
+rlua            = "0.9.3"
 reqwest         = "0.8.0"
 select          = "0.4.2"
 regex           = "0.2.2"
 lazy_static     = "0.2.9"
 serde           = "1.0.15"
 serde_json      = "1.0.3"
+chrono          = "0.4.0"
 glob            = "0.2"
 
 frippy_derive = { path = "frippy_derive" }
@@ -42,13 +43,19 @@ frippy_derive = { path = "frippy_derive" }
 git = 'https://github.com/Jokler/unicode_names'
 branch = 'update-to-latest-unicode'
 
+
 [dependencies.diesel]
-version = "0.16.0"
+version = "1.0.0-beta1"
+optional = true
+features = ["mysql", "chrono"]
+
+[dependencies.diesel_infer_schema]
+version = "1.0.0-beta1"
 optional = true
 features = ["mysql"]
 
-[dependencies.diesel_codegen]
-version = "0.16.0"
+[dependencies.diesel_migrations]
+version = "1.0.0-beta1"
 optional = true
 features = ["mysql"]
 
@@ -58,4 +65,4 @@ optional = true
 
 [dependencies.clippy]
 version = "*"
-optional = true
\ No newline at end of file
+optional = true
diff --git a/bin/main.rs b/bin/main.rs
index 1597a70..7869548 100644
--- a/bin/main.rs
+++ b/bin/main.rs
@@ -4,9 +4,16 @@ extern crate tokio_core;
 extern crate glob;
 extern crate futures;
 
+#[cfg(feature = "mysql")]
+#[macro_use]
+extern crate diesel_migrations;
+#[cfg(feature = "mysql")]
+extern crate diesel;
+
 #[macro_use]
 extern crate log;
 
+#[cfg(not(feature = "mysql"))]
 use std::collections::HashMap;
 
 use log::{LogRecord, LogLevel, LogLevelFilter, LogMetadata};
@@ -18,6 +25,9 @@ use glob::glob;
 use frippy::plugins;
 use frippy::Config;
 
+#[cfg(feature = "mysql")]
+embed_migrations!();
+
 struct Logger;
 
 impl log::Log for Logger {
@@ -44,7 +54,6 @@ impl log::Log for Logger {
 }
 
 fn main() {
-
     let log_level = if cfg!(debug_assertions) {
         LogLevelFilter::Debug
     } else {
@@ -87,7 +96,10 @@ fn main() {
         let mut disabled_plugins = None;
         if let &Some(ref options) = &config.options {
             if let Some(disabled) = options.get("disabled_plugins") {
-                disabled_plugins = Some(disabled.split(",").map(|p| p.trim()).collect::<Vec<_>>());
+                disabled_plugins = Some(disabled
+                                            .split(",")
+                                            .map(|p| p.trim())
+                                            .collect::<Vec<_>>());
             }
         }
 
@@ -97,8 +109,22 @@ fn main() {
         bot.add_plugin(plugins::Emoji::new());
         bot.add_plugin(plugins::Currency::new());
         bot.add_plugin(plugins::KeepNick::new());
+        #[cfg(feature = "mysql")]
+        {
+            use diesel;
+            use diesel::Connection;
+            match diesel::mysql::MysqlConnection::establish("mysql://user:password@address/db") {
+                Ok(conn) => {
+                    embedded_migrations::run(&conn).unwrap();
+                    bot.add_plugin(plugins::Factoids::new(conn));
+                }
+                Err(e) => error!("Failed to connect to database: {}", e),
+            }
+        }
+        #[cfg(not(feature = "mysql"))]
         bot.add_plugin(plugins::Factoids::new(HashMap::new()));
 
+
         if let Some(disabled_plugins) = disabled_plugins {
             for name in disabled_plugins {
                 if let None = bot.remove_plugin(name) {
diff --git a/migrations/.gitkeep b/migrations/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/migrations/2017-11-03-164322_create_factoids/up.sql b/migrations/2017-11-03-164322_create_factoids/up.sql
index 84e9f9d..784a3f4 100644
--- a/migrations/2017-11-03-164322_create_factoids/up.sql
+++ b/migrations/2017-11-03-164322_create_factoids/up.sql
@@ -1,4 +1,8 @@
 CREATE TABLE factoids (
-    name VARCHAR(32) PRIMARY KEY,
-    content VARCHAR(5000) NOT NULL
+    name VARCHAR(32) NOT NULL,
+    idx INTEGER NOT NULL,
+    content TEXT NOT NULL,
+    author VARCHAR(32) NOT NULL,
+    created TIMESTAMP NOT NULL,
+    PRIMARY KEY (name, idx)
 )
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),
         }
     }
-- 
cgit v1.2.3-70-g09d2

