diff options
author | Louis-Francis Ratté-Boulianne <louis-francis.ratte-boulianne@collabora.co.uk> | 2009-08-11 22:48:33 -0400 |
---|---|---|
committer | Louis-Francis Ratté-Boulianne <louis-francis.ratte-boulianne@collabora.co.uk> | 2009-08-11 22:48:33 -0400 |
commit | 2d8eaadde631e28bf251f4a7164a90c1301efd7c (patch) | |
tree | d816d7014ac183bd2c9a085a945574b86b04e855 /src | |
parent | 54ac81d1e44bdd24d27b40a4549ad3c530ca9c69 (diff) | |
parent | 5487b662f43c5cda757e7275a14116aa64538d82 (diff) |
Merge branch 'master' of git+ssh://git.collabora.co.uk/git/user/lfrb/telepathy-python/
Diffstat (limited to 'src')
-rw-r--r-- | src/_version.py | 2 | ||||
-rw-r--r-- | src/client/conn.py | 8 | ||||
-rw-r--r-- | src/server/__init__.py | 1 | ||||
-rw-r--r-- | src/server/channel.py | 75 | ||||
-rw-r--r-- | src/server/channelmanager.py | 99 | ||||
-rw-r--r-- | src/server/conn.py | 217 |
6 files changed, 370 insertions, 32 deletions
diff --git a/src/_version.py b/src/_version.py index f087efe..d643b4f 100644 --- a/src/_version.py +++ b/src/_version.py @@ -1,4 +1,4 @@ __all__ = ('version', '__version__') -version = (0, 15, 7, 1) +version = (0, 15, 10, 1) __version__ = '.'.join(str(x) for x in version) diff --git a/src/client/conn.py b/src/client/conn.py index a37a11d..e46c3ea 100644 --- a/src/client/conn.py +++ b/src/client/conn.py @@ -22,9 +22,11 @@ import dbus from telepathy.client.channel import Channel from telepathy.client.interfacefactory import ( InterfaceFactory, default_error_handler) -from telepathy.interfaces import CONN_INTERFACE +from telepathy.interfaces import ( + CONN_INTERFACE, CONNECTION_INTERFACE_REQUESTS) from telepathy.constants import CONNECTION_STATUS_CONNECTED + class Connection(InterfaceFactory): def __init__(self, service_name, object_path=None, bus=None, ready_handler=None, error_handler=default_error_handler): @@ -99,6 +101,10 @@ class Connection(InterfaceFactory): path = self.RequestChannel(type, handle_type, handle, suppress_handler) return Channel(self.service_name, path, self.bus) + def create_channel(self, props): + object_path, props = self[CONNECTION_INTERFACE_REQUESTS].CreateChannel(props) + return Channel(self.service_name, object_path, self.bus) + def call_when_ready(self, handler): if self._ready: handler(self) diff --git a/src/server/__init__.py b/src/server/__init__.py index 52e804f..aa077be 100644 --- a/src/server/__init__.py +++ b/src/server/__init__.py @@ -23,6 +23,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from telepathy.server.connmgr import * from telepathy.server.conn import * from telepathy.server.channel import * +from telepathy.server.channelmanager import * from telepathy.server.handle import * from telepathy.server.media import * from telepathy.server.properties import * diff --git a/src/server/channel.py b/src/server/channel.py index 9455235..b454823 100644 --- a/src/server/channel.py +++ b/src/server/channel.py @@ -44,30 +44,49 @@ from telepathy.server.properties import DBusProperties class Channel(_Channel, DBusProperties): - def __init__(self, connection, type, handle): + def __init__(self, connection, manager, props): """ Initialise the base channel object. Parameters: connection - the parent Connection object - type - interface name for the type of this channel - handle - the channels handle if applicable + props - initial channel properties """ self._conn = connection + self._chan_manager = manager object_path = self._conn.get_channel_path() _Channel.__init__(self, self._conn._name, object_path) - self._type = type - self._handle = handle + self._type = props[CHANNEL_INTERFACE + '.ChannelType'] + self._requested = props[CHANNEL_INTERFACE + '.Requested'] + + self._immutable_properties = dict() + + self._handle = self._conn.handle( + props[CHANNEL_INTERFACE + '.TargetHandleType'], + props[CHANNEL_INTERFACE + '.TargetHandle']) self._interfaces = set() DBusProperties.__init__(self) self._implement_property_get(CHANNEL_INTERFACE, {'ChannelType': lambda: dbus.String(self.GetChannelType()), 'Interfaces': lambda: dbus.Array(self.GetInterfaces(), signature='s'), - 'TargetHandle': lambda: dbus.UInt32(self._handle), + 'TargetHandle': lambda: dbus.UInt32(self._handle.get_id()), 'TargetHandleType': lambda: dbus.UInt32(self._get_handle_type()), - 'TargetID': lambda: dbus.String(self._get_target_id())}) + 'TargetID': lambda: dbus.String(self._get_target_id()), + 'Requested': lambda: self._requested}) + + self._add_immutables({ + 'ChannelType': CHANNEL_INTERFACE, + 'TargetHandle': CHANNEL_INTERFACE, + 'Interfaces': CHANNEL_INTERFACE, + 'TargetHandleType': CHANNEL_INTERFACE, + 'TargetID': CHANNEL_INTERFACE, + 'Requested': CHANNEL_INTERFACE + }) + + def _add_immutables(self, props): + self._immutable_properties.update(props) def _get_handle_type(self): if self._handle: @@ -81,9 +100,17 @@ class Channel(_Channel, DBusProperties): else: return '' + def get_props(self): + props = dict() + for prop, iface in self._immutable_properties.items(): + props[iface + '.' + prop] = \ + self._prop_getters[iface][prop]() + return props + @dbus.service.method(CHANNEL_INTERFACE, in_signature='', out_signature='') def Close(self): self.Closed() + self._chan_manager.remove_channel(self) self._conn.remove_channel(self) @dbus.service.method(CHANNEL_INTERFACE, in_signature='', out_signature='s') @@ -117,14 +144,30 @@ from telepathy._generated.Channel_Type_Contact_List \ class ChannelTypeContactList(Channel, _ChannelTypeContactListIface): __doc__ = _ChannelTypeContactListIface.__doc__ - def __init__(self, connection, handle): + def __init__(self, connection, manager, props): """ Initialise the channel. Parameters: connection - the parent Telepathy Connection object """ - Channel.__init__(self, connection, CHANNEL_TYPE_CONTACT_LIST, handle) + Channel.__init__(self, connection, manager, props) + + +from telepathy._generated.Channel_Type_File_Transfer \ + import ChannelTypeFileTransfer as _ChannelTypeFileTransferIface + +class ChannelTypeFileTransfer(Channel, _ChannelTypeFileTransferIface): + __doc__ = _ChannelTypeFileTransferIface.__doc__ + + def __init__(self, connection, manager, props): + """ + Initialise the channel. + + Parameters: + connection - the parent Telepathy Connection object + """ + Channel.__init__(self, connection, manager, props) from telepathy._generated.Channel_Type_File_Transfer \ @@ -149,14 +192,14 @@ from telepathy._generated.Channel_Type_Streamed_Media \ class ChannelTypeStreamedMedia(Channel, _ChannelTypeStreamedMediaIface): __doc__ = _ChannelTypeStreamedMediaIface.__doc__ - def __init__(self, connection, handle): + def __init__(self, connection, manager, props): """ Initialise the channel. Parameters: connection - the parent Telepathy Connection object """ - Channel.__init__(self, connection, CHANNEL_TYPE_STREAMED_MEDIA, handle) + Channel.__init__(self, connection, manager, props) from telepathy._generated.Channel_Type_Room_List \ @@ -165,17 +208,19 @@ from telepathy._generated.Channel_Type_Room_List \ class ChannelTypeRoomList(Channel, _ChannelTypeRoomListIface): __doc__ = _ChannelTypeRoomListIface.__doc__ - def __init__(self, connection): + def __init__(self, connection, manager, props): """ Initialise the channel. Parameters: connection - the parent Telepathy Connection object """ - Channel.__init__(self, connection, CHANNEL_TYPE_ROOM_LIST, 0) + Channel.__init__(self, connection, manager, props) self._listing_rooms = False self._rooms = {} + self._add_immutables(self, {'Server': CHANNEL_TYPE_ROOM_LIST}) + @dbus.service.method(CHANNEL_TYPE_ROOM_LIST, in_signature='', out_signature='b') def GetListingRooms(self): return self._listing_rooms @@ -191,14 +236,14 @@ from telepathy._generated.Channel_Type_Text \ class ChannelTypeText(Channel, _ChannelTypeTextIface): __doc__ = _ChannelTypeTextIface.__doc__ - def __init__(self, connection, handle): + def __init__(self, connection, manager, props): """ Initialise the channel. Parameters: connection - the parent Telepathy Connection object """ - Channel.__init__(self, connection, CHANNEL_TYPE_TEXT, handle) + Channel.__init__(self, connection, manager, props) self._pending_messages = {} self._message_types = [CHANNEL_TEXT_MESSAGE_TYPE_NORMAL] diff --git a/src/server/channelmanager.py b/src/server/channelmanager.py new file mode 100644 index 0000000..aacde30 --- /dev/null +++ b/src/server/channelmanager.py @@ -0,0 +1,99 @@ +# telepathy-python - Base classes defining the interfaces of the Telepathy framework +# +# Copyright (C) 2009 Collabora Limited +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from telepathy.errors import NotImplemented + +from telepathy.interfaces import (CHANNEL_INTERFACE, + CHANNEL_TYPE_CONTACT_LIST, + CHANNEL_TYPE_TEXT) + +class ChannelManager(object): + + def __init__(self, connection): + self._conn = connection + + self._requestable_channel_classes = dict() + self._channels = dict() + self._fixed_properties = dict() + self._available_properties = dict() + + def close(self): + for channel_type in self._requestable_channel_classes: + for channel in self._channels[channel_type].values(): + if channel._type == CHANNEL_TYPE_CONTACT_LIST: + channel.remove_from_connection() + else: + channel.Close() + + def remove_channel(self, channel): + for channel_type in self._requestable_channel_classes: + for handle, chan in self._channels[channel_type].items(): + if channel == chan: + del self._channels[channel_type][handle] + + def _get_type_requested_handle(self, props): + type = props[CHANNEL_INTERFACE + '.ChannelType'] + requested = props[CHANNEL_INTERFACE + '.Requested'] + target_handle = props[CHANNEL_INTERFACE + '.TargetHandle'] + target_handle_type = props[CHANNEL_INTERFACE + '.TargetHandleType'] + + handle = self._conn._handles[target_handle_type, target_handle] + + return (type, requested, handle) + + def channel_exists(self, props): + type, _, handle = self._get_type_requested_handle(props) + + if type in self._channels: + if handle in self._channels[type]: + return True + + return False + + def channel_for_props(self, props, signal=True, **args): + type, _, handle = self._get_type_requested_handle(props) + + if type not in self._requestable_channel_classes: + raise NotImplemented('Unknown channel type "%s"' % type) + + if self.channel_exists(props): + return self._channels[type][handle] + + channel = self._requestable_channel_classes[type]( + props, **args) + + self._conn.add_channels([channel], signal=signal) + self._channels[type][handle] = channel + + return channel + + def _implement_channel_class(self, type, make_channel, fixed, available): + self._requestable_channel_classes[type] = make_channel + self._channels.setdefault(type, {}) + + self._fixed_properties[type] = fixed + self._available_properties[type] = available + + def get_requestable_channel_classes(self): + retval = [] + + for channel_type in self._requestable_channel_classes: + retval.append((self._fixed_properties[channel_type], + self._available_properties[channel_type])) + + return retval diff --git a/src/server/conn.py b/src/server/conn.py index 031e2db..0ef2206 100644 --- a/src/server/conn.py +++ b/src/server/conn.py @@ -26,15 +26,19 @@ import weakref from telepathy.constants import (CONNECTION_STATUS_DISCONNECTED, CONNECTION_STATUS_CONNECTED, HANDLE_TYPE_NONE, + HANDLE_TYPE_CONTACT, LAST_HANDLE_TYPE) from telepathy.errors import (Disconnected, InvalidArgument, - InvalidHandle, NotAvailable) + InvalidHandle, NotAvailable, + NotImplemented) from telepathy.interfaces import (CONN_INTERFACE, CONN_INTERFACE_ALIASING, CONN_INTERFACE_AVATARS, CONN_INTERFACE_CAPABILITIES, CONN_INTERFACE_PRESENCE, - CONN_INTERFACE_RENAMING) + CONN_INTERFACE_RENAMING, + CONNECTION_INTERFACE_REQUESTS, + CHANNEL_INTERFACE) from telepathy.server.handle import Handle from telepathy.server.properties import DBusProperties @@ -179,13 +183,37 @@ class Connection(_Connection, DBusProperties): self._next_channel_id += 1 return ret - def add_channel(self, channel, handle, suppress_handler): - """ add a new channel and signal its creation""" - self._channels.add(channel) - self.NewChannel(channel._object_path, channel._type, handle.get_type(), handle.get_id(), suppress_handler) + def add_channels(self, channels, signal=True): + """ add new channels and signal their creation""" + signal_channels = set() + + for channel in channels: + if channel not in self._channels: + self._channels.add(channel) + signal_channels.add(channel) + + if signal: + self.signal_new_channels(signal_channels) + + def signal_new_channels(self, channels): + self.NewChannels([(channel._object_path, channel.get_props()) + for channel in channels]) + + # Now NewChannel needs to be called for each new channel. + for channel in channels: + props = channel.get_props() + + target_handle_type = props[CHANNEL_INTERFACE + '.TargetHandleType'] + target_handle = props[CHANNEL_INTERFACE + '.TargetHandle'] + suppress_handler = props[CHANNEL_INTERFACE + '.Requested'] + + self.NewChannel(channel._object_path, channel._type, + target_handle_type, target_handle, + suppress_handler) def remove_channel(self, channel): self._channels.remove(channel) + self.ChannelClosed(channel._object_path) @dbus.service.method(CONN_INTERFACE, in_signature='', out_signature='as') def GetInterfaces(self): @@ -281,7 +309,7 @@ class Connection(_Connection, DBusProperties): self.check_connected() ret = [] for channel in self._channels: - chan = (channel._object_path, channel._type, channel._handle.get_type(), channel._handle) + chan = (channel._object_path, channel._type, channel._get_handle_type(), channel._handle) ret.append(chan) return ret @@ -308,13 +336,15 @@ class ConnectionInterfaceCapabilities(_ConnectionInterfaceCapabilities): @dbus.service.method(CONN_INTERFACE_CAPABILITIES, in_signature='au', out_signature='a(usuu)') def GetCapabilities(self, handles): ret = [] + handle_type = HANDLE_TYPE_CONTACT for handle in handles: - if (handle != 0 and handle not in self._handles): + if (handle != 0 and (handle_type, handle) not in self._handles): raise InvalidHandle elif handle in self._caps: - theirs = self._caps[handle] - for type in theirs: - ret.append([handle, type, theirs[0], theirs[1]]) + types = self._caps[handle] + for ctype, specs in types.items(): + ret.append([handle, ctype, specs[0], specs[1]]) + return ret @dbus.service.signal(CONN_INTERFACE_CAPABILITIES, signature='a(usuuuu)') def CapabilitiesChanged(self, caps): @@ -345,11 +375,171 @@ class ConnectionInterfaceCapabilities(_ConnectionInterfaceCapabilities): caps.append((self._self_handle, ctype, gen_old, gen_new, spec_old, spec_new)) - self.CapabilitiesChanged(self._self_handle, caps) + self.CapabilitiesChanged(caps) # return all my capabilities return [(ctype, caps[1]) for ctype, caps in my_caps.iteritems()] +from telepathy._generated.Connection_Interface_Requests \ + import ConnectionInterfaceRequests \ + as _ConnectionInterfaceRequests + +class ConnectionInterfaceRequests( + _ConnectionInterfaceRequests, + DBusProperties): + + def __init__(self): + _ConnectionInterfaceRequests.__init__(self) + DBusProperties.__init__(self) + + self._implement_property_get(CONNECTION_INTERFACE_REQUESTS, + {'Channels': lambda: dbus.Array(self._get_channels(), + signature='(oa{sv})'), + 'RequestableChannelClasses': lambda: dbus.Array( + self._channel_manager.get_requestable_channel_classes(), + signature='(a{sv}as)')}) + + def _get_channels(self): + return [(c._object_path, c.get_props()) for c in self._channels] + + def _check_basic_properties(self, props): + # ChannelType must be present and must be a string. + if CHANNEL_INTERFACE + '.ChannelType' not in props or \ + not isinstance(props[CHANNEL_INTERFACE + '.ChannelType'], + dbus.String): + raise InvalidArgument('ChannelType is required') + + def check_valid_type_if_exists(prop, fun): + p = CHANNEL_INTERFACE + '.' + prop + if p in props and not fun(props[p]): + raise InvalidArgument('Invalid %s' % prop) + + # Allow TargetHandleType to be missing, but not to be otherwise broken. + check_valid_type_if_exists('TargetHandleType', + lambda p: p > 0 and p < (2**32)-1) + + # Allow TargetType to be missing, but not to be otherwise broken. + check_valid_type_if_exists('TargetHandle', + lambda p: p > 0 and p < (2**32)-1) + if props.get(CHANNEL_INTERFACE + '.TargetHandle') == 0: + raise InvalidArgument("TargetHandle may not be 0") + + # Allow TargetID to be missing, but not to be otherwise broken. + check_valid_type_if_exists('TargetID', + lambda p: isinstance(p, dbus.String)) + + # Disallow InitiatorHandle, InitiatorID and Requested. + check_valid_type_if_exists('InitiatorHandle', lambda p: False) + check_valid_type_if_exists('InitiatorID', lambda p: False) + check_valid_type_if_exists('Requested', lambda p: False) + + type = props[CHANNEL_INTERFACE + '.ChannelType'] + handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType', + HANDLE_TYPE_NONE) + handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', 0) + + return (type, handle_type, handle) + + def _validate_handle(self, props): + target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType', + HANDLE_TYPE_NONE) + target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None) + target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None) + + # Handle type 0 cannot have a handle. + if target_handle_type == HANDLE_TYPE_NONE and target_handle != None: + raise InvalidArgument('When TargetHandleType is NONE, ' + + 'TargetHandle must be omitted') + + # Handle type 0 cannot have a TargetID. + if target_handle_type == HANDLE_TYPE_NONE and target_id != None: + raise InvalidArgument('When TargetHandleType is NONE, TargetID ' + + 'must be omitted') + + if target_handle_type != HANDLE_TYPE_NONE: + if target_handle == None and target_id == None: + raise InvalidArgument('When TargetHandleType is not NONE, ' + + 'either TargetHandle or TargetID must also be given') + + if target_handle != None and target_id != None: + raise InvalidArgument('TargetHandle and TargetID must not ' + + 'both be given') + + self.check_handle_type(target_handle_type) + + + def _alter_properties(self, props): + target_handle_type = props.get(CHANNEL_INTERFACE + '.TargetHandleType', + HANDLE_TYPE_NONE) + target_handle = props.get(CHANNEL_INTERFACE + '.TargetHandle', None) + target_id = props.get(CHANNEL_INTERFACE + '.TargetID', None) + + altered_properties = props.copy() + + if target_handle_type != HANDLE_TYPE_NONE: + if target_handle == None: + # Turn TargetID into TargetHandle. + for handle in self._handles.itervalues(): + if handle.get_name() == target_id and handle.get_type() == target_handle_type: + target_handle = handle.get_id() + if not target_handle: + raise InvalidHandle('TargetID %s not valid for type %d' % + target_id, target_handle_type) + + altered_properties[CHANNEL_INTERFACE + '.TargetHandle'] = \ + target_handle + del altered_properties[CHANNEL_INTERFACE + '.TargetID'] + else: + # Check the supplied TargetHandle is valid + self.check_handle(target_handle_type, target_handle) + + altered_properties[CHANNEL_INTERFACE + '.Requested'] = True + + return altered_properties + + @dbus.service.method(CONNECTION_INTERFACE_REQUESTS, + in_signature='a{sv}', out_signature='oa{sv}', + async_callbacks=('_success', '_error')) + def CreateChannel(self, request, _success, _error): + type, handle_type, handle = self._check_basic_properties(request) + self._validate_handle(request) + props = self._alter_properties(request) + + channel = self._channel_manager.channel_for_props(props, signal=False) + + # Remove mutable properties + todel = [] + for prop in props: + iface, name = prop.rsplit('.', 1) # a bit of a hack + if name in channel._immutable_properties: + if channel._immutable_properties[name] != iface: + todel.append(prop) + else: + todel.append(prop) + + for p in todel: + del props[p] + + _success(channel._object_path, props) + + # CreateChannel MUST return *before* NewChannels is emitted. + self.signal_new_channels([channel]) + + @dbus.service.method(CONNECTION_INTERFACE_REQUESTS, + in_signature='a{sv}', out_signature='boa{sv}', + async_callbacks=('_success', '_error')) + def EnsureChannel(self, request, _success, _error): + type, handle_type, handle = self._check_basic_properties(request) + self._validate_handle(request) + props = self._alter_properties(request) + + yours = not self._channel_manager.channel_exists(props) + + channel = self._channel_manager.channel_for_props(props, signal=False) + + _success(yours, channel._object_path, props) + + self.signal_new_channels([channel]) from telepathy._generated.Connection_Interface_Presence \ import ConnectionInterfacePresence @@ -359,6 +549,3 @@ from telepathy._generated.Connection_Interface_Simple_Presence \ from telepathy._generated.Connection_Interface_Contacts \ import ConnectionInterfaceContacts - -from telepathy._generated.Connection_Interface_Requests \ - import ConnectionInterfaceRequests |