events.py 5.1 KB

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