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.types.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):
204 ret = '%s/channel%d' % (self._object_path, self._next_channel_id)
205 self._next_channel_id += 1
208 def add_channels(self, channels, signal=True):
209 """ add new channels and signal their creation"""
210 signal_channels = set()
212 for channel in channels:
213 if channel not in self._channels:
214 self._channels.add(channel)
215 signal_channels.add(channel)
218 self.signal_new_channels(signal_channels)
220 def signal_new_channels(self, channels):
221 self.NewChannels([(channel._object_path, channel.get_props())
222 for channel in channels])
224 # Now NewChannel needs to be called for each new channel.
225 for channel in channels:
226 props = channel.get_props()
228 target_handle_type = props[CHANNEL_INTERFACE + '.TargetHandleType']
229 target_handle = props[CHANNEL_INTERFACE + '.TargetHandle']
230 suppress_handler = props[CHANNEL_INTERFACE + '.Requested']
232 self.NewChannel(channel._object_path, channel._type,
233 target_handle_type, target_handle,
236 def remove_channel(self, channel):
237 self._channels.remove(channel)
238 self.ChannelClosed(channel._object_path)
240 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='as')
241 def GetInterfaces(self):
242 self.check_connected()
243 return self._interfaces
245 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='s')
246 def GetProtocol(self):
249 @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='as')
250 def InspectHandles(self, handle_type, handles):
251 self.check_connected()
252 self.check_handle_type(handle_type)
254 for handle in handles:
255 self.check_handle(handle_type, handle)
258 for handle in handles:
259 ret.append(self._handles[handle_type, handle].get_name())
263 @dbus.service.method(CONN_INTERFACE, in_signature='uas', out_signature='au', sender_keyword='sender')
264 def RequestHandles(self, handle_type, names, sender):
265 self.check_connected()
266 self.check_handle_type(handle_type)
270 handle = self.get_handle_by_name(handle_type, name)
271 self.add_client_handle(handle, sender)
272 ret.append(handle.get_id())
276 @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='', sender_keyword='sender')
277 def HoldHandles(self, handle_type, handles, sender):
278 self.check_connected()
279 self.check_handle_type(handle_type)
281 for handle in handles:
282 self.check_handle(handle_type, handle)
284 for handle in handles:
285 hand = self._handles[handle_type, handle]
286 self.add_client_handle(hand, sender)
288 @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='', sender_keyword='sender')
289 def ReleaseHandles(self, handle_type, handles, sender):
290 self.check_connected()
291 self.check_handle_type(handle_type)
293 for handle in handles:
294 self.check_handle(handle_type, handle)
295 hand = self._handles[handle_type, handle]
296 if sender in self._client_handles:
297 if (handle_type, hand) not in self._client_handles[sender]:
298 raise NotAvailable('client is not holding handle %s of type %s' % (handle, handle_type))
300 raise NotAvailable('client does not hold any handles')
302 for handle in handles:
303 hand = self._handles[handle_type, handle]
304 self._client_handles[sender].remove((handle_type, hand))
306 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
307 def GetSelfHandle(self):
308 self.check_connected()
309 return self._self_handle
311 @dbus.service.signal(CONN_INTERFACE, signature='uu')
312 def StatusChanged(self, status, reason):
313 self._status = status
315 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
319 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='a(osuu)')
320 def ListChannels(self):
321 self.check_connected()
323 for channel in self._channels:
324 chan = (channel._object_path, channel._type, channel._get_handle_type(), channel._handle)
329 from telepathy._generated.Connection_Interface_Aliasing \
330 import ConnectionInterfaceAliasing
333 from telepathy._generated.Connection_Interface_Avatars \
334 import ConnectionInterfaceAvatars
337 from telepathy._generated.Connection_Interface_Capabilities \
338 import ConnectionInterfaceCapabilities \
339 as _ConnectionInterfaceCapabilities
341 class ConnectionInterfaceCapabilities(_ConnectionInterfaceCapabilities):
343 _ConnectionInterfaceCapabilities.__init__(self)
344 # { contact handle : { str channel type : [int, int] }}
345 # the first int is the generic caps, the second is the type-specific
348 @dbus.service.method(CONN_INTERFACE_CAPABILITIES, in_signature='au', out_signature='a(usuu)')
349 def GetCapabilities(self, handles):
351 handle_type = HANDLE_TYPE_CONTACT
352 for handle in handles:
353 if (handle != 0 and (handle_type, handle) not in self._handles):
355 elif handle in self._caps:
356 types = self._caps[handle]
357 for ctype, specs in types.items():
358 ret.append([handle, ctype, specs[0], specs[1]])
361 @dbus.service.signal(CONN_INTERFACE_CAPABILITIES, signature='a(usuuuu)')
362 def CapabilitiesChanged(self, caps):
363 for handle, ctype, gen_old, gen_new, spec_old, spec_new in caps:
364 self._caps.setdefault(handle, {})[ctype] = [gen_new, spec_new]
366 @dbus.service.method(CONN_INTERFACE_CAPABILITIES,
367 in_signature='a(su)as', out_signature='a(su)')
368 def AdvertiseCapabilities(self, add, remove):
369 my_caps = self._caps.setdefault(self._self_handle, {})
372 for ctype, spec_caps in add:
373 changed[ctype] = spec_caps
375 changed[ctype] = None
378 for ctype, spec_caps in changed.iteritems():
379 gen_old, spec_old = my_caps.get(ctype, (0, 0))
380 if spec_caps is None:
381 # channel type no longer supported (provider has gone away)
382 gen_new, spec_new = 0, 0
384 # channel type supports new capabilities
385 gen_new, spec_new = gen_old, spec_old | spec_caps
386 if spec_old != spec_new or gen_old != gen_new:
387 caps.append((self._self_handle, ctype, gen_old, gen_new,
390 self.CapabilitiesChanged(caps)
392 # return all my capabilities
393 return [(ctype, caps[1]) for ctype, caps in my_caps.iteritems()]
395 from telepathy._generated.Connection_Interface_Requests \
396 import ConnectionInterfaceRequests \
397 as _ConnectionInterfaceRequests
399 class ConnectionInterfaceRequests(
400 _ConnectionInterfaceRequests,
404 _ConnectionInterfaceRequests.__init__(self)
405 DBusProperties.__init__(self)
407 self._implement_property_get(CONNECTION_INTERFACE_REQUESTS,
408 {'Channels': lambda: dbus.Array(self._get_channels(),
409 signature='(oa{sv})'),
410 'RequestableChannelClasses': lambda: dbus.Array(
411 self._channel_manager.get_requestable_channel_classes(),
412 signature='(a{sv}as)')})
414 def _get_channels(self):
415 return [(c._object_path, c.get_props()) for c in self._channels]
417 def _check_basic_properties(self, props):
418 # ChannelType must be present and must be a string.
419 if CHANNEL_INTERFACE + '.ChannelType' not in props or \
420 not isinstance(props[CHANNEL_INTERFACE + '.ChannelType'],
422 raise InvalidArgument('ChannelType is required')
424 def check_valid_type_if_exists(prop, fun):
425 p = CHANNEL_INTERFACE + '.' + prop
426 if p in props and not fun(props[p]):
427 raise InvalidArgument('Invalid %s' % prop)
429 # Allow TargetHandleType to be missing, but not to be otherwise broken.
430 check_valid_type_if_exists('TargetHandleType',
431 lambda p: p > 0 and p < (2**32)-1)
433 # Allow TargetType to be missing, but not to be otherwise broken.
434 check_valid_type_if_exists('TargetHandle',
435 lambda p: p > 0 and p < (2**32)-1)
436 if props.get(CHANNEL_INTERFACE + '.TargetHandle') == 0:
437 raise InvalidArgument("TargetHandle may not be 0")
439 # Allow TargetID to be missing, but not to be otherwise broken.
440 check_valid_type_if_exists('TargetID',
441 lambda p: isinstance(p, dbus.String))
443 # Disallow InitiatorHandle, InitiatorID and Requested.
444 check_valid_type_if_exists('InitiatorHandle', lambda p: False)
445 check_valid_type_if_exists('InitiatorID', lambda p: False)
446 check_valid_type_if_exists('Requested', lambda p: False)
448 type = props[CHANNEL_INTERFACE + '.ChannelType']
449 handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
451 handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', 0)
453 return (type, handle_type, handle)
455 def _validate_handle(self, props):
456 target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
458 target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
459 target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
461 # Handle type 0 cannot have a handle.
462 if target_handle_type == HANDLE_TYPE_NONE and target_handle != None:
463 raise InvalidArgument('When TargetHandleType is NONE, ' +
464 'TargetHandle must be omitted')
466 # Handle type 0 cannot have a TargetID.
467 if target_handle_type == HANDLE_TYPE_NONE and target_id != None:
468 raise InvalidArgument('When TargetHandleType is NONE, TargetID ' +
471 if target_handle_type != HANDLE_TYPE_NONE:
472 if target_handle == None and target_id == None:
473 raise InvalidArgument('When TargetHandleType is not NONE, ' +
474 'either TargetHandle or TargetID must also be given')
476 if target_handle != None and target_id != None:
477 raise InvalidArgument('TargetHandle and TargetID must not ' +
480 self.check_handle_type(target_handle_type)
483 def _alter_properties(self, props):
484 target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
486 target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
487 target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
488 # Note: what the spec calls a handle, we call an ID
489 # What the spec calls an ID, we call a name
490 # What we call a handle is a handle object
492 altered_properties = props.copy()
494 if target_handle_type != HANDLE_TYPE_NONE:
495 if target_handle is None:
496 handle = self.get_handle_by_name(target_handle_type, target_id)
497 self.add_client_handle(handle, None)
498 target_handle = handle.get_id()
499 altered_properties[CHANNEL_INTERFACE + '.TargetHandle'] = \
502 # Check the supplied TargetHandle is valid
503 self.check_handle(target_handle_type, target_handle)
505 target_id = self._handles[target_handle_type,\
506 target_handle].get_name()
507 altered_properties[CHANNEL_INTERFACE + '.TargetID'] = \
510 altered_properties[CHANNEL_INTERFACE + '.Requested'] = True
512 return altered_properties
514 @dbus.service.method(CONNECTION_INTERFACE_REQUESTS,
515 in_signature='a{sv}', out_signature='oa{sv}',
516 async_callbacks=('_success', '_error'))
517 def CreateChannel(self, request, _success, _error):
518 type, handle_type, handle = self._check_basic_properties(request)
519 self._validate_handle(request)
520 props = self._alter_properties(request)
522 channel = self._channel_manager.create_channel_for_props(props, signal=False)
524 # Remove mutable properties
527 iface, name = prop.rsplit('.', 1) # a bit of a hack
528 if name in channel._immutable_properties:
529 if channel._immutable_properties[name] != iface:
537 _success(channel._object_path, props)
539 # CreateChannel MUST return *before* NewChannels is emitted.
540 self.signal_new_channels([channel])
542 @dbus.service.method(CONNECTION_INTERFACE_REQUESTS,
543 in_signature='a{sv}', out_signature='boa{sv}',
544 async_callbacks=('_success', '_error'))
545 def EnsureChannel(self, request, _success, _error):
546 type, handle_type, handle = self._check_basic_properties(request)
547 self._validate_handle(request)
548 props = self._alter_properties(request)
550 yours = not self._channel_manager.channel_exists(props)
552 channel = self._channel_manager.channel_for_props(props, signal=False)
554 _success(yours, channel._object_path, props)
556 self.signal_new_channels([channel])
558 from telepathy._generated.Connection_Interface_Presence \
559 import ConnectionInterfacePresence
561 from telepathy._generated.Connection_Interface_Simple_Presence \
562 import ConnectionInterfaceSimplePresence
564 from telepathy._generated.Connection_Interface_Contacts \
565 import ConnectionInterfaceContacts