diff options
Diffstat (limited to 'deluge/component.py')
-rw-r--r-- | deluge/component.py | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/deluge/component.py b/deluge/component.py new file mode 100644 index 0000000..421f49a --- /dev/null +++ b/deluge/component.py @@ -0,0 +1,488 @@ +# +# Copyright (C) 2007-2010 Andrew Resch <andrewresch@gmail.com> +# +# 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 logging +import traceback +from collections import defaultdict + +from twisted.internet import reactor +from twisted.internet.defer import DeferredList, fail, maybeDeferred, succeed +from twisted.internet.task import LoopingCall, deferLater + +log = logging.getLogger(__name__) + + +class ComponentAlreadyRegistered(Exception): + pass + + +class ComponentException(Exception): + def __init__(self, message, tb): + super().__init__(message) + self.message = message + self.tb = tb + + def __str__(self): + s = super().__str__() + return '{}\n{}'.format(s, ''.join(self.tb)) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.message == other.message + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + +class Component: + """Component objects are singletons managed by the :class:`ComponentRegistry`. + + When a new Component object is instantiated, it will be automatically + registered with the :class:`ComponentRegistry`. + + The ComponentRegistry has the ability to start, stop, pause and shutdown the + components registered with it. + + **Events:** + + **start()** - This method is called when the client has connected to a + Deluge core. + + **stop()** - This method is called when the client has disconnected from a + Deluge core. + + **update()** - This method is called every 1 second by default while the + Component is in a *Started* state. The interval can be + specified during instantiation. The update() timer can be + paused by instructing the :class:`ComponentRegistry` to pause + this Component. + + **pause()** - This method is called when the component is being paused. + + **resume()** - This method is called when the component resumes from a Paused + state. + + **shutdown()** - This method is called when the client is exiting. If the + Component is in a "Started" state when this is called, a + call to stop() will be issued prior to shutdown(). + + **States:** + + A Component can be in one of these 5 states. + + **Started** - The Component has been started by the :class:`ComponentRegistry` + and will have it's update timer started. + + **Starting** - The Component has had it's start method called, but it hasn't + fully started yet. + + **Stopped** - The Component has either been stopped or has yet to be started. + + **Stopping** - The Component has had its stop method called, but it hasn't + fully stopped yet. + + **Paused** - The Component has had its update timer stopped, but will + still be considered in a Started state. + + """ + + def __init__(self, name, interval=1, depend=None): + """Initialize component. + + Args: + name (str): Name of component. + interval (int, optional): The interval in seconds to call the update function. + depend (list, optional): The names of components this component depends on. + + """ + self._component_name = name + self._component_interval = interval + self._component_depend = depend + self._component_state = 'Stopped' + self._component_timer = None + self._component_starting_deferred = None + self._component_stopping_deferred = None + _ComponentRegistry.register(self) + + def __del__(self): + if _ComponentRegistry: + _ComponentRegistry.deregister(self) + + def _component_start_timer(self): + self._component_timer = LoopingCall(self.update) + self._component_timer.start(self._component_interval) + + def _component_start(self): + def on_start(result): + self._component_state = 'Started' + self._component_starting_deferred = None + self._component_start_timer() + return True + + def on_start_fail(result): + self._component_state = 'Stopped' + self._component_starting_deferred = None + log.error(result) + return fail(result) + + if self._component_state == 'Stopped': + self._component_state = 'Starting' + d = deferLater(reactor, 0, self.start) + d.addCallbacks(on_start, on_start_fail) + self._component_starting_deferred = d + elif self._component_state == 'Starting': + return self._component_starting_deferred + elif self._component_state == 'Started': + d = succeed(True) + else: + d = fail( + ComponentException( + 'Trying to start component "%s" but it is ' + 'not in a stopped state. Current state: %s' + % (self._component_name, self._component_state), + traceback.format_stack(limit=4), + ) + ) + return d + + def _component_stop(self): + def on_stop(result): + self._component_state = 'Stopped' + if self._component_timer and self._component_timer.running: + self._component_timer.stop() + return True + + def on_stop_fail(result): + self._component_state = 'Started' + self._component_stopping_deferred = None + log.error(result) + return result + + if self._component_state != 'Stopped' and self._component_state != 'Stopping': + self._component_state = 'Stopping' + d = maybeDeferred(self.stop) + d.addCallback(on_stop) + d.addErrback(on_stop_fail) + self._component_stopping_deferred = d + + if self._component_state == 'Stopping': + return self._component_stopping_deferred + + return succeed(None) + + def _component_pause(self): + def on_pause(result): + self._component_state = 'Paused' + if self._component_timer and self._component_timer.running: + self._component_timer.stop() + + if self._component_state == 'Started': + d = maybeDeferred(self.pause) + d.addCallback(on_pause) + elif self._component_state == 'Paused': + d = succeed(None) + else: + d = fail( + ComponentException( + 'Trying to pause component "%s" but it is ' + 'not in a started state. Current state: %s' + % (self._component_name, self._component_state), + traceback.format_stack(limit=4), + ) + ) + return d + + def _component_resume(self): + def on_resume(result): + self._component_state = 'Started' + self._component_start_timer() + + if self._component_state == 'Paused': + d = maybeDeferred(self.resume) + d.addCallback(on_resume) + else: + d = fail( + ComponentException( + 'Trying to resume component "%s" but it is ' + 'not in a paused state. Current state: %s' + % (self._component_name, self._component_state), + traceback.format_stack(limit=4), + ) + ) + return d + + def _component_shutdown(self): + def on_stop(result): + return maybeDeferred(self.shutdown) + + d = self._component_stop() + d.addCallback(on_stop) + return d + + def get_state(self): + return self._component_state + + def start(self): + pass + + def stop(self): + pass + + def update(self): + pass + + def shutdown(self): + pass + + def pause(self): + pass + + def resume(self): + pass + + +class ComponentRegistry: + """The ComponentRegistry holds a list of currently registered :class:`Component` objects. + + It is used to manage the Components by starting, stopping, pausing and shutting them down. + """ + + def __init__(self): + self.components = {} + # Stores all of the components that are dependent on a particular component + self.dependents = defaultdict(list) + + def register(self, obj): + """Register a component object with the registry. + + Note: + This is done automatically when a Component object is instantiated. + + Args: + obj (Component): A component object to register. + + Raises: + ComponentAlreadyRegistered: If a component with the same name is already registered. + + """ + name = obj._component_name + if name in self.components: + raise ComponentAlreadyRegistered( + 'Component already registered with name %s' % name + ) + + self.components[obj._component_name] = obj + if obj._component_depend: + for depend in obj._component_depend: + self.dependents[depend].append(name) + + def deregister(self, obj): + """Deregister a component from the registry. A stop will be + issued to the component prior to deregistering it. + + Args: + obj (Component): a component object to deregister + + Returns: + Deferred: a deferred object that will fire once the Component has been + successfully deregistered + + """ + 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): + # 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) + + def start(self, names=None): + """Start Components, and their dependencies, that are currently in a Stopped state. + + Note: + If no names are specified then all registered components will be started. + + Args: + names (list): A list of Components to start and their dependencies. + + Returns: + Deferred: Fired once all Components have been successfully started. + + """ + # Start all the components if names is empty + if not names: + names = list(self.components) + elif isinstance(names, str): + names = [names] + + def on_depends_started(result, name): + return self.components[name]._component_start() + + deferreds = [] + + for name in names: + if self.components[name]._component_depend: + # This component has depends, so we need to start them first. + d = self.start(self.components[name]._component_depend) + d.addCallback(on_depends_started, name) + deferreds.append(d) + else: + deferreds.append(self.components[name]._component_start()) + + return DeferredList(deferreds) + + def stop(self, names=None): + """Stop Components that are currently not in a Stopped state. + + Note: + If no names are specified then all registered components will be stopped. + + Args: + names (list): A list of Components to stop. + + Returns: + Deferred: Fired once all Components have been successfully stopped. + + """ + if not names: + names = list(self.components) + elif isinstance(names, str): + names = [names] + + def on_dependents_stopped(result, name): + return self.components[name]._component_stop() + + stopped_in_deferred = set() + deferreds = [] + + for name in names: + if name in stopped_in_deferred: + continue + if name in self.components: + if name in self.dependents: + # If other components depend on this component, stop them first + d = self.stop(self.dependents[name]).addCallback( + on_dependents_stopped, name + ) + deferreds.append(d) + stopped_in_deferred.update(self.dependents[name]) + else: + deferreds.append(self.components[name]._component_stop()) + + return DeferredList(deferreds) + + def pause(self, names=None): + """Pause Components that are currently in a Started state. + + Note: + If no names are specified then all registered components will be paused. + + Args: + names (list): A list of Components to pause. + + Returns: + Deferred: Fired once all Components have been successfully paused. + + """ + if not names: + names = list(self.components) + elif isinstance(names, str): + names = [names] + + deferreds = [] + + for name in names: + if self.components[name]._component_state == 'Started': + deferreds.append(self.components[name]._component_pause()) + + return DeferredList(deferreds) + + def resume(self, names=None): + """Resume Components that are currently in a Paused state. + + Note: + If no names are specified then all registered components will be resumed. + + Args: + names (list): A list of Components to to resume. + + Returns: + Deferred: Fired once all Components have been successfully resumed. + + """ + if not names: + names = list(self.components) + elif isinstance(names, str): + names = [names] + + deferreds = [] + + for name in names: + if self.components[name]._component_state == 'Paused': + deferreds.append(self.components[name]._component_resume()) + + return DeferredList(deferreds) + + def shutdown(self): + """Shutdown all Components regardless of state. + + This will call stop() on all the components prior to shutting down. This should be called + when the program is exiting to ensure all Components have a chance to properly shutdown. + + Returns: + Deferred: Fired once all Components have been successfully shut down. + + """ + + def on_stopped(result): + return DeferredList( + [comp._component_shutdown() for comp in list(self.components.values())] + ) + + return self.stop(list(self.components)).addCallback(on_stopped) + + def update(self): + """Update all Components that are in a Started state.""" + for component in self.components.items(): + try: + component.update() + except BaseException as ex: + log.exception(ex) + + +_ComponentRegistry = ComponentRegistry() + +deregister = _ComponentRegistry.deregister +start = _ComponentRegistry.start +stop = _ComponentRegistry.stop +pause = _ComponentRegistry.pause +resume = _ComponentRegistry.resume +update = _ComponentRegistry.update +shutdown = _ComponentRegistry.shutdown + + +def get(name): + """Return a reference to a component. + + Args: + name (str): The Component name to get. + + Returns: + Component: The Component object. + + Raises: + KeyError: If the Component does not exist. + + """ + return _ComponentRegistry.components[name] |