[telepathy-ashes/master] General clean-up and streamlining.

David Laban david.laban at collabora.co.uk
Thu Oct 1 10:59:04 PDT 2009


Cleaned up how channel handlers are registered, deleted a lot of cruft
and made media echoer use the identity pipeline.
---
 ashes/tools/bases.py        |   19 +++++++-
 ashes/tools/dispatchers.py  |   74 ++++++++++++++++++++++------------
 ashes/tools/echo_bot.py     |   14 +-----
 ashes/tools/groups.py       |    4 ++
 ashes/tools/media_echoer.py |   95 +++++++++----------------------------------
 ashes/tools/text.py         |    3 +
 6 files changed, 92 insertions(+), 117 deletions(-)

diff --git a/ashes/tools/bases.py b/ashes/tools/bases.py
index cf75dbc..e367702 100644
--- a/ashes/tools/bases.py
+++ b/ashes/tools/bases.py
@@ -76,6 +76,7 @@ class ConnectionListener(ObjectListener):
     classes, push it up to the parent class.
 
     WARNING: connections are not guaranteed to be ready when this is initialised.
+    This is what finish_setup is for.
     """
     CONNECTION = telepathy.interfaces.CONNECTION
     def __init__(self, connection, contact_regexp='.*'):
@@ -102,6 +103,7 @@ class ConnectionListener(ObjectListener):
         print 'finishing setup'
         self._register_callbacks(self.connection)
         self.connection.Connect()
+
     def _register_callbacks(self, conn):
         """
         Registers callbacks for all signals provided by the connection.
@@ -153,10 +155,18 @@ class ConnectionListener(ObjectListener):
 class ChannelListener(ObjectListener):
     """
     The base class for all things which listen/respond to channels.
-    Remember to also use the ChannelDispatcher.handler_for decorator
-    so that you actually get called back.
-    The channel object must be ready for use when the constructor is called.
+    The method for registering channel handlers with subclasses of
+    ChannelDispatcher is currently in flux. In the past, you used the
+    ChannelDispatcher.handler_for decorator, which is made of bong.
+    What you now need to do is ensure that your ChannelListener subclass has
+    non-None values for channel_type, handle_type, and capabilities_flag, and
+    then ensure that your ChannelDispatcher subclass/mixin contains a dict.
+
+    The channel object will be ready for use when the constructor is called.
     """
+    channel_type = None
+    handle_type = None
+    capabilities_flag = None
 
     _signal_names = {
         'org.freedesktop.Telepathy.Channel.Type.Text':
@@ -166,6 +176,9 @@ class ChannelListener(ObjectListener):
             'StreamRemoved', 'StreamStateChanged',],
         'org.freedesktop.Telepathy.Channel.Interface.Destroyable':
             [],
+        'org.freedesktop.Telepathy.Channel.Type.FileTransfer':
+            ['FileTransferStateChanged', 'TransferredBytesChanged',
+            'InitialOffsetDefined'],
         'org.freedesktop.Telepathy.Channel.Type.ContactList':
             [],
         'org.freedesktop.Telepathy.Channel.Interface.Messages':
diff --git a/ashes/tools/dispatchers.py b/ashes/tools/dispatchers.py
index de1395c..8f14f4c 100644
--- a/ashes/tools/dispatchers.py
+++ b/ashes/tools/dispatchers.py
@@ -2,7 +2,7 @@
 import re
 
 import telepathy
-from bases import ConnectionListener
+from bases import ConnectionListener, ChannelListener
 
 from helper_functions import (get_connections, get_property,
         green, red, printer, rpartial)
@@ -20,45 +20,67 @@ class ChannelDispatcher(ConnectionListener):
     Listens for Requests.NewChannels and Connection.NewChannel, creates a
     Channel object, and passes it to the appropriate handler class
     (as registered using @ChannelDispatcher.handler_for())
-    TODO: should probably do something with ListChannels too.
+
+    Add a mixin with a list/tuple called __handler_classes which contains
+    all of the channel handler classes you want to include.
     """
     REQUESTS = telepathy.interfaces.CONNECTION_INTERFACE_REQUESTS
+    CAPS = telepathy.interfaces.CONNECTION_INTERFACE_CAPABILITIES
 
     _handler_classes = {}
 
-    @classmethod
-    def handler_for(cls, channel_type, handle_type):
+    def _collect_handler_classes(self):
         """
