From 122ef02605a9c8df23292fd76cc18b47bca949ea Mon Sep 17 00:00:00 2001 From: ZeroNet Date: Wed, 10 Aug 2016 12:43:35 +0200 Subject: [PATCH] Hot-patch in memory plugin classes --- src/Plugin/PluginManager.py | 50 ++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Plugin/PluginManager.py b/src/Plugin/PluginManager.py index c1069da7..c9fc14e1 100644 --- a/src/Plugin/PluginManager.py +++ b/src/Plugin/PluginManager.py @@ -1,17 +1,18 @@ import logging import os import sys +from collections import defaultdict from Debug import Debug from Config import config class PluginManager: - def __init__(self): self.log = logging.getLogger("PluginManager") self.plugin_path = "plugins" # Plugin directory - self.plugins = {} # 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.pluggable = {} self.plugin_names = [] # Loaded plugin names sys.path.append(self.plugin_path) @@ -42,10 +43,18 @@ class PluginManager: # Reload all plugins def reloadPlugins(self): - self.plugins = {} # Reset registered plugins + self.plugins_before = self.plugins + self.plugins = defaultdict(list) # Reset registered plugins for module_name, module in sys.modules.items(): if module and "__file__" in dir(module) and self.plugin_path in module.__file__: # Module file within plugin_path - if "allow_reload" not in dir(module) or module.allow_reload: # Check if reload disabled + if "allow_reload" in dir(module) and not module.allow_reload: # Reload disabled + # Re-add non-reloadable plugins + for class_name, classes in self.plugins_before.iteritems(): + for c in classes: + if c.__module__ != module.__name__: + continue + self.plugins[class_name].append(c) + else: try: reload(module) except Exception, err: @@ -53,6 +62,38 @@ class PluginManager: self.loadPlugins() # Load new plugins + # Change current classes in memory + import gc + patched = {} + for class_name, classes in self.plugins.iteritems(): + classes = classes[:] # Copy the current plugins + classes.reverse() + base_class = self.pluggable[class_name] # Original class + classes.append(base_class) # Add the class itself to end of inherience line + plugined_class = type(class_name, tuple(classes), dict()) # Create the plugined class + for obj in gc.get_objects(): + if type(obj).__name__ == class_name: + obj.__class__ = plugined_class + patched[class_name] = patched.get(class_name, 0) + 1 + self.log.debug("Patched objects: %s" % patched) + + # Change classes in modules + patched = {} + for class_name, classes in self.plugins.iteritems(): + for module_name, module in sys.modules.iteritems(): + if class_name in dir(module): + if "__class__" not in dir(getattr(module, class_name)): # Not a class + continue + base_class = self.pluggable[class_name] + classes = self.plugins[class_name][:] + classes.reverse() + classes.append(base_class) + plugined_class = type(class_name, tuple(classes), dict()) + setattr(module, class_name, plugined_class) + patched[class_name] = patched.get(class_name, 0) + 1 + + self.log.debug("Patched modules: %s" % patched) + plugin_manager = PluginManager() # Singletone @@ -63,6 +104,7 @@ plugin_manager = PluginManager() # Singletone def acceptPlugins(base_class): class_name = base_class.__name__ + plugin_manager.pluggable[class_name] = base_class if class_name in plugin_manager.plugins: # Has plugins classes = plugin_manager.plugins[class_name][:] # Copy the current plugins classes.reverse()