Include _generated now so I get all the latest fancy stuff
[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 _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                 'Interfaces': lambda: dbus.Array(self.GetInterfaces(), signature='s'),
97                 'Status': lambda: dbus.UInt32(self.GetStatus())
98                  })
99
100         self._proto = proto
101
102         self._status = CONNECTION_STATUS_DISCONNECTED
103
104         self._handles = weakref.WeakValueDictionary()
105         self._next_handle_id = 1
106         self._client_handles = {}
107
108         self._channels = set()
109         self._next_channel_id = 0
110
111     def check_parameters(self, parameters):
112         """
113         Uses the values of self._mandatory_parameters and
114         self._optional_parameters to validate and type check all of the
115         provided parameters, and check all mandatory parameters are present.
116         Sets defaults according to the defaults if the client has not
117         provided any.
118         """
119         for (parm, value) in parameters.iteritems():
120             if parm in self._mandatory_parameters.keys():
121                 sig = self._mandatory_parameters[parm]
122             elif parm in self._optional_parameters.keys():
123                 sig = self._optional_parameters[parm]
124             else:
125                 raise InvalidArgument('unknown parameter name %s' % parm)
126
127             # we currently support strings, (u)int16/32 and booleans
128             if sig == 's':
129                 if not isinstance(value, unicode):
130                     raise InvalidArgument('incorrect type to %s parameter, got %s, expected a string' % (parm, type(value)))
131             elif sig in 'iunq':
132                 if not isinstance(value, (int, long)):
133                     raise InvalidArgument('incorrect type to %s parameter, got %s, expected an int' % (parm, type(value)))
134             elif sig == 'b':
135                 if not isinstance(value, (bool, dbus.Boolean)):
136                     raise InvalidArgument('incorrect type to %s parameter, got %s, expected an boolean' % (parm, type(value)))
137             else:
138                 raise TypeError('unknown type signature %s in protocol parameters' % type)
139
140         for (parm, value) in self._parameter_defaults.iteritems():
141             if parm not in parameters:
142                 parameters[parm] = value
143
144         missing = set(self._mandatory_parameters.keys()).difference(parameters.keys())
145         if missing:
146             raise InvalidArgument('required parameters %s not given' % missing)
147
148     def check_connected(self):
149         if self._status != CONNECTION_STATUS_CONNECTED:
150             raise Disconnected('method cannot be called unless status is CONNECTION_STATUS_CONNECTED')
151
152     def check_handle(self, handle_type, handle):
153         if (handle_type, handle) not in self._handles:
154             print "Connection.check_handle", handle, handle_type, self._handles.keys()
155             print str(list( [ self._handles[x] for x in self._handles.keys() ] ) )
156             raise InvalidHandle('handle number %d not valid for type %d' %
157                 (handle, handle_type))
158
159     def check_handle_type(self, type):
160         if (type <= HANDLE_TYPE_NONE or type > LAST_HANDLE_TYPE):
161             raise InvalidArgument('handle type %s not known' % type)
162
163     def get_handle_id(self):
164         id = self._next_handle_id
165         self._next_handle_id += 1
166         return id
167
168     def add_client_handle(self, handle, sender):
169         if sender in self._client_handles:
170             self._client_handles[sender].add((handle.get_type(), handle))
171         else:
172             self._client_handles[sender] = set([(handle.get_type(), handle)])
173
174     def get_handle_by_id(self, handle_type, handle_id):
175         # Strip off dbus stuff so we can be consistent
176         handle_type, handle_id = int(handle_type), int(handle_id)
177
178         self.check_handle(handle_type, handle_id)
179         return self._handles[handle_type, handle_id]
180
181     def get_handle_by_name(self, handle_type, handle_name):
182         self.check_handle_type(handle_type)
183         handle = None
184
185         for candidate in self._handles.itervalues():
186             if candidate.get_name() == handle_name:
187                 handle = candidate
188                 break
189         else:
190             id = self.get_handle_id()
191             handle = Handle(id, handle_type, handle_name)
192             self._handles[handle_type, id] = handle
193
194         return handle
195
196     def name_owner_changed_callback(self, name, old_owner, new_owner):
197         # when name and old_owner are the same, and new_owner is
198         # blank, it is the client itself releasing its name... aka exiting
199         if (name == old_owner and new_owner == "" and name in self._client_handles):
200             print "deleting handles for", name
201             del self._client_handles[name]
202
203     def set_self_handle(self, handle):
204         self._self_handle = handle
205
206     def get_channel_path(self, suffix):
207         if not suffix:
208             ret = '%s/channel%d' % (self._object_path, self._next_channel_id)
209             self._next_channel_id += 1
210         else:
211             ret = '%s/%s' % (self._object_path, suffix)
212         return ret
213
214     def add_channels(self, channels, signal=True):
215         """ add new channels and signal their creation"""
216         signal_channels = set()
217
218         for channel in channels:
219             if channel not in self._channels:
220                 self._channels.add(channel)
221                 signal_channels.add(channel)
222
223         if signal:
224             self.signal_new_channels(signal_channels)
225
226     def signal_new_channels(self, channels):
227         self.NewChannels([(channel._object_path, channel.get_props())
228             for channel in channels])
229
230         # Now NewChannel needs to be called for each new channel.
231         for channel in channels:
232             props = channel.get_props()
233
234             target_handle_type = props[CHANNEL_INTERFACE + '.TargetHandleType']
235             target_handle = props[CHANNEL_INTERFACE + '.TargetHandle']
236             suppress_handler = props[CHANNEL_INTERFACE + '.Requested']
237
238             self.NewChannel(channel._object_path, channel._type,
239                 target_handle_type, target_handle,
240                 suppress_handler)
241
242     def remove_channel(self, channel):
243         self._channels.remove(channel)
244         self.ChannelClosed(channel._object_path)
245
246     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='as')
247     def GetInterfaces(self):
248         self.check_connected()
249         return self._interfaces
250
251     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='s')
252     def GetProtocol(self):
253         return self._proto
254
255     @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='as')
256     def InspectHandles(self, handle_type, handles):
257         self.check_connected()
258         self.check_handle_type(handle_type)
259
260         for handle in handles:
261             self.check_handle(handle_type, handle)
262
263         ret = []
264         for handle in handles:
265             ret.append(self._handles[handle_type, handle].get_name())
266
267         return ret
268
269     @dbus.service.method(CONN_INTERFACE, in_signature='uas', out_signature='au', sender_keyword='sender')
270     def RequestHandles(self, handle_type, names, sender):
271         self.check_connected()
272         self.check_handle_type(handle_type)
273
274         ret = []
275         for name in names:
276             handle = self.get_handle_by_name(handle_type, name)
277             self.add_client_handle(handle, sender)
278             ret.append(handle.get_id())
279
280         return ret
281
282     @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='', sender_keyword='sender')
283     def HoldHandles(self, handle_type, handles, sender):
284         self.check_connected()
285         self.check_handle_type(handle_type)
286
287         for handle in handles:
288             self.check_handle(handle_type, handle)
289
290         for handle in handles:
291             hand = self._handles[handle_type, handle]
292             self.add_client_handle(hand, sender)
293
294     @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='', sender_keyword='sender')
295     def ReleaseHandles(self, handle_type, handles, sender):
296         self.check_connected()
297         self.check_handle_type(handle_type)
298
299         for handle in handles:
300             self.check_handle(handle_type, handle)
301             hand = self._handles[handle_type, handle]
302             if sender in self._client_handles:
303                 if (handle_type, hand) not in self._client_handles[sender]:
304                     raise NotAvailable('client is not holding handle %s of type %s' % (handle, handle_type))
305             else:
306                 raise NotAvailable('client does not hold any handles')
307
308         for handle in handles:
309             hand = self._handles[handle_type, handle]
310             self._client_handles[sender].remove((handle_type, hand))
311
312     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
313     def GetSelfHandle(self):
314         self.check_connected()
315         return self._self_handle
316
317     @dbus.service.signal(CONN_INTERFACE, signature='uu')
318     def StatusChanged(self, status, reason):
319         self._status = status
320
321     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
322     def GetStatus(self):
323         return self._status
324
325     @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='a(osuu)')
326     def ListChannels(self):
327         self.check_connected()
328         ret = []
329         for channel in self._channels:
330             chan = (channel._object_path, channel._type, channel._handle.get_type(), channel._handle.get_id())
331             ret.append(chan)
332         return ret
333
334
335 from _generated.Connection_Interface_Aliasing \
336         import ConnectionInterfaceAliasing
337
338
339 from _generated.Connection_Interface_Avatars \
340         import ConnectionInterfaceAvatars
341
342
343 from _generated.Connection_Interface_Capabilities \
344         import ConnectionInterfaceCapabilities \
345         as _ConnectionInterfaceCapabilities
346
347 class ConnectionInterfaceCapabilities(_ConnectionInterfaceCapabilities):
348     def __init__(self):
349         _ConnectionInterfaceCapabilities.__init__(self)
350         # { contact handle : { str channel type : [int, int] }}
351         # the first int is the generic caps, the second is the type-specific
352         self._caps = {}
353
354     @dbus.service.method(CONN_INTERFACE_CAPABILITIES, in_signature='au', out_signature='a(usuu)')
355     def GetCapabilities(self, handles):
356         ret = []
357         handle_type = HANDLE_TYPE_CONTACT
358         for handle in handles:
359             if (handle != 0 and (handle_type, handle) not in self._handles):
360                 raise InvalidHandle
361             elif handle in self._caps:
362                 types = self._caps[handle]
363                 for ctype, specs in types.items():
364                     ret.append([handle, ctype, specs[0], specs[1]])
365         return ret
366
367     @dbus.service.signal(CONN_INTERFACE_CAPABILITIES, signature='a(usuuuu)')
368     def CapabilitiesChanged(self, caps):
369         for handle, ctype, gen_old, gen_new, spec_old, spec_new in caps:
370             self._caps.setdefault(handle, {})[ctype] = [gen_new, spec_new]
371
372     @dbus.service.method(CONN_INTERFACE_CAPABILITIES,
373                          in_signature='a(su)as', out_signature='a(su)')
374     def AdvertiseCapabilities(self, add, remove):
375         my_caps = self._caps.setdefault(self._self_handle, {})
376
377         changed = {}
378         for ctype, spec_caps in add:
379             changed[ctype] = spec_caps
380         for ctype in remove:
381             changed[ctype] = None
382
383         caps = []
384         for ctype, spec_caps in changed.iteritems():
385             gen_old, spec_old = my_caps.get(ctype, (0, 0))
386             if spec_caps is None:
387                 # channel type no longer supported (provider has gone away)
388                 gen_new, spec_new = 0, 0
389             else:
390                 # channel type supports new capabilities
391                 gen_new, spec_new = gen_old, spec_old | spec_caps
392             if spec_old != spec_new or gen_old != gen_new:
393                 caps.append((self._self_handle, ctype, gen_old, gen_new,
394                             spec_old, spec_new))
395
396         self.CapabilitiesChanged(caps)
397
398         # return all my capabilities
399         return [(ctype, caps[1]) for ctype, caps in my_caps.iteritems()]
400
401 from _generated.Connection_Interface_Requests \
402         import ConnectionInterfaceRequests \
403         as _ConnectionInterfaceRequests
404
405 class ConnectionInterfaceRequests(
406     _ConnectionInterfaceRequests,
407     DBusProperties):
408
409     def __init__(self):
410         _ConnectionInterfaceRequests.__init__(self)
411         DBusProperties.__init__(self)
412
413         self._implement_property_get(CONNECTION_INTERFACE_REQUESTS,
414             {'Channels': lambda: dbus.Array(self._get_channels(),
415                 signature='(oa{sv})'),
416             'RequestableChannelClasses': lambda: dbus.Array(
417                 self._channel_manager.get_requestable_channel_classes(),
418                 signature='(a{sv}as)')})
419
420     def _get_channels(self):
421         return [(c._object_path, c.get_props()) for c in self._channels]
422
423     def _check_basic_properties(self, props):
424         # ChannelType must be present and must be a string.
425         if CHANNEL_INTERFACE + '.ChannelType' not in props or \
426                 not isinstance(props[CHANNEL_INTERFACE + '.ChannelType'],
427                     dbus.String):
428             raise InvalidArgument('ChannelType is required')
429
430         def check_valid_type_if_exists(prop, fun):
431             p = CHANNEL_INTERFACE + '.' + prop
432             if p in props and not fun(props[p]):
433                 raise InvalidArgument('Invalid %s' % prop)
434
435         # Allow TargetHandleType to be missing, but not to be otherwise broken.
436         check_valid_type_if_exists('TargetHandleType',
437             lambda p: p >= 0 and p <= LAST_HANDLE_TYPE)
438
439         # Allow TargetType to be missing, but not to be otherwise broken.
440         check_valid_type_if_exists('TargetHandle',
441             lambda p: p > 0 and p < (2**32)-1)
442         if props.get(CHANNEL_INTERFACE + '.TargetHandle') == 0:
443             raise InvalidArgument("TargetHandle may not be 0")
444
445         # Allow TargetID to be missing, but not to be otherwise broken.
446         check_valid_type_if_exists('TargetID',
447             lambda p: isinstance(p, dbus.String))
448
449         # Disallow InitiatorHandle, InitiatorID and Requested.
450         check_valid_type_if_exists('InitiatorHandle', lambda p: False)
451         check_valid_type_if_exists('InitiatorID', lambda p: False)
452         check_valid_type_if_exists('Requested', lambda p: False)
453
454         type = props[CHANNEL_INTERFACE + '.ChannelType']
455         handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
456                 HANDLE_TYPE_NONE)
457         handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', 0)
458
459         return (type, handle_type, handle)
460
461     def _validate_handle(self, props):
462         target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
463             HANDLE_TYPE_NONE)
464         target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
465         target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
466
467         # Handle type 0 cannot have a handle.
468         if target_handle_type == HANDLE_TYPE_NONE and target_handle not in (None, 0):
469             raise InvalidArgument('When TargetHandleType is NONE, ' +
470                 'TargetHandle must be omitted or 0')
471
472         # Handle type 0 cannot have a TargetID.
473         if target_handle_type == HANDLE_TYPE_NONE and target_id != None:
474             raise InvalidArgument('When TargetHandleType is NONE, TargetID ' +
475                 'must be omitted')
476
477         if target_handle_type != HANDLE_TYPE_NONE:
478             if target_handle == None and target_id == None:
479                 raise InvalidArgument('When TargetHandleType is not NONE, ' +
480                     'either TargetHandle or TargetID must also be given')
481
482             if target_handle != None and target_id != None:
483                 raise InvalidArgument('TargetHandle and TargetID must not ' +
484                     'both be given')
485
486             self.check_handle_type(target_handle_type)
487
488
489     def _alter_properties(self, props):
490         target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
491             HANDLE_TYPE_NONE)
492         target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
493         target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
494         # Note: what the spec calls a handle, we call an ID
495         # What the spec calls an ID, we call a name
496         # What we call a handle is a handle object
497
498         altered_properties = props.copy()
499
500         if target_handle_type != HANDLE_TYPE_NONE:
501             if target_handle is None:
502                 handle = self.get_handle_by_name(target_handle_type, target_id)
503                 self.add_client_handle(handle, None)
504                 target_handle = handle.get_id()
505                 altered_properties[CHANNEL_INTERFACE + '.TargetHandle'] = \
506                     target_handle
507             else:
508                 # Check the supplied TargetHandle is valid
509                 self.check_handle(target_handle_type, target_handle)
510
511                 target_id = self._handles[target_handle_type,\
512                                             target_handle].get_name()
513                 altered_properties[CHANNEL_INTERFACE + '.TargetID'] = \
514                     target_id
515
516         altered_properties[CHANNEL_INTERFACE + '.Requested'] = True
517
518         return altered_properties
519
520     @dbus.service.method(CONNECTION_INTERFACE_REQUESTS,
521         in_signature='a{sv}', out_signature='oa{sv}',
522         async_callbacks=('_success', '_error'))
523     def CreateChannel(self, request, _success, _error):
524         type, handle_type, handle = self._check_basic_properties(request)
525         self._validate_handle(request)
526         props = self._alter_properties(request)
527
528         channel = self._channel_manager.create_channel_for_props(props, signal=False)
529
530         returnedProps = channel.get_props()
531         _success(channel._object_path, returnedProps)
532
533         # CreateChannel MUST return *before* NewChannels is emitted.
534         self.signal_new_channels([channel])
535
536     @dbus.service.method(CONNECTION_INTERFACE_REQUESTS,
537         in_signature='a{sv}', out_signature='boa{sv}',
538         async_callbacks=('_success', '_error'))
539     def EnsureChannel(self, request, _success, _error):
540         type, handle_type, handle = self._check_basic_properties(request)
541         self._validate_handle(request)
542         props = self._alter_properties(request)
543
544         yours = not self._channel_manager.channel_exists(props)
545
546         channel = self._channel_manager.channel_for_props(props, signal=False)
547
548         returnedProps = channel.get_props()
549         _success(yours, channel._object_path, returnedProps)
550
551         self.signal_new_channels([channel])
552
553 from _generated.Connection_Interface_Presence \
554         import ConnectionInterfacePresence
555
556 from _generated.Connection_Interface_Simple_Presence \
557         import ConnectionInterfaceSimplePresence
558
559 from _generated.Connection_Interface_Contacts \
560         import ConnectionInterfaceContacts