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.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__)

View File

@ -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,24 +159,24 @@ class PluginRef(object):
self.unloadError = None
finally:
self.pluginModule = None
deadKeys = []
for k, v in sys.modules.iteritems():
if v is module:
deadKeys.append(k)
for k in deadKeys:
sys.modules.pop(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)
if classes:
for cls in classes:
_unregisterClass(cls)
_loadedModules.pop(module.__FOUND_FILENAME__)
self.fullpath = None
return True
@property
def isLoaded(self):
@ -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):
"""

View File

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

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)