[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