2007-10-04 00:43:41 +00:00
|
|
|
#
|
2008-11-23 04:58:01 +00:00
|
|
|
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
|
2008-07-05 16:34:46 +00:00
|
|
|
#
|
2014-09-03 22:28:28 +01:00
|
|
|
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
|
|
|
|
|
# the additional special exception to link portions of this program with the OpenSSL library.
|
|
|
|
|
# See LICENSE for more details.
|
2007-10-04 00:43:41 +00:00
|
|
|
#
|
2008-11-23 04:58:01 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
|
|
|
|
|
"""PluginManagerBase"""
|
2022-01-12 20:03:09 +00:00
|
|
|
import email
|
2010-12-06 11:20:22 +00:00
|
|
|
import logging
|
2014-09-03 18:18:29 +01:00
|
|
|
import os.path
|
|
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
import pkg_resources
|
2016-04-13 21:38:33 +02:00
|
|
|
from twisted.internet import defer
|
|
|
|
|
from twisted.python.failure import Failure
|
2007-10-04 00:43:41 +00:00
|
|
|
|
|
|
|
|
import deluge.common
|
2009-07-06 05:18:18 +00:00
|
|
|
import deluge.component as component
|
2014-09-03 18:18:29 +01:00
|
|
|
import deluge.configmanager
|
2009-07-06 05:18:18 +00:00
|
|
|
|
2010-12-06 11:20:22 +00:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
2008-10-27 03:31:11 +00:00
|
|
|
METADATA_KEYS = [
|
2016-11-03 21:26:46 +00:00
|
|
|
'Name',
|
|
|
|
|
'License',
|
|
|
|
|
'Author',
|
|
|
|
|
'Home-page',
|
|
|
|
|
'Summary',
|
|
|
|
|
'Platform',
|
|
|
|
|
'Version',
|
|
|
|
|
'Author-email',
|
|
|
|
|
'Description',
|
2008-10-27 03:31:11 +00:00
|
|
|
]
|
|
|
|
|
|
2010-12-10 03:15:36 +00:00
|
|
|
DEPRECATION_WARNING = """
|
2022-02-06 16:27:47 +00:00
|
|
|
The plugin %s is not using the "deluge_" namespace.
|
2010-12-10 03:15:36 +00:00
|
|
|
In order to avoid package name clashes between regular python packages and
|
|
|
|
|
deluge plugins, the way deluge plugins should be created has changed.
|
|
|
|
|
If you're seeing this message and you're not the developer of the plugin which
|
|
|
|
|
triggered this warning, please report to it's author.
|
|
|
|
|
If you're the developer, please take a look at the plugins hosted on deluge's
|
|
|
|
|
git repository to have an idea of what needs to be changed.
|
|
|
|
|
"""
|
|
|
|
|
|
2014-09-03 22:28:28 +01:00
|
|
|
|
2021-12-28 22:20:57 +02:00
|
|
|
class PluginManagerBase:
|
2007-10-04 00:43:41 +00:00
|
|
|
"""PluginManagerBase is a base class for PluginManagers to inherit"""
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
def __init__(self, config_file, entry_name):
|
2016-11-03 21:26:46 +00:00
|
|
|
log.debug('Plugin manager init..')
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2008-04-08 03:56:27 +00:00
|
|
|
self.config = deluge.configmanager.ConfigManager(config_file)
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2008-09-24 01:23:38 +00:00
|
|
|
# Create the plugins folder if it doesn't exist
|
2018-10-02 15:39:51 +01:00
|
|
|
if not os.path.exists(
|
|
|
|
|
os.path.join(deluge.configmanager.get_config_dir(), 'plugins')
|
|
|
|
|
):
|
2016-11-03 21:26:46 +00:00
|
|
|
os.mkdir(os.path.join(deluge.configmanager.get_config_dir(), 'plugins'))
|
2008-10-27 03:31:11 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
# This is the entry we want to load..
|
|
|
|
|
self.entry_name = entry_name
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
# Loaded plugins
|
|
|
|
|
self.plugins = {}
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
# Scan the plugin folders for plugins
|
|
|
|
|
self.scan_for_plugins()
|
|
|
|
|
|
2007-12-11 04:45:47 +00:00
|
|
|
def enable_plugins(self):
|
2007-10-04 00:43:41 +00:00
|
|
|
# Load plugins that are enabled in the config.
|
2016-11-03 21:26:46 +00:00
|
|
|
for name in self.config['enabled_plugins']:
|
2007-10-05 00:58:26 +00:00
|
|
|
self.enable_plugin(name)
|
2007-12-11 04:45:47 +00:00
|
|
|
|
|
|
|
|
def disable_plugins(self):
|
2016-10-31 19:54:10 +01:00
|
|
|
"""Disable all plugins that are enabled"""
|
|
|
|
|
# Dict will be modified so iterate over generated list
|
|
|
|
|
for key in list(self.plugins):
|
2009-11-22 02:34:51 +00:00
|
|
|
self.disable_plugin(key)
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
def __getitem__(self, key):
|
|
|
|
|
return self.plugins[key]
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
def get_available_plugins(self):
|
2007-10-05 00:58:26 +00:00
|
|
|
"""Returns a list of the available plugins name"""
|
2007-10-04 00:43:41 +00:00
|
|
|
return self.available_plugins
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-05 00:58:26 +00:00
|
|
|
def get_enabled_plugins(self):
|
|
|
|
|
"""Returns a list of enabled plugins"""
|
2017-03-16 21:39:57 +00:00
|
|
|
return list(self.plugins)
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
def scan_for_plugins(self):
|
|
|
|
|
"""Scans for available plugins"""
|
2018-10-31 09:53:22 +00:00
|
|
|
base_dir = deluge.common.resource_filename('deluge', 'plugins')
|
|
|
|
|
user_dir = os.path.join(deluge.configmanager.get_config_dir(), 'plugins')
|
|
|
|
|
base_subdir = [
|
|
|
|
|
os.path.join(base_dir, f)
|
|
|
|
|
for f in os.listdir(base_dir)
|
|
|
|
|
if os.path.isdir(os.path.join(base_dir, f))
|
|
|
|
|
]
|
|
|
|
|
plugin_dirs = [base_dir, user_dir] + base_subdir
|
|
|
|
|
|
|
|
|
|
for dirname in plugin_dirs:
|
|
|
|
|
pkg_resources.working_set.add_entry(dirname)
|
2021-11-05 20:58:26 +01:00
|
|
|
self.pkg_env = pkg_resources.Environment(
|
|
|
|
|
plugin_dirs, platform=None, python=None
|
|
|
|
|
)
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
self.available_plugins = []
|
|
|
|
|
for name in self.pkg_env:
|
2018-06-01 15:35:19 +01:00
|
|
|
log.debug(
|
|
|
|
|
'Found plugin: %s %s at %s',
|
|
|
|
|
self.pkg_env[name][0].project_name,
|
|
|
|
|
self.pkg_env[name][0].version,
|
|
|
|
|
self.pkg_env[name][0].location,
|
|
|
|
|
)
|
2008-10-27 03:31:11 +00:00
|
|
|
self.available_plugins.append(self.pkg_env[name][0].project_name)
|
2007-10-04 00:43:41 +00:00
|
|
|
|
2007-11-02 02:40:44 +00:00
|
|
|
def enable_plugin(self, plugin_name):
|
2016-05-24 23:47:46 +01:00
|
|
|
"""Enable a plugin.
|
2016-04-13 21:38:33 +02:00
|
|
|
|
|
|
|
|
Args:
|
2016-05-24 23:47:46 +01:00
|
|
|
plugin_name (str): The plugin name.
|
2016-04-13 21:38:33 +02:00
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Deferred: A deferred with callback value True or False indicating
|
2016-05-24 23:47:46 +01:00
|
|
|
whether the plugin is enabled or not.
|
|
|
|
|
|
2016-04-13 21:38:33 +02:00
|
|
|
"""
|
2007-11-02 02:40:44 +00:00
|
|
|
if plugin_name not in self.available_plugins:
|
2021-09-21 21:40:13 +01:00
|
|
|
log.warning('Cannot enable non-existent plugin %s', plugin_name)
|
2016-04-13 21:38:33 +02:00
|
|
|
return defer.succeed(False)
|
2007-11-02 02:40:44 +00:00
|
|
|
|
2009-09-05 19:32:01 +00:00
|
|
|
if plugin_name in self.plugins:
|
2016-11-03 21:26:46 +00:00
|
|
|
log.warning('Cannot enable already enabled plugin %s', plugin_name)
|
2016-04-13 21:38:33 +02:00
|
|
|
return defer.succeed(True)
|
2009-09-05 19:32:01 +00:00
|
|
|
|
2016-11-03 21:26:46 +00:00
|
|
|
plugin_name = plugin_name.replace(' ', '-')
|
2007-11-02 02:40:44 +00:00
|
|
|
egg = self.pkg_env[plugin_name][0]
|
2018-10-31 09:53:22 +00:00
|
|
|
# Activate is required by non-namespace plugins.
|
2007-10-04 00:43:41 +00:00
|
|
|
egg.activate()
|
2016-04-13 21:38:33 +02:00
|
|
|
return_d = defer.succeed(True)
|
|
|
|
|
|
2007-10-04 00:43:41 +00:00
|
|
|
for name in egg.get_entry_map(self.entry_name):
|
2009-02-23 23:54:06 +00:00
|
|
|
try:
|
2018-10-31 09:53:22 +00:00
|
|
|
cls = egg.load_entry_point(self.entry_name, name)
|
2016-11-03 21:26:46 +00:00
|
|
|
instance = cls(plugin_name.replace('-', '_'))
|
2016-04-13 21:38:33 +02:00
|
|
|
except component.ComponentAlreadyRegistered as ex:
|
|
|
|
|
log.error(ex)
|
|
|
|
|
return defer.succeed(False)
|
2014-09-03 13:23:28 +01:00
|
|
|
except Exception as ex:
|
2018-10-02 15:39:51 +01:00
|
|
|
log.error(
|
|
|
|
|
'Unable to instantiate plugin %r from %r!', name, egg.location
|
|
|
|
|
)
|
2014-09-03 13:23:28 +01:00
|
|
|
log.exception(ex)
|
2009-10-28 13:33:27 +00:00
|
|
|
continue
|
2016-04-13 21:38:33 +02:00
|
|
|
try:
|
|
|
|
|
return_d = defer.maybeDeferred(instance.enable)
|
|
|
|
|
except Exception as ex:
|
2016-11-13 15:09:30 +00:00
|
|
|
log.error('Unable to enable plugin: %s', name)
|
2016-04-13 21:38:33 +02:00
|
|
|
log.exception(ex)
|
|
|
|
|
return_d = defer.fail(False)
|
|
|
|
|
|
2022-02-06 16:27:47 +00:00
|
|
|
if not instance.__module__.startswith('deluge_'):
|
2010-12-10 03:15:36 +00:00
|
|
|
import warnings
|
2018-10-02 15:39:51 +01:00
|
|
|
|
2010-12-10 03:15:36 +00:00
|
|
|
warnings.warn_explicit(
|
|
|
|
|
DEPRECATION_WARNING % name,
|
|
|
|
|
DeprecationWarning,
|
2018-10-02 15:39:51 +01:00
|
|
|
instance.__module__,
|
|
|
|
|
0,
|
2010-12-10 03:15:36 +00:00
|
|
|
)
|
2016-11-03 21:26:46 +00:00
|
|
|
if self._component_state == 'Started':
|
2018-10-02 15:39:51 +01:00
|
|
|
|
2016-04-13 21:38:33 +02:00
|
|
|
def on_enabled(result, instance):
|
|
|
|
|
return component.start([instance.plugin._component_name])
|
2018-10-02 15:39:51 +01:00
|
|
|
|
2016-04-13 21:38:33 +02:00
|
|
|
return_d.addCallback(on_enabled, instance)
|
|
|
|
|
|
|
|
|
|
def on_started(result, instance):
|
2016-11-03 21:26:46 +00:00
|
|
|
plugin_name_space = plugin_name.replace('-', ' ')
|
2016-04-13 21:38:33 +02:00
|
|
|
self.plugins[plugin_name_space] = instance
|
2016-11-03 21:26:46 +00:00
|
|
|
if plugin_name_space not in self.config['enabled_plugins']:
|
2018-10-02 15:39:51 +01:00
|
|
|
log.debug(
|
|
|
|
|
'Adding %s to enabled_plugins list in config', plugin_name_space
|
|
|
|
|
)
|
2016-11-03 21:26:46 +00:00
|
|
|
self.config['enabled_plugins'].append(plugin_name_space)
|
2016-11-13 15:09:30 +00:00
|
|
|
log.info('Plugin %s enabled...', plugin_name_space)
|
2016-04-13 21:38:33 +02:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def on_started_error(result, instance):
|
2018-06-01 15:35:19 +01:00
|
|
|
log.error(
|
2018-10-02 15:39:51 +01:00
|
|
|
'Failed to start plugin: %s\n%s',
|
|
|
|
|
plugin_name,
|
2018-06-01 15:35:19 +01:00
|
|
|
result.getTraceback(elideFrameworkCode=1, detail='brief'),
|
|
|
|
|
)
|
2017-03-22 00:07:34 +00:00
|
|
|
self.plugins[plugin_name.replace('-', ' ')] = instance
|
|
|
|
|
self.disable_plugin(plugin_name)
|
2016-04-13 21:38:33 +02:00
|
|
|
return False
|
|
|
|
|
|
2018-10-02 15:39:51 +01:00
|
|
|
return_d.addCallbacks(
|
|
|
|
|
on_started,
|
|
|
|
|
on_started_error,
|
|
|
|
|
callbackArgs=[instance],
|
|
|
|
|
errbackArgs=[instance],
|
|
|
|
|
)
|
2016-04-13 21:38:33 +02:00
|
|
|
return return_d
|
|
|
|
|
|
|
|
|
|
return defer.succeed(False)
|
2008-07-05 16:34:46 +00:00
|
|
|
|
2007-10-05 00:58:26 +00:00
|
|
|
def disable_plugin(self, name):
|
2016-05-24 23:47:46 +01:00
|
|
|
"""Disable a plugin.
|
2016-04-13 21:38:33 +02:00
|
|
|
|
|
|
|
|
Args:
|
2016-05-24 23:47:46 +01:00
|
|
|
plugin_name (str): The plugin name.
|
2016-04-13 21:38:33 +02:00
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Deferred: A deferred with callback value True or False indicating
|
2016-05-24 23:47:46 +01:00
|
|
|
whether the plugin is disabled or not.
|
|
|
|
|
|
2016-04-13 21:38:33 +02:00
|
|
|
"""
|
|
|
|
|
if name not in self.plugins:
|
2016-11-13 15:09:30 +00:00
|
|
|
log.warning('Plugin "%s" is not enabled...', name)
|
2016-04-13 21:38:33 +02:00
|
|
|
return defer.succeed(True)
|
|
|
|
|
|
2007-11-02 02:40:44 +00:00
|
|
|
try:
|
2016-04-13 21:38:33 +02:00
|
|
|
d = defer.maybeDeferred(self.plugins[name].disable)
|
|
|
|
|
except Exception as ex:
|
2016-11-13 15:09:30 +00:00
|
|
|
log.error('Error when disabling plugin: %s', self.plugin._component_name)
|
2017-03-22 00:07:34 +00:00
|
|
|
log.debug(ex)
|
2016-04-13 21:38:33 +02:00
|
|
|
d = defer.succeed(False)
|
|
|
|
|
|
|
|
|
|
def on_disabled(result):
|
|
|
|
|
ret = True
|
|
|
|
|
if isinstance(result, Failure):
|
2018-10-02 15:39:51 +01:00
|
|
|
log.debug(
|
|
|
|
|
'Error when disabling plugin %s: %s', name, result.getTraceback()
|
|
|
|
|
)
|
2016-04-13 21:38:33 +02:00
|
|
|
ret = False
|
|
|
|
|
try:
|
|
|
|
|
component.deregister(self.plugins[name].plugin)
|
|
|
|
|
del self.plugins[name]
|
2016-11-03 21:26:46 +00:00
|
|
|
self.config['enabled_plugins'].remove(name)
|
2016-04-13 21:38:33 +02:00
|
|
|
except Exception as ex:
|
2021-09-21 21:40:13 +01:00
|
|
|
log.warning('Problems occurred disabling plugin: %s', name)
|
2017-03-22 00:07:34 +00:00
|
|
|
log.debug(ex)
|
2016-04-13 21:38:33 +02:00
|
|
|
ret = False
|
|
|
|
|
else:
|
2016-11-13 15:09:30 +00:00
|
|
|
log.info('Plugin %s disabled...', name)
|
2016-04-13 21:38:33 +02:00
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
d.addBoth(on_disabled)
|
|
|
|
|
return d
|
2008-10-27 03:31:11 +00:00
|
|
|
|
|
|
|
|
def get_plugin_info(self, name):
|
|
|
|
|
"""Returns a dictionary of plugin info from the metadata"""
|
2022-01-12 19:03:07 +00:00
|
|
|
|
2013-05-21 18:36:21 +02:00
|
|
|
if not self.pkg_env[name]:
|
2021-09-21 21:40:13 +01:00
|
|
|
log.warning('Failed to retrieve info for plugin: %s', name)
|
2022-01-12 19:03:07 +00:00
|
|
|
info = {}.fromkeys(METADATA_KEYS, '')
|
|
|
|
|
info['Name'] = info['Version'] = 'not available'
|
2013-05-21 18:36:21 +02:00
|
|
|
return info
|
2022-01-12 19:03:07 +00:00
|
|
|
|
|
|
|
|
pkg_info = self.pkg_env[name][0].get_metadata('PKG-INFO')
|
|
|
|
|
return self.parse_pkg_info(pkg_info)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def parse_pkg_info(pkg_info):
|
2022-01-12 20:03:09 +00:00
|
|
|
metadata_msg = email.message_from_string(pkg_info)
|
|
|
|
|
metadata_ver = metadata_msg.get('Metadata-Version')
|
2022-01-12 19:03:07 +00:00
|
|
|
|
2022-01-12 20:03:09 +00:00
|
|
|
info = {key: metadata_msg.get(key, '') for key in METADATA_KEYS}
|
2022-01-12 19:03:07 +00:00
|
|
|
|
2022-01-12 20:03:09 +00:00
|
|
|
# Optional Description field in body (Metadata spec >=2.1)
|
|
|
|
|
if not info['Description'] and metadata_ver.startswith('2'):
|
|
|
|
|
info['Description'] = metadata_msg.get_payload().strip()
|
2022-01-12 19:03:07 +00:00
|
|
|
|
2008-10-27 03:31:11 +00:00
|
|
|
return info
|