From 332a5c2553db41de777473a1e1be9cd1522c9496 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 31 Mar 2016 18:54:41 +0100 Subject: Move the src directory to poezio, for better cython compatibility. --- poezio/plugin_manager.py | 384 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 poezio/plugin_manager.py (limited to 'poezio/plugin_manager.py') diff --git a/poezio/plugin_manager.py b/poezio/plugin_manager.py new file mode 100644 index 00000000..549753a9 --- /dev/null +++ b/poezio/plugin_manager.py @@ -0,0 +1,384 @@ +""" +Plugin manager module. +Define the PluginManager class, the one that glues all the plugins and +the API together. Defines also a bunch of variables related to the +plugin env. +""" + +import os +from os import path +import logging + +import core +import tabs +from plugin import PluginAPI +from config import config + +log = logging.getLogger(__name__) + +class PluginManager(object): + """ + Plugin Manager + Contains all the references to the plugins + And keeps track of everything the plugin has done through the API. + """ + def __init__(self, core): + self.core = core + # module name -> module object + self.modules = {} + # module name -> plugin object + self.plugins = {} + # module name -> dict of commands loaded for the module + self.commands = {} + # module name -> list of event_name/handler pairs loaded for the module + self.event_handlers = {} + # module name -> dict of tab types; tab type -> commands + # loaded by the module + self.tab_commands = {} + # module name → dict of keys/handlers loaded for the module + self.keys = {} + # module name → dict of tab types; tab type → list of keybinds (tuples) + self.tab_keys = {} + self.roster_elements = {} + + from importlib import machinery + self.finder = machinery.PathFinder() + + self.initial_set_plugins_dir() + self.initial_set_plugins_conf_dir() + self.fill_load_path() + + self.plugin_api = PluginAPI(core, self) + + def disable_plugins(self): + for plugin in set(self.plugins.keys()): + try: + self.unload(plugin, notify=False) + except: + pass + + def load(self, name, notify=True): + """ + Load a plugin. + """ + if name in self.plugins: + self.unload(name) + + try: + module = None + loader = self.finder.find_module(name, self.load_path) + if not loader: + self.core.information('Could not find plugin: %s' % name) + return + module = loader.load_module() + except Exception as e: + log.debug("Could not load plugin %s", name, exc_info=True) + self.core.information("Could not load plugin %s: %s" % (name, e), + 'Error') + finally: + if not module: + return + + self.modules[name] = module + self.commands[name] = {} + self.keys[name] = {} + self.tab_keys[name] = {} + self.tab_commands[name] = {} + self.event_handlers[name] = [] + try: + self.plugins[name] = None + self.plugins[name] = module.Plugin(self.plugin_api, self.core, + self.plugins_conf_dir) + except Exception as e: + log.error('Error while loading the plugin %s', name, exc_info=True) + if notify: + self.core.information('Unable to load the plugin %s: %s' % + (name, e), + 'Error') + self.unload(name, notify=False) + else: + if notify: + self.core.information('Plugin %s loaded' % name, 'Info') + + def unload(self, name, notify=True): + if name in self.plugins: + try: + for command in self.commands[name].keys(): + del self.core.commands[command] + for key in self.keys[name].keys(): + del self.core.key_func[key] + for tab in list(self.tab_commands[name].keys()): + for command in self.tab_commands[name][tab][:]: + self.del_tab_command(name, getattr(tabs, tab), + command[0]) + del self.tab_commands[name][tab] + for tab in list(self.tab_keys[name].keys()): + for key in self.tab_keys[name][tab][:]: + self.del_tab_key(name, getattr(tabs, tab), key[0]) + del self.tab_keys[name][tab] + for event_name, handler in self.event_handlers[name][:]: + self.del_event_handler(name, event_name, handler) + + if self.plugins[name] is not None: + self.plugins[name].unload() + del self.plugins[name] + del self.commands[name] + del self.keys[name] + del self.tab_commands[name] + del self.event_handlers[name] + if notify: + self.core.information('Plugin %s unloaded' % name, 'Info') + except Exception as e: + log.debug("Could not unload plugin %s", name, exc_info=True) + self.core.information("Could not unload plugin %s: %s" % + (name, e), + 'Error') + + def add_command(self, module_name, name, handler, help, + completion=None, short='', usage=''): + """ + Add a global command. + """ + if name in self.core.commands: + raise Exception("Command '%s' already exists" % (name,)) + + commands = self.commands[module_name] + commands[name] = core.Command(handler, help, completion, short, usage) + self.core.commands[name] = commands[name] + + def del_command(self, module_name, name): + """ + Remove a global command added through add_command. + """ + if name in self.commands[module_name]: + del self.commands[module_name][name] + if name in self.core.commands: + del self.core.commands[name] + + def add_tab_command(self, module_name, tab_type, name, handler, help, + completion=None, short='', usage=''): + """ + Add a command only for a type of Tab. + """ + commands = self.tab_commands[module_name] + t = tab_type.__name__ + if name in tab_type.plugin_commands: + return + if not t in commands: + commands[t] = [] + commands[t].append((name, handler, help, completion)) + tab_type.plugin_commands[name] = core.Command(handler, help, + completion, short, usage) + for tab in self.core.tabs: + if isinstance(tab, tab_type): + tab.update_commands() + + def del_tab_command(self, module_name, tab_type, name): + """ + Remove a command added through add_tab_command. + """ + commands = self.tab_commands[module_name] + t = tab_type.__name__ + if not t in commands: + return + for command in commands[t]: + if command[0] == name: + commands[t].remove(command) + del tab_type.plugin_commands[name] + for tab in self.core.tabs: + if isinstance(tab, tab_type) and name in tab.commands: + del tab.commands[name] + + def add_tab_key(self, module_name, tab_type, key, handler): + """ + Associate a key binding to a handler only for a type of Tab. + """ + keys = self.tab_keys[module_name] + t = tab_type.__name__ + if key in tab_type.plugin_keys: + return + if not t in keys: + keys[t] = [] + keys[t].append((key, handler)) + tab_type.plugin_keys[key] = handler + for tab in self.core.tabs: + if isinstance(tab, tab_type): + tab.update_keys() + + def del_tab_key(self, module_name, tab_type, key): + """ + Remove a key binding added through add_tab_key. + """ + keys = self.tab_keys[module_name] + t = tab_type.__name__ + if not t in keys: + return + for _key in keys[t]: + if _key[0] == key: + keys[t].remove(_key) + del tab_type.plugin_keys[key] + for tab in self.core.tabs: + if isinstance(tab, tab_type) and key in tab.key_func: + del tab.key_func[key] + + def add_key(self, module_name, key, handler): + """ + Associate a global key binding to a handler, except if it + already exists. + """ + if key in self.core.key_func: + raise Exception("Key '%s' already exists" % (key,)) + keys = self.keys[module_name] + keys[key] = handler + self.core.key_func[key] = handler + + def del_key(self, module_name, key): + """ + Remove a global key binding added by a plugin. + """ + if key in self.keys[module_name]: + del self.keys[module_name][key] + if key in self.core.key_func: + del self.core.commands[key] + + def add_event_handler(self, module_name, event_name, handler, position=0): + """ + Add an event handler. If event_name isn’t in the event list, assume + it is a slixmpp event. + """ + eh = self.event_handlers[module_name] + eh.append((event_name, handler)) + if event_name in self.core.events.events: + self.core.events.add_event_handler(event_name, handler, position) + else: + self.core.xmpp.add_event_handler(event_name, handler) + + def del_event_handler(self, module_name, event_name, handler): + """ + Remove an event handler if it exists. + """ + if event_name in self.core.events.events: + self.core.events.del_event_handler(None, handler) + else: + self.core.xmpp.del_event_handler(event_name, handler) + eh = self.event_handlers[module_name] + eh = list(filter(lambda e: e != (event_name, handler), eh)) + + def completion_load(self, the_input): + """ + completion function that completes the name of the plugins, from + all .py files in plugins_dir + """ + try: + names = set() + for path in self.load_path: + try: + add = set(os.listdir(path)) + names |= add + except: + pass + except OSError as e: + self.core.information('Completion failed: %s' % e, 'Error') + return + plugins_files = [name[:-3] for name in names if name.endswith('.py') + and name != '__init__.py' and not name.startswith('.')] + plugins_files.sort() + position = the_input.get_argument_position(quoted=False) + return the_input.new_completion(plugins_files, position, '', + quotify=False) + + def completion_unload(self, the_input): + """ + completion function that completes the name of loaded plugins + """ + position = the_input.get_argument_position(quoted=False) + return the_input.new_completion(sorted(self.plugins.keys()), position, + '', quotify=False) + + def on_plugins_dir_change(self, new_value): + self.plugins_dir = new_value + self.check_create_plugins_dir() + self.fill_load_path() + + def on_plugins_conf_dir_change(self, new_value): + self.plugins_conf_dir = new_value + self.check_create_plugins_conf_dir() + + def initial_set_plugins_conf_dir(self): + """ + Create the plugins_conf_dir + """ + plugins_conf_dir = config.get('plugins_conf_dir') + if not plugins_conf_dir: + config_home = os.environ.get('XDG_CONFIG_HOME') + if not config_home: + config_home = os.path.join(os.environ.get('HOME'), '.config') + plugins_conf_dir = os.path.join(config_home, 'poezio', 'plugins') + self.plugins_conf_dir = os.path.expanduser(plugins_conf_dir) + self.check_create_plugins_conf_dir() + + def check_create_plugins_conf_dir(self): + """ + Create the plugins config directory if it does not exist. + Returns True on success, False on failure. + """ + if not os.access(self.plugins_conf_dir, os.R_OK | os.X_OK): + try: + os.makedirs(self.plugins_conf_dir) + except OSError: + log.error('Unable to create the plugin conf dir: %s', + self.plugins_conf_dir, exc_info=True) + return False + return True + + def initial_set_plugins_dir(self): + """ + Set the plugins_dir on start + """ + plugins_dir = config.get('plugins_dir') + plugins_dir = plugins_dir or\ + os.path.join(os.environ.get('XDG_DATA_HOME') or\ + os.path.join(os.environ.get('HOME'), + '.local', 'share'), + 'poezio', 'plugins') + self.plugins_dir = os.path.expanduser(plugins_dir) + self.check_create_plugins_dir() + + def check_create_plugins_dir(self): + """ + Create the plugins directory if it does not exist. + Returns True on success, False on failure. + """ + if not os.access(self.plugins_dir, os.R_OK | os.X_OK): + try: + os.makedirs(self.plugins_dir, exist_ok=True) + except OSError: + log.error('Unable to create the plugins dir: %s', + self.plugins_dir, exc_info=True) + return False + return True + + def fill_load_path(self): + """ + Append the global packages and the source directory if available + """ + + self.load_path = [] + + default_plugin_path = path.join(path.dirname(path.dirname(__file__)), + 'plugins') + + if os.access(default_plugin_path, os.R_OK | os.X_OK): + self.load_path.insert(0, default_plugin_path) + + if os.access(self.plugins_dir, os.R_OK | os.X_OK): + self.load_path.append(self.plugins_dir) + + try: + import poezio_plugins + except: + pass + else: + if poezio_plugins.__path__: + self.load_path.append(list(poezio_plugins.__path__)[0]) + -- cgit v1.2.3