1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 __maintainer__ = 'Philippe Normand <philippe@fluendo.com>'
17 __maintainer2__ = 'Florian Boucault <florian@fluendo.com>'
18
19
20 import threading
21
22 from elisa.core import log
23 from elisa.core.bus import bus_message
24 from elisa.core.utils import threadsafe_list
25 from twisted.internet import reactor, threads, defer
26
27 -class Bus(log.Loggable):
28 """
29 Python objects can register callbacks with the bus and be called when
30 L{elisa.core.bus.bus_message.Message}s are sent by other objects.
31
32 Here is a simple example::
33
34 bus = Bus()
35
36 def my_cb(message, sender):
37 print 'Got message %r from %r' % (message, sender)
38
39 bus.register(my_cb)
40
41 bus.send_message(Message())
42
43
44 Messages dispatching empties the message queue and call the registered
45 callbacks. Messages filtering is also supported, just pass a Message
46 type class to bus.register(), like this::
47
48 bus.register(my_cb, Message)
49
50 You can filter on multiple Message types by supplying a list to
51 bus.register()::
52
53 bus.register(my_cb, (FooMessage, DataMessage))
54
55 @ivar callbacks: registered callbacks
56 @type callbacks: dict, keys are callable objects and values are
57 Message types lists
58 """
59
61 """Initialize the Message queue and the callbacks dictionary.
62 """
63 log.Loggable.__init__(self)
64 self.debug("Creating")
65
66 self._queue = threadsafe_list.ThreadsafeList()
67
68 self._running = False
69
70
71 self._callbacks = {}
72
73
74 self._callbacks_lock = threading.Lock()
75
77
78 while self._queue:
79 message, sender = self._queue.pop()
80 reactor.callFromThread(self._dispatch, message, sender)
81
83 """
84 Start message dispatching: once started, messages sent over the bus
85 are guaranteed to be dispatched.
86
87 Queued messages are dispatched inside a separate thread.
88 """
89 self.info("Starting")
90 self._running = True
91 self._dispatch_queued_msgs()
92
94 """
95 Stop message dispatching: messages sent over the bus will not be
96 dispatched automatically anymore, they will be locally queued.
97 """
98 self.info("Stopping")
99 self._running = False
100
102 """Send a message over the bus. The message is automatically
103 dispatched (inside a thread) if the L{Bus} is
104 running. Otherwise the message is locally queued until the
105 L{Bus} starts.
106
107 MT safe.
108
109 @param message: the message to send
110 @type message: L{elisa.core.bus.bus_message.Message}
111 @param sender: the sender object. None by default. Will be passed to
112 receiver callbacks.
113 @type sender: object
114 """
115 assert isinstance(message, bus_message.Message), message
116 self.debug("Sending message %r", message)
117
118 if self._running:
119 reactor.callFromThread(self._dispatch, message, sender)
120 else:
121 self._queue.append((message, sender))
122
123 - def register(self, callback, *message_types):
124 """
125 Register a new callback with the bus. The given callback will be
126 called when a message of one of the given types is dispatched on the
127 bus.
128
129 MT safe.
130
131 @param callback: the callback to register
132 @type callback: callable
133 @param message_types: Message types to filter on
134 @type message_types: type or list of types
135 """
136 if not message_types:
137 message_types = (bus_message.Message,)
138
139 self.debug("Registering callback %r for %r message types",
140 callback, message_types)
141
142 try:
143 self._callbacks_lock.acquire()
144 self._callbacks[callback] = message_types
145 finally:
146 self._callbacks_lock.release()
147
149 """Unregister a callback from the bus.
150
151 MT safe.
152
153 @param callback: the callback to register
154 @type callback: callable
155 """
156 try:
157 self._callbacks_lock.acquire()
158 if callback in self._callbacks:
159 del self._callbacks[callback]
160 finally:
161 self._callbacks_lock.release()
162
164 """Dispatch messages to registered callbacks and empty the
165 message queue.
166 """
167 dispatched = False
168 try:
169 self._callbacks_lock.acquire()
170 for callback, mfilter in self._callbacks.iteritems():
171 if isinstance(message, mfilter):
172 try:
173 cb_name = "%s.%s" % (callback.im_class.__name__,
174 callback.__name__)
175 except AttributeError:
176 cb_name = callback.__name__
177 self.debug("Dispatching message %r to %r", message, cb_name)
178 callback(message, sender)
179 dispatched = True
180 finally:
181 self._callbacks_lock.release()
182
183 if not dispatched:
184 self.debug("Undispatched message: %r", message)
185 return dispatched
186
188 """ Utility decorator to simply register a function or method on
189 the message bus.
190 """
191 def decorator(func):
192 bus.register(func, *message_types)
193
194 return decorator
195