## EDataBookCursor

EDataBookCursor — The abstract cursor API

## Synopsis

    #include <libedata-book/libedata-book.h>

    gboolean            (*EDataBookCursorSetSexpFunc)       (EDataBookCursor *cursor,
                                                             const gchar *sexp,
                                                             GError **error);
    gint                (*EDataBookCursorStepFunc)          (EDataBookCursor *cursor,
                                                             const gchar *revision_guard,
                                                             EBookCursorStepFlags flags,
                                                             EBookCursorOrigin origin,
                                                             gint count,
                                                             GSList **results,
                                                             GCancellable *cancellable,
                                                             GError **error);
    gboolean            (*EDataBookCursorSetAlphabetIndexFunc)
                                                            (EDataBookCursor *cursor,
                                                             gint index,
                                                             const gchar *locale,
                                                             GError **error);
    gboolean            (*EDataBookCursorGetPositionFunc)   (EDataBookCursor *cursor,
                                                             gint *total,
                                                             gint *position,
                                                             GCancellable *cancellable,
                                                             GError **error);
    gint                (*EDataBookCursorCompareContactFunc)
                                                            (EDataBookCursor *cursor,
                                                             EContact *contact,
                                                             gboolean *matches_sexp);
    gboolean            (*EDataBookCursorLoadLocaleFunc)    (EDataBookCursor *cursor,
                                                             gchar **locale,
                                                             GError **error);
    struct              EDataBookCursor;
    struct              EDataBookCursorClass;
    struct _EBookBackend * e_data_book_cursor_get_backend   (EDataBookCursor *cursor);
    gint                e_data_book_cursor_get_total        (EDataBookCursor *cursor);
    gint                e_data_book_cursor_get_position     (EDataBookCursor *cursor);
    gboolean            e_data_book_cursor_set_sexp         (EDataBookCursor *cursor,
                                                             const gchar *sexp,
                                                             GCancellable *cancellable,
                                                             GError **error);
    gint                e_data_book_cursor_step             (EDataBookCursor *cursor,
                                                             const gchar *revision_guard,
                                                             EBookCursorStepFlags flags,
                                                             EBookCursorOrigin origin,
                                                             gint count,
                                                             GSList **results,
                                                             GCancellable *cancellable,
                                                             GError **error);
    gboolean            e_data_book_cursor_set_alphabetic_index
                                                            (EDataBookCursor *cursor,
                                                             gint index,
                                                             const gchar *locale,
                                                             GCancellable *cancellable,
                                                             GError **error);
    gboolean            e_data_book_cursor_recalculate      (EDataBookCursor *cursor,
                                                             GCancellable *cancellable,
                                                             GError **error);
    gboolean            e_data_book_cursor_load_locale      (EDataBookCursor *cursor,
                                                             gchar **locale,
                                                             GCancellable *cancellable,
                                                             GError **error);
    void                e_data_book_cursor_contact_added    (EDataBookCursor *cursor,
                                                             EContact *contact);
    void                e_data_book_cursor_contact_removed  (EDataBookCursor *cursor,
                                                             EContact *contact);
    gboolean            e_data_book_cursor_register_gdbus_object
                                                            (EDataBookCursor *cursor,
                                                             GDBusConnection *connection,
                                                             const gchar *object_path,
                                                             GError **error);

## Description

The EDataBookCursor API is the high level cursor API on the addressbook server, it can respond to client requests directly when opened in direct read access mode, otherwise it will implement the org.gnome.evolution.dataserver.AddressBookCursor D-Bus interface when instantiated by the addressbook server.

### Note

EDataBookCursor is an implementation detail for backends who wish to implement cursors. If you need to use the client API to iterate over contacts stored in Evolution Data Server; you should be using EBookClientCursor instead.

### Implementing Cursors

In order for an addressbook backend to implement cursors, it must first be locale aware, persist a current locale setting and implement the `EBookBackendClass.set_locale()` and `EBookBackendClass.get_locale()` methods.

The backend indicates that it supports cursors by implementing the `EBookBackendClass.create_cursor()` and returning an EDataBookCursor, any backend implementing `EBookBackendClass.create_cursor()` should also implement `EBookBackendClass.delete_cursor()`.

