Here’s my very first screen cast showing the synchronisation between Akonadi applications using different APIs:
In the video, I change addresses in KAddressBook, and the entries in gtkAddressBook are immediately updated too. It would work the other way around too if the code was more complete, but python-twisted-akonadi is just getting started, so most features are not implemented yet.
This is a follow up to my previous blog about a Gtk Akonadi library.
The reason the synchronisation works is all down to the design of Akonadi. There is an architecture diagram here showing the basic principle of the design. The main feature is that all communication with Akonadi is done in a platform neutral way using D-Bus for notifications and an IMAP-like protocol for data transfer. One of the principle benefits of that is that most modern platforms already have libraries for dealing with D-Bus and IMAP, so there is a natural starting point for any Akonadi client implementation. The KDE API and the python API hide those implementation details, and provide ways for applications to fetch, delete, change and move items, and to receive notifications when another process or application performs one of those actions. That means that any application written in any language on any platform can access and modify the data in Akonadi, and the changes would be synchronised everywhere else.
The applications are not communicating with each other via plugins. That’s KDE 3 design and it doesn’t scale very well and leads to a lot of duplication. For example, a fully working Kontact has the complete address book in memory 6 times! The applications aren’t even aware that other applications exist. They’re all part of a macro scale Model/View pattern where each application is a View, and Akonadi is the Model.
This could be useful if for example you want to use gtkAddressBook instead of KAddressBook (hypothetically🙂 ), but you still want auto-completion of your contacts in KMail. Because the contacts are accessed through Akonadi, not the address book application, that would work just fine. For the user, it seems that ‘My contacts are in KAddressBook’, but because the technical reality is different, any other applications can use the same data in a versatile way.
Low level implementation
So how does a motivated developer implement Akonadi support for a new platform? A good starting point is finding existing frameworks for IMAP parsing, asynchronous communication, Unix socket communication (or named pipes on Windows), flexible event loops, and D-Bus support. As I chose to write an Akonadi library in python, I could easily find existing code for all of those pieces. python-dbus provides the D-Bus support, and I was quickly directed to Twisted python which provides the rest.
Twisted has some central components like reactors which provide an event loop and reading/writing to network sockets, deferreds, which fulfil a role similar to KJob in that they make asynchronous programming easy, and implementations of clients and servers in many different protocols. The only one needed for Akonadi is twisted-mail, which provides an IMAP parser and lots of convenience to hide the implementation details of the actual communication of the data.
I’ve written several times that the protocol Akonadi uses is Almost IMAP. The protocol mostly follows the IMAP standard, but it has some non-standard extensions which bring the flexibility and performance needed in a PIM system. One of the benefits of the python language is that all methods on classes behave as if they were virtual methods in C++. They can be overridden in subclasses and the overridden version will always be called. By implementing an overridden method in the twisted.imap4.IMAP4Client, I could implement support for the non-standard extensions and let the base class handle the rest (all the difficult stuff).
The next step was to enable serialisation to and from standard PIM types, like RFC822 messages, vCards, iCal entries etc. Again, python already has several high quality libraries for parsing and generating these types, and thanks to dynamic typing, handling the types can be done in a generic way, leading to even more type independence in the library.
The bit about models
The high level part of the KDE Akonadi libraries is the EntityTreeModel which simplifies data in Akonadi into a tree structure for use in a GUI. High level API will always be toolkit dependent, so I needed to choose a GUI toolkit to get the high level stuff started. PyGtk has a TreeStore class which provide an API somewhat similar to QStandardItemModel in that it allows creation of a tree of items on a conceptual level (the model), a TreeView class, somewhat simliar to QTreeView, and a CellRenderer somewhat similar to a QItemDelegate. Gtk also provides proxy models for sorting and filtering.
The gtkAkonadi.EntityTreeStore is the conceptual equivalent to the EntityTreeModel in the python API. It is a generic model for storing the data from Akonadi. There are also two existing subclasses, the MailTreeStore and ContactsTreeStore, which make a tree of Akonadi.Items appear as emails or contacts respectively. The rest is pure PyGtk code to make a real application out of those classes.
PIM could be shared across the whole free desktop, with cross platform Akonadi at the center of it all. Getting this far with the python API was remarkably simple, considering that I only started writing the code a week and a half ago, and didn’t have a lot of time to spend on it. There’s a long way to go, but if you want to help out on the python API or another client for any other toolkit, we can get you started on firstname.lastname@example.org or #akonadi on freenode.