Evolution's "Data Server" architecture

Evolution Data Server manages access to the calendar, tasks and addressbook information available for Evolution and other applications.

It is a CORBA component which, when activated, allows concurrent access by several client applications to the same data, adding to that notifications of changes, which are signalled to all clients.

Its extensible architecture, allows the addition of plugins to manage different kinds of calendar/tasks/addressbook sources, by just writing a shared library, which will be loaded by evolution-data-server on startup.

Addressbook

thumb

libebook

This library provides classes for client interaction. It relies on the classes in libedata-book running in an instance of Evolution Data Server to provide the data.

libedata-book

This library provides classes for backend implementation.

Writing an address book backend for the Evolution Data Server involves extending the EBookBackend class and implementing its virtual methods.

Backends that need network communication generally extend the EBookBackend class. This class lets the backend return the results of its operations asynchronously.

Other backends, like the file and vcf ones, extend EBookBackendSync, which derives from EBookBackend and provides wrappers around its virtual functions to facilitate backend implementation when network performance is not a factor.

EBookBackend

The EBookBackend class is the base of the backend classes. It is an abstract class. Backend implementations should extend this and provide implementations for all of its virtual methods.

Virtual methods

The following are the methods that individual backend implementations should override. EBookBackend makes sure that the backend provides the implementation for this method, and invokes it. This is achieved by individual backend implementations assigning function pointers to fields in the EBookBackendClass structure.

List of EBookBackend virtual methods

authenticate_user

Authenticates to server using the requested user name, password and authentication method. Reports success or failure by calling e_data_book_respond_authenticate_user().

cancel_operation

Cancels all of the current operations on this backend, for example terminate all the server access currently happening. Currently only the LDAP backend implements this.

create_contact

Creates a contact passed in the form of a vcard, in the folder corresponding to this backend. Returns success or failure by calling e_data_book_respond_create().

get_changes

Retrieves all changes corresponding to the provided change id. Returns success or failure and a list of changes with e_data_book_respond_get_changes().

get_contact

Retrieve the contact with the provided ID. Success or failure is reported using e_data_book_respond_get_contact(). If successful, the contact is returned in vcard form in that call. On failure, an empty string needs to returned.

get_contact_list

Retrieves all the contacts matching a query. Reports success or failure with e_data_book_respond_get_contact_list(). If successful, a list of matching vcard strings has to be returned in that call. On failure, NULL has to be returned.

get_required_fields

Return a list of contact field names which this backend requires to be present to be able to create or modify a contact. The names of the fields have to be retrieved using e_contact_field_name(). The field list has to be returned using e_data_book_respond_get_required_fields().

get_static_capabilities

Returns a string which has the list of comma separated static capabilities advertised by this backend.

get_supported_fields

Returns a list of contact field names which this backend supports. The names of the fields has to be retrieved using e_contact_field_name(). The field list has to be returned using e_data_book_respond_get_supported_fields(). This function is used to enable only those fields which are supported by backend in contact editor.

get_supported_auth_methods

Returns a list of authentication methods supported by server using e_data_book_respond_get_supported_auth_methods. Each auth method can be a string like \u201cplain/password\u201d.

modify_contact

Gets the ID of the contact from the passed in vcard by converting it to an EContact. We need to modify the contact with that ID with the values present in the passed in vcard. Reports success or failure using e_data_book_respond_modify().

remove

Deletes the address book folder on the file system or server from which this backend is reading the contacts.

remove_contacts

Removes the contacts with passed-in IDs from the folder corresponding to this backend. Returns success or failure by calling e_data_book_respond_remove_contacts(). Also returns the list containing the ids of the contacts which were successfully deleted.

set_mode

This method sets the online/offline mode on the backend when the backend is created and also whenever there is a change in the online/offline state. EBookBackendFactory iterates over all the backends it has created so far. Each backend which is in a loaded state should notify all its clients about the change in online/offline state by calling e_book_backend_notify_connection_status(), and change in the writable state by calling e_book_backend_notify_writable(). If the backend holds a connection to the server, it should do a logout operation when mode is offline and should call e_book_backend_notify_auth_required() when mode is online, so that clients can call e_book_backend_authenticate_user() to establish a connection with server.

start_book_view

Updates the passed-in view with all the contacts satisfying the view query using e_data_book_view_notify_update(). When done with all contacts, calls e_data_book_view_notify_complete(). e_data_book_view_notify_update() matches the contact added to view against the query, so iterating over all contacts is not a big overhead for a backend.