For backends which use EBookBackendSqliteDB to store contacts, an EDataBookCursorSqlite can be used as a cursor implementation.

Implementing a concrete cursor class for your own addressbook backend is a matter of implementing all of the virtual methods on the EDataBookCursorClass vtable, each virtual method has documentation describing how each of the methods should be implemented.

### Tracking Cursor State

The cursor state itself is defined as an array of sort keys and an `E_CONTACT_UID` value. There should be one sort key stored for each contact field which was passed to `EBookBackendClass.create_cursor()`.

Initially, the cursor state is assumed to be clear and positioned naturally at the beginning so that the first calls to `EDataBookCursorClass.step()` using the `E_BOOK_CURSOR_ORIGIN_CURRENT` origin would respond in the same way as the `E_BOOK_CURSOR_ORIGIN_BEGIN` origin would.

Unless the `E_BOOK_CURSOR_STEP_FETCH` flag is not specified in calls to `EDataBookCursorClass.step()`, the cursor state should be always be set to the last contact which was traversed in every call to `EDataBookCursorClass.step()`. In the case that `EDataBookCursorClass.step()` was asked to step beyond the bounderies of the list, i.e. when stepping passed the end of the list of before the beginning, then the cursor state can be cleared and the implementation must track whether the cursor is at the beginning or the end of the list.

The task of actually collecting the cursor state from a contact should be done using an ECollator created for the locale in which your EBookBackend is configured for.

Example 2. Collecting sort keys for a given contact

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    static void
    update_state_from_contact (EBookBackendSmth        *smth,
                               EBookBackendSmthCursor  *cursor,
                               EContact                *contact)
    {
         gint i;

         clear_state (smth, cursor);

         // For each sort key the cursor was created for
         for (i = 0; i < cursor->n_sort_fields; i++) {

                 // Using an ECollator created for the locale
                 // set on your EBookBackend...
                 const gchar *string = e_contact_get_const (contact, cursor->sort_fields[i]);

                 // Generate a sort key for each value
                 if (string)
                         cursor->state->values[i] =
                                 e_collator_generate_key (smth->collator,
                                                          string, NULL);
                 else
                         cursor->state->values[i] = g_strdup ("");
         }

         state->last_uid = e_contact_get (contact, E_CONTACT_UID);
    }

Using the strings collected above for a given contact, two contacts can easily be compared for equality in a locale sensitive way, using `strcmp()` directly on the generated sort keys.

Calls to `EDataBookCursorClass.step()` with the `E_BOOK_CURSOR_ORIGIN_BEGIN` or `E_BOOK_CURSOR_ORIGIN_END` reset the cursor state before fetching results from either the beginning or ending of the result list respectively.

### Implementing Localized Sorting

To implement localized sorting in an addressbook backend, an ECollator can be used. The ECollator provides all the functionality needed to respond to the cursor methods.

When storing contacts in your backend, sort keys should be generated for any fields which might be used as sort key parameters for a cursor, keys for these fields should be generated with `e_collator_generate_key()` using an ECollator created for the locale in which your addressbook is currently configured (undesired fields may be rejected at cursor creation time with an `E_CLIENT_ERROR_INVALID_QUERY` error).

The generated sort keys can then be used with `strcmp()` in order to compare results with the currently stored cursor state. These comparisons should compare contact fields in order of precedence in the array of sort fields which the cursor was configured with. If two contacts match exactly, then the `E_CONTACT_UID` value is used to determine which contact sorts above or below the other.

When your sort keys are generated using ECollator, you can easily use `e_collator_generate_key_for_index()` to implement `EDataBookCursorClass.set_alphabetic_index()` and set the cursor position before (below) a given letter in the active alphabet. The key generated for an alphabetic index is guaranteed to sort below any word starting with the given letter, and above any word starting with the preceeding letter.

### Direct Read Access

In order to support cursors in backends which support Direct Read Access mode, the underlying addressbook data must be written atomically along with each new revision attribute. The cursor mechanics rely on this atomicity in order to avoid any race conditions and ensure that data read back from the addressbook is current and up to date.

