Add support for dynamic reloading of plugins

‎* rename the remove_plugin function to uninstall_plugin because it
deletes files on the disc and uninstall is the label of the button which
triggers this function
* added a new remove_plugin function, which deactivates the plugin (if
needed), removes it from the list of managed plugins and deletes the
contents of sys.modules
This commit is contained in:
Markus Wintermann 2018-04-25 21:55:54 +02:00 committed by Philipp Hörist
parent 379a4b03d7
commit 49bfc1f226
2 changed files with 33 additions and 23 deletions

View File

@ -252,7 +252,7 @@ class PluginsWindow(object):
if iter:
plugin = model.get_value(iter, Column.PLUGIN)
try:
app.plugin_manager.remove_plugin(plugin)
app.plugin_manager.uninstall_plugin(plugin)
except PluginsystemError as e:
WarningDialog(_('Unable to properly remove the plugin'),
str(e), self.window)

View File

@ -149,6 +149,32 @@ class PluginManager(metaclass=Singleton):
' short name: %s). Plugin already loaded.' % (plugin.name,
plugin.version, plugin.__module__, plugin.short_name))
@log_calls('PluginManager')
def remove_plugin(self, plugin):
'''
removes the plugin from the plugin list and deletes all loaded modules
from sys. This way we will have a fresh start when the plugin gets added
agin.
'''
if plugin.active:
self.deactivate_plugin(plugin)
self.plugins.remove(plugin)
# remove modules from cache
base_package = plugin.__module__.split('.')[0]
# get the subpackages/-modules of the base_package. Add a dot to the
# name to avoid name problems (removing module_abc if base_package is
# module_ab)
modules_to_remove = [module for module in sys.modules
if module.startswith('{}.'.format(base_package))]
# remove the base_package itself
if base_package in sys.modules:
modules_to_remove.append(base_package)
for module_to_remove in modules_to_remove:
del sys.modules[module_to_remove]
@log_calls('PluginManager')
def add_plugins(self, plugin_classes):
for plugin_class in plugin_classes:
@ -532,20 +558,9 @@ class PluginManager(metaclass=Singleton):
continue
module = None
try:
if module_name in sys.modules:
if path == configpaths.get('PLUGINS_BASE'):
# Only reload plugins from Gajim base dir when they
# dont exist. This means plugins in the user path are
# always preferred.
continue
from imp import reload
log.info('Reloading %s', module_name)
module = reload(sys.modules[module_name])
else:
log.info('Loading %s', module_name)
module = __import__(module_name)
log.info('Loading %s', module_name)
module = __import__(module_name)
except Exception as error:
log.warning(
"While trying to load {plugin}, exception occurred".format(plugin=elem_name),
@ -633,14 +648,14 @@ class PluginManager(metaclass=Singleton):
if len(dirs) > 1:
raise PluginsystemError(_('Archive is malformed'))
base_dir, user_dir = configpaths.get('PLUGINS_DIRS')
user_dir = configpaths.get('PLUGINS_USER')
plugin_dir = os.path.join(user_dir, dirs[0])
if os.path.isdir(plugin_dir):
# Plugin dir already exists
if not owerwrite:
raise PluginsystemError(_('Plugin already exists'))
self.remove_plugin(self.get_plugin_by_path(plugin_dir))
self.uninstall_plugin(self.get_plugin_by_path(plugin_dir))
zip_file.extractall(user_dir)
zip_file.close()
@ -652,7 +667,7 @@ class PluginManager(metaclass=Singleton):
plugin = self.plugins[-1]
return plugin
def remove_plugin(self, plugin):
def uninstall_plugin(self, plugin):
'''
Deactivate and remove plugin from `plugins` list
'''
@ -665,15 +680,10 @@ class PluginManager(metaclass=Singleton):
raise PluginsystemError(error[1][1])
if plugin:
if plugin.active:
self.deactivate_plugin(plugin)
self.remove_plugin(plugin)
rmtree(plugin.__path__, False, on_error)
self.plugins.remove(plugin)
if self._plugin_has_entry_in_global_config(plugin):
self._remove_plugin_entry_in_global_config(plugin)
del sys.modules[plugin.__module__.split('.')[0]]
del plugin.__module__.split('.')[-1]
del plugin
def get_plugin_by_path(self, plugin_dir):
for plugin in self.plugins: