summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--poezio/core/core.py4
-rw-r--r--poezio/plugin.py6
-rw-r--r--poezio/plugin_manager.py45
3 files changed, 52 insertions, 3 deletions
diff --git a/poezio/core/core.py b/poezio/core/core.py
index 525d02a6..14852ac2 100644
--- a/poezio/core/core.py
+++ b/poezio/core/core.py
@@ -518,10 +518,10 @@ class Core:
plugins = config.get('plugins_autoload')
if ':' in plugins:
for plugin in plugins.split(':'):
- self.plugin_manager.load(plugin)
+ self.plugin_manager.load(plugin, unload_first=False)
else:
for plugin in plugins.split():
- self.plugin_manager.load(plugin)
+ self.plugin_manager.load(plugin, unload_first=False)
self.plugins_autoloaded = True
def start(self):
diff --git a/poezio/plugin.py b/poezio/plugin.py
index 61e0ea87..81c849b4 100644
--- a/poezio/plugin.py
+++ b/poezio/plugin.py
@@ -3,6 +3,8 @@ Define the PluginConfig and Plugin classes, plus the SafetyMetaclass.
These are used in the plugin system added in poezio 0.7.5
(see plugin_manager.py)
"""
+
+from typing import Set
from asyncio import iscoroutinefunction
from functools import partial
from configparser import RawConfigParser
@@ -399,7 +401,11 @@ class BasePlugin(object, metaclass=SafetyMetaclass):
Class that all plugins derive from.
"""
+ # Internal use only
+ _unloading = False
+
default_config = None
+ dependencies: Set[str] = set()
def __init__(self, name, plugin_api, core, plugins_conf_dir):
self.__name = name
diff --git a/poezio/plugin_manager.py b/poezio/plugin_manager.py
index 75a6b4a3..c44a8ecc 100644
--- a/poezio/plugin_manager.py
+++ b/poezio/plugin_manager.py
@@ -7,6 +7,7 @@ plugin env.
import logging
import os
+from typing import Dict, Set
from importlib import import_module, machinery
from pathlib import Path
from os import path
@@ -27,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
@@ -58,10 +61,25 @@ class PluginManager:
for plugin in set(self.plugins.keys()):
self.unload(plugin, notify=False)
- def load(self, name: str, 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)
@@ -109,8 +127,20 @@ class PluginManager:
self.event_handlers[name] = []
try:
self.plugins[name] = None
+
+ 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
+
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:
@@ -122,8 +152,20 @@ class PluginManager:
self.core.information('Plugin %s loaded' % name, 'Info')
def unload(self, name: str, notify=True):
+ """
+ Unloads plugin as well as plugins depending on it.
+ """
+
if name in self.plugins:
try:
+ 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():
@@ -143,6 +185,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]