From 70d8b65f0a499d36f21c2c311ee02e3a9d67f03d Mon Sep 17 00:00:00 2001 From: bendikro Date: Wed, 13 Apr 2016 21:38:33 +0200 Subject: [PATCH] [WebUi] [Core] Fixes to plugin handling and WebUi plugin + tests This should fix problems with errors occuring when failing to enable plugins. Errors in plugin handling are handled better and properly logged. WebUI plugin in particular had issues when being enabled and disabled multiple times because it was trying to create DelugeWeb component each time it was enabled. If deluge-web is already listening on the same port, enabling the WebUI plugin will fail, and the checkbox will not be checked. There are still some issues when enabling/disabling plugins by clicking fast multiple times on the checkbox. --- .travis.yml | 2 +- deluge/component.py | 14 +-- deluge/core/core.py | 6 +- deluge/core/pluginmanager.py | 27 ++++- deluge/pluginmanagerbase.py | 107 ++++++++++++++---- deluge/plugins/Stats/__init__.py | 0 .../Stats/deluge/plugins/stats/core.py | 2 +- .../deluge/plugins/stats/tests/__init__.py | 0 .../deluge/plugins/stats/tests/test_stats.py | 55 +++++---- deluge/plugins/WebUi/__init__.py | 0 .../WebUi/deluge/plugins/webui/core.py | 50 ++++---- .../deluge/plugins/webui/tests/__init__.py | 0 .../plugins/webui/tests/test_plugin_webui.py | 48 ++++++++ deluge/plugins/init.py | 12 +- deluge/tests/test_alertmanager.py | 27 ++--- deluge/ui/client.py | 2 +- deluge/ui/gtkui/pluginmanager.py | 6 +- deluge/ui/gtkui/preferences.py | 13 ++- deluge/ui/web/json_api.py | 10 +- deluge/ui/web/server.py | 11 +- tox.ini | 4 +- 21 files changed, 276 insertions(+), 120 deletions(-) create mode 100644 deluge/plugins/Stats/__init__.py create mode 100644 deluge/plugins/Stats/deluge/plugins/stats/tests/__init__.py create mode 100644 deluge/plugins/WebUi/__init__.py create mode 100644 deluge/plugins/WebUi/deluge/plugins/webui/tests/__init__.py create mode 100644 deluge/plugins/WebUi/deluge/plugins/webui/tests/test_plugin_webui.py diff --git a/.travis.yml b/.travis.yml index 07168cae7..a1a674f17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ env: - TOX_ENV=trial APTPACKAGES="$APTPACKAGES $APTPACKAGES_GTKUI" - TOX_ENV=pygtkui APTPACKAGES="$APTPACKAGES $APTPACKAGES_GTKUI" # - TOX_ENV=testcoverage APTPACKAGES="$APTPACKAGES $APTPACKAGES_GTKUI" -# - TOX_ENV=plugins + - TOX_ENV=plugins virtualenv: system_site_packages: true diff --git a/deluge/component.py b/deluge/component.py index 60bfbd78e..b24a848c3 100644 --- a/deluge/component.py +++ b/deluge/component.py @@ -10,8 +10,9 @@ import logging from collections import defaultdict +from twisted.internet import reactor from twisted.internet.defer import DeferredList, fail, maybeDeferred, succeed -from twisted.internet.task import LoopingCall +from twisted.internet.task import LoopingCall, deferLater log = logging.getLogger(__name__) @@ -96,14 +97,13 @@ class Component(object): self._component_state = "Stopped" self._component_starting_deferred = None log.error(result) - return result + return fail(result) if self._component_state == "Stopped": if hasattr(self, "start"): self._component_state = "Starting" - d = maybeDeferred(self.start) - d.addCallback(on_start) - d.addErrback(on_start_fail) + d = deferLater(reactor, 1, self.start) + d.addCallbacks(on_start, on_start_fail) self._component_starting_deferred = d else: d = maybeDeferred(on_start, None) @@ -240,13 +240,13 @@ class ComponentRegistry(object): :type obj: object """ - if obj in self.components.values(): log.debug("Deregistering Component: %s", obj._component_name) d = self.stop([obj._component_name]) def on_stop(result, name): - del self.components[name] + # Component may have been removed, so pop to ensure it doesn't fail + self.components.pop(name, None) return d.addCallback(on_stop, obj._component_name) else: return succeed(None) diff --git a/deluge/core/core.py b/deluge/core/core.py index 407b79856..9f51e1773 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -579,13 +579,11 @@ class Core(component.Component): @export def enable_plugin(self, plugin): - self.pluginmanager.enable_plugin(plugin) - return None + return self.pluginmanager.enable_plugin(plugin) @export def disable_plugin(self, plugin): - self.pluginmanager.disable_plugin(plugin) - return None + return self.pluginmanager.disable_plugin(plugin) @export def force_recheck(self, torrent_ids): diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 4b237dca8..8f82d362f 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -12,6 +12,8 @@ import logging +from twisted.internet import defer + import deluge.component as component import deluge.pluginmanagerbase from deluge.event import PluginDisabledEvent, PluginEnabledEvent @@ -52,16 +54,29 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Compon log.exception(ex) def enable_plugin(self, name): + d = defer.succeed(True) if name not in self.plugins: - deluge.pluginmanagerbase.PluginManagerBase.enable_plugin(self, name) - if name in self.plugins: - component.get("EventManager").emit(PluginEnabledEvent(name)) + d = deluge.pluginmanagerbase.PluginManagerBase.enable_plugin(self, name) + + def on_enable_plugin(result): + if result is True and name in self.plugins: + component.get("EventManager").emit(PluginEnabledEvent(name)) + return result + + d.addBoth(on_enable_plugin) + return d def disable_plugin(self, name): + d = defer.succeed(True) if name in self.plugins: - deluge.pluginmanagerbase.PluginManagerBase.disable_plugin(self, name) - if name not in self.plugins: - component.get("EventManager").emit(PluginDisabledEvent(name)) + d = deluge.pluginmanagerbase.PluginManagerBase.disable_plugin(self, name) + + def on_disable_plugin(result): + if name not in self.plugins: + component.get("EventManager").emit(PluginDisabledEvent(name)) + return result + d.addBoth(on_disable_plugin) + return d def get_status(self, torrent_id, fields): """Return the value of status fields for the selected torrent_id.""" diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index 8ca875e77..ec3094241 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -14,6 +14,8 @@ import logging import os.path import pkg_resources +from twisted.internet import defer +from twisted.python.failure import Failure import deluge.common import deluge.component as component @@ -111,28 +113,47 @@ class PluginManagerBase(object): self.available_plugins.append(self.pkg_env[name][0].project_name) def enable_plugin(self, plugin_name): - """Enables a plugin""" + """Enable a plugin + + Args: + plugin_name (str): The plugin name + + Returns: + Deferred: A deferred with callback value True or False indicating + whether the plugin is enabled or not. + """ if plugin_name not in self.available_plugins: log.warning("Cannot enable non-existant plugin %s", plugin_name) - return + return defer.succeed(False) if plugin_name in self.plugins: log.warning("Cannot enable already enabled plugin %s", plugin_name) - return + return defer.succeed(True) plugin_name = plugin_name.replace(" ", "-") egg = self.pkg_env[plugin_name][0] egg.activate() + return_d = defer.succeed(True) + for name in egg.get_entry_map(self.entry_name): entry_point = egg.get_entry_info(self.entry_name, name) try: cls = entry_point.load() instance = cls(plugin_name.replace("-", "_")) + except component.ComponentAlreadyRegistered as ex: + log.error(ex) + return defer.succeed(False) except Exception as ex: log.error("Unable to instantiate plugin %r from %r!", name, egg.location) log.exception(ex) continue - instance.enable() + try: + return_d = defer.maybeDeferred(instance.enable) + except Exception as ex: + log.error("Unable to enable plugin '%s'!", name) + log.exception(ex) + return_d = defer.fail(False) + if not instance.__module__.startswith("deluge.plugins."): import warnings warnings.warn_explicit( @@ -141,25 +162,71 @@ class PluginManagerBase(object): instance.__module__, 0 ) if self._component_state == "Started": - component.start([instance.plugin._component_name]) - plugin_name = plugin_name.replace("-", " ") - self.plugins[plugin_name] = instance - if plugin_name not in self.config["enabled_plugins"]: - log.debug("Adding %s to enabled_plugins list in config", plugin_name) - self.config["enabled_plugins"].append(plugin_name) - log.info("Plugin %s enabled..", plugin_name) + def on_enabled(result, instance): + return component.start([instance.plugin._component_name]) + return_d.addCallback(on_enabled, instance) + + def on_started(result, instance): + plugin_name_space = plugin_name.replace("-", " ") + self.plugins[plugin_name_space] = instance + if plugin_name_space not in self.config["enabled_plugins"]: + log.debug("Adding %s to enabled_plugins list in config", plugin_name_space) + self.config["enabled_plugins"].append(plugin_name_space) + log.info("Plugin %s enabled..", plugin_name_space) + return True + + def on_started_error(result, instance): + log.warn("Failed to start plugin '%s': %s", plugin_name, result.getTraceback()) + component.deregister(instance.plugin) + return False + + return_d.addCallbacks(on_started, on_started_error, callbackArgs=[instance], errbackArgs=[instance]) + return return_d + + return defer.succeed(False) def disable_plugin(self, name): - """Disables a plugin""" - try: - self.plugins[name].disable() - component.deregister(self.plugins[name].plugin) - del self.plugins[name] - self.config["enabled_plugins"].remove(name) - except KeyError: - log.warning("Plugin %s is not enabled..", name) + """ + Disable a plugin - log.info("Plugin %s disabled..", name) + Args: + plugin_name (str): The plugin name + + Returns: + Deferred: A deferred with callback value True or False indicating + whether the plugin is disabled or not. + """ + if name not in self.plugins: + log.warning("Plugin '%s' is not enabled..", name) + return defer.succeed(True) + + try: + d = defer.maybeDeferred(self.plugins[name].disable) + except Exception as ex: + log.error("Error when disabling plugin '%s'", self.plugin._component_name) + log.exception(ex) + d = defer.succeed(False) + + def on_disabled(result): + ret = True + if isinstance(result, Failure): + log.error("Error when disabling plugin '%s'", name) + log.exception(result.getTraceback()) + ret = False + try: + component.deregister(self.plugins[name].plugin) + del self.plugins[name] + self.config["enabled_plugins"].remove(name) + except Exception as ex: + log.error("Unable to disable plugin '%s'!", name) + log.exception(ex) + ret = False + else: + log.info("Plugin %s disabled..", name) + return ret + + d.addBoth(on_disabled) + return d def get_plugin_info(self, name): """Returns a dictionary of plugin info from the metadata""" diff --git a/deluge/plugins/Stats/__init__.py b/deluge/plugins/Stats/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/plugins/Stats/deluge/plugins/stats/core.py b/deluge/plugins/Stats/deluge/plugins/stats/core.py index f54ff860b..ab44d2f31 100644 --- a/deluge/plugins/Stats/deluge/plugins/stats/core.py +++ b/deluge/plugins/Stats/deluge/plugins/stats/core.py @@ -100,7 +100,7 @@ class Core(CorePluginBase): try: self.update_timer.stop() self.save_timer.stop() - except: + except AssertionError: pass def add_stats(self, *stats): diff --git a/deluge/plugins/Stats/deluge/plugins/stats/tests/__init__.py b/deluge/plugins/Stats/deluge/plugins/stats/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/plugins/Stats/deluge/plugins/stats/tests/test_stats.py b/deluge/plugins/Stats/deluge/plugins/stats/tests/test_stats.py index 147ee78bb..e992ba64d 100644 --- a/deluge/plugins/Stats/deluge/plugins/stats/tests/test_stats.py +++ b/deluge/plugins/Stats/deluge/plugins/stats/tests/test_stats.py @@ -1,10 +1,17 @@ -import pytest -import twisted.internet.defer as defer +# -*- coding: utf-8 -*- +# +# 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. +# + +from twisted.internet import defer from twisted.trial import unittest import deluge.component as component from deluge.common import fsize from deluge.tests import common as tests_common +from deluge.tests.basetest import BaseTestCase from deluge.ui.client import client @@ -17,35 +24,41 @@ def print_totals(totals): print("down:", fsize(totals["total_download"] - totals["total_payload_download"])) -class StatsTestCase(unittest.TestCase): +class StatsTestCase(BaseTestCase): - def setUp(self): # NOQA + def set_up(self): defer.setDebugging(True) tests_common.set_tmp_config_dir() client.start_classic_mode() client.core.enable_plugin("Stats") + return component.start() - def tearDown(self): # NOQA + def tear_down(self): client.stop_classic_mode() + return component.shutdown() - def on_shutdown(result): - component._ComponentRegistry.components = {} - return component.shutdown().addCallback(on_shutdown) - - @pytest.mark.todo + @defer.inlineCallbacks def test_client_totals(self): - StatsTestCase.test_client_totals.im_func.todo = "To be fixed" + plugins = yield client.core.get_available_plugins() + if "Stats" not in plugins: + raise unittest.SkipTest("WebUi plugin not available for testing") - def callback(args): - print_totals(args) - d = client.stats.get_totals() - d.addCallback(callback) + totals = yield client.stats.get_totals() + self.assertEquals(totals['total_upload'], 0) + self.assertEquals(totals['total_payload_upload'], 0) + self.assertEquals(totals['total_payload_download'], 0) + self.assertEquals(totals['total_download'], 0) + # print_totals(totals) - @pytest.mark.todo + @defer.inlineCallbacks def test_session_totals(self): - StatsTestCase.test_session_totals.im_func.todo = "To be fixed" + plugins = yield client.core.get_available_plugins() + if "Stats" not in plugins: + raise unittest.SkipTest("WebUi plugin not available for testing") - def callback(args): - print_totals(args) - d = client.stats.get_session_totals() - d.addCallback(callback) + totals = yield client.stats.get_session_totals() + self.assertEquals(totals['total_upload'], 0) + self.assertEquals(totals['total_payload_upload'], 0) + self.assertEquals(totals['total_payload_download'], 0) + self.assertEquals(totals['total_download'], 0) + # print_totals(totals) diff --git a/deluge/plugins/WebUi/__init__.py b/deluge/plugins/WebUi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/plugins/WebUi/deluge/plugins/webui/core.py b/deluge/plugins/WebUi/deluge/plugins/webui/core.py index cf2811a81..b3a1a2722 100644 --- a/deluge/plugins/WebUi/deluge/plugins/webui/core.py +++ b/deluge/plugins/WebUi/deluge/plugins/webui/core.py @@ -13,6 +13,10 @@ import logging +from twisted.internet import defer +from twisted.internet.error import CannotListenError + +import deluge.component as component from deluge import configmanager from deluge.core.rpcserver import export from deluge.plugins.pluginbase import CorePluginBase @@ -27,28 +31,21 @@ DEFAULT_PREFS = { class Core(CorePluginBase): + server = None def enable(self): self.config = configmanager.ConfigManager("web_plugin.conf", DEFAULT_PREFS) - self.server = None if self.config['enabled']: - self.start() + self.start_server() def disable(self): - if self.server: - self.server.stop() + self.stop_server() def update(self): pass - def restart(self): - if self.server: - self.server.stop().addCallback(self.on_stop) - else: - self.start() - - def on_stop(self, *args): - self.start() + def _on_stop(self, *args): + return self.start_server() @export def got_deluge_web(self): @@ -59,25 +56,34 @@ class Core(CorePluginBase): except ImportError: return False - @export - def start(self): + def start_server(self): if not self.server: try: from deluge.ui.web import server except ImportError: return False - self.server = server.DelugeWeb() + try: + self.server = component.get("DelugeWeb") + except KeyError: + self.server = server.DelugeWeb() self.server.port = self.config["port"] self.server.https = self.config["ssl"] - self.server.start(standalone=False) + try: + self.server.start(standalone=False) + except CannotListenError as ex: + log.warn("Failed to start WebUI server: %s", ex) + raise return True - @export - def stop(self): + def stop_server(self): if self.server: - self.server.stop() + return self.server.stop() + return defer.succeed(True) + + def restart_server(self): + return self.stop_server().addCallback(self._on_stop) @export def set_config(self, config): @@ -97,11 +103,11 @@ class Core(CorePluginBase): self.config.save() if action == 'start': - return self.start() + return self.start_server() elif action == 'stop': - return self.stop() + return self.stop_server() elif action == 'restart': - return self.restart() + return self.restart_server() @export def get_config(self): diff --git a/deluge/plugins/WebUi/deluge/plugins/webui/tests/__init__.py b/deluge/plugins/WebUi/deluge/plugins/webui/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deluge/plugins/WebUi/deluge/plugins/webui/tests/test_plugin_webui.py b/deluge/plugins/WebUi/deluge/plugins/webui/tests/test_plugin_webui.py new file mode 100644 index 000000000..ed6c5ed1f --- /dev/null +++ b/deluge/plugins/WebUi/deluge/plugins/webui/tests/test_plugin_webui.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2016 bendikro +# +# 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. +# + +from twisted.trial import unittest + +import deluge.component as component +from deluge.core.core import Core +from deluge.core.rpcserver import RPCServer +from deluge.tests import common +from deluge.tests.basetest import BaseTestCase + +common.disable_new_release_check() + + +class WebUIPluginTestCase(BaseTestCase): + + def set_up(self): + common.set_tmp_config_dir() + self.rpcserver = RPCServer(listen=False) + self.core = Core() + return component.start() + + def tear_down(self): + + def on_shutdown(result): + del self.rpcserver + del self.core + return component.shutdown().addCallback(on_shutdown) + + def test_enable_webui(self): + if "WebUi" not in self.core.get_available_plugins(): + raise unittest.SkipTest("WebUi plugin not available for testing") + + d = self.core.enable_plugin("WebUi") + + def result_cb(result): + if "WebUi" not in self.core.get_enabled_plugins(): + self.fail("Failed to enable WebUi plugin") + self.assertTrue(result) + + d.addBoth(result_cb) + return d diff --git a/deluge/plugins/init.py b/deluge/plugins/init.py index 7569df890..337a1dfd5 100644 --- a/deluge/plugins/init.py +++ b/deluge/plugins/init.py @@ -22,15 +22,7 @@ class PluginInitBase(object): self.plugin = self._plugin_cls(plugin_name) def enable(self): - try: - self.plugin.enable() - except Exception as ex: - log.error("Unable to enable plugin \"%s\"!", self.plugin._component_name) - log.exception(ex) + return self.plugin.enable() def disable(self): - try: - self.plugin.disable() - except Exception as ex: - log.error("Unable to disable plugin \"%s\"!", self.plugin._component_name) - log.exception(ex) + return self.plugin.disable() diff --git a/deluge/tests/test_alertmanager.py b/deluge/tests/test_alertmanager.py index cb0e22557..fc6a020b6 100644 --- a/deluge/tests/test_alertmanager.py +++ b/deluge/tests/test_alertmanager.py @@ -1,30 +1,31 @@ -from twisted.trial import unittest +# -*- coding: utf-8 -*- +# +# 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. +# import deluge.component as component from deluge.core.core import Core +from .basetest import BaseTestCase -class AlertManagerTestCase(unittest.TestCase): - def setUp(self): # NOQA + +class AlertManagerTestCase(BaseTestCase): + + def set_up(self): self.core = Core() - self.am = component.get("AlertManager") - component.start(["AlertManager"]) + return component.start(["AlertManager"]) - def tearDown(self): # NOQA - def on_shutdown(result): - component._ComponentRegistry.components = {} - del self.am - del self.core - - return component.shutdown().addCallback(on_shutdown) + def tear_down(self): + return component.shutdown() def test_register_handler(self): def handler(alert): return self.am.register_handler("dummy_alert", handler) - self.assertEquals(self.am.handlers["dummy_alert"], [handler]) def test_deregister_handler(self): diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 70ddda62a..6743f75cc 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -588,7 +588,7 @@ class Client(object): if self.is_classicmode(): self._daemon_proxy.disconnect() self.stop_classic_mode() - return + return defer.succeed(True) if self._daemon_proxy: return self._daemon_proxy.disconnect() diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index ab693c5f6..ab53a53b8 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -66,7 +66,11 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Compon self.enable_plugin(plugin) def _on_plugin_enabled_event(self, name): - self.enable_plugin(name) + try: + self.enable_plugin(name) + except Exception as ex: + log.warn("Failed to enable plugin '%s': ex: %s", name, ex) + self.run_on_show_prefs() def _on_plugin_disabled_event(self, name): diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index e9b27a87c..98cedb8c2 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -929,15 +929,22 @@ class Preferences(component.Component): client.force_call() def on_plugin_toggled(self, renderer, path): - log.debug("on_plugin_toggled") row = self.plugin_liststore.get_iter_from_string(path) name = self.plugin_liststore.get_value(row, 0) value = self.plugin_liststore.get_value(row, 1) + log.debug("on_plugin_toggled - %s: %s", name, value) self.plugin_liststore.set_value(row, 1, not value) if not value: - client.core.enable_plugin(name) + d = client.core.enable_plugin(name) else: - client.core.disable_plugin(name) + d = client.core.disable_plugin(name) + + def on_plugin_action(arg): + if not value and arg is False: + log.warn("Failed to enable plugin '%s'", name) + self.plugin_liststore.set_value(row, 1, False) + + d.addBoth(on_plugin_action) def on_plugin_selection_changed(self, treeselection): log.debug("on_plugin_selection_changed") diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py index 5951cbc55..3266467cb 100644 --- a/deluge/ui/web/json_api.py +++ b/deluge/ui/web/json_api.py @@ -392,13 +392,14 @@ class WebApi(JSONComponent): default = component.get("DelugeWeb").config["default_daemon"] host = component.get("Web")._get_host(default) if host: - self._connect_daemon(*host[1:]) + return self._connect_daemon(*host[1:]) else: - self._connect_daemon() + return self._connect_daemon() + return defer.succeed(True) def _on_client_disconnect(self, *args): component.get("Web.PluginManager").stop() - self.stop() + return self.stop() def _get_host(self, host_id): """ @@ -415,11 +416,12 @@ class WebApi(JSONComponent): def start(self): self.core_config.start() - self.sessionproxy.start() + return self.sessionproxy.start() def stop(self): self.core_config.stop() self.sessionproxy.stop() + return defer.succeed(True) def _connect_daemon(self, host="localhost", port=58846, username="", password=""): """ diff --git a/deluge/ui/web/server.py b/deluge/ui/web/server.py index a6c8abdd9..41671f067 100644 --- a/deluge/ui/web/server.py +++ b/deluge/ui/web/server.py @@ -577,12 +577,13 @@ class DelugeWeb(component.Component): Args: standalone (bool): Whether the server runs as a standalone process If standalone, start twisted reactor. - - Returns: - Deferred """ - log.info("%s %s.", _("Starting server in PID"), os.getpid()) + if self.socket: + log.warn("DelugeWeb is already running and cannot be started") + return + self.standalone = standalone + log.info("Starting webui server at PID %s", os.getpid()) if self.https: self.start_ssl() else: @@ -629,7 +630,7 @@ class DelugeWeb(component.Component): def shutdown(self, *args): self.stop() - if self.standalone: + if self.standalone and reactor.running: reactor.stop() diff --git a/tox.ini b/tox.ini index 38b3e7f0c..5cf421e8c 100644 --- a/tox.ini +++ b/tox.ini @@ -61,7 +61,9 @@ whitelist_externals = trial commands = trial --reporter=deluge-reporter deluge/tests [testenv:plugins] -commands = py.test deluge/plugins +commands = + python setup.py egg_info_plugins + py.test deluge/plugins [testenv:py26] basepython = python2.6