### Backend Tasks

Backends have ownership of the cursors which they create and have some responsibility when supporting cursors.

As mentioned above, in Direct Read Access mode (if supported), all revision writes and addressbook modifications must be committed atomically.

Beyond that, it is the responsibility of the backend to call `e_data_book_cursor_contact_added()` and `e_data_book_cursor_contact_removed()` whenever the addressbook is modified. When a contact is modified but not added or removed, then `e_data_book_cursor_contact_removed()` should be called with the old existing contact and then `e_data_book_cursor_contact_added()` should be called with the new contact. This will automatically update the cursor total and position status.

Note that if it's too much trouble to load the existing contact data when a contact is modified, then `e_data_book_cursor_recalculate()` can be called instead. This will use the `EDataBookCursorClass.get_position()` method recalculate current cursor position from scratch.

## Details

### EDataBookCursorSetSexpFunc ()

    gboolean            (*EDataBookCursorSetSexpFunc)       (EDataBookCursor *cursor,
                                                             const gchar *sexp,
                                                             GError **error);

Method type for `EDataBookCursorClass.set_sexp()`

A cursor implementation must implement this in order to modify the search expression for `cursor`. After this is called, the position and total will be recalculated.

If the cursor implementation is unable to deal with the EContactFields referred to in `sexp`, then an `E_CLIENT_ERROR_INVALID_QUERY` error should be set to indicate this.

`cursor` : | an EDataBookCursor
`sexp` : | the search expression to set, or `NULL` for unfiltered results. [allow-none]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | `TRUE` on Success, otherwise `FALSE` is returned if any error occurred and `error` is set to reflect the error which occurred.

Since 3.12

### EDataBookCursorStepFunc ()

    gint                (*EDataBookCursorStepFunc)          (EDataBookCursor *cursor,
                                                             const gchar *revision_guard,
                                                             EBookCursorStepFlags flags,
                                                             EBookCursorOrigin origin,
                                                             gint count,
                                                             GSList **results,
                                                             GCancellable *cancellable,
                                                             GError **error);

Method type for `EDataBookCursorClass.step()`

As all cursor methods may be called either by the addressbook service or directly by a client in Direct Read Access mode, it is important that the operation be an atomic transaction with the underlying database.

The `revision_guard`, if specified, will be set to the `CLIENT_BACKEND_PROPERTY_REVISION` value at the time which the given client issued the call to move the cursor. If the `revision_guard` provided by the client does not match the stored addressbook revision, then an `E_CLIENT_ERROR_OUT_OF_SYNC` error should be set to indicate that the revision was out of sync while attempting to move the cursor.

### Note

If the addressbook backend supports direct read access, then the revision comparison and reading of the data store must be coupled into a single atomic operation (the data read back from the store must be the correct data for the given addressbook revision).

See `e_data_book_cursor_step()` for more details on the expected behaviour of this method.

`cursor` : | an EDataBookCursor
`revision_guard` : | The expected current addressbook revision, or `NULL`. [allow-none]
`flags` : | The EBookCursorStepFlags for this step
`origin` : | The EBookCursorOrigin from whence to step
`count` : | a positive or negative amount of contacts to try and fetch
`results` : | A return location to store the results, or `NULL` if `E_BOOK_CURSOR_STEP_FETCH` is not specified in `flags`. [out][allow-none][element-type utf8][transfer full]
`cancellable` : | A GCancellable. [allow-none]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | The number of contacts traversed if successfull, otherwise -1 is returned and `error` is set.

Since 3.12

### EDataBookCursorSetAlphabetIndexFunc ()

    gboolean            (*EDataBookCursorSetAlphabetIndexFunc)
                                                            (EDataBookCursor *cursor,
                                                             gint index,
                                                             const gchar *locale,
                                                             GError **error);

Method type for `EDataBookCursorClass.set_alphabetic_index()`

Sets the cursor state to point to an index into the active alphabet.

The implementing class must check that `locale` matches the current locale setting of the underlying database and report an `E_CLIENT_ERROR_OUT_OF_SYNC` error in the case that the locales do not match.

