diff options
Diffstat (limited to 'poezio/plugin_manager.py')
-rw-r--r-- | poezio/plugin_manager.py | 99 |
1 files changed, 79 insertions, 20 deletions
diff --git a/poezio/plugin_manager.py b/poezio/plugin_manager.py index 89849747..17673a9e 100644 --- a/poezio/plugin_manager.py +++ b/poezio/plugin_manager.py @@ -5,10 +5,13 @@ the API together. Defines also a bunch of variables related to the plugin env. """ +import logging import os -from os import path +from typing import Dict, Set +from importlib import import_module, machinery from pathlib import Path -import logging +from os import path +import pkg_resources from poezio import tabs, xdg from poezio.core.structs import Command, Completion @@ -25,6 +28,8 @@ class PluginManager: And keeps track of everything the plugin has done through the API. """ + rdeps: Dict[str, Set[str]] = {} + def __init__(self, core): self.core = core # module name -> module object @@ -44,7 +49,6 @@ class PluginManager: self.tab_keys = {} self.roster_elements = {} - from importlib import machinery self.finder = machinery.PathFinder() self.initial_set_plugins_dir() @@ -57,21 +61,56 @@ class PluginManager: for plugin in set(self.plugins.keys()): self.unload(plugin, notify=False) - def load(self, name, notify=True): + def set_rdeps(self, name): + """ + Runs through plugin dependencies to build the reverse dependencies table. + """ + + if name not in self.rdeps: + self.rdeps[name] = set() + for dep in self.plugins[name].dependencies: + if dep not in self.rdeps: + self.rdeps[dep] = {name} + else: + self.rdeps[dep].add(name) + + def load(self, name: str, notify=True, unload_first=True): """ Load a plugin. """ + if not unload_first and name in self.plugins: + return None if name in self.plugins: self.unload(name) try: module = None loader = self.finder.find_module(name, self.load_path) - if not loader: + if loader: + log.debug('Found candidate loader for plugin %s: %r', name, loader) + module = loader.load_module() + if module is None: + log.debug('Failed to load plugin %s from loader', name) + else: + try: + module = import_module('poezio_plugins.%s' % name) + except ModuleNotFoundError: + pass + for entry in pkg_resources.iter_entry_points('poezio_plugins'): + if entry.name == name: + log.debug('Found candidate entry for plugin %s: %r', name, entry) + try: + module = entry.load() + except Exception as exn: + log.debug('Failed to import plugin: %s\n%r', name, + exn, exc_info=True) + finally: + break + if not module: self.core.information('Could not find plugin: %s' % name, 'Error') return - module = loader.load_module() + log.debug('Plugin %s loaded from "%s"', name, module.__file__) 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), @@ -88,8 +127,22 @@ class PluginManager: self.event_handlers[name] = [] try: self.plugins[name] = None - self.plugins[name] = module.Plugin(self.plugin_api, self.core, + + for dep in module.Plugin.dependencies: + self.load(dep, unload_first=False) + if dep not in self.plugins: + log.debug( + 'Plugin %s couldn\'t load because of dependency %s', + name, dep + ) + return None + # Add reference of the dep to the plugin's usage + module.Plugin.refs[dep] = self.plugins[dep] + + self.plugins[name] = module.Plugin(name, self.plugin_api, self.core, self.plugins_conf_dir) + self.set_rdeps(name) + except Exception as e: log.error('Error while loading the plugin %s', name, exc_info=True) if notify: @@ -100,9 +153,22 @@ class PluginManager: if notify: self.core.information('Plugin %s loaded' % name, 'Info') - def unload(self, name, notify=True): + def unload(self, name: str, notify=True): + """ + Unloads plugin as well as plugins depending on it. + """ + if name in self.plugins: try: + if self.plugins[name] is not None: + self.plugins[name]._unloading = True # Prevents loops + for rdep in self.rdeps[name].copy(): + if rdep in self.plugins and not self.plugins[rdep]._unloading: + self.unload(rdep) + if rdep in self.plugins: + log.debug('Failed to unload reverse dependency %s first.', rdep) + return None + for command in self.commands[name].keys(): del self.core.commands[command] for key in self.keys[name].keys(): @@ -122,6 +188,7 @@ class PluginManager: if self.plugins[name] is not None: self.plugins[name].unload() del self.plugins[name] + del self.rdeps[name] del self.commands[name] del self.keys[name] del self.tab_commands[name] @@ -253,7 +320,7 @@ class PluginManager: if key in self.core.key_func: del self.core.commands[key] - def add_event_handler(self, module_name, event_name, handler, position=0): + def add_event_handler(self, module_name, event_name, handler, *args, **kwargs): """ Add an event handler. If event_name isn’t in the event list, assume it is a slixmpp event. @@ -261,7 +328,7 @@ class PluginManager: 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) + self.core.events.add_event_handler(event_name, handler, *args, **kwargs) else: self.core.xmpp.add_event_handler(event_name, handler) @@ -326,7 +393,7 @@ class PluginManager: """ Create the plugins_conf_dir """ - plugins_conf_dir = config.get('plugins_conf_dir') + plugins_conf_dir = config.getstr('plugins_conf_dir') self.plugins_conf_dir = Path(plugins_conf_dir).expanduser( ) if plugins_conf_dir else xdg.CONFIG_HOME / 'plugins' self.check_create_plugins_conf_dir() @@ -351,7 +418,7 @@ class PluginManager: """ Set the plugins_dir on start """ - plugins_dir = config.get('plugins_dir') + plugins_dir = config.getstr('plugins_dir') self.plugins_dir = Path(plugins_dir).expanduser( ) if plugins_dir else xdg.DATA_HOME / 'plugins' self.check_create_plugins_dir() @@ -387,11 +454,3 @@ class PluginManager: if os.access(str(self.plugins_dir), os.R_OK | os.X_OK): self.load_path.append(str(self.plugins_dir)) - - try: - import poezio_plugins - except: - pass - else: - if poezio_plugins.__path__: - self.load_path.append(list(poezio_plugins.__path__)[0]) |