Rev 5: Implement revision announcing broadcast service. in file:///home/robertc/source/baz/plugins/dbus/trunk/

Robert Collins robertc at robertcollins.net
Tue Feb 6 07:12:43 GMT 2007


------------------------------------------------------------
revno: 5
revision-id: robertc at robertcollins.net-20070206071242-f1htyq1f1utkj77a
parent: robertc at robertcollins.net-20070206050548-fr9um4fo1070o1sw
committer: Robert Collins <robertc at robertcollins.net>
branch nick: trunk
timestamp: Tue 2007-02-06 18:12:42 +1100
message:
  Implement revision announcing broadcast service.
modified:
  TODO                           todo-20070206032729-2b5teqiwctqrfgei-3
  activity.py                    activity.py-20070206034725-q208d0jtkshwu0fx-1
  tests/test_activity.py         test_activity.py-20070206035206-bnhzvtm6m6hpgylz-1
=== modified file 'TODO'
--- a/TODO	2007-02-06 04:16:36 +0000
+++ b/TODO	2007-02-06 07:12:42 +0000
@@ -3,10 +3,10 @@
 
  * advertise_branch sends the message 'announce_revision' when the service is
    running
- * announce_revision when recieved sends the revision to all activity 
-   subscribers
- * can subscribe to activity
  * simple subscriber that sits in a corner and shows revisions
  * dbus service to act as the broadcast point for revisions
+ * .service details for the dbus service.
  * bzr server integration to republish tip revisions from file:/// to public
    URLs when one is available.
+ * debug why raising a dbus returned error skips regular error handling and 
+   jumps to sys.excepthook [wager: crack from the dbus python bindings]

=== modified file 'activity.py'
--- a/activity.py	2007-02-06 04:16:36 +0000
+++ b/activity.py	2007-02-06 07:12:42 +0000
@@ -22,7 +22,9 @@
 about bzr activity: See the class for more detail.
 """
 
-import dbus
+import dbus.mainloop
+import dbus.service
+import gobject
 
 
 class Activity(object):
@@ -52,3 +54,47 @@
         :return: None
         :raises: Nothing should be raised.
         """
+
+    def serve_agency(self):
+        """Serve an advertising agency. 
+
+        This service contains a list of subscribers, but no state per 
+        subscriber, so it should have minimal footprint.
+        """
+        # lazy import to not pollute bzr during startup.
+        import dbus.mainloop.glib
+        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+        mainloop = gobject.MainLoop()
+        mainloop.run()
+
+
+class Broadcast(dbus.service.Object):
+
+    # Dont try to put a '-' in bazaar-vcs here, its NOT dns and dbus considers -
+    # illegal.
+    DBUS_NAME = "org.bazaarvcs.plugins.dbus.Broadcast"
+    DBUS_PATH = "/org/bazaarvcs/plugins/dbus/Broadcast"
+    DBUS_INTERFACE = "org.bazaarvcs.plugins.dbus.Broadcast"
+
+    def __init__(self, bus):
+        """Create a Broadcast service.
+
+        :param bus: The bus to serve on.
+        """
+        bus_name = dbus.service.BusName(
+            Broadcast.DBUS_NAME, bus=bus)
+        dbus.service.Object.__init__(self, bus, Broadcast.DBUS_PATH, bus_name)
+
+    @dbus.service.method(DBUS_INTERFACE,
+                         in_signature='ss', out_signature='')
+    def announce_revision(self, revision_id, url):
+        """Announce revision_id as being now present at url.
+
+        To avoid information disclosure, no details are handed over the wire:
+        clients should access the revision to determine its contents.
+        """
+        self.Revision(revision_id, url)
+
+    @dbus.service.signal(DBUS_INTERFACE)
+    def Revision(self, revision, url):
+        """A revision has been observed at url."""

=== modified file 'tests/test_activity.py'
--- a/tests/test_activity.py	2007-02-06 05:05:48 +0000
+++ b/tests/test_activity.py	2007-02-06 07:12:42 +0000
@@ -26,7 +26,14 @@
 import weakref
 
 import dbus
+# to create a private Bus
 import _dbus_bindings
+import dbus.mainloop.glib
+import gobject
+
+# done here to just do it once, and not in the plugin module to avoid doing it
+# by default.
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
 from bzrlib.tests import TestCaseWithMemoryTransport
 
