diff --git a/python/utils.py b/python/utils.py index 06d67cbbe4b..94b9f8ef8fc 100644 --- a/python/utils.py +++ b/python/utils.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ QGIS utilities module @@ -6,6 +7,9 @@ QGIS utilities module from PyQt4.QtCore import QCoreApplication import sys import traceback +import glob +import os.path +import re ####################### @@ -94,6 +98,30 @@ plugins = {} # list of active (started) plugins active_plugins = [] +# list of plugins in plugin directory and home plugin directory +available_plugins = [] + + +def updateAvailablePlugins(): + from qgis.core import QgsApplication + pythonPath = unicode(QgsApplication.pkgDataPath()) + "/python" + homePythonPath = unicode(QgsApplication.qgisSettingsDirPath()) + "/python" + + plugins = map(os.path.basename, glob.glob(pythonPath + "/plugins/*")) + homePlugins = map(os.path.basename, glob.glob(homePythonPath + "/plugins/*")) + + # merge the lists + for p in homePlugins: + if p not in plugins: + plugins.append(p) + + global available_plugins + available_plugins = plugins + +# update list on start +updateAvailablePlugins() + + def pluginMetadata(packageName, fct): """ fetch metadata from a plugin """ try: @@ -103,7 +131,7 @@ def pluginMetadata(packageName, fct): return "__error__" def loadPlugin(packageName): - """ load plugin's package and ensure that plugin is reloaded when changed """ + """ load plugin's package """ try: __import__(packageName) @@ -117,7 +145,6 @@ def loadPlugin(packageName): # retry try: __import__(packageName) - reload(packageName) return True except: msgTemplate = QCoreApplication.translate("Python", "Couldn't load plugin '%1' from ['%2']") @@ -140,6 +167,7 @@ def startPlugin(packageName): try: plugins[packageName] = package.classFactory(iface) except: + _unloadPluginModules(packageName) msg = QCoreApplication.translate("Python", "%1 due an error when calling its classFactory() method").arg(errMsg) showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg) return False @@ -148,6 +176,8 @@ def startPlugin(packageName): try: plugins[packageName].initGui() except: + del plugins[packageName] + _unloadPluginModules(packageName) msg = QCoreApplication.translate("Python", "%1 due an error when calling its initGui() method" ).arg( errMsg ) showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg) return False @@ -169,15 +199,77 @@ def unloadPlugin(packageName): plugins[packageName].unload() del plugins[packageName] active_plugins.remove(packageName) + _unloadPluginModules(packageName) return True except Exception, e: msg = QCoreApplication.translate("Python", "Error while unloading plugin %1").arg(packageName) showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg) return False + +def _unloadPluginModules(packageName): + """ unload plugin package with all its modules (files) """ + global _plugin_modules + mods = _plugin_modules[packageName] + + for mod in mods: + # if it looks like a Qt resource file, try to do a cleanup + # otherwise we might experience a segfault next time the plugin is loaded + # because Qt will try to access invalid plugin resource data + if "resources" in mod: + try: + sys.modules[mod].qCleanupResources() + except: + pass + # try to remove the module from python + try: + del sys.modules[mod] + except: + pass + # remove the plugin entry + del _plugin_modules[packageName] + + def isPluginLoaded(packageName): """ find out whether a plugin is active (i.e. has been started) """ global plugins, active_plugins if not plugins.has_key(packageName): return False return (packageName in active_plugins) + + +def reloadPlugin(packageName): + """ unload and start again a plugin """ + global active_plugins + if packageName not in active_plugins: + return # it's not active + + unloadPlugin(packageName) + loadPlugin(packageName) + startPlugin(packageName) + + +####################### +# IMPORT wrapper + +import __builtin__ + +_builtin_import = __builtin__.__import__ +_plugin_modules = { } + +def _import(name, globals={}, locals={}, fromlist=[], level=-1): + """ wrapper around builtin import that keeps track of loaded plugin modules """ + mod = _builtin_import(name, globals, locals, fromlist, level) + + if mod and '__file__' in mod.__dict__: + module_name = mod.__name__ + package_name = module_name.split('.')[0] + # check whether the module belongs to one of our plugins + if package_name in available_plugins: + if package_name not in _plugin_modules: + _plugin_modules[package_name] = set() + _plugin_modules[package_name].add(module_name) + + return mod + +__builtin__.__import__ = _import diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp index 10d49dc30dc..1a786e00080 100644 --- a/src/python/qgspythonutilsimpl.cpp +++ b/src/python/qgspythonutilsimpl.cpp @@ -424,23 +424,11 @@ QString QgsPythonUtilsImpl::homePluginsPath() QStringList QgsPythonUtilsImpl::pluginList() { - QDir pluginDir( QgsPythonUtilsImpl::pluginsPath(), "*", - QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoDotAndDotDot ); + runString( "qgis.utils.updateAvailablePlugins()" ); - QDir homePluginDir( QgsPythonUtilsImpl::homePluginsPath(), "*", - QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoDotAndDotDot ); - - QStringList pluginList = pluginDir.entryList(); - - for ( uint i = 0; i < homePluginDir.count(); i++ ) - { - QString packageName = homePluginDir[i]; - if ( !pluginList.contains( packageName ) ) - pluginList.append( packageName ); - - } - - return pluginList; + QString output; + evalString( "'\\n'.join(qgis.utils.available_plugins)", output ); + return output.split( QChar( '\n' ) ); } QString QgsPythonUtilsImpl::getPluginMetadata( QString pluginName, QString function )