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.
This commit is contained in:
David Vierra 2016-05-21 15:00:35 -10:00
parent a5584de41f
commit bea7d8e8d5
4 changed files with 92 additions and 74 deletions

View File

@ -10,6 +10,7 @@ from PySide import QtCore, QtGui
from mcedit2.command import SimpleRevisionCommand from mcedit2.command import SimpleRevisionCommand
from mcedit2.editortools import EditorTool from mcedit2.editortools import EditorTool
from mcedit2.handles.boxhandle import BoxHandle from mcedit2.handles.boxhandle import BoxHandle
from mcedit2.plugins.registry import PluginClassRegistry
from mcedit2.rendering.scenegraph import scenenode from mcedit2.rendering.scenegraph import scenenode
from mcedit2.rendering.scenegraph.matrix import Translate from mcedit2.rendering.scenegraph.matrix import Translate
from mcedit2.rendering.scenegraph.scenenode import Node from mcedit2.rendering.scenegraph.scenenode import Node
@ -134,24 +135,11 @@ class GeneratePlugin(QtCore.QObject):
def editorSession(self): def editorSession(self):
return self.generateTool.editorSession return self.generateTool.editorSession
_pluginClasses = []
class _GeneratePlugins(PluginClassRegistry):
pluginClass = GeneratePlugin
def registerGeneratePlugin(cls): GeneratePlugins = _GeneratePlugins()
_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()
class GenerateTool(EditorTool): class GenerateTool(EditorTool):
@ -171,7 +159,7 @@ class GenerateTool(EditorTool):
self.toolWidget = toolWidget self.toolWidget = toolWidget
column = [] column = []
self.generatorTypes = [pluginClass(self) for pluginClass in _pluginClasses] self.generatorTypes = [pluginClass(self) for pluginClass in GeneratePlugins.registeredPlugins]
self.currentGenerator = None self.currentGenerator = None
if len(self.generatorTypes): if len(self.generatorTypes):
self.currentGenerator = self.generatorTypes[0] self.currentGenerator = self.generatorTypes[0]
@ -236,8 +224,8 @@ class GenerateTool(EditorTool):
# so it can be reselected if it is immediately reloaded # so it can be reselected if it is immediately reloaded
self._lastTypeName = None self._lastTypeName = None
_GeneratePlugins.instance.pluginAdded.connect(self.addPlugin) GeneratePlugins.pluginAdded.connect(self.addPlugin)
_GeneratePlugins.instance.pluginRemoved.connect(self.removePlugin) GeneratePlugins.pluginRemoved.connect(self.removePlugin)
def removePlugin(self, cls): def removePlugin(self, cls):
log.info("Removing plugin %s", cls.__name__) log.info("Removing plugin %s", cls.__name__)

View File

