1 # telepathy-python - Base classes defining the interfaces of the Telepathy framework
3 # Copyright (C) 2005, 2006 Collabora Limited
4 # Copyright (C) 2005, 2006 Nokia Corporation
5 # Copyright (C) 2006 INdT
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.
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.
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
26 from telepathy.constants import (CONNECTION_STATUS_DISCONNECTED,
27 CONNECTION_STATUS_CONNECTED,
31 from telepathy.errors import (Disconnected, InvalidArgument,
32 InvalidHandle, NotAvailable,
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,
42 from handle import Handle
43 from properties import DBusProperties
45 from _generated.Connection import Connection as _Connection
47 _BAD = re.compile(r'(?:^[0-9])|(?:[^A-Za-z0-9])')
49 def _escape_as_identifier(name):
50 if isinstance(name, unicode):
51 name = name.encode('utf-8')
54 return _BAD.sub(lambda match: '_%02x' % ord(match.group(0)), name)
56 class Connection(_Connection, DBusProperties):
58 _optional_parameters = {}
59 _mandatory_parameters = {}
61 def __init__(self, proto, account, manager=None):
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
71 warnings.warn('The manager parameter to Connection.__init__ '
72 'should be supplied', DeprecationWarning)
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())
80 object_path = '/org/freedesktop/Telepathy/Connection/%s/%s/%s' % \
81 (manager, proto, clean_account)
82 _Connection.__init__(self, bus_name, object_path)
84 # monitor clients dying so we can release handles
85 dbus.SessionBus().add_signal_receiver(self.name_owner_changed_callback,
87 'org.freedesktop.DBus',
88 'org.freedesktop.DBus',
89 '/org/freedesktop/DBus')
91 self._interfaces = set()
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())
102 self._status = CONNECTION_STATUS_DISCONNECTED
104 self._handles = weakref.WeakValueDictionary()
105 self._next_handle_id = 1
106 self._client_handles = {}
108 self._channels = set()
109 self._next_channel_id = 0
111 def check_parameters(self, parameters):
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
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]
125 raise InvalidArgument('unknown parameter name %s' % parm)
127 # we currently support strings, (u)int16/32 and booleans
129 if not isinstance(value, unicode):
130 raise InvalidArgument('incorrect type to %s parameter, got %s, expected a string' % (parm, type(value)))
132 if not isinstance(value, (int, long)):
133 raise InvalidArgument('incorrect type to %s parameter, got %s, expected an int' % (parm, type(value)))
135 if not isinstance(value, (bool, dbus.Boolean)):
136 raise InvalidArgument('incorrect type to %s parameter, got %s, expected an boolean' % (parm, type(value)))
138 raise TypeError('unknown type signature %s in protocol parameters' % type)
140 for (parm, value) in self._parameter_defaults.iteritems():
141 if parm not in parameters:
142 parameters[parm] = value
144 missing = set(self._mandatory_parameters.keys()).difference(parameters.keys())
146 raise InvalidArgument('required parameters %s not given' % missing)
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')
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))
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)
163 def get_handle_id(self):
164 id = self._next_handle_id
165 self._next_handle_id += 1
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))
172 self._client_handles[sender] = set([(handle.get_type(), handle)])
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)
178 self.check_handle(handle_type, handle_id)
179 return self._handles[handle_type, handle_id]
181 def get_handle_by_name(self, handle_type, handle_name):
182 self.check_handle_type(handle_type)
185 for candidate in self._handles.itervalues():
186 if candidate.get_name() == handle_name:
190 id = self.get_handle_id()
191 handle = Handle(id, handle_type, handle_name)
192 self._handles[handle_type, id] = handle
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]
203 def set_self_handle(self, handle):
204 self._self_handle = handle
206 def get_channel_path(self, suffix):
208 ret = '%s/channel%d' % (self._object_path, self._next_channel_id)
209 self._next_channel_id += 1
211 ret = '%s/%s' % (self._object_path, suffix)
214 def add_channels(self, channels, signal=True):
215 """ add new channels and signal their creation"""
216 signal_channels = set()
218 for channel in channels:
219 if channel not in self._channels:
220 self._channels.add(channel)
221 signal_channels.add(channel)
224 self.signal_new_channels(signal_channels)
226 def signal_new_channels(self, channels):
227 self.NewChannels([(channel._object_path, channel.get_props())
228 for channel in channels])
230 # Now NewChannel needs to be called for each new channel.
231 for channel in channels:
232 props = channel.get_props()
234 target_handle_type = props[CHANNEL_INTERFACE + '.TargetHandleType']
235 target_handle = props[CHANNEL_INTERFACE + '.TargetHandle']
236 suppress_handler = props[CHANNEL_INTERFACE + '.Requested']
238 self.NewChannel(channel._object_path, channel._type,
239 target_handle_type, target_handle,
242 def remove_channel(self, channel):
243 self._channels.remove(channel)
244 self.ChannelClosed(channel._object_path)
246 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='as')
247 def GetInterfaces(self):
248 self.check_connected()
249 return self._interfaces
251 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='s')
252 def GetProtocol(self):
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)
260 for handle in handles:
261 self.check_handle(handle_type, handle)
264 for handle in handles:
265 ret.append(self._handles[handle_type, handle].get_name())
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)
276 handle = self.get_handle_by_name(handle_type, name)
277 self.add_client_handle(handle, sender)
278 ret.append(handle.get_id())
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)
287 for handle in handles:
288 self.check_handle(handle_type, handle)
290 for handle in handles:
291 hand = self._handles[handle_type, handle]
292 self.add_client_handle(hand, sender)
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)
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))
306 raise NotAvailable('client does not hold any handles')
308 for handle in handles:
309 hand = self._handles[handle_type, handle]
310 self._client_handles[sender].remove((handle_type, hand))
312 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
313 def GetSelfHandle(self):
314 self.check_connected()
315 return self._self_handle
317 @dbus.service.signal(CONN_INTERFACE, signature='uu')
318 def StatusChanged(self, status, reason):
319 self._status = status
321 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
325 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='a(osuu)')
326 def ListChannels(self):
327 self.check_connected()
329 for channel in self._channels:
330 chan = (channel._object_path, channel._type, channel._handle.get_type(), channel._handle.get_id())
335 from _generated.Connection_Interface_Aliasing \
336 import ConnectionInterfaceAliasing
339 from _generated.Connection_Interface_Avatars \
340 import ConnectionInterfaceAvatars
343 from _generated.Connection_Interface_Capabilities \
344 import ConnectionInterfaceCapabilities \
345 as _ConnectionInterfaceCapabilities
347 class ConnectionInterfaceCapabilities(_ConnectionInterfaceCapabilities):
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
354 @dbus.service.method(CONN_INTERFACE_CAPABILITIES, in_signature='au', out_signature='a(usuu)')
355 def GetCapabilities(self, handles):
357 handle_type = HANDLE_TYPE_CONTACT
358 for handle in handles:
359 if (handle != 0 and (handle_type, handle) not in self._handles):
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]])
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]
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, {})
378 for ctype, spec_caps in add:
379 changed[ctype] = spec_caps
381 changed[ctype] = None
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
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,
396 self.CapabilitiesChanged(caps)
398 # return all my capabilities
399 return [(ctype, caps[1]) for ctype, caps in my_caps.iteritems()]
401 from _generated.Connection_Interface_Requests \
402 import ConnectionInterfaceRequests \
403 as _ConnectionInterfaceRequests
405 class ConnectionInterfaceRequests(
406 _ConnectionInterfaceRequests,
410 _ConnectionInterfaceRequests.__init__(self)
411 DBusProperties.__init__(self)
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)')})
420 def _get_channels(self):
421 return [(c._object_path, c.get_props()) for c in self._channels]
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'],
428 raise InvalidArgument('ChannelType is required')
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)
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)
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")
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))
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)
454 type = props[CHANNEL_INTERFACE + '.ChannelType']
455 handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
457 handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', 0)
459 return (type, handle_type, handle)
461 def _validate_handle(self, props):
462 target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
464 target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
465 target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
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')
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 ' +
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')
482 if target_handle != None and target_id != None:
483 raise InvalidArgument('TargetHandle and TargetID must not ' +
486 self.check_handle_type(target_handle_type)
489 def _alter_properties(self, props):
490 target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
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
498 altered_properties = props.copy()
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'] = \
508 # Check the supplied TargetHandle is valid
509 self.check_handle(target_handle_type, target_handle)
511 target_id = self._handles[target_handle_type,\
512 target_handle].get_name()
513 altered_properties[CHANNEL_INTERFACE + '.TargetID'] = \
516 altered_properties[CHANNEL_INTERFACE + '.Requested'] = True
518 return altered_properties
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)
528 channel = self._channel_manager.create_channel_for_props(props, signal=False)
530 returnedProps = channel.get_props()
531 _success(channel._object_path, returnedProps)
533 # CreateChannel MUST return *before* NewChannels is emitted.
534 self.signal_new_channels([channel])
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)
544 yours = not self._channel_manager.channel_exists(props)
546 channel = self._channel_manager.channel_for_props(props, signal=False)
548 returnedProps = channel.get_props()
549 _success(yours, channel._object_path, returnedProps)
551 self.signal_new_channels([channel])
553 from _generated.Connection_Interface_Presence \
554 import ConnectionInterfacePresence
556 from _generated.Connection_Interface_Simple_Presence \
557 import ConnectionInterfaceSimplePresence
559 from _generated.Connection_Interface_Contacts \
560 import ConnectionInterfaceContacts