# # component.py # # Copyright (C) 2007-2010 Andrew Resch # # Deluge is free software. # # You may redistribute it and/or modify it under the terms of the # GNU General Public License, as published by the Free Software # Foundation; either version 3 of the License, or (at your option) # any later version. # # deluge is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with deluge. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor # Boston, MA 02110-1301, USA. # # In addition, as a special exception, the copyright holders give # permission to link the code of portions of this program with the OpenSSL # library. # You must obey the GNU General Public License in all respects for all of # the code used other than OpenSSL. If you modify file(s) with this # exception, you may extend this exception to your version of the file(s), # but you are not obligated to do so. If you do not wish to do so, delete # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. # # import logging from collections import defaultdict from twisted.internet.defer import maybeDeferred, succeed, DeferredList, fail from twisted.internet.task import LoopingCall log = logging.getLogger(__name__) class ComponentAlreadyRegistered(Exception): pass class Component(object): """ 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 Componented 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. **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 it's stop method called, but it hasn't fully stopped yet. **Paused** - The Component has had it's update timer stopped, but will still be considered in a Started state. """ def __init__(self, name, interval=1, depend=None): 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): if hasattr(self, "update"): 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 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) self._component_starting_deferred = d else: d = maybeDeferred(on_start, None) elif self._component_state == "Starting": return self._component_starting_deferred elif self._component_state == "Started": d = succeed(True) else: d = fail("Cannot start a component not in a Stopped state!") 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": if hasattr(self, "stop"): self._component_state = "Stopping" d = maybeDeferred(self.stop) d.addCallback(on_stop) d.addErrback(on_stop_fail) self._component_stopping_deferred = d else: d = maybeDeferred(on_stop, None) 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_state == "Started": if self._component_timer and self._component_timer.running: d = maybeDeferred(self._component_timer.stop) d.addCallback(on_pause) else: d = succeed(None) elif self._component_state == "Paused": d = succeed(None) else: d = fail("Cannot pause a component in a non-Started state!") return d def _component_resume(self): def on_resume(result): self._component_state = "Started" if self._component_state == "Paused": d = maybeDeferred(self._component_start_timer) d.addCallback(on_resume) else: d = fail("Component cannot be resumed from a non-Paused state!") return d def _component_shutdown(self): def on_stop(result): if hasattr(self, "shutdown"): return maybeDeferred(self.shutdown) return succeed(None) d = self._component_stop() d.addCallback(on_stop) return d def start(self): pass def stop(self): pass def update(self): pass def shutdown(self): pass class ComponentRegistry(object): """ 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): """ Registers a component object with the registry. This is done automatically when a Component object is instantiated. :param obj: the Component object :type obj: object :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): """ Deregisters a component from the registry. A stop will be issued to the component prior to deregistering it. :param obj: the Component 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] return d.addCallback(on_stop, obj._component_name) else: return succeed(None) def start(self, names=[]): """ Starts Components that are currently in a Stopped state and their dependencies. If *names* is specified, will only start those Components and their dependencies and if not it will start all registered components. :param names: a list of Components to start :type names: list :returns: a Deferred object that will fire once all Components have been sucessfully started :rtype: twisted.internet.defer.Deferred """ # Start all the components if names is empty if not names: names = self.components.keys() 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=[]): """ Stops Components that are currently not in a Stopped state. If *names* is specified, then it will only stop those Components, and if not it will stop all the registered Components. :param names: a list of Components to start :type names: list :returns: a Deferred object that will fire once all Components have been sucessfully stopped :rtype: twisted.internet.defer.Deferred """ if not names: names = self.components.keys() 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=[]): """ Pauses Components that are currently in a Started state. If *names* is specified, then it will only pause those Components, and if not it will pause all the registered Components. :param names: a list of Components to pause :type names: list :returns: a Deferred object that will fire once all Components have been sucessfully paused :rtype: twisted.internet.defer.Deferred """ if not names: names = self.components.keys() 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=[]): """ Resumes Components that are currently in a Paused state. If *names* is specified, then it will only resume those Components, and if not it will resume all the registered Components. :param names: a list of Components to resume :type names: list :returns: a Deferred object that will fire once all Components have been successfully resumed :rtype: twisted.internet.defer.Deferred """ if not names: names = self.components.keys() 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): """ Shutdowns all Components regardless of state. This will call :meth:`stop` on call 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: a Deferred object that will fire once all Components have been successfully shut down :rtype: twisted.internet.defer.Deferred """ def on_stopped(result): return DeferredList(map(lambda c: c._component_shutdown(), self.components.values())) return self.stop(self.components.keys()).addCallback(on_stopped) def update(self): """ Updates all Components that are in a Started state. """ for component in self.components.items(): component.update() _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. :param name: the Component name to get :type name: string :returns: the Component object :rtype: object :raises KeyError: if the Component does not exist """ return _ComponentRegistry.components[name]