@@ -95,6 +102,7 @@
         the GIL)"""
 
         bus.add_message_filter(bus.__class__._signal_func)
+        bus.set_exit_on_disconnect(False)
 
         return bus
 
@@ -110,7 +118,7 @@
             self._bus_type, id(self))
 
 
-class TestActivity(TestCaseWithMemoryTransport):
+class TestCaseWithDBus(TestCaseWithMemoryTransport):
 
     def setUp(self):
         TestCaseWithMemoryTransport.setUp(self)
@@ -118,11 +126,40 @@
         # the users desktop!
         self.bus = PrivateBus()
         self.addCleanup(self.bus.nuke)
+
+
+class TestActivity(TestCaseWithDBus):
         
     def test_advertise_branch_no_service_running(self):
         # should not error: just call it
         activity.Activity(bus=self.bus).advertise_branch(self.make_branch('.'))
 
+    def in_dev_test_advertise_branch_service_running(self):
+        # We could construct a test-broadcaster to test with, but for now the
+        # sheer overhead of that scares me.
+        # attach a Broadcaster to our test branch: creates a running service.
+        broadcaster = activity.Broadcast(self.bus)
+        # get the object so we can subscribe to callbacks from it.
+        dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
+            activity.Broadcast.DBUS_PATH)
+        # we want to recieve the callbacks to inspect them.
+        branches = []
+        def catch_branch(revision, url):
+            branches.append((revision, url))
+        dbus_object.connect_to_signal("Revision", catch_branch,
+            dbus_interface=activity.Broadcast.DBUS_INTERFACE)
+
+        # now call our convenience JustDoIt method.
+        branch = self.make_branch('.')
+        activity.Activity(bus=self.bus).advertise_branch(branch)
+        # now, let the broadcast method interactions all happen
+
+        mainloop = gobject.MainLoop()
+        gobject.idle_add(mainloop.quit)
+        mainloop.run()
+        self.assertEqual([('revision1', 'url1'), ('revision2', 'url2')],
+            branches)
+
     def test_constructor(self):
         """The constructor should setup a good working environment."""
         obj = activity.Activity()
@@ -130,3 +167,61 @@
         # it has __eq__ or if it would be reliable, so we use object identity.
         self.assertTrue(obj.bus is dbus.SessionBus(),
             "%r is not %r" % (obj.bus, dbus.SessionBus()))
+
+    def test_server(self):
+        """Calling Activity.serve() provides a convient means to serve."""
+        obj = activity.Activity(bus=self.bus)
+        calls = []
+        #obj.serve_agency()
+
+
+class TestBroadcast(TestCaseWithDBus):
+
+    def test_announce_revision(self):
+        """Calling announce_revision via dbus should work and return nothing."""
+        # attach a Broadcaster.
+        broadcaster = activity.Broadcast(self.bus)
+        dbus_object = self.bus.get_object(activity.Broadcast.DBUS_NAME,
+            activity.Broadcast.DBUS_PATH)
+        dbus_iface = dbus.Interface(dbus_object,
+            activity.Broadcast.DBUS_INTERFACE)
+        # we want to recieve the callbacks to inspect them.
+        signals1 = []
+        def catch_signal1(revision, url):
+            signals1.append((revision, url))
+        # second method to avoid any possible dbus muppetry
+        signals2 = []
+        def catch_signal2(revision, url):
+            signals2.append((revision, url))
+        dbus_object.connect_to_signal("Revision", catch_signal1,
+            dbus_interface=activity.Broadcast.DBUS_INTERFACE)
+        dbus_object.connect_to_signal("Revision", catch_signal2,
+            dbus_interface=activity.Broadcast.DBUS_INTERFACE)
+
+        # now try to call the announce method, which should call the signal 
+        # handlers - all of them..
+        errors = []
+        results = []
+        def handle_reply():
+            results.append(None)
+            if len(results) + len(errors) == 2:
+                mainloop.quit() # end the mainloop
+        def handle_error(error):
+            errors.append(error)
+            if len(results) + len(errors) == 2:
+                mainloop.quit() # end the mainloop
+        dbus_iface.announce_revision('revision1', 'url1',
+            reply_handler=handle_reply,
+            error_handler=handle_error)
+        # for each revision.
+        dbus_iface.announce_revision('revision2', 'url2',
+            reply_handler=handle_reply,
+            error_handler=handle_error)
+        mainloop = gobject.MainLoop()
+        mainloop.run()
+        if errors:
+            raise errors[0]
+        self.assertEqual([('revision1', 'url1'), ('revision2', 'url2')],
+            signals1)
+        self.assertEqual([('revision1', 'url1'), ('revision2', 'url2')],
+            signals2)



More information about the bazaar-commits mailing list