@ -2,11 +2,13 @@
plugins plugins
""" """
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
from collections import defaultdict
import logging
import itertools
import os
import imp import imp
import itertools
import logging
import os
from collections import defaultdict
from mcedit2 import editortools from mcedit2 import editortools
from mcedit2.editortools import generate from mcedit2.editortools import generate
from mcedit2.plugins import command from mcedit2.plugins import command
@ -115,9 +117,15 @@ class PluginRef(object):
_currentPluginPathname = pathname _currentPluginPathname = pathname
self.pluginModule = imp.load_module(basename, io, pathname, description) self.pluginModule = imp.load_module(basename, io, pathname, description)
self.registerModule() _loadedModules[self.fullpath] = self.pluginModule
self.pluginModule.__FOUND_FILENAME__ = self.fullpath
_currentPluginPathname = None _currentPluginPathname = None
# Plugin registration is deferred until the module is fully loaded.
for registry in _registries:
registry.commitRegistrations()
if hasattr(self.pluginModule, 'displayName'): if hasattr(self.pluginModule, 'displayName'):
self._displayName = self.pluginModule.displayName self._displayName = self.pluginModule.displayName
@ -134,19 +142,14 @@ class PluginRef(object):
return True return True
def registerModule(self):
module = self.pluginModule
_loadedModules[self.fullpath] = module
module.__FOUND_FILENAME__ = self.fullpath
def unload(self): def unload(self):
if self.pluginModule is None: if self.pluginModule is None:
return return
module = self.pluginModule module = self.pluginModule
try: try:
self.unregisterModule() if hasattr(module, "unregister"):
module.unregister()
except Exception as e: except Exception as e:
self.unloadError = sys.exc_info() self.unloadError = sys.exc_info()
@ -156,24 +159,24 @@ class PluginRef(object):
self.unloadError = None self.unloadError = None
finally: finally:
self.pluginModule = None self.pluginModule = None
deadKeys = []
for k, v in sys.modules.iteritems(): for k, v in sys.modules.iteritems():
if v is module: if v is module:
deadKeys.append(k)
for k in deadKeys:
sys.modules.pop(k) sys.modules.pop(k)
log.info("Removed module %s from sys.modules", k) log.info("Removed module %s from sys.modules", k)
return True
def unregisterModule(self):
module = self.pluginModule
if hasattr(module, "unregister"):
module.unregister()
classes = _pluginClassesByPathname.pop(self.fullpath) classes = _pluginClassesByPathname.pop(self.fullpath)
if classes: if classes:
for cls in classes: for cls in classes:
_unregisterClass(cls) _unregisterClass(cls)
_loadedModules.pop(module.__FOUND_FILENAME__) _loadedModules.pop(module.__FOUND_FILENAME__)
self.fullpath = None
return True
@property @property
def isLoaded(self): def isLoaded(self):
@ -256,6 +259,10 @@ _loadedModules = {}
_pluginClassesByPathname = defaultdict(list) _pluginClassesByPathname = defaultdict(list)
_currentPluginPathname = None _currentPluginPathname = None
_registries = [
command.CommandPlugins,
generate.GeneratePlugins
]
def _registerClass(cls): def _registerClass(cls):
_pluginClassesByPathname[_currentPluginPathname].append(cls) _pluginClassesByPathname[_currentPluginPathname].append(cls)
@ -265,10 +272,11 @@ def _registerClass(cls):
def _unregisterClass(cls): def _unregisterClass(cls):
load_ui.unregisterCustomWidget(cls) load_ui.unregisterCustomWidget(cls)
editortools.unregisterToolClass(cls) editortools.unregisterToolClass(cls)
generate.unregisterGeneratePlugin(cls)
inspector.unregisterBlockInspectorWidget(cls) inspector.unregisterBlockInspectorWidget(cls)
entities.unregisterTileEntityRefClass(cls) entities.unregisterTileEntityRefClass(cls)
command.unregisterPluginCommand(cls)
generate.GeneratePlugins.unregisterClass(cls)
command.CommandPlugins.unregisterClass(cls)
# --- Registration functions --- # --- Registration functions ---
@ -290,7 +298,7 @@ def registerPluginCommand(cls):
cls : Class cls : Class
""" """
_registerClass(cls) _registerClass(cls)
return command.registerPluginCommand(cls) return command.CommandPlugins.registerClass(cls)
def registerCustomWidget(cls): def registerCustomWidget(cls):
@ -354,7 +362,7 @@ def registerGeneratePlugin(cls):
cls : Class cls : Class
""" """
_registerClass(cls) _registerClass(cls)
return generate.registerGeneratePlugin(cls) return generate.GeneratePlugins.registerClass(cls)
def registerBlockInspectorWidget(cls): def registerBlockInspectorWidget(cls):
""" """

View File

@ -7,6 +7,8 @@ from collections import defaultdict
from PySide import QtCore, QtGui from PySide import QtCore, QtGui
from mcedit2.plugins.registry import PluginClassRegistry
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -52,28 +54,10 @@ class PluginCommand(QtCore.QObject):
self.editorSession.menuPlugins.addPluginMenuItem(self.__class__, text, func, submenu) self.editorSession.menuPlugins.addPluginMenuItem(self.__class__, text, func, submenu)
class _CommandPlugins(QtCore.QObject): class _CommandPlugins(PluginClassRegistry):
pluginRemoved = QtCore.Signal(object) pluginClass = PluginCommand
pluginAdded = QtCore.Signal(object)
_CommandPlugins.instance = _CommandPlugins() CommandPlugins = _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)
class PluginsMenu(QtGui.QMenu): class PluginsMenu(QtGui.QMenu):
@ -82,14 +66,14 @@ class PluginsMenu(QtGui.QMenu):
self.setTitle(self.tr("Plugins")) self.setTitle(self.tr("Plugins"))
self.editorSession = editorSession self.editorSession = editorSession
self.submenus = {} self.submenus = {}
_CommandPlugins.instance.pluginRemoved.connect(self.pluginRemoved) CommandPlugins.pluginRemoved.connect(self.pluginRemoved)
_CommandPlugins.instance.pluginAdded.connect(self.pluginAdded) CommandPlugins.pluginAdded.connect(self.pluginAdded)
self.plugins = [] self.plugins = []
self.actionsByClass = defaultdict(list) self.actionsByClass = defaultdict(list)
def loadPlugins(self): def loadPlugins(self):
for cls in _registeredCommands: for cls in CommandPlugins.registeredPlugins:
instance = cls(self.editorSession) instance = cls(self.editorSession)
self.plugins.append(instance) self.plugins.append(instance)
@ -119,5 +103,5 @@ class PluginsMenu(QtGui.QMenu):
action = menu.addAction(text, func) action = menu.addAction(text, func)
self.actionsByClass[cls].append(action) self.actionsByClass[cls].append(action)
log.info("Added action %s") log.info("Added action %s", action)
return action return action

View File

@ -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)