[telepathy-ashes/master] Refactoring to enable reconnecting on disconnect.
David Laban
david.laban at collabora.co.uk
Thu Oct 22 10:04:22 PDT 2009
All connection objects now take a parent argument to their constructor,
which gets connection_disconnected() called back on it.
This pattern of keeping a ref to your parent is used by channels too.
I also use it as a mechanism for passing config variables around.
The refs should probably be weakrefs, thinking about it. Sorting out
garbage collection and suchlike is next on my todo.
---
ashes/tools/bases.py | 23 +++----
ashes/tools/dispatchers.py | 9 ++-
ashes/tools/echo_bot.py | 159 +++++++++++++++++++++++++++++++-------------
3 files changed, 127 insertions(+), 64 deletions(-)
diff --git a/ashes/tools/bases.py b/ashes/tools/bases.py
index 5b496c3..e291e34 100644
--- a/ashes/tools/bases.py
+++ b/ashes/tools/bases.py
@@ -97,20 +97,14 @@ class ConnectionListener(ObjectListener):
This is what finish_setup is for.
"""
CONNECTION = telepathy.interfaces.CONNECTION
- def __init__(self, connection, contact_regexp='.*'):
- """
- contact_regexp is used in derived classes to selectively echo only specific users.
- """
- self.init(connection, contact_regexp)
-
- def init(self, connection, contact_regexp='.*'):
+ def __init__(self, parent, connection):
print "Listening to connection"
ObjectListener.__init__(self, connection)
# I could use self.dbus_object everywhere, but
- # self.connection is clearer.
+ # self.connection is clearer, so I will duplicate it here.
self.connection = connection
- self.contact_regexp = contact_regexp
- self.channel_extra_args = [contact_regexp]
+ self.parent = parent
+ self.config = parent.config
self.connection.Connect()
connection.call_when_ready(self.finish_setup)
@@ -134,11 +128,10 @@ class ConnectionListener(ObjectListener):
self.connect_to_signal(iface_name, signal_name)
def StatusChanged(self, status, reason=0):
- if status == 2: #Disconnected
- print "Disconnected. Exiting."
- gobject.idle_add(quit)
+ if status == 2: # Disconnected.
+ self.parent.connection_disconnected(self, status, reason)
- #FIXME: this should really be auto-generated from the spec globally.
+ # FIXME: this should really be auto-generated from the spec globally.
_signal_names = {
'org.freedesktop.Telepathy.Connection':
['SelfHandleChanged', 'NewChannel', 'ConnectionError', 'StatusChanged'],
@@ -232,6 +225,8 @@ class ChannelListener(ObjectListener):
ObjectListener.__init__(self, channel)
self.connection = connection
self.channel = channel
+ self.parent = connection
+ self.config = connection.config
self.properties = properties
channel_type = properties['org.freedesktop.Telepathy.Channel.ChannelType']
self._register_callbacks()
diff --git a/ashes/tools/dispatchers.py b/ashes/tools/dispatchers.py
index e9878bb..ce7a8dc 100644
--- a/ashes/tools/dispatchers.py
+++ b/ashes/tools/dispatchers.py
@@ -22,6 +22,8 @@ class ChannelDispatcher(ConnectionListener):
Add a mixin with a list/tuple called __handler_classes which contains
all of the channel handler classes you want to include.
+
+ Note that this could be implemented using Client.Approver and Client.Handler
"""
REQUESTS = telepathy.interfaces.CONNECTION_INTERFACE_REQUESTS
CAPS = telepathy.interfaces.CONNECTION_INTERFACE_CAPABILITIES
@@ -137,16 +139,17 @@ class ChannelDispatcher(ConnectionListener):
id = properties['org.freedesktop.Telepathy.Channel.TargetID']
# FIXME. This optimisation is probably not very general.
# Don't be putting it in any libraries.
+ contact_regexp = self.config.get("contact_regexp", ".*")
if handle_type == telepathy.HANDLE_TYPE_CONTACT:
- if re.match(self.contact_regexp, id):
+ if re.match(contact_regexp, id):
#assert not requested, "We shouldn't be creating any new channels."
if requested:
print "Outgoing channel",
else:
print "Incoming channel",
- print id, 'matching', self.contact_regexp
+ print id, 'matching', contact_regexp
else:
- print id, 'does not match', self.contact_regexp
+ print id, 'does not match', contact_regexp
return
if channel in self.channels:
print "Channel already handled:", channel.object_path
diff --git a/ashes/tools/echo_bot.py b/ashes/tools/echo_bot.py
index 5594714..83810d7 100644
--- a/ashes/tools/echo_bot.py
+++ b/ashes/tools/echo_bot.py
@@ -14,19 +14,15 @@ from helper_functions import (get_connections, get_property,
from account import connection_from_file
from bases import ObjectListener, ConnectionListener, ChannelListener
-from dispatchers import ChannelDispatcher, connections
+from dispatchers import ChannelDispatcher#, connections
from media_echoer import MediaChannelEchoer, IceEchoer
from presence import PresenceEchoer, Onlineifier
from groups import ContactSubscriber
from text import TextChannelEchoer
from commands import CommandExecutor
from file_transfer import FileTransferEchoer
-#TODO: wrap everything to 80 chars so that I can hack on my eeepc.
-
-
-#@shiny.debug_class
class EchoBot( ContactSubscriber,
ChannelDispatcher,
Onlineifier,
@@ -39,62 +35,131 @@ class EchoBot( ContactSubscriber,
__channel_handler_classes = [MediaChannelEchoer,
CommandExecutor, FileTransferEchoer]
-
-
-def run_echo_service(conn, contact_regexp):
- """
- Runs the echo service unless it's already running.
+class EchoBotRunner(object):
"""
- if conn in connections:
- #note: requires patch to dbus-python which implements __eq__
- print conn.service_name, 'already handled'
- else:
- print green('Running Echo Service on:'), conn.service_name
- bot = EchoBot(conn, contact_regexp)
- assert bot == conn # so that 'if conn in connections:' works.
- connections.append(bot)
- return connections
-
+ Runs the echo service, and restarts it if it dies. This is in theory capable
+ of running multiple echo services in the same process, but I prefer to run
+ things separately.
-def main(connection_regexp='.*echo123.*',
- contact_regexp='.*',
- account_file=''):
- """
- This program will look on the bus for a connection object which matches
- connection_regexp.
- TODO: add an option for data rates, to select codecs.
+ In theory, this could be done using AccountManager, but for some reason,
+ I decided not to, and to use .account files instead.
"""
- mainloop = gobject.MainLoop(is_running=True)
- dmainloop = dbus.mainloop.glib.DBusGMainLoop()
- dbus.set_default_main_loop(dmainloop)
- regexp = re.compile(connection_regexp)
-
- if account_file:
+ # A list of all connections, including ones we don't own.
+ connections = []
+
+ def __init__(self, **config):
+ """
+ Config is an arbitrary dict of configuration options that is passed
+ down to connection handlers and channel handlers. This is so that you
+ can extend connection or channel subclasses, and not have to find your
+ own mechanism for configuring them.
+ """
+ self.owned_connections = [] # Connections we own.
+ self.account_files = [] # Account files used to [re]create connections.
+ self.config = config
+ self.mainloop = gobject.MainLoop(is_running=True)
+ dmainloop = dbus.mainloop.glib.DBusGMainLoop()
+ dbus.set_default_main_loop(dmainloop)
+
+ def echo_from_file(self, account_file):
+ """Runs the echo bot on a connection created by reading account_file."""
print 'Creating connection'
connection = connection_from_file(account_file)
- run_echo_service(connection, contact_regexp)
- else:
+ bot = self.run_echo_service(connection)
+ self.owned_connections.append(bot)
+ self.account_files.append(account_file)
+
+ def echo_from_bus(self, connection_regexp):
connections_ = get_connections()
for connection in connections_:
if re.match(connection_regexp, connection.service_name):
- run_echo_service(connection, contact_regexp)
+ self.run_echo_service(connection)
else:
print connection.service_name, "does not match", connection_regexp
- while mainloop.is_running():
- print 'running'
+ def run_echo_service(self, conn):
+ """
+ Runs the echo service unless it's already running.
+ """
+ if conn in self.connections:
+ print conn.service_name, 'already handled'
+ else:
+ print green('Running Echo Service on:'), conn.service_name
+ bot = EchoBot(self, conn)
+ assert bot == conn # so that 'if conn in connections:' works.
+ self.connections.append(bot)
+ return bot
+
+ def get_account_file_for(self, conn):
+ index = self.owned_connections.index(conn)
+ return self.account_files[index]
+
+ def remove_connection(self, conn, prevent_quit=False):
+ """
+ Removes the connection from all places we reference it.
+ """
try:
- mainloop.run()
- except KeyboardInterrupt:
+ index = self.owned_connections.index(conn)
+ self.owned_connections.pop(index)
+ self.account_files.pop(index)
+ except ValueError:
+ pass # We don't own this connection.
+ self.connections.remove(conn)
+ if len(self.connections) == 0 and not prevent_quit:
+ print "No longer handling any connections. Exiting."
gobject.idle_add(quit)
- if account_file:
- # If we set it up we should probably take it down.
- assert connections
- assert len(connections) == 1
- connections[0][telepathy.CONN_INTERFACE].Disconnect()
- account_file = None
- print 'done\n\n'
+
+ def reconnect(self, conn):
+ """
+ Only works if we were the ones who started the service in the first
+ place.
+ """
+ account_file = self.get_account_file_for(conn)
+ self.remove_connection(conn, prevent_quit=True)
+ self.echo_from_file(account_file)
+
+ def connection_disconnected(self, conn, status, reason):
+ """
+ This is called whenever a connection gets disconnected.
+ """
+ if reason == 2: # network error.
+ print "Network error. Re-connecting"
+ self.reconnect(conn)
+ elif reason == 5: # name in use.
+ print "The service is being run elsewhere. Removing."
+ self.remove_connection(conn)
+ else:
+ print "Disconnected because %s. Removing." % reason
+ self.remove_connection(conn)
+
+ def run(self):
+ while self.mainloop.is_running():
+ print 'running'
+ try:
+ self.mainloop.run()
+ except KeyboardInterrupt:
+ gobject.idle_add(quit, 1)
+ print "Disconnecting %s connections." % len(self.owned_connections)
+ for connection in self.owned_connections:
+ connection[telepathy.CONN_INTERFACE].Disconnect()
+ print 'done\n\n'
+
+
+def main(connection_regexp='.*echo123.*',
+ contact_regexp='.*',
+ account_file=''):
+ """
+ If account file is provided, connects to the specified account, and runs
+ the echo service on it. Otherwise looks on the bus for connections matching
+ connection_regexp.
+ """
+ runner = EchoBotRunner(contact_regexp=contact_regexp)
+ if account_file:
+ runner.echo_from_file(account_file)
+ else:
+ runner.echo_from_bus(connection_regexp)
+ runner.run()
--
1.5.6.5
More information about the telepathy-commits
mailing list