-        A decorator factory for registering classes with the dispatcher.
-
-        @ChannelDispatcher.handler_for(channel_type, handle_type)
-        class SomeChannelListener(ChannelListener):
-            def __init__(self, connection, channel, properties):
-                ...
-
-        This causes an instance of your class to be created whenever a channel
-        of the appropriate type is created. (It is guaranteed to be "ready"
-        when you are called. You can chain this decorator in
-        the usual way to register interest in many handle types etc.
+        This function collects handler classes registered to subclasses/
+        sibling mixin classes of ChannelDispatcher, and adds them to
+        self._handler_classes.
 
-
-        #TODO: advertise handlers using Capabilities.AdvertiseCapabilities
-        based upon what we can actually handle.
+        FIXME: This is made of magic and bong. It should really be better
+        designed.
         """
-        def decorate(class_):
-            cls._handler_classes[channel_type, handle_type] = class_
-            return class_
-        return decorate
+        handler_classes = {}
+        handler_classes.update(self._handler_classes) # For backwards compat.
+        useful_names = [n for n in dir(self) if n.endswith("handler_classes")]
+        for name in useful_names:
+            print name
+            # TODO: Might be worth having some pattern to match name against.
+            classes = getattr(self, name)
+            if isinstance(classes, dict):
+                for key, handler_class in classes.items():
+                    if issubclass(handler_class, ChannelListener):
+                        handler_classes[key] = value
+            elif isinstance(classes, (tuple, list)):
+                for handler_class in classes:
+                    if issubclass(handler_class, ChannelListener):
+                        key = (handler_class.channel_type,
+                                handler_class.handle_type)
+                        handler_classes[key] = handler_class
+        # handler_classes is now an instance variable, as this assignment
+        # shadows the class variable.
+        self._handler_classes = handler_classes
+        return handler_classes
 
     def finish_setup(self, *conn):
         """
         Sets up super-classes recursively, then sets capabilities
-        FIXME: do this based on _handler_classes rather than hardcoded.
+        FIXME: do caps based on _handler_classes rather than hardcoded.
         """
-        print "advertising streamed media caps"
         super(ChannelDispatcher, self).finish_setup()
-        self.connection[telepathy.CONN_INTERFACE_CAPABILITIES].AdvertiseCapabilities(
-            [(telepathy.CHANNEL_TYPE_STREAMED_MEDIA, 0xff)], [])
+        handler_classes = self._collect_handler_classes()
+        caps = []
+        for (chantype, handletype), handler_class in handler_classes.items():
+            if (handler_class.channel_type != None and
+                    handler_class.handle_type != None and
+                    handler_class.capabilities_flag != None):
+                caps.append((handler_class.channel_type,
+                        handler_class.capabilities_flag))
+                assert chantype == handler_class.channel_type
+                assert handletype == handler_class.handle_type
+            else:
+                print "WARNING: %s needs to override channel_type handle_type"\
+                    "and capabilities_flag. It doesn't." % handler_class
+
+        self.connection[self.CAPS].AdvertiseCapabilities(caps, [])
         get_property(self.connection, telepathy.CONNECTION_INTERFACE_REQUESTS,
             'Channels', reply_handler=self.NewChannels, error_handler=printer)
 
diff --git a/ashes/tools/echo_bot.py b/ashes/tools/echo_bot.py
index 079caac..04222b9 100644
--- a/ashes/tools/echo_bot.py
+++ b/ashes/tools/echo_bot.py
@@ -33,24 +33,14 @@ class EchoBot(  PresenceEchoer,
     This class just mixes together functionality from PresenceEchoer and friends 
     into a nice tasty echo bot.
     """
-    pass
-
-# FIXME: this should probably be done on echo bot somehow.
-# In case multiple things inheret from ChannelDispatcher.
-# This mess is because I originally did it with class decorators (which don't work in py2.5)
-ChannelDispatcher.handler_for('org.freedesktop.Telepathy.Channel.Type.ContactList',
-                                telepathy.HANDLE_TYPE_LIST)(ContactAcceptor)
-ChannelDispatcher.handler_for('org.freedesktop.Telepathy.Channel.Type.Text',
-                                telepathy.HANDLE_TYPE_CONTACT)(TextChannelEchoer)
-ChannelDispatcher.handler_for('org.freedesktop.Telepathy.Channel.Type.StreamedMedia',
-                                telepathy.HANDLE_TYPE_CONTACT)(MediaChannelEchoer)
+    # Using python's name mangling to avoid name collisions.
+    __handler_classes = MediaChannelEchoer, ContactAcceptor, TextChannelEchoer
 
 
 
 def run_echo_service(conn, contact_regexp):
     """
     Runs the echo service unless it's already running.
-    Also helpfully appends it to connections and returns that.
     """
     if conn in connections:
         #note: requires patch to dbus-python which implements __eq__
diff --git a/ashes/tools/groups.py b/ashes/tools/groups.py
index 5a090df..4053912 100644
--- a/ashes/tools/groups.py
+++ b/ashes/tools/groups.py
@@ -6,6 +6,10 @@ from helper_functions import get_property, printer, green
 
 class ContactAcceptor(ChannelListener):
     """Automatically accepts local_pending members of groups."""
+
+    channel_type = 'org.freedesktop.Telepathy.Channel.Type.ContactList'
+    handle_type = telepathy.HANDLE_TYPE_LIST
+
     def __init__(self, connection, channel, properties):
         ChannelListener.__init__(self, connection, channel, properties)
 
diff --git a/ashes/tools/media_echoer.py b/ashes/tools/media_echoer.py
index c6175af..493038b 100644
--- a/ashes/tools/media_echoer.py
+++ b/ashes/tools/media_echoer.py
@@ -24,6 +24,11 @@ class MediaChannelEchoer(ContactAcceptor):
     Listens to a media channel and echoes everything it hears/sees.
     # NOTE: this isn't currently true: it currently just uses testsrc.
     """
+
+    channel_type = 'org.freedesktop.Telepathy.Channel.Type.StreamedMedia'
+    handle_type = telepathy.HANDLE_TYPE_CONTACT
+    capabilities_flag = 0xff
+
     def __init__(self, connection, channel, properties):
         ContactAcceptor.__init__(self, connection, channel, properties)
         print green('Media Channel Handled.')
@@ -59,9 +64,9 @@ class MediaChannelEchoer(ContactAcceptor):
         print green("Accepting, (and throwing away) incoming audio/video.")
         type = stream.get_property ("media-type")
         if type == farsight.MEDIA_TYPE_AUDIO:
-            pipe = gst.parse_bin_from_description("audioconvert ! audioresample ! audioconvert", True)
+            pipe = gst.parse_bin_from_description("identity", True)
         elif type == farsight.MEDIA_TYPE_VIDEO:
-            pipe = gst.parse_bin_from_description("ffmpegcolorspace ! videoscale", True)
+            pipe = gst.parse_bin_from_description("identity", True)
 
         sinkpad = stream.get_property("sink-pad")
 
@@ -76,72 +81,14 @@ class MediaChannelEchoer(ContactAcceptor):
         self.src_pad_added to handle incoming media pads.
         """
         stream.connect("src-pad-added", self.src_pad_added)
-        print green('Sending test sound/video')
-
-        return # Skip the rest of this.
-        sinkpad = stream.get_property("sink-pad")
-
-        type = stream.get_property ("media-type")
-
-        if type == farsight.MEDIA_TYPE_AUDIO:
-            src = gst.element_factory_make("audiotestsrc")
-            src.set_property("is-live", True)
-        elif type == farsight.MEDIA_TYPE_VIDEO:
-            src = gst.element_factory_make("videotestsrc")
-            src.set_property("is-live", True)
-
-        self.pipeline.add(src)
-        src.get_pad("src").link(sinkpad)
-        src.set_state(gst.STATE_PLAYING)
-
-    def make_video_source(self):
-        bin = gst.Bin()
-        if os.environ.has_key("VIDEO_DEVICE"):
-            source = gst.element_factory_make("v4l2src")
-            source.set_property("device", os.environ["VIDEO_DEVICE"])
-        else:
-            source = gst.element_factory_make("videotestsrc")
-            source.set_property("is-live", True)
-
-        bin.add(source)
-
-        filter = gst.element_factory_make("capsfilter")
-        filter.set_property("caps",
-          gst.Caps("video/x-raw-yuv,width=320,height=240," + \
-            "framerate=(fraction)30/1"))
-        bin.add(filter)
-        source.link(filter)
-
-        rate = gst.element_factory_make("fsvideoanyrate")
-        bin.add(rate)
-        filter.link(rate)
-
-        pad = gst.GhostPad("src", rate.get_pad("src"))
-        bin.add_pad(pad)
-
-        return bin
-
+        print green('Waiting for incoming audio/video.')
 
     def session_created(self, channel, conference, participant):
         """
-        #todo: work out why we add conference to the pipeline.
         """
         print green("Adding conference to the pipeline")
         self.pipeline.add(conference)
         self.pipeline.set_state(gst.STATE_PLAYING)
-        #notifier = farsight.ElementAddedNotifier()
-        #notifier.connect("element-added", self.element_added_cb)
-        #notifier.add(self.pipeline)
-
-    def element_added_cb(self, notifier, bin, element):
-        if element.get_factory().get_name() == "x264enc":
-            element.set_property("byte-stream", True)
-            element.set_property("bframes", 0)
-            element.set_property("b-adapt", False)
-            element.set_property("cabac", False)
-            element.set_property("dct8x8", False)
-        elif element.get_factory().get_name() == "gstrtpbin":
-            element.set_property("latency", 100)
 
 
 
@@ -152,28 +99,24 @@ class MediaChannelEchoer(ContactAcceptor):
         print green("got codec config")
         if media_type == farsight.MEDIA_TYPE_VIDEO:
             codecs = [ farsight.Codec(farsight.CODEC_ID_ANY, "H264",
-                farsight.MEDIA_TYPE_VIDEO, 0) ]
-            if self.conn.GetProtocol() == "sip" :
-                codecs += [ farsight.Codec(farsight.CODEC_ID_DISABLE, "THEORA",
-                                        farsight.MEDIA_TYPE_VIDEO, 0) ]
-            else:
-                codecs += [ farsight.Codec(farsight.CODEC_ID_ANY, "THEORA",
-                                        farsight.MEDIA_TYPE_VIDEO, 0) ]
-            codecs += [
-                farsight.Codec(farsight.CODEC_ID_ANY, "H263",
                                         farsight.MEDIA_TYPE_VIDEO, 0),
-                farsight.Codec(farsight.CODEC_ID_DISABLE, "DV",
+                    farsight.Codec(farsight.CODEC_ID_ANY, "THEORA",
+                                        farsight.MEDIA_TYPE_VIDEO, 0),
+                    farsight.Codec(farsight.CODEC_ID_ANY, "H263",
                                         farsight.MEDIA_TYPE_VIDEO, 0),
-                farsight.Codec(farsight.CODEC_ID_ANY, "JPEG",
+                    farsight.Codec(farsight.CODEC_ID_DISABLE, "DV",
                                         farsight.MEDIA_TYPE_VIDEO, 0),
-                farsight.Codec(farsight.CODEC_ID_ANY, "MPV",
+                    farsight.Codec(farsight.CODEC_ID_ANY, "JPEG",
+                                        farsight.MEDIA_TYPE_VIDEO, 0),
+                    farsight.Codec(farsight.CODEC_ID_ANY, "MPV",
                                         farsight.MEDIA_TYPE_VIDEO, 0),
             ]
-
-            return codecs
         else:
-            return [farsight.Codec(farsight.CODEC_ID_DISABLE, "SIREN",
+            # For some reason, siren doesn't work.
+            codecs = [farsight.Codec(farsight.CODEC_ID_DISABLE, "SIREN",
                                         farsight.MEDIA_TYPE_AUDIO, 0)]
+        #codecs = []
+        return codecs
 
     #def session_created(self, *args):
     #    print green('session_created(%s)' % ', '.join(map(str, args)))
diff --git a/ashes/tools/text.py b/ashes/tools/text.py
index 8ad1a96..05a26c7 100644
--- a/ashes/tools/text.py
+++ b/ashes/tools/text.py
@@ -12,6 +12,9 @@ class TextChannelEchoer(ChannelListener):
     """
     Listens to a text channel and echoes everything with id and timestamp etc.
     """
+    channel_type = 'org.freedesktop.Telepathy.Channel.Type.Text'
+    handle_type = telepathy.HANDLE_TYPE_CONTACT
+
     def __init__(self, connection, channel, properties):
         ChannelListener.__init__(self, connection, channel, properties)
         self.contact_string = properties['org.freedesktop.Telepathy.Channel.TargetID']
-- 
1.5.6.5




More information about the telepathy-commits mailing list