Allow load installed third-party plugins and enable/disable plugins in config file data/plugins.json

This commit is contained in:
shortcutme 2019-08-02 16:18:37 +02:00
parent 0c659a477d
commit 713ff17e91
No known key found for this signature in database
GPG key ID: 5B63BAE6CB9613AE

View file

@ -5,62 +5,143 @@ import shutil
import time import time
from collections import defaultdict from collections import defaultdict
import importlib
import json
from Debug import Debug from Debug import Debug
from Config import config from Config import config
import plugins import plugins
import importlib
class PluginManager: class PluginManager:
def __init__(self): def __init__(self):
self.log = logging.getLogger("PluginManager") self.log = logging.getLogger("PluginManager")
self.plugin_path = os.path.abspath(os.path.dirname(plugins.__file__)) self.path_plugins = os.path.abspath(os.path.dirname(plugins.__file__))
self.path_installed_plugins = config.data_dir + "/__plugins__"
self.plugins = defaultdict(list) # Registered plugins (key: class name, value: list of plugins for class) self.plugins = defaultdict(list) # Registered plugins (key: class name, value: list of plugins for class)
self.subclass_order = {} # Record the load order of the plugins, to keep it after reload self.subclass_order = {} # Record the load order of the plugins, to keep it after reload
self.pluggable = {} self.pluggable = {}
self.plugin_names = [] # Loaded plugin names self.plugin_names = [] # Loaded plugin names
self.after_load = [] # Execute functions after loaded plugins self.plugins_updated = {} # List of updated plugins since restart
self.plugins_rev = {} # Installed plugins revision numbers
self.after_load = [] # Execute functions after loaded plugins
self.function_flags = {} # Flag function for permissions
self.reloading = False self.reloading = False
self.config_path = config.data_dir + "/plugins.json"
self.loadConfig()
sys.path.append(os.path.join(os.getcwd(), self.plugin_path)) self.config.setdefault("builtin", {})
sys.path.append(os.path.join(os.getcwd(), self.path_plugins))
self.migratePlugins() self.migratePlugins()
if config.debug: # Auto reload Plugins on file change if config.debug: # Auto reload Plugins on file change
from Debug import DebugReloader from Debug import DebugReloader
DebugReloader.watcher.addCallback(self.reloadPlugins) DebugReloader.watcher.addCallback(self.reloadPlugins)
def loadConfig(self):
if os.path.isfile(self.config_path):
try:
self.config = json.load(open(self.config_path, encoding="utf8"))
except Exception as err:
self.log.error("Error loading %s: %s" % (self.config_path, err))
self.config = {}
else:
self.config = {}
def saveConfig(self):
f = open(self.config_path, "w", encoding="utf8")
json.dump(self.config, f, ensure_ascii=False, sort_keys=True, indent=2)
def migratePlugins(self): def migratePlugins(self):
for dir_name in os.listdir(self.plugin_path): for dir_name in os.listdir(self.path_plugins):
if dir_name == "Mute": if dir_name == "Mute":
self.log.info("Deleting deprecated/renamed plugin: %s" % dir_name) self.log.info("Deleting deprecated/renamed plugin: %s" % dir_name)
shutil.rmtree("%s/%s" % (self.plugin_path, dir_name)) shutil.rmtree("%s/%s" % (self.path_plugins, dir_name))
# -- Load / Unload -- # -- Load / Unload --
def listPlugins(self, list_disabled=False):
plugins = []
for dir_name in sorted(os.listdir(self.path_plugins)):
dir_path = os.path.join(self.path_plugins, dir_name)
plugin_name = dir_name.replace("disabled-", "")
if dir_name.startswith("disabled"):
is_enabled = False
else:
is_enabled = True
plugin_config = self.config["builtin"].get(plugin_name, {})
if "enabled" in plugin_config:
is_enabled = plugin_config["enabled"]
if dir_name == "__pycache__" or not os.path.isdir(dir_path):
continue # skip
if dir_name.startswith("Debug") and not config.debug:
continue # Only load in debug mode if module name starts with Debug
if not is_enabled and not list_disabled:
continue # Dont load if disabled
plugin = {}
plugin["source"] = "builtin"
plugin["name"] = plugin_name
plugin["dir_name"] = dir_name
plugin["dir_path"] = dir_path
plugin["inner_path"] = plugin_name
plugin["enabled"] = is_enabled
plugin["rev"] = config.rev
plugin["loaded"] = plugin_name in self.plugin_names
plugins.append(plugin)
plugins += self.listInstalledPlugins(list_disabled)
return plugins
def listInstalledPlugins(self, list_disabled=False):
plugins = []
for address, site_plugins in self.config.items():
if address == "builtin":
continue
for plugin_inner_path, plugin_config in site_plugins.items():
is_enabled = plugin_config.get("enabled", False)
if not is_enabled and not list_disabled:
continue
plugin_name = os.path.basename(plugin_inner_path)
dir_path = "%s/%s/%s" % (self.path_installed_plugins, address, plugin_inner_path)
plugin = {}
plugin["source"] = address
plugin["name"] = plugin_name
plugin["dir_name"] = plugin_name
plugin["dir_path"] = dir_path
plugin["inner_path"] = plugin_inner_path
plugin["enabled"] = is_enabled
plugin["rev"] = plugin_config.get("rev", 0)
plugin["loaded"] = plugin_name in self.plugin_names
plugins.append(plugin)
return plugins
# Load all plugin # Load all plugin
def loadPlugins(self): def loadPlugins(self):
all_loaded = True all_loaded = True
s = time.time() s = time.time()
for dir_name in sorted(os.listdir(self.plugin_path)): print(sys.path)
dir_path = os.path.join(self.plugin_path, dir_name) for plugin in self.listPlugins():
if dir_name == "__pycache__": self.log.debug("Loading plugin: %s (%s)" % (plugin["name"], plugin["source"]))
continue # skip if plugin["source"] != "builtin":
if dir_name.startswith("disabled"): self.plugins_rev[plugin["name"]] = plugin["rev"]
continue # Dont load if disabled site_plugin_dir = os.path.dirname(plugin["dir_path"])
if not os.path.isdir(dir_path): if site_plugin_dir not in sys.path:
continue # Dont load if not dir sys.path.append(site_plugin_dir)
if dir_name.startswith("Debug") and not config.debug:
continue # Only load in debug mode if module name starts with Debug
self.log.debug("Loading plugin: %s" % dir_name)
try: try:
__import__(dir_name) sys.modules[plugin["name"]] = __import__(plugin["dir_name"])
except Exception as err: except Exception as err:
self.log.error("Plugin %s load error: %s" % (dir_name, Debug.formatException(err))) self.log.error("Plugin %s load error: %s" % (plugin["name"], Debug.formatException(err)))
all_loaded = False all_loaded = False
if dir_name not in self.plugin_names: if plugin["name"] not in self.plugin_names:
self.plugin_names.append(dir_name) self.plugin_names.append(plugin["name"])
self.log.debug("Plugins loaded in %.3fs" % (time.time() - s)) self.log.debug("Plugins loaded in %.3fs" % (time.time() - s))
for func in self.after_load: for func in self.after_load:
@ -74,19 +155,23 @@ class PluginManager:
self.plugins_before = self.plugins self.plugins_before = self.plugins
self.plugins = defaultdict(list) # Reset registered plugins self.plugins = defaultdict(list) # Reset registered plugins
for module_name, module in list(sys.modules.items()): for module_name, module in list(sys.modules.items()):
if module and getattr(module, "__file__", None) and self.plugin_path in module.__file__: # Module file in plugin_path if not module or not getattr(module, "__file__", None):
if "allow_reload" in dir(module) and not module.allow_reload: # Reload disabled continue
# Re-add non-reloadable plugins if self.path_plugins not in module.__file__ and self.path_installed_plugins not in module.__file__:
for class_name, classes in self.plugins_before.items(): continue
for c in classes:
if c.__module__ != module.__name__: if "allow_reload" in dir(module) and not module.allow_reload: # Reload disabled
continue # Re-add non-reloadable plugins
self.plugins[class_name].append(c) for class_name, classes in self.plugins_before.items():
else: for c in classes:
try: if c.__module__ != module.__name__:
importlib.reload(module) continue
except Exception as err: self.plugins[class_name].append(c)
self.log.error("Plugin %s reload error: %s" % (module_name, Debug.formatException(err))) else:
try:
importlib.reload(module)
except Exception as err:
self.log.error("Plugin %s reload error: %s" % (module_name, Debug.formatException(err)))
self.loadPlugins() # Load new plugins self.loadPlugins() # Load new plugins