From bea7d8e8d52e3ec84f8bee2d074d7eb9efb80edf Mon Sep 17 00:00:00 2001 From: David Vierra Date: Sat, 21 May 2016 15:00:35 -1000 Subject: [PATCH] Create PluginClassRegistry class to handle class loading/unloading Implements signals emitted when a plugin class is loaded/unloaded and defers registration of classes until the plugin module is fully loaded. --- src/mcedit2/editortools/generate.py | 26 +++-------- src/mcedit2/plugins/__init__.py | 68 ++++++++++++++++------------- src/mcedit2/plugins/command.py | 34 ++++----------- src/mcedit2/plugins/registry.py | 38 ++++++++++++++++ 4 files changed, 92 insertions(+), 74 deletions(-) create mode 100644 src/mcedit2/plugins/registry.py diff --git a/src/mcedit2/editortools/generate.py b/src/mcedit2/editortools/generate.py index d964745..db0fd5a 100644 --- a/src/mcedit2/editortools/generate.py +++ b/src/mcedit2/editortools/generate.py @@ -10,6 +10,7 @@ from PySide import QtCore, QtGui from mcedit2.command import SimpleRevisionCommand from mcedit2.editortools import EditorTool from mcedit2.handles.boxhandle import BoxHandle +from mcedit2.plugins.registry import PluginClassRegistry from mcedit2.rendering.scenegraph import scenenode from mcedit2.rendering.scenegraph.matrix import Translate from mcedit2.rendering.scenegraph.scenenode import Node @@ -134,24 +135,11 @@ class GeneratePlugin(QtCore.QObject): def editorSession(self): return self.generateTool.editorSession -_pluginClasses = [] +class _GeneratePlugins(PluginClassRegistry): + pluginClass = GeneratePlugin -def registerGeneratePlugin(cls): - _pluginClasses.append(cls) - _GeneratePlugins.instance.pluginAdded.emit(cls) - return cls - - -def unregisterGeneratePlugin(cls): - _pluginClasses[:] = [c for c in _pluginClasses if c != cls] - _GeneratePlugins.instance.pluginRemoved.emit(cls) - -class _GeneratePlugins(QtCore.QObject): - pluginRemoved = QtCore.Signal(object) - pluginAdded = QtCore.Signal(object) - -_GeneratePlugins.instance = _GeneratePlugins() +GeneratePlugins = _GeneratePlugins() class GenerateTool(EditorTool): @@ -171,7 +159,7 @@ class GenerateTool(EditorTool): self.toolWidget = toolWidget column = [] - self.generatorTypes = [pluginClass(self) for pluginClass in _pluginClasses] + self.generatorTypes = [pluginClass(self) for pluginClass in GeneratePlugins.registeredPlugins] self.currentGenerator = None if len(self.generatorTypes): self.currentGenerator = self.generatorTypes[0] @@ -236,8 +224,8 @@ class GenerateTool(EditorTool): # so it can be reselected if it is immediately reloaded self._lastTypeName = None - _GeneratePlugins.instance.pluginAdded.connect(self.addPlugin) - _GeneratePlugins.instance.pluginRemoved.connect(self.removePlugin) + GeneratePlugins.pluginAdded.connect(self.addPlugin) + GeneratePlugins.pluginRemoved.connect(self.removePlugin) def removePlugin(self, cls): log.info("Removing plugin %s", cls.__name__) diff --git a/src/mcedit2/plugins/__init__.py b/src/mcedit2/plugins/__init__.py index ee3eee3..cd85e11 100644 --- a/src/mcedit2/plugins/__init__.py +++ b/src/mcedit2/plugins/__init__.py @@ -2,11 +2,13 @@ plugins """ from __future__ import absolute_import, division, print_function -from collections import defaultdict -import logging -import itertools -import os + import imp +import itertools +import logging +import os +from collections import defaultdict + from mcedit2 import editortools from mcedit2.editortools import generate from mcedit2.plugins import command @@ -115,9 +117,15 @@ class PluginRef(object): _currentPluginPathname = pathname self.pluginModule = imp.load_module(basename, io, pathname, description) - self.registerModule() + _loadedModules[self.fullpath] = self.pluginModule + self.pluginModule.__FOUND_FILENAME__ = self.fullpath + _currentPluginPathname = None + # Plugin registration is deferred until the module is fully loaded. + for registry in _registries: + registry.commitRegistrations() + if hasattr(self.pluginModule, 'displayName'): self._displayName = self.pluginModule.displayName @@ -134,19 +142,14 @@ class PluginRef(object): return True - def registerModule(self): - module = self.pluginModule - - _loadedModules[self.fullpath] = module - module.__FOUND_FILENAME__ = self.fullpath - def unload(self): if self.pluginModule is None: return module = self.pluginModule try: - self.unregisterModule() + if hasattr(module, "unregister"): + module.unregister() except Exception as e: self.unloadError = sys.exc_info() @@ -156,25 +159,25 @@ class PluginRef(object): self.unloadError = None finally: self.pluginModule = None + deadKeys = [] for k, v in sys.modules.iteritems(): if v is module: - sys.modules.pop(k) - log.info("Removed module %s from sys.modules", k) + deadKeys.append(k) + + for k in deadKeys: + sys.modules.pop(k) + log.info("Removed module %s from sys.modules", k) + + classes = _pluginClassesByPathname.pop(self.fullpath) + if classes: + for cls in classes: + _unregisterClass(cls) + + _loadedModules.pop(module.__FOUND_FILENAME__) + self.fullpath = None return True - def unregisterModule(self): - module = self.pluginModule - if hasattr(module, "unregister"): - module.unregister() - - classes = _pluginClassesByPathname.pop(self.fullpath) - if classes: - for cls in classes: - _unregisterClass(cls) - - _loadedModules.pop(module.__FOUND_FILENAME__) - @property def isLoaded(self): return self.pluginModule is not None @@ -256,6 +259,10 @@ _loadedModules = {} _pluginClassesByPathname = defaultdict(list) _currentPluginPathname = None +_registries = [ + command.CommandPlugins, + generate.GeneratePlugins +] def _registerClass(cls): _pluginClassesByPathname[_currentPluginPathname].append(cls) @@ -265,10 +272,11 @@ def _registerClass(cls): def _unregisterClass(cls): load_ui.unregisterCustomWidget(cls) editortools.unregisterToolClass(cls) - generate.unregisterGeneratePlugin(cls) inspector.unregisterBlockInspectorWidget(cls) entities.unregisterTileEntityRefClass(cls) - command.unregisterPluginCommand(cls) + + generate.GeneratePlugins.unregisterClass(cls) + command.CommandPlugins.unregisterClass(cls) # --- Registration functions --- @@ -290,7 +298,7 @@ def registerPluginCommand(cls): cls : Class """ _registerClass(cls) - return command.registerPluginCommand(cls) + return command.CommandPlugins.registerClass(cls) def registerCustomWidget(cls): @@ -354,7 +362,7 @@ def registerGeneratePlugin(cls): cls : Class """ _registerClass(cls) - return generate.registerGeneratePlugin(cls) + return generate.GeneratePlugins.registerClass(cls) def registerBlockInspectorWidget(cls): """ diff --git a/src/mcedit2/plugins/command.py b/src/mcedit2/plugins/command.py index fa7cb50..12bfcd1 100644 --- a/src/mcedit2/plugins/command.py +++ b/src/mcedit2/plugins/command.py @@ -7,6 +7,8 @@ from collections import defaultdict from PySide import QtCore, QtGui +from mcedit2.plugins.registry import PluginClassRegistry + log = logging.getLogger(__name__) @@ -52,28 +54,10 @@ class PluginCommand(QtCore.QObject): self.editorSession.menuPlugins.addPluginMenuItem(self.__class__, text, func, submenu) -class _CommandPlugins(QtCore.QObject): - pluginRemoved = QtCore.Signal(object) - pluginAdded = QtCore.Signal(object) +class _CommandPlugins(PluginClassRegistry): + pluginClass = PluginCommand -_CommandPlugins.instance = _CommandPlugins() - -_registeredCommands = [] - - -def registerPluginCommand(cls): - if issubclass(cls, PluginCommand): - _registeredCommands.append(cls) - else: - raise ValueError("Class %s must inherit from PluginCommand" % cls) - - _CommandPlugins.instance.pluginAdded.emit(cls) - return cls - - -def unregisterPluginCommand(cls): - if issubclass(cls, PluginCommand): - _CommandPlugins.instance.pluginRemoved.emit(cls) +CommandPlugins = _CommandPlugins() class PluginsMenu(QtGui.QMenu): @@ -82,14 +66,14 @@ class PluginsMenu(QtGui.QMenu): self.setTitle(self.tr("Plugins")) self.editorSession = editorSession self.submenus = {} - _CommandPlugins.instance.pluginRemoved.connect(self.pluginRemoved) - _CommandPlugins.instance.pluginAdded.connect(self.pluginAdded) + CommandPlugins.pluginRemoved.connect(self.pluginRemoved) + CommandPlugins.pluginAdded.connect(self.pluginAdded) self.plugins = [] self.actionsByClass = defaultdict(list) def loadPlugins(self): - for cls in _registeredCommands: + for cls in CommandPlugins.registeredPlugins: instance = cls(self.editorSession) self.plugins.append(instance) @@ -119,5 +103,5 @@ class PluginsMenu(QtGui.QMenu): action = menu.addAction(text, func) self.actionsByClass[cls].append(action) - log.info("Added action %s") + log.info("Added action %s", action) return action diff --git a/src/mcedit2/plugins/registry.py b/src/mcedit2/plugins/registry.py new file mode 100644 index 0000000..91c06a0 --- /dev/null +++ b/src/mcedit2/plugins/registry.py @@ -0,0 +1,38 @@ +""" + registry +""" +from __future__ import absolute_import, division, print_function, unicode_literals +import logging + +from PySide import QtCore + +log = logging.getLogger(__name__) + + +class PluginClassRegistry(QtCore.QObject): + pluginRemoved = QtCore.Signal(object) + pluginAdded = QtCore.Signal(object) + + pluginClass = NotImplemented + + def __init__(self): + super(PluginClassRegistry, self).__init__() + self.pendingRegistrations = [] + self.registeredPlugins = [] + + def commitRegistrations(self): + for cls in self.pendingRegistrations: + self.registeredPlugins.append(cls) + self.pluginAdded.emit(cls) + self.pendingRegistrations[:] = [] + + def registerClass(self, cls): + if issubclass(cls, self.pluginClass): + self.pendingRegistrations.append(cls) + else: + raise ValueError("Class %s must inherit from PluginCommand" % cls) + return cls + + def unregisterClass(self, cls): + if issubclass(cls, self.pluginClass): + self.pluginRemoved.emit(cls) \ No newline at end of file