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 telepathy._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())})
99 self._status = CONNECTION_STATUS_DISCONNECTED
101 self._handles = weakref.WeakValueDictionary()
102 self._next_handle_id = 1
103 self._client_handles = {}
105 self._channels = set()
106 self._next_channel_id = 0
108 def check_parameters(self, parameters):
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
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]
122 raise InvalidArgument('unknown parameter name %s' % parm)
124 # we currently support strings, (u)int16/32 and booleans
126 if not isinstance(value, unicode):
127 raise InvalidArgument('incorrect type to %s parameter, got %s, expected a string' % (parm, type(value)))
129 if not isinstance(value, (int, long)):
130 raise InvalidArgument('incorrect type to %s parameter, got %s, expected an int' % (parm, type(value)))
132 if not isinstance(value, (bool, dbus.Boolean)):
133 raise InvalidArgument('incorrect type to %s parameter, got %s, expected an boolean' % (parm, type(value)))
135 raise TypeError('unknown type signature %s in protocol parameters' % type)
137 for (parm, value) in self._parameter_defaults.iteritems():
138 if parm not in parameters:
139 parameters[parm] = value
141 missing = set(self._mandatory_parameters.keys()).difference(parameters.keys())
143 raise InvalidArgument('required parameters %s not given' % missing)
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')
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))
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)
160 def get_handle_id(self):
161 id = self._next_handle_id
162 self._next_handle_id += 1
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))
169 self._client_handles[sender] = set([(handle.get_type(), handle)])
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)
175 self.check_handle(handle_type, handle_id)
176 return self._handles[handle_type, handle_id]
178 def get_handle_by_name(self, handle_type, handle_name):
179 self.check_handle_type(handle_type)
182 for candidate in self._handles.itervalues():
183 if candidate.get_name() == handle_name:
187 id = self.get_handle_id()
188 handle = Handle(id, handle_type, handle_name)
189 self._handles[handle_type, id] = handle
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]
200 def set_self_handle(self, handle):
201 self._self_handle = handle
203 def get_channel_path(self, suffix):
205 ret = '%s/channel%d' % (self._object_path, self._next_channel_id)
206 self._next_channel_id += 1
208 ret = '%s/%s' % (self._object_path, suffix)
211 def add_channels(self, channels, signal=True):
212 """ add new channels and signal their creation"""
213 signal_channels = set()
215 for channel in channels:
216 if channel not in self._channels:
217 self._channels.add(channel)
218 signal_channels.add(channel)
221 self.signal_new_channels(signal_channels)
223 def signal_new_channels(self, channels):
224 self.NewChannels([(channel._object_path, channel.get_props())
225 for channel in channels])
227 # Now NewChannel needs to be called for each new channel.
228 for channel in channels:
229 props = channel.get_props()
231 target_handle_type = props[CHANNEL_INTERFACE + '.TargetHandleType']
232 target_handle = props[CHANNEL_INTERFACE + '.TargetHandle']
233 suppress_handler = props[CHANNEL_INTERFACE + '.Requested']
235 self.NewChannel(channel._object_path, channel._type,
236 target_handle_type, target_handle,
239 def remove_channel(self, channel):
240 self._channels.remove(channel)
241 self.ChannelClosed(channel._object_path)
243 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='as')
244 def GetInterfaces(self):
245 self.check_connected()
246 return self._interfaces
248 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='s')
249 def GetProtocol(self):
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)
257 for handle in handles:
258 self.check_handle(handle_type, handle)
261 for handle in handles:
262 ret.append(self._handles[handle_type, handle].get_name())
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)
273 handle = self.get_handle_by_name(handle_type, name)
274 self.add_client_handle(handle, sender)
275 ret.append(handle.get_id())
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)
284 for handle in handles:
285 self.check_handle(handle_type, handle)
287 for handle in handles:
288 hand = self._handles[handle_type, handle]
289 self.add_client_handle(hand, sender)
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)
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))
303 raise NotAvailable('client does not hold any handles')
305 for handle in handles:
306 hand = self._handles[handle_type, handle]
307 self._client_handles[sender].remove((handle_type, hand))
309 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
310 def GetSelfHandle(self):
311 self.check_connected()
312 return self._self_handle
314 @dbus.service.signal(CONN_INTERFACE, signature='uu')
315 def StatusChanged(self, status, reason):
316 self._status = status
318 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
322 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='a(osuu)')
323 def ListChannels(self):
324 self.check_connected()
326 for channel in self._channels:
327 chan = (channel._object_path, channel._type, channel._handle.get_type(), channel._handle.get_id())
332 from telepathy._generated.Connection_Interface_Aliasing \
333 import ConnectionInterfaceAliasing
336 from telepathy._generated.Connection_Interface_Avatars \
337 import ConnectionInterfaceAvatars
340 from telepathy._generated.Connection_Interface_Capabilities \
341 import ConnectionInterfaceCapabilities \
342 as _ConnectionInterfaceCapabilities
344 class ConnectionInterfaceCapabilities(_ConnectionInterfaceCapabilities):
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
351 @dbus.service.method(CONN_INTERFACE_CAPABILITIES, in_signature='au', out_signature='a(usuu)')
352 def GetCapabilities(self, handles):
354 handle_type = HANDLE_TYPE_CONTACT
355 for handle in handles:
356 if (handle != 0 and (handle_type, handle) not in self._handles):
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]])
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]
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, {})
375 for ctype, spec_caps in add:
376 changed[ctype] = spec_caps
378 changed[ctype] = None
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
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,
393 self.CapabilitiesChanged(caps)
395 # return all my capabilities
396 return [(ctype, caps[1]) for ctype, caps in my_caps.iteritems()]
398 from telepathy._generated.Connection_Interface_Requests \
399 import ConnectionInterfaceRequests \
400 as _ConnectionInterfaceRequests
402 class ConnectionInterfaceRequests(
403 _ConnectionInterfaceRequests,
407 _ConnectionInterfaceRequests.__init__(self)
408 DBusProperties.__init__(self)
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)')})
417 def _get_channels(self):
418 return [(c._object_path, c.get_props()) for c in self._channels]
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'],
425 raise InvalidArgument('ChannelType is required')
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)
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)
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")
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))
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)
451 type = props[CHANNEL_INTERFACE + '.ChannelType']
452 handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
454 handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', 0)
456 return (type, handle_type, handle)
458 def _validate_handle(self, props):
459 target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
461 target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
462 target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
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')
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 ' +
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')
479 if target_handle != None and target_id != None:
480 raise InvalidArgument('TargetHandle and TargetID must not ' +
483 self.check_handle_type(target_handle_type)
486 def _alter_properties(self, props):
487 target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
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
495 altered_properties = props.copy()
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'] = \
505 # Check the supplied TargetHandle is valid
506 self.check_handle(target_handle_type, target_handle)
508 target_id = self._handles[target_handle_type,\
509 target_handle].get_name()
510 altered_properties[CHANNEL_INTERFACE + '.TargetID'] = \
513 altered_properties[CHANNEL_INTERFACE + '.Requested'] = True
515 return altered_properties
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)
525 channel = self._channel_manager.create_channel_for_props(props, signal=False)
527 returnedProps = channel.get_props()
528 _success(channel._object_path, returnedProps)
530 # CreateChannel MUST return *before* NewChannels is emitted.
531 self.signal_new_channels([channel])
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)
541 yours = not self._channel_manager.channel_exists(props)
543 channel = self._channel_manager.channel_for_props(props, signal=False)
545 returnedProps = channel.get_props()
546 _success(yours, channel._object_path, returnedProps)
548 self.signal_new_channels([channel])
550 from telepathy._generated.Connection_Interface_Presence \
551 import ConnectionInterfacePresence
553 from telepathy._generated.Connection_Interface_Simple_Presence \
554 import ConnectionInterfaceSimplePresence
556 from telepathy._generated.Connection_Interface_Contacts \
557 import ConnectionInterfaceContacts