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):
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 self.check_handle(handle_type, handle_id)
173 return self._handles[handle_type, handle_id]
175 def get_handle_by_name(self, handle_type, handle_name):
176 self.check_handle_type(handle_type)
179 for candidate in self._handles.itervalues():
180 if candidate.get_name() == handle_name:
184 id = self.get_handle_id()
185 handle = Handle(id, handle_type, handle_name)
186 self._handles[handle_type, id] = handle
190 def name_owner_changed_callback(self, name, old_owner, new_owner):
191 # when name and old_owner are the same, and new_owner is
192 # blank, it is the client itself releasing its name... aka exiting
193 if (name == old_owner and new_owner == "" and name in self._client_handles):
194 print "deleting handles for", name
195 del self._client_handles[name]
197 def set_self_handle(self, handle):
198 self._self_handle = handle
200 def get_channel_path(self):
201 ret = '%s/channel%d' % (self._object_path, self._next_channel_id)
202 self._next_channel_id += 1
205 def add_channels(self, channels, signal=True):
206 """ add new channels and signal their creation"""
207 signal_channels = set()
209 for channel in channels:
210 if channel not in self._channels:
211 self._channels.add(channel)
212 signal_channels.add(channel)
215 self.signal_new_channels(signal_channels)
217 def signal_new_channels(self, channels):
218 self.NewChannels([(channel._object_path, channel.get_props())
219 for channel in channels])
221 # Now NewChannel needs to be called for each new channel.
222 for channel in channels:
223 props = channel.get_props()
225 target_handle_type = props[CHANNEL_INTERFACE + '.TargetHandleType']
226 target_handle = props[CHANNEL_INTERFACE + '.TargetHandle']
227 suppress_handler = props[CHANNEL_INTERFACE + '.Requested']
229 self.NewChannel(channel._object_path, channel._type,
230 target_handle_type, target_handle,
233 def remove_channel(self, channel):
234 self._channels.remove(channel)
235 self.ChannelClosed(channel._object_path)
237 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='as')
238 def GetInterfaces(self):
239 self.check_connected()
240 return self._interfaces
242 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='s')
243 def GetProtocol(self):
246 @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='as')
247 def InspectHandles(self, handle_type, handles):
248 self.check_connected()
249 self.check_handle_type(handle_type)
251 for handle in handles:
252 self.check_handle(handle_type, handle)
255 for handle in handles:
256 ret.append(self._handles[handle_type, handle].get_name())
260 @dbus.service.method(CONN_INTERFACE, in_signature='uas', out_signature='au', sender_keyword='sender')
261 def RequestHandles(self, handle_type, names, sender):
262 self.check_connected()
263 self.check_handle_type(handle_type)
267 handle = self.get_handle_by_name(handle_type, name)
268 self.add_client_handle(handle, sender)
269 ret.append(handle.get_id())
273 @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='', sender_keyword='sender')
274 def HoldHandles(self, handle_type, handles, sender):
275 self.check_connected()
276 self.check_handle_type(handle_type)
278 for handle in handles:
279 self.check_handle(handle_type, handle)
281 for handle in handles:
282 hand = self._handles[handle_type, handle]
283 self.add_client_handle(hand, sender)
285 @dbus.service.method(CONN_INTERFACE, in_signature='uau', out_signature='', sender_keyword='sender')
286 def ReleaseHandles(self, handle_type, handles, sender):
287 self.check_connected()
288 self.check_handle_type(handle_type)
290 for handle in handles:
291 self.check_handle(handle_type, handle)
292 hand = self._handles[handle_type, handle]
293 if sender in self._client_handles:
294 if (handle_type, hand) not in self._client_handles[sender]:
295 raise NotAvailable('client is not holding handle %s of type %s' % (handle, handle_type))
297 raise NotAvailable('client does not hold any handles')
299 for handle in handles:
300 hand = self._handles[handle_type, handle]
301 self._client_handles[sender].remove((handle_type, hand))
303 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
304 def GetSelfHandle(self):
305 self.check_connected()
306 return self._self_handle
308 @dbus.service.signal(CONN_INTERFACE, signature='uu')
309 def StatusChanged(self, status, reason):
310 self._status = status
312 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='u')
316 @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='a(osuu)')
317 def ListChannels(self):
318 self.check_connected()
320 for channel in self._channels:
321 chan = (channel._object_path, channel._type, channel._get_handle_type(), channel._handle)
326 from telepathy._generated.Connection_Interface_Aliasing \
327 import ConnectionInterfaceAliasing
330 from telepathy._generated.Connection_Interface_Avatars \
331 import ConnectionInterfaceAvatars
334 from telepathy._generated.Connection_Interface_Capabilities \
335 import ConnectionInterfaceCapabilities \
336 as _ConnectionInterfaceCapabilities
338 class ConnectionInterfaceCapabilities(_ConnectionInterfaceCapabilities):
340 _ConnectionInterfaceCapabilities.__init__(self)
341 # { contact handle : { str channel type : [int, int] }}
342 # the first int is the generic caps, the second is the type-specific
345 @dbus.service.method(CONN_INTERFACE_CAPABILITIES, in_signature='au', out_signature='a(usuu)')
346 def GetCapabilities(self, handles):
348 handle_type = HANDLE_TYPE_CONTACT
349 for handle in handles:
350 if (handle != 0 and (handle_type, handle) not in self._handles):
352 elif handle in self._caps:
353 types = self._caps[handle]
354 for ctype, specs in types.items():
355 ret.append([handle, ctype, specs[0], specs[1]])
358 @dbus.service.signal(CONN_INTERFACE_CAPABILITIES, signature='a(usuuuu)')
359 def CapabilitiesChanged(self, caps):
360 for handle, ctype, gen_old, gen_new, spec_old, spec_new in caps:
361 self._caps.setdefault(handle, {})[ctype] = [gen_new, spec_new]
363 @dbus.service.method(CONN_INTERFACE_CAPABILITIES,
364 in_signature='a(su)as', out_signature='a(su)')
365 def AdvertiseCapabilities(self, add, remove):
366 my_caps = self._caps.setdefault(self._self_handle, {})
369 for ctype, spec_caps in add:
370 changed[ctype] = spec_caps
372 changed[ctype] = None
375 for ctype, spec_caps in changed.iteritems():
376 gen_old, spec_old = my_caps.get(ctype, (0, 0))
377 if spec_caps is None:
378 # channel type no longer supported (provider has gone away)
379 gen_new, spec_new = 0, 0
381 # channel type supports new capabilities
382 gen_new, spec_new = gen_old, spec_old | spec_caps
383 if spec_old != spec_new or gen_old != gen_new:
384 caps.append((self._self_handle, ctype, gen_old, gen_new,
387 self.CapabilitiesChanged(caps)
389 # return all my capabilities
390 return [(ctype, caps[1]) for ctype, caps in my_caps.iteritems()]
392 from telepathy._generated.Connection_Interface_Requests \
393 import ConnectionInterfaceRequests \
394 as _ConnectionInterfaceRequests
396 class ConnectionInterfaceRequests(
397 _ConnectionInterfaceRequests,
401 _ConnectionInterfaceRequests.__init__(self)
402 DBusProperties.__init__(self)
404 self._implement_property_get(CONNECTION_INTERFACE_REQUESTS,
405 {'Channels': lambda: dbus.Array(self._get_channels(),
406 signature='(oa{sv})'),
407 'RequestableChannelClasses': lambda: dbus.Array(
408 self._channel_manager.get_requestable_channel_classes(),
409 signature='(a{sv}as)')})
411 def _get_channels(self):
412 return [(c._object_path, c.get_props()) for c in self._channels]
414 def _check_basic_properties(self, props):
415 # ChannelType must be present and must be a string.
416 if CHANNEL_INTERFACE + '.ChannelType' not in props or \
417 not isinstance(props[CHANNEL_INTERFACE + '.ChannelType'],
419 raise InvalidArgument('ChannelType is required')
421 def check_valid_type_if_exists(prop, fun):
422 p = CHANNEL_INTERFACE + '.' + prop
423 if p in props and not fun(props[p]):
424 raise InvalidArgument('Invalid %s' % prop)
426 # Allow TargetHandleType to be missing, but not to be otherwise broken.
427 check_valid_type_if_exists('TargetHandleType',
428 lambda p: p > 0 and p < (2**32)-1)
430 # Allow TargetType to be missing, but not to be otherwise broken.
431 check_valid_type_if_exists('TargetHandle',
432 lambda p: p > 0 and p < (2**32)-1)
433 if props.get(CHANNEL_INTERFACE + '.TargetHandle') == 0:
434 raise InvalidArgument("TargetHandle may not be 0")
436 # Allow TargetID to be missing, but not to be otherwise broken.
437 check_valid_type_if_exists('TargetID',
438 lambda p: isinstance(p, dbus.String))
440 # Disallow InitiatorHandle, InitiatorID and Requested.
441 check_valid_type_if_exists('InitiatorHandle', lambda p: False)
442 check_valid_type_if_exists('InitiatorID', lambda p: False)
443 check_valid_type_if_exists('Requested', lambda p: False)
445 type = props[CHANNEL_INTERFACE + '.ChannelType']
446 handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
448 handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', 0)
450 return (type, handle_type, handle)
452 def _validate_handle(self, props):
453 target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
455 target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
456 target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
458 # Handle type 0 cannot have a handle.
459 if target_handle_type == HANDLE_TYPE_NONE and target_handle != None:
460 raise InvalidArgument('When TargetHandleType is NONE, ' +
461 'TargetHandle must be omitted')
463 # Handle type 0 cannot have a TargetID.
464 if target_handle_type == HANDLE_TYPE_NONE and target_id != None:
465 raise InvalidArgument('When TargetHandleType is NONE, TargetID ' +
468 if target_handle_type != HANDLE_TYPE_NONE:
469 if target_handle == None and target_id == None:
470 raise InvalidArgument('When TargetHandleType is not NONE, ' +
471 'either TargetHandle or TargetID must also be given')
473 if target_handle != None and target_id != None:
474 raise InvalidArgument('TargetHandle and TargetID must not ' +
477 self.check_handle_type(target_handle_type)
480 def _alter_properties(self, props):
481 target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType',
483 target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None)
484 target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None)
485 # Note: what the spec calls a handle, we call an ID
486 # What the spec calls an ID, we call a name
487 # What we call a handle is a handle object
489 altered_properties = props.copy()
491 if target_handle_type != HANDLE_TYPE_NONE:
492 if target_handle == None:
493 target_handle = self.get_handle_by_name(target_handle_type, target_id).get_id()
494 altered_properties[CHANNEL_INTERFACE + '.TargetHandle'] = \
497 # Check the supplied TargetHandle is valid
498 self.check_handle(target_handle_type, target_handle)
500 target_id = self._handles[target_handle_type,\
501 target_handle].get_name()
502 altered_properties[CHANNEL_INTERFACE + '.TargetID'] = \
505 altered_properties[CHANNEL_INTERFACE + '.Requested'] = True
507 return altered_properties
509 @dbus.service.method(CONNECTION_INTERFACE_REQUESTS,
510 in_signature='a{sv}', out_signature='oa{sv}',
511 async_callbacks=('_success', '_error'))
512 def CreateChannel(self, request, _success, _error):
513 type, handle_type, handle = self._check_basic_properties(request)
514 self._validate_handle(request)
515 props = self._alter_properties(request)
517 channel = self._channel_manager.channel_for_props(props, signal=False)
519 # Remove mutable properties
522 iface, name = prop.rsplit('.', 1) # a bit of a hack
523 if name in channel._immutable_properties:
524 if channel._immutable_properties[name] != iface:
532 _success(channel._object_path, props)
534 # CreateChannel MUST return *before* NewChannels is emitted.
535 self.signal_new_channels([channel])
537 @dbus.service.method(CONNECTION_INTERFACE_REQUESTS,
538 in_signature='a{sv}', out_signature='boa{sv}',
539 async_callbacks=('_success', '_error'))
540 def EnsureChannel(self, request, _success, _error):
541 type, handle_type, handle = self._check_basic_properties(request)
542 self._validate_handle(request)
543 props = self._alter_properties(request)
545 yours = not self._channel_manager.channel_exists(props)
547 channel = self._channel_manager.channel_for_props(props, signal=False)
549 _success(yours, channel._object_path, props)
551 self.signal_new_channels([channel])
553 from telepathy._generated.Connection_Interface_Presence \
554 import ConnectionInterfacePresence
556 from telepathy._generated.Connection_Interface_Simple_Presence \
557 import ConnectionInterfaceSimplePresence
559 from telepathy._generated.Connection_Interface_Contacts \
560 import ConnectionInterfaceContacts