Getting Things GNOME! (well, at least my GSoC branch of it) is on its way to having a feature-complete DBus interface to its data store and the objects (Tasks and Tags) inside of it, as well as a client-side library.
More on that later. This post is a memo of some lessons learned while reading & grokking the entire dbus-python codebase, its still-incomplete tutorial, and trying to make use of it all. Aptdaemon was an invaluable help.
1. Claim your bus…and give it up nicely
This is akin to your mother telling you to, "Clean your room!" Claiming an object path (
/org/gnome/GTG/Server) on a bus (
org.gnome.GTG) and then silently exiting is a good way to confuse the session bus and any clients who are trying to talk to your object. There's no automatic notification that you're gone; the proxies and interfaces they may be using won't self-destruct. Even the session bus may think you're still there, and tell new clients to look in the wrong place.
This can lead to client code blocking for a long time as it waits in vain for a response—annoying! To avoid this, explicitly claim the bus from the server on startup:
>>> import dbus >>> dbus.SessionBus().request_name('org.gnome.GTG')
…and on shutdown (even if it's caused by a process signal or exception) be sure to release it:
You can also do this by creating a
dbus.service.BusName instance, but be sure to
del it before exiting.
2. Start simply
Like us, you may be able to start the server-side code from the command line (
gtg --daemon). Trying to call this directly—for example via an os.popen—is asking for a major headache. There is also no need to muck about with daemonization recipes or ensure the server code runs in the proper environment; the DBus authors and your distro developers have taken care of that.
Instead, make use of service definition files. On Ubuntu, these go in
/usr/share/dbus-1/services/ and look like:
[D-BUS Service] Name=org.gnome.GTG Exec=/usr/bin/gtg --daemon
From your code, you can invoke this precisely and directly with the single call:
>>> import dbus >>> dbus.SessionBus().start_service_by_name('org.gnome.GTG')
If the server is already running, the method simply returns a different value.
3. Object paths aren't URLs
In the age of Google, we are accustomed to seeing all kinds of data URL-encoded, resulting in monstrosities like:
The object path looks like a filesystem path, for example an object could be named
Don't get too excited by this. The characters you can use in a DBus object path are: /, a-z, A-Z, 0-9 and _. That's it. In GTG!, we use UUIDs for Task and Tag IDs:
>>> import uuid >>> str(uuid.uuid()) '9bcb25f3-a630-4030-b82f-cd4cb7b17d65'
So you might guess that a path for this object (a Task, say) could be
/org/gnome/GTG/Task/9bcb25f3-a630-4030-b82f-cd4cb7b17d65, right? Wrong. The hyphens aren't allowed, and we have to use:
>>> uuid.uuid4().hex '4a08654fce994a92b49a8b62ff5a7817'
Similarly, if you want to use human-readable strings (Tag names, for example) in paths, you'll either have to munge them to be acceptable, or deny your users the use of hyphens and spaces. Messy. Much easier just to choose a simple scheme of unique identifiers and pass actual data through actual method calls.