Rephrasing the call buttons
[theonering] / hand_tests / dbus_signals.py
1 #!/usr/bin/env python
2
3 import sys
4 sys.path.insert(0,"../src")
5 import logging
6 import pprint
7
8 import gobject
9 import dbus
10 import dbus.mainloop.glib
11 import telepathy
12
13 import util.go_utils as gobject_utils
14 import gtk_toolbox
15
16
17 _moduleLogger = logging.getLogger("receptionist")
18 DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties'
19
20
21 class AutoAcceptCall(object):
22
23         def __init__(self, bus, conn, chan, on_success, on_error):
24                 self._sessionBus = bus
25                 self._conn = conn
26                 self._chan = chan
27                 self._outstandingRequests = []
28                 self._on_success = on_success
29                 self._on_error = on_error
30
31                 self._initiatorHandle = None
32                 self._initiatorID = None
33                 self._targetHandle = None
34                 self._targetID = None
35                 self._requested = None
36                 self._pendingHandles = None
37
38                 self._chan[DBUS_PROPERTIES].GetAll(
39                         telepathy.interfaces.CHANNEL_INTERFACE,
40                         reply_handler = self._on_got_all,
41                         error_handler = self._custom_error(self._on_got_all),
42                 )
43                 self._outstandingRequests.append(self._on_got_all)
44
45                 if False:
46                         # @bug Unsure why this isn't working
47                         self._chan[DBUS_PROPERTIES].Get(
48                                 telepathy.interfaces.CHANNEL_INTERFACE_GROUP,
49                                 'LocalPendingMembers',
50                                 reply_handler = self._on_got_pending_members,
51                                 error_handler = self._custom_error(self._on_got_pending_members),
52                         )
53                 else:
54                         self._chan[telepathy.interfaces.CHANNEL_INTERFACE_GROUP].GetLocalPendingMembersWithInfo(
55                                 reply_handler = self._on_got_pending_members,
56                                 error_handler = self._custom_error(self._on_got_pending_members),
57                         )
58                 self._outstandingRequests.append(self._on_got_pending_members)
59
60         def is_inbound(self):
61                 return not self._requested
62                 isInbound = self._targetHandle == self._initiatorHandle
63                 return isInbound
64
65         @property
66         def initiator(self):
67                 return self._initiatorID
68
69         @property
70         def target(self):
71                 return self._targetID
72
73         def accept_call(self, on_accepted, on_error):
74                 self._chan[telepathy.interfaces.CHANNEL_INTERFACE_GROUP].AddMembers(
75                         self._pendingHandles,
76                         "",
77                         reply_handler = self._custom_on_accept(on_accepted),
78                         error_handler = self._custom_on_accept_error(on_error),
79                 )
80
81         def _custom_on_accept(self, callback):
82
83                 def on_accept(*args):
84                         callback(self)
85
86                 return on_accept
87
88         def _custom_on_accept_error(self, callback):
89
90                 def on_error(*args):
91                         callback(self, *args)
92
93                 return on_error
94
95         def _custom_error(self, action):
96
97                 def _on_error(*args):
98                         _moduleLogger.error("Failed for %r (%r)" % (action, args))
99                         self._outstandingRequests.remove(action)
100                         if self._outstandingRequests:
101                                 return
102
103                         self._on_error(self)
104
105                 return _on_error
106
107         def _report_callback_done(self, action):
108                 _moduleLogger.debug("Succeded with %r" % (action, ))
109                 self._outstandingRequests.remove(action)
110                 if self._outstandingRequests:
111                         return
112
113                 assert None not in (
114                         self._initiatorHandle,
115                         self._initiatorID,
116                         self._targetHandle,
117                         self._targetID,
118                         self._pendingHandles,
119                         self._requested,
120                 )
121
122                 self._on_success(self)
123
124         @gtk_toolbox.log_exception(_moduleLogger)
125         def _on_got_all(self, properties):
126                 self._initiatorID = properties["InitiatorID"]
127                 self._initiatorHandle = properties["InitiatorHandle"]
128                 self._targetID = properties["InitiatorID"]
129                 self._targetHandle = properties["InitiatorHandle"]
130                 self._requested = properties["Requested"]
131
132                 self._report_callback_done(self._on_got_all)
133
134         @gtk_toolbox.log_exception(_moduleLogger)
135         def _on_got_pending_members(self, pendings):
136                 for pendingHandle, instigatorHandle, reason, message in pendings:
137                         print pendingHandle, instigatorHandle, reason, message
138
139                 self._pendingHandles = [pendingWithInfo[0] for pendingWithInfo in pendings]
140
141                 self._report_callback_done(self._on_got_pending_members)
142
143
144 class WasMissedCall(object):
145
146         def __init__(self, bus, conn, chan, on_success, on_error):
147                 self._on_success = on_success
148                 self._on_error = on_error
149
150                 self._requested = None
151                 self._didMembersChange = False
152                 self._didClose = False
153                 self._didReport = False
154
155                 self._timeoutId = gobject_utils.timeout_add_seconds(10, self._on_timeout)
156
157                 chan[telepathy.interfaces.CHANNEL_INTERFACE_GROUP].connect_to_signal(
158                         "MembersChanged",
159                         self._on_members_changed,
160                 )
161
162                 chan[telepathy.interfaces.CHANNEL].connect_to_signal(
163                         "Closed",
164                         self._on_closed,
165                 )
166
167                 chan[DBUS_PROPERTIES].GetAll(
168                         telepathy.interfaces.CHANNEL_INTERFACE,
169                         reply_handler = self._on_got_all,
170                         error_handler = self._on_got_all,
171                 )
172
173         def cancel(self):
174                 self._report_error("by request")
175
176         def _report_missed_if_ready(self):
177                 if self._didReport:
178                         pass
179                 elif self._requested is not None and (self._didMembersChange or self._didClose):
180                         if self._requested:
181                                 self._report_error("wrong direction")
182                         elif self._didClose:
183                                 self._report_success()
184                         else:
185                                 self._report_error("members added")
186                 else:
187                         if self._didClose:
188                                 self._report_error("closed too early")
189
190         def _report_success(self):
191                 assert not self._didReport
192                 self._didReport = True
193                 if self._timeoutId:
194                         gobject.source_remove(self._timeoutId)
195                         self._timeoutId = None
196                 self._on_success(self)
197
198         def _report_error(self, reason):
199                 assert not self._didReport
200                 self._didReport = True
201                 if self._timeoutId:
202                         gobject.source_remove(self._timeoutId)
203                         self._timeoutId = None
204                 self._on_error(self, reason)
205
206         @gtk_toolbox.log_exception(_moduleLogger)
207         def _on_got_all(self, properties):
208                 self._requested = properties["Requested"]
209                 self._report_missed_if_ready()
210
211         @gtk_toolbox.log_exception(_moduleLogger)
212         def _on_members_changed(self, message, added, removed, lp, rp, actor, reason):
213                 pprint.pprint((message, added, removed, lp, rp, actor, reason))
214                 if added:
215                         self._didMembersChange = True
216                         self._report_missed_if_ready()
217
218         @gtk_toolbox.log_exception(_moduleLogger)
219         def _on_closed(self):
220                 self._didClose = True
221                 self._report_missed_if_ready()
222
223         @gtk_toolbox.log_exception(_moduleLogger)
224         def _on_error(self, *args):
225                 self._report_error(args)
226
227         @gtk_toolbox.log_exception(_moduleLogger)
228         def _on_timeout(self):
229                 self._report_error("timeout")
230                 return False
231
232
233 class NewChannelSignaller(object):
234
235         def __init__(self, on_new_channel):
236                 self._sessionBus = dbus.SessionBus()
237                 self._on_user_new_channel = on_new_channel
238
239         def start(self):
240                 self._sessionBus.add_signal_receiver(
241                         self._on_new_channel,
242                         "NewChannel",
243                         "org.freedesktop.Telepathy.Connection",
244                         None,
245                         None
246                 )
247
248         def stop(self):
249                 self._sessionBus.remove_signal_receiver(
250                         self._on_new_channel,
251                         "NewChannel",
252                         "org.freedesktop.Telepathy.Connection",
253                         None,
254                         None
255                 )
256
257         @gtk_toolbox.log_exception(_moduleLogger)
258         def _on_new_channel(
259                 self, channelObjectPath, channelType, handleType, handle, supressHandler
260         ):
261                 connObjectPath = channelObjectPath.rsplit("/", 1)[0]
262                 serviceName = connObjectPath[1:].replace("/", ".")
263                 conn = telepathy.client.Connection(serviceName, connObjectPath)
264                 chan = telepathy.client.Channel(serviceName, channelObjectPath)
265                 self._on_user_new_channel(self._sessionBus, conn, chan, channelType)
266
267
268 class AutoAcceptManager(object):
269
270         def __init__(self):
271                 self._newChannelSignaller = NewChannelSignaller(self._on_new_channel)
272
273         def start(self):
274                 self._newChannelSignaller.start()
275
276         @gtk_toolbox.log_exception(_moduleLogger)
277         def _on_new_channel(self, bus, conn, chan, channelType):
278                 pprint.pprint((bus, conn, chan, channelType))
279                 if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
280                         return
281
282                 # @todo distinguish between preferred CMs
283                 attemptPickup = AutoAcceptCall(bus, conn, chan, self._on_inbound_call, self._on_inbound_call_error)
284
285         @gtk_toolbox.log_exception(_moduleLogger)
286         def _on_inbound_call(self, autoAcceptCall):
287                 # @todo Add a comparison for picking up for only certain contacts
288                 print autoAcceptCall.initiator, autoAcceptCall.target
289                 if autoAcceptCall.is_inbound():
290                         autoAcceptCall.accept_call(self._on_call_pickedup, self._on_pickup_error)
291                 else:
292                         _moduleLogger.debug(
293                                 "Not an inbound call (initiator=%r, target=%r)" % (autoAcceptCall.initiator, autoAcceptCall.target)
294                         )
295
296         @gtk_toolbox.log_exception(_moduleLogger)
297         def _on_inbound_call_error(self, *args):
298                 _moduleLogger.info("Inbound call error")
299
300         @gtk_toolbox.log_exception(_moduleLogger)
301         def _on_call_pickedup(self, autoAcceptCall):
302                 _moduleLogger.info("Call picked up")
303
304         @gtk_toolbox.log_exception(_moduleLogger)
305         def _on_pickup_error(self, autoAcceptCall, *args):
306                 _moduleLogger.info("Call failed to pick up (%r)" % (args, ))
307
308
309 class MissedManager(object):
310
311         def __init__(self):
312                 self._newChannelSignaller = NewChannelSignaller(self._on_new_channel)
313
314         def start(self):
315                 self._newChannelSignaller.start()
316
317         @gtk_toolbox.log_exception(_moduleLogger)
318         def _on_new_channel(self, bus, conn, chan, channelType):
319                 pprint.pprint((bus, conn, chan, channelType))
320                 if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
321                         return
322
323                 missDetection = WasMissedCall(
324                         bus, conn, chan, self._on_missed_call, self._on_error_for_missed
325                 )
326
327         @gtk_toolbox.log_exception(_moduleLogger)
328         def _on_missed_call(self, missDetection):
329                 _moduleLogger.info("Missed a call")
330
331         @gtk_toolbox.log_exception(_moduleLogger)
332         def _on_error_for_missed(self, missDetection, reason):
333                 _moduleLogger.info("Error: %r claims %r" % (missDetection, reason))
334
335
336 if __name__ == "__main__":
337         logging.basicConfig(level=logging.DEBUG)
338         l = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
339         if False:
340                 manager = AutoAcceptManager()
341         else:
342                 manager = MissedManager()
343
344         gobject.threads_init()
345         gobject.idle_add(manager.start)
346
347         mainloop = gobject.MainLoop(is_running=True)
348         mainloop.run()