Skip to main content

Vala and DBus

http://ubuntuone.com/1KneGMNdDYv7UGwFevbhSC

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:

/galleries/dropbox/d-feet-full.thumbnail.png

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:

/* Important: keep demo variable out of try/catch scope not lose signals! */
Demo demo = null;

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.

$ valac publicfiles.vala --pkg gee-1.0 --pkg gio-2.0
$ ./publicfiles
...
/home/rtg/Ubuntu One/passwords.txt -> http://ubuntuone.com/7LwB9iLAXvVKQbCbsbmxRT
402 files published