adding more debug info
[theonering] / src / tp / conn.py
1 # telepathy-python - Base classes defining the interfaces of the Telepathy framework
2 #
3 # Copyright (C) 2005, 2006 Collabora Limited
4 # Copyright (C) 2005, 2006 Nokia Corporation
5 # Copyright (C) 2006 INdT
6 #
7 # This library is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation; either
10 # version 2.1 of the License, or (at your option) any later version.
11 #
12 # This library 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 GNU
15 # Lesser General Public License for more details.
16 #
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this library; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21 import dbus
22 import dbus.service
23 import re
24 import weakref
25
26 from telepathy.constants import (CONNECTION_STATUS_DISCONNECTED,
27                                  CONNECTION_STATUS_CONNECTED,
28                                  HANDLE_TYPE_NONE,
29                                  HANDLE_TYPE_CONTACT,
30                                  LAST_HANDLE_TYPE)
31 from telepathy.errors import (Disconnected, InvalidArgument,
32                               InvalidHandle, NotAvailable,
33                               NotImplemented)
34 from telepathy.interfaces import (CONN_INTERFACE,
35                                   CONN_INTERFACE_ALIASING,
36                                   CONN_INTERFACE_AVATARS,
37                                   CONN_INTERFACE_CAPABILITIES,
38                                   CONN_INTERFACE_PRESENCE,
39                                   CONN_INTERFACE_RENAMING,
40                                   CONNECTION_INTERFACE_REQUESTS,
41                                   CHANNEL_INTERFACE)
42 from handle import Handle
43 from properties import DBusProperties
44
45 from telepathy._generated.Connection import Connection as _Connection
46
47 _BAD = re.compile(r'(?:^[0-9])|(?:[^A-Za-z0-9])')
48
49 def _escape_as_identifier(name):
50     if isinstance(name, unicode):
51         name = name.encode('utf-8')
52     if not name:
53         return '_'
54     return _BAD.sub(lambda match: '_%02x' % ord(match.group(0)), name)
55
56 class Connection(_Connection, DBusProperties):
57
58     _optional_parameters = {}
59     _mandatory_parameters = {}
60
61     def __init__(self, proto, account, manager=None):
62         """
63         Parameters:
64         proto - the name of the protcol this conection should be handling.
65         account - a protocol-specific account name
66         manager - the name of the connection manager
67         """
68
69         if manager is None:
70             import warnings
71             warnings.warn('The manager parameter to Connection.__init__ '
72                           'should be supplied', DeprecationWarning)
73             manager = 'python'
74
75         clean_account = _escape_as_identifier(account)
76         bus_name = u'org.freedesktop.Telepathy.Connection.%s.%s.%s' % \
77                 (manager, proto, clean_account)
78         bus_name = dbus.service.BusName(bus_name, bus=dbus.SessionBus())
79
80         object_path = '/org/freedesktop/Telepathy/Connection/%s/%s/%s' % \
81                 (manager, proto, clean_account)
82         _Connection.__init__(self, bus_name, object_path)
83
84         # monitor clients dying so we can release handles
85         dbus.SessionBus().add_signal_receiver(self.name_owner_changed_callback,
86                                               'NameOwnerChanged',
87                                               'org.freedesktop.DBus',
88                                               'org.freedesktop.DBus',
89                                               '/org/freedesktop/DBus')
90
91         self._interfaces = set()
92
93         DBusProperties.__init__(self)
94         self._implement_property_get(CONN_INTERFACE,
95                 {'SelfHandle': lambda: dbus.UInt32(self.GetSelfHandle())})
96
97         self._proto = proto
98
99         self._status = CONNECTION_STATUS_DISCONNECTED
100
101         self._handles = weakref.WeakValueDictionary()
102         self._next_handle_id = 1
103         self._client_handles = {}
104
105         self._channels = set()
106         self._next_channel_id = 0
107
108     def check_parameters(self, parameters):
109         """
110         Uses the values of self._mandatory_parameters and
111         self._optional_parameters to validate and type check all of the
112         provided parameters, and check all mandatory parameters are present.
113         Sets defaults according to the defaults if the client has not
114         provided any.
115         """
116         for (parm, value) in parameters.iteritems():
117             if parm in self._mandatory_parameters.keys():
118                 sig = self._mandatory_parameters[parm]
119             elif parm in self._optional_parameters.keys():
120                 sig = self._optional_parameters[parm]
121             else:
122                 raise InvalidArgument('unknown parameter name %s' % parm)
123
124             # we currently support strings, (u)int16/32 and booleans
125             if sig == 's':
126                 if not isinstance(value, unicode):
127                     raise InvalidArgument('incorrect type to %s parameter, got %s, expected a string' % (parm, type(value)))
128             elif sig in 'iunq':
129                 if not isinstance(value, (int, long)):
130                     raise InvalidArgument('incorrect type to %s parameter, got %s, expected an int' % (parm, type(value)))
131             elif sig == 'b':
132                 if not isinstance(value, (bool, dbus.Boolean)):
133                     raise InvalidArgument('incorrect type to %s parameter, got %s, expected an boolean' % (parm, type(value)))
134             else:
135                 raise TypeError('unknown type signature %s in protocol parameters' % type)
136
137         for (parm, value) in self._parameter_defaults.iteritems():
138             if parm not in parameters:
139                 parameters[parm] = value
140
141         missing = set(self._mandatory_parameters.keys()).difference(parameters.keys())
142         if missing:
143             raise InvalidArgument('required parameters %s not given' % missing)
144
145     def check_connected(self):
146         if self._status != CONNECTION_STATUS_CONNECTED:
147             raise Disconnected('method cannot be called unless status is CONNECTION_STATUS_CONNECTED')
148
149     def check_handle(self, handle_type, handle):
150         if (handle_type, handle) not in self._handles:
151             print "Connection.check_handle", handle, handle_type, self._handles.keys()
152             print str(list( [ self._handles[x] for x in self._handles.keys() ] ) )
153             raise InvalidHandle('handle number %d not valid for type %d' %
154                 (handle, handle_type))
155
156     def check_handle_type(self, type):
157         if (type <= HANDLE_TYPE_NONE or type > LAST_HANDLE_TYPE):
158             raise InvalidArgument('handle type %s not known' % type)
159
160     def get_handle_id(self):
161         id = self._next_handle_id
162         self._next_handle_id += 1
163         return id
164
165     def add_client_handle(self, handle, sender):
166         if sender in self._client_handles:
167             self._client_handles[sender].add((handle.get_type(), handle))
168         else:
169             self._client_handles[sender] = set([(handle.get_type(), handle)])
170
171     def get_handle_by_id(self, handle_type, handle_id):
172         # Strip off dbus stuff so we can be consistent
173         handle_type, handle_id = int(handle_type), int(handle_id)
174
175         self.check_handle(handle_type, handle_id)
176         return self._handles[handle_type, handle_id]
177
178     def get_handle_by_name(self, handle_type, handle_name):
179         self.check_handle_type(handle_type)
180         handle = None
181
182         for candidate in self._handles.itervalues():
183             if candidate.get_name() == handle_name:
184                 handle = candidate
185                 break
186         else:
187             id = self.get_handle_id()
188             handle = Handle(id, handle_type, handle_name)
189             self._handles[handle_type, id] = handle
190
191         return handle
192
193     def name_owner_changed_callback(self, name, old_owner, new_owner):
194         # when name and old_owner are the same, and new_owner is
195         # blank, it is the client itself releasing its name... aka exiting
196         if (name == old_owner and new_owner == "" and name in self._client_handles):
197             print "deleting handles for", name
198             del self._client_handles[name]
199
200     def set_self_handle(self, handle):
201         self._self_handle = handle
202
203     def get_channel_path(self, suffix):
204         if not suffix:
205             ret = '%s/channel%d' % (self._object_path, self._next_channel_id)
206             self._next_channel_id += 1
207         else:
208             ret = '%s/%s' % (self._object_path, suffix)
209         return ret
210
211     def add_channels(self, channels, signal=True):
212         """ add new channels and signal their creation"""
213         signal_channels = set()
214
215         for channel in channels:
216             if channel not in self._channels:
217                 self._channels.add(channel)
218                 signal_channels.add(channel)
219
220         if signal:
221             self.signal_new_channels(signal_channels)
222
223     def signal_new_channels(self, channels):
224         self.NewChannels([(channel._object_path, channel.get_props())
225             for channel in channels])
226
227         # Now NewChannel needs to be called for each new channel.
228         for channel in channels:
229             props = channel.get_props()
230
231             target_handle_type = props[CHANNEL_INTERFACE + '.TargetHandleType']
232             target_handle = props[CHANNEL_INTERFACE + '.TargetHandle']
233             suppress_handler = props[CHANNEL_INTERFACE + '.Requested']
234
235             self.NewChannel(channel._object_path, channel._type,
236                 target_handle_type, target_handle,
237                 suppress_handler)
238
239     def remove_channel(self, channel):
240         self._channels.remove(channel)
241         self.ChannelClosed(channel._object_path)
242
243     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='as')
244     def GetInterfaces(self):
245         self.check_connected()
246         return self._interfaces
247
248     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='s')
249     def GetProtocol(self):
250         return self._proto
251
252     @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='as')
253     def InspectHandles(self, handle_type, handles):
254         self.check_connected()
255         self.check_handle_type(handle_type)
256
257         for handle in handles:
258             self.check_handle(handle_type, handle)
259
260         ret = []
261         for handle in handles:
262             ret.append(self._handles[handle_type, handle].get_name())
263
264         return ret
265
266     @dbus.service.method(CONN_INTERFACE, in_signature='uas', out_signature='au', sender_keyword='sender')
267     def RequestHandles(self, handle_type, names, sender):
268         self.check_connected()
269         self.check_handle_type(handle_type)
270
271         ret = []
272         for name in names:
273             handle = self.get_handle_by_name(handle_type, name)
274             self.add_client_handle(handle, sender)
275             ret.append(handle.get_id())
276
277         return ret
278
279     @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='', sender_keyword='sender')
280     def HoldHandles(self, handle_type, handles, sender):
281         self.check_connected()
282         self.check_handle_type(handle_type)
283
284         for handle in handles:
285             self.check_handle(handle_type, handle)
286
287         for handle in handles:
288             hand = self._handles[handle_type, handle]
289             self.add_client_handle(hand, sender)
290
291     @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='', sender_keyword='sender')
292     def ReleaseHandles(self, handle_type, handles, sender):
293         self.check_connected()
294         self.check_handle_type(handle_type)
295
296         for handle in handles:
297             self.check_handle(handle_type, handle)
298             hand = self._handles[handle_type, handle]
299             if sender in self._client_handles:
300                 if (handle_type, hand) not in self._client_handles[sender]:
301                     raise NotAvailable('client is not holding handle %s of type %s' % (handle, handle_type))
302             else:
303                 raise NotAvailable('client does not hold any handles')
304
305         for handle in handles:
306             hand = self._handles[handle_type, handle]
307             self._client_handles[sender].remove((handle_type, hand))
308
309     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
310     def GetSelfHandle(self):
311         self.check_connected()
312         return self._self_handle
313
314     @dbus.service.signal(CONN_INTERFACE, signature='uu')
315     def StatusChanged(self, status, reason):
316         self._status = status
317
318     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
319     def GetStatus(self):
320         return self._status
321
322     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='a(osuu)')
323     def ListChannels(self):
324         self.check_connected()
325         ret = []
326         for channel in self._channels:
327             chan = (channel._object_path, channel._type, channel._handle.get_type(), channel._handle.get_id())
328             ret.append(chan)
329         return ret
330
331
332 from telepathy._generated.Connection_Interface_Aliasing \
333         import ConnectionInterfaceAliasing
334
335
336 from telepathy._generated.Connection_Interface_Avatars \
337         import ConnectionInterfaceAvatars
338
339
340 from telepathy._generated.Connection_Interface_Capabilities \
341         import ConnectionInterfaceCapabilities \
342         as _ConnectionInterfaceCapabilities
343
344 class ConnectionInterfaceCapabilities(_ConnectionInterfaceCapabilities):
345     def __init__(self):
346         _ConnectionInterfaceCapabilities.__init__(self)
347         # { contact handle : { str channel type : [int, int] }}
348         # the first int is the generic caps, the second is the type-specific
349         self._caps = {}
350
351     @dbus.service.method(CONN_INTERFACE_CAPABILITIES, in_signature='au', out_signature='a(usuu)')
352     def GetCapabilities(self, handles):
353         ret = []
354         handle_type = HANDLE_TYPE_CONTACT
355         for handle in handles:
356             if (handle != 0 and (handle_type, handle) not in self._handles):
357                 raise InvalidHandle
358             elif handle in self._caps:
359                 types = self._caps[handle]
360                 for ctype, specs in types.items():
361                     ret.append([handle, ctype, specs[0], specs[1]])
362         return ret
363
364     @dbus.service.signal(CONN_INTERFACE_CAPABILITIES, signature='a(usuuuu)')
365     def CapabilitiesChanged(self, caps):
366         for handle, ctype, gen_old, gen_new, spec_old, spec_new in caps:
367             self._caps.setdefault(handle, {})[ctype] = [gen_new, spec_new]
368
369     @dbus.service.method(CONN_INTERFACE_CAPABILITIES,
370                          in_signature='a(su)as', out_signature='a(su)')
371     def AdvertiseCapabilities(self, add, remove):
372         my_caps = self._caps.setdefault(self._self_handle, {})
373
374         changed = {}
375         for ctype, spec_caps in add:
376             changed[ctype] = spec_caps
377         for ctype in remove:
378             changed[ctype] = None
379
380         caps = []
381         for ctype, spec_caps in changed.iteritems():
382             gen_old, spec_old = my_caps.get(ctype, (0, 0))
383             if spec_caps is None:
384                 # channel type no longer supported (provider has gone away)
385                 gen_new, spec_new = 0, 0
386             else:
387                 # channel type supports new capabilities
388                 gen_new, spec_new = gen_old, spec_old | spec_caps
389             if spec_old != spec_new or gen_old != gen_new:
390                 caps.append((self._self_handle, ctype, gen_old, gen_new,
391                             spec_old, spec_new))
392
393         self.CapabilitiesChanged(caps)
394
395         # return all my capabilities
396         return [(ctype, caps[1]) for ctype, caps in my_caps.iteritems()]
397
398 from telepathy._generated.Connection_Interface_Requests \
399         import ConnectionInterfaceRequests \
400         as _ConnectionInterfaceRequests
401
402 class ConnectionInterfaceRequests(
403     _ConnectionInterfaceRequests,
404     DBusProperties):
405
406     def __init__(self):
407         _ConnectionInterfaceRequests.__init__(self)
408         DBusProperties.__init__(self)
409
410         self._implement_property_get(CONNECTION_INTERFACE_REQUESTS,
411             {'Channels': lambda: dbus.Array(self._get_channels(),
412                 signature='(oa{sv})'),
413             'RequestableChannelClasses': lambda: dbus.Array(
414                 self._channel_manager.get_requestable_channel_classes(),
415                 signature='(a{sv}as)')})
416
417     def _get_channels(self):
418         return [(c._object_path, c.get_props()) for c in self._channels]
419
420     def _check_basic_properties(self, props):
421         # ChannelType must be present and must be a string.
422         if CHANNEL_INTERFACE + '.ChannelType' not in props or \
423                 not isinstance(props[CHANNEL_INTERFACE + '.ChannelType'],
424                     dbus.String):
425             raise InvalidArgument('ChannelType is required')
426
427         def check_valid_type_if_exists(prop, fun):
428             p = CHANNEL_INTERFACE + '.' + prop
429             if p in props and not fun(props[p]):
430                 raise InvalidArgument('Invalid %s' % prop)
431
432         # Allow TargetHandleType to be missing, but not to be otherwise broken.
433         check_valid_type_if_exists('TargetHandleType',
434             lambda p: p >= 0 and p <= LAST_HANDLE_TYPE)
435
436         # Allow TargetType to be missing, but not to be otherwise broken.
437         check_valid_type_if_exists('TargetHandle',
438             lambda p: p > 0 and p < (2**32)-1)
439         if props.get(CHANNEL_INTERFACE + '.TargetHandle') == 0:
440             raise InvalidArgument("TargetHandle may not be 0")
441
442         # Allow TargetID to be missing, but not to be otherwise broken.
443         check_valid_type_if_exists('TargetID',
444             lambda p: isinstance(p, dbus.String))
445
446         # Disallow InitiatorHandle, InitiatorID and Requested.
447         check_valid_type_if_exists('InitiatorHandle', lambda p: False)
448         check_valid_type_if_exists('InitiatorID', lambda p: False)
449         check_valid_type_if_exists('Requested', lambda p: False)
450
451         type = props[CHANNEL_INTERFACE + '.ChannelType']
452         handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
453                 HANDLE_TYPE_NONE)
454         handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', 0)
455
456         return (type, handle_type, handle)
457
458     def _validate_handle(self, props):
459         target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
460             HANDLE_TYPE_NONE)
461         target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
462         target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
463
464         # Handle type 0 cannot have a handle.
465         if target_handle_type == HANDLE_TYPE_NONE and target_handle not in (None, 0):
466             raise InvalidArgument('When TargetHandleType is NONE, ' +
467                 'TargetHandle must be omitted or 0')
468
469         # Handle type 0 cannot have a TargetID.
470         if target_handle_type == HANDLE_TYPE_NONE and target_id != None:
471             raise InvalidArgument('When TargetHandleType is NONE, TargetID ' +
472                 'must be omitted')
473
474         if target_handle_type != HANDLE_TYPE_NONE:
475             if target_handle == None and target_id == None:
476                 raise InvalidArgument('When TargetHandleType is not NONE, ' +
477                     'either TargetHandle or TargetID must also be given')
478
479             if target_handle != None and target_id != None:
480                 raise InvalidArgument('TargetHandle and TargetID must not ' +
481                     'both be given')
482
483             self.check_handle_type(target_handle_type)
484
485
486     def _alter_properties(self, props):
487         target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
488             HANDLE_TYPE_NONE)
489         target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
490         target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
491         # Note: what the spec calls a handle, we call an ID
492         # What the spec calls an ID, we call a name
493         # What we call a handle is a handle object
494
495         altered_properties = props.copy()
496
497         if target_handle_type != HANDLE_TYPE_NONE:
498             if target_handle is None:
499                 handle = self.get_handle_by_name(target_handle_type, target_id)
500                 self.add_client_handle(handle, None)
501                 target_handle = handle.get_id()
502                 altered_properties[CHANNEL_INTERFACE + '.TargetHandle'] = \
503                     target_handle
504             else:
505                 # Check the supplied TargetHandle is valid
506                 self.check_handle(target_handle_type, target_handle)
507
508                 target_id = self._handles[target_handle_type,\
509                                             target_handle].get_name()
510                 altered_properties[CHANNEL_INTERFACE + '.TargetID'] = \
511                     target_id
512
513         altered_properties[CHANNEL_INTERFACE + '.Requested'] = True
514
515         return altered_properties
516
517     @dbus.service.method(CONNECTION_INTERFACE_REQUESTS,
518         in_signature='a{sv}', out_signature='oa{sv}',
519         async_callbacks=('_success', '_error'))
520     def CreateChannel(self, request, _success, _error):
521         type, handle_type, handle = self._check_basic_properties(request)
522         self._validate_handle(request)
523         props = self._alter_properties(request)
524
525         channel = self._channel_manager.create_channel_for_props(props, signal=False)
526
527         returnedProps = channel.get_props()
528         _success(channel._object_path, returnedProps)
529
530         # CreateChannel MUST return *before* NewChannels is emitted.
531         self.signal_new_channels([channel])
532
533     @dbus.service.method(CONNECTION_INTERFACE_REQUESTS,
534         in_signature='a{sv}', out_signature='boa{sv}',
535         async_callbacks=('_success', '_error'))
536     def EnsureChannel(self, request, _success, _error):
537         type, handle_type, handle = self._check_basic_properties(request)
538         self._validate_handle(request)
539         props = self._alter_properties(request)
540
541         yours = not self._channel_manager.channel_exists(props)
542
543         channel = self._channel_manager.channel_for_props(props, signal=False)
544
545         returnedProps = channel.get_props()
546         _success(yours, channel._object_path, returnedProps)
547
548         self.signal_new_channels([channel])
549
550 from telepathy._generated.Connection_Interface_Presence \
551         import ConnectionInterfacePresence
552
553 from telepathy._generated.Connection_Interface_Simple_Presence \
554         import ConnectionInterfaceSimplePresence
555
556 from telepathy._generated.Connection_Interface_Contacts \
557         import ConnectionInterfaceContacts