`cursor` : | an EDataBookCursor
`index` : | the alphabetic index
`locale` : | the locale in which `index` is expected to be a valid alphabetic index
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | `TRUE` on Success, otherwise `FALSE` is returned if any error occurred and `error` is set to reflect the error which occurred.

Since 3.12

### EDataBookCursorGetPositionFunc ()

    gboolean            (*EDataBookCursorGetPositionFunc)   (EDataBookCursor *cursor,
                                                             gint *total,
                                                             gint *position,
                                                             GCancellable *cancellable,
                                                             GError **error);

Method type for `EDataBookCursorClass.get_position()`

Cursor implementations must implement this to count the total results matching `cursor`'s query expression and to calculate the amount of contacts leading up to the current cursor state (cursor inclusive).

A cursor position is defined as an integer which is inclusive of the current contact to which it points (if the cursor points to an exact contact). A position of `0` indicates that the cursor is situated in a position that is before and after the entire result set. The cursor position should be `0` at creation time, and should start again from the symbolic `0` position whenever `E_BOOK_CURSOR_ORIGIN_BEGIN` is specified in the `EDataBookCursorClass.step()` method (or whenever moving the cursor beyond the end of the result set).

If the cursor is positioned beyond the end of the list, then the position should be the total amount of contacts available in the list (as returned through the `total` argument) plus one.

This method is called by `e_data_book_cursor_recalculate()` and in some other cases where `cursor`'s current position and total must be recalculated from scratch.

`cursor` : | an EDataBookCursor
`total` : | The total number of contacts matching `cursor`'s query expression. [out]
`position` : | The current position of `cursor` in it's result list. [out]
`cancellable` : | A GCancellable. [allow-none]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | `TRUE` on Success, otherwise `FALSE` is returned if any error occurred and `error` is set to reflect the error which occurred.

Since 3.12

### EDataBookCursorCompareContactFunc ()

    gint                (*EDataBookCursorCompareContactFunc)
                                                            (EDataBookCursor *cursor,
                                                             EContact *contact,
                                                             gboolean *matches_sexp);

Method type for `EDataBookCursorClass.compare_contact()`

Cursor implementations must implement this in order to compare a contact with the current cursor state.

This is called when the addressbook backends notify active cursors that the addressbook has been modified with `e_data_book_cursor_contact_added()` and `e_data_book_cursor_contact_removed()`.

`cursor` : | an EDataBookCursor
`contact` : | the EContact to compare with `cursor`
`matches_sexp` : | return location to set whether `contact` matched `cursor`'s search expression. [out][allow-none]
Returns : | A value that is less than, equal to, or greater than zero if `contact` is found, respectively, to be less than, to match, or be greater than the current value of `cursor`.

Since 3.12

### EDataBookCursorLoadLocaleFunc ()

    gboolean            (*EDataBookCursorLoadLocaleFunc)    (EDataBookCursor *cursor,
                                                             gchar **locale,
                                                             GError **error);

Method type for `EDataBookCursorClass.load_locale()`

Fetches the locale setting from `cursor`'s addressbook

If the locale setting has changed, the cursor must reload any internal locale specific data and ensure that comparisons of sort keys will function properly in the new locale.

Upon locale changes, the implementation need not worry about updating it's current cursor state, the cursor state will be reset automatically for you.

`cursor` : | an EDataBookCursor
`locale` : | return location to store the newly loaded locale. [out][transfer full]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | `TRUE` on Success, otherwise `FALSE` is returned if any error occurred and `error` is set to reflect the error which occurred.

Since 3.12

### struct EDataBookCursor

    struct EDataBookCursor {
    	GObject parent;

    	EDataBookCursorPrivate *priv;
    };

An opaque handle for an addressbook cursor

Since 3.12

### struct EDataBookCursorClass

    struct EDataBookCursorClass {
    	EDataBookCursorSetSexpFunc set_sexp;
    	EDataBookCursorStepFunc step;
    	EDataBookCursorSetAlphabetIndexFunc set_alphabetic_index;
    	EDataBookCursorGetPositionFunc get_position;
    	EDataBookCursorCompareContactFunc compare_contact;
    	EDataBookCursorLoadLocaleFunc load_locale;
    };