stop_book_view

If the backend is currently populating the view (in the start_book_view method), stop that operation. Generally this is achieved by setting a flag in the book_view structure. start_book_view implementation keeps looking at this flag after populating each contact, stopping further populating and calling e_data_book_view_notify_complete() if it has to be stopped.

Non-virtual methods

The following functions are completely implemented in the EBookBackend class. Individual backend implementations can not override these.

List of EBookBackend non-virtual methods

add_book_view

Adds the view to the list of views which are displaying the contents of folder corresponding to this backend. If one client creates a new contact, it will be notified to all the views by calling e_book_backend_notify_update(), which iterates over all the views and adds the new contact. The list of views is maintained in priv->views.

add_client

Adds the passed in EDataBook to the backend's list of clients. EDataBook on e-d-s side represents an EBook on the client side. This list effectively corresponds to the list of EBook clients talking to this backend.

get_book_views

Returns the list of views to this backend.

get_source

Returns a reference to the ESource corresponding to this backend.

has_out_of_proc_clients

Returns TRUE if any of the backend's clients is from another process. An out-of-process client is one where the EBook is created in a process other than e-d-s. For example an EBook created in Evolution and talking to this backend is an out of proc client. In e-d-s itself other backends can create EBook clients and use them to talk to this backend. For example, the 'contacts' calendar backend creates an EBook and uses it to access personal address books. These are called in-proc clients.

is_loaded

Returns TRUE if the the passed-in backend is already in a loaded state, otherwise FALSE.

is_writable

Returns TRUE if the passed-in backend is in a writeable state. If a backend is not in a writeable state one can not perform operations like create, delete and modify.

is_removed

Returns TRUE if the folder which this backend is reading (either on the local file system or on a server) is removed, otherwise FALSE.

notify_auth_required

This function is used to notify all the clients of the backend that it has switched to online mode, and now clients have to call e_book_backend_authenticate_user(), so that the backend can establish a connection to server. This function is called from the set_mode() method implementation of the backends.

notify_update

Traverses all of the backend's views and updates the contact passed as argument into them by calling e_data_book_view_notify_update() on each view.

notify_remove

Traverses all of the backend's views and informs them of the deletion of the contact with the given ID by calling e_data_book_view_notify_remove() on each view.

notify_complete

Notifies all of the backend's views that the current set of notifications is complete. This has to be called after a series of e_book_backend_notify_update(), e_book_backend_notify_remove() calls.

notify_connection_status

Notifies all of the backend's clients about its current online state. e_data_book_report_connection_status() is called on each EDataBook.

notify_writable

Notifies all the clients about the current writable state of the backend. e_data_book_report_writable() is called on each EdataBook. present in priv->clients.

open

Checks whether the backend is already loaded, if so it just responds with success and reports the writable state of the backend (by using e_data_book_respond_*). If it is not yet loaded it calls the load_source() virtual method. The implementation of the load_source function should extract the required properties and URI form the passed-in ESource and store it in its private data members. It should also set the load state and writable state of the backend by calling e_book_backend_set_is_loaded(), e_book_backend_set_is_writable() and notify all the clients about the connection status and writable status by calling e_book_backend_notify_connection_status() and e_book_backend_notify_writable().

e_book_backend_open() gets called each time a new EBook client is created using the same URI or ESource. But the load_source() implementation of the corresponding backend gets called only once as e_book_backend_open() just returns if the backend is already loaded. The \u201conly_if_exists\u201d flag indicates whether it should create the address book if one does not already exist.

remove_book_view

Removes the passed-in view from the internal views list.

remove_client

Removes the passed-in client form the internal list of clients. When the list becomes empty, the backend emits the \u201clast_client_gone\u201d signal.

set_is_loaded

Sets the load status of the backend. This should be called from backend implementations only.

set_is_removed

Sets the removed status of the backend. This should be called from backend implementations only.

set_is_writable

Sets the writable status of the backend. This should be called from backend implementations only.

EBookBackendSync

EBookBackendSync extends the EBookBackend class and implements synchronous wrappers for many of its virtual methods. The synchronous methods will return results immediately to its caller.

When a backend implementation extends the EBookBackendSync class, it has to provide implementations for all the virtual methods defined therein.

Existing backends

There are four existing backends - EBookBackendFile and EBookBackendVCF deriving from EBookBackendSync, and EBookBackendLDAP and EBookBackendGroupwise deriving from EBookBackend.

