Imitiating buttfly in being explicitly typed
[theonering] / src / tp / channelmanager.py
index 17cef39..d5e2bce 100644 (file)
@@ -1,6 +1,6 @@
 # telepathy-python - Base classes defining the interfaces of the Telepathy framework
 #
-# Copyright (C) 2009 Collabora Limited
+# Copyright (C) 2009-2010 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 along with this library; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
+import warnings
+
 from telepathy.errors import NotImplemented
 
 from telepathy.interfaces import (CHANNEL_INTERFACE,
-                                 CHANNEL_TYPE_CONTACT_LIST,
-                                 CHANNEL_TYPE_TEXT)
+                                  CHANNEL_TYPE_CONTACT_LIST)
 
-class ChannelManager(object):
+from telepathy.constants import HANDLE_TYPE_NONE
+
+from handle import NoneHandle
 
+class ChannelManager(object):
     def __init__(self, connection):
         self._conn = connection
 
-        self._requestable_channel_classes = dict()
+        self._requestable_channels = dict()
         self._channels = dict()
+
         self._fixed_properties = dict()
         self._available_properties = dict()
 
+        self._requestables = 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()
+        """Close channel manager and all the existing channels."""
+        for channel_type in self._requestable_channels:
+            for channels in self._channels[channel_type].values():
+                for channel in channels:
+                    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]
+        "Remove channel from the channel manager"
+        for channel_type in self._requestable_channels:
+            for handle, channels in self._channels[channel_type].items():
+                if channel in channels :
+                    channels.remove(channel)
 
     def _get_type_requested_handle(self, props):
+        """Return the type, request and target handle from the requested
+        properties"""
         type = props[CHANNEL_INTERFACE + '.ChannelType']
         requested = props[CHANNEL_INTERFACE + '.Requested']
-        target_handle = int(props[CHANNEL_INTERFACE + '.TargetHandle'])
-        target_handle_type = int(props[CHANNEL_INTERFACE + '.TargetHandleType'])
 
-        handle = self._conn._handles[target_handle_type, target_handle]
+        target_handle_type = \
+            props.get(CHANNEL_INTERFACE + '.TargetHandleType', HANDLE_TYPE_NONE)
+
+        if target_handle_type == HANDLE_TYPE_NONE:
+            handle = NoneHandle()
+        else:
+            target_handle = props[CHANNEL_INTERFACE + '.TargetHandle']
+            handle = self._conn._handles[target_handle_type, target_handle]
 
         return (type, requested, handle)
 
-    def channel_exists(self, props):
+    def existing_channel(self, props):
+        """ Return a channel corresponding to theses properties if such
+        one exists, otherwhise return None. Default implementation will
+        return the last created channel of the same kind identified by
+        handle and type.
+        Connection Manager should subclass this function
+        to implement more appropriate behaviour. """
+
         type, _, handle = self._get_type_requested_handle(props)
 
         if type in self._channels:
             if handle in self._channels[type]:
-                return True
+                if len(self._channels[type][handle]) > 0:
+                    return self._channels[type][handle][-1]
 
-        return False
+        return None
 
-    def channel_for_props(self, props, signal=True, **args):
+    def channel_exists(self, props):
+        """ Return True if channel exist with theses props, False otherwhise"""
+        return self.existing_channel(props) != None
+
+    def create_channel_for_props(self, props, signal=True, **args):
+        """Create a new channel with theses properties"""
         type, _, handle = self._get_type_requested_handle(props)
 
-        if type not in self._requestable_channel_classes:
+        if type not in self._requestable_channels:
             raise NotImplemented('Unknown channel type "%s"' % type)
 
-        if self.channel_exists(props):
-            return self._channels[type][handle]
-
-        channel = self._requestable_channel_classes[type](
+        channel = self._requestable_channels[type](
             props, **args)
 
         self._conn.add_channels([channel], signal=signal)
-        self._channels[type][handle] = channel
+        if handle.get_type() != HANDLE_TYPE_NONE and type in self._channels:
+            self._channels[type].setdefault(handle, []).append(channel)
 
         return channel
 
+    def channel_for_props(self, props, signal=True, **args):
+        channel = self.existing_channel(props)
+        """Return an existing channel with theses properties if it already
+        exists, otherwhise return a new one"""
+        if channel:
+            return channel
+        else:
+            return self.create_channel_for_props(props, signal, **args)
+
+    # Should use implement_channel_classes instead.
     def _implement_channel_class(self, type, make_channel, fixed, available):
-        self._requestable_channel_classes[type] = make_channel
+        """Implement channel types in the channel manager, and add one channel
+        class that is retrieved in RequestableChannelClasses.
+
+        self.implement_channel_classes should be used instead, as it allows
+        implementing multiple channel classes."""
+        warnings.warn('deprecated in favour of implement_channel_classes',
+            DeprecationWarning)
+
+        self._requestable_channels[type] = make_channel
         self._channels.setdefault(type, {})
 
         self._fixed_properties[type] = fixed
         self._available_properties[type] = available
 
+    # Use this function instead of _implement_channel_class.
+    def implement_channel_classes(self, type, make_channel, classes):
+        """Implement channel types in the channel manager, and add channel
+        classes that are retrieved in RequestableChannelClasses.
+
+          @type: the channel type
+          @make_channel: a function to call which returns a Channel object
+          @classes: a list of channel classes. E.g.
+
+            [ ( { '...ChannelType': '...Text', '...TargetHandleType': HANDLE_TYPE_CONTACT },
+                ['...TargetHandle'] )
+            ]
+
+            See the spec for more documentation on the
+            Requestable_Channel_Class struct.
+        """
+        self._requestable_channels[type] = make_channel
+        self._channels.setdefault(type, {})
+
+        self._requestables[type] = classes
+
     def get_requestable_channel_classes(self):
+        """Return all the channel types that can be created"""
         retval = []
 
-        for channel_type in self._requestable_channel_classes:
-            retval.append((self._fixed_properties[channel_type],
-                self._available_properties[channel_type]))
+        for channel_type in self._requestable_channels:
+            retval.extend(self._requestables.get(channel_type, []))
+
+            # _implement_channel_class was used.
+            if channel_type in self._fixed_properties:
+                retval.append((self._fixed_properties[channel_type],
+                    self._available_properties.get(channel_type, [])))
+
 
         return retval