1# -*- coding: utf-8 -*- 2# 3# Copyright (C) 2007-2010 Andrew Resch <andrewresch@gmail.com> 4# 5# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with 6# the additional special exception to link portions of this program with the OpenSSL library. 7# See LICENSE for more details. 8# 9 10from __future__ import unicode_literals 11 12import logging 13import traceback 14from collections import defaultdict 15 16from six import string_types 17from twisted.internet import reactor 18from twisted.internet.defer import DeferredList, fail, maybeDeferred, succeed 19from twisted.internet.task import LoopingCall, deferLater 20 21log = logging.getLogger(__name__) 22 23 24class ComponentAlreadyRegistered(Exception): 25 pass 26 27 28class ComponentException(Exception): 29 def __init__(self, message, tb): 30 super(ComponentException, self).__init__(message) 31 self.message = message 32 self.tb = tb 33 34 def __str__(self): 35 s = super(ComponentException, self).__str__() 36 return '%s\n%s' % (s, ''.join(self.tb)) 37 38 def __eq__(self, other): 39 if isinstance(other, self.__class__): 40 return self.message == other.message 41 else: 42 return False 43 44 def __ne__(self, other): 45 return not self.__eq__(other) 46 47 48class Component(object): 49 """Component objects are singletons managed by the :class:`ComponentRegistry`. 50 51 When a new Component object is instantiated, it will be automatically 52 registered with the :class:`ComponentRegistry`. 53 54 The ComponentRegistry has the ability to start, stop, pause and shutdown the 55 components registered with it. 56 57 **Events:** 58 59 **start()** - This method is called when the client has connected to a 60 Deluge core. 61 62 **stop()** - This method is called when the client has disconnected from a 63 Deluge core. 64 65 **update()** - This method is called every 1 second by default while the 66 Componented is in a *Started* state. The interval can be 67 specified during instantiation. The update() timer can be 68 paused by instructing the :class:`ComponentRegistry` to pause 69 this Component. 70 71 **shutdown()** - This method is called when the client is exiting. If the 72 Component is in a "Started" state when this is called, a 73 call to stop() will be issued prior to shutdown(). 74 75 **States:** 76 77 A Component can be in one of these 5 states. 78 79 **Started** - The Component has been started by the :class:`ComponentRegistry` 80 and will have it's update timer started. 81 82 **Starting** - The Component has had it's start method called, but it hasn't 83 fully started yet. 84 85 **Stopped** - The Component has either been stopped or has yet to be started. 86 87 **Stopping** - The Component has had it's stop method called, but it hasn't 88 fully stopped yet. 89 90 **Paused** - The Component has had it's update timer stopped, but will 91 still be considered in a Started state. 92 93 """ 94 95 def __init__(self, name, interval=1, depend=None): 96 """Initialize component. 97 98 Args: 99 name (str): Name of component. 100 interval (int, optional): The interval in seconds to call the update function. 101 depend (list, optional): The names of components this component depends on. 102 103 """ 104 self._component_name = name 105 self._component_interval = interval 106 self._component_depend = depend 107 self._component_state = 'Stopped' 108 self._component_timer = None 109 self._component_starting_deferred = None 110 self._component_stopping_deferred = None 111 _ComponentRegistry.register(self) 112 113 def __del__(self): 114 if _ComponentRegistry: 115 _ComponentRegistry.deregister(self) 116 117 def _component_start_timer(self): 118 if hasattr(self, 'update'): 119 self._component_timer = LoopingCall(self.update) 120 self._component_timer.start(self._component_interval) 121 122 def _component_start(self): 123 def on_start(result): 124 self._component_state = 'Started' 125 self._component_starting_deferred = None 126 self._component_start_timer() 127 return True 128 129 def on_start_fail(result): 130 self._component_state = 'Stopped' 131 self._component_starting_deferred = None 132 log.error(result) 133 return fail(result) 134 135 if self._component_state == 'Stopped': 136 if hasattr(self, 'start'): 137 self._component_state = 'Starting' 138 d = deferLater(reactor, 0, self.start) 139 d.addCallbacks(on_start, on_start_fail) 140 self._component_starting_deferred = d 141 else: 142 d = maybeDeferred(on_start, None) 143 elif self._component_state == 'Starting': 144 return self._component_starting_deferred 145 elif self._component_state == 'Started': 146 d = succeed(True) 147 else: 148 d = fail( 149 ComponentException( 150 'Trying to start component "%s" but it is ' 151 'not in a stopped state. Current state: %s' 152 % (self._component_name, self._component_state), 153 traceback.format_stack(limit=4), 154 ) 155 ) 156 return d 157 158 def _component_stop(self): 159 def on_stop(result): 160 self._component_state = 'Stopped' 161 if self._component_timer and self._component_timer.running: 162 self._component_timer.stop() 163 return True 164 165 def on_stop_fail(result): 166 self._component_state = 'Started' 167 self._component_stopping_deferred = None 168 log.error(result) 169 return result 170 171 if self._component_state != 'Stopped' and self._component_state != 'Stopping': 172 if hasattr(self, 'stop'): 173 self._component_state = 'Stopping' 174 d = maybeDeferred(self.stop) 175 d.addCallback(on_stop) 176 d.addErrback(on_stop_fail) 177 self._component_stopping_deferred = d 178 else: 179 d = maybeDeferred(on_stop, None) 180 181 if self._component_state == 'Stopping': 182 return self._component_stopping_deferred 183 184 return succeed(None) 185 186 def _component_pause(self): 187 def on_pause(result): 188 self._component_state = 'Paused' 189 190 if self._component_state == 'Started': 191 if self._component_timer and self._component_timer.running: 192 d = maybeDeferred(self._component_timer.stop) 193 d.addCallback(on_pause) 194 else: 195 d = succeed(None) 196 elif self._component_state == 'Paused': 197 d = succeed(None) 198 else: 199 d = fail( 200 ComponentException( 201 'Trying to pause component "%s" but it is ' 202 'not in a started state. Current state: %s' 203 % (self._component_name, self._component_state), 204 traceback.format_stack(limit=4), 205 ) 206 ) 207 return d 208 209 def _component_resume(self): 210 def on_resume(result): 211 self._component_state = 'Started' 212 213 if self._component_state == 'Paused': 214 d = maybeDeferred(self._component_start_timer) 215 d.addCallback(on_resume) 216 else: 217 d = fail( 218 ComponentException( 219 'Trying to resume component "%s" but it is ' 220 'not in a paused state. Current state: %s' 221 % (self._component_name, self._component_state), 222 traceback.format_stack(limit=4), 223 ) 224 ) 225 return d 226 227 def _component_shutdown(self): 228 def on_stop(result): 229 if hasattr(self, 'shutdown'): 230 return maybeDeferred(self.shutdown) 231 return succeed(None) 232 233 d = self._component_stop() 234 d.addCallback(on_stop) 235 return d 236 237 def get_state(self): 238 return self._component_state 239 240 def start(self): 241 pass 242 243 def stop(self): 244 pass 245 246 def update(self): 247 pass 248 249 def shutdown(self): 250 pass 251 252 253class ComponentRegistry(object): 254 """The ComponentRegistry holds a list of currently registered :class:`Component` objects. 255 256 It is used to manage the Components by starting, stopping, pausing and shutting them down. 257 """ 258 259 def __init__(self): 260 self.components = {} 261 # Stores all of the components that are dependent on a particular component 262 self.dependents = defaultdict(list) 263 264 def register(self, obj): 265 """Register a component object with the registry. 266 267 Note: 268 This is done automatically when a Component object is instantiated. 269 270 Args: 271 obj (Component): A component object to register. 272 273 Raises: 274 ComponentAlreadyRegistered: If a component with the same name is already registered. 275 276 """ 277 name = obj._component_name 278 if name in self.components: 279 raise ComponentAlreadyRegistered( 280 'Component already registered with name %s' % name 281 ) 282 283 self.components[obj._component_name] = obj 284 if obj._component_depend: 285 for depend in obj._component_depend: 286 self.dependents[depend].append(name) 287 288 def deregister(self, obj): 289 """Deregister a component from the registry. A stop will be 290 issued to the component prior to deregistering it. 291 292 Args: 293 obj (Component): a component object to deregister 294 295 Returns: 296 Deferred: a deferred object that will fire once the Component has been sucessfully deregistered 297 298 """ 299 if obj in self.components.values(): 300 log.debug('Deregistering Component: %s', obj._component_name) 301 d = self.stop([obj._component_name]) 302 303 def on_stop(result, name): 304 # Component may have been removed, so pop to ensure it doesn't fail 305 self.components.pop(name, None) 306 307 return d.addCallback(on_stop, obj._component_name) 308 else: 309 return succeed(None) 310 311 def start(self, names=None): 312 """Start Components, and their dependencies, that are currently in a Stopped state. 313 314 Note: 315 If no names are specified then all registered components will be started. 316 317 Args: 318 names (list): A list of Components to start and their dependencies. 319 320 Returns: 321 Deferred: Fired once all Components have been successfully started. 322 323 """ 324 # Start all the components if names is empty 325 if not names: 326 names = list(self.components) 327 elif isinstance(names, string_types): 328 names = [names] 329 330 def on_depends_started(result, name): 331 return self.components[name]._component_start() 332 333 deferreds = [] 334 335 for name in names: 336 if self.components[name]._component_depend: 337 # This component has depends, so we need to start them first. 338 d = self.start(self.components[name]._component_depend) 339 d.addCallback(on_depends_started, name) 340 deferreds.append(d) 341 else: 342 deferreds.append(self.components[name]._component_start()) 343 344 return DeferredList(deferreds) 345 346 def stop(self, names=None): 347 """Stop Components that are currently not in a Stopped state. 348 349 Note: 350 If no names are specified then all registered components will be stopped. 351 352 Args: 353 names (list): A list of Components to stop. 354 355 Returns: 356 Deferred: Fired once all Components have been successfully stopped. 357 358 """ 359 if not names: 360 names = list(self.components) 361 elif isinstance(names, string_types): 362 names = [names] 363 364 def on_dependents_stopped(result, name): 365 return self.components[name]._component_stop() 366 367 stopped_in_deferred = set() 368 deferreds = [] 369 370 for name in names: 371 if name in stopped_in_deferred: 372 continue 373 if name in self.components: 374 if name in self.dependents: 375 # If other components depend on this component, stop them first 376 d = self.stop(self.dependents[name]).addCallback( 377 on_dependents_stopped, name 378 ) 379 deferreds.append(d) 380 stopped_in_deferred.update(self.dependents[name]) 381 else: 382 deferreds.append(self.components[name]._component_stop()) 383 384 return DeferredList(deferreds) 385 386 def pause(self, names=None): 387 """Pause Components that are currently in a Started state. 388 389 Note: 390 If no names are specified then all registered components will be paused. 391 392 Args: 393 names (list): A list of Components to pause. 394 395 Returns: 396 Deferred: Fired once all Components have been successfully paused. 397 398 """ 399 if not names: 400 names = list(self.components) 401 elif isinstance(names, string_types): 402 names = [names] 403 404 deferreds = [] 405 406 for name in names: 407 if self.components[name]._component_state == 'Started': 408 deferreds.append(self.components[name]._component_pause()) 409 410 return DeferredList(deferreds) 411 412 def resume(self, names=None): 413 """Resume Components that are currently in a Paused state. 414 415 Note: 416 If no names are specified then all registered components will be resumed. 417 418 Args: 419 names (list): A list of Components to to resume. 420 421 Returns: 422 Deferred: Fired once all Components have been successfully resumed. 423 424 """ 425 if not names: 426 names = list(self.components) 427 elif isinstance(names, string_types): 428 names = [names] 429 430 deferreds = [] 431 432 for name in names: 433 if self.components[name]._component_state == 'Paused': 434 deferreds.append(self.components[name]._component_resume()) 435 436 return DeferredList(deferreds) 437 438 def shutdown(self): 439 """Shutdown all Components regardless of state. 440 441 This will call stop() on all the components prior to shutting down. This should be called 442 when the program is exiting to ensure all Components have a chance to properly shutdown. 443 444 Returns: 445 Deferred: Fired once all Components have been successfully shut down. 446 447 """ 448 449 def on_stopped(result): 450 return DeferredList( 451 [comp._component_shutdown() for comp in self.components.values()] 452 ) 453 454 return self.stop(list(self.components)).addCallback(on_stopped) 455 456 def update(self): 457 """Update all Components that are in a Started state.""" 458 for component in self.components.items(): 459 try: 460 component.update() 461 except BaseException as ex: 462 log.exception(ex) 463 464 465_ComponentRegistry = ComponentRegistry() 466 467deregister = _ComponentRegistry.deregister 468start = _ComponentRegistry.start 469stop = _ComponentRegistry.stop 470pause = _ComponentRegistry.pause 471resume = _ComponentRegistry.resume 472update = _ComponentRegistry.update 473shutdown = _ComponentRegistry.shutdown 474 475 476def get(name): 477 """Return a reference to a component. 478 479 Args: 480 name (str): The Component name to get. 481 482 Returns: 483 Component: The Component object. 484 485 Raises: 486 KeyError: If the Component does not exist. 487 488 """ 489 return _ComponentRegistry.components[name] 490