events.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #
  2. # The Qubes OS Project, https://www.qubes-os.org/
  3. #
  4. # Copyright (C) 2014-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
  5. # Copyright (C) 2014-2015 Wojtek Porczyk <woju@invisiblethingslab.com>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License along
  18. # with this program; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. #
  21. '''Qubes events.
  22. Events are fired when something happens, like VM start or stop, property change
  23. etc.
  24. '''
  25. import collections
  26. import itertools
  27. def handler(*events):
  28. '''Event handler decorator factory.
  29. To hook an event, decorate a method in your plugin class with this
  30. decorator.
  31. It probably makes no sense to specify more than one handler for specific
  32. event in one class, because handlers are not run concurrently and there is
  33. no guarantee of the order of execution.
  34. .. note::
  35. For hooking events from extensions, see :py:func:`qubes.ext.handler`.
  36. :param str event: event type
  37. '''
  38. def decorator(func):
  39. # pylint: disable=missing-docstring
  40. func.ha_events = events
  41. # mark class own handler (i.e. not from extension)
  42. func.ha_bound = True
  43. return func
  44. return decorator
  45. def ishandler(obj):
  46. '''Test if a method is hooked to an event.
  47. :param object o: suspected hook
  48. :return: :py:obj:`True` when function is a hook, :py:obj:`False` otherwise
  49. :rtype: bool
  50. '''
  51. return callable(obj) \
  52. and hasattr(obj, 'ha_events')
  53. class EmitterMeta(type):
  54. '''Metaclass for :py:class:`Emitter`'''
  55. def __init__(cls, name, bases, dict_):
  56. super(EmitterMeta, cls).__init__(name, bases, dict_)
  57. cls.__handlers__ = collections.defaultdict(set)
  58. try:
  59. propnames = set(prop.__name__ for prop in cls.property_list())
  60. except AttributeError:
  61. propnames = set()
  62. for attr in dict_:
  63. if attr in propnames:
  64. # we have to be careful, not to getattr() on properties which
  65. # may be unset
  66. continue
  67. attr = dict_[attr]
  68. if not ishandler(attr):
  69. continue
  70. for event in attr.ha_events:
  71. cls.__handlers__[event].add(attr)
  72. class Emitter(object, metaclass=EmitterMeta):
  73. '''Subject that can emit events.
  74. By default all events are disabled not to interfere with loading from XML.
  75. To enable event dispatch, set :py:attr:`events_enabled` to :py:obj:`True`.
  76. '''
  77. def __init__(self, *args, **kwargs):
  78. super(Emitter, self).__init__(*args, **kwargs)
  79. if not hasattr(self, 'events_enabled'):
  80. self.events_enabled = False
  81. self.__handlers__ = collections.defaultdict(set)
  82. def add_handler(self, event, func):
  83. '''Add event handler to subject's class.
  84. This is class method, it is invalid to call it on object instance.
  85. :param str event: event identificator
  86. :param collections.Callable handler: handler callable
  87. '''
  88. # pylint: disable=no-member
  89. self.__handlers__[event].add(func)
  90. def remove_handler(self, event, func):
  91. '''Remove event handler from subject's class.
  92. This is class method, it is invalid to call it on object instance.
  93. This method must be called on the same class as
  94. :py:meth:`add_handler` was called to register the handler.
  95. :param str event: event identificator
  96. :param collections.Callable handler: handler callable
  97. '''
  98. # pylint: disable=no-member
  99. self.__handlers__[event].remove(func)
  100. def _fire_event_in_order(self, order, event, kwargs):
  101. '''Fire event for classes in given order.
  102. Do not use this method. Use :py:meth:`fire_event` or
  103. :py:meth:`fire_event_pre`.
  104. '''
  105. if not self.events_enabled:
  106. return []
  107. effects = []
  108. for i in order:
  109. try:
  110. handlers_dict = i.__handlers__
  111. except AttributeError:
  112. continue
  113. handlers = handlers_dict.get(event, set())
  114. if '*' in handlers_dict:
  115. handlers = handlers_dict['*'] | handlers
  116. for func in sorted(handlers,
  117. key=(lambda handler: hasattr(handler, 'ha_bound')),
  118. reverse=True):
  119. effect = func(self, event, **kwargs)
  120. if effect is not None:
  121. effects.extend(effect)
  122. return effects
  123. def fire_event(self, event, **kwargs):
  124. '''Call all handlers for an event.
  125. Handlers are called for class and all parent classess, in **reversed**
  126. method resolution order. For each class first are called bound handlers
  127. (specified in class definition), then handlers from extensions. Aside
  128. from above, remaining order is undefined.
  129. .. seealso::
  130. :py:meth:`fire_event_pre`
  131. :param str event: event identificator
  132. :returns: list of effects
  133. All *kwargs* are passed verbatim. They are different for different
  134. events.
  135. '''
  136. return self._fire_event_in_order(
  137. itertools.chain(reversed(self.__class__.__mro__), (self,)),
  138. event, kwargs)
  139. def fire_event_pre(self, event, **kwargs):
  140. '''Call all handlers for an event.
  141. Handlers are called for class and all parent classess, in **true**
  142. method resolution order. This is intended for ``-pre-`` events, where
  143. order of invocation should be reversed.
  144. .. seealso::
  145. :py:meth:`fire_event`
  146. :param str event: event identificator
  147. :returns: list of effects
  148. All *kwargs* are passed verbatim. They are different for different
  149. events.
  150. '''
  151. return self._fire_event_in_order(
  152. itertools.chain((self,), self.__class__.__mro__),
  153. event, kwargs)