EBookBackendFile

The file backend uses libdb (addressbook.db) to store the contacts. While storing the contacts, the ID of the contact is used as key and the vcard string of the contact is used as the value. It also maintains one addressbook.db.summary file which contains a few field values of each contact on which searches are generally made. This summary file is loaded into memory when the backend is loaded.

When a query arrives, first the query is checked to see if it is a summary query (i.e the query contains only fields which are present in the summary), the IDs of matching contacts are retrieved. Using these IDs the full contact info is read from the the addressbook.db file.

If the query is not a summary query, we iterate over the database and read each contact and match it against the query. Create, modify operations are simple, as we store the vcards in the database, so we just need to insert the supplied vcard string into the database with the ID as the key. Since both contacts and contact lists are represented as vcard strings and vcards are stored in database, file backend needs not treat them differently in any way.

EBookBackendVCF

The VCF backend uses a flat file (.vcf) to store the contacts. When the backend is loaded, all the existing contacts in the file are read into memory. The create/delete/modify operations are done on data present in memory.

The contacts stored in memory are dumped to the file periodically. It does not maintain any summary indexes, like the file backend, as all the data is there in memory.

This backend gets launched when the protocol prefix of the URI is vcf://. As of now there is no way to create an address book folder of this type in the Evolution UI.

Probably this backend will replace the file backend when we get rid of the libdb dependency. Create, modify operations are simple as we store the vcards in the database - we just need to insert the passed-in vcard string with ID as the key.

Since both contacts and contact lists are represented as vcard strings, and as vcards are stored in the file, the VCF backend needs not treat them differently in any way.

EBookBackendLDAP

The LDAP backend uses asynchronous openldap APIs for communicating with the server. It supports both LDAPv2 and LDAPv3. It follows the 'inetorgperson' schema to store and retrieve contacts. It supports anonymous access and login using both DN and email ID of users. It supports communication over TLS. It does not support contact lists yet.

Unlike the file and VCF backends, the LDAP backend cannot store all fields of a contact - it keeps a list of the supported contact fields and the corresponding LDAP attribute names which store those values. Also, the LDAP backend converts LDAP results into LDAPMod structures, which allows the supported contact fields to be grouped by conversion function (contact <-> LDAP). To convert a field value, one needs to find the type of the field (i.e in which group it falls) and call the corresponding functions.

EBookBackendGroupwise

The GroupWise backend uses the SOAP interface provided by the GroupWise server. It supports communication over SSL.

A GroupWise server has four types of address book items - Contact, Resource, Organization and Group. Resource corresponds to a resource, like a meeting room. Organization represents a company. Group represents a group containing any of the aforementioned three types of item.

The difference between Contact, Resource and Organization is only in terms of the fields they support. Organization and Resource may not have all the fields of Contact - for example, the Job Title field is not applicable for them.

Evolution has only two types of address book items - Contacts and Contact Lists. GroupWise Contact, Organization and Resource are shown as Contacts in Evolution, and the Group item will be shown as a Contact List.

When a contact is created, it will always be created as a GroupWise Contact, and when a contact list is created, it will be created as a GroupWise Group item. You cannot create Resource and Organization type contacts from Evolution.

Also, GroupWise stores the Organization field of contacts, as well as members of Contact Lists, using references to Organization and Contact objects respectively, unlike the other backends, which store the actual values. For example, when a Contact is created with the name 'Siva' and organization 'Novell' from Evolution, we first search for any Organization contact with name Novell in that folder, and if it does not exist, we need to create it. Then we store a reference to it in the 'Siva' contact. Similarly, when creating a Group item we have to make sure that there are contacts corresponding to all the members and store references to those.

Calendar

E-D-S calendar architecture

Calendar client libraries

E-D-S contains two libraries that are used for client access to calendar and tasks data.

libecal

This is the main client library for calendar and tasks. It offers all that is needed for client applications to open and manage calendars.

  • e-cal.[ch]: this is the main entry point for calendar/tasks clients. This contains the ECal object, which manages the communication between the clients and the calendar/tasks backends. It offers methods for all operations available in the backends.

  • e-cal-component.[ch]: this contains ECalComponent, a GObject-based wrapper around libical's icalcomponent structure, although it does not offer all of its functionality (for instance, ECalComponent does not manage VCALENDAR objects).

  • e-cal-listener.[ch]: this implements a listener which gets signals for the completion of the operations started by the clients in the backends.

  • e-cal-view.[ch]: implements the client-side of the live queries to the backends.

  • e-cal-view-listener.[ch]: implements the listener for ECalView, which listens for additions/updates/removals of objects in the live queries.

  • e-cal-recur.[ch]: implements the functions for recurrence expansion of recurrent appointments.

  • e-cal-time-util.[ch]: contains utility functions for date/time management.

  • e-cal-util.[ch]: contains utility functions for various common tasks.

