KDE PIM/Meetings/Osnabrueck 4/Proposal Daemon
Tobias's PIM Daemon Proposal
The Proposal
Abstract for a KDE PIM Daemon ========================================== During the KDE 3.X development cycle the KDE PIM developer were faced with new challenges concerning the dimension of data to handle. When there was just a small address book or calendar file on the users desktop PC in the past, we'll have to handle LDAP directories with more than 100000 entries and several groupware servers with even more data records in the future. With libkabc and libkcal, kdepim already provides the tools for managing these amount of data but there are still two bottlenecks which have to be solved for KDE 4.0: 1) Synchronous access to the data - the contacts/events are loaded synchronous from the groupware server which blocks the graphical user interface 2) Per process storage of the data - every applications which wants to access contacts/events has to load _all_ contacts/events into its own address space, which causes a big memory footprint and is often unnecessary Since a few years component based software becomes more and more popular and KDE should/does profit from it as well. So in the near future the two issues will become more important, because every pim component would suffer of them. A possible solution would be a KDE PIM Daemon, the design and API is the topic of this RFC. Why do we need a PIM daemon? ----------------------------- - loading data synchronous blocks the GUI -> asynchronous loading -> with asynchronous loading the data aren't available immediately -> user has to wait for data -> that's bad - all data get loaded into the applications address space -> no sharing available -> 3 applications have loaded the whole address book 3 times -> that's bad as well - no central change notification -> when one application adds a new calendar, another application won't get notified about it and can't update its display -> not acceptable What should the PIM daemon look like? -------------------------------------- The pim daemon (KPimD) is a standalone application or kded module which offers its service via a DCOP interface. The daemon can either be started on KDE startup or when the first pim application queries its service. KPimD handels the address books, calendars and notes of the user, maybe also mails in a later version. To load/store the data it uses the K Resource Framework which will be rewritten for KDE 4.0. The application developer will have a thin wrapper library around the dcop service, but he will be able to use the dcop service directly as well. These libraries will have a similar API to kabc/kcal. A new part of KDE PIM 4.0 will be components which utilize common pim tasks (e.g. search for an contact/event) and make heavy use of the KPimD dcop interface. So the whole structure will look like this: --------------- --------------- < PIM Component > ( Application ) --------------- --------------- \ / ------------------------------------- | Thin Wrapper Library | ------------------------------------- | | ------------------------------------- | DCOP Interface | ..................................... | KDE PIM Daemon | ------------------------------------- | ------------------------------------- | K Resource Framework | ------------------------------------- How does the PIM daemon solve our problems? -------------------------------------------- Our main problems where the synchronous/asynchronous loading, the memory footprint and the missing notification system in current kde pim libraries. The first issue is solved by the daemon, because it is a separated process so it can load all the data and the pim application doesn't have to wait. Furthermore caching can be implemented in a single place, so also when the daemon has just started, some (maybe not up-to-data) data are already available. The second problem is solved by the daemon as well since it will offer a query interface. Today KOrganizer loads all events/todos even when it presents only the events of the current week to the user. KAddressBook also loads all contacts even when the user just want to see the contacts which starts with an 'A' in the given name. With the query interface the pim applications can request exactly the data they are really interested in, so the memory footprint of the single pim applications stays small. Since the daemon is a separated and unique process which manages all pim data handling it knows everything about changes => it can send notifications for all pim related actions. This feature allows us to develop model-view-controller based components which guarantees up-to-date views of contacts and calendars all over the desktop. What shall the APIs look like? ------------------------------- During KDE 3.X the address book and calendar libraries were designed as a kind of container classes for contacts and events. The idea behind it was to provide tools for pim development. The concept was nice but not what's needed in a desktop environment. There you need services which can be used by 3rd-party developer and, also it seems to be the opposite of general development practise, it shouldn't be to general but give a straight forward idea of how to get stuff done. For this reason the new APIs shouldn't allow the developer to create new containers (address books or calendars) as C++ objects and work on them, but rather create new address books or calendars _in_ the service and work with them by using unique identifiers. At the first glance it looks like a big limitation, but it isn't because you can do all things needed for a desktop environment in this way as well and you get all the above mentioned features for free. And what do the APIs look like in detail? K Resource Framework ...................... The first change has to be done in the K Resource Framework. Here we have to separated the basic resource framework (located in kdelibs/kresources) from the pim specific framework (located in kdelibs/kabc and kdepim/libkcal). The basic framework needs support for - subresources - asynchronous data handling The pim framework needs support for - categories - distribution lists (only KABC::Resource I guess) So pim resources will be able to load/store categories and distribution lists from/to groupware servers as well. KDE PIM Daemon ............... The pim daemon will provide a dcop interface for creating, changing and deleting address books, calendars, notebooks and their items, here a short abstract of functionality: - AddressBookInterface - create address books - delete address books - lock address books (needed for synchronization) - activate/deactivate address books - add contact to address books - remove contact from address books - change contact in address books - query contacts - add categories to address books - remove categories from address books - create distribution lists - change distribution lists - remove distribution lists - add custom fields - remove custom fields - CalendarInterface - create calendar - delete calendar - lock calendar (needed for synchronization) - activate/deactivate calendar - add event/todo to calendar - remove event/todo from calendar - change event/todo in calendar - query events/todos - add categories to calendar - remove categories from calendar - add custom fields - remove custom fields - NotesInterface - add notebook - remove notebook - lock notebook (needed for synchronization) - activate/deactivate notebook - add item to notebook - change item in notebook - remove item from notebook - query items - add categories to notebook - remove categories from notebook - add custom fields - remove custom fields - Signals .... (come later) The arguments and return values of this methods are primitive types like booleans, integers, strings and stringlists, so 3rd-party developer can easily write applications by using the dcop bindings for their preferred language (even with shell scripting). The data (contacts, events/todos, notes) are passed as strings in vCard3.0 resp. iCal format. An example address book interface is attached. What's new in these interfaces? The locking of address books, calendars and notebooks is necessary for synchronization, because you need a static set of data records during the synchronization, otherwise it leads to massive data loose. The categories and distribution lists are associated to an address book, calendar or notebook, so it will be possible to store them on groupware servers. The custom fields are handled in this layer (not in the wrapper libraries) to make them available in the search queries. That's an often asked feature and of course it makes no sense to provide custom fields when you can't use them (like it's atm with libkabc/libkcal). The notebook interface should be the backend for knotes, so it's easier to add support for groupware servers because the caching and asynchronous data handling is for free, furthermore the API allows a clean way for synchronization. There will be dcop signals for every action you can do with the API, so whenever a new address book, calendar or notebook is created/changed/deleted a signal will be emitted which can be used by model-view-controller based components. Errors will be delivered by dcop signals as well. Are there any limitations? --------------------------- Of course there are limitations with the new concept, but they are nothing which can't be solved by doing it in another clean way. Examples: How to add a new address book from within the groupwarewizard? The old way was to load the resource manager of the address books family in the groupwarewizard, create a new resource locally (wich means linking against this resource plugin), add the resource to the manager, save the managers configuration and delete the resource object. That's really bad because you have to link against the plugin and must install it's header file in a public place. Furthermore the local instantiation of the resource manager can cause race conditions with the current implementation. The new way is a lot smarter. To add a new address book for example, you just call createAddressBook( "eGroupware", "egroupware", configXML, false ); in the dcop interface. The first argument is the name of the address book, the second one the type identifier, the third one the configuration in xml format and the last one tells the pim daemon not to show the resource type specific configuration dialog. So you just have to create the configuration of the new address book in xml format and pass it to the pim daemon, which will configure and add the address book for you. The advantages: - no linking to the plugin - global notification of the new address book Since XML is quite flexible we can add version control for the configuration format. [Please add other questions here] This RFC is not complete now, constructive discussion is always welcome!
Prototype - Transporting PIM data via DCOP
Here you can find the code of a small prototype to transport PIM data via DCOP. You need a current libkabc from 3.5 branch to get the transportation of contacts done. For serializing events/todos/journals I'll provide a patch for libkcal soon.
API Example
class AddressBookInterface { K_DCOP public: AddressBookInterface(); ~AddressBookInterface(); enum LockType { NoLock, ReadLock, WriteLock, ReadWriteLock }; k_dcop: /** Address Book Management The following functions allow you to manage address books ( create, edit, delete ). */ /** Returns the identifiers of all available address book types. */ QStringList addressBookTypes() const; /** Returns the i18n'ed name for a given address book type. */ QString addressBookTypeName( const QString &type ) const; /** Returns the i18n'ed description for a given address book type. */ QString addressBookTypeDescription( const QString &type ) const; /** Returns the icon assocciated with the given address book type. */ QPixmap addressBookTypeIcon( const QString &type ) const; /** Returns the unique identifiers of all available address books. */ QStringList addressBooks() const; /** Returns the name for a given address book identifier. */ QString addressBookName( const QString &identifier ) const; /** Returns the type for a given address book identifier. */ QString addressBookType( const QString &identifier ) const; /** Creates a new address book with the given @param type and the given @param name. The @param configuration is an address book type specific xml string which is used as default configuration by the new address book. If @showConfigDialog is true, a configuration dialog is shown where the user can configure the address book manually. @returns The unique identifier for the new address book or an empty string if the creation was canceled. */ QString createAddressBook( const QString &name, const QString &type, const QString &configuration, bool showConfigDialog = true ); /** Changes the address book with the given unique @param identifier. @param configuration The new configuration of this address book. @param showConfigDialog If true a configuration dialog is shown where the user can configure the address book manually. */ void changeAddressBook( const QString &identifier, const QString &configuration, bool showConfigDialog = true ); /** Deletes the address book with the given unique @param identifier. */ void deleteAddressBook( const QString &identifier ); /** Returns the current lock status of the address book with the given address book @param identifier. */ LockType addressBookLock( const QString &identifier ) const; /** Sets a lock status for the address book with the given address book @param identifier. */ void addressBookSetLock( const QString &identifier, LockType lockType ); /** Returns whether the address book with the given unique @param identifier is active. */ bool addressBookActive( const QString &identifier ) const; /** Sets the address book with the given identifier active or passive. */ void addressBookSetActive( const QString &identifier, bool active ); /** Contact Management The following functions allow you to manage contacts ( create, change, delete ). */ /** Adds a new contact to the given address book. @param identifier The address book identifier of the address book where the contact shall be added. @param vCard contains the contact in vCard 3.0 format. */ void createContact( const QString &identifier, const QString &vCard ); /** Adds a list of new contacts to the given address book. @param identifier The address book identifier of the address book where the contacts shall be added. @param vCards contains the contacts in vCard 3.0 format. */ void createContacts( const QString &identifier, const QStringList &vCards ); /** Changes the contact with given unique @param identifier. @param vCard contains the contact in vCard 3.0 format. */ void changeContact( const QString &identifier, const QString &vCard ); /** Removes the contact with the given unique @param identifier from the address book. */ void deleteContact( const QString &identifier ); /** Removes the contacts with the given unique @param identifiers from the address book. */ void deleteContacts( const QStringList &identifiers ); /** Returns the contact in vCard 3.0 format for the given unique @param identifier. */ QString contact( const QString &identifier ); /** Returns the contacts in vCard 3.0 format for the given unique @param identifiers. */ QStringList contacts( const QStringList &identifiers ); /** Category Management The following functions allow you to manage the address book categories. */ /** Returns all categories provided by the address book with the given @param identifier. */ QStringList categories( const QString &identifier ); /** Creates a new category for the address book with the given @param identifier. */ void createCategory( const QString &identifier, const QString &category ); /** Deletes the @param category from the address book with the given @param identifier. */ void deleteCategory( const QString &identifier, const QString &category ); /** Custom Fields Management The following functions allow you to manage the custom fields. */ /** Creates a new custom field. @param key The key of this custom field. @param label The i18n'ed label for this custom field. */ void createCustomField( const QString &key, const QString &label ); /** Deletes the custom field with the given @param key. */ void deleteCustomField( const QString &key ); /** Returns the list of keys from all custom fields. */ QStringList customFields() const; /** Returns the label of the custom field with the given @param key. */ QString customFieldLabel( const QString &key ) const; k_dcop_signals: void addressBookCreated( const QString &identifier ); void addressBookChanged( const QString &identifier ); void addressBookDeleted( const QString &identifier ); void addressBookLocked( const QString &identifier, bool locked ); void addressBookActivated( const QString &identifier, bool activated ); void contactCreated( const QString &addressBookIdentifier, const QString &identifier ); void contactChanged( const QString &addressBookIdentifier, const QString &identifier ); void contactDeleted( const QString &addressBookIdentifier, const QString &identifier ); void categoryCreated( const QString &addressBookIdentifier, const QString &category ); void categoryDeleted( const QString &addressBookIdentifier, const QString &category ); void customFieldCreated( const QString &key ); void customFieldDeleted( const QString &key ); };