From ddf42bc0292b0befe2b2f47f3284d9ffeaf6f4b4 Mon Sep 17 00:00:00 2001 From: Jokler Date: Sat, 10 Feb 2018 21:00:51 +0100 Subject: Move main file into the src directory --- src/main.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..21e27dc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,113 @@ +extern crate frippy; +extern crate time; +extern crate irc; +extern crate glob; + +#[macro_use] +extern crate log; + +use log::{Record, Level, LevelFilter, Metadata}; + +use irc::client::reactor::IrcReactor; +use glob::glob; + +use frippy::plugins; +use frippy::Config; + +struct Logger; + +impl log::Log for Logger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.target().contains("frippy") + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + if record.metadata().level() >= Level::Debug { + println!("[{}]({}) {} -> {}", + time::now().rfc822(), + record.level(), + record.target(), + record.args()); + } else { + println!("[{}]({}) {}", + time::now().rfc822(), + record.level(), + record.args()); + } + } + } + + fn flush(&self) {} +} + +static LOGGER: Logger = Logger; + +fn main() { + + log::set_max_level(if cfg!(debug_assertions) { + LevelFilter::Debug + } else { + LevelFilter::Info + }); + + log::set_logger(&LOGGER).unwrap(); + + // Load all toml files in the configs directory + let mut configs = Vec::new(); + for toml in glob("configs/*.toml").unwrap() { + match toml { + Ok(path) => { + info!("Loading {}", path.to_str().unwrap()); + match Config::load(path) { + Ok(v) => configs.push(v), + Err(e) => error!("Incorrect config file {}", e), + } + } + Err(e) => error!("Failed to read path {}", e), + } + } + + // Without configs the bot would just idle + if configs.is_empty() { + error!("No config file found"); + return; + } + + // Create an event loop to run the connections on. + let mut reactor = IrcReactor::new().unwrap(); + + // Open a connection and add work for each config + for config in configs { + + 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::>()); + } + } + + let mut bot = frippy::Bot::new(); + bot.add_plugin(plugins::Help::new()); + bot.add_plugin(plugins::Url::new(1024)); + bot.add_plugin(plugins::Emoji::new()); + bot.add_plugin(plugins::Currency::new()); + bot.add_plugin(plugins::KeepNick::new()); + + if let Some(disabled_plugins) = disabled_plugins { + for name in disabled_plugins { + if let None = bot.remove_plugin(name) { + error!("{:?} was not found - could not disable", name); + } + } + } + + bot.connect(&mut reactor, &config).expect("Failed to connect"); + } + + // Run the bots until they throw an error - an error could be loss of connection + reactor.run().unwrap(); +} -- cgit v1.2.3-70-g09d2 From da5c2c8e4893bfb095a8e2122b943c4dca61c41d Mon Sep 17 00:00:00 2001 From: Jokler Date: Mon, 12 Feb 2018 19:16:59 +0100 Subject: Replace is_allowed with a single-threaded execute function The old execute got renamed to exeute_threaded. --- src/lib.rs | 40 ++++++++++++++++++++++------------------ src/main.rs | 12 ++++++++---- src/plugin.rs | 10 ++++++++-- src/plugins/currency.rs | 43 +++++++++++++++++++++---------------------- src/plugins/emoji.rs | 26 +++++++++++++------------- src/plugins/help.rs | 12 ++++++------ src/plugins/keepnick.rs | 42 +++++++++++++++++++++--------------------- src/plugins/url.rs | 22 +++++++++++++--------- 8 files changed, 112 insertions(+), 95 deletions(-) (limited to 'src/main.rs') diff --git a/src/lib.rs b/src/lib.rs index 3e47c8c..8a3a0d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,7 +146,7 @@ impl Bot { let plugins = self.plugins.clone(); reactor.register_client_with_handler(client, move |client, message| { - process_msg(&client, plugins.clone(), message) + process_msg(client, plugins.clone(), message) }); Ok(()) @@ -206,23 +206,27 @@ impl ThreadedPlugins { for (name, plugin) in self.plugins.clone() { // Send the message to the plugin if the plugin needs it - if plugin.is_allowed(server, &message) { - - debug!("Executing {} with {}", - name, - message.to_string().replace("\r\n", "")); - - // Clone everything before the move - the server uses an Arc internally too - let plugin = Arc::clone(&plugin); - let message = Arc::clone(&message); - let server = server.clone(); - - // Execute the plugin in another thread - spawn(move || { - if let Err(e) = plugin.execute(&server, &message) { - error!("Error in {} - {}", name, e); - }; - }); + match plugin.execute(server, &message) { + ExecutionStatus::Done => (), + ExecutionStatus::Err(e) => error!("Error in {} - {}", name, e), + ExecutionStatus::RequiresThread => { + + debug!("Spawning thread to execute {} with {}", + name, + message.to_string().replace("\r\n", "")); + + // Clone everything before the move - the server uses an Arc internally too + let plugin = Arc::clone(&plugin); + let message = Arc::clone(&message); + let server = server.clone(); + + // Execute the plugin in another thread + spawn(move || { + if let Err(e) = plugin.execute_threaded(&server, &message) { + error!("Error in {} - {}", name, e); + }; + }); + } } } } diff --git a/src/main.rs b/src/main.rs index 21e27dc..7df8b3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +#![cfg_attr(feature="clippy", feature(plugin))] +#![cfg_attr(feature="clippy", plugin(clippy))] + extern crate frippy; extern crate time; extern crate irc; @@ -81,10 +84,10 @@ fn main() { for config in configs { let mut disabled_plugins = None; - if let &Some(ref options) = &config.options { + if let Some(ref options) = config.options { if let Some(disabled) = options.get("disabled_plugins") { disabled_plugins = Some(disabled - .split(",") + .split(',') .map(|p| p.trim()) .collect::>()); } @@ -96,11 +99,12 @@ fn main() { bot.add_plugin(plugins::Emoji::new()); bot.add_plugin(plugins::Currency::new()); bot.add_plugin(plugins::KeepNick::new()); + bot.add_plugin(plugins::Tell::new()); if let Some(disabled_plugins) = disabled_plugins { for name in disabled_plugins { - if let None = bot.remove_plugin(name) { - error!("{:?} was not found - could not disable", name); + if bot.remove_plugin(name).is_none() { + error!("\"{}\" was not found - could not disable", name); } } } diff --git a/src/plugin.rs b/src/plugin.rs index 8785708..88fe3ce 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -4,13 +4,19 @@ use std::fmt; use irc::client::prelude::*; use irc::error::IrcError; +pub enum ExecutionStatus { + Done, + Err(IrcError), + RequiresThread, +} + /// `Plugin` has to be implemented for any struct that should be usable /// as a plugin in frippy. pub trait Plugin: PluginName + Send + Sync + fmt::Debug { /// This should return true if the `Plugin` wants to do work on the message. - fn is_allowed(&self, server: &IrcClient, message: &Message) -> bool; + fn execute(&self, server: &IrcClient, message: &Message) -> ExecutionStatus; /// Handles messages which are not commands but still necessary. - fn execute(&self, server: &IrcClient, message: &Message) -> Result<(), IrcError>; + fn execute_threaded(&self, server: &IrcClient, message: &Message) -> Result<(), IrcError>; /// Handles any command directed at this plugin. fn command(&self, server: &IrcClient, command: PluginCommand) -> Result<(), IrcError>; /// Should work like command but return a String instead of sending messages to IRC. diff --git a/src/plugins/currency.rs b/src/plugins/currency.rs index 32a2506..21330a1 100644 --- a/src/plugins/currency.rs +++ b/src/plugins/currency.rs @@ -78,16 +78,15 @@ impl Currency { }) } - fn convert(&self, server: &IrcClient, command: &mut PluginCommand) -> Result { - + fn convert(&self, client: &IrcClient, command: &mut PluginCommand) -> Result { if command.tokens.len() < 3 { - return Err(self.invalid_command(server)); + return Err(self.invalid_command(client)); } let request = match self.eval_command(&command.tokens) { Ok(request) => request, Err(_) => { - return Err(self.invalid_command(server)); + return Err(self.invalid_command(client)); } }; @@ -105,56 +104,56 @@ impl Currency { } } - fn help(&self, server: &IrcClient) -> String { + fn help(&self, client: &IrcClient) -> String { format!("usage: {} currency value from_currency to_currency\r\n\ - example: 1.5 eur usd\r\n\ + example: {0} currency 1.5 eur usd\r\n\ available currencies: AUD, BGN, BRL, CAD, \ CHF, CNY, CZK, DKK, GBP, HKD, HRK, HUF, \ IDR, ILS, INR, JPY, KRW, MXN, MYR, NOK, \ NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, \ TRY, USD, ZAR", - server.current_nickname()) + client.current_nickname()) } - fn invalid_command(&self, server: &IrcClient) -> String { + fn invalid_command(&self, client: &IrcClient) -> String { format!("Incorrect Command. \ Send \"{} currency help\" for help.", - server.current_nickname()) + client.current_nickname()) } } impl Plugin for Currency { - fn is_allowed(&self, _: &IrcClient, _: &Message) -> bool { - false + fn execute(&self, _: &IrcClient, _: &Message) -> ExecutionStatus { + ExecutionStatus::Done } - fn execute(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { panic!("Currency does not implement the execute function!") } - fn command(&self, server: &IrcClient, mut command: PluginCommand) -> Result<(), IrcError> { + fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), IrcError> { if command.tokens.is_empty() { - return server.send_notice(&command.source, &self.invalid_command(server)); + return client.send_notice(&command.source, &self.invalid_command(client)); } match command.tokens[0].as_ref() { - "help" => server.send_notice(&command.source, &self.help(server)), - _ => match self.convert(server, &mut command) { - Ok(msg) => server.send_privmsg(&command.target, &msg), - Err(msg) => server.send_notice(&command.source, &msg), + "help" => client.send_notice(&command.source, &self.help(client)), + _ => match self.convert(client, &mut command) { + Ok(msg) => client.send_privmsg(&command.target, &msg), + Err(msg) => client.send_notice(&command.source, &msg), } } } - fn evaluate(&self, server: &IrcClient, mut command: PluginCommand) -> Result{ + fn evaluate(&self, client: &IrcClient, mut command: PluginCommand) -> Result{ if command.tokens.is_empty() { - return Err(self.invalid_command(server)); + return Err(self.invalid_command(client)); } match command.tokens[0].as_ref() { - "help" => Ok(self.help(server)), - _ => self.convert(server, &mut command), + "help" => Ok(self.help(client)), + _ => self.convert(client, &mut command), } } } diff --git a/src/plugins/emoji.rs b/src/plugins/emoji.rs index c19593d..02a31f8 100644 --- a/src/plugins/emoji.rs +++ b/src/plugins/emoji.rs @@ -97,25 +97,25 @@ impl Emoji { } impl Plugin for Emoji { - fn is_allowed(&self, _: &IrcClient, message: &Message) -> bool { - match message.command { - Command::PRIVMSG(_, _) => true, - _ => false, - } - } - - fn execute(&self, server: &IrcClient, message: &Message) -> Result<(), IrcError> { + fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus { match message.command { Command::PRIVMSG(_, ref content) => { - server.send_privmsg(message.response_target().unwrap(), - &self.emoji(content)) + match client.send_privmsg(message.response_target().unwrap(), + &self.emoji(content)) { + Ok(_) => ExecutionStatus::Done, + Err(e) => ExecutionStatus::Err(e), + } } - _ => Ok(()), + _ => ExecutionStatus::Done, } } - fn command(&self, server: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - server.send_notice(&command.source, + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + panic!("Emoji should not use threading") + } + + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { + client.send_notice(&command.source, "This Plugin does not implement any commands.") } diff --git a/src/plugins/help.rs b/src/plugins/help.rs index 1bb15e1..4dd93d7 100644 --- a/src/plugins/help.rs +++ b/src/plugins/help.rs @@ -13,16 +13,16 @@ impl Help { } impl Plugin for Help { - fn is_allowed(&self, _: &IrcClient, _: &Message) -> bool { - false + fn execute(&self, _: &IrcClient, _: &Message) -> ExecutionStatus { + ExecutionStatus::Done } - fn execute(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { - panic!("Help does not implement the execute function!") + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + panic!("Help should not use threading") } - fn command(&self, server: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - server.send_notice(&command.source, "Help has not been added yet.") + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { + client.send_notice(&command.source, "Help has not been added yet.") } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { diff --git a/src/plugins/keepnick.rs b/src/plugins/keepnick.rs index 4a40e8f..e973769 100644 --- a/src/plugins/keepnick.rs +++ b/src/plugins/keepnick.rs @@ -11,47 +11,47 @@ impl KeepNick { KeepNick {} } - fn check_nick(&self, server: &IrcClient, leaver: &str) -> Result<(), IrcError> { - let cfg_nick = match server.config().nickname { + fn check_nick(&self, client: &IrcClient, leaver: &str) -> ExecutionStatus { + let cfg_nick = match client.config().nickname { Some(ref nick) => nick.clone(), - None => return Ok(()), + None => return ExecutionStatus::Done, }; if leaver != cfg_nick { - return Ok(()); + return ExecutionStatus::Done; } - let server_nick = server.current_nickname(); + let client_nick = client.current_nickname(); - if server_nick != cfg_nick { - info!("Trying to switch nick from {} to {}", server_nick, cfg_nick); - server.send(Command::NICK(cfg_nick)) + if client_nick != cfg_nick { + info!("Trying to switch nick from {} to {}", client_nick, cfg_nick); + match client.send(Command::NICK(cfg_nick)) { + Ok(_) => ExecutionStatus::Done, + Err(e) => ExecutionStatus::Err(e), + } } else { - Ok(()) + ExecutionStatus::Done } } } impl Plugin for KeepNick { - fn is_allowed(&self, _: &IrcClient, message: &Message) -> bool { - match message.command { - Command::QUIT(_) => true, - _ => false, - } - } - - fn execute(&self, server: &IrcClient, message: &Message) -> Result<(), IrcError> { + fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus { match message.command { Command::QUIT(ref nick) => { - self.check_nick(server, &nick.clone().unwrap_or_else(|| String::new())) + self.check_nick(client, &nick.clone().unwrap_or_else(String::new)) } - _ => Ok(()), + _ => ExecutionStatus::Done, } } - fn command(&self, server: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - server.send_notice(&command.source, + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + panic!("Tell should not use threading") + } + + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { + client.send_notice(&command.source, "This Plugin does not implement any commands.") } diff --git a/src/plugins/url.rs b/src/plugins/url.rs index b980d3e..9ce5a6a 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -90,7 +90,7 @@ impl Url { } Err(e) => { debug!("Bad response from {:?}: ({})", url, e); - return None; + None } } } @@ -126,18 +126,22 @@ impl Url { } impl Plugin for Url { - fn is_allowed(&self, _: &IrcClient, message: &Message) -> bool { + fn execute(&self, _: &IrcClient, message: &Message) -> ExecutionStatus { match message.command { - Command::PRIVMSG(_, ref msg) => RE.is_match(msg), - _ => false, + Command::PRIVMSG(_, ref msg) => if RE.is_match(msg) { + ExecutionStatus::RequiresThread + } else { + ExecutionStatus::Done + }, + _ => ExecutionStatus::Done, } } - fn execute(&self, server: &IrcClient, message: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), IrcError> { match message.command { Command::PRIVMSG(_, ref content) => { match self.url(content) { - Ok(title) => server.send_privmsg(&message.response_target().unwrap(), &title), + Ok(title) => client.send_privmsg(message.response_target().unwrap(), &title), Err(_) => Ok(()), } } @@ -145,13 +149,13 @@ impl Plugin for Url { } } - fn command(&self, server: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - server.send_notice(&command.source, + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { + client.send_notice(&command.source, "This Plugin does not implement any commands.") } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { - self.url(&command.tokens[0]).map_err(|e| String::from(e)) + self.url(&command.tokens[0]).map_err(String::from) } } -- cgit v1.2.3-70-g09d2 From 72f1411f1c72a9271c7d3993a3e307050e8d1b31 Mon Sep 17 00:00:00 2001 From: Jokler Date: Mon, 12 Feb 2018 20:31:56 +0100 Subject: Run latest rustfmt --- src/lib.rs | 74 +++++++++++++++++++++++++++---------------------- src/main.rs | 50 ++++++++++++++++----------------- src/plugin.rs | 18 ++++-------- src/plugins/currency.rs | 64 +++++++++++++++++++++++------------------- src/plugins/emoji.rs | 22 +++++++-------- src/plugins/keepnick.rs | 7 +++-- src/plugins/url.rs | 38 +++++++++++-------------- 7 files changed, 137 insertions(+), 136 deletions(-) (limited to 'src/main.rs') diff --git a/src/lib.rs b/src/lib.rs index 87c06c3..da14277 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -#![cfg_attr(feature="clippy", feature(plugin))] -#![cfg_attr(feature="clippy", plugin(clippy))] +#![cfg_attr(feature = "clippy", feature(plugin))] +#![cfg_attr(feature = "clippy", plugin(clippy))] //! Frippy is an IRC bot that runs plugins on each message //! received. @@ -30,11 +30,11 @@ //! which might be of interest. #[macro_use] -extern crate log; +extern crate frippy_derive; #[macro_use] extern crate lazy_static; #[macro_use] -extern crate frippy_derive; +extern crate log; extern crate irc; @@ -70,7 +70,9 @@ impl Bot { /// let mut bot = Bot::new(); /// ``` pub fn new() -> Bot { - Bot { plugins: ThreadedPlugins::new() } + Bot { + plugins: ThreadedPlugins::new(), + } } /// Adds the [`Plugin`](plugin/trait.Plugin.html). @@ -103,10 +105,13 @@ impl Bot { self.plugins.remove(name) } - /// This connects the `Bot` to IRC and creates a task on the [`IrcReactor`](../irc/client/reactor/struct.IrcReactor.html) - /// which returns an Ok if the connection was cleanly closed and an Err if the connection was lost. + /// This connects the `Bot` to IRC and creates a task on the + /// [`IrcReactor`](../irc/client/reactor/struct.IrcReactor.html) + /// which returns an Ok if the connection was cleanly closed and + /// an Err if the connection was lost. /// - /// You need to run the [`IrcReactor`](../irc/client/reactor/struct.IrcReactor.html), so that the `Bot` + /// You need to run the [`IrcReactor`](../irc/client/reactor/struct.IrcReactor.html), + /// so that the `Bot` /// can actually do its work. /// /// # Examples @@ -151,11 +156,11 @@ impl Bot { } } -fn process_msg(server: &IrcClient, - mut plugins: ThreadedPlugins, - message: Message) - -> Result<(), IrcError> { - +fn process_msg( + server: &IrcClient, + mut plugins: ThreadedPlugins, + message: Message, +) -> Result<(), IrcError> { // Log any channels we join if let Command::JOIN(ref channel, _, _) = message.command { if message.source_nickname().unwrap() == server.current_nickname() { @@ -185,7 +190,9 @@ struct ThreadedPlugins { impl ThreadedPlugins { pub fn new() -> ThreadedPlugins { - ThreadedPlugins { plugins: HashMap::new() } + ThreadedPlugins { + plugins: HashMap::new(), + } } pub fn add(&mut self, plugin: T) { @@ -208,10 +215,11 @@ impl ThreadedPlugins { ExecutionStatus::Done => (), ExecutionStatus::Err(e) => error!("Error in {} - {}", name, e), ExecutionStatus::RequiresThread => { - - debug!("Spawning thread to execute {} with {}", - name, - message.to_string().replace("\r\n", "")); + debug!( + "Spawning thread to execute {} with {}", + name, + message.to_string().replace("\r\n", "") + ); // Clone everything before the move - the server uses an Arc internally too let plugin = Arc::clone(&plugin); @@ -229,11 +237,11 @@ impl ThreadedPlugins { } } - pub fn handle_command(&mut self, - server: &IrcClient, - mut command: PluginCommand) - -> Result<(), IrcError> { - + pub fn handle_command( + &mut self, + server: &IrcClient, + mut command: PluginCommand, + ) -> Result<(), IrcError> { if !command.tokens.iter().any(|s| !s.is_empty()) { let help = format!("Use \"{} help\" to get help", server.current_nickname()); return server.send_notice(&command.source, &help); @@ -241,7 +249,6 @@ impl ThreadedPlugins { // Check if the command is for this plugin if let Some(plugin) = self.plugins.get(&command.tokens[0].to_lowercase()) { - // The first token contains the name of the plugin let name = command.tokens.remove(0); @@ -251,18 +258,19 @@ impl ThreadedPlugins { let server = server.clone(); let plugin = Arc::clone(plugin); spawn(move || { - if let Err(e) = plugin.command(&server, command) { - error!("Error in {} command - {}", name, e); - }; - }); + if let Err(e) = plugin.command(&server, command) { + error!("Error in {} command - {}", name, e); + }; + }); Ok(()) - } else { - let help = format!("\"{} {}\" is not a command, \ - try \"{0} help\" instead.", - server.current_nickname(), - command.tokens[0]); + let help = format!( + "\"{} {}\" is not a command, \ + try \"{0} help\" instead.", + server.current_nickname(), + command.tokens[0] + ); server.send_notice(&command.source, &help) } diff --git a/src/main.rs b/src/main.rs index 7df8b3b..9a96791 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,15 @@ -#![cfg_attr(feature="clippy", feature(plugin))] -#![cfg_attr(feature="clippy", plugin(clippy))] +#![cfg_attr(feature = "clippy", feature(plugin))] +#![cfg_attr(feature = "clippy", plugin(clippy))] extern crate frippy; -extern crate time; -extern crate irc; extern crate glob; +extern crate irc; +extern crate time; #[macro_use] extern crate log; -use log::{Record, Level, LevelFilter, Metadata}; +use log::{Level, LevelFilter, Metadata, Record}; use irc::client::reactor::IrcReactor; use glob::glob; @@ -27,16 +27,20 @@ impl log::Log for Logger { fn log(&self, record: &Record) { if self.enabled(record.metadata()) { if record.metadata().level() >= Level::Debug { - println!("[{}]({}) {} -> {}", - time::now().rfc822(), - record.level(), - record.target(), - record.args()); + println!( + "[{}]({}) {} -> {}", + time::now().rfc822(), + record.level(), + record.target(), + record.args() + ); } else { - println!("[{}]({}) {}", - time::now().rfc822(), - record.level(), - record.args()); + println!( + "[{}]({}) {}", + time::now().rfc822(), + record.level(), + record.args() + ); } } } @@ -47,12 +51,11 @@ impl log::Log for Logger { static LOGGER: Logger = Logger; fn main() { - log::set_max_level(if cfg!(debug_assertions) { - LevelFilter::Debug - } else { - LevelFilter::Info - }); + LevelFilter::Debug + } else { + LevelFilter::Info + }); log::set_logger(&LOGGER).unwrap(); @@ -82,14 +85,10 @@ fn main() { // Open a connection and add work for each config for config in configs { - 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::>()); + disabled_plugins = Some(disabled.split(',').map(|p| p.trim()).collect::>()); } } @@ -109,7 +108,8 @@ fn main() { } } - bot.connect(&mut reactor, &config).expect("Failed to connect"); + bot.connect(&mut reactor, &config) + .expect("Failed to connect"); } // Run the bots until they throw an error - an error could be loss of connection diff --git a/src/plugin.rs b/src/plugin.rs index e343a2c..a67d68f 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -30,7 +30,7 @@ pub trait Plugin: PluginName + Send + Sync + fmt::Debug { fn evaluate(&self, server: &IrcClient, command: PluginCommand) -> Result; } -/// `PluginName` is required by [`Plugin`](trait.Plugin.html). +/// `PluginName` is required by [`Plugin`](trait.Plugin.html). /// /// To implement it simply add `#[derive(PluginName)]` /// above the definition of the struct. @@ -64,25 +64,19 @@ impl PluginCommand { /// if it contains a [`PRIVMSG`](../../irc/proto/command/enum.Command.html#variant.PRIVMSG) /// that starts with the provided `nick`. pub fn from(nick: &str, message: &Message) -> Option { - // Get the actual message out of PRIVMSG if let Command::PRIVMSG(_, ref content) = message.command { - // Split content by spaces and filter empty tokens let mut tokens: Vec = content.split(' ').map(ToOwned::to_owned).collect(); // Commands start with our name if tokens[0].to_lowercase().starts_with(nick) { - // Remove the bot's name from the first token tokens[0].drain(..nick.len()); // We assume that only ':' and ',' are used as suffixes on IRC // If there are any other chars we assume that it is not ment for the bot - tokens[0] = tokens[0] - .chars() - .filter(|&c| !":,".contains(c)) - .collect(); + tokens[0] = tokens[0].chars().filter(|&c| !":,".contains(c)).collect(); if !tokens[0].is_empty() { return None; } @@ -91,10 +85,10 @@ impl PluginCommand { tokens.remove(0); Some(PluginCommand { - source: message.source_nickname().unwrap().to_string(), - target: message.response_target().unwrap().to_string(), - tokens: tokens, - }) + source: message.source_nickname().unwrap().to_string(), + target: message.response_target().unwrap().to_string(), + tokens: tokens, + }) } else { None } diff --git a/src/plugins/currency.rs b/src/plugins/currency.rs index 21330a1..393df9b 100644 --- a/src/plugins/currency.rs +++ b/src/plugins/currency.rs @@ -34,7 +34,6 @@ macro_rules! try_option { impl<'a> ConvertionRequest<'a> { fn send(&self) -> Option { - let response = Client::new() .get("https://api.fixer.io/latest") .form(&[("base", self.source)]) @@ -49,7 +48,6 @@ impl<'a> ConvertionRequest<'a> { let convertion_rates: Result = serde_json::from_str(&body); match convertion_rates { Ok(convertion_rates) => { - let rates: &Value = try_option!(convertion_rates.get("rates")); let target_rate: &Value = try_option!(rates.get(self.target.to_uppercase())); @@ -68,14 +66,15 @@ impl Currency { Currency {} } - fn eval_command<'a>(&self, - tokens: &'a [String]) - -> Result, ParseFloatError> { + fn eval_command<'a>( + &self, + tokens: &'a [String], + ) -> Result, ParseFloatError> { Ok(ConvertionRequest { - value: tokens[0].parse()?, - source: &tokens[1], - target: &tokens[2], - }) + value: tokens[0].parse()?, + source: &tokens[1], + target: &tokens[2], + }) } fn convert(&self, client: &IrcClient, command: &mut PluginCommand) -> Result { @@ -92,33 +91,41 @@ impl Currency { match request.send() { Some(response) => { - let response = format!("{} {} => {:.4} {}", - request.value, - request.source.to_lowercase(), - response / 1.00000000, - request.target.to_lowercase()); + let response = format!( + "{} {} => {:.4} {}", + request.value, + request.source.to_lowercase(), + response / 1.00000000, + request.target.to_lowercase() + ); Ok(response) } - None => Err(String::from("An error occured during the conversion of the given currency")), + None => Err(String::from( + "An error occured during the conversion of the given currency", + )), } } fn help(&self, client: &IrcClient) -> String { - format!("usage: {} currency value from_currency to_currency\r\n\ - example: {0} currency 1.5 eur usd\r\n\ - available currencies: AUD, BGN, BRL, CAD, \ - CHF, CNY, CZK, DKK, GBP, HKD, HRK, HUF, \ - IDR, ILS, INR, JPY, KRW, MXN, MYR, NOK, \ - NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, \ - TRY, USD, ZAR", - client.current_nickname()) + format!( + "usage: {} currency value from_currency to_currency\r\n\ + example: {0} currency 1.5 eur usd\r\n\ + available currencies: AUD, BGN, BRL, CAD, \ + CHF, CNY, CZK, DKK, GBP, HKD, HRK, HUF, \ + IDR, ILS, INR, JPY, KRW, MXN, MYR, NOK, \ + NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, \ + TRY, USD, ZAR", + client.current_nickname() + ) } fn invalid_command(&self, client: &IrcClient) -> String { - format!("Incorrect Command. \ - Send \"{} currency help\" for help.", - client.current_nickname()) + format!( + "Incorrect Command. \ + Send \"{} currency help\" for help.", + client.current_nickname() + ) } } @@ -132,7 +139,6 @@ impl Plugin for Currency { } fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), IrcError> { - if command.tokens.is_empty() { return client.send_notice(&command.source, &self.invalid_command(client)); } @@ -142,11 +148,11 @@ impl Plugin for Currency { _ => match self.convert(client, &mut command) { Ok(msg) => client.send_privmsg(&command.target, &msg), Err(msg) => client.send_notice(&command.source, &msg), - } + }, } } - fn evaluate(&self, client: &IrcClient, mut command: PluginCommand) -> Result{ + fn evaluate(&self, client: &IrcClient, mut command: PluginCommand) -> Result { if command.tokens.is_empty() { return Err(self.invalid_command(client)); } diff --git a/src/plugins/emoji.rs b/src/plugins/emoji.rs index 02a31f8..fcb04d1 100644 --- a/src/plugins/emoji.rs +++ b/src/plugins/emoji.rs @@ -14,7 +14,6 @@ struct EmojiHandle { impl fmt::Display for EmojiHandle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let name = match unicode_names::name(self.symbol) { Some(sym) => sym.to_string().to_lowercase(), None => String::from("UNKNOWN"), @@ -52,7 +51,6 @@ impl Emoji { count: 0, }; - for c in string.chars() { if !self.is_emoji(&c) { continue; @@ -60,7 +58,6 @@ impl Emoji { if current.symbol == c { current.count += 1; - } else { if current.count > 0 { emojis.push(current); @@ -99,13 +96,12 @@ impl Emoji { impl Plugin for Emoji { fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus { match message.command { - Command::PRIVMSG(_, ref content) => { - match client.send_privmsg(message.response_target().unwrap(), - &self.emoji(content)) { - Ok(_) => ExecutionStatus::Done, - Err(e) => ExecutionStatus::Err(e), - } - } + Command::PRIVMSG(_, ref content) => match client + .send_privmsg(message.response_target().unwrap(), &self.emoji(content)) + { + Ok(_) => ExecutionStatus::Done, + Err(e) => ExecutionStatus::Err(e), + }, _ => ExecutionStatus::Done, } } @@ -115,8 +111,10 @@ impl Plugin for Emoji { } fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice(&command.source, - "This Plugin does not implement any commands.") + client.send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { diff --git a/src/plugins/keepnick.rs b/src/plugins/keepnick.rs index e973769..73f4893 100644 --- a/src/plugins/keepnick.rs +++ b/src/plugins/keepnick.rs @@ -29,7 +29,6 @@ impl KeepNick { Ok(_) => ExecutionStatus::Done, Err(e) => ExecutionStatus::Err(e), } - } else { ExecutionStatus::Done } @@ -51,8 +50,10 @@ impl Plugin for KeepNick { } fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice(&command.source, - "This Plugin does not implement any commands.") + client.send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { diff --git a/src/plugins/url.rs b/src/plugins/url.rs index 9ce5a6a..455aa4e 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -29,7 +29,7 @@ pub struct Url { impl Url { /// If a file is larger than `max_kib` KiB the download is stopped pub fn new(max_kib: usize) -> Url { - Url {max_kib: max_kib} + Url { max_kib: max_kib } } fn grep_url(&self, msg: &str) -> Option { @@ -44,10 +44,7 @@ impl Url { } fn download(&self, url: &str) -> Option { - let response = Client::new() - .get(url) - .header(Connection::close()) - .send(); + let response = Client::new().get(url).header(Connection::close()).send(); match response { Ok(mut response) => { @@ -81,10 +78,12 @@ impl Url { // Check if the file is too large to download if written > self.max_kib * 1024 { - debug!("Stopping download - File from {:?} is larger than {} KiB", url, self.max_kib); + debug!( + "Stopping download - File from {:?} is larger than {} KiB", + url, self.max_kib + ); return None; } - } Some(body) // once told me } @@ -98,15 +97,11 @@ impl Url { fn url(&self, text: &str) -> Result { let url = match self.grep_url(text) { Some(url) => url, - None => { - return Err("No Url was found.") - } + None => return Err("No Url was found."), }; - match self.download(&url) { Some(body) => { - let doc = Document::from(body.as_ref()); if let Some(title) = doc.find(Name("title")).next() { let title = title.children().next().unwrap(); @@ -115,12 +110,11 @@ impl Url { debug!("Text: {:?}", title_text); Ok(title_text) - } else { Err("No title was found.") } } - None => Err("Failed to download document.") + None => Err("Failed to download document."), } } } @@ -139,19 +133,19 @@ impl Plugin for Url { fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), IrcError> { match message.command { - Command::PRIVMSG(_, ref content) => { - match self.url(content) { - Ok(title) => client.send_privmsg(message.response_target().unwrap(), &title), - Err(_) => Ok(()), - } - } + Command::PRIVMSG(_, ref content) => match self.url(content) { + Ok(title) => client.send_privmsg(message.response_target().unwrap(), &title), + Err(_) => Ok(()), + }, _ => Ok(()), } } fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice(&command.source, - "This Plugin does not implement any commands.") + client.send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { -- cgit v1.2.3-70-g09d2 From db0f14a163a087261db7360a90d6ac70fb92e884 Mon Sep 17 00:00:00 2001 From: Jokler Date: Sat, 24 Feb 2018 17:18:09 +0100 Subject: Use r2d2 as a ConnectionPool for diesel --- Cargo.lock | 38 +++++++++++++++++++++++++++++++++ Cargo.toml | 17 ++++++++++++++- src/lib.rs | 4 ++++ src/main.rs | 19 +++++++++++------ src/plugins/factoids/database.rs | 46 ++++++++++++++++++++++++---------------- src/plugins/factoids/mod.rs | 20 ++++++++--------- 6 files changed, 109 insertions(+), 35 deletions(-) (limited to 'src/main.rs') diff --git a/Cargo.lock b/Cargo.lock index e2a0350..d36e32e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,11 @@ dependencies = [ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "antidote" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "backtrace" version = "0.3.5" @@ -391,6 +396,8 @@ dependencies = [ "irc 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2-diesel 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", "rlua 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -927,6 +934,25 @@ name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "r2d2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scheduled-thread-pool 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "r2d2-diesel" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "diesel 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.3.22" @@ -1044,6 +1070,14 @@ dependencies = [ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scoped-tls" version = "0.1.0" @@ -1483,6 +1517,7 @@ dependencies = [ [metadata] "checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" +"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2" "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" "checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4" @@ -1588,6 +1623,8 @@ dependencies = [ "checksum pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "378e941dbd392c101f2cb88097fa4d7167bc421d4b88de3ff7dbee503bc3233b" "checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9078ca6a8a5568ed142083bb2f7dc9295b69d16f867ddcc9849e51b17d8db46" +"checksum r2d2-diesel 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9c29bad92da76d02bc2c020452ebc3a3fe6fa74cfab91e711c43116e4fb1a3" "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" @@ -1601,6 +1638,7 @@ dependencies = [ "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" "checksum schannel 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "acece75e0f987c48863a6c792ec8b7d6c4177d4a027f8ccc72f849794f437016" +"checksum scheduled-thread-pool 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a2ff3fc5223829be817806c6441279c676e454cc7da608faf03b0ccc09d3889" "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" "checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" "checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" diff --git a/Cargo.toml b/Cargo.toml index bc49f61..7b7c6f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,14 @@ name = "frippy" doc = false [features] -mysql = ["diesel", "diesel_infer_schema", "diesel_migrations", "dotenv"] +mysql = [ + "diesel", + "diesel_infer_schema", + "diesel_migrations", + "dotenv", + "r2d2", + "r2d2-diesel" +] [dependencies] irc = "0.13.4" @@ -52,6 +59,14 @@ version = "1.1.0" optional = true features = ["mysql"] +[dependencies.r2d2] +version = "0.8.2" +optional = true + +[dependencies.r2d2-diesel] +version = "1.0.0" +optional = true + [dependencies.dotenv] version = "0.11.0" optional = true diff --git a/src/lib.rs b/src/lib.rs index bd2e302..aeead16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,10 @@ #[cfg(feature = "mysql")] #[macro_use] extern crate diesel; +#[cfg(feature = "mysql")] +extern crate r2d2; +#[cfg(feature = "mysql")] +extern crate r2d2_diesel; #[macro_use] extern crate frippy_derive; diff --git a/src/main.rs b/src/main.rs index cb4e384..6605693 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,10 @@ extern crate time; extern crate diesel_migrations; #[cfg(feature = "mysql")] extern crate diesel; +#[cfg(feature = "mysql")] +extern crate r2d2; +#[cfg(feature = "mysql")] +extern crate r2d2_diesel; #[macro_use] extern crate log; @@ -119,13 +123,16 @@ fn main() { #[cfg(feature = "mysql")] { if let Some(url) = mysql_url { - use diesel; - use diesel::Connection; - match diesel::mysql::MysqlConnection::establish(url) { - Ok(conn) => { - match embedded_migrations::run(&conn) { + use diesel::MysqlConnection; + use r2d2; + use r2d2_diesel::ConnectionManager; + + let manager = ConnectionManager::::new(url.clone()); + match r2d2::Pool::builder().build(manager) { + Ok(pool) => { + match embedded_migrations::run(&*pool.get().expect("Failed to get connection")) { Ok(_) => { - bot.add_plugin(plugins::Factoids::new(conn)); + bot.add_plugin(plugins::Factoids::new(pool)); info!("Connected to MySQL server") } Err(e) => { diff --git a/src/plugins/factoids/database.rs b/src/plugins/factoids/database.rs index cb5ef29..f003419 100644 --- a/src/plugins/factoids/database.rs +++ b/src/plugins/factoids/database.rs @@ -5,9 +5,12 @@ use std::collections::HashMap; #[cfg(feature = "mysql")] use diesel::prelude::*; - #[cfg(feature = "mysql")] use diesel::mysql::MysqlConnection; +#[cfg(feature = "mysql")] +use r2d2::Pool; +#[cfg(feature = "mysql")] +use r2d2_diesel::ConnectionManager; use chrono::NaiveDateTime; @@ -40,15 +43,15 @@ pub struct NewFactoid<'a> { pub trait Database: Send { - fn insert(&mut self, factoid: &NewFactoid) -> DbResponse; - fn get(&self, name: &str, idx: i32) -> Option; - fn delete(&mut self, name: &str, idx: i32) -> DbResponse; - fn count(&self, name: &str) -> Result; + fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse; + fn get_factoid(&self, name: &str, idx: i32) -> Option; + fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse; + fn count_factoids(&self, name: &str) -> Result; } // HashMap impl Database for HashMap<(String, i32), Factoid> { - fn insert(&mut self, factoid: &NewFactoid) -> DbResponse { + fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse { let factoid = Factoid { name: String::from(factoid.name), idx: factoid.idx, @@ -64,18 +67,18 @@ impl Database for HashMap<(String, i32), Factoid> { } } - fn get(&self, name: &str, idx: i32) -> Option { + fn get_factoid(&self, name: &str, idx: i32) -> Option { self.get(&(String::from(name), idx)).map(|f| f.clone()) } - fn delete(&mut self, name: &str, idx: i32) -> DbResponse { + fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse { match self.remove(&(String::from(name), idx)) { Some(_) => DbResponse::Success, None => DbResponse::Failed("Factoid not found"), } } - fn count(&self, name: &str) -> Result { + fn count_factoids(&self, name: &str) -> Result { Ok(self.iter() .filter(|&(&(ref n, _), _)| n == name) .count() as i32) @@ -98,12 +101,14 @@ mod mysql { } #[cfg(feature = "mysql")] -impl Database for MysqlConnection { - fn insert(&mut self, factoid: &NewFactoid) -> DbResponse { +impl Database for Pool> { + fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse { use diesel; + + let conn = &*self.get().expect("Failed to get connection"); match diesel::insert_into(factoids::table) .values(factoid) - .execute(self) { + .execute(conn) { Ok(_) => DbResponse::Success, Err(e) => { error!("DB Insertion Error: {}", e); @@ -112,8 +117,9 @@ impl Database for MysqlConnection { } } - fn get(&self, name: &str, idx: i32) -> Option { - match factoids::table.find((name, idx)).first(self) { + fn get_factoid(&self, name: &str, idx: i32) -> Option { + let conn = &*self.get().expect("Failed to get connection"); + match factoids::table.find((name, idx)).first(conn) { Ok(f) => Some(f), Err(e) => { error!("DB Count Error: {}", e); @@ -122,13 +128,15 @@ impl Database for MysqlConnection { } } - fn delete(&mut self, name: &str, idx: i32) -> DbResponse { + fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse { use diesel; use self::factoids::columns; + + let conn = &*self.get().expect("Failed to get connection"); match diesel::delete(factoids::table .filter(columns::name.eq(name)) .filter(columns::idx.eq(idx))) - .execute(self) { + .execute(conn) { Ok(v) => { if v > 0 { DbResponse::Success @@ -143,11 +151,13 @@ impl Database for MysqlConnection { } } - fn count(&self, name: &str) -> Result { + fn count_factoids(&self, name: &str) -> Result { + + let conn = &*self.get().expect("Failed to get connection"); let count: Result = factoids::table .filter(factoids::columns::name.eq(name)) .count() - .first(self); + .first(conn); match count { Ok(c) => Ok(c as i32), diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index 49ace10..b662d22 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -39,7 +39,7 @@ impl Factoids { } fn create_factoid(&self, name: &str, content: &str, author: &str) -> Result<&str, &str> { - let count = try_lock!(self.factoids).count(&name)?; + let count = try_lock!(self.factoids).count_factoids(&name)?; let tm = time::now().to_timespec(); let factoid = database::NewFactoid { @@ -50,7 +50,7 @@ impl Factoids { created: NaiveDateTime::from_timestamp(tm.sec, tm.nsec as u32), }; - match try_lock!(self.factoids).insert(&factoid) { + match try_lock!(self.factoids).insert_factoid(&factoid) { DbResponse::Success => Ok("Successfully added"), DbResponse::Failed(e) => Err(e), } @@ -93,12 +93,12 @@ impl Factoids { } let name = command.tokens.remove(0); - let count = match try_lock!(self.factoids).count(&name) { + let count = match try_lock!(self.factoids).count_factoids(&name) { Ok(c) => c, Err(e) => return client.send_notice(&command.source, e), }; - match try_lock!(self.factoids).delete(&name, count - 1) { + match try_lock!(self.factoids).delete_factoid(&name, count - 1) { DbResponse::Success => client.send_notice(&command.source, "Successfully removed"), DbResponse::Failed(e) => client.send_notice(&command.source, &e), } @@ -110,7 +110,7 @@ impl Factoids { 0 => return self.invalid_command(client, command), 1 => { let name = &command.tokens[0]; - let count = match try_lock!(self.factoids).count(name) { + let count = match try_lock!(self.factoids).count_factoids(name) { Ok(c) => c, Err(e) => return client.send_notice(&command.source, e), }; @@ -132,7 +132,7 @@ impl Factoids { } }; - let factoid = match try_lock!(self.factoids).get(name, idx) { + let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { Some(v) => v, None => { return client.send_notice(&command.source, @@ -152,7 +152,7 @@ impl Factoids { 0 => self.invalid_command(client, command), 1 => { let name = &command.tokens[0]; - let count = match try_lock!(self.factoids).count(name) { + let count = match try_lock!(self.factoids).count_factoids(name) { Ok(c) => c, Err(e) => return client.send_notice(&command.source, e), }; @@ -176,7 +176,7 @@ impl Factoids { Err(_) => return client.send_notice(&command.source, "Invalid index"), }; - let factoid = match try_lock!(self.factoids).get(name, idx) { + let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { Some(v) => v, None => { return client.send_notice(&command.source, @@ -204,12 +204,12 @@ impl Factoids { } else { let name = command.tokens.remove(0); - let count = match try_lock!(self.factoids).count(&name) { + let count = match try_lock!(self.factoids).count_factoids(&name) { Ok(c) => c, Err(e) => return client.send_notice(&command.source, e), }; - let factoid = match try_lock!(self.factoids).get(&name, count - 1) { + let factoid = match try_lock!(self.factoids).get_factoid(&name, count - 1) { Some(v) => v.content, None if error => return self.invalid_command(client, &command), None => return Ok(()), -- cgit v1.2.3-70-g09d2 From ce2db228aac1c0114bcac30bb6c01212faca025a Mon Sep 17 00:00:00 2001 From: Jokler Date: Sat, 24 Feb 2018 20:13:32 +0100 Subject: Run rustfmt and clippy --- Cargo.lock | 6 +- src/lib.rs | 2 +- src/main.rs | 34 +++++------- src/plugin.rs | 2 +- src/plugins/emoji.rs | 2 +- src/plugins/factoids/database.rs | 31 +++++------ src/plugins/factoids/mod.rs | 117 ++++++++++++++++++++------------------- src/plugins/keepnick.rs | 2 +- src/plugins/tell.rs | 11 ++-- src/plugins/url.rs | 1 - src/utils.rs | 13 ++--- 11 files changed, 111 insertions(+), 110 deletions(-) (limited to 'src/main.rs') diff --git a/Cargo.lock b/Cargo.lock index d36e32e..92cf4ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,7 +721,7 @@ dependencies = [ [[package]] name = "mime_guess" -version = "2.0.0-alpha.3" +version = "2.0.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1024,7 +1024,7 @@ dependencies = [ "hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "libflate 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 2.0.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1599,7 +1599,7 @@ dependencies = [ "checksum migrations_internals 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd916de6df9ac7e811e7e1ac28e0abfebe5205f3b29a7bda9ec8a41ee980a4eb" "checksum migrations_macros 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a550cfd76f6cfdf15a7b541893d7c79b68277b0b309f12179211a373a56e617" "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 mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130ea3c9c1b65dba905ab5a4d9ac59234a9585c24d135f264e187fe7336febbd" "checksum mio 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "7da01a5e23070d92d99b1ecd1cd0af36447c6fd44b0fe283c2db199fa136724f" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum mysqlclient-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "879ce08e38739c54d87b7f8332a476004fe2a095f40a142a36f889779d9942b7" diff --git a/src/lib.rs b/src/lib.rs index aeead16..cc6e921 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,8 +44,8 @@ extern crate lazy_static; #[macro_use] extern crate log; -extern crate irc; extern crate chrono; +extern crate irc; extern crate time; pub mod plugin; diff --git a/src/main.rs b/src/main.rs index 6605693..511fe09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,12 @@ extern crate glob; extern crate irc; extern crate time; +#[cfg(feature = "mysql")] +extern crate diesel; #[cfg(feature = "mysql")] #[macro_use] extern crate diesel_migrations; #[cfg(feature = "mysql")] -extern crate diesel; -#[cfg(feature = "mysql")] extern crate r2d2; #[cfg(feature = "mysql")] extern crate r2d2_diesel; @@ -103,10 +103,7 @@ fn main() { let mut mysql_url = 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::>()); + disabled_plugins = Some(disabled.split(',').map(|p| p.trim()).collect::>()); } mysql_url = options.get("mysql_url"); @@ -129,18 +126,18 @@ fn main() { let manager = ConnectionManager::::new(url.clone()); match r2d2::Pool::builder().build(manager) { - Ok(pool) => { - match embedded_migrations::run(&*pool.get().expect("Failed to get connection")) { - Ok(_) => { - bot.add_plugin(plugins::Factoids::new(pool)); - info!("Connected to MySQL server") - } - Err(e) => { - bot.add_plugin(plugins::Factoids::new(HashMap::new())); - error!("Failed to run migrations: {}", e); - } + Ok(pool) => match embedded_migrations::run(&*pool.get() + .expect("Failed to get connection")) + { + Ok(_) => { + bot.add_plugin(plugins::Factoids::new(pool)); + info!("Connected to MySQL server") + } + Err(e) => { + bot.add_plugin(plugins::Factoids::new(HashMap::new())); + error!("Failed to run migrations: {}", e); } - } + }, Err(e) => error!("Failed to connect to database: {}", e), } } else { @@ -149,13 +146,12 @@ fn main() { } #[cfg(not(feature = "mysql"))] { - if let Some(_) = mysql_url { + if mysql_url.is_some() { error!("frippy was not built with the mysql feature") } bot.add_plugin(plugins::Factoids::new(HashMap::new())); } - if let Some(disabled_plugins) = disabled_plugins { for name in disabled_plugins { if bot.remove_plugin(name).is_none() { diff --git a/src/plugin.rs b/src/plugin.rs index a67d68f..f33fa80 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -11,7 +11,7 @@ pub enum ExecutionStatus { /// The [`Plugin`](trait.Plugin.html) does not need to do any more work on this [`Message`](../../irc/proto/message/struct.Message.html). Done, /// An error occured during the execution. - Err(IrcError), + Err(Box), /// The execution needs to be done by [`execute_threaded()`](trait.Plugin.html#tymethod.execute_threaded). RequiresThread, } diff --git a/src/plugins/emoji.rs b/src/plugins/emoji.rs index fcb04d1..a4276f4 100644 --- a/src/plugins/emoji.rs +++ b/src/plugins/emoji.rs @@ -100,7 +100,7 @@ impl Plugin for Emoji { .send_privmsg(message.response_target().unwrap(), &self.emoji(content)) { Ok(_) => ExecutionStatus::Done, - Err(e) => ExecutionStatus::Err(e), + Err(e) => ExecutionStatus::Err(Box::new(e)), }, _ => ExecutionStatus::Done, } diff --git a/src/plugins/factoids/database.rs b/src/plugins/factoids/database.rs index f003419..ccebfee 100644 --- a/src/plugins/factoids/database.rs +++ b/src/plugins/factoids/database.rs @@ -32,7 +32,7 @@ pub struct Factoid { #[cfg(feature = "mysql")] use self::mysql::factoids; #[cfg_attr(feature = "mysql", derive(Insertable))] -#[cfg_attr(feature = "mysql", table_name="factoids")] +#[cfg_attr(feature = "mysql", table_name = "factoids")] pub struct NewFactoid<'a> { pub name: &'a str, pub idx: i32, @@ -41,7 +41,6 @@ pub struct NewFactoid<'a> { pub created: NaiveDateTime, } - pub trait Database: Send { fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse; fn get_factoid(&self, name: &str, idx: i32) -> Option; @@ -60,7 +59,7 @@ impl Database for HashMap<(String, i32), Factoid> { created: factoid.created, }; - let name = String::from(factoid.name.clone()); + let name = factoid.name.clone(); match self.insert((name, factoid.idx), factoid) { None => DbResponse::Success, Some(_) => DbResponse::Failed("Factoid was overwritten"), @@ -68,7 +67,7 @@ impl Database for HashMap<(String, i32), Factoid> { } fn get_factoid(&self, name: &str, idx: i32) -> Option { - self.get(&(String::from(name), idx)).map(|f| f.clone()) + self.get(&(String::from(name), idx)).cloned() } fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse { @@ -79,9 +78,7 @@ impl Database for HashMap<(String, i32), Factoid> { } fn count_factoids(&self, name: &str) -> Result { - Ok(self.iter() - .filter(|&(&(ref n, _), _)| n == name) - .count() as i32) + Ok(self.iter().filter(|&(&(ref n, _), _)| n == name).count() as i32) } } @@ -107,8 +104,9 @@ impl Database for Pool> { let conn = &*self.get().expect("Failed to get connection"); match diesel::insert_into(factoids::table) - .values(factoid) - .execute(conn) { + .values(factoid) + .execute(conn) + { Ok(_) => DbResponse::Success, Err(e) => { error!("DB Insertion Error: {}", e); @@ -124,7 +122,7 @@ impl Database for Pool> { Err(e) => { error!("DB Count Error: {}", e); None - }, + } } } @@ -133,10 +131,12 @@ impl Database for Pool> { use self::factoids::columns; let conn = &*self.get().expect("Failed to get connection"); - match diesel::delete(factoids::table - .filter(columns::name.eq(name)) - .filter(columns::idx.eq(idx))) - .execute(conn) { + match diesel::delete( + factoids::table + .filter(columns::name.eq(name)) + .filter(columns::idx.eq(idx)), + ).execute(conn) + { Ok(v) => { if v > 0 { DbResponse::Success @@ -152,7 +152,6 @@ impl Database for Pool> { } fn count_factoids(&self, name: &str) -> Result { - let conn = &*self.get().expect("Failed to get connection"); let count: Result = factoids::table .filter(factoids::columns::name.eq(name)) @@ -164,7 +163,7 @@ impl Database for Pool> { Err(e) => { error!("DB Count Error: {}", e); Err("Database Error") - }, + } } } } diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index b662d22..c3d19b0 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -35,25 +35,27 @@ macro_rules! try_lock { impl Factoids { pub fn new(db: T) -> Factoids { - Factoids { factoids: Mutex::new(db) } + Factoids { + factoids: Mutex::new(db), + } } fn create_factoid(&self, name: &str, content: &str, author: &str) -> Result<&str, &str> { - let count = try_lock!(self.factoids).count_factoids(&name)?; - let tm = time::now().to_timespec(); - - let factoid = database::NewFactoid { - name: name, - idx: count, - content: content, - author: author, - created: NaiveDateTime::from_timestamp(tm.sec, tm.nsec as u32), - }; + let count = try_lock!(self.factoids).count_factoids(name)?; + let tm = time::now().to_timespec(); + + let factoid = database::NewFactoid { + name: name, + idx: count, + content: content, + author: author, + created: NaiveDateTime::from_timestamp(tm.sec, tm.nsec as u32), + }; - match try_lock!(self.factoids).insert_factoid(&factoid) { - DbResponse::Success => Ok("Successfully added"), - DbResponse::Failed(e) => Err(e), - } + match try_lock!(self.factoids).insert_factoid(&factoid) { + DbResponse::Success => Ok("Successfully added"), + DbResponse::Failed(e) => Err(e), + } } fn add(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<(), IrcError> { @@ -70,7 +72,11 @@ impl Factoids { } } - fn from_url(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<(), IrcError> { + fn save_from_url( + &self, + client: &IrcClient, + command: &mut PluginCommand, + ) -> Result<(), IrcError> { if command.tokens.len() < 2 { return self.invalid_command(client, command); } @@ -100,12 +106,11 @@ impl Factoids { match try_lock!(self.factoids).delete_factoid(&name, count - 1) { DbResponse::Success => client.send_notice(&command.source, "Successfully removed"), - DbResponse::Failed(e) => client.send_notice(&command.source, &e), + DbResponse::Failed(e) => client.send_notice(&command.source, e), } } fn get(&self, client: &IrcClient, command: &PluginCommand) -> Result<(), IrcError> { - let (name, idx) = match command.tokens.len() { 0 => return self.invalid_command(client, command), 1 => { @@ -135,19 +140,17 @@ impl Factoids { let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { Some(v) => v, None => { - return client.send_notice(&command.source, - &format!("{}~{} does not exist", name, idx)) + return client + .send_notice(&command.source, &format!("{}~{} does not exist", name, idx)) } }; let message = factoid.content.replace("\n", "|").replace("\r", ""); - client.send_privmsg(&command.target, - &format!("{}: {}", factoid.name, message)) + client.send_privmsg(&command.target, &format!("{}: {}", factoid.name, message)) } fn info(&self, client: &IrcClient, command: &PluginCommand) -> Result<(), IrcError> { - match command.tokens.len() { 0 => self.invalid_command(client, command), 1 => { @@ -159,14 +162,12 @@ impl Factoids { match count { 0 => client.send_notice(&command.source, &format!("{} does not exist", name)), - 1 => { - client.send_privmsg(&command.target, - &format!("There is 1 version of {}", name)) - } - _ => { - client.send_privmsg(&command.target, - &format!("There are {} versions of {}", count, name)) - } + 1 => client + .send_privmsg(&command.target, &format!("There is 1 version of {}", name)), + _ => client.send_privmsg( + &command.target, + &format!("There are {} versions of {}", count, name), + ), } } _ => { @@ -179,29 +180,32 @@ impl Factoids { let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { Some(v) => v, None => { - return client.send_notice(&command.source, - &format!("{}~{} does not exist", name, idx)) + return client.send_notice( + &command.source, + &format!("{}~{} does not exist", name, idx), + ) } }; - client.send_privmsg(&command.target, - &format!("{}: Added by {} at {} UTC", - name, - factoid.author, - factoid.created)) + client.send_privmsg( + &command.target, + &format!( + "{}: Added by {} at {} UTC", + name, factoid.author, factoid.created + ), + ) } - } } - fn exec(&self, - client: &IrcClient, - mut command: PluginCommand, - error: bool) - -> Result<(), IrcError> { + fn exec( + &self, + client: &IrcClient, + mut command: PluginCommand, + error: bool, + ) -> Result<(), IrcError> { if command.tokens.len() < 1 { self.invalid_command(client, &command) - } else { let name = command.tokens.remove(0); let count = match try_lock!(self.factoids).count_factoids(&name) { @@ -215,10 +219,10 @@ impl Factoids { 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(">") { + if factoid.starts_with('>') { factoid } else { match self.run_lua(&name, &factoid, &command) { @@ -234,12 +238,12 @@ impl Factoids { } } - fn run_lua(&self, - name: &str, - code: &str, - command: &PluginCommand) - -> Result { - + fn run_lua( + &self, + name: &str, + code: &str, + command: &PluginCommand, + ) -> Result { let args = command .tokens .iter() @@ -295,7 +299,6 @@ impl Plugin for Factoids { }; self.exec(client, c, false) - } else { Ok(()) } @@ -309,7 +312,7 @@ impl Plugin for Factoids { let sub_command = command.tokens.remove(0); match sub_command.as_ref() { "add" => self.add(client, &mut command), - "fromurl" => self.from_url(client, &mut command), + "fromurl" => self.save_from_url(client, &mut command), "remove" => self.remove(client, &mut command), "get" => self.get(client, &command), "info" => self.info(client, &command), @@ -319,7 +322,9 @@ impl Plugin for Factoids { } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { - Err(String::from("Evaluation of commands is not implemented for Factoids at this time")) + Err(String::from( + "Evaluation of commands is not implemented for Factoids at this time", + )) } } diff --git a/src/plugins/keepnick.rs b/src/plugins/keepnick.rs index 73f4893..bdabd90 100644 --- a/src/plugins/keepnick.rs +++ b/src/plugins/keepnick.rs @@ -27,7 +27,7 @@ impl KeepNick { info!("Trying to switch nick from {} to {}", client_nick, cfg_nick); match client.send(Command::NICK(cfg_nick)) { Ok(_) => ExecutionStatus::Done, - Err(e) => ExecutionStatus::Err(e), + Err(e) => ExecutionStatus::Err(Box::new(e)), } } else { ExecutionStatus::Done diff --git a/src/plugins/tell.rs b/src/plugins/tell.rs index 34d7cf8..89d91f2 100644 --- a/src/plugins/tell.rs +++ b/src/plugins/tell.rs @@ -61,7 +61,9 @@ impl Tell { }; let mut tells = try_lock!(self.tells); - let tell_messages = tells.entry(receiver).or_insert(Vec::with_capacity(3)); + let tell_messages = tells + .entry(receiver) + .or_insert_with(|| Vec::with_capacity(3)); (*tell_messages).push(tell); Ok("Got it!") @@ -75,7 +77,7 @@ impl Tell { receiver, &format!("Tell from {}: {}", tell.sender, tell.message), ) { - return ExecutionStatus::Err(e); + return ExecutionStatus::Err(Box::new(e)); } debug!( "Sent {:?} from {:?} to {:?}", @@ -107,8 +109,9 @@ impl Tell { impl Plugin for Tell { fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus { match message.command { - Command::JOIN(_, _, _) => self.send_tell(client, message.source_nickname().unwrap()), - Command::PRIVMSG(_, _) => self.send_tell(client, message.source_nickname().unwrap()), + Command::JOIN(_, _, _) | Command::PRIVMSG(_, _) => { + self.send_tell(client, message.source_nickname().unwrap()) + } _ => ExecutionStatus::Done, } } diff --git a/src/plugins/url.rs b/src/plugins/url.rs index df4fdf2..52f92d8 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -44,7 +44,6 @@ impl Url { None => return Err("No Url was found."), }; - match utils::download(self.max_kib, &url) { Some(body) => { let doc = Document::from(body.as_ref()); diff --git a/src/utils.rs b/src/utils.rs index 59a5a54..0b965c8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,10 +7,7 @@ use self::reqwest::Client; use self::reqwest::header::Connection; pub fn download(max_kib: usize, url: &str) -> Option { - let response = Client::new() - .get(url) - .header(Connection::close()) - .send(); + let response = Client::new().get(url).header(Connection::close()).send(); match response { Ok(mut response) => { @@ -44,16 +41,18 @@ pub fn download(max_kib: usize, url: &str) -> Option { // Check if the file is too large to download if written > max_kib * 1024 { - debug!("Stopping download - File from {:?} is larger than {} KiB", url, max_kib); + debug!( + "Stopping download - File from {:?} is larger than {} KiB", + url, max_kib + ); return None; } - } Some(body) } Err(e) => { debug!("Bad response from {:?}: ({})", url, e); - return None; + None } } } -- cgit v1.2.3-70-g09d2 From 92668ad4c53edcc1a317a16aa5ea30ca502f54ee Mon Sep 17 00:00:00 2001 From: Jokler Date: Sun, 25 Feb 2018 18:54:16 +0100 Subject: Add Mysql as a possible database to the Tell plugin --- migrations/2018-02-25-154922_create_tells/down.sql | 1 + migrations/2018-02-25-154922_create_tells/up.sql | 7 + src/main.rs | 10 +- src/plugins/factoids/database.rs | 15 ++- src/plugins/factoids/mod.rs | 2 +- src/plugins/tell.rs | 143 -------------------- src/plugins/tell/database.rs | 143 ++++++++++++++++++++ src/plugins/tell/mod.rs | 144 +++++++++++++++++++++ 8 files changed, 313 insertions(+), 152 deletions(-) create mode 100644 migrations/2018-02-25-154922_create_tells/down.sql create mode 100644 migrations/2018-02-25-154922_create_tells/up.sql delete mode 100644 src/plugins/tell.rs create mode 100644 src/plugins/tell/database.rs create mode 100644 src/plugins/tell/mod.rs (limited to 'src/main.rs') diff --git a/migrations/2018-02-25-154922_create_tells/down.sql b/migrations/2018-02-25-154922_create_tells/down.sql new file mode 100644 index 0000000..73c9b9b --- /dev/null +++ b/migrations/2018-02-25-154922_create_tells/down.sql @@ -0,0 +1 @@ +DROP TABLE tells diff --git a/migrations/2018-02-25-154922_create_tells/up.sql b/migrations/2018-02-25-154922_create_tells/up.sql new file mode 100644 index 0000000..9de037c --- /dev/null +++ b/migrations/2018-02-25-154922_create_tells/up.sql @@ -0,0 +1,7 @@ +CREATE TABLE tells ( + id SERIAL PRIMARY KEY, + sender VARCHAR(32) NOT NULL, + receiver VARCHAR(32) NOT NULL, + time TIMESTAMP NOT NULL, + message VARCHAR(512) NOT NULL +) diff --git a/src/main.rs b/src/main.rs index 511fe09..ec04d33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,8 @@ extern crate r2d2_diesel; #[macro_use] extern crate log; +#[cfg(feature = "mysql")] +use std::sync::Arc; use std::collections::HashMap; use log::{Level, LevelFilter, Metadata, Record}; @@ -115,7 +117,6 @@ fn main() { bot.add_plugin(plugins::Emoji::new()); bot.add_plugin(plugins::Currency::new()); bot.add_plugin(plugins::KeepNick::new()); - bot.add_plugin(plugins::Tell::new()); #[cfg(feature = "mysql")] { @@ -130,11 +131,14 @@ fn main() { .expect("Failed to get connection")) { Ok(_) => { - bot.add_plugin(plugins::Factoids::new(pool)); + let pool = Arc::new(pool); + bot.add_plugin(plugins::Factoids::new(pool.clone())); + bot.add_plugin(plugins::Tell::new(pool.clone())); info!("Connected to MySQL server") } Err(e) => { bot.add_plugin(plugins::Factoids::new(HashMap::new())); + bot.add_plugin(plugins::Tell::new(HashMap::new())); error!("Failed to run migrations: {}", e); } }, @@ -142,6 +146,7 @@ fn main() { } } else { bot.add_plugin(plugins::Factoids::new(HashMap::new())); + bot.add_plugin(plugins::Tell::new(HashMap::new())); } } #[cfg(not(feature = "mysql"))] @@ -150,6 +155,7 @@ fn main() { error!("frippy was not built with the mysql feature") } bot.add_plugin(plugins::Factoids::new(HashMap::new())); + bot.add_plugin(plugins::Tell::new(HashMap::new())); } if let Some(disabled_plugins) = disabled_plugins { diff --git a/src/plugins/factoids/database.rs b/src/plugins/factoids/database.rs index 386d3f7..88aa0fd 100644 --- a/src/plugins/factoids/database.rs +++ b/src/plugins/factoids/database.rs @@ -1,6 +1,8 @@ #[cfg(feature = "mysql")] extern crate dotenv; +#[cfg(feature = "mysql")] +use std::sync::Arc; use std::collections::HashMap; #[cfg(feature = "mysql")] @@ -29,8 +31,6 @@ pub struct Factoid { pub created: NaiveDateTime, } -#[cfg(feature = "mysql")] -use self::mysql::factoids; #[cfg_attr(feature = "mysql", derive(Insertable))] #[cfg_attr(feature = "mysql", table_name = "factoids")] pub struct NewFactoid<'a> { @@ -82,10 +82,10 @@ impl Database for HashMap<(String, i32), Factoid> { } } -// Diesel automatically define the factoids module as public. -// For now this is how we keep it private. +// Diesel automatically defines the factoids module as public. +// We create a schema module to keep it private. #[cfg(feature = "mysql")] -mod mysql { +mod schema { table! { factoids (name, idx) { name -> Varchar, @@ -98,7 +98,10 @@ mod mysql { } #[cfg(feature = "mysql")] -impl Database for Pool> { +use self::schema::factoids; + +#[cfg(feature = "mysql")] +impl Database for Arc>> { fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse { use diesel; diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index c3d19b0..d6abb1d 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -49,7 +49,7 @@ impl Factoids { idx: count, content: content, author: author, - created: NaiveDateTime::from_timestamp(tm.sec, tm.nsec as u32), + created: NaiveDateTime::from_timestamp(tm.sec, 0u32), }; match try_lock!(self.factoids).insert_factoid(&factoid) { diff --git a/src/plugins/tell.rs b/src/plugins/tell.rs deleted file mode 100644 index 89d91f2..0000000 --- a/src/plugins/tell.rs +++ /dev/null @@ -1,143 +0,0 @@ -use irc::client::prelude::*; -use irc::error::IrcError; - -use std::collections::HashMap; -use std::sync::Mutex; - -use plugin::*; - -macro_rules! try_lock { - ( $m:expr ) => { - match $m.lock() { - Ok(guard) => guard, - Err(poisoned) => poisoned.into_inner(), - } - } -} - -#[derive(PluginName, Default, Debug)] -pub struct Tell { - tells: Mutex>>, -} - -#[derive(Default, Debug)] -struct TellMessage { - sender: String, - // TODO Add time - message: String, -} - -impl Tell { - pub fn new() -> Tell { - Tell { - tells: Mutex::new(HashMap::new()), - } - } - - fn tell_command(&self, client: &IrcClient, command: &PluginCommand) -> Result<&str, String> { - if command.tokens.len() < 2 { - return Err(self.invalid_command(client)); - } - - let receiver = command.tokens[0].to_string(); - let sender = command.source.to_owned(); - - if receiver == sender { - return Err(String::from("That's your name!")); - } - - if command.source != command.target { - if let Some(users) = client.list_users(&command.target) { - if users.iter().any(|u| u.get_nickname() == receiver) { - return Err(format!("{} is in this channel.", receiver)); - } - } - } - - let message = command.tokens[1..].join(" "); - let tell = TellMessage { - sender: sender, - message: message, - }; - - let mut tells = try_lock!(self.tells); - let tell_messages = tells - .entry(receiver) - .or_insert_with(|| Vec::with_capacity(3)); - (*tell_messages).push(tell); - - Ok("Got it!") - } - - fn send_tell(&self, client: &IrcClient, receiver: &str) -> ExecutionStatus { - let mut tells = try_lock!(self.tells); - if let Some(tell_messages) = tells.get_mut(receiver) { - for tell in tell_messages { - if let Err(e) = client.send_notice( - receiver, - &format!("Tell from {}: {}", tell.sender, tell.message), - ) { - return ExecutionStatus::Err(Box::new(e)); - } - debug!( - "Sent {:?} from {:?} to {:?}", - tell.message, tell.sender, receiver - ); - } - } - tells.remove(receiver); - ExecutionStatus::Done - } - - fn invalid_command(&self, client: &IrcClient) -> String { - format!( - "Incorrect Command. \ - Send \"{} tell help\" for help.", - client.current_nickname() - ) - } - - fn help(&self, client: &IrcClient) -> String { - format!( - "usage: {} tell user message\r\n\ - example: {0} tell Foobar Hello!", - client.current_nickname() - ) - } -} - -impl Plugin for Tell { - fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus { - match message.command { - Command::JOIN(_, _, _) | Command::PRIVMSG(_, _) => { - self.send_tell(client, message.source_nickname().unwrap()) - } - _ => ExecutionStatus::Done, - } - } - - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { - panic!("Tell should not use threading") - } - - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - if command.tokens.is_empty() { - return client.send_notice(&command.source, &self.invalid_command(client)); - } - - match command.tokens[0].as_ref() { - "help" => client.send_notice(&command.source, &self.help(client)), - _ => match self.tell_command(client, &command) { - Ok(msg) => client.send_notice(&command.source, msg), - Err(msg) => client.send_notice(&command.source, &msg), - }, - } - } - - fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { - Err(String::from("This Plugin does not implement any commands.")) - } -} - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/tell/database.rs b/src/plugins/tell/database.rs new file mode 100644 index 0000000..277847e --- /dev/null +++ b/src/plugins/tell/database.rs @@ -0,0 +1,143 @@ +#[cfg(feature = "mysql")] +extern crate dotenv; + +#[cfg(feature = "mysql")] +use std::sync::Arc; +use std::collections::HashMap; + +#[cfg(feature = "mysql")] +use diesel::prelude::*; +#[cfg(feature = "mysql")] +use diesel::mysql::MysqlConnection; +#[cfg(feature = "mysql")] +use r2d2::Pool; +#[cfg(feature = "mysql")] +use r2d2_diesel::ConnectionManager; + +use chrono::NaiveDateTime; + +pub enum DbResponse { + Success, + Failed(&'static str), +} + +#[cfg_attr(feature = "mysql", derive(Queryable))] +#[derive(PartialEq, Clone, Debug)] +pub struct TellMessage { + pub id: i64, + pub sender: String, + pub receiver: String, + pub time: NaiveDateTime, + pub message: String, +} + +#[cfg_attr(feature = "mysql", derive(Insertable))] +#[cfg_attr(feature = "mysql", table_name = "tells")] +pub struct NewTellMessage<'a> { + pub sender: &'a str, + pub receiver: &'a str, + pub time: NaiveDateTime, + pub message: &'a str, +} + +pub trait Database: Send { + fn insert_tell(&mut self, tell: &NewTellMessage) -> DbResponse; + fn get_tells(&self, receiver: &str) -> Option>; + fn delete_tells(&mut self, receiver: &str) -> DbResponse; +} + +// HashMap +impl Database for HashMap> { + fn insert_tell(&mut self, tell: &NewTellMessage) -> DbResponse { + let tell = TellMessage { + id: 0, + sender: tell.sender.to_string(), + receiver: tell.receiver.to_string(), + time: tell.time, + message: tell.message.to_string(), + }; + + let receiver = tell.receiver.clone(); + let tell_messages = self.entry(receiver) + .or_insert_with(|| Vec::with_capacity(3)); + (*tell_messages).push(tell); + + DbResponse::Success + } + + fn get_tells(&self, receiver: &str) -> Option> { + self.get(receiver).cloned() + } + + fn delete_tells(&mut self, receiver: &str) -> DbResponse { + match self.remove(receiver) { + Some(_) => DbResponse::Success, + None => DbResponse::Failed("Tells not found"), + } + } +} + +// Diesel automatically defines the tells module as public. +// We create a schema module to keep it private. +#[cfg(feature = "mysql")] +mod schema { + table! { + tells (id) { + id -> Bigint, + sender -> Varchar, + receiver -> Varchar, + time -> Timestamp, + message -> Varchar, + } + } +} + +#[cfg(feature = "mysql")] +use self::schema::tells; + +#[cfg(feature = "mysql")] +impl Database for Arc>> { + fn insert_tell(&mut self, tell: &NewTellMessage) -> DbResponse { + use diesel; + + let conn = &*self.get().expect("Failed to get connection"); + match diesel::insert_into(tells::table).values(tell).execute(conn) { + Ok(_) => DbResponse::Success, + Err(e) => { + error!("DB failed to insert tell: {}", e); + DbResponse::Failed("Failed to save Tell") + } + } + } + + fn get_tells(&self, receiver: &str) -> Option> { + use self::tells::columns; + + let conn = &*self.get().expect("Failed to get connection"); + match tells::table + .filter(columns::receiver.eq(receiver)) + .order(columns::time.asc()) + .load::(conn) + { + Ok(f) => Some(f), + Err(e) => { + error!("DB failed to get tells: {}", e); + None + } + } + } + + fn delete_tells(&mut self, receiver: &str) -> DbResponse { + use diesel; + use self::tells::columns; + + let conn = &*self.get().expect("Failed to get connection"); + match diesel::delete(tells::table.filter(columns::receiver.eq(receiver))).execute(conn) { + Ok(_) => DbResponse::Success, + Err(e) => { + error!("DB failed to delete tells: {}", e); + DbResponse::Failed("Failed to delete tells") + } + } + } +} diff --git a/src/plugins/tell/mod.rs b/src/plugins/tell/mod.rs new file mode 100644 index 0000000..1a1bef1 --- /dev/null +++ b/src/plugins/tell/mod.rs @@ -0,0 +1,144 @@ +use irc::client::prelude::*; +use irc::error::IrcError; + +use time; +use chrono::NaiveDateTime; +use std::sync::Mutex; + +use plugin::*; + +pub mod database; +use self::database::{Database, DbResponse}; + +macro_rules! try_lock { + ( $m:expr ) => { + match $m.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + } + } +} + +#[derive(PluginName, Default)] +pub struct Tell { + tells: Mutex, +} + +impl Tell { + pub fn new(db: T) -> Tell { + Tell { + tells: Mutex::new(db), + } + } + + fn tell_command(&self, client: &IrcClient, command: &PluginCommand) -> Result<&str, String> { + if command.tokens.len() < 2 { + return Err(self.invalid_command(client)); + } + + let receiver = command.tokens[0].to_string(); + let sender = command.source.to_owned(); + + if receiver == sender { + return Err(String::from("That's your name!")); + } + + if command.source != command.target { + if let Some(users) = client.list_users(&command.target) { + if users.iter().any(|u| u.get_nickname() == receiver) { + return Err(format!("{} is in this channel.", receiver)); + } + } + } + + let tm = time::now().to_timespec(); + let message = command.tokens[1..].join(" "); + let tell = database::NewTellMessage { + sender: &sender, + receiver: &receiver, + time: NaiveDateTime::from_timestamp(tm.sec, 0u32), + message: &message, + }; + + match try_lock!(self.tells).insert_tell(&tell) { + DbResponse::Success => Ok("Got it!"), + DbResponse::Failed(e) => Err(e.to_string()), + } + } + + fn send_tell(&self, client: &IrcClient, receiver: &str) -> ExecutionStatus { + let mut tells = try_lock!(self.tells); + if let Some(tell_messages) = tells.get_tells(receiver) { + for tell in tell_messages { + if let Err(e) = client.send_notice( + receiver, + &format!("Tell from {}: {}", tell.sender, tell.message), + ) { + return ExecutionStatus::Err(Box::new(e)); + } + debug!( + "Sent {:?} from {:?} to {:?}", + tell.message, tell.sender, receiver + ); + } + } + tells.delete_tells(receiver); + ExecutionStatus::Done + } + + fn invalid_command(&self, client: &IrcClient) -> String { + format!( + "Incorrect Command. \ + Send \"{} tell help\" for help.", + client.current_nickname() + ) + } + + fn help(&self, client: &IrcClient) -> String { + format!( + "usage: {} tell user message\r\n\ + example: {0} tell Foobar Hello!", + client.current_nickname() + ) + } +} + +impl Plugin for Tell { + fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus { + match message.command { + Command::JOIN(_, _, _) | Command::PRIVMSG(_, _) => { + self.send_tell(client, message.source_nickname().unwrap()) + } + _ => ExecutionStatus::Done, + } + } + + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + panic!("Tell should not use threading") + } + + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { + if command.tokens.is_empty() { + return client.send_notice(&command.source, &self.invalid_command(client)); + } + + match command.tokens[0].as_ref() { + "help" => client.send_notice(&command.source, &self.help(client)), + _ => match self.tell_command(client, &command) { + Ok(msg) => client.send_notice(&command.source, msg), + Err(msg) => client.send_notice(&command.source, &msg), + }, + } + } + + fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { + Err(String::from("This Plugin does not implement any commands.")) + } +} + +use std::fmt; +impl fmt::Debug for Tell { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Tell {{ ... }}") + } +} -- cgit v1.2.3-70-g09d2 From b40a984ed9b6a948265e1287ecc08f4e16c64ecd Mon Sep 17 00:00:00 2001 From: Jokler Date: Wed, 28 Feb 2018 01:21:25 +0100 Subject: Create errors with failure The plugins are mostly not using the new errors yet and the error handling in the Factoids plugin is just temporary. --- Cargo.lock | 1 + Cargo.toml | 1 + src/error.rs | 98 ++++++++++++++++++++++++ src/lib.rs | 26 +++---- src/main.rs | 28 +++++-- src/plugins/factoids/mod.rs | 174 +++++++++++++++++------------------------- src/plugins/factoids/utils.rs | 4 +- src/plugins/url.rs | 37 ++++----- src/utils.rs | 101 +++++++++++------------- 9 files changed, 262 insertions(+), 208 deletions(-) create mode 100644 src/error.rs (limited to 'src/main.rs') diff --git a/Cargo.lock b/Cargo.lock index d823d90..dcbaad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,6 +391,7 @@ dependencies = [ "diesel_infer_schema 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_migrations 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "frippy_derive 0.1.0", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 961ffb9..2d4087a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ serde = "1.0.27" serde_json = "1.0.9" chrono = "0.4.0" glob = "0.2.11" +failure = "0.1.1" frippy_derive = { path = "frippy_derive" } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..9ada5b6 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,98 @@ +//! Errors for `frippy` crate using `failure`. + +use std::io::Error as IoError; +use std::str::Utf8Error; +use irc::error::IrcError; +use reqwest::Error as ReqwestError; + +/// The main crate-wide error type. +#[derive(Debug, Fail)] +pub enum FrippyError { + /// A plugin error + #[fail(display = "A plugin error occured")] + Plugin(#[cause] PluginError), + + /// An IRC error + #[fail(display = "An IRC error occured")] + Irc(#[cause] IrcError), + + /// Missing config error + #[fail(display = "No config file was found")] + MissingConfig, + + /// A reqwest error + #[fail(display = "A reqwest error occured")] + Reqwest(#[cause] ReqwestError), + + /// An I/O error + #[fail(display = "An I/O error occured")] + Io(#[cause] IoError), + + /// A UTF8 error + #[fail(display = "A UTF8 error occured")] + Utf8(#[cause] Utf8Error), + + /// Reached download limit error + #[fail(display = "Reached download limit of {} KiB", limit)] + DownloadLimit { limit: usize }, +} + +/// Errors related to plugins +#[derive(Debug, Fail)] +pub enum PluginError { + /// A Url error + #[fail(display = "A Url error occured")] + Url(#[cause] UrlError), + + /// A Factoids error + #[fail(display = "{}", error)] + Factoids { error: String }, +} + +/// A URL plugin error +#[derive(Debug, Fail)] +pub enum UrlError { + /// Missing URL error + #[fail(display = "No URL was found")] + MissingUrl, + + /// Missing title error + #[fail(display = "No title was found")] + MissingTitle, +} + +impl From for FrippyError { + fn from(e: UrlError) -> FrippyError { + FrippyError::Plugin(PluginError::Url(e)) + } +} + +impl From for FrippyError { + fn from(e: PluginError) -> FrippyError { + FrippyError::Plugin(e) + } +} + +impl From for FrippyError { + fn from(e: IrcError) -> FrippyError { + FrippyError::Irc(e) + } +} + +impl From for FrippyError { + fn from(e: ReqwestError) -> FrippyError { + FrippyError::Reqwest(e) + } +} + +impl From for FrippyError { + fn from(e: IoError) -> FrippyError { + FrippyError::Io(e) + } +} + +impl From for FrippyError { + fn from(e: Utf8Error) -> FrippyError { + FrippyError::Utf8(e) + } +} diff --git a/src/lib.rs b/src/lib.rs index 657b0eb..42b0089 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,8 @@ extern crate r2d2; #[cfg(feature = "mysql")] extern crate r2d2_diesel; +#[macro_use] +extern crate failure; #[macro_use] extern crate frippy_derive; #[macro_use] @@ -47,11 +49,13 @@ extern crate log; extern crate chrono; extern crate humantime; extern crate irc; +extern crate reqwest; extern crate time; pub mod plugin; pub mod plugins; pub mod utils; +pub mod error; use std::collections::HashMap; use std::fmt; @@ -60,6 +64,7 @@ use std::sync::Arc; pub use irc::client::prelude::*; pub use irc::error::IrcError; +use error::FrippyError; use plugin::*; @@ -142,20 +147,15 @@ impl Bot { /// reactor.run().unwrap(); /// # } /// ``` - pub fn connect(&self, reactor: &mut IrcReactor, config: &Config) -> Result<(), String> { + pub fn connect(&self, reactor: &mut IrcReactor, config: &Config) -> Result<(), FrippyError> { info!("Plugins loaded: {}", self.plugins); - let client = match reactor.prepare_client_and_connect(config) { - Ok(v) => v, - Err(e) => return Err(format!("Failed to connect: {}", e)), - }; + let client = reactor.prepare_client_and_connect(config)?; info!("Connected to IRC server"); - match client.identify() { - Ok(_) => info!("Identified"), - Err(e) => return Err(format!("Failed to identify: {}", e)), - }; + client.identify()?; + info!("Identified"); // TODO Verify if we actually need to clone plugins twice let plugins = self.plugins.clone(); @@ -253,10 +253,10 @@ impl ThreadedPlugins { &mut self, server: &IrcClient, mut command: PluginCommand, - ) -> Result<(), IrcError> { + ) -> Result<(), FrippyError> { if !command.tokens.iter().any(|s| !s.is_empty()) { let help = format!("Use \"{} help\" to get help", server.current_nickname()); - return server.send_notice(&command.source, &help); + server.send_notice(&command.source, &help)?; } // Check if the command is for this plugin @@ -284,7 +284,7 @@ impl ThreadedPlugins { command.tokens[0] ); - server.send_notice(&command.source, &help) + Ok(server.send_notice(&command.source, &help)?) } } } @@ -293,7 +293,7 @@ impl fmt::Display for ThreadedPlugins { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let plugin_names = self.plugins .iter() - .map(|(_, p)| p.name().to_string()) + .map(|(_, p)| p.name().to_owned()) .collect::>(); write!(f, "{}", plugin_names.join(", ")) } diff --git a/src/main.rs b/src/main.rs index ec04d33..461387d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ extern crate frippy; extern crate glob; extern crate irc; extern crate time; +extern crate failure; #[cfg(feature = "mysql")] extern crate diesel; @@ -26,9 +27,11 @@ use log::{Level, LevelFilter, Metadata, Record}; use irc::client::reactor::IrcReactor; use glob::glob; +use failure::Fail; use frippy::plugins; use frippy::Config; +use frippy::error::FrippyError; #[cfg(feature = "mysql")] embed_migrations!(); @@ -67,6 +70,18 @@ impl log::Log for Logger { static LOGGER: Logger = Logger; fn main() { + // Print any errors that caused frippy to shut down + if let Err(e) = run() { + let mut causes = e.causes(); + + error!("{}", causes.next().unwrap()); + for cause in causes { + error!("caused by: {}", cause); + } + }; +} + +fn run() -> Result<(), FrippyError> { log::set_max_level(if cfg!(debug_assertions) { LevelFilter::Debug } else { @@ -92,12 +107,11 @@ fn main() { // Without configs the bot would just idle if configs.is_empty() { - error!("No config file found"); - return; + return Err(FrippyError::MissingConfig); } // Create an event loop to run the connections on. - let mut reactor = IrcReactor::new().unwrap(); + let mut reactor = IrcReactor::new()?; // Open a connection and add work for each config for config in configs { @@ -127,8 +141,7 @@ fn main() { let manager = ConnectionManager::::new(url.clone()); match r2d2::Pool::builder().build(manager) { - Ok(pool) => match embedded_migrations::run(&*pool.get() - .expect("Failed to get connection")) + Ok(pool) => match embedded_migrations::run(&*pool.get()?) { Ok(_) => { let pool = Arc::new(pool); @@ -166,10 +179,9 @@ fn main() { } } - bot.connect(&mut reactor, &config) - .expect("Failed to connect"); + bot.connect(&mut reactor, &config)?; } // Run the bots until they throw an error - an error could be loss of connection - reactor.run().unwrap(); + Ok(reactor.run()?) } diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index d6abb1d..3f89943 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -6,6 +6,9 @@ use std::sync::Mutex; use self::rlua::prelude::*; use irc::client::prelude::*; use irc::error::IrcError; +use error::FrippyError; +use error::PluginError; +use failure::Fail; use time; use chrono::NaiveDateTime; @@ -40,8 +43,8 @@ impl Factoids { } } - fn create_factoid(&self, name: &str, content: &str, author: &str) -> Result<&str, &str> { - let count = try_lock!(self.factoids).count_factoids(name)?; + fn create_factoid(&self, name: &str, content: &str, author: &str) -> Result<&str, FrippyError> { + let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() })?; let tm = time::now().to_timespec(); let factoid = database::NewFactoid { @@ -54,74 +57,60 @@ impl Factoids { match try_lock!(self.factoids).insert_factoid(&factoid) { DbResponse::Success => Ok("Successfully added"), - DbResponse::Failed(e) => Err(e), + DbResponse::Failed(e) => Err(PluginError::Factoids { error: e.to_owned() })?, } } - fn add(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<(), IrcError> { + fn add(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<&str, FrippyError> { if command.tokens.len() < 2 { - return self.invalid_command(client, command); + return Ok(self.invalid_command(client, command).map(|()| "")?); } let name = command.tokens.remove(0); let content = command.tokens.join(" "); - match self.create_factoid(&name, &content, &command.source) { - Ok(v) => client.send_notice(&command.source, v), - Err(e) => client.send_notice(&command.source, e), - } + Ok(self.create_factoid(&name, &content, &command.source)?) } - fn save_from_url( + fn add_from_url( &self, client: &IrcClient, command: &mut PluginCommand, - ) -> Result<(), IrcError> { + ) -> Result<&str, FrippyError> { if command.tokens.len() < 2 { - return self.invalid_command(client, command); + return Ok(self.invalid_command(client, command).map(|()| "")?); } let name = command.tokens.remove(0); let url = &command.tokens[0]; - if let Some(content) = ::utils::download(1024, url) { - match self.create_factoid(&name, &content, &command.source) { - Ok(v) => client.send_notice(&command.source, v), - Err(e) => client.send_notice(&command.source, e), - } - } else { - client.send_notice(&command.source, "Failed to download.") - } + let content = ::utils::download(1024, url)?; + + Ok(self.create_factoid(&name, &content, &command.source)?) } - fn remove(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<(), IrcError> { + fn remove(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<&str, FrippyError> { if command.tokens.len() < 1 { - return self.invalid_command(client, command); + return Ok(self.invalid_command(client, command).map(|()| "")?); } let name = command.tokens.remove(0); - let count = match try_lock!(self.factoids).count_factoids(&name) { - Ok(c) => c, - Err(e) => return client.send_notice(&command.source, e), - }; + let count = try_lock!(self.factoids).count_factoids(&name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; match try_lock!(self.factoids).delete_factoid(&name, count - 1) { - DbResponse::Success => client.send_notice(&command.source, "Successfully removed"), - DbResponse::Failed(e) => client.send_notice(&command.source, e), + DbResponse::Success => Ok("Successfully removed"), + DbResponse::Failed(e) => Err(PluginError::Factoids { error: e.to_owned() })?, } } - fn get(&self, client: &IrcClient, command: &PluginCommand) -> Result<(), IrcError> { + fn get(&self, client: &IrcClient, command: &PluginCommand) -> Result { let (name, idx) = match command.tokens.len() { - 0 => return self.invalid_command(client, command), + 0 => return Ok(self.invalid_command(client, command).map(|()| String::new())?), 1 => { let name = &command.tokens[0]; - let count = match try_lock!(self.factoids).count_factoids(name) { - Ok(c) => c, - Err(e) => return client.send_notice(&command.source, e), - }; + let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; if count < 1 { - return client.send_notice(&command.source, &format!("{} does not exist", name)); + Err(PluginError::Factoids { error: format!("{} does not exist", name) })?; } (name, count - 1) @@ -130,7 +119,7 @@ impl Factoids { let name = &command.tokens[0]; let idx = match i32::from_str(&command.tokens[1]) { Ok(i) => i, - Err(_) => return client.send_notice(&command.source, "Invalid index"), + Err(_) => Err(PluginError::Factoids { error: String::from("Invalid index") })?, }; (name, idx) @@ -139,61 +128,37 @@ impl Factoids { let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { Some(v) => v, - None => { - return client - .send_notice(&command.source, &format!("{}~{} does not exist", name, idx)) - } + None => Err(PluginError::Factoids { error: format!("{}~{} does not exist", name, idx) })?, }; let message = factoid.content.replace("\n", "|").replace("\r", ""); - client.send_privmsg(&command.target, &format!("{}: {}", factoid.name, message)) + Ok(format!("{}: {}", factoid.name, message)) } - fn info(&self, client: &IrcClient, command: &PluginCommand) -> Result<(), IrcError> { + fn info(&self, client: &IrcClient, command: &PluginCommand) -> Result { match command.tokens.len() { - 0 => self.invalid_command(client, command), + 0 => Ok(self.invalid_command(client, command).map(|()| String::new())?), 1 => { let name = &command.tokens[0]; - let count = match try_lock!(self.factoids).count_factoids(name) { - Ok(c) => c, - Err(e) => return client.send_notice(&command.source, e), - }; + let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; - match count { - 0 => client.send_notice(&command.source, &format!("{} does not exist", name)), - 1 => client - .send_privmsg(&command.target, &format!("There is 1 version of {}", name)), - _ => client.send_privmsg( - &command.target, - &format!("There are {} versions of {}", count, name), - ), - } + Ok(match count { + 0 => Err(PluginError::Factoids { error: format!("{} does not exist", name) })?, + 1 => format!("There is 1 version of {}", name), + _ => 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 client.send_notice(&command.source, "Invalid index"), - }; + let idx = i32::from_str(&command.tokens[1]).map_err(|_| PluginError::Factoids { error: String::from("Invalid index") })?; let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { Some(v) => v, - None => { - return client.send_notice( - &command.source, - &format!("{}~{} does not exist", name, idx), - ) - } + None => return Ok(format!("{}~{} does not exist", name, idx)), }; - client.send_privmsg( - &command.target, - &format!( - "{}: Added by {} at {} UTC", - name, factoid.author, factoid.created - ), - ) + Ok(format!("{}: Added by {} at {} UTC", name, factoid.author, factoid.created)) } } } @@ -202,39 +167,31 @@ impl Factoids { &self, client: &IrcClient, mut command: PluginCommand, - error: bool, - ) -> Result<(), IrcError> { + ) -> Result { if command.tokens.len() < 1 { - self.invalid_command(client, &command) + Ok(self.invalid_command(client, &command).map(|()| String::new())?) } else { let name = command.tokens.remove(0); - let count = match try_lock!(self.factoids).count_factoids(&name) { - Ok(c) => c, - Err(e) => return client.send_notice(&command.source, e), - }; - - let factoid = match try_lock!(self.factoids).get_factoid(&name, count - 1) { - Some(v) => v.content, - None if error => return self.invalid_command(client, &command), - None => return Ok(()), - }; + let count = try_lock!(self.factoids).count_factoids(&name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; + let factoid = try_lock!(self.factoids).get_factoid(&name, count - 1).ok_or(PluginError::Factoids { error: format!("The factoid \"{}\" does not exist", name) })?; - let value = &if factoid.starts_with('>') { - let factoid = String::from(&factoid[1..]); + let content = factoid.content; + let value = if content.starts_with('>') { + let content = String::from(&content[1..]); - if factoid.starts_with('>') { - factoid + if content.starts_with('>') { + content } else { - match self.run_lua(&name, &factoid, &command) { + match self.run_lua(&name, &content, &command) { Ok(v) => v, Err(e) => format!("\"{}\"", e), } } } else { - factoid + content }; - client.send_privmsg(&command.target, &value.replace("\n", "|").replace("\r", "")) + Ok(value.replace("\n", "|").replace("\r", "")) } } @@ -293,12 +250,15 @@ impl Plugin for Factoids { let t: Vec = content.split(' ').map(ToOwned::to_owned).collect(); let c = PluginCommand { - source: message.source_nickname().unwrap().to_string(), - target: message.response_target().unwrap().to_string(), + source: message.source_nickname().unwrap().to_owned(), + target: message.response_target().unwrap().to_owned(), tokens: t, }; - self.exec(client, c, false) + match self.exec(client, c) { + Ok(f) => client.send_privmsg(&message.response_target().unwrap(), &f), + Err(_) => Ok(()), + } } else { Ok(()) } @@ -309,16 +269,24 @@ impl Plugin for Factoids { return self.invalid_command(client, &command); } + let target = command.target.clone(); + let source = command.source.clone(); + let sub_command = command.tokens.remove(0); - match sub_command.as_ref() { - "add" => self.add(client, &mut command), - "fromurl" => self.save_from_url(client, &mut command), - "remove" => self.remove(client, &mut command), + let result = match sub_command.as_ref() { + "add" => self.add(client, &mut command).map(|s| s.to_owned()), + "fromurl" => self.add_from_url(client, &mut command).map(|s| s.to_owned()), + "remove" => self.remove(client, &mut command).map(|s| s.to_owned()), "get" => self.get(client, &command), "info" => self.info(client, &command), - "exec" => self.exec(client, command, true), - _ => self.invalid_command(client, &command), - } + "exec" => self.exec(client, command), + _ => self.invalid_command(client, &command).map(|()| String::new()).map_err(|e| e.into()), + }; + + Ok(match result { + Ok(v) => client.send_privmsg(&target, &v), + Err(e) => client.send_notice(&source, &e.cause().unwrap().to_string()), + }?) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs index fc86fb3..036dcc6 100644 --- a/src/plugins/factoids/utils.rs +++ b/src/plugins/factoids/utils.rs @@ -10,8 +10,8 @@ use self::LuaError::RuntimeError; pub fn download(_: &Lua, url: String) -> Result { match utils::download(1024, &url) { - Some(v) => Ok(v), - None => Err(RuntimeError(format!("Failed to download {}", url))), + Ok(v) => Ok(v), + Err(e) => Err(RuntimeError(format!("Failed to download {} - {}", url, e.to_string()))), } } diff --git a/src/plugins/url.rs b/src/plugins/url.rs index b19d4e5..af6f36f 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -11,6 +11,9 @@ use self::select::predicate::Name; use plugin::*; use utils; +use error::FrippyError; +use error::UrlError; +use failure::Fail; lazy_static! { static ref RE: Regex = Regex::new(r"(^|\s)(https?://\S+)").unwrap(); @@ -28,14 +31,10 @@ impl Url { } fn grep_url(&self, msg: &str) -> Option { - match RE.captures(msg) { - Some(captures) => { - debug!("Url captures: {:?}", captures); + let captures = RE.captures(msg)?; + debug!("Url captures: {:?}", captures); - Some(captures.get(2)?.as_str().to_string()) - } - None => None, - } + Some(captures.get(2)?.as_str().to_owned()) } fn get_title(&self, body: &str) -> Option { @@ -49,21 +48,11 @@ impl Url { Some(title_text) } - fn url(&self, text: &str) -> Result { - let url = match self.grep_url(text) { - Some(url) => url, - None => return Err("No Url was found."), - }; - - match utils::download(self.max_kib, &url) { - Some(body) => { - match self.get_title(&body) { - Some(title) => Ok(title), - None => Err("No title was found.") - } - } - None => Err("Failed to download document."), - } + fn url(&self, text: &str) -> Result { + let url = self.grep_url(text).ok_or(UrlError::MissingUrl)?; + let body = utils::download(self.max_kib, &url)?; + + Ok(self.get_title(&body).ok_or(UrlError::MissingTitle)?) } } @@ -86,7 +75,7 @@ impl Plugin for Url { Err(e) => { error!("Url plugin error: {}", e); Ok(()) - }, + } }, _ => Ok(()), } @@ -100,7 +89,7 @@ impl Plugin for Url { } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { - self.url(&command.tokens[0]).map_err(String::from) + self.url(&command.tokens[0]).map_err(|e| e.cause().unwrap().to_string()) } } diff --git a/src/utils.rs b/src/utils.rs index 99b04c4..68ad4d8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,73 +1,58 @@ -extern crate reqwest; - use std::str; use std::io::{self, Read}; -use self::reqwest::Client; -use self::reqwest::header::Connection; +use reqwest::Client; +use reqwest::header::Connection; -pub fn download(max_kib: usize, url: &str) -> Option { - let response = Client::new().get(url).header(Connection::close()).send(); +use error::FrippyError; - match response { - Ok(mut response) => { - let mut body = String::new(); +pub fn download(max_kib: usize, url: &str) -> Result { + let mut response = Client::new().get(url).header(Connection::close()).send()?; - // 100 kibibyte buffer - let mut buf = [0; 100 * 1024]; - let mut written = 0; - let mut vec = Vec::new(); - let mut end_of_valid = None; + let mut body = String::new(); - // Read until we reach EOF or max_kib KiB - loop { - if let Some(eov) = end_of_valid { - vec = vec[..eov].to_vec(); - } + // 100 kibibyte buffer + let mut buf = [0; 100 * 1024]; + let mut written = 0; + let mut vec = Vec::new(); + let mut end_of_valid = None; - let len = match response.read(&mut buf) { - Ok(0) => break, - Ok(len) => len, - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, - Err(e) => { - debug!("Download from {:?} failed: {}", url, e); - return None; - } - }; - vec.extend_from_slice(&buf); + // Read until we reach EOF or max_kib KiB + loop { + if let Some(eov) = end_of_valid { + vec = vec[..eov].to_vec(); + } - end_of_valid = None; - let body_slice = match str::from_utf8(&vec[..len]) { - Ok(slice) => slice, - Err(e) => { - let valid = e.valid_up_to(); - if valid == 0 { - error!("Failed to read bytes from {:?} as UTF8: {}", url, e); - return None; - } - end_of_valid = Some(valid); + let len = match response.read(&mut buf) { + Ok(0) => break, + Ok(len) => len, + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => Err(e)?, + }; + vec.extend_from_slice(&buf); + + end_of_valid = None; + let body_slice = match str::from_utf8(&vec[..len]) { + Ok(slice) => slice, + Err(e) => { + let valid = e.valid_up_to(); + if valid == 0 { + Err(e)?; + } + end_of_valid = Some(valid); - str::from_utf8(&buf[..valid]).unwrap() - } - }; + str::from_utf8(&buf[..valid])? + } + }; - body.push_str(body_slice); - written += len; + body.push_str(body_slice); + written += len; - // Check if the file is too large to download - if written > max_kib * 1024 { - debug!( - "Stopping download - File from {:?} is larger than {} KiB", - url, max_kib - ); - return None; - } - } - Some(body) - } - Err(e) => { - debug!("Bad response from {:?}: ({})", url, e); - None + // Check if the file is too large to download + if written > max_kib * 1024 { + Err(FrippyError::DownloadLimit { limit: max_kib })?; } } + + Ok(body) } -- cgit v1.2.3-70-g09d2 From d8406b107c651321c9e166abc36fc66730d965a1 Mon Sep 17 00:00:00 2001 From: Jokler Date: Thu, 1 Mar 2018 18:34:03 +0100 Subject: Use lossy UTF8 conversion and add a log_error function --- src/error.rs | 11 ----------- src/main.rs | 9 +-------- src/plugins/factoids/mod.rs | 2 +- src/plugins/factoids/utils.rs | 2 +- src/plugins/url.rs | 7 ++----- src/utils.rs | 26 +++++++++++++++++++++----- 6 files changed, 26 insertions(+), 31 deletions(-) (limited to 'src/main.rs') diff --git a/src/error.rs b/src/error.rs index 674faaa..fa232be 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,6 @@ //! Errors for `frippy` crate using `failure`. use std::io::Error as IoError; -use std::str::Utf8Error; use irc::error::IrcError; use reqwest::Error as ReqwestError; #[cfg(feature = "mysql")] @@ -30,10 +29,6 @@ pub enum FrippyError { #[fail(display = "An I/O error occured")] Io(#[cause] IoError), - /// A decoding error - #[fail(display = "Failed to decode bytes")] - Decoding(#[cause] Utf8Error), - /// An r2d2 error #[cfg(feature = "mysql")] #[fail(display = "An r2d2 error occured")] @@ -98,12 +93,6 @@ impl From for FrippyError { } } -impl From for FrippyError { - fn from(e: Utf8Error) -> FrippyError { - FrippyError::Decoding(e) - } -} - #[cfg(feature = "mysql")] impl From for FrippyError { fn from(e: R2d2Error) -> FrippyError { diff --git a/src/main.rs b/src/main.rs index 461387d..3432e3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ extern crate frippy; extern crate glob; extern crate irc; extern crate time; -extern crate failure; #[cfg(feature = "mysql")] extern crate diesel; @@ -27,7 +26,6 @@ use log::{Level, LevelFilter, Metadata, Record}; use irc::client::reactor::IrcReactor; use glob::glob; -use failure::Fail; use frippy::plugins; use frippy::Config; @@ -72,12 +70,7 @@ static LOGGER: Logger = Logger; fn main() { // Print any errors that caused frippy to shut down if let Err(e) = run() { - let mut causes = e.causes(); - - error!("{}", causes.next().unwrap()); - for cause in causes { - error!("caused by: {}", cause); - } + frippy::utils::log_error(e); }; } diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index 3f89943..806bb7e 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -83,7 +83,7 @@ impl Factoids { let name = command.tokens.remove(0); let url = &command.tokens[0]; - let content = ::utils::download(1024, url)?; + let content = ::utils::download(url, Some(1024))?; Ok(self.create_factoid(&name, &content, &command.source)?) } diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs index 036dcc6..009b46b 100644 --- a/src/plugins/factoids/utils.rs +++ b/src/plugins/factoids/utils.rs @@ -9,7 +9,7 @@ use super::rlua::prelude::*; use self::LuaError::RuntimeError; pub fn download(_: &Lua, url: String) -> Result { - match utils::download(1024, &url) { + match utils::download(&url, Some(1024)) { Ok(v) => Ok(v), Err(e) => Err(RuntimeError(format!("Failed to download {} - {}", url, e.to_string()))), } diff --git a/src/plugins/url.rs b/src/plugins/url.rs index af6f36f..6f00466 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -50,7 +50,7 @@ impl Url { fn url(&self, text: &str) -> Result { let url = self.grep_url(text).ok_or(UrlError::MissingUrl)?; - let body = utils::download(self.max_kib, &url)?; + let body = utils::download(&url, Some(self.max_kib))?; Ok(self.get_title(&body).ok_or(UrlError::MissingTitle)?) } @@ -72,10 +72,7 @@ impl Plugin for Url { match message.command { Command::PRIVMSG(_, ref content) => match self.url(content) { Ok(title) => client.send_privmsg(message.response_target().unwrap(), &title), - Err(e) => { - error!("Url plugin error: {}", e); - Ok(()) - } + Err(e) => Ok(utils::log_error(e)), }, _ => Ok(()), } diff --git a/src/utils.rs b/src/utils.rs index 1f3a117..cf91b37 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,9 +4,14 @@ use std::io::{self, Read}; use reqwest::Client; use reqwest::header::Connection; +use failure::Fail; use error::FrippyError; -pub fn download(max_kib: usize, url: &str) -> Result { +/// Downloads the file and converts it to a String. +/// Any invalid bytes are converted to a replacement character. +/// +/// The error indicated either a failed download or that the DownloadLimit was reached +pub fn download(url: &str, max_kib: Option) -> Result { let mut response = Client::new().get(url).header(Connection::close()).send()?; // 100 kibibyte buffer @@ -27,11 +32,22 @@ pub fn download(max_kib: usize, url: &str) -> Result { written += len; // Check if the file is too large to download - if written > max_kib * 1024 { - Err(FrippyError::DownloadLimit { limit: max_kib })?; + if let Some(max_kib) = max_kib { + if written > max_kib * 1024 { + Err(FrippyError::DownloadLimit { limit: max_kib })?; + } } } - let body = str::from_utf8(&bytes)?; - Ok(body.to_string()) + Ok(String::from_utf8_lossy(&bytes).into_owned()) +} + + +pub fn log_error(e: FrippyError) { + let mut causes = e.causes(); + + error!("{}", causes.next().unwrap()); + for cause in causes { + error!("caused by: {}", cause); + } } -- cgit v1.2.3-70-g09d2 From 0b4131e8cf91ed10f24d3faed341034d518aea53 Mon Sep 17 00:00:00 2001 From: Jokler Date: Fri, 2 Mar 2018 22:11:21 +0100 Subject: Use Error & ErrorKind pair instead of simple enums Each plugin should define its own errors with a respective variant in the main ErrorKind of frippy. A new procedural macro was added to reduce the boilerplate required for new error system. It can be used by deriving "Error" and adding a name for the Error via the "error" attribute. So far non of the plugins except for Url and Factoids use their own errors yet. --- Cargo.lock | 40 ++++++++- frippy_derive/Cargo.toml | 5 +- frippy_derive/src/lib.rs | 86 +++++++++++++++++- src/error.rs | 114 +++++------------------- src/lib.rs | 56 ++++++------ src/main.rs | 51 ++++++----- src/plugin.rs | 21 +++-- src/plugins/currency.rs | 28 +++--- src/plugins/emoji.rs | 25 +++--- src/plugins/factoids/database.rs | 88 ++++++++---------- src/plugins/factoids/mod.rs | 187 +++++++++++++++++++++++---------------- src/plugins/factoids/utils.rs | 6 +- src/plugins/help.rs | 16 ++-- src/plugins/keepnick.rs | 29 +++--- src/plugins/mod.rs | 23 ++--- src/plugins/tell/mod.rs | 32 ++++--- src/plugins/url.rs | 63 ++++++++----- src/utils.rs | 34 ++++--- 18 files changed, 528 insertions(+), 376 deletions(-) (limited to 'src/main.rs') diff --git a/Cargo.lock b/Cargo.lock index dcbaad5..acb6f80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,8 +414,9 @@ dependencies = [ name = "frippy_derive" version = "0.1.0" dependencies = [ - "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)", + "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -925,6 +926,14 @@ name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pulldown-cmark" version = "0.0.15" @@ -949,6 +958,14 @@ name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "quote" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "r2d2" version = "0.8.2" @@ -1246,6 +1263,16 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "synom" version = "0.11.3" @@ -1427,6 +1454,11 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode_names" version = "0.1.7" @@ -1636,10 +1668,12 @@ dependencies = [ "checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" "checksum pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "378e941dbd392c101f2cb88097fa4d7167bc421d4b88de3ff7dbee503bc3233b" "checksum quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4" "checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" "checksum r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9078ca6a8a5568ed142083bb2f7dc9295b69d16f867ddcc9849e51b17d8db46" "checksum r2d2-diesel 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9c29bad92da76d02bc2c020452ebc3a3fe6fa74cfab91e711c43116e4fb1a3" "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" @@ -1675,6 +1709,7 @@ dependencies = [ "checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)" = "517f6da31bc53bf080b9a77b29fbd0ff8da2f5a2ebd24c73c2238274a94ac7cb" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" @@ -1695,6 +1730,7 @@ dependencies = [ "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode_names 0.1.7 (git+https://github.com/Jokler/unicode_names?branch=update-to-latest-unicode)" = "" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" diff --git a/frippy_derive/Cargo.toml b/frippy_derive/Cargo.toml index 937afba..b258f57 100644 --- a/frippy_derive/Cargo.toml +++ b/frippy_derive/Cargo.toml @@ -7,5 +7,6 @@ authors = ["Jokler "] proc-macro = true [dependencies] -syn = "0.11.11" -quote = "0.3.15" +syn = "0.12.13" +quote = "0.4.2" +failure = "0.1.1" diff --git a/frippy_derive/src/lib.rs b/frippy_derive/src/lib.rs index 2622f0b..efa349a 100644 --- a/frippy_derive/src/lib.rs +++ b/frippy_derive/src/lib.rs @@ -1,18 +1,20 @@ - //! Provides the plugin derive macro +#![recursion_limit="128"] extern crate proc_macro; extern crate syn; #[macro_use] extern crate quote; +extern crate failure; + use proc_macro::TokenStream; #[proc_macro_derive(PluginName)] pub fn derive_plugin(data: TokenStream) -> TokenStream { - let ast = syn::parse_derive_input(&data.to_string()).unwrap(); + let ast = syn::parse(data).unwrap(); let gen = expand_plugin(&ast); - gen.parse().unwrap() + gen.into() } fn expand_plugin(ast: &syn::DeriveInput) -> quote::Tokens { @@ -27,3 +29,81 @@ fn expand_plugin(ast: &syn::DeriveInput) -> quote::Tokens { } } } + +#[proc_macro_derive(Error, attributes(error))] +pub fn derive_error(data: TokenStream) -> TokenStream { + let ast = syn::parse(data).unwrap(); + let tokens = expand_error(&ast); + // panic!("{}", tokens.to_string()); + tokens.into() +} + +fn expand_error(ast: &syn::DeriveInput) -> quote::Tokens { + if let syn::Data::Enum(_) = ast.data { + } else { + panic!("Error should only be derived on ErrorKind enums"); + }; + + let mut name = None; + for attr in &ast.attrs { + if let Some(syn::Meta::NameValue(name_value)) = attr.interpret_meta() { + if "error" == name_value.ident.to_string() { + if let syn::Lit::Str(lit) = name_value.lit { + name = Some(lit.value()); + } + } + } + }; + + let struct_name = if let Some(name) = name { + syn::Ident::from(name) + } else { + panic!("Define the error attribute for all Error derives"); + }; + + let enum_name = &ast.ident; + + quote! { + #[derive(Debug)] + pub struct #struct_name { + inner: ::failure::Context<#enum_name>, + } + + impl #struct_name { + pub fn kind(&self) -> #enum_name { + *self.inner.get_context() + } + } + + impl From<#enum_name> for #struct_name { + fn from(kind: #enum_name) -> #struct_name { + #struct_name { + inner: ::failure::Context::new(kind), + } + } + } + + impl From<::failure::Context<#enum_name>> for #struct_name { + fn from(inner: ::failure::Context<#enum_name>) -> #struct_name { + #struct_name { inner: inner } + } + } + + impl ::failure::Fail for #struct_name { + fn cause(&self) -> Option<&::failure::Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&::failure::Backtrace> { + self.inner.backtrace() + } + } + + impl ::std::fmt::Display for #struct_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + use std::fmt; + fmt::Display::fmt(&self.inner, f) + } + } + } +} diff --git a/src/error.rs b/src/error.rs index fa232be..36d5724 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,101 +1,31 @@ //! Errors for `frippy` crate using `failure`. -use std::io::Error as IoError; -use irc::error::IrcError; -use reqwest::Error as ReqwestError; -#[cfg(feature = "mysql")] -use r2d2::Error as R2d2Error; +use failure::Fail; -/// The main crate-wide error type. -#[derive(Debug, Fail)] -pub enum FrippyError { - /// A plugin error - #[fail(display = "A plugin error occured")] - Plugin(#[cause] PluginError), - - /// An IRC error - #[fail(display = "An IRC error occured")] - Irc(#[cause] IrcError), - - /// Missing config error - #[fail(display = "No config file was found")] - MissingConfig, - - /// A reqwest error - #[fail(display = "A reqwest error occured")] - Reqwest(#[cause] ReqwestError), - - /// An I/O error - #[fail(display = "An I/O error occured")] - Io(#[cause] IoError), - - /// An r2d2 error - #[cfg(feature = "mysql")] - #[fail(display = "An r2d2 error occured")] - R2d2(#[cause] R2d2Error), - - /// Reached download limit error - #[fail(display = "Reached download limit of {} KiB", limit)] - DownloadLimit { limit: usize }, +pub fn log_error(e: FrippyError) { + let text = e.causes() + .skip(1) + .fold(format!("{}", e), |acc, err| format!("{}: {}", acc, err)); + error!("{}", text); } -/// Errors related to plugins -#[derive(Debug, Fail)] -pub enum PluginError { - /// A Url error - #[fail(display = "A Url error occured")] - Url(#[cause] UrlError), - - /// A Factoids error - #[fail(display = "{}", error)] - Factoids { error: String }, -} - -/// A URL plugin error -#[derive(Debug, Fail)] -pub enum UrlError { - /// Missing URL error - #[fail(display = "No URL was found")] - MissingUrl, - - /// Missing title error - #[fail(display = "No title was found")] - MissingTitle, -} - -impl From for FrippyError { - fn from(e: UrlError) -> FrippyError { - FrippyError::Plugin(PluginError::Url(e)) - } -} - -impl From for FrippyError { - fn from(e: PluginError) -> FrippyError { - FrippyError::Plugin(e) - } -} - -impl From for FrippyError { - fn from(e: IrcError) -> FrippyError { - FrippyError::Irc(e) - } -} +/// The main crate-wide error type. +#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] +#[error = "FrippyError"] +pub enum ErrorKind { + /// Connection error + #[fail(display = "A connection error occured")] + Connection, -impl From for FrippyError { - fn from(e: ReqwestError) -> FrippyError { - FrippyError::Reqwest(e) - } -} + /// A Url error + #[fail(display = "A Url error has occured")] + Url, -impl From for FrippyError { - fn from(e: IoError) -> FrippyError { - FrippyError::Io(e) - } -} + /// A Tell error + #[fail(display = "A Tell error has occured")] + Tell, -#[cfg(feature = "mysql")] -impl From for FrippyError { - fn from(e: R2d2Error) -> FrippyError { - FrippyError::R2d2(e) - } + /// A Factoids error + #[fail(display = "A Factoids error has occured")] + Factoids, } diff --git a/src/lib.rs b/src/lib.rs index 42b0089..8cf1e2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,8 @@ use std::sync::Arc; pub use irc::client::prelude::*; pub use irc::error::IrcError; -use error::FrippyError; +use error::*; +use failure::ResultExt; use plugin::*; @@ -150,11 +151,13 @@ impl Bot { pub fn connect(&self, reactor: &mut IrcReactor, config: &Config) -> Result<(), FrippyError> { info!("Plugins loaded: {}", self.plugins); - let client = reactor.prepare_client_and_connect(config)?; + let client = reactor + .prepare_client_and_connect(config) + .context(ErrorKind::Connection)?; info!("Connected to IRC server"); - client.identify()?; + client.identify().context(ErrorKind::Connection)?; info!("Identified"); // TODO Verify if we actually need to clone plugins twice @@ -169,25 +172,25 @@ impl Bot { } fn process_msg( - server: &IrcClient, + client: &IrcClient, mut plugins: ThreadedPlugins, message: Message, ) -> Result<(), IrcError> { // Log any channels we join if let Command::JOIN(ref channel, _, _) = message.command { - if message.source_nickname().unwrap() == server.current_nickname() { + if message.source_nickname().unwrap() == client.current_nickname() { info!("Joined {}", channel); } } // Check for possible command and save the result for later - let command = PluginCommand::from(&server.current_nickname().to_lowercase(), &message); + let command = PluginCommand::from(&client.current_nickname().to_lowercase(), &message); - plugins.execute_plugins(server, message); + plugins.execute_plugins(client, message); // If the message contained a command, handle it if let Some(command) = command { - if let Err(e) = plugins.handle_command(server, command) { + if let Err(e) = plugins.handle_command(client, command) { error!("Failed to handle command: {}", e); } } @@ -218,12 +221,12 @@ impl ThreadedPlugins { self.plugins.remove(&name.to_lowercase()).map(|_| ()) } - pub fn execute_plugins(&mut self, server: &IrcClient, message: Message) { + pub fn execute_plugins(&mut self, client: &IrcClient, message: Message) { let message = Arc::new(message); for (name, plugin) in self.plugins.clone() { // Send the message to the plugin if the plugin needs it - match plugin.execute(server, &message) { + match plugin.execute(client, &message) { ExecutionStatus::Done => (), ExecutionStatus::Err(e) => error!("Error in {} - {}", name, e), ExecutionStatus::RequiresThread => { @@ -233,15 +236,15 @@ impl ThreadedPlugins { message.to_string().replace("\r\n", "") ); - // Clone everything before the move - the server uses an Arc internally too + // Clone everything before the move - the client uses an Arc internally too let plugin = Arc::clone(&plugin); let message = Arc::clone(&message); - let server = server.clone(); + let client = client.clone(); // Execute the plugin in another thread spawn(move || { - if let Err(e) = plugin.execute_threaded(&server, &message) { - error!("Error in {} - {}", name, e); + if let Err(e) = plugin.execute_threaded(&client, &message) { + log_error(e); }; }); } @@ -251,12 +254,14 @@ impl ThreadedPlugins { pub fn handle_command( &mut self, - server: &IrcClient, + client: &IrcClient, mut command: PluginCommand, ) -> Result<(), FrippyError> { if !command.tokens.iter().any(|s| !s.is_empty()) { - let help = format!("Use \"{} help\" to get help", server.current_nickname()); - server.send_notice(&command.source, &help)?; + let help = format!("Use \"{} help\" to get help", client.current_nickname()); + client + .send_notice(&command.source, &help) + .context(ErrorKind::Connection)?; } // Check if the command is for this plugin @@ -266,12 +271,12 @@ impl ThreadedPlugins { debug!("Sending command \"{:?}\" to {}", command, name); - // Clone for the move - the server uses an Arc internally - let server = server.clone(); + // Clone for the move - the client uses an Arc internally + let client = client.clone(); let plugin = Arc::clone(plugin); spawn(move || { - if let Err(e) = plugin.command(&server, command) { - error!("Error in {} command - {}", name, e); + if let Err(e) = plugin.command(&client, command) { + log_error(e); }; }); @@ -280,11 +285,13 @@ impl ThreadedPlugins { let help = format!( "\"{} {}\" is not a command, \ try \"{0} help\" instead.", - server.current_nickname(), + client.current_nickname(), command.tokens[0] ); - Ok(server.send_notice(&command.source, &help)?) + Ok(client + .send_notice(&command.source, &help) + .context(ErrorKind::Connection)?) } } } @@ -298,6 +305,3 @@ impl fmt::Display for ThreadedPlugins { write!(f, "{}", plugin_names.join(", ")) } } - -#[cfg(test)] -mod tests {} diff --git a/src/main.rs b/src/main.rs index 3432e3e..b9a4b8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,8 @@ extern crate r2d2; #[cfg(feature = "mysql")] extern crate r2d2_diesel; +#[macro_use] +extern crate failure; #[macro_use] extern crate log; @@ -27,9 +29,16 @@ use log::{Level, LevelFilter, Metadata, Record}; use irc::client::reactor::IrcReactor; use glob::glob; -use frippy::plugins; +pub use frippy::plugins::help::Help; +pub use frippy::plugins::url::Url; +pub use frippy::plugins::emoji::Emoji; +pub use frippy::plugins::tell::Tell; +pub use frippy::plugins::currency::Currency; +pub use frippy::plugins::keepnick::KeepNick; +pub use frippy::plugins::factoids::Factoids; + use frippy::Config; -use frippy::error::FrippyError; +use failure::Error; #[cfg(feature = "mysql")] embed_migrations!(); @@ -70,11 +79,14 @@ static LOGGER: Logger = Logger; fn main() { // Print any errors that caused frippy to shut down if let Err(e) = run() { - frippy::utils::log_error(e); + let text = e.causes() + .skip(1) + .fold(format!("{}", e), |acc, err| format!("{}: {}", acc, err)); + error!("{}", text); }; } -fn run() -> Result<(), FrippyError> { +fn run() -> Result<(), Error> { log::set_max_level(if cfg!(debug_assertions) { LevelFilter::Debug } else { @@ -100,7 +112,7 @@ fn run() -> Result<(), FrippyError> { // Without configs the bot would just idle if configs.is_empty() { - return Err(FrippyError::MissingConfig); + bail!("No config file was found"); } // Create an event loop to run the connections on. @@ -119,11 +131,11 @@ fn run() -> Result<(), FrippyError> { } let mut bot = frippy::Bot::new(); - bot.add_plugin(plugins::Help::new()); - bot.add_plugin(plugins::Url::new(1024)); - bot.add_plugin(plugins::Emoji::new()); - bot.add_plugin(plugins::Currency::new()); - bot.add_plugin(plugins::KeepNick::new()); + bot.add_plugin(Help::new()); + bot.add_plugin(Url::new(1024)); + bot.add_plugin(Emoji::new()); + bot.add_plugin(Currency::new()); + bot.add_plugin(KeepNick::new()); #[cfg(feature = "mysql")] { @@ -134,25 +146,24 @@ fn run() -> Result<(), FrippyError> { let manager = ConnectionManager::::new(url.clone()); match r2d2::Pool::builder().build(manager) { - Ok(pool) => match embedded_migrations::run(&*pool.get()?) - { + Ok(pool) => match embedded_migrations::run(&*pool.get()?) { Ok(_) => { let pool = Arc::new(pool); - bot.add_plugin(plugins::Factoids::new(pool.clone())); - bot.add_plugin(plugins::Tell::new(pool.clone())); + bot.add_plugin(Factoids::new(pool.clone())); + bot.add_plugin(Tell::new(pool.clone())); info!("Connected to MySQL server") } Err(e) => { - bot.add_plugin(plugins::Factoids::new(HashMap::new())); - bot.add_plugin(plugins::Tell::new(HashMap::new())); + bot.add_plugin(Factoids::new(HashMap::new())); + bot.add_plugin(Tell::new(HashMap::new())); error!("Failed to run migrations: {}", e); } }, Err(e) => error!("Failed to connect to database: {}", e), } } else { - bot.add_plugin(plugins::Factoids::new(HashMap::new())); - bot.add_plugin(plugins::Tell::new(HashMap::new())); + bot.add_plugin(Factoids::new(HashMap::new())); + bot.add_plugin(Tell::new(HashMap::new())); } } #[cfg(not(feature = "mysql"))] @@ -160,8 +171,8 @@ fn run() -> Result<(), FrippyError> { if mysql_url.is_some() { error!("frippy was not built with the mysql feature") } - bot.add_plugin(plugins::Factoids::new(HashMap::new())); - bot.add_plugin(plugins::Tell::new(HashMap::new())); + bot.add_plugin(Factoids::new(HashMap::new())); + bot.add_plugin(Tell::new(HashMap::new())); } if let Some(disabled_plugins) = disabled_plugins { diff --git a/src/plugin.rs b/src/plugin.rs index f33fa80..e57f072 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,16 +2,17 @@ use std::fmt; use irc::client::prelude::*; -use irc::error::IrcError; +use error::FrippyError; /// Describes if a [`Plugin`](trait.Plugin.html) is done working on a /// [`Message`](../../irc/proto/message/struct.Message.html) or if another thread is required. #[derive(Debug)] pub enum ExecutionStatus { - /// The [`Plugin`](trait.Plugin.html) does not need to do any more work on this [`Message`](../../irc/proto/message/struct.Message.html). + /// The [`Plugin`](trait.Plugin.html) does not need to do any more work on this + /// [`Message`](../../irc/proto/message/struct.Message.html). Done, /// An error occured during the execution. - Err(Box), + Err(FrippyError), /// The execution needs to be done by [`execute_threaded()`](trait.Plugin.html#tymethod.execute_threaded). RequiresThread, } @@ -19,15 +20,17 @@ pub enum ExecutionStatus { /// `Plugin` has to be implemented for any struct that should be usable /// as a `Plugin` in frippy. pub trait Plugin: PluginName + Send + Sync + fmt::Debug { - /// Handles messages which are not commands or returns [`RequiresThread`](enum.ExecutionStatus.html#variant.RequiresThread) + /// Handles messages which are not commands or returns + /// [`RequiresThread`](enum.ExecutionStatus.html#variant.RequiresThread) /// if [`execute_threaded()`](trait.Plugin.html#tymethod.execute_threaded) should be used instead. - fn execute(&self, server: &IrcClient, message: &Message) -> ExecutionStatus; + fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus; /// Handles messages which are not commands in a new thread. - fn execute_threaded(&self, server: &IrcClient, message: &Message) -> Result<(), IrcError>; + fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError>; /// Handles any command directed at this plugin. - fn command(&self, server: &IrcClient, command: PluginCommand) -> Result<(), IrcError>; - /// Similar to [`command()`](trait.Plugin.html#tymethod.command) but return a String instead of sending messages directly to IRC. - fn evaluate(&self, server: &IrcClient, command: PluginCommand) -> Result; + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError>; + /// Similar to [`command()`](trait.Plugin.html#tymethod.command) but return a String instead of + /// sending messages directly to IRC. + fn evaluate(&self, client: &IrcClient, command: PluginCommand) -> Result; } /// `PluginName` is required by [`Plugin`](trait.Plugin.html). diff --git a/src/plugins/currency.rs b/src/plugins/currency.rs index 958c8e2..53a245c 100644 --- a/src/plugins/currency.rs +++ b/src/plugins/currency.rs @@ -6,7 +6,6 @@ use std::io::Read; use std::num::ParseFloatError; use irc::client::prelude::*; -use irc::error::IrcError; use self::reqwest::Client; use self::reqwest::header::Connection; @@ -14,6 +13,10 @@ use self::serde_json::Value; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::ResultExt; + #[derive(PluginName, Default, Debug)] pub struct Currency; @@ -124,20 +127,28 @@ impl Plugin for Currency { ExecutionStatus::Done } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Currency does not implement the execute function!") } - fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), IrcError> { + fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), FrippyError> { if command.tokens.is_empty() { - return client.send_notice(&command.source, &self.invalid_command(client)); + return Ok(client + .send_notice(&command.source, &self.invalid_command(client)) + .context(FrippyErrorKind::Connection)?); } match command.tokens[0].as_ref() { - "help" => client.send_notice(&command.source, &self.help(client)), + "help" => Ok(client + .send_notice(&command.source, &self.help(client)) + .context(FrippyErrorKind::Connection)?), _ => match self.convert(client, &mut command) { - Ok(msg) => client.send_privmsg(&command.target, &msg), - Err(msg) => client.send_notice(&command.source, &msg), + Ok(msg) => Ok(client + .send_privmsg(&command.target, &msg) + .context(FrippyErrorKind::Connection)?), + Err(msg) => Ok(client + .send_notice(&command.source, &msg) + .context(FrippyErrorKind::Connection)?), }, } } @@ -153,6 +164,3 @@ impl Plugin for Currency { } } } - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/emoji.rs b/src/plugins/emoji.rs index a4276f4..f1d9376 100644 --- a/src/plugins/emoji.rs +++ b/src/plugins/emoji.rs @@ -3,10 +3,14 @@ extern crate unicode_names; use std::fmt; use irc::client::prelude::*; -use irc::error::IrcError; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::Fail; +use failure::ResultExt; + struct EmojiHandle { symbol: char, count: i32, @@ -100,21 +104,23 @@ impl Plugin for Emoji { .send_privmsg(message.response_target().unwrap(), &self.emoji(content)) { Ok(_) => ExecutionStatus::Done, - Err(e) => ExecutionStatus::Err(Box::new(e)), + Err(e) => ExecutionStatus::Err(e.context(FrippyErrorKind::Connection).into()), }, _ => ExecutionStatus::Done, } } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Emoji should not use threading") } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice( - &command.source, - "This Plugin does not implement any commands.", - ) + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { + Ok(client + .send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) + .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { @@ -126,6 +132,3 @@ impl Plugin for Emoji { } } } - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/factoids/database.rs b/src/plugins/factoids/database.rs index 88aa0fd..b1fe8dd 100644 --- a/src/plugins/factoids/database.rs +++ b/src/plugins/factoids/database.rs @@ -13,13 +13,12 @@ use diesel::mysql::MysqlConnection; use r2d2::Pool; #[cfg(feature = "mysql")] use r2d2_diesel::ConnectionManager; +#[cfg(feature = "mysql")] +use failure::ResultExt; use chrono::NaiveDateTime; -pub enum DbResponse { - Success, - Failed(&'static str), -} +use super::error::*; #[cfg_attr(feature = "mysql", derive(Queryable))] #[derive(Clone, Debug)] @@ -42,15 +41,15 @@ pub struct NewFactoid<'a> { } pub trait Database: Send { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse; - fn get_factoid(&self, name: &str, idx: i32) -> Option; - fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse; - fn count_factoids(&self, name: &str) -> Result; + 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> { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse { + fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError> { let factoid = Factoid { name: String::from(factoid.name), idx: factoid.idx, @@ -61,23 +60,25 @@ impl Database for HashMap<(String, i32), Factoid> { let name = factoid.name.clone(); match self.insert((name, factoid.idx), factoid) { - None => DbResponse::Success, - Some(_) => DbResponse::Failed("Factoid was overwritten"), + None => Ok(()), + Some(_) => Err(ErrorKind::Duplicate)?, } } - fn get_factoid(&self, name: &str, idx: i32) -> Option { - self.get(&(String::from(name), idx)).cloned() + fn get_factoid(&self, name: &str, idx: i32) -> Result { + Ok(self.get(&(String::from(name), idx)) + .cloned() + .ok_or(ErrorKind::NotFound)?) } - fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse { + fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError> { match self.remove(&(String::from(name), idx)) { - Some(_) => DbResponse::Success, - None => DbResponse::Failed("Factoid not found"), + Some(_) => Ok(()), + None => Err(ErrorKind::NotFound)?, } } - fn count_factoids(&self, name: &str) -> Result { + fn count_factoids(&self, name: &str) -> Result { Ok(self.iter().filter(|&(&(ref n, _), _)| n == name).count() as i32) } } @@ -102,38 +103,31 @@ use self::schema::factoids; #[cfg(feature = "mysql")] impl Database for Arc>> { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse { + fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError> { use diesel; - let conn = &*self.get().expect("Failed to get connection"); - match diesel::insert_into(factoids::table) + let conn = &*self.get().context(ErrorKind::NoConnection)?; + diesel::insert_into(factoids::table) .values(factoid) .execute(conn) - { - Ok(_) => DbResponse::Success, - Err(e) => { - error!("DB Insertion Error: {}", e); - DbResponse::Failed("Failed to add factoid") - } - } + .context(ErrorKind::MysqlError)?; + + Ok(()) } - fn get_factoid(&self, name: &str, idx: i32) -> Option { - let conn = &*self.get().expect("Failed to get connection"); - match factoids::table.find((name, idx)).first(conn) { - Ok(f) => Some(f), - Err(e) => { - error!("DB Count Error: {}", e); - None - } - } + 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) -> DbResponse { + fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError> { use diesel; use self::factoids::columns; - let conn = &*self.get().expect("Failed to get connection"); + let conn = &*self.get().context(ErrorKind::NoConnection)?; match diesel::delete( factoids::table .filter(columns::name.eq(name)) @@ -142,22 +136,19 @@ impl Database for Arc>> { { Ok(v) => { if v > 0 { - DbResponse::Success + Ok(()) } else { - DbResponse::Failed("Could not find any factoid with that name") + Err(ErrorKind::NotFound)? } } - Err(e) => { - error!("DB Deletion Error: {}", e); - DbResponse::Failed("Failed to delete factoid") - } + Err(e) => Err(e).context(ErrorKind::MysqlError)?, } } - fn count_factoids(&self, name: &str) -> Result { + fn count_factoids(&self, name: &str) -> Result { use diesel; - let conn = &*self.get().expect("Failed to get connection"); + let conn = &*self.get().context(ErrorKind::NoConnection)?; let count: Result = factoids::table .filter(factoids::columns::name.eq(name)) .count() @@ -166,10 +157,7 @@ impl Database for Arc>> { match count { Ok(c) => Ok(c as i32), Err(diesel::NotFound) => Ok(0), - Err(e) => { - error!("DB Count Error: {}", e); - Err("Database Error") - } + Err(e) => Err(e).context(ErrorKind::MysqlError)?, } } } diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index 806bb7e..2f3690f 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -5,21 +5,22 @@ use std::str::FromStr; use std::sync::Mutex; use self::rlua::prelude::*; use irc::client::prelude::*; -use irc::error::IrcError; -use error::FrippyError; -use error::PluginError; -use failure::Fail; use time; use chrono::NaiveDateTime; use plugin::*; pub mod database; -use self::database::{Database, DbResponse}; +use self::database::Database; mod utils; use self::utils::*; +use failure::ResultExt; +use error::ErrorKind as FrippyErrorKind; +use error::FrippyError; +use self::error::*; + static LUA_SANDBOX: &'static str = include_str!("sandbox.lua"); #[derive(PluginName)] @@ -43,8 +44,13 @@ impl Factoids { } } - fn create_factoid(&self, name: &str, content: &str, author: &str) -> Result<&str, FrippyError> { - let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() })?; + fn create_factoid( + &self, + name: &str, + content: &str, + author: &str, + ) -> Result<&str, FactoidsError> { + let count = try_lock!(self.factoids).count_factoids(name)?; let tm = time::now().to_timespec(); let factoid = database::NewFactoid { @@ -55,15 +61,14 @@ impl Factoids { created: NaiveDateTime::from_timestamp(tm.sec, 0u32), }; - match try_lock!(self.factoids).insert_factoid(&factoid) { - DbResponse::Success => Ok("Successfully added"), - DbResponse::Failed(e) => Err(PluginError::Factoids { error: e.to_owned() })?, - } + Ok(try_lock!(self.factoids) + .insert_factoid(&factoid) + .map(|()| "Successfully added!")?) } - fn add(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<&str, FrippyError> { + fn add(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { if command.tokens.len() < 2 { - return Ok(self.invalid_command(client, command).map(|()| "")?); + Err(ErrorKind::InvalidCommand)?; } let name = command.tokens.remove(0); @@ -72,45 +77,41 @@ impl Factoids { Ok(self.create_factoid(&name, &content, &command.source)?) } - fn add_from_url( - &self, - client: &IrcClient, - command: &mut PluginCommand, - ) -> Result<&str, FrippyError> { + fn add_from_url(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { if command.tokens.len() < 2 { - return Ok(self.invalid_command(client, command).map(|()| "")?); + Err(ErrorKind::InvalidCommand)?; } let name = command.tokens.remove(0); let url = &command.tokens[0]; - let content = ::utils::download(url, Some(1024))?; + let content = ::utils::download(url, Some(1024)).context(ErrorKind::Download)?; Ok(self.create_factoid(&name, &content, &command.source)?) } - fn remove(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<&str, FrippyError> { + fn remove(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { if command.tokens.len() < 1 { - return Ok(self.invalid_command(client, command).map(|()| "")?); + Err(ErrorKind::InvalidCommand)?; } let name = command.tokens.remove(0); - let count = try_lock!(self.factoids).count_factoids(&name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; + let count = try_lock!(self.factoids).count_factoids(&name)?; match try_lock!(self.factoids).delete_factoid(&name, count - 1) { - DbResponse::Success => Ok("Successfully removed"), - DbResponse::Failed(e) => Err(PluginError::Factoids { error: e.to_owned() })?, + Ok(()) => Ok("Successfully removed"), + Err(e) => Err(e)?, } } - fn get(&self, client: &IrcClient, command: &PluginCommand) -> Result { + fn get(&self, command: &PluginCommand) -> Result { let (name, idx) = match command.tokens.len() { - 0 => return Ok(self.invalid_command(client, command).map(|()| String::new())?), + 0 => Err(ErrorKind::InvalidCommand)?, 1 => { let name = &command.tokens[0]; - let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; + let count = try_lock!(self.factoids).count_factoids(name)?; if count < 1 { - Err(PluginError::Factoids { error: format!("{} does not exist", name) })?; + Err(ErrorKind::NotFound)?; } (name, count - 1) @@ -119,61 +120,55 @@ impl Factoids { let name = &command.tokens[0]; let idx = match i32::from_str(&command.tokens[1]) { Ok(i) => i, - Err(_) => Err(PluginError::Factoids { error: String::from("Invalid index") })?, + Err(_) => Err(ErrorKind::InvalidCommand)?, }; (name, idx) } }; - let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { - Some(v) => v, - None => Err(PluginError::Factoids { error: format!("{}~{} does not exist", name, idx) })?, - }; + let factoid = try_lock!(self.factoids) + .get_factoid(name, idx) + .context(ErrorKind::NotFound)?; let message = factoid.content.replace("\n", "|").replace("\r", ""); Ok(format!("{}: {}", factoid.name, message)) } - fn info(&self, client: &IrcClient, command: &PluginCommand) -> Result { + fn info(&self, command: &PluginCommand) -> Result { match command.tokens.len() { - 0 => Ok(self.invalid_command(client, command).map(|()| String::new())?), + 0 => Err(ErrorKind::InvalidCommand)?, 1 => { let name = &command.tokens[0]; - let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; + let count = try_lock!(self.factoids).count_factoids(name)?; Ok(match count { - 0 => Err(PluginError::Factoids { error: format!("{} does not exist", name) })?, + 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]).map_err(|_| PluginError::Factoids { error: String::from("Invalid index") })?; - - let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { - Some(v) => v, - None => return Ok(format!("{}~{} does not exist", name, idx)), - }; + let idx = i32::from_str(&command.tokens[1]).context(ErrorKind::InvalidIndex)?; + let factoid = try_lock!(self.factoids).get_factoid(name, idx)?; - Ok(format!("{}: Added by {} at {} UTC", name, factoid.author, factoid.created)) + Ok(format!( + "{}: Added by {} at {} UTC", + name, factoid.author, factoid.created + )) } } } - fn exec( - &self, - client: &IrcClient, - mut command: PluginCommand, - ) -> Result { + fn exec(&self, mut command: PluginCommand) -> Result { if command.tokens.len() < 1 { - Ok(self.invalid_command(client, &command).map(|()| String::new())?) + Err(ErrorKind::InvalidIndex)? } else { let name = command.tokens.remove(0); - let count = try_lock!(self.factoids).count_factoids(&name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; - let factoid = try_lock!(self.factoids).get_factoid(&name, count - 1).ok_or(PluginError::Factoids { error: format!("The factoid \"{}\" does not exist", name) })?; + let count = try_lock!(self.factoids).count_factoids(&name)?; + let factoid = try_lock!(self.factoids).get_factoid(&name, count - 1)?; let content = factoid.content; let value = if content.starts_with('>') { @@ -225,10 +220,6 @@ impl Factoids { Ok(output.join("|")) } - - fn invalid_command(&self, client: &IrcClient, command: &PluginCommand) -> Result<(), IrcError> { - client.send_notice(&command.source, "Invalid Command") - } } impl Plugin for Factoids { @@ -243,7 +234,7 @@ impl Plugin for Factoids { } } - fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError> { if let Command::PRIVMSG(_, mut content) = message.command.clone() { content.remove(0); @@ -255,18 +246,22 @@ impl Plugin for Factoids { tokens: t, }; - match self.exec(client, c) { - Ok(f) => client.send_privmsg(&message.response_target().unwrap(), &f), - Err(_) => Ok(()), - } + Ok(match self.exec(c) { + Ok(f) => client + .send_privmsg(&message.response_target().unwrap(), &f) + .context(FrippyErrorKind::Connection)?, + Err(_) => (), + }) } else { Ok(()) } } - fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), IrcError> { + fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), FrippyError> { if command.tokens.is_empty() { - return self.invalid_command(client, &command); + return Ok(client + .send_notice(&command.target, "Invalid command") + .context(FrippyErrorKind::Connection)?); } let target = command.target.clone(); @@ -274,19 +269,27 @@ impl Plugin for Factoids { let sub_command = command.tokens.remove(0); let result = match sub_command.as_ref() { - "add" => self.add(client, &mut command).map(|s| s.to_owned()), - "fromurl" => self.add_from_url(client, &mut command).map(|s| s.to_owned()), - "remove" => self.remove(client, &mut command).map(|s| s.to_owned()), - "get" => self.get(client, &command), - "info" => self.info(client, &command), - "exec" => self.exec(client, command), - _ => self.invalid_command(client, &command).map(|()| String::new()).map_err(|e| e.into()), + "add" => self.add(&mut command).map(|s| s.to_owned()), + "fromurl" => self.add_from_url(&mut command).map(|s| s.to_owned()), + "remove" => self.remove(&mut command).map(|s| s.to_owned()), + "get" => self.get(&command), + "info" => self.info(&command), + "exec" => self.exec(command), + _ => Err(ErrorKind::InvalidCommand.into()), }; Ok(match result { - Ok(v) => client.send_privmsg(&target, &v), - Err(e) => client.send_notice(&source, &e.cause().unwrap().to_string()), - }?) + Ok(v) => client + .send_privmsg(&target, &v) + .context(FrippyErrorKind::Connection)?, + Err(e) => { + let message = e.to_string(); + client + .send_notice(&source, &message) + .context(FrippyErrorKind::Connection)?; + Err(e).context(FrippyErrorKind::Factoids)? + } + }) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { @@ -301,3 +304,39 @@ impl fmt::Debug for Factoids { 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/utils.rs b/src/plugins/factoids/utils.rs index 009b46b..70ac8a7 100644 --- a/src/plugins/factoids/utils.rs +++ b/src/plugins/factoids/utils.rs @@ -11,7 +11,11 @@ use self::LuaError::RuntimeError; pub fn download(_: &Lua, url: String) -> Result { match utils::download(&url, Some(1024)) { Ok(v) => Ok(v), - Err(e) => Err(RuntimeError(format!("Failed to download {} - {}", url, e.to_string()))), + Err(e) => Err(RuntimeError(format!( + "Failed to download {} - {}", + url, + e.to_string() + ))), } } diff --git a/src/plugins/help.rs b/src/plugins/help.rs index 4dd93d7..7e3658d 100644 --- a/src/plugins/help.rs +++ b/src/plugins/help.rs @@ -1,8 +1,11 @@ use irc::client::prelude::*; -use irc::error::IrcError; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::ResultExt; + #[derive(PluginName, Default, Debug)] pub struct Help; @@ -17,18 +20,17 @@ impl Plugin for Help { ExecutionStatus::Done } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Help should not use threading") } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice(&command.source, "Help has not been added yet.") + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { + Ok(client + .send_notice(&command.source, "Help has not been added yet.") + .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { Err(String::from("Help has not been added yet.")) } } - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/keepnick.rs b/src/plugins/keepnick.rs index bdabd90..58ac167 100644 --- a/src/plugins/keepnick.rs +++ b/src/plugins/keepnick.rs @@ -1,8 +1,11 @@ use irc::client::prelude::*; -use irc::error::IrcError; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::ResultExt; + #[derive(PluginName, Default, Debug)] pub struct KeepNick; @@ -25,9 +28,12 @@ impl KeepNick { if client_nick != cfg_nick { info!("Trying to switch nick from {} to {}", client_nick, cfg_nick); - match client.send(Command::NICK(cfg_nick)) { + match client + .send(Command::NICK(cfg_nick)) + .context(FrippyErrorKind::Connection) + { Ok(_) => ExecutionStatus::Done, - Err(e) => ExecutionStatus::Err(Box::new(e)), + Err(e) => ExecutionStatus::Err(e.into()), } } else { ExecutionStatus::Done @@ -45,21 +51,20 @@ impl Plugin for KeepNick { } } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Tell should not use threading") } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice( - &command.source, - "This Plugin does not implement any commands.", - ) + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { + Ok(client + .send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) + .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { Err(String::from("This Plugin does not implement any commands.")) } } - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 5b32efd..9a3ba2f 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,17 +1,8 @@ //! Collection of plugins included -mod help; -mod url; -mod emoji; -mod tell; -mod currency; -mod factoids; -mod keepnick; - -pub use self::help::Help; -pub use self::url::Url; -pub use self::emoji::Emoji; -pub use self::tell::Tell; -pub use self::currency::Currency; -pub use self::factoids::Factoids; -pub use self::factoids::database; -pub use self::keepnick::KeepNick; +pub mod help; +pub mod url; +pub mod emoji; +pub mod tell; +pub mod currency; +pub mod factoids; +pub mod keepnick; diff --git a/src/plugins/tell/mod.rs b/src/plugins/tell/mod.rs index 3a8feeb..7052b3e 100644 --- a/src/plugins/tell/mod.rs +++ b/src/plugins/tell/mod.rs @@ -1,5 +1,4 @@ use irc::client::prelude::*; -use irc::error::IrcError; use std::time::Duration; use std::sync::Mutex; @@ -10,6 +9,11 @@ use humantime::format_duration; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::Fail; +use failure::ResultExt; + pub mod database; use self::database::{Database, DbResponse}; @@ -84,7 +88,7 @@ impl Tell { tell.sender, human_dur, tell.message ), ) { - return ExecutionStatus::Err(Box::new(e)); + return ExecutionStatus::Err(e.context(FrippyErrorKind::Connection).into()); } debug!( "Sent {:?} from {:?} to {:?}", @@ -123,22 +127,30 @@ impl Plugin for Tell { } } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Tell should not use threading") } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { if command.tokens.is_empty() { - return client.send_notice(&command.source, &self.invalid_command(client)); + return Ok(client + .send_notice(&command.source, &self.invalid_command(client)) + .context(FrippyErrorKind::Connection)?); } - match command.tokens[0].as_ref() { - "help" => client.send_notice(&command.source, &self.help(client)), + Ok(match command.tokens[0].as_ref() { + "help" => client + .send_notice(&command.source, &self.help(client)) + .context(FrippyErrorKind::Connection), _ => match self.tell_command(client, &command) { - Ok(msg) => client.send_notice(&command.source, msg), - Err(msg) => client.send_notice(&command.source, &msg), + Ok(msg) => client + .send_notice(&command.source, msg) + .context(FrippyErrorKind::Connection), + Err(msg) => client + .send_notice(&command.source, &msg) + .context(FrippyErrorKind::Connection), }, - } + }?) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { diff --git a/src/plugins/url.rs b/src/plugins/url.rs index 6f00466..fa4c6f4 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -2,7 +2,6 @@ extern crate regex; extern crate select; use irc::client::prelude::*; -use irc::error::IrcError; use self::regex::Regex; @@ -11,9 +10,12 @@ use self::select::predicate::Name; use plugin::*; use utils; + +use self::error::*; use error::FrippyError; -use error::UrlError; +use error::ErrorKind as FrippyErrorKind; use failure::Fail; +use failure::ResultExt; lazy_static! { static ref RE: Regex = Regex::new(r"(^|\s)(https?://\S+)").unwrap(); @@ -48,11 +50,11 @@ impl Url { Some(title_text) } - fn url(&self, text: &str) -> Result { - let url = self.grep_url(text).ok_or(UrlError::MissingUrl)?; - let body = utils::download(&url, Some(self.max_kib))?; + fn url(&self, text: &str) -> Result { + let url = self.grep_url(text).ok_or(ErrorKind::MissingUrl)?; + let body = utils::download(&url, Some(self.max_kib)).context(ErrorKind::Download)?; - Ok(self.get_title(&body).ok_or(UrlError::MissingTitle)?) + Ok(self.get_title(&body).ok_or(ErrorKind::MissingTitle)?) } } @@ -68,27 +70,48 @@ impl Plugin for Url { } } - fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), IrcError> { - match message.command { + fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError> { + Ok(match message.command { Command::PRIVMSG(_, ref content) => match self.url(content) { - Ok(title) => client.send_privmsg(message.response_target().unwrap(), &title), - Err(e) => Ok(utils::log_error(e)), + Ok(title) => client + .send_privmsg(message.response_target().unwrap(), &title) + .context(FrippyErrorKind::Connection)?, + Err(e) => Err(e).context(FrippyErrorKind::Url)?, }, - _ => Ok(()), - } + _ => (), + }) } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice( - &command.source, - "This Plugin does not implement any commands.", - ) + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { + Ok(client + .send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) + .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { - self.url(&command.tokens[0]).map_err(|e| e.cause().unwrap().to_string()) + self.url(&command.tokens[0]) + .map_err(|e| e.cause().unwrap().to_string()) } } -#[cfg(test)] -mod tests {} +pub mod error { + /// A URL plugin error + #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] + #[error = "UrlError"] + pub enum ErrorKind { + /// A download error + #[fail(display = "A download error occured")] + Download, + + /// Missing URL error + #[fail(display = "No URL was found")] + MissingUrl, + + /// Missing title error + #[fail(display = "No title was found")] + MissingTitle, + } +} diff --git a/src/utils.rs b/src/utils.rs index cf91b37..06156be 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,15 +4,19 @@ use std::io::{self, Read}; use reqwest::Client; use reqwest::header::Connection; -use failure::Fail; -use error::FrippyError; +use failure::ResultExt; +use self::error::{DownloadError, ErrorKind}; /// Downloads the file and converts it to a String. /// Any invalid bytes are converted to a replacement character. /// /// The error indicated either a failed download or that the DownloadLimit was reached -pub fn download(url: &str, max_kib: Option) -> Result { - let mut response = Client::new().get(url).header(Connection::close()).send()?; +pub fn download(url: &str, max_kib: Option) -> Result { + let mut response = Client::new() + .get(url) + .header(Connection::close()) + .send() + .context(ErrorKind::Connection)?; // 100 kibibyte buffer let mut buf = [0; 100 * 1024]; @@ -25,7 +29,7 @@ pub fn download(url: &str, max_kib: Option) -> Result break, Ok(len) => len, Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, - Err(e) => Err(e)?, + Err(e) => Err(e).context(ErrorKind::Read)?, }; bytes.extend_from_slice(&buf); @@ -34,7 +38,7 @@ pub fn download(url: &str, max_kib: Option) -> Result max_kib * 1024 { - Err(FrippyError::DownloadLimit { limit: max_kib })?; + Err(ErrorKind::DownloadLimit)?; } } } @@ -42,12 +46,20 @@ pub fn download(url: &str, max_kib: Option) -> Result