Methods to implement on an EDataBookCursor concrete class.

EDataBookCursorSetSexpFunc `set_sexp`; | The EDataBookCursorSetSexpFunc delegate to set the search expression
EDataBookCursorStepFunc `step`; | The EDataBookCursorStepFunc delegate to navigate the cursor
EDataBookCursorSetAlphabetIndexFunc `set_alphabetic_index`; | The EDataBookCursorSetAlphabetIndexFunc delegate to set the alphabetic position
EDataBookCursorGetPositionFunc `get_position`; | The EDataBookCursorGetPositionFunc delegate to calculate the current total and position values
EDataBookCursorCompareContactFunc `compare_contact`; | The EDataBookCursorCompareContactFunc delegate to compare an EContact with the the cursor position
EDataBookCursorLoadLocaleFunc `load_locale`; | The EDataBookCursorLoadLocaleFunc delegate used to reload the locale setting

Since 3.12

### e_data_book_cursor_get_backend ()

    struct _EBookBackend * e_data_book_cursor_get_backend   (EDataBookCursor *cursor);

Gets the backend which created and owns `cursor`.

`cursor` : | an EDataBookCursor
Returns : | The EBookBackend owning `cursor`. [transfer none]

Since 3.12

### e_data_book_cursor_get_total ()

    gint                e_data_book_cursor_get_total        (EDataBookCursor *cursor);

Fetch the total number of contacts which match `cursor`'s query expression.

`cursor` : | an EDataBookCursor
Returns : | the total contacts for `cursor`

Since 3.12

### e_data_book_cursor_get_position ()

    gint                e_data_book_cursor_get_position     (EDataBookCursor *cursor);

Fetch the current position of `cursor` in its result list.

`cursor` : | an EDataBookCursor
Returns : | the current position of `cursor`

Since 3.12

### e_data_book_cursor_set_sexp ()

    gboolean            e_data_book_cursor_set_sexp         (EDataBookCursor *cursor,
                                                             const gchar *sexp,
                                                             GCancellable *cancellable,
                                                             GError **error);

Sets the search expression for the cursor

`cursor` : | an EDataBookCursor
`sexp` : | the search expression to set. [allow-none]
`cancellable` : | A GCancellable. [allow-none]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | `TRUE` on success, otherwise `FALSE` is returned and `error` is set.

Since 3.12

### e_data_book_cursor_step ()

    gint                e_data_book_cursor_step             (EDataBookCursor *cursor,
                                                             const gchar *revision_guard,
                                                             EBookCursorStepFlags flags,
                                                             EBookCursorOrigin origin,
                                                             gint count,
                                                             GSList **results,
                                                             GCancellable *cancellable,
                                                             GError **error);

Steps `cursor` through it's sorted query by a maximum of `count` contacts starting from `origin`.

If `count` is negative, then the cursor will move through the list in reverse.

If `cursor` reaches the beginning or end of the query results, then the returned list might not contain the amount of desired contacts, or might return no results if the cursor currently points to the last contact. Reaching the end of the list is not considered an error condition. Attempts to step beyond the end of the list after having reached the end of the list will however trigger an `E_CLIENT_ERROR_QUERY_REFUSED` error.

If `E_BOOK_CURSOR_STEP_FETCH` is specified in `flags`, a pointer to a `NULL` GSList pointer should be provided for the `results` parameter.

The result list will be stored to `results` and should be freed with `g_slist_free()` and all elements freed with `g_free()`.

If a `revision_guard` is specified, the cursor implementation will issue an `E_CLIENT_ERROR_OUT_OF_SYNC` error if the `revision_guard` does not match the current addressbook revision.

An explanation of how stepping is expected to behave can be found in the user facing reference documentation.

