From f9e3022756ea454a31f797bfc9cfdc1d81ee86cf Mon Sep 17 00:00:00 2001 From: Jokler Date: Mon, 22 Oct 2018 03:14:51 +0200 Subject: Factoids: Rename to Factoid --- src/plugins/factoids/database.rs | 160 ----------------- src/plugins/factoids/mod.rs | 373 --------------------------------------- src/plugins/factoids/sandbox.lua | 126 ------------- src/plugins/factoids/utils.rs | 81 --------- 4 files changed, 740 deletions(-) delete mode 100644 src/plugins/factoids/database.rs delete mode 100644 src/plugins/factoids/mod.rs delete mode 100644 src/plugins/factoids/sandbox.lua delete mode 100644 src/plugins/factoids/utils.rs (limited to 'src/plugins/factoids') diff --git a/src/plugins/factoids/database.rs b/src/plugins/factoids/database.rs deleted file mode 100644 index ec8ed3e..0000000 --- a/src/plugins/factoids/database.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::collections::HashMap; -#[cfg(feature = "mysql")] -use std::sync::Arc; - -#[cfg(feature = "mysql")] -use diesel::mysql::MysqlConnection; -#[cfg(feature = "mysql")] -use diesel::prelude::*; -#[cfg(feature = "mysql")] -use failure::ResultExt; -#[cfg(feature = "mysql")] -use r2d2::Pool; -#[cfg(feature = "mysql")] -use r2d2_diesel::ConnectionManager; - -use chrono::NaiveDateTime; - -use super::error::*; - -#[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 + Sync { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError>; - fn get_factoid(&self, name: &str, idx: i32) -> Result; - fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError>; - fn count_factoids(&self, name: &str) -> Result; -} - -// HashMap -impl Database for HashMap<(String, i32), Factoid, S> { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError> { - let factoid = Factoid { - name: factoid.name.to_owned(), - idx: factoid.idx, - content: factoid.content.to_owned(), - author: factoid.author.to_owned(), - created: factoid.created, - }; - - let name = factoid.name.clone(); - match self.insert((name, factoid.idx), factoid) { - None => Ok(()), - Some(_) => Err(ErrorKind::Duplicate)?, - } - } - - fn get_factoid(&self, name: &str, idx: i32) -> Result { - Ok(self.get(&(name.to_owned(), idx)) - .cloned() - .ok_or(ErrorKind::NotFound)?) - } - - fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError> { - match self.remove(&(name.to_owned(), idx)) { - Some(_) => Ok(()), - None => Err(ErrorKind::NotFound)?, - } - } - - fn count_factoids(&self, name: &str) -> Result { - Ok(self.iter().filter(|&(&(ref n, _), _)| n == name).count() as i32) - } -} - -// Diesel automatically defines the factoids module as public. -// We create a schema module to keep it private. -#[cfg(feature = "mysql")] -mod schema { - table! { - factoids (name, idx) { - name -> Varchar, - idx -> Integer, - content -> Text, - author -> Varchar, - created -> Timestamp, - } - } -} - -#[cfg(feature = "mysql")] -use self::schema::factoids; - -#[cfg(feature = "mysql")] -impl Database for Arc>> { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError> { - use diesel; - - let conn = &*self.get().context(ErrorKind::NoConnection)?; - diesel::insert_into(factoids::table) - .values(factoid) - .execute(conn) - .context(ErrorKind::MysqlError)?; - - Ok(()) - } - - fn get_factoid(&self, name: &str, idx: i32) -> Result { - let conn = &*self.get().context(ErrorKind::NoConnection)?; - Ok(factoids::table - .find((name, idx)) - .first(conn) - .context(ErrorKind::MysqlError)?) - } - - fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError> { - use self::factoids::columns; - use diesel; - - let conn = &*self.get().context(ErrorKind::NoConnection)?; - match diesel::delete( - factoids::table - .filter(columns::name.eq(name)) - .filter(columns::idx.eq(idx)), - ).execute(conn) - { - Ok(v) => { - if v > 0 { - Ok(()) - } else { - Err(ErrorKind::NotFound)? - } - } - Err(e) => Err(e).context(ErrorKind::MysqlError)?, - } - } - - fn count_factoids(&self, name: &str) -> Result { - use diesel; - - let conn = &*self.get().context(ErrorKind::NoConnection)?; - let count: Result = factoids::table - .filter(factoids::columns::name.eq(name)) - .count() - .get_result(conn); - - match count { - Ok(c) => Ok(c as i32), - Err(diesel::NotFound) => Ok(0), - Err(e) => Err(e).context(ErrorKind::MysqlError)?, - } - } -} diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs deleted file mode 100644 index a3d521a..0000000 --- a/src/plugins/factoids/mod.rs +++ /dev/null @@ -1,373 +0,0 @@ -extern crate rlua; - -use self::rlua::prelude::*; -use antidote::RwLock; -use irc::client::prelude::*; -use std::fmt; -use std::marker::PhantomData; -use std::str::FromStr; - -use chrono::NaiveDateTime; -use time; - -use plugin::*; -use FrippyClient; -pub mod database; -use self::database::Database; - -mod utils; -use self::utils::*; -use utils::Url; - -use self::error::*; -use error::ErrorKind as FrippyErrorKind; -use error::FrippyError; -use failure::ResultExt; - -static LUA_SANDBOX: &'static str = include_str!("sandbox.lua"); - -enum FactoidResponse { - Public(String), - Private(String), -} - -#[derive(PluginName)] -pub struct Factoids { - factoids: RwLock, - phantom: PhantomData, -} - -impl Factoids { - pub fn new(db: T) -> Self { - Factoids { - factoids: RwLock::new(db), - phantom: PhantomData, - } - } - - fn create_factoid( - &self, - name: &str, - content: &str, - author: &str, - ) -> Result<&str, FactoidsError> { - let count = self.factoids.read().count_factoids(name)?; - let tm = time::now().to_timespec(); - - let factoid = database::NewFactoid { - name, - idx: count, - content, - author, - created: NaiveDateTime::from_timestamp(tm.sec, 0u32), - }; - - Ok(self.factoids - .write() - .insert_factoid(&factoid) - .map(|()| "Successfully added!")?) - } - - fn add(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { - if command.tokens.len() < 2 { - Err(ErrorKind::InvalidCommand)?; - } - - let name = command.tokens.remove(0); - let content = command.tokens.join(" "); - - Ok(self.create_factoid(&name, &content, &command.source)?) - } - - fn add_from_url(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { - if command.tokens.len() < 2 { - Err(ErrorKind::InvalidCommand)?; - } - - let name = command.tokens.remove(0); - let url = &command.tokens[0]; - let content = Url::from(url.as_ref()) - .max_kib(1024) - .request() - .context(ErrorKind::Download)?; - - Ok(self.create_factoid(&name, &content, &command.source)?) - } - - fn remove(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { - if command.tokens.is_empty() { - Err(ErrorKind::InvalidCommand)?; - } - - let name = command.tokens.remove(0); - let count = self.factoids.read().count_factoids(&name)?; - - match self.factoids.write().delete_factoid(&name, count - 1) { - Ok(()) => Ok("Successfully removed"), - Err(e) => Err(e)?, - } - } - - fn get(&self, command: &PluginCommand) -> Result { - let (name, idx) = match command.tokens.len() { - 0 => Err(ErrorKind::InvalidCommand)?, - 1 => { - let name = &command.tokens[0]; - let count = self.factoids.read().count_factoids(name)?; - - if count < 1 { - Err(ErrorKind::NotFound)?; - } - - (name, count - 1) - } - _ => { - let name = &command.tokens[0]; - let idx = match i32::from_str(&command.tokens[1]) { - Ok(i) => i, - Err(_) => Err(ErrorKind::InvalidCommand)?, - }; - - (name, idx) - } - }; - - let factoid = self.factoids - .read() - .get_factoid(name, idx) - .context(ErrorKind::NotFound)?; - - let message = factoid.content.replace("\n", "|").replace("\r", ""); - - Ok(format!("{}: {}", factoid.name, message)) - } - - fn info(&self, command: &PluginCommand) -> Result { - match command.tokens.len() { - 0 => Err(ErrorKind::InvalidCommand)?, - 1 => { - let name = &command.tokens[0]; - let count = self.factoids.read().count_factoids(name)?; - - Ok(match count { - 0 => Err(ErrorKind::NotFound)?, - 1 => format!("There is 1 version of {}", name), - _ => format!("There are {} versions of {}", count, name), - }) - } - _ => { - let name = &command.tokens[0]; - let idx = i32::from_str(&command.tokens[1]).context(ErrorKind::InvalidIndex)?; - let factoid = self.factoids.read().get_factoid(name, idx)?; - - Ok(format!( - "{}: Added by {} at {} UTC", - name, factoid.author, factoid.created - )) - } - } - } - - fn exec(&self, mut command: PluginCommand) -> Result { - if command.tokens.is_empty() { - Err(ErrorKind::InvalidIndex)? - } else { - let name = command.tokens.remove(0); - let count = self.factoids.read().count_factoids(&name)?; - let factoid = self.factoids.read().get_factoid(&name, count - 1)?; - - let content = factoid.content; - let value = if content.starts_with('>') { - let content = String::from(&content[1..]); - - if content.starts_with('>') { - content - } else { - match self.run_lua(&name, &content, &command) { - Ok(v) => v, - Err(e) => match e { - LuaError::CallbackError { cause, .. } => cause.to_string(), - _ => e.to_string(), - }, - } - } - } else { - content - }; - - Ok(value.replace("\n", "|").replace("\r", "")) - } - } - - fn run_lua(&self, name: &str, code: &str, command: &PluginCommand) -> Result { - let args = command - .tokens - .iter() - .filter(|x| !x.is_empty()) - .map(ToOwned::to_owned) - .collect::>(); - - let lua = unsafe { Lua::new_with_debug() }; - let globals = lua.globals(); - - globals.set("factoid", code)?; - globals.set("download", lua.create_function(download)?)?; - globals.set("json_decode", lua.create_function(json_decode)?)?; - globals.set("sleep", lua.create_function(sleep)?)?; - globals.set("args", args)?; - globals.set("input", command.tokens.join(" "))?; - globals.set("user", command.source.clone())?; - globals.set("channel", command.target.clone())?; - globals.set("output", lua.create_table()?)?; - - lua.exec::<()>(LUA_SANDBOX, Some(name))?; - let output: Vec = globals.get::<_, Vec>("output")?; - - Ok(output.join("|")) - } - - fn help(&self) -> &str { - "usage: factoids \r\n\ - subcommands: add, fromurl, remove, get, info, exec, help" - } -} - -impl Plugin for Factoids { - type Client = C; - fn execute(&self, _: &Self::Client, message: &Message) -> ExecutionStatus { - match message.command { - Command::PRIVMSG(_, ref content) => if content.starts_with('!') { - ExecutionStatus::RequiresThread - } else { - ExecutionStatus::Done - }, - _ => ExecutionStatus::Done, - } - } - - fn execute_threaded( - &self, - client: &Self::Client, - message: &Message, - ) -> Result<(), FrippyError> { - if let Command::PRIVMSG(_, mut content) = message.command.clone() { - content.remove(0); - - let t: Vec = content.split(' ').map(ToOwned::to_owned).collect(); - - let c = PluginCommand { - source: message.source_nickname().unwrap().to_owned(), - target: message.response_target().unwrap().to_owned(), - tokens: t, - }; - - if let Ok(f) = self.exec(c) { - client - .send_privmsg(&message.response_target().unwrap(), &f) - .context(FrippyErrorKind::Connection)?; - } - } - - Ok(()) - } - - fn command( - &self, - client: &Self::Client, - mut command: PluginCommand, - ) -> Result<(), FrippyError> { - use self::FactoidResponse::{Private, Public}; - - if command.tokens.is_empty() { - client - .send_notice(&command.source, "Invalid command") - .context(FrippyErrorKind::Connection)?; - - return Ok(()); - } - - let target = command.target.clone(); - let source = command.source.clone(); - - let sub_command = command.tokens.remove(0); - let result = match sub_command.as_ref() { - "add" => self.add(&mut command).map(|s| Private(s.to_owned())), - "fromurl" => self.add_from_url(&mut command) - .map(|s| Private(s.to_owned())), - "remove" => self.remove(&mut command).map(|s| Private(s.to_owned())), - "get" => self.get(&command).map(Public), - "info" => self.info(&command).map(Public), - "exec" => self.exec(command).map(Public), - "help" => Ok(Private(self.help().to_owned())), - _ => Err(ErrorKind::InvalidCommand.into()), - }; - - match result { - Ok(v) => match v { - Public(m) => client - .send_privmsg(&target, &m) - .context(FrippyErrorKind::Connection)?, - Private(m) => client - .send_notice(&source, &m) - .context(FrippyErrorKind::Connection)?, - }, - Err(e) => { - let message = e.to_string(); - client - .send_notice(&source, &message) - .context(FrippyErrorKind::Connection)?; - Err(e).context(FrippyErrorKind::Factoids)? - } - } - - Ok(()) - } - - fn evaluate(&self, _: &Self::Client, _: PluginCommand) -> Result { - Err(String::from( - "Evaluation of commands is not implemented for Factoids at this time", - )) - } -} - -impl fmt::Debug for Factoids { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Factoids {{ ... }}") - } -} - -pub mod error { - #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] - #[error = "FactoidsError"] - pub enum ErrorKind { - /// Invalid command error - #[fail(display = "Invalid Command")] - InvalidCommand, - - /// Invalid index error - #[fail(display = "Invalid index")] - InvalidIndex, - - /// Download error - #[fail(display = "Download failed")] - Download, - - /// Duplicate error - #[fail(display = "Entry already exists")] - Duplicate, - - /// Not found error - #[fail(display = "Factoid was not found")] - NotFound, - - /// MySQL error - #[cfg(feature = "mysql")] - #[fail(display = "Failed to execute MySQL Query")] - MysqlError, - - /// No connection error - #[cfg(feature = "mysql")] - #[fail(display = "No connection to the database")] - NoConnection, - } -} diff --git a/src/plugins/factoids/sandbox.lua b/src/plugins/factoids/sandbox.lua deleted file mode 100644 index a927535..0000000 --- a/src/plugins/factoids/sandbox.lua +++ /dev/null @@ -1,126 +0,0 @@ -function send(text) - local text = tostring(text) - local len = #output - if len < 1 then - output = { text } - else - output[len] = output[len] .. text - end -end - -function sendln(text) - send(text) - table.insert(output, "") -end - -function trim(s) - local from = s:match"^%s*()" - return from > #s and "" or s:match(".*%S", from) -end - -trimmedInput = trim(input) - -if trimmedInput == "" then - ioru = user -else - ioru = trimmedInput -end - -local sandbox_env = { - print = send, - println = sendln, - trim = trim, - eval = nil, - sleep = nil, - json = {decode = json_decode}, - args = args, - input = input, - user = user, - ioru = ioru, - channel = channel, - request = download, - string = string, - math = math, - table = table, - pairs = pairs, - ipairs = ipairs, - next = next, - select = select, - unpack = unpack, - tostring = tostring, - tonumber = tonumber, - type = type, - assert = assert, - error = error, - pcall = pcall, - xpcall = xpcall, - _VERSION = _VERSION -} - -sandbox_env.os = { - clock = os.clock, - time = os.time, - difftime = os.difftime -} - -sandbox_env.string.rep = nil -sandbox_env.string.dump = nil -sandbox_env.math.randomseed = nil - --- Temporary evaluation function -function eval(code) - local c, e = load(code, nil, nil, sandbox_env) - if c then - return c() - else - error(e) - end -end - --- Only sleeps for 1 second at a time --- This ensures that the timeout check can still run -function safesleep(dur) - while dur > 1000 do - dur = dur - 1000 - sleep(1000) - end - sleep(dur) -end - -sandbox_env.eval = eval -sandbox_env.sleep = safesleep - --- Check if the factoid timed out -function checktime() - if os.time() - time >= timeout then - error("Timed out after " .. timeout .. " seconds", 0) - else - -- Limit the cpu usage of factoids - sleep(1) - end -end - --- Check if the factoid uses too much memory -function checkmem() - if collectgarbage("count") > maxmem then - error("Factoid used over " .. maxmem .. " kbyte of ram") - end -end - -local f, e = load(factoid, nil, nil, sandbox_env) - --- Add timeout hook -time = os.time() --- The timeout is defined in seconds -timeout = 30 -debug.sethook(checktime, "l") --- Add memory check hook --- The max memory is defined in kilobytes -maxmem = 1000 -debug.sethook(checkmem, "l") - -if f then - f() -else - error(e) -end diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs deleted file mode 100644 index a35dd27..0000000 --- a/src/plugins/factoids/utils.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::thread; -use std::time::Duration; - -use serde_json::{self, Value as SerdeValue}; - -use super::rlua::Error as LuaError; -use super::rlua::Error::RuntimeError; -use super::rlua::{Lua, Value as LuaValue}; - -use utils::error::ErrorKind::Connection; -use utils::Url; - -use failure::Fail; - -pub fn sleep(_: &Lua, dur: u64) -> Result<(), LuaError> { - thread::sleep(Duration::from_millis(dur)); - Ok(()) -} - -pub fn download(_: &Lua, url: String) -> Result { - let url = Url::from(url).max_kib(1024); - match url.request() { - Ok(v) => Ok(v), - Err(e) => { - let error = match e.kind() { - Connection => e.cause().unwrap().to_string(), - _ => e.to_string(), - }; - - Err(RuntimeError(format!( - "Failed to download {} - {}", - url.as_str(), - error - ))) - } - } -} - -fn convert_value(lua: &Lua, sval: SerdeValue, max_recurs: usize) -> Result { - if max_recurs == 0 { - return Err(RuntimeError(String::from( - "Reached max recursion level - json is nested too deep", - ))); - } - - let lval = match sval { - SerdeValue::Null => LuaValue::Nil, - SerdeValue::Bool(b) => LuaValue::Boolean(b), - SerdeValue::String(s) => LuaValue::String(lua.create_string(&s)?), - SerdeValue::Number(n) => { - let f = n.as_f64().ok_or_else(|| RuntimeError(String::from( - "Failed to convert number into double", - )))?; - LuaValue::Number(f) - } - SerdeValue::Array(arr) => { - let table = lua.create_table()?; - for (i, val) in arr.into_iter().enumerate() { - table.set(i + 1, convert_value(lua, val, max_recurs - 1)?)?; - } - - LuaValue::Table(table) - } - SerdeValue::Object(obj) => { - let table = lua.create_table()?; - for (key, val) in obj { - table.set(key, convert_value(lua, val, max_recurs - 1)?)?; - } - - LuaValue::Table(table) - } - }; - - Ok(lval) -} - -pub fn json_decode(lua: &Lua, json: String) -> Result { - let ser_val: SerdeValue = serde_json::from_str(&json).map_err(|e| RuntimeError(e.to_string()))?; - - convert_value(lua, ser_val, 25) -} -- cgit v1.2.3-70-g09d2