libical

libical is the iCalendar spec implementation we use. It parses iCalendar text and offers a rich set of types and functions for dealing with all things related to iCalendar.

Calendar backends

Calendar backends deal with the communication between evolution-data-server and specific calendar servers/types. Thus, a backend must be written for any different calendar source (file backend for local files, webcal backend for HTTP, Groupwise, Exchange, etc).

The way backends are loaded is as follows. First, backends need to install a shared library to a known place ($evolution_prefix/lib/evolution-data-server-$VERSION/extensions), which will be loaded by the evolution-data-server process. When loading the shared libraries, e-d-s looks for some named symbols, like eds_module_init, eds_module_shutdown and eds_module_list_types. Extensions need to implement these functions to:

  • eds_module_init: register all ECalBackendFactory-derived classes. These factories are responsible for creating backends of a given kind (events, tasks, journal), so a factory class for each kind must be registered. For each ECalBackendFactory-derived classes, 3 methods need to be implemented:

** get_kind: returns the kind of the backends created by the factory. ** get_protocol: returns the protocol used by the backends. ** new_backend: creates a new backend for the given kind.

  • eds_module_shutdown: cleans up all memory used by the extension. This is called when the extension is about to be unloaded.

  • eds_module_list_types: returns a list of the factory types implemented by the extension.

Their main purpose of the backends is to translate the format/mode of operation of the calendar source they access into the API used by E-D-S calendar clients. Thus, in theory, a backend for any kind of source can be written, as it is shown in the weather backend, which implements the calendar backends API to allow Evolution to display weather information in the calendar.

To write a calendar/tasks backend, a subclass of ECalBackend (for asynchronous) or ECalBackendSync (for synchronous) needs to be written. ECalBackend contains the following virtual methods, which need to be implemented by backend implementations:

  • is_read_only: returns whether the calendar is read only or not.

  • get_cal_address: returns the email address of the owner of the calendar.

  • get_alarm_email_address: returns the email address to be used for alarms.

  • get_ldap_attribute: returns specific LDAP attributes.

  • get_static_capabilities: returns the capabilities provided by the backend, like whether it supports recurrences or not, for instance.

  • open: opens the calendar.

  • remove: removes the calendar.

  • create_object: creates a new event/task in the calendar.

  • modify_object: modifies an existing event/task.

  • remove_object: removes an object from the calendar.

  • discard_alarm: discards an alarm (removes it or marks it as already displayed to the user).

  • receive_objects: import a set of events/tasks in one go.

  • send_objects: send a set of meetings in one go, which means, for backends that do support it, sending information about the meeting to all attendees.

  • get_default_object: returns an empty object with the default values used for the backend.

  • get_object: returns an event/task, given its UID.

  • get_object_list: returns a list of events/tasks given a set of conditions.

  • get_timezone: returns timezone objects for a given TZID.

  • add_timezone: adds a timezone to the backend.

  • set_default_timezone: sets the timezone to be used as the default.

  • start_query: starts a live query on the backend.

  • get_mode: returns the current online/offline mode for the backend.

  • set_mode: sets the current online/offline mode.

  • get_free_busy: returns F/B information for a list of users.

  • get_changes: returns a list of changes made since last check.

There are also a couple of internal methods, not used by clients, but by the ECalBackend class itself, which are:

  • internal_get_default_timezone: returns the default timezone.

  • internal_get_timezone: returns a given timezone.

ECalBackendSync includes the same methods, but with a _sync suffix. Also, the big difference between the two versions is that, when implementing ECalBackend-based classes, those need to do the notification of the results themselves. This means calling the e_data_cal_notify_* functions. For ECalBackendSync, this is all done internally, so subclasses don't need to deal with that.

There is also a locking mechanism for backends to use. This is done for synchronous backends (ECalBackendSync) via the e_cal_backend_sync_set_lock function. When the lock is activated, only one operation at a time will be run for the given backend, using for that, internally, a GMutex object, which locks operations when there is already one running.

Apps/Evolution/EDS_Architecture (last edited 2022-03-08 01:16:24 by JeffFortin)