`cursor` : | an EDataBookCursor
`revision_guard` : | The expected current addressbook revision, or `NULL`
`flags` : | The EBookCursorStepFlags for this step
`origin` : | The EBookCursorOrigin from whence to step
`count` : | a positive or negative amount of contacts to try and fetch
`results` : | A return location to store the results, or `NULL` if `E_BOOK_CURSOR_STEP_FETCH` is not specified in `flags`. [out][allow-none][element-type utf8][transfer full]
`cancellable` : | A GCancellable. [allow-none]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | The number of contacts traversed if successful, otherwise -1 is returned and `error` is set.

Since 3.12

### e_data_book_cursor_set_alphabetic_index ()

    gboolean            e_data_book_cursor_set_alphabetic_index
                                                            (EDataBookCursor *cursor,
                                                             gint index,
                                                             const gchar *locale,
                                                             GCancellable *cancellable,
                                                             GError **error);

Sets the `cursor` position to an Alphabetic Index into the alphabet active in the `locale` of the addressbook.

After setting the target to an alphabetic index, for example the index for letter 'E', then further calls to `e_data_book_cursor_step()` will return results starting with the letter 'E' (or results starting with the last result in 'D', if moving in a negative direction).

The passed index must be a valid index in `locale`, if by some chance the addressbook backend has changed into a new locale after this call has been issued, an `E_CLIENT_ERROR_OUT_OF_SYNC` error will be issued indicating that there was a locale mismatch.

`cursor` : | an EDataBookCursor
`index` : | the alphabetic index
`locale` : | the locale in which `index` is expected to be a valid alphabetic index
`cancellable` : | A GCancellable. [allow-none]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | `TRUE` on success, otherwise `FALSE` is returned and `error` is set.

Since 3.12

### e_data_book_cursor_recalculate ()

    gboolean            e_data_book_cursor_recalculate      (EDataBookCursor *cursor,
                                                             GCancellable *cancellable,
                                                             GError **error);

Recalculates the cursor's total and position, this is meant for cursor created in Direct Read Access mode to synchronously recalculate the position and total values when the addressbook revision has changed.

`cursor` : | an EDataBookCursor
`cancellable` : | A GCancellable. [allow-none]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | `TRUE` on success, otherwise `FALSE` is returned and `error` is set.

Since 3.12

### e_data_book_cursor_load_locale ()

    gboolean            e_data_book_cursor_load_locale      (EDataBookCursor *cursor,
                                                             gchar **locale,
                                                             GCancellable *cancellable,
                                                             GError **error);

Load the current locale setting from the cursor's underlying database.

Addressbook backends implementing cursors should call this function on all active cursor when the locale setting changes.

This will implicitly reset `cursor`'s state and position.

`cursor` : | an EDataBookCursor
`locale` : | return location for the locale. [out][allow-none]
`cancellable` : | A GCancellable. [allow-none]
`error` : | return location for a GError, or `NULL`. [out][allow-none]
Returns : | `TRUE` on success, otherwise `FALSE` is returned and `error` is set.

Since 3.12

### e_data_book_cursor_contact_added ()

    void                e_data_book_cursor_contact_added    (EDataBookCursor *cursor,
                                                             EContact *contact);

Should be called by addressbook backends whenever a contact is added.

`cursor` : | an EDataBookCursor
`contact` : | the EContact which was added to the addressbook

Since 3.12

### e_data_book_cursor_contact_removed ()

    void                e_data_book_cursor_contact_removed  (EDataBookCursor *cursor,
                                                             EContact *contact);

Should be called by addressbook backends whenever a contact is removed.

`cursor` : | an EDataBookCursor
`contact` : | the EContact which was removed from the addressbook

Since 3.12

### e_data_book_cursor_register_gdbus_object ()

    gboolean            e_data_book_cursor_register_gdbus_object
                                                            (EDataBookCursor *cursor,
                                                             GDBusConnection *connection,
                                                             const gchar *object_path,
                                                             GError **error);

Places `cursor` on the `connection` at `object_path`

`cursor` : | an EDataBookCursor
`connection` : | the GDBusConnection to register with
`object_path` : | the object path to place the direct access configuration data
`error` : | a location to store any error which might occur while registering. [out][allow-none]
Returns : | `TRUE` on success, otherwise `FALSE` is returned and `error` is set.

Since 3.12