events.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. #!/usr/bin/python2 -O
  2. '''Qubes events.
  3. Events are fired when something happens, like VM start or stop, property change
  4. etc.
  5. '''
  6. import collections
  7. def handler(event):
  8. '''Event handler decorator factory.
  9. To hook an event, decorate a method in your plugin class with this
  10. decorator.
  11. It probably makes no sense to specify more than one handler for specific
  12. event in one class, because handlers are not run concurrently and there is
  13. no guarantee of the order of execution.
  14. .. note::
  15. For hooking events from extensions, see :py:func:`qubes.ext.handler`.
  16. :param str event: event type
  17. '''
  18. def decorator(f):
  19. f.ha_event = event
  20. f.ha_bound = True
  21. return f
  22. return decorator
  23. def ishandler(o):
  24. '''Test if a method is hooked to an event.
  25. :param object o: suspected hook
  26. :return: :py:obj:`True` when function is a hook, :py:obj:`False` otherwise
  27. :rtype: bool
  28. '''
  29. return callable(o) \
  30. and hasattr(o, 'ha_event')
  31. class EmitterMeta(type):
  32. '''Metaclass for :py:class:`Emitter`'''
  33. def __init__(cls, name, bases, dict_):
  34. super(type, cls).__init__(name, bases, dict_)
  35. cls.__handlers__ = collections.defaultdict(set)
  36. class Emitter(object):
  37. '''Subject that can emit events
  38. '''
  39. __metaclass__ = EmitterMeta
  40. def __init__(self, *args, **kwargs):
  41. super(Emitter, self).__init__(*args, **kwargs)
  42. self.events_enabled = True
  43. try:
  44. propnames = set(prop.__name__ for prop in self.get_props_list())
  45. except AttributeError:
  46. propnames = set()
  47. for attr in dir(self):
  48. if attr in propnames:
  49. # we have to be careful, not to getattr() on properties which
  50. # may be unset
  51. continue
  52. attr = getattr(self, attr)
  53. if not ishandler(attr):
  54. continue
  55. self.add_handler(attr.ha_event, attr)
  56. @classmethod
  57. def add_handler(cls, event, handler):
  58. '''Add event handler to subject's class
  59. :param str event: event identificator
  60. :param collections.Callable handler: handler callable
  61. '''
  62. cls.__handlers__[event].add(handler)
  63. def _fire_event_in_order(self, order, event, *args, **kwargs):
  64. '''Fire event for classes in given order.
  65. Do not use this method. Use :py:meth:`fire_event` or
  66. :py:meth:`fire_event_pre`.
  67. '''
  68. if not self.events_enabled:
  69. return
  70. for cls in order:
  71. # first fire bound (= our own) handlers, then handlers from extensions
  72. if not hasattr(cls, '__handlers__'):
  73. continue
  74. for handler in sorted(cls.__handlers__[event],
  75. key=(lambda handler: hasattr(handler, 'ha_bound')), reverse=True):
  76. if hasattr(handler, 'ha_bound'):
  77. # this is our (bound) method, self is implicit
  78. handler(event, *args, **kwargs)
  79. else:
  80. # this is from extension or hand-added, so we see method as
  81. # unbound, therefore we need to pass self
  82. handler(self, event, *args, **kwargs)
  83. def fire_event(self, event, *args, **kwargs):
  84. '''Call all handlers for an event.
  85. Handlers are called for class and all parent classess, in **reversed**
  86. method resolution order. For each class first are called bound handlers
  87. (specified in class definition), then handlers from extensions. Aside
  88. from above, remaining order is undefined.
  89. .. seealso::
  90. :py:meth:`fire_event_pre`
  91. :param str event: event identificator
  92. All *args* and *kwargs* are passed verbatim. They are different for
  93. different events.
  94. '''
  95. self._fire_event_in_order(reversed(self.__class__.__mro__), event, *args, **kwargs)
  96. def fire_event_pre(self, event, *args, **kwargs):
  97. '''Call all handlers for an event.
  98. Handlers are called for class and all parent classess, in **true**
  99. method resolution order. This is intended for ``-pre-`` events, where
  100. order of invocation should be reversed.
  101. .. seealso::
  102. :py:meth:`fire_event`
  103. :param str event: event identificator
  104. All *args* and *kwargs* are passed verbatim. They are different for
  105. different events.
  106. '''
  107. self._fire_event_in_order(self.__class__.__mro__, event, *args, **kwargs)