summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJokler <jokler.contact@gmail.com>2017-10-27 15:39:06 +0200
committerJokler <jokler.contact@gmail.com>2017-10-27 15:39:06 +0200
commit0139861d1e41a765b6ffde7cae37c1e16f9c053c (patch)
tree22d6f26b998edeb06e01ad80ada2fd92ff4b4fab
parent4c6f2f088521cf0876b67f21a25e524983b7ada7 (diff)
downloadfrippy-0139861d1e41a765b6ffde7cae37c1e16f9c053c.tar.gz
frippy-0139861d1e41a765b6ffde7cae37c1e16f9c053c.zip
Refactor plugin management
ThreadedPlugins should take care of anything Plugin related now.
-rw-r--r--src/lib.rs150
-rw-r--r--src/plugin.rs148
2 files changed, 164 insertions, 134 deletions
diff --git a/src/lib.rs b/src/lib.rs
index da36d91..55b121e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -24,29 +24,17 @@ extern crate glob;
mod plugin;
mod plugins;
-use std::thread::spawn;
-use std::sync::{Arc, Mutex};
-use glob::glob;
+use std::sync::Arc;
use irc::client::prelude::*;
-use irc::proto::Command::PRIVMSG;
use irc::error::Error as IrcError;
use tokio_core::reactor::Core;
use futures::future;
+use glob::glob;
use plugin::*;
-// Lock the mutex and ignore if it is poisoned
-macro_rules! lock_plugin {
- ($e:expr) => {
- match $e.lock() {
- Ok(plugin) => plugin,
- Err(poisoned) => poisoned.into_inner(),
- }
- }
-}
-
/// Runs the bot
///
/// # Remarks
@@ -76,18 +64,10 @@ pub fn run() {
}
// The list of plugins in use
- let plugins: Vec<Arc<Mutex<Plugin>>> =
- vec![Arc::new(Mutex::new(plugins::emoji::Emoji::new())),
- Arc::new(Mutex::new(plugins::currency::Currency::new()))];
-
- // We need the plugins' names to make sure the user gets a response
- // if they use an incorrect plugin name
- let plugin_names: Vec<String> = plugins
- .iter()
- .map(|p| lock_plugin!(p).name().to_lowercase())
- .collect();
-
- info!("Plugins loaded: {}", plugin_names.join(", "));
+ let mut plugins = ThreadedPlugins::new();
+ plugins.add(plugins::emoji::Emoji::new());
+ plugins.add(plugins::currency::Currency::new());
+ info!("Plugins loaded: {}", plugins);
// Create an event loop to run the connections on.
let mut reactor = Core::new().unwrap();
@@ -110,13 +90,12 @@ pub fn run() {
Err(e) => error!("Failed to identify: {}", e),
};
- // TODO Duplicate clone...
+ // TODO Verify if we actually need to clone plugins twice
let plugins = plugins.clone();
- let plugin_names = plugin_names.clone();
let task = server
.stream()
- .for_each(move |message| process_msg(&server, &plugin_names, plugins.clone(), message))
+ .for_each(move |message| process_msg(&server, plugins.clone(), message))
.map_err(|e| error!("Failed to process message: {}", e));
reactor.handle().spawn(task);
@@ -127,8 +106,7 @@ pub fn run() {
}
fn process_msg(server: &IrcServer,
- plugin_names: &[String],
- plugins: Vec<Arc<Mutex<Plugin>>>,
+ mut plugins: ThreadedPlugins,
message: Message)
-> Result<(), IrcError> {
@@ -138,118 +116,22 @@ fn process_msg(server: &IrcServer,
}
}
- let message = Arc::new(message);
// Check for possible command and save the result for later
- let command = get_command(&server.current_nickname().to_lowercase(), &message);
+ let command = PluginCommand::from(&server.current_nickname().to_lowercase(), &message);
- // Check if the first token of the command is valid
- if let Some(ref c) = command {
- if c.tokens.is_empty() {
- let help = format!("Use \"{} help\" to get help", server.current_nickname());
- server.send_notice(&c.source, &help)?;
-
- } else if "help" == &c.tokens[0].to_lowercase() {
- send_help_message(server, c)?;
-
- } else if !plugin_names.contains(&c.tokens[0].to_lowercase()) {
-
- let help = format!("\"{} {}\" is not a command, \
- try \"{0} help\" instead.",
- server.current_nickname(),
- c.tokens[0]);
+ let message = Arc::new(message);
+ plugins.execute_plugins(server, message);
- server.send_notice(&c.source, &help)?;
+ // If the message contained a command, handle it
+ if let Some(command) = command {
+ if let Err(e) = plugins.handle_command(server, command) {
+ error!("Failed to handle command: {}", e);
}
}
- for plugin in plugins {
- // Send the message to the plugin if the plugin needs it
- if lock_plugin!(plugin).is_allowed(server, &message) {
-
- // 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) = lock_plugin!(plugin).execute(&server, &message) {
- let name = lock_plugin!(plugin).name().to_string();
- error!("Error in {} - {}", name, e);
- };
- });
- }
-
- // Check if the command is for this plugin
- if let Some(mut c) = command.clone() {
-
- // Skip empty commands
- if c.tokens.is_empty() {
- continue;
- }
-
- if lock_plugin!(plugin).name().to_lowercase() == c.tokens[0].to_lowercase() {
-
- // The first token contains the name of the plugin
- let name = c.tokens.remove(0);
-
- // Clone the server for the move - it uses an Arc internally
- let server = server.clone();
- spawn(move || {
- if let Err(e) = lock_plugin!(plugin).command(&server, c) {
- error!("Error in {} - {}", name, e);
- };
- });
- }
- }
- }
Ok(())
}
-fn send_help_message(server: &IrcServer, command: &PluginCommand) -> Result<(), IrcError> {
- server.send_notice(&command.source, "Help has not been added yet.")
-}
-
-fn get_command(nick: &str, message: &Message) -> Option<PluginCommand> {
-
- // Get the actual message out of PRIVMSG
- if let PRIVMSG(_, ref content) = message.command {
-
- // Split content by spaces and filter empty tokens
- let mut tokens: Vec<String> = 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();
- if !tokens[0].is_empty() {
- return None;
- }
-
- // The first token contained the name of the bot
- tokens.remove(0);
-
- Some(PluginCommand {
- source: message.source_nickname().unwrap().to_string(),
- target: message.response_target().unwrap().to_string(),
- tokens: tokens,
- })
- } else {
- None
- }
- } else {
- None
- }
-}
#[cfg(test)]
mod tests {}
diff --git a/src/plugin.rs b/src/plugin.rs
index 0a4034d..d1a1f3d 100644
--- a/src/plugin.rs
+++ b/src/plugin.rs
@@ -1,4 +1,8 @@
use std::fmt;
+use std::collections::HashMap;
+use std::thread::spawn;
+use std::sync::{Arc, Mutex};
+
use irc::client::prelude::*;
use irc::error::Error as IrcError;
@@ -18,3 +22,147 @@ pub struct PluginCommand {
pub target: String,
pub tokens: Vec<String>,
}
+
+impl PluginCommand {
+ pub fn from(nick: &str, message: &Message) -> Option<PluginCommand> {
+
+ // 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<String> = 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();
+ if !tokens[0].is_empty() {
+ return None;
+ }
+
+ // The first token contained the name of the bot
+ tokens.remove(0);
+
+ Some(PluginCommand {
+ source: message.source_nickname().unwrap().to_string(),
+ target: message.response_target().unwrap().to_string(),
+ tokens: tokens,
+ })
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+}
+
+// Lock the mutex and ignore if it is poisoned
+macro_rules! lock_plugin {
+ ($e:expr) => {
+ match $e.lock() {
+ Ok(plugin) => plugin,
+ Err(poisoned) => poisoned.into_inner(),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct ThreadedPlugins {
+ plugins: HashMap<String, Arc<Mutex<Plugin>>>,
+}
+
+impl ThreadedPlugins {
+ pub fn new() -> ThreadedPlugins {
+ ThreadedPlugins { plugins: HashMap::new() }
+ }
+
+ pub fn add<T: Plugin + 'static>(&mut self, plugin: T) {
+ let name = plugin.name().to_lowercase();
+ let safe_plugin = Arc::new(Mutex::new(plugin));
+
+ self.plugins.insert(name, safe_plugin);
+ }
+
+ pub fn execute_plugins(&mut self, server: &IrcServer, message: Arc<Message>) {
+
+ for (name, plugin) in self.plugins.clone() {
+ // Send the message to the plugin if the plugin needs it
+ if lock_plugin!(plugin).is_allowed(server, &message) {
+
+ // 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) = lock_plugin!(plugin).execute(&server, &message) {
+ error!("Error in {} - {}", name, e);
+ };
+ });
+ }
+ }
+ }
+
+ pub fn handle_command(&mut self, server: &IrcServer, 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);
+ }
+
+ if &command.tokens[0].to_lowercase() == "help" {
+ return self.send_help_message(server, &command);
+ }
+
+ // 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);
+
+ // Clone for the move - the server uses an Arc internally
+ let server = server.clone();
+ let plugin = Arc::clone(plugin);
+ spawn(move || {
+ if let Err(e) = lock_plugin!(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]);
+
+ server.send_notice(&command.source, &help)
+ }
+ }
+
+ fn send_help_message(&self, server: &IrcServer, command: &PluginCommand) -> Result<(), IrcError> {
+ server.send_notice(&command.source, "Help has not been added yet.")
+ }
+}
+
+impl fmt::Display for ThreadedPlugins {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let plugin_names = self.plugins
+ .iter()
+ .map(|(_, p)| lock_plugin!(p).name().to_string())
+ .collect::<Vec<String>>();
+ write!(f, "{}", plugin_names.join(", "))
+ }
+}