Vala and DBus
Once in a while I return to the idea to write something in Vala. Vala's integration with Glib and Gnome is outstanding, you can write a useful DBus client in 10 lines, compile and run at native code speed without any scripting language interpreter overhead.
Ubuntu One filesync service exports a ton of DBus methods that are then used for the Control Panel, nautilus plugin, rhythmbox music store plugin and others. All the communication between the clients and the service on Ubuntu is based on DBus.
So, let's make a client that asks syncdaemon for a list of all published files and prints it out. Before we start, we need to find out what methods and signals are actually exported.
Launch D-Feet and find com.ubuntuone.SyncDaemon
bus name:
As you can see, there is a get_public_files()
method which does not return
anything. We need to refer either to the documentation for the method in the
introspection XML data by invoking Introspect()
method on
org.freedesktop.DBus.Introspectable
interface, on the developer website
and if nothing else helps, grep the sources and look at the code.
In Ubuntu One case, Introspect() works:
<node name="/publicfiles"> ... <interface name="com.ubuntuone.SyncDaemon.PublicFiles"> <signal name="PublicFilesListError"> <docstring><![CDATA[Report an error in geting the public files list.]]></docstring> <arg name="error" type="s" /> </signal> <signal name="PublicFilesList"> <docstring><![CDATA[Notify the list of public files.]]></docstring> <arg name="files" type="aa{ss}" /> </signal><method name="get_public_files"> <docstring><![CDATA[Request the list of public files to the server. The result will be send in a PublicFilesList signal. ]]></docstring> </method> ... </interface> </node>
So, when we call get_public_files()
, the "output" is sent back in
PublicFilesList
signal. So far so good.
We start writing publicfiles.vala with using
statements (as you may know,
Vala syntax is based on C#), which provide us with DBus functionality and
collection objects (such as HashTable). Also, we declare our DBus service.
using GLib; using Gee; /* Interface name */ [DBus (name = "com.ubuntuone.SyncDaemon.PublicFiles")] interface PublicFiles : Object { /* vala converts symbol_name to SymbolName for DBus symbols. This * preserves the name */ [DBus (name = "get_public_files")] public abstract void get_public_files() throws IOError; /* This is converted to PublicFilesList*/ public signal void public_files_list (HashTable<string, string>[] files); }
Since we are writing a client which needs to react to DBus signals, we need to use glib mainloop and provide the handler for the signals we are expecting.
But first, how do I know what GObject types are mapped to what DBus types?
Look at the Introspect() output above, and notice that PublicFilesList
signature is aa{ss}
. This translates to an "array of dict of string,string".
Look at the DBusServerExample to find out how types map. In our case,
aa{ss}
is HashTable<string, string>[]
. GObject does not support
deserialization of DBus dict to HashMap
, you need to use HashTable
for
that.
MainLoop loop; /* Handler for PublicFilesList signal */ void on_public_files_list( HashTable<string, string>[] files) { foreach (var file in files) { stdout.printf("%s -> %s\n", file["path"], file["public_url"]); } stdout.printf("%d files published\n", files.length); /* We have nothing else to do so quit */ loop.quit(); }
All that is left is to get the DBus proxy, and connect the signals. This has
proven to be a bit time-consuming. The code was written fast but my
application was not receiving any signals. I verified the code several times
and started debugging the generated C code (vala -C ...
). Should I have read
this warning in the DBusServerExample code, I'd complete it a lot faster:
Yes, I was declaring my proxies within try/catch block and by the end of the try block they were all gone and signals were never processed. Here's the correct code:
int main(string[] args) { /* This is extremely important: * If you put this in try/catch block, then the signals will _NOT_ be * delivered as the object will go out of scope. */ PublicFiles public_files = null; try { public_files = Bus.get_proxy_sync(BusType.SESSION, "com.ubuntuone.SyncDaemon", "/publicfiles"); /* Connecting the signal*/ public_files.public_files_list.connect(on_public_files_list); /* requesting public files, the "response" will be delivered via DBus * as a signal since it can take a lot of time. */ public_files.get_public_files(); } catch (IOError e) { return 1; } loop = new MainLoop(); loop.run(); return 0; }
That's it, time to compile the resulting file and run it, making sure that Ubuntu One syncdaemon is connected.