[evolution-data-server/wip/offline-cache] Add unfinished ECalCache descendant of ECache



commit 5621ec0e9fe047b24e05bd5d0d449d4e90965d47
Author: Milan Crha <mcrha redhat com>
Date:   Tue Feb 14 10:40:08 2017 +0100

    Add unfinished ECalCache descendant of ECache

 po/POTFILES.in                                 |    1 +
 src/addressbook/libedata-book/e-book-cache.c   |  136 ++-
 src/addressbook/libedata-book/e-book-cache.h   |   35 +-
 src/calendar/libedata-cal/CMakeLists.txt       |    2 +
 src/calendar/libedata-cal/e-cal-backend-sexp.c |   54 +-
 src/calendar/libedata-cal/e-cal-cache.c        | 2105 ++++++++++++++++++++++++
 src/calendar/libedata-cal/e-cal-cache.h        |  278 ++++
 src/calendar/libedata-cal/libedata-cal.h       |    1 +
 src/libebackend/e-cache.c                      |  314 ++++-
 src/libebackend/e-cache.h                      |   41 +-
 src/libedataserver/e-data-server-util.c        |   46 +
 src/libedataserver/e-data-server-util.h        |    1 +
 12 files changed, 2925 insertions(+), 89 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2f82f57..483e797 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -39,6 +39,7 @@ src/calendar/libedata-cal/e-cal-backend.c
 src/calendar/libedata-cal/e-cal-backend-sexp.c
 src/calendar/libedata-cal/e-cal-backend-sync.c
 src/calendar/libedata-cal/e-cal-backend-util.c
+src/calendar/libedata-cal/e-cal-cache.c
 src/calendar/libedata-cal/e-data-cal.c
 src/calendar/libedata-cal/e-data-cal-factory.c
 src/camel/camel-address.c
diff --git a/src/addressbook/libedata-book/e-book-cache.c b/src/addressbook/libedata-book/e-book-cache.c
index ff4808c..0fbdaff 100644
--- a/src/addressbook/libedata-book/e-book-cache.c
+++ b/src/addressbook/libedata-book/e-book-cache.c
@@ -25,7 +25,7 @@
  * The #EBookCache is an API for storing and looking up #EContacts
  * in an #ECache. It also supports cursors.
  *
- * The API is thread safe, in the similar was as the #ECache is.
+ * The API is thread safe, in the similar way as the #ECache is.
  *
  * Any operations which can take a lot of time to complete (depending
  * on the size of your addressbook) can be cancelled using a #GCancellable.
@@ -145,7 +145,7 @@ G_DEFINE_BOXED_TYPE (EBookCacheSearchData, e_book_cache_search_data, e_book_cach
  * e_book_cache_search_data_new:
  * @uid: a contact UID; cannot be %NULL
  * @vcard: the contact as a vCard string; cannot be %NULL
- * @extra: (nullable): any extra data stoed with the contact, or %NULL
+ * @extra: (nullable): any extra data stored with the contact, or %NULL
  *
  * Creates a new EBookCacheSearchData prefilled with the given values.
  *
@@ -836,7 +836,7 @@ remove_leading_zeros (gchar *number)
 static void
 ebc_fill_other_columns (EBookCache *book_cache,
                        EContact *contact,
-                       GHashTable *other_columns)
+                       ECacheColumnValues *other_columns)
 {
        gint ii;
 
@@ -860,7 +860,7 @@ ebc_fill_other_columns (EBookCache *book_cache,
                        val = e_contact_get (contact, field->field_id);
                        normal = e_util_utf8_normalize (val);
 
-                       g_hash_table_insert (other_columns, (gpointer) field->dbname, normal);
+                       e_cache_column_values_take_value (other_columns, field->dbname, normal);
 
                        if ((field->index & INDEX_FLAG (SORT_KEY)) != 0) {
                                if (val)
@@ -868,7 +868,7 @@ ebc_fill_other_columns (EBookCache *book_cache,
                                else
                                        str = g_strdup ("");
 
-                               g_hash_table_insert (other_columns, field->dbname_idx_sort_key, str);
+                               e_cache_column_values_take_value (other_columns, field->dbname_idx_sort_key, 
str);
                        }
 
                        if ((field->index & INDEX_FLAG (SUFFIX)) != 0) {
@@ -877,7 +877,7 @@ ebc_fill_other_columns (EBookCache *book_cache,
                                else
                                        str = NULL;
 
-                               g_hash_table_insert (other_columns, field->dbname_idx_suffix, str);
+                               e_cache_column_values_take_value (other_columns, field->dbname_idx_suffix, 
str);
                        }
 
                        if ((field->index & INDEX_FLAG (PHONE)) != 0) {
@@ -886,11 +886,11 @@ ebc_fill_other_columns (EBookCache *book_cache,
                                str = convert_phone (normal, book_cache->priv->region_code, &country_code);
                                str = remove_leading_zeros (str);
 
-                               g_hash_table_insert (other_columns, field->dbname_idx_phone, str);
+                               e_cache_column_values_take_value (other_columns, field->dbname_idx_phone, 
str);
 
                                str = g_strdup_printf ("%d", country_code);
 
-                               g_hash_table_insert (other_columns, field->dbname_idx_country, str);
+                               e_cache_column_values_take_value (other_columns, field->dbname_idx_country, 
str);
                        }
 
                        g_free (val);
@@ -899,7 +899,7 @@ ebc_fill_other_columns (EBookCache *book_cache,
 
                        val = e_contact_get (contact, field->field_id) ? TRUE : FALSE;
 
-                       g_hash_table_insert (other_columns, (gpointer) field->dbname, g_strdup_printf ("%d", 
val ? 1 : 0));
+                       e_cache_column_values_take_value (other_columns, field->dbname, g_strdup_printf 
("%d", val ? 1 : 0));
                } else if (field->type == E_TYPE_CONTACT_CERT) {
                        EContactCert *cert = NULL;
 
@@ -907,7 +907,7 @@ ebc_fill_other_columns (EBookCache *book_cache,
 
                        /* We don't actually store the cert; only a boolean to indicate
                         * that is *has* a cert. */
-                       g_hash_table_insert (other_columns, (gpointer) field->dbname, g_strdup_printf ("%d", 
cert ? 1 : 0));
+                       e_cache_column_values_take_value (other_columns, field->dbname, g_strdup_printf 
("%d", cert ? 1 : 0));
                        e_contact_cert_free (cert);
                } else if (field->type != E_TYPE_CONTACT_ATTR_LIST) {
                        g_warn_if_reached ();
@@ -1262,11 +1262,11 @@ ebc_upgrade_cb (ECache *cache,
                gchar **out_revision,
                gchar **out_object,
                EOfflineState *out_offline_state,
-               GHashTable **out_other_columns,
+               ECacheColumnValues **out_other_columns,
                gpointer user_data)
 {
        EContact *contact;
-       GHashTable *other_columns;
+       ECacheColumnValues *other_columns;
 
        g_return_val_if_fail (E_IS_BOOK_CACHE (cache), FALSE);
 
@@ -1276,7 +1276,7 @@ ebc_upgrade_cb (ECache *cache,
        if (!contact)
                return TRUE;
 
-       other_columns = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, NULL, g_free);
+       other_columns = e_cache_column_values_new ();
 
        ebc_fill_other_columns (E_BOOK_CACHE (cache), contact, other_columns);
 
@@ -3013,12 +3013,12 @@ ebc_search_uids_cb (ECache *cache,
        *out_list = g_slist_prepend (*out_list, g_strdup (uid));
 }
 
-typedef void (* EBookCacheSearchFunc)  (ECache *cache,
-                                        const gchar *uid,
-                                        const gchar *revision,
-                                        const gchar *object,
-                                        const gchar *extra,
-                                        gpointer out_value);
+typedef void (* EBookCacheInternalSearchFunc)  (ECache *cache,
+                                                const gchar *uid,
+                                                const gchar *revision,
+                                                const gchar *object,
+                                                const gchar *extra,
+                                                gpointer out_value);
 
 /* Generates the SELECT portion of the query, this will take care of
  * preparing the context of the query, and add the needed JOIN statements
@@ -3027,14 +3027,14 @@ typedef void (* EBookCacheSearchFunc)   (ECache *cache,
  * This also handles getting the correct callback and asking for the
  * right data depending on the 'search_type'
  */
-static EBookCacheSearchFunc
+static EBookCacheInternalSearchFunc
 ebc_generate_select (EBookCache *book_cache,
                     GString *string,
                     SearchType search_type,
                     PreflightContext *context,
                     GError **error)
 {
-       EBookCacheSearchFunc callback = NULL;
+       EBookCacheInternalSearchFunc callback = NULL;
        gboolean add_auxiliary_tables = FALSE;
        gint ii;
 
@@ -3052,6 +3052,7 @@ ebc_generate_select (EBookCache *book_cache,
                g_string_append (string, "summary." E_CACHE_COLUMN_UID ",");
                g_string_append (string, "summary." E_CACHE_COLUMN_REVISION ",");
                g_string_append (string, "summary." E_CACHE_COLUMN_OBJECT ",");
+               g_string_append (string, "summary." E_CACHE_COLUMN_STATE ",");
                g_string_append (string, "summary." EBC_COLUMN_EXTRA " ");
                break;
        case SEARCH_UID_AND_REV:
@@ -3149,7 +3150,7 @@ ebc_is_autocomplete_query (PreflightContext *context)
        return non_aux_fields != 0;
 }
 
-static EBookCacheSearchFunc
+static EBookCacheInternalSearchFunc
 ebc_generate_autocomplete_query (EBookCache *book_cache,
                                 GString *string,
                                 SearchType search_type,
@@ -3160,7 +3161,7 @@ ebc_generate_autocomplete_query (EBookCache *book_cache,
        gint n_elements, ii;
        guint64 aux_mask = context->aux_mask;
        guint64 left_join_mask = context->left_join_mask;
-       EBookCacheSearchFunc callback;
+       EBookCacheInternalSearchFunc callback;
        gboolean first = TRUE;
 
        elements = (QueryElement **) context->constraints->pdata;
@@ -3238,9 +3239,13 @@ struct EBCSearchData {
        gint revision_index;
        gint object_index;
        gint extra_index;
+       gint state_index;
 
-       EBookCacheSearchFunc func;
+       EBookCacheInternalSearchFunc func;
        gpointer out_value;
+
+       EBookCacheSearchFunc user_func;
+       gpointer user_func_user_data;
 };
 
 static gboolean
@@ -3252,21 +3257,24 @@ ebc_search_select_cb (ECache *cache,
 {
        struct EBCSearchData *sd = user_data;
        const gchar *object = NULL, *extra = NULL;
+       EOfflineState offline_state = E_OFFLINE_STATE_UNKNOWN;
 
        g_return_val_if_fail (sd != NULL, FALSE);
-       g_return_val_if_fail (sd->func != NULL, FALSE);
-       g_return_val_if_fail (sd->out_value != NULL, FALSE);
+       g_return_val_if_fail (sd->func != NULL || sd->user_func != NULL, FALSE);
+       g_return_val_if_fail (sd->out_value != NULL || sd->user_func != NULL, FALSE);
 
        if (sd->uid_index == -1 ||
            sd->revision_index == -1 ||
            sd->object_index == -1 ||
-           sd->extra_index == -1) {
+           sd->extra_index == -1 ||
+           sd->state_index == -1) {
                gint ii;
 
                for (ii = 0; ii < ncols && (sd->uid_index == -1 ||
                     sd->revision_index == -1 ||
                     sd->object_index == -1 ||
-                    sd->extra_index == -1); ii++) {
+                    sd->extra_index == -1 ||
+                    sd->state_index == -1); ii++) {
                        const gchar *cname = column_names[ii];
 
                        if (!cname)
@@ -3283,6 +3291,8 @@ ebc_search_select_cb (ECache *cache,
                                sd->object_index = ii;
                        } else if (sd->extra_index == -1 && g_ascii_strcasecmp (cname, EBC_COLUMN_EXTRA) == 
0) {
                                sd->extra_index = ii;
+                       } else if (sd->state_index == -1 && g_ascii_strcasecmp (cname, E_CACHE_COLUMN_STATE) 
== 0) {
+                               sd->state_index = ii;
                        }
                }
        }
@@ -3300,6 +3310,20 @@ ebc_search_select_cb (ECache *cache,
                extra = column_values[sd->extra_index];
        }
 
+       if (sd->state_index != -2) {
+               g_return_val_if_fail (sd->extra_index >= 0 && sd->extra_index < ncols, FALSE);
+
+               if (!column_values[sd->state_index])
+                       offline_state = E_OFFLINE_STATE_UNKNOWN;
+               else
+                       offline_state = g_ascii_strtoull (column_values[sd->state_index], NULL, 10);
+       }
+
+       if (sd->user_func) {
+               return sd->user_func (cache, column_values[sd->uid_index], column_values[sd->revision_index],
+                       object, extra, offline_state, sd->user_func_user_data);
+       }
+
        sd->func (cache, column_values[sd->uid_index], column_values[sd->revision_index], object, extra, 
sd->out_value);
 
        return TRUE;
@@ -3311,6 +3335,8 @@ ebc_do_search_query (EBookCache *book_cache,
                     const gchar *sexp,
                     SearchType search_type,
                     gpointer out_value,
+                    EBookCacheSearchFunc func,
+                    gpointer func_user_data,
                     GCancellable *cancellable,
                     GError **error)
 {
@@ -3355,7 +3381,10 @@ ebc_do_search_query (EBookCache *book_cache,
                sd.revision_index = -1;
                sd.object_index = search_type == SEARCH_FULL ? -1 : -2;
                sd.extra_index = search_type == SEARCH_UID ? -2 : -1;
+               sd.state_index = search_type == SEARCH_FULL ? -1 : -2;
                sd.out_value = out_value;
+               sd.user_func = func;
+               sd.user_func_user_data = func_user_data;
 
                success = e_cache_sqlite_select (E_CACHE (book_cache), stmt->str,
                        ebc_search_select_cb, &sd, cancellable, error);
@@ -3371,6 +3400,8 @@ ebc_search_internal (EBookCache *book_cache,
                     const gchar *sexp,
                     SearchType search_type,
                     gpointer out_value,
+                    EBookCacheSearchFunc func,
+                    gpointer func_user_data,
                     GCancellable *cancellable,
                     GError **error)
 {
@@ -3387,7 +3418,7 @@ ebc_search_internal (EBookCache *book_cache,
                /* No errors, let's really search */
                success = ebc_do_search_query (
                        book_cache, &context, sexp,
-                       search_type, out_value,
+                       search_type, out_value, func, func_user_data,
                        cancellable, error);
                break;
 
@@ -4707,7 +4738,7 @@ e_book_cache_put_contact (EBookCache *book_cache,
  * @book_cache: An #EBookCache
  * @contacts: (element-type EContact): A list of contacts to add to @book_cache
  * @extras: (nullable) (element-type utf8): A list of extra data to store in association with the @contacts
- * @offline_flag: one of #ECacheOfflineFlag offline_flag, whether putting these contacts in offline
+ * @offline_flag: one of #ECacheOfflineFlag, whether putting these contacts in offline
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
@@ -4731,7 +4762,7 @@ e_book_cache_put_contacts (EBookCache *book_cache,
 {
        const GSList *clink, *elink;
        ECache *cache;
-       GHashTable *other_columns;
+       ECacheColumnValues *other_columns;
        gboolean success;
 
        g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
@@ -4739,7 +4770,7 @@ e_book_cache_put_contacts (EBookCache *book_cache,
        g_return_val_if_fail (extras == NULL || g_slist_length ((GSList *) extras) == g_slist_length ((GSList 
*) contacts), FALSE);
 
        cache = E_CACHE (book_cache);
-       other_columns = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, NULL, g_free);
+       other_columns = e_cache_column_values_new ();
 
        e_cache_lock (cache, E_CACHE_LOCK_WRITE);
 
@@ -4753,10 +4784,10 @@ e_book_cache_put_contacts (EBookCache *book_cache,
                vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
                g_return_val_if_fail (vcard != NULL, FALSE);
 
-               g_hash_table_remove_all (other_columns);
+               e_cache_column_values_remove_all (other_columns);
 
                if (extra)
-                       g_hash_table_insert (other_columns, (gpointer) EBC_COLUMN_EXTRA, g_strdup (extra));
+                       e_cache_column_values_take_value (other_columns, EBC_COLUMN_EXTRA, g_strdup (extra));
 
                uid = e_contact_get (contact, E_CONTACT_UID);
                rev = e_contact_get (contact, E_CONTACT_REV);
@@ -4775,7 +4806,7 @@ e_book_cache_put_contacts (EBookCache *book_cache,
 
        e_cache_unlock (cache, success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
 
-       g_hash_table_destroy (other_columns);
+       e_cache_column_values_free (other_columns);
 
        return success;
 }
@@ -5105,7 +5136,7 @@ e_book_cache_search (EBookCache *book_cache,
 
        return ebc_search_internal (book_cache, sexp,
                meta_contacts ? SEARCH_UID_AND_REV : SEARCH_FULL,
-               out_list, cancellable, error);
+               out_list, NULL, NULL, cancellable, error);
 }
 
 /**
@@ -5137,7 +5168,36 @@ e_book_cache_search_uids (EBookCache *book_cache,
 
        *out_list = NULL;
 
-       return ebc_search_internal (book_cache, sexp, SEARCH_UID, out_list, cancellable, error);
+       return ebc_search_internal (book_cache, sexp, SEARCH_UID, out_list, NULL, NULL, cancellable, error);
+}
+
+/**
+ * e_book_cache_search_with_callback:
+ * @book_cache: An #EBookCache
+ * @sexp: (nullable): search expression; use %NULL or an empty string to get all stored contacts
+ * @func: an #EBookCacheSearchFunc callback to call for each found row
+ * @user_data: user data for @func
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Similar to e_book_cache_search(), but calls the @func for each found contact.
+ *
+ * Returns: %TRUE on success, otherwise %FALSE is returned and @error is set appropriately.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_book_cache_search_with_callback (EBookCache *book_cache,
+                                  const gchar *sexp,
+                                  EBookCacheSearchFunc func,
+                                  gpointer user_data,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       g_return_val_if_fail (E_IS_BOOK_CACHE (book_cache), FALSE);
+       g_return_val_if_fail (func != NULL, FALSE);
+
+       return ebc_search_internal (book_cache, sexp, SEARCH_FULL, NULL, func, user_data, cancellable, error);
 }
 
 /**
@@ -5804,7 +5864,7 @@ e_book_cache_put_locked (ECache *cache,
                         const gchar *uid,
                         const gchar *revision,
                         const gchar *object,
-                        GHashTable *other_columns,
+                        ECacheColumnValues *other_columns,
                         EOfflineState offline_state,
                         gboolean is_replace,
                         GCancellable *cancellable,
diff --git a/src/addressbook/libedata-book/e-book-cache.h b/src/addressbook/libedata-book/e-book-cache.h
index 994a429..760ce4a 100644
--- a/src/addressbook/libedata-book/e-book-cache.h
+++ b/src/addressbook/libedata-book/e-book-cache.h
@@ -63,7 +63,8 @@ typedef struct _EBookCachePrivate EBookCachePrivate;
  * such as e_book_cache_search().
  *
  * The @extra parameter will contain any data which was
- * previously passed for this contact in e_book_cache_add_contact().
+ * previously passed for this contact in e_book_cache_put_contact()
+ * or set with e_book_cache_set_contact_extra().
  *
  * These should be freed with e_book_cache_search_data_free().
  *
@@ -88,6 +89,31 @@ EBookCacheSearchData *
 void           e_book_cache_search_data_free   (/* EBookCacheSearchData * */ gpointer data);
 
 /**
+ * EBookCacheSearchFunc:
+ * @cache: an #ECache
+ * @uid: a unique object identifier
+ * @revision: the object revision
+ * @object: the object itself
+ * @extra: extra data stored with the object
+ * @offline_state: objects offline state, one of #EOfflineState
+ * @user_data: user data, as used in e_book_cache_search_with_callback()
+ *
+ * A callback called for each object row when using
+ * e_book_cache_search_with_callback() function.
+ *
+ * Returns: %TRUE to continue, %FALSE to stop walk through.
+ *
+ * Since: 3.26
+ **/
+typedef gboolean (* EBookCacheSearchFunc)      (ECache *cache,
+                                                const gchar *uid,
+                                                const gchar *revision,
+                                                const gchar *object,
+                                                const gchar *extra,
+                                                EOfflineState offline_state,
+                                                gpointer user_data);
+
+/**
  * EBookCache:
  *
  * Contains only private data that should be read and manipulated using
@@ -240,6 +266,13 @@ gboolean   e_book_cache_search_uids        (EBookCache *book_cache,
                                                 GSList **out_list,
                                                 GCancellable *cancellable,
                                                 GError **error);
+gboolean       e_book_cache_search_with_callback
+                                               (EBookCache *book_cache,
+                                                const gchar *sexp,
+                                                EBookCacheSearchFunc func,
+                                                gpointer user_data,
+                                                GCancellable *cancellable,
+                                                GError **error);
 /* Cursor API */
 EBookCacheCursor *
                e_book_cache_cursor_new         (EBookCache *book_cache,
diff --git a/src/calendar/libedata-cal/CMakeLists.txt b/src/calendar/libedata-cal/CMakeLists.txt
index 484c21c..86109af 100644
--- a/src/calendar/libedata-cal/CMakeLists.txt
+++ b/src/calendar/libedata-cal/CMakeLists.txt
@@ -17,6 +17,7 @@ set(SOURCES
        e-cal-backend-sync.c
        e-cal-backend-util.c
        e-cal-backend-store.c
+       e-cal-cache.c
        e-data-cal.c
        e-data-cal-factory.c
        e-data-cal-view.c
@@ -32,6 +33,7 @@ set(HEADERS
        e-cal-backend-sync.h
        e-cal-backend-util.h
        e-cal-backend-sexp.h
+       e-cal-cache.h
        e-data-cal.h
        e-data-cal-factory.h
        e-cal-backend-store.h
diff --git a/src/calendar/libedata-cal/e-cal-backend-sexp.c b/src/calendar/libedata-cal/e-cal-backend-sexp.c
index 5da3e40..4f53ef1 100644
--- a/src/calendar/libedata-cal/e-cal-backend-sexp.c
+++ b/src/calendar/libedata-cal/e-cal-backend-sexp.c
@@ -427,8 +427,8 @@ matches_attendee (ECalComponent *comp,
        for (l = a_list; l; l = l->next) {
                ECalComponentAttendee *att = l->data;
 
-               if ((att->value && e_util_strstrcase (att->value, str)) || (att->cn != NULL &&
-                                       e_util_strstrcase (att->cn, str))) {
+               if ((att->value && e_util_utf8_strstrcasedecomp (att->value, str)) ||
+                   (att->cn != NULL && e_util_utf8_strstrcasedecomp (att->cn, str))) {
                        matches = TRUE;
                        break;
                }
@@ -451,8 +451,8 @@ matches_organizer (ECalComponent *comp,
        if (str && !*str)
                return TRUE;
 
-       if ((org.value && e_util_strstrcase (org.value, str)) ||
-                       (org.cn && e_util_strstrcase (org.cn, str)))
+       if ((org.value && e_util_utf8_strstrcasedecomp (org.value, str)) ||
+           (org.cn && e_util_utf8_strstrcasedecomp (org.cn, str)))
                return TRUE;
 
        return FALSE;
@@ -565,24 +565,34 @@ matches_status (ECalComponent *comp ,const gchar *str)
 
        e_cal_component_get_status (comp, &status);
 
-       if (g_str_equal (str, "NOT STARTED") && status == ICAL_STATUS_NONE)
-                       return TRUE;
-       else if (g_str_equal (str, "COMPLETED") && status == ICAL_STATUS_COMPLETED)
-                       return TRUE;
-       else if (g_str_equal (str, "CANCELLED") && status == ICAL_STATUS_CANCELLED)
-                       return TRUE;
-       else if (g_str_equal (str, "IN PROGRESS") && status == ICAL_STATUS_INPROCESS)
-                       return TRUE;
-       else if (g_str_equal (str, "NEEDS ACTION") && status == ICAL_STATUS_NEEDSACTION)
-                       return TRUE;
-       else if (g_str_equal (str, "TENTATIVE") && status == ICAL_STATUS_TENTATIVE)
-                       return TRUE;
-       else if (g_str_equal (str, "CONFIRMED") && status == ICAL_STATUS_CONFIRMED)
-                       return TRUE;
-       else if (g_str_equal (str, "DRAFT") && status == ICAL_STATUS_DRAFT)
-                       return TRUE;
-       else if (g_str_equal (str, "FINAL") && status == ICAL_STATUS_FINAL)
-                       return TRUE;
+       switch (status) {
+       case ICAL_STATUS_NONE:
+               return g_str_equal (str, "NOT STARTED");
+       case ICAL_STATUS_COMPLETED:
+               return g_str_equal (str, "COMPLETED");
+       case ICAL_STATUS_CANCELLED:
+               return g_str_equal (str, "CANCELLED");
+       case ICAL_STATUS_INPROCESS:
+               return g_str_equal (str, "IN PROGRESS");
+       case ICAL_STATUS_NEEDSACTION:
+               return g_str_equal (str, "NEEDS ACTION");
+       case ICAL_STATUS_TENTATIVE:
+               return g_str_equal (str, "TENTATIVE");
+       case ICAL_STATUS_CONFIRMED:
+               return g_str_equal (str, "CONFIRMED");
+       case ICAL_STATUS_DRAFT:
+               return g_str_equal (str, "DRAFT");
+       case ICAL_STATUS_FINAL:
+               return g_str_equal (str, "FINAL");
+       case ICAL_STATUS_SUBMITTED:
+               return g_str_equal (str, "SUBMITTED");
+       case ICAL_STATUS_PENDING:
+               return g_str_equal (str, "PENDING");
+       case ICAL_STATUS_FAILED:
+               return g_str_equal (str, "FAILED");
+       case ICAL_STATUS_X:
+               break;
+       }
 
        return FALSE;
 }
diff --git a/src/calendar/libedata-cal/e-cal-cache.c b/src/calendar/libedata-cal/e-cal-cache.c
new file mode 100644
index 0000000..bde0ed3
--- /dev/null
+++ b/src/calendar/libedata-cal/e-cal-cache.c
@@ -0,0 +1,2105 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION: e-cal-cache
+ * @include: libedata-cal/libedata-cal.h
+ * @short_description: An #ECache descendant for calendars
+ *
+ * The #ECalCache is an API for storing and looking up calendar
+ * components in an #ECache.
+ *
+ * The API is thread safe, in the similar way as the #ECache is.
+ *
+ * Any operations which can take a lot of time to complete (depending
+ * on the size of your calendar) can be cancelled using a #GCancellable.
+ **/
+
+#include "evolution-data-server-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <libebackend/libebackend.h>
+#include <libecal/libecal.h>
+
+#include "e-cal-cache.h"
+
+#define E_CAL_CACHE_VERSION            1
+
+#define ECC_TABLE_TIMEZONES            "timezones"
+
+#define ECC_COLUMN_OCCUR_START         "occur_start"
+#define ECC_COLUMN_OCCUR_END           "occur_end"
+#define ECC_COLUMN_DUE                 "due"
+#define ECC_COLUMN_COMPLETED           "completed"
+#define ECC_COLUMN_SUMMARY             "summary"
+#define ECC_COLUMN_COMMENT             "comment"
+#define ECC_COLUMN_DESCRIPTION         "description"
+#define ECC_COLUMN_LOCATION            "location"
+#define ECC_COLUMN_ATTENDEES           "attendees"
+#define ECC_COLUMN_ORGANIZER           "organizer"
+#define ECC_COLUMN_CLASSIFICATION      "classification"
+#define ECC_COLUMN_STATUS              "status"
+#define ECC_COLUMN_PRIORITY            "priority"
+#define ECC_COLUMN_CATEGORIES          "categories"
+#define ECC_COLUMN_HAS_ALARM           "has_alarm"
+#define ECC_COLUMN_HAS_START           "has_start"
+#define ECC_COLUMN_HAS_RECURRENCES     "has_recurrences"
+#define ECC_COLUMN_EXTRA               "bdata"
+
+struct _ECalCachePrivate {
+       GHashTable *loaded_timezones; /* gchar *tzid ~> icaltimezone * */
+       GMutex loaded_timezones_lock;
+};
+
+enum {
+       DUP_COMPONENT_REVISION,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (ECalCache, e_cal_cache, E_TYPE_CACHE,
+                        G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
+
+G_DEFINE_BOXED_TYPE (ECalCacheSearchData, e_cal_cache_search_data, e_cal_cache_search_data_copy, 
e_cal_cache_search_data_free)
+
+/**
+ * e_cal_cache_search_data_new:
+ * @uid: a component UID; cannot be %NULL
+ * @rid: (nullable): a component Recurrence-ID; can be %NULL
+ * @object: the component as an iCal string; cannot be %NULL
+ * @extra: (nullable): any extra data stored with the component, or %NULL
+ *
+ * Creates a new ECalCacheSearchData prefilled with the given values.
+ *
+ * Returns: (transfer full): A new #ECalCacheSearchData. Free it with
+ *    e_cal_cache_search_data_free() when no longer needed.
+ *
+ * Since: 3.26
+ **/
+ECalCacheSearchData *
+e_cal_cache_search_data_new (const gchar *uid,
+                            const gchar *rid,
+                            const gchar *object,
+                            const gchar *extra)
+{
+       ECalCacheSearchData *data;
+
+       g_return_val_if_fail (uid != NULL, NULL);
+       g_return_val_if_fail (object != NULL, NULL);
+
+       data = g_new0 (ECalCacheSearchData, 1);
+       data->uid = g_strdup (uid);
+       data->rid = (rid && *rid) ? g_strdup (rid) : NULL;
+       data->object = g_strdup (object);
+       data->extra = g_strdup (extra);
+
+       return data;
+}
+
+/**
+ * e_cal_cache_search_data_copy:
+ * @data: (nullable): a source #ECalCacheSearchData to copy, or %NULL
+ *
+ * Returns: (transfer full): Copy of the given @data. Free it with
+ *    e_cal_cache_search_data_free() when no longer needed.
+ *    If the @data is %NULL, then returns %NULL as well.
+ *
+ * Since: 3.26
+ **/
+ECalCacheSearchData *
+e_cal_cache_search_data_copy (const ECalCacheSearchData *data)
+{
+       if (!data)
+               return NULL;
+
+       return e_cal_cache_search_data_new (data->uid, data->rid, data->object, data->extra);
+}
+
+/**
+ * e_cal_cache_search_data_free:
+ * @data: (nullable): an #ECalCacheSearchData
+ *
+ * Frees the @data structure, previously allocated with e_cal_cache_search_data_new()
+ * or e_cal_cache_search_data_copy().
+ *
+ * Since: 3.26
+ **/
+void
+e_cal_cache_search_data_free (gpointer ptr)
+{
+       ECalCacheSearchData *data = ptr;
+
+       if (data) {
+               g_free (data->uid);
+               g_free (data->rid);
+               g_free (data->object);
+               g_free (data->extra);
+               g_free (data);
+       }
+}
+
+static gboolean
+e_cal_cache_get_string (ECache *cache,
+                       gint ncols,
+                       const gchar **column_names,
+                       const gchar **column_values,
+                       gpointer user_data)
+{
+       gchar **pvalue = user_data;
+
+       g_return_val_if_fail (ncols == 1, FALSE);
+       g_return_val_if_fail (column_names != NULL, FALSE);
+       g_return_val_if_fail (column_values != NULL, FALSE);
+       g_return_val_if_fail (pvalue != NULL, FALSE);
+
+       if (!*pvalue)
+               *pvalue = g_strdup (column_values[0]);
+
+       return TRUE;
+}
+
+static gboolean
+e_cal_cache_get_strings (ECache *cache,
+                        gint ncols,
+                        const gchar **column_names,
+                        const gchar **column_values,
+                        gpointer user_data)
+{
+       GSList **pstrings = user_data;
+
+       g_return_val_if_fail (ncols == 1, FALSE);
+       g_return_val_if_fail (column_names != NULL, FALSE);
+       g_return_val_if_fail (column_values != NULL, FALSE);
+       g_return_val_if_fail (pstrings != NULL, FALSE);
+
+       *pstrings = g_slist_prepend (*pstrings, g_strdup (column_values[0]));
+
+       return TRUE;
+}
+
+static void
+e_cal_cache_populate_other_columns (ECalCache *cal_cache,
+                                   GSList **out_other_columns)
+{
+       g_return_if_fail (out_other_columns != NULL);
+
+       *out_other_columns = NULL;
+
+       #define add_column(name, type, idx_name) \
+               *out_other_columns = g_slist_prepend (*out_other_columns, \
+                       e_cache_column_info_new (name, type, idx_name))
+
+       add_column (ECC_COLUMN_OCCUR_START, "TEXT", "IDX_OCCURSTART");
+       add_column (ECC_COLUMN_OCCUR_END, "TEXT", "IDX_OCCUREND");
+       add_column (ECC_COLUMN_DUE, "TEXT", "IDX_DUE");
+       add_column (ECC_COLUMN_COMPLETED, "TEXT", "IDX_COMPLETED");
+       add_column (ECC_COLUMN_SUMMARY, "TEXT", "IDX_SUMMARY");
+       add_column (ECC_COLUMN_COMMENT, "TEXT", NULL);
+       add_column (ECC_COLUMN_DESCRIPTION, "TEXT", NULL);
+       add_column (ECC_COLUMN_LOCATION, "TEXT", NULL);
+       add_column (ECC_COLUMN_ATTENDEES, "TEXT", NULL);
+       add_column (ECC_COLUMN_ORGANIZER, "TEXT", NULL);
+       add_column (ECC_COLUMN_CLASSIFICATION, "TEXT", NULL);
+       add_column (ECC_COLUMN_STATUS, "TEXT", NULL);
+       add_column (ECC_COLUMN_PRIORITY, "INTEGER", NULL);
+       add_column (ECC_COLUMN_CATEGORIES, "TEXT", NULL);
+       add_column (ECC_COLUMN_HAS_ALARM, "INTEGER", NULL);
+       add_column (ECC_COLUMN_HAS_START, "INTEGER", NULL);
+       add_column (ECC_COLUMN_HAS_RECURRENCES, "INTEGER", NULL);
+       add_column (ECC_COLUMN_EXTRA, "TEXT", NULL);
+
+       #undef add_column
+
+       *out_other_columns = g_slist_reverse (*out_other_columns);
+}
+
+static gchar *
+ecc_encode_id_sql (const gchar *uid,
+                  const gchar *rid)
+{
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       if (rid && *rid)
+               return g_strdup_printf ("%s\n%s", uid, rid);
+
+       return g_strdup (uid);
+}
+
+/*static gboolean
+ecc_decode_id_sql (const gchar *id,
+                  gchar **out_uid,
+                  gchar **out_rid)
+{
+       gchar **split;
+
+       g_return_val_if_fail (id != NULL, FALSE);
+       g_return_val_if_fail (out_uid != NULL, FALSE);
+       g_return_val_if_fail (out_rid != NULL, FALSE);
+
+       *out_uid = NULL;
+       *out_rid = NULL;
+
+       if (!*id)
+               return FALSE;
+
+       split = g_strsplit (id, "\n", 1);
+
+       if (!split || !split[0] || !*split[0]) {
+               g_strfreev (split);
+               return FALSE;
+       }
+
+       *out_uid = split[0];
+
+       if (split[1])
+               *out_rid = split[1];
+
+       / * array elements are taken by the out arguments * /
+       g_free (split);
+
+       return TRUE;
+}*/
+
+static gchar *
+ecc_encode_itt_to_sql (struct icaltimetype itt)
+{
+       return g_strdup_printf ("%04d%02d%02d%02d%02d%02d",
+               itt.year, itt.month, itt.day,
+               itt.hour, itt.minute, itt.second);
+}
+
+static gchar *
+ecc_encode_time_to_sql (ECalCache *cal_cache,
+                       const ECalComponentDateTime *dt)
+{
+       struct icaltimetype itt;
+       icaltimezone *zone = NULL;
+
+       if (!dt || !dt->value)
+               return NULL;
+
+       itt = *dt->value;
+
+       if (!e_cal_cache_get_timezone (cal_cache, dt->tzid, &zone, NULL, NULL))
+               zone = NULL;
+
+       icaltimezone_convert_time (&itt, zone, icaltimezone_get_utc_timezone ());
+
+       return ecc_encode_itt_to_sql (itt);
+}
+
+static gchar *
+ecc_encode_timet_to_sql (ECalCache *cal_cache,
+                        time_t tt)
+{
+       struct icaltimetype itt;
+
+       if (tt <= 0)
+               return NULL;
+
+       itt = icaltime_from_timet_with_zone (tt, FALSE, NULL);
+
+       return ecc_encode_itt_to_sql (itt);
+}
+
+static icaltimezone *
+ecc_resolve_tzid_cb (const gchar *tzid,
+                    gpointer user_data)
+{
+       ECalCache *cal_cache = user_data;
+       icaltimezone *zone = NULL;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), NULL);
+
+       if (!e_cal_cache_get_timezone (cal_cache, tzid, &zone, NULL, NULL))
+               return NULL;
+
+       return zone;
+}
+
+static gchar *
+ecc_extract_text_list (const GSList *list)
+{
+       const GSList *link;
+       GString *value;
+
+       if (!list)
+               return NULL;
+
+       value = g_string_new ("");
+
+       for (link = list; link; link = g_slist_next (link)) {
+               ECalComponentText *text = link->data;
+
+               if (text && text->value) {
+                       gchar *str;
+
+                       str = e_util_utf8_decompose (text->value);
+                       if (str)
+                               g_string_append (value, str);
+                       g_free (str);
+               }
+       }
+
+       return g_string_free (value, !value->len);
+}
+
+static gchar *
+ecc_extract_comment (ECalComponent *comp)
+{
+       GSList *list = NULL;
+       gchar *value;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+       e_cal_component_get_comment_list (comp, &list);
+       value = ecc_extract_text_list (list);
+       e_cal_component_free_text_list (list);
+
+       return value;
+}
+
+static gchar *
+ecc_extract_description (ECalComponent *comp)
+{
+       GSList *list = NULL;
+       gchar *value;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+       e_cal_component_get_description_list (comp, &list);
+       value = ecc_extract_text_list (list);
+       e_cal_component_free_text_list (list);
+
+       return value;
+}
+
+static void
+ecc_encode_mail (GString *out_value,
+                const gchar *in_cn,
+                const gchar *in_val)
+{
+       gchar *cn = NULL, *val = NULL;
+
+       g_return_if_fail (in_val != NULL);
+
+       if (in_cn && *in_cn)
+               cn = e_util_utf8_decompose (in_cn);
+
+       if (in_val) {
+               const gchar *str = in_val;
+
+               if (g_ascii_strncasecmp (str, "mailto:";, 7) == 0) {
+                       str += 7;
+               }
+
+               if (*str)
+                       val = e_util_utf8_decompose (str);
+       }
+
+       if ((cn && *cn) || (val && *val)) {
+               if (out_value->len)
+                       g_string_append_c (out_value, '\n');
+               if (cn && *cn)
+                       g_string_append (out_value, cn);
+               if (val && *val) {
+                       if (cn && *cn)
+                               g_string_append_c (out_value, '\t');
+                       g_string_append (out_value, val);
+               }
+       }
+
+       g_free (cn);
+       g_free (val);
+}
+
+static gchar *
+ecc_extract_attendees (ECalComponent *comp)
+{
+       GSList *attendees = NULL, *link;
+       GString *value;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+       e_cal_component_get_attendee_list (comp, &attendees);
+       if (!attendees)
+               return NULL;
+
+       value = g_string_new ("");
+
+       for (link = attendees; link; link = g_slist_next (link)) {
+               ECalComponentAttendee *att = link->data;
+
+               if (!att)
+                       continue;
+
+               ecc_encode_mail (value, att->cn, att->value);
+       }
+
+       e_cal_component_free_attendee_list (attendees);
+
+       if (value->len) {
+               /* This way it is encoded as:
+                  <\n> <common-name> <\t> <mail> <\n> <common-name> <\t> <mail> <\n> ... </n> */
+               g_string_prepend (value, "\n");
+               g_string_append (value, "\n");
+       }
+
+       return g_string_free (value, !value->len);
+}
+
+static gchar *
+ecc_extract_organizer (ECalComponent *comp)
+{
+       ECalComponentOrganizer org;
+       GString *value;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+       e_cal_component_get_organizer (comp, &org);
+
+       value = g_string_new ("");
+
+       ecc_encode_mail (value, org.cn, org.value);
+
+       return g_string_free (value, !value->len);
+}
+
+static gchar *
+ecc_extract_categories (ECalComponent *comp)
+{
+       GSList *categories, *link;
+       GString *value;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+       e_cal_component_get_categories_list (comp, &categories);
+
+       if (!categories)
+               return NULL;
+
+       value = g_string_new ("");
+
+       for (link = categories; link; link = g_slist_next (link)) {
+               const gchar *category = link->data;
+
+               if (category && *category) {
+                       if (value->len)
+                               g_string_append_c (value, '\n');
+                       g_string_append (value, category);
+               }
+       }
+
+       e_cal_component_free_categories_list (categories);
+
+       if (value->len) {
+               /* This way it is encoded as:
+                  <\n> <category> <\n> <category> <\n> ... </n>
+                  which allows to search for exact category with: LIKE "%\ncategory\n%"
+               */
+               g_string_prepend (value, "\n");
+               g_string_append (value, "\n");
+       }
+
+       return g_string_free (value, !value->len);
+}
+
+static const gchar *
+ecc_get_classification_as_string (ECalComponentClassification classification)
+{
+       const gchar *str;
+
+       switch (classification) {
+       case E_CAL_COMPONENT_CLASS_PUBLIC:
+               str = "public";
+               break;
+       case E_CAL_COMPONENT_CLASS_PRIVATE:
+               str = "private";
+               break;
+       case E_CAL_COMPONENT_CLASS_CONFIDENTIAL:
+               str = "confidential";
+               break;
+       default:
+               str = NULL;
+               break;
+       }
+
+       return str;
+}
+
+static const gchar *
+ecc_get_status_as_string (icalproperty_status status)
+{
+       switch (status) {
+       case ICAL_STATUS_NONE:
+               return "NOT STARTED";
+       case ICAL_STATUS_COMPLETED:
+               return "COMPLETED";
+       case ICAL_STATUS_CANCELLED:
+               return "CANCELLED";
+       case ICAL_STATUS_INPROCESS:
+               return "IN PROGRESS";
+       case ICAL_STATUS_NEEDSACTION:
+               return "NEEDS ACTION";
+       case ICAL_STATUS_TENTATIVE:
+               return "TENTATIVE";
+       case ICAL_STATUS_CONFIRMED:
+               return "CONFIRMED";
+       case ICAL_STATUS_DRAFT:
+               return "DRAFT";
+       case ICAL_STATUS_FINAL:
+               return "FINAL";
+       case ICAL_STATUS_SUBMITTED:
+               return "SUBMITTED";
+       case ICAL_STATUS_PENDING:
+               return "PENDING";
+       case ICAL_STATUS_FAILED:
+               return "FAILED";
+       case ICAL_STATUS_X:
+               break;
+       }
+
+       return NULL;
+}
+
+static void
+ecc_fill_other_columns (ECalCache *cal_cache,
+                       ECacheColumnValues *other_columns,
+                       ECalComponent *comp)
+{
+       time_t occur_start = -1, occur_end = -1;
+       ECalComponentDateTime dt;
+       ECalComponentText text;
+       ECalComponentClassification classification;
+       icalcomponent *icalcomp;
+       icalproperty_status status;
+       struct icaltimetype *itt;
+       const gchar *str = NULL;
+       gint *priority = NULL;
+       gboolean has;
+
+       g_return_if_fail (E_IS_CAL_CACHE (cal_cache));
+       g_return_if_fail (other_columns != NULL);
+       g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+       #define add_value(_col, _val) e_cache_column_values_take_value (other_columns, _col, _val)
+
+       icalcomp = e_cal_component_get_icalcomponent (comp);
+
+       e_cal_util_get_component_occur_times (
+               comp, &occur_start, &occur_end,
+               ecc_resolve_tzid_cb, cal_cache, icaltimezone_get_utc_timezone (),
+               icalcomponent_isa (icalcomp));
+
+       add_value (ECC_COLUMN_OCCUR_START, ecc_encode_timet_to_sql (cal_cache, occur_start));
+       add_value (ECC_COLUMN_OCCUR_END, ecc_encode_timet_to_sql (cal_cache, occur_end));
+
+       e_cal_component_get_due (comp, &dt);
+       add_value (ECC_COLUMN_DUE, ecc_encode_time_to_sql (cal_cache, &dt));
+       e_cal_component_free_datetime (&dt);
+
+       itt = NULL;
+       e_cal_component_get_completed (comp, &itt);
+       add_value (ECC_COLUMN_COMPLETED, itt ? ecc_encode_itt_to_sql (*itt) : NULL);
+
+       text.value = NULL;
+       e_cal_component_get_summary (comp, &text);
+       add_value (ECC_COLUMN_SUMMARY, text.value ? e_util_utf8_decompose (text.value) : NULL);
+
+       e_cal_component_get_location (comp, &str);
+       add_value (ECC_COLUMN_LOCATION, str ? e_util_utf8_decompose (str) : NULL);
+
+       e_cal_component_get_classification (comp, &classification);
+       add_value (ECC_COLUMN_CLASSIFICATION, g_strdup (ecc_get_classification_as_string (classification)));
+
+       e_cal_component_get_status (comp, &status);
+       add_value (ECC_COLUMN_STATUS, g_strdup (ecc_get_status_as_string (status)));
+
+       e_cal_component_get_priority (comp, &priority);
+       add_value (ECC_COLUMN_PRIORITY, priority && *priority ? g_strdup_printf ("%d", *priority) : NULL);
+
+       has = e_cal_component_has_alarms (comp);
+       add_value (ECC_COLUMN_HAS_ALARM, g_strdup (has ? "1" : "0"));
+
+       e_cal_component_get_dtstart (comp, &dt);
+       has = dt.value != NULL;
+       add_value (ECC_COLUMN_HAS_START, g_strdup (has ? "1" : "0"));
+       e_cal_component_free_datetime (&dt);
+
+       has = e_cal_component_has_recurrences (comp) ||
+             e_cal_component_is_instance (comp);
+       add_value (ECC_COLUMN_HAS_RECURRENCES, g_strdup (has ? "1" : "0"));
+
+       add_value (ECC_COLUMN_COMMENT, ecc_extract_comment (comp));
+       add_value (ECC_COLUMN_DESCRIPTION, ecc_extract_description (comp));
+       add_value (ECC_COLUMN_ATTENDEES, ecc_extract_attendees (comp));
+       add_value (ECC_COLUMN_ORGANIZER, ecc_extract_organizer (comp));
+       add_value (ECC_COLUMN_CATEGORIES, ecc_extract_categories (comp));
+}
+
+static gboolean
+ecc_init_aux_tables (ECalCache *cal_cache,
+                    GCancellable *cancellable,
+                    GError **error)
+{
+       gchar *stmt;
+       gboolean success;
+
+       stmt = e_cache_sqlite_stmt_printf ("CREATE TABLE IF NOT EXISTS %Q ("
+               "tzid TEXT PRIMARY INDEX, "
+               "zone TEXT)",
+               ECC_TABLE_TIMEZONES);
+       success = e_cache_sqlite_exec (E_CACHE (cal_cache), stmt, cancellable, error);
+       e_cache_sqlite_stmt_free (stmt);
+
+       return success;
+}
+
+static gboolean
+e_cal_cache_migrate (ECache *cache,
+                    gint from_version,
+                    GCancellable *cancellable,
+                    GError **error)
+{
+       /* ECalCache *cal_cache = E_CAL_CACHE (cache); */
+       gboolean success = TRUE;
+
+       /* Add any version-related changes here */
+       /*if (from_version < E_CAL_CACHE_VERSION) {
+       }*/
+
+       return success;
+}
+
+static gboolean
+e_cal_cache_initialize (ECalCache *cal_cache,
+                       const gchar *filename,
+                       GCancellable *cancellable,
+                       GError **error)
+{
+       ECache *cache;
+       GSList *other_columns = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (filename != NULL, FALSE);
+
+       cache = E_CACHE (cal_cache);
+
+       e_cal_cache_populate_other_columns (cal_cache, &other_columns);
+
+       success = e_cache_initialize_sync (cache, filename, other_columns, cancellable, error);
+       if (!success)
+               goto exit;
+
+       e_cache_lock (cache, E_CACHE_LOCK_WRITE);
+
+       success = success && ecc_init_aux_tables (cal_cache, cancellable, error);
+
+       /* Check for data migration */
+       success = success && e_cal_cache_migrate (cache, e_cache_get_version (cache), cancellable, error);
+
+       e_cache_unlock (cache, success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
+
+       if (!success)
+               goto exit;
+
+       if (e_cache_get_version (cache) != E_CAL_CACHE_VERSION)
+               e_cache_set_version (cache, E_CAL_CACHE_VERSION);
+
+ exit:
+       g_slist_free_full (other_columns, e_cache_column_info_free);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_new:
+ * @filename: file name to load or create the new cache
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new #ECalCache.
+ *
+ * Returns: (transfer full) (nullable): A new #ECalCache or %NULL on error
+ *
+ * Since: 3.26
+ **/
+ECalCache *
+e_cal_cache_new (const gchar *filename,
+                GCancellable *cancellable,
+                GError **error)
+{
+       ECalCache *cal_cache;
+
+       g_return_val_if_fail (filename != NULL, NULL);
+
+       cal_cache = g_object_new (E_TYPE_CAL_CACHE, NULL);
+
+       if (!e_cal_cache_initialize (cal_cache, filename, cancellable, error)) {
+               g_object_unref (cal_cache);
+               cal_cache = NULL;
+       }
+
+       return cal_cache;
+}
+
+/**
+ * e_cal_cache_dup_component_revision:
+ * @cal_cache: an #ECalCache
+ * @component: an #ECalComponent
+ *
+ * Returns the @component revision, used to detect changes.
+ * The returned string should be freed with g_free(), when
+ * no longer needed.
+ *
+ * Returns: (transfer full): A newly allocated string containing
+ *    revision of the @component.
+ *
+ * Since: 3.26
+ **/
+gchar *
+e_cal_cache_dup_component_revision (ECalCache *cal_cache,
+                                   ECalComponent *component)
+{
+       gchar *revision = NULL;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), NULL);
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (component), NULL);
+
+       g_signal_emit (cal_cache, signals[DUP_COMPONENT_REVISION], 0, component, &revision);
+
+       return revision;
+}
+
+/**
+ * e_cal_cache_put_component:
+ * @cal_cache: an #ECalCache
+ * @component: an #ECalComponent to put into the @cal_cache
+ * @extra: (nullable): an extra data to store in association with the @component
+ * @offline_flag: one of #ECacheOfflineFlag, whether putting this component in offline
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Adds a @component into the @cal_cache. Any existing with the same UID
+ * and RID is replaced.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_put_component (ECalCache *cal_cache,
+                          ECalComponent *component,
+                          const gchar *extra,
+                          ECacheOfflineFlag offline_flag,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+       GSList *components = NULL;
+       GSList *extras = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+
+       components = g_slist_prepend (components, component);
+       if (extra)
+               extras = g_slist_prepend (extras, (gpointer) extra);
+
+       success = e_cal_cache_put_components (cal_cache, components, extras, offline_flag, cancellable, 
error);
+
+       g_slist_free (components);
+       g_slist_free (extras);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_put_components:
+ * @cal_cache: an #ECalCache
+ * @components: (element-type ECalComponent): a #GSList of #ECalComponent to put into the @cal_cache
+ * @extras: (nullable) (element-type utf8): an extra data to store in association with the @components
+ * @offline_flag: one of #ECacheOfflineFlag, whether putting these components in offline
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Adds a list of @components into the @cal_cache. Any existing with the same UID
+ * and RID are replaced.
+ *
+ * If @extras is not %NULL, it's length should be the same as the length
+ * of the @components.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_put_components (ECalCache *cal_cache,
+                           const GSList *components,
+                           const GSList *extras,
+                           ECacheOfflineFlag offline_flag,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       const GSList *clink, *elink;
+       ECache *cache;
+       ECacheColumnValues *other_columns;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (extras == NULL || g_slist_length ((GSList *) components) == g_slist_length 
((GSList *) extras), FALSE);
+
+       cache = E_CACHE (cal_cache);
+       other_columns = e_cache_column_values_new ();
+
+       e_cache_lock (cache, E_CACHE_LOCK_WRITE);
+
+       for (clink = components, elink = extras; clink; clink = g_slist_next (clink), elink = g_slist_next 
(elink)) {
+               ECalComponent *component = clink->data;
+               const gchar *extra = elink ? elink->data : NULL;
+               ECalComponentId *id;
+               gchar *uid, *rev, *icalstring;
+
+               g_return_val_if_fail (E_IS_CAL_COMPONENT (component), FALSE);
+
+               icalstring = e_cal_component_get_as_string (component);
+               g_return_val_if_fail (icalstring != NULL, FALSE);
+
+               e_cache_column_values_remove_all (other_columns);
+
+               if (extra)
+                       e_cache_column_values_take_value (other_columns, ECC_COLUMN_EXTRA, g_strdup (extra));
+
+               id = e_cal_component_get_id (component);
+               if (id) {
+                       uid = ecc_encode_id_sql (id->uid, id->rid);
+               } else {
+                       g_warn_if_reached ();
+                       uid = g_strdup ("");
+               }
+               e_cal_component_free_id (id);
+
+               rev = e_cal_cache_dup_component_revision (cal_cache, component);
+
+               success = e_cache_put (cache, uid, rev, icalstring, other_columns, offline_flag, cancellable, 
error);
+
+               g_free (icalstring);
+               g_free (rev);
+               g_free (uid);
+
+               if (!success)
+                       break;
+       }
+
+       e_cache_unlock (cache, success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
+
+       e_cache_column_values_free (other_columns);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_remove_component:
+ * @cal_cache: an #ECalCache
+ * @uid: a UID of the component to remove
+ * @rid: (nullable): an optional Recurrence-ID to remove
+ * @offline_flag: one of #ECacheOfflineFlag, whether removing this component in offline
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Removes a component idenitified by @uid and @rid from the @cal_cache.
+ * When the @rid is %NULL, or an empty string, then removes the master
+ * object and all detached instances identified by @uid.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_remove_component (ECalCache *cal_cache,
+                             const gchar *uid,
+                             const gchar *rid,
+                             ECacheOfflineFlag offline_flag,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       ECalComponentId id;
+       GSList *ids = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+
+       id.uid = (gchar *) uid;
+       id.rid = (gchar *) rid;
+
+       ids = g_slist_prepend (ids, &id);
+
+       success = e_cal_cache_remove_components (cal_cache, ids, offline_flag, cancellable, error);
+
+       g_slist_free (ids);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_remove_components:
+ * @cal_cache: an #ECalCache
+ * @ids: (element-type ECalComponentId): a #GSList of components to remove
+ * @offline_flag: one of #ECacheOfflineFlag, whether removing these comonents in offline
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Removes components idenitified by @uid and @rid from the @cal_cache
+ * in the @ids list. When the @rid is %NULL, or an empty string, then
+ * removes the master object and all detached instances identified by @uid.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_remove_components (ECalCache *cal_cache,
+                              const GSList *ids,
+                              ECacheOfflineFlag offline_flag,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       ECache *cache;
+       const GSList *link;
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+
+       cache = E_CACHE (cal_cache);
+
+       e_cache_lock (cache, E_CACHE_LOCK_WRITE);
+
+       for (link = ids; success && link; link = g_slist_next (link)) {
+               const ECalComponentId *id = link->data;
+               gchar *uid;
+
+               g_warn_if_fail (id != NULL);
+
+               if (!id)
+                       continue;
+
+               uid = ecc_encode_id_sql (id->uid, id->rid);
+
+               success = e_cache_remove (cache, uid, offline_flag, cancellable, error);
+
+               g_free (uid);
+       }
+
+       e_cache_unlock (cache, success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_get_component:
+ * @cal_cache: an #ECalCache
+ * @uid: a UID of the component
+ * @rid: (nullable): an optional Recurrence-ID
+ * @out_component: (out) (transfer full): return location for an #ECalComponent
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a component identified by @uid, and optionally by the @rid,
+ * from the @cal_cache. The returned @out_component should be freed with
+ * g_object_unref(), when no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_get_component (ECalCache *cal_cache,
+                          const gchar *uid,
+                          const gchar *rid,
+                          ECalComponent **out_component,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+       gchar *icalstring = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+       g_return_val_if_fail (out_component != NULL, FALSE);
+
+       success = e_cal_cache_get_component_as_string (cal_cache, uid, rid, &icalstring, cancellable, error);
+       if (success) {
+               *out_component = e_cal_component_new_from_string (icalstring);
+               g_free (icalstring);
+       }
+
+       return success;
+}
+
+/**
+ * e_cal_cache_get_component_as_string:
+ * @cal_cache: an #ECalCache
+ * @uid: a UID of the component
+ * @rid: (nullable): an optional Recurrence-ID
+ * @out_icalstring: (out) (transfer full): return location for an iCalendar string
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a component identified by @uid, and optionally by the @rid,
+ * from the @cal_cache. The returned @out_icalstring should be freed with
+ * g_free(), when no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_get_component_as_string (ECalCache *cal_cache,
+                                    const gchar *uid,
+                                    const gchar *rid,
+                                    gchar **out_icalstring,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       gchar *id;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+       g_return_val_if_fail (out_icalstring != NULL, FALSE);
+
+       id = ecc_encode_id_sql (uid, rid);
+
+       *out_icalstring = e_cache_get (E_CACHE (cal_cache), id, NULL, NULL, cancellable, error);
+
+       g_free (id);
+
+       return *out_icalstring != NULL;
+}
+
+/**
+ * e_cal_cache_set_component_extra:
+ * @cal_cache: an #ECalCache
+ * @uid: a UID of the component
+ * @rid: (nullable): an optional Recurrence-ID
+ * @extra: (nullable): extra data to set for the component
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Sets or replaces the extra data associated with a component
+ * identified by @uid and optionally @rid.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_set_component_extra (ECalCache *cal_cache,
+                                const gchar *uid,
+                                const gchar *rid,
+                                const gchar *extra,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       gchar *id, *stmt;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+
+       id = ecc_encode_id_sql (uid, rid);
+
+       if (!e_cache_contains (E_CACHE (cal_cache), id, E_CACHE_INCLUDE_DELETED)) {
+               g_free (id);
+
+               if (rid && *rid)
+                       g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s”, “%s” not 
found"), uid, rid);
+               else
+                       g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s” not 
found"), uid);
+
+               return FALSE;
+       }
+
+       if (extra) {
+               stmt = e_cache_sqlite_stmt_printf (
+                       "UPDATE " E_CACHE_TABLE_OBJECTS " SET " ECC_COLUMN_EXTRA "=%Q"
+                       " WHERE " E_CACHE_COLUMN_UID "=%Q",
+                       extra, id);
+       } else {
+               stmt = e_cache_sqlite_stmt_printf (
+                       "UPDATE " E_CACHE_TABLE_OBJECTS " SET " ECC_COLUMN_EXTRA "=NULL"
+                       " WHERE " E_CACHE_COLUMN_UID "=%Q",
+                       id);
+       }
+
+       success = e_cache_sqlite_exec (E_CACHE (cal_cache), stmt, cancellable, error);
+
+       e_cache_sqlite_stmt_free (stmt);
+       g_free (id);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_get_component_extra:
+ * @cal_cache: an #ECalCache
+ * @uid: a UID of the component
+ * @rid: (nullable): an optional Recurrence-ID
+ * @out_extra: (out) (transfer full): return location to store the extra data
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the extra data previously set for @uid and @rid, either with
+ * e_cal_cache_set_component_extra() or when adding components.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_get_component_extra (ECalCache *cal_cache,
+                                const gchar *uid,
+                                const gchar *rid,
+                                gchar **out_extra,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       gchar *id, *stmt;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+
+       id = ecc_encode_id_sql (uid, rid);
+
+       if (!e_cache_contains (E_CACHE (cal_cache), id, E_CACHE_INCLUDE_DELETED)) {
+               g_free (id);
+
+               if (rid && *rid)
+                       g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s”, “%s” not 
found"), uid, rid);
+               else
+                       g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s” not 
found"), uid);
+
+               return FALSE;
+       }
+
+       stmt = e_cache_sqlite_stmt_printf (
+               "SELECT " ECC_COLUMN_EXTRA " FROM " E_CACHE_TABLE_OBJECTS
+               " WHERE " E_CACHE_COLUMN_UID "=%Q",
+               id);
+
+       success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt, e_cal_cache_get_string, out_extra, 
cancellable, error);
+
+       e_cache_sqlite_stmt_free (stmt);
+       g_free (id);
+
+       return success;
+}
+
+static GSList *
+ecc_icalstrings_to_components (GSList *icalstrings)
+{
+       GSList *link;
+
+       for (link = icalstrings; link; link = g_slist_next (link)) {
+               gchar *icalstring = link->data;
+
+               link->data = e_cal_component_new_from_string (icalstring);
+
+               g_free (icalstring);
+       }
+
+       return icalstrings;
+}
+
+/**
+ * e_cal_cache_get_components_by_uid:
+ * @cal_cache: an #ECalCache
+ * @uid: a UID of the component
+ * @out_components: (out) (transfer full) (element-type ECalComponent): return location for the components
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the master object and all detached instances for a component
+ * identified by the @uid. Free the returned #GSList with
+ * g_slist_free_full (components, g_object_unref); when
+ * no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_get_components_by_uid (ECalCache *cal_cache,
+                                  const gchar *uid,
+                                  GSList **out_components,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       GSList *icalstrings = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+       g_return_val_if_fail (out_components != NULL, FALSE);
+
+       success = e_cal_cache_get_components_by_uid_as_strings (cal_cache, uid, &icalstrings, cancellable, 
error);
+       if (success) {
+               *out_components = ecc_icalstrings_to_components (icalstrings);
+       }
+
+       return success;
+}
+
+/**
+ * e_cal_cache_get_components_by_uid_as_strings:
+ * @cal_cache: an #ECalCache
+ * @uid: a UID of the component
+ * @out_icalstrings: (out) (transfer full) (element-type utf8): return location for the iCal strings
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the master object and all detached instances as string
+ * for a component identified by the @uid. Free the returned #GSList
+ * with g_slist_free_full (icalstrings, g_free); when no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_get_components_by_uid_as_strings (ECalCache *cal_cache,
+                                             const gchar *uid,
+                                             GSList **out_icalstrings,
+                                             GCancellable *cancellable,
+                                             GError **error)
+{
+       gchar *stmt;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+       g_return_val_if_fail (out_icalstrings != NULL, FALSE);
+
+       *out_icalstrings = NULL;
+
+       /* Using 'ORDER BY' to get the master object first */
+       stmt = e_cache_sqlite_stmt_printf (
+               "SELECT " E_CACHE_COLUMN_OBJECT " FROM " E_CACHE_TABLE_OBJECTS
+               " WHERE " E_CACHE_COLUMN_UID "=%Q OR " E_CACHE_COLUMN_UID " LIKE '%q\n%%'"
+               " ORDER BY " E_CACHE_COLUMN_UID,
+               uid, uid);
+
+       success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt, e_cal_cache_get_strings, out_icalstrings, 
cancellable, error);
+
+       e_cache_sqlite_stmt_free (stmt);
+
+       if (success && !*out_icalstrings) {
+               success = FALSE;
+               g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object “%s” not found"), uid);
+       } else if (success) {
+               *out_icalstrings = g_slist_reverse (*out_icalstrings);
+       }
+
+       return success;
+}
+
+/**
+ * e_cal_cache_get_components_in_range:
+ * @cal_cache: an #ECalCache
+ * @range_start: start of the range, as time_t, inclusive
+ * @range_end: end of the range, as time_t, exclusive
+ * @out_components: (out) (transfer full) (element-type ECalComponent): return location for the components
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a list of components which occur in the given time range.
+ * It's not an error if none is found.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_get_components_in_range (ECalCache *cal_cache,
+                                    time_t range_start,
+                                    time_t range_end,
+                                    GSList **out_components,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       GSList *icalstrings = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (out_components != NULL, FALSE);
+
+       success = e_cal_cache_get_components_in_range_as_strings (cal_cache, range_start, range_end, 
&icalstrings, cancellable, error);
+       if (success)
+               *out_components = ecc_icalstrings_to_components (icalstrings);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_get_components_in_range_as_strings:
+ * @cal_cache: an #ECalCache
+ * @range_start: start of the range, as time_t, inclusive
+ * @range_end: end of the range, as time_t, exclusive
+ * @out_icalstrings: (out) (transfer full) (element-type utf8): return location for the iCal strings
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a list of components, as iCal strings, which occur in the given time range.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_get_components_in_range_as_strings (ECalCache *cal_cache,
+                                               time_t range_start,
+                                               time_t range_end,
+                                               GSList **out_icalstrings,
+                                               GCancellable *cancellable,
+                                               GError **error)
+{
+       gchar *stmt, *range_start_str, *range_end_str;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (out_icalstrings != NULL, FALSE);
+
+       *out_icalstrings = NULL;
+
+       range_start_str = ecc_encode_timet_to_sql (cal_cache, range_start);
+       range_end_str = ecc_encode_timet_to_sql (cal_cache, range_end);
+
+       /* Using 'ORDER BY' to get the master object first */
+       stmt = e_cache_sqlite_stmt_printf (
+               "SELECT " E_CACHE_COLUMN_OBJECT " FROM " E_CACHE_TABLE_OBJECTS
+               " WHERE (" ECC_COLUMN_OCCUR_START " IS NULL OR " ECC_COLUMN_OCCUR_START "<%Q)"
+               " AND (" ECC_COLUMN_OCCUR_END " IS NULL OR " ECC_COLUMN_OCCUR_END ">%Q)"
+               " ORDER BY " E_CACHE_COLUMN_UID,
+               range_end, range_start);
+
+       success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt, e_cal_cache_get_strings, out_icalstrings, 
cancellable, error);
+
+       e_cache_sqlite_stmt_free (stmt);
+       g_free (range_start_str);
+       g_free (range_end_str);
+
+       return success;
+}
+
+static gboolean
+ecc_search_components_cb (ECache *cache,
+                         const gchar *uid,
+                         const gchar *rid,
+                         const gchar *revision,
+                         const gchar *object,
+                         const gchar *extra,
+                         EOfflineState offline_state,
+                         gpointer user_data)
+{
+       GSList **out_components = user_data;
+
+       g_return_val_if_fail (out_components != NULL, FALSE);
+       g_return_val_if_fail (object != NULL, FALSE);
+
+       *out_components = g_slist_prepend (*out_components,
+               e_cal_component_new_from_string (object));
+
+       return TRUE;
+}
+
+static gboolean
+ecc_search_icalstrings_cb (ECache *cache,
+                          const gchar *uid,
+                          const gchar *rid,
+                          const gchar *revision,
+                          const gchar *object,
+                          const gchar *extra,
+                          EOfflineState offline_state,
+                          gpointer user_data)
+{
+       GSList **out_icalstrings = user_data;
+
+       g_return_val_if_fail (out_icalstrings != NULL, FALSE);
+       g_return_val_if_fail (object != NULL, FALSE);
+
+       *out_icalstrings = g_slist_prepend (*out_icalstrings, g_strdup (object));
+
+       return TRUE;
+}
+
+static gboolean
+ecc_search_ids_cb (ECache *cache,
+                  const gchar *uid,
+                  const gchar *rid,
+                  const gchar *revision,
+                  const gchar *object,
+                  const gchar *extra,
+                  EOfflineState offline_state,
+                  gpointer user_data)
+{
+       GSList **out_ids = user_data;
+
+       g_return_val_if_fail (out_ids != NULL, FALSE);
+       g_return_val_if_fail (object != NULL, FALSE);
+
+       *out_ids = g_slist_prepend (*out_ids, e_cal_component_id_new (uid, rid));
+
+       return TRUE;
+}
+
+/**
+ * e_cal_cache_search:
+ * @cal_cache: an #ECalCache
+ * @sexp: (nullable): search expression; use %NULL or an empty string to list all stored components
+ * @out_components: (out) (transfer full) (element-type ECalComponent): stored components satisfied by @sexp
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches the @cal_cache with the given @sexp and
+ * returns those components which satisfy the search
+ * expression. The @out_components should be freed with
+ * g_slist_free_full (components, g_object_unref); when
+ * no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_search (ECalCache *cal_cache,
+                   const gchar *sexp,
+                   GSList **out_components,
+                   GCancellable *cancellable,
+                   GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (out_components != NULL, FALSE);
+
+       *out_components = NULL;
+
+       success = e_cal_cache_search_with_callback (cal_cache, sexp, ecc_search_components_cb,
+               out_components, cancellable, error);
+       if (success) {
+               *out_components = g_slist_reverse (*out_components);
+       } else {
+               g_slist_free_full (*out_components, g_object_unref);
+               *out_components = NULL;
+       }
+
+       return success;
+}
+
+/**
+ * e_cal_cache_search_as_strings:
+ * @cal_cache: an #ECalCache
+ * @sexp: (nullable): search expression; use %NULL or an empty string to list all stored components
+ * @out_icalstrings: (out) (transfer full) (element-type utf8): stored components satisfied by @sexp as iCal 
strings
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches the @cal_cache with the given @sexp and returns those
+ * components which satisfy the search expression as iCal strings.
+ * The @out_icalstrings should be freed with
+ * g_slist_free_full (components, g_free); when
+ * no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_search_as_strings (ECalCache *cal_cache,
+                              const gchar *sexp,
+                              GSList **out_icalstrings,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (out_icalstrings != NULL, FALSE);
+
+       *out_icalstrings = NULL;
+
+       success = e_cal_cache_search_with_callback (cal_cache, sexp, ecc_search_icalstrings_cb,
+               out_icalstrings, cancellable, error);
+       if (success) {
+               *out_icalstrings = g_slist_reverse (*out_icalstrings);
+       } else {
+               g_slist_free_full (*out_icalstrings, g_free);
+               *out_icalstrings = NULL;
+       }
+
+       return success;
+}
+
+/**
+ * e_cal_cache_search_ids:
+ * @cal_cache: an #ECalCache
+ * @sexp: (nullable): search expression; use %NULL or an empty string to list all stored components
+ * @out_ids: (out) (transfer full) (element-type ECalComponentId): IDs of stored components satisfied by 
@sexp
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches the @cal_cache with the given @sexp and returns ECalComponentId
+ * for those components which satisfy the search expression.
+ * The @out_ids should be freed with
+ * g_slist_free_full (components, (GDestroyNotify) e_cal_component_free_id);
+ * when no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_search_ids (ECalCache *cal_cache,
+                       const gchar *sexp,
+                       GSList **out_ids,
+                       GCancellable *cancellable,
+                       GError **error)
+
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (out_ids != NULL, FALSE);
+
+       *out_ids = NULL;
+
+       success = e_cal_cache_search_with_callback (cal_cache, sexp, ecc_search_ids_cb,
+               out_ids, cancellable, error);
+       if (success) {
+               *out_ids = g_slist_reverse (*out_ids);
+       } else {
+               g_slist_free_full (*out_ids, g_object_unref);
+               *out_ids = NULL;
+       }
+
+       return success;
+}
+
+/**
+ * e_cal_cache_search_with_callback:
+ * @cal_cache: an #ECalCache
+ * @sexp: (nullable): search expression; use %NULL or an empty string to list all stored components
+ * @func: an #ECalCacheSearchFunc callback to call for each row which satisfies @sexp
+ * @user_data: user data for @func
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches the @cal_cache with the given @sexp and calls @func for each
+ * row which satisfy the search expression.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_search_with_callback (ECalCache *cal_cache,
+                                 const gchar *sexp,
+                                 ECalCacheSearchFunc func,
+                                 gpointer user_data,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_put_timezone:
+ * @cal_cache: an #ECalCache
+ * @zone: an icaltimezone to put
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Puts the @zone into the @cal_cache using its timezone ID as
+ * an identificator. The function does nothing if any such already
+ * exists in the @cal_cache.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_put_timezone (ECalCache *cal_cache,
+                         icaltimezone *zone,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       gboolean success;
+       gchar *stmt;
+       const gchar *tzid;
+       gchar *component_str;
+       icalcomponent *component;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (zone != NULL, FALSE);
+
+       tzid = icaltimezone_get_tzid (zone);
+       if (!tzid) {
+               g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Cannot add timezone 
without tzid"));
+               return FALSE;
+       }
+
+       component = icaltimezone_get_component (zone);
+       if (!component) {
+               g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Cannot add timezone 
without component"));
+               return FALSE;
+       }
+
+       component_str = icalcomponent_as_ical_string_r (component);
+       if (!component_str) {
+               g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Cannot add timezone 
with invalid component"));
+               return FALSE;
+       }
+
+       stmt = e_cache_sqlite_stmt_printf (
+               "INSERT or REPLACE INTO " ECC_TABLE_TIMEZONES " (tzid, zone) VALUES (%Q, %Q)",
+               tzid, component_str);
+
+       success = e_cache_sqlite_exec (E_CACHE (cal_cache), stmt, cancellable, error);
+
+       e_cache_sqlite_stmt_free (stmt);
+
+       g_free (component_str);
+
+       return success;
+}
+
+static icaltimezone *
+ecc_timezone_from_string (const gchar *icalstring)
+{
+       icalcomponent *component;
+
+       g_return_val_if_fail (icalstring != NULL, NULL);
+
+       component = icalcomponent_new_from_string (icalstring);
+       if (component) {
+               icaltimezone *zone;
+
+               zone = icaltimezone_new ();
+               if (!icaltimezone_set_component (zone, component)) {
+                       icalcomponent_free (component);
+                       icaltimezone_free (zone, 1);
+               } else {
+                       return zone;
+               }
+       }
+
+       return NULL;
+}
+
+/**
+ * e_cal_cache_get_timezone:
+ * @cal_cache: an #ECalCache
+ * @tzid: a timezone ID to get
+ * @out_zone: (out) (transfer none): return location for the icaltimezone
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a timezone with given @tzid, which had been previously put
+ * into the @cal_cache with e_cal_cache_put_timezone().
+ * The returned icaltimezone is owned by the @cal_cache and should
+ * not be freed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_get_timezone (ECalCache *cal_cache,
+                         const gchar *tzid,
+                         icaltimezone **out_zone,
+                         GCancellable *cancellable,
+                         GError **error)
+
+{
+       gchar *zone_str = NULL;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (tzid != NULL, FALSE);
+       g_return_val_if_fail (out_zone != NULL, FALSE);
+
+       g_mutex_lock (&cal_cache->priv->loaded_timezones_lock);
+
+       *out_zone = g_hash_table_lookup (cal_cache->priv->loaded_timezones, tzid);
+       if (*out_zone) {
+               g_mutex_unlock (&cal_cache->priv->loaded_timezones_lock);
+               return TRUE;
+       }
+
+       success = e_cal_cache_dup_timezone_as_string (cal_cache, tzid, &zone_str, cancellable, error);
+
+       if (success && zone_str) {
+               icaltimezone *zone;
+
+               zone = ecc_timezone_from_string (zone_str);
+               if (zone) {
+                       g_hash_table_insert (cal_cache->priv->loaded_timezones, g_strdup (tzid), zone);
+                       *out_zone = zone;
+               } else {
+                       success = FALSE;
+               }
+       }
+
+       g_mutex_unlock (&cal_cache->priv->loaded_timezones_lock);
+
+       g_free (zone_str);
+
+       return success;
+}
+
+/**
+ * e_cal_cache_dup_timezone_as_string:
+ * @cal_cache: an #ECalCache
+ * @tzid: a timezone ID to get
+ * @out_zone_string: (out) (transfer full): return location for the icaltimezone as iCal string
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a timezone with given @tzid, which had been previously put
+ * into the @cal_cache with e_cal_cache_put_timezone().
+ * The returned string is an iCal string for that icaltimezone and
+ * should be freed with g_free() when no longer needed.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_dup_timezone_as_string (ECalCache *cal_cache,
+                                   const gchar *tzid,
+                                   gchar **out_zone_string,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       gchar *stmt;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (tzid != NULL, FALSE);
+       g_return_val_if_fail (out_zone_string, FALSE);
+
+       *out_zone_string = NULL;
+
+       stmt = e_cache_sqlite_stmt_printf (
+               "SELECT zone FROM " ECC_TABLE_TIMEZONES " WHERE tzid=%Q",
+               tzid);
+
+       success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt, e_cal_cache_get_string, out_zone_string, 
cancellable, error);
+
+       e_cache_sqlite_stmt_free (stmt);
+
+       return success;
+}
+
+static gboolean
+e_cal_cache_get_uint64_cb (ECache *cache,
+                          gint ncols,
+                          const gchar **column_names,
+                          const gchar **column_values,
+                          gpointer user_data)
+{
+       guint64 *pui64 = user_data;
+
+       g_return_val_if_fail (pui64 != NULL, FALSE);
+
+       if (ncols == 1) {
+               *pui64 = column_values[0] ? g_ascii_strtoull (column_values[0], NULL, 10) : 0;
+       } else {
+               *pui64 = 0;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+e_cal_cache_load_zones_cb (ECache *cache,
+                          gint ncols,
+                          const gchar *column_names[],
+                          const gchar *column_values[],
+                          gpointer user_data)
+{
+       GHashTable *loaded_zones = user_data;
+
+       g_return_val_if_fail (loaded_zones != NULL, FALSE);
+       g_return_val_if_fail (ncols != 2, FALSE);
+
+       /* Do not overwrite already loaded timezones, they can be used anywhere around */
+       if (!g_hash_table_lookup (loaded_zones, column_values[0])) {
+               icaltimezone *zone;
+
+               zone = ecc_timezone_from_string (column_values[1]);
+               if (zone) {
+                       g_hash_table_insert (loaded_zones, g_strdup (column_values[0]), zone);
+               }
+       }
+
+       return TRUE;
+}
+
+/**
+ * e_cal_cache_list_timezones:
+ * @cal_cache: an #ECalCache
+ * @out_timezones: (out) (transfer container) (element-type icaltimezone): return location for the list of 
stored timezones
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets a list of all stored timezones by the @cal_cache.
+ * Only the returned list should be freed with g_list_free()
+ * when no longer needed; the icaltimezone-s are owned
+ * by the @cal_cache.
+ *
+ * Returns: Whether succeeded.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cal_cache_list_timezones (ECalCache *cal_cache,
+                           GList **out_timezones,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       guint64 n_stored = 0;
+       gchar *stmt;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
+       g_return_val_if_fail (out_timezones != NULL, FALSE);
+
+       g_mutex_lock (&cal_cache->priv->loaded_timezones_lock);
+
+       success = e_cache_sqlite_select (E_CACHE (cal_cache),
+               "SELECT COUNT(*) FROM " ECC_TABLE_TIMEZONES,
+               e_cal_cache_get_uint64_cb, &n_stored, cancellable, error);
+
+       if (success && n_stored != g_hash_table_size (cal_cache->priv->loaded_timezones)) {
+               stmt = e_cache_sqlite_stmt_printf ("SELECT tzid, zone FROM " ECC_TABLE_TIMEZONES);
+               success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt,
+                       e_cal_cache_load_zones_cb, cal_cache->priv->loaded_timezones, cancellable, error);
+               e_cache_sqlite_stmt_free (stmt);
+       }
+
+       if (success)
+               *out_timezones = g_hash_table_get_values (cal_cache->priv->loaded_timezones);
+
+       g_mutex_unlock (&cal_cache->priv->loaded_timezones_lock);
+
+       return success;
+}
+
+static gboolean
+ecc_empty_aux_tables (ECache *cache,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       gchar *stmt;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cache), FALSE);
+
+       stmt = e_cache_sqlite_stmt_printf ("DELETE FROM %Q", ECC_TABLE_TIMEZONES);
+       success = e_cache_sqlite_exec (cache, stmt, cancellable, error);
+       e_cache_sqlite_stmt_free (stmt);
+
+       return success;
+}
+
+/* The default revision is a concatenation of
+   <DTSTAMP> "-" <LAST-MODIFIED> "-" <SEQUENCE> */
+static gchar *
+ecc_dup_component_revision (ECalCache *cal_cache,
+                           ECalComponent *component)
+{
+       icalcomponent *icalcomp;
+       struct icaltimetype itt;
+       icalproperty *prop;
+       GString *revision;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (component), NULL);
+
+       icalcomp = e_cal_component_get_icalcomponent (component);
+
+       revision = g_string_sized_new (48);
+
+       itt = icalcomponent_get_dtstamp (icalcomp);
+       if (icaltime_is_null_time (itt) || !icaltime_is_valid_time (itt)) {
+               g_string_append_c (revision, 'x');
+       } else {
+               g_string_append_printf (revision, "%04d%02d%02d%02d%02d%02d",
+                       itt.year, itt.month, itt.day,
+                       itt.hour, itt.minute, itt.second);
+       }
+
+       g_string_append_c (revision, '-');
+
+       prop = icalcomponent_get_first_property (icalcomp, ICAL_LASTMODIFIED_PROPERTY);
+       if (prop)
+               itt = icalproperty_get_lastmodified (prop);
+
+       if (!prop || icaltime_is_null_time (itt) || !icaltime_is_valid_time (itt)) {
+               g_string_append_c (revision, 'x');
+       } else {
+               g_string_append_printf (revision, "%04d%02d%02d%02d%02d%02d",
+                       itt.year, itt.month, itt.day,
+                       itt.hour, itt.minute, itt.second);
+       }
+
+       g_string_append_c (revision, '-');
+
+       prop = icalcomponent_get_first_property (icalcomp, ICAL_SEQUENCE_PROPERTY);
+       if (!prop) {
+               g_string_append_c (revision, 'x');
+       } else {
+               g_string_append_printf (revision, "%d", icalproperty_get_sequence (prop));
+       }
+
+       return g_string_free (revision, FALSE);
+}
+
+static gboolean
+e_cal_cache_put_locked (ECache *cache,
+                       const gchar *uid,
+                       const gchar *revision,
+                       const gchar *object,
+                       ECacheColumnValues *other_columns,
+                       EOfflineState offline_state,
+                       gboolean is_replace,
+                       GCancellable *cancellable,
+                       GError **error)
+{
+       ECalCache *cal_cache;
+       ECalComponent *comp;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cache), FALSE);
+       g_return_val_if_fail (E_CACHE_CLASS (e_cal_cache_parent_class)->put_locked != NULL, FALSE);
+
+       cal_cache = E_CAL_CACHE (cache);
+
+       comp = e_cal_component_new_from_string (object);
+       if (!comp)
+               return FALSE;
+
+       ecc_fill_other_columns (cal_cache, other_columns, comp);
+
+       success = E_CACHE_CLASS (e_cal_cache_parent_class)->put_locked (cache, uid, revision, object, 
other_columns, offline_state,
+               is_replace, cancellable, error);
+
+       g_clear_object (&comp);
+
+       return success;
+}
+
+static gboolean
+e_cal_cache_remove_all_locked (ECache *cache,
+                              const GSList *uids,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cache), FALSE);
+       g_return_val_if_fail (E_CACHE_CLASS (e_cal_cache_parent_class)->remove_all_locked != NULL, FALSE);
+
+       /* Cannot free content of priv->loaded_timezones, because those can be used anywhere */
+       success = ecc_empty_aux_tables (cache, cancellable, error);
+
+       success = success && E_CACHE_CLASS (e_cal_cache_parent_class)->remove_all_locked (cache, uids, 
cancellable, error);
+
+       return success;
+}
+
+static void
+cal_cache_free_zone (gpointer ptr)
+{
+       icaltimezone *zone = ptr;
+
+       if (zone)
+               icaltimezone_free (zone, 1);
+}
+
+static void
+e_cal_cache_finalize (GObject *object)
+{
+       ECalCache *cal_cache = E_CAL_CACHE (object);
+
+       g_hash_table_destroy (cal_cache->priv->loaded_timezones);
+
+       g_mutex_clear (&cal_cache->priv->loaded_timezones_lock);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_cal_cache_parent_class)->finalize (object);
+}
+
+static void
+e_cal_cache_class_init (ECalCacheClass *klass)
+{
+       GObjectClass *object_class;
+       ECacheClass *cache_class;
+
+       g_type_class_add_private (klass, sizeof (ECalCachePrivate));
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = e_cal_cache_finalize;
+
+       cache_class = E_CACHE_CLASS (klass);
+       cache_class->put_locked = e_cal_cache_put_locked;
+       cache_class->remove_all_locked = e_cal_cache_remove_all_locked;
+
+       klass->dup_component_revision = ecc_dup_component_revision;
+
+       /**
+        * @ECalCache:dup-component-revision:
+        * A signal being called to get revision of an #ECalComponent.
+        * The default implementation uses a concatenation of
+        * DTSTAMP '-' LASTMODIFIED '-' SEQUENCE.
+        **/
+       signals[DUP_COMPONENT_REVISION] = g_signal_new (
+               "dup-component-revision",
+               G_OBJECT_CLASS_TYPE (klass),
+               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+               G_STRUCT_OFFSET (ECalCacheClass, dup_component_revision),
+               g_signal_accumulator_first_wins,
+               NULL,
+               g_cclosure_marshal_generic,
+               G_TYPE_STRING, 1,
+               E_TYPE_CAL_COMPONENT);
+}
+
+static void
+e_cal_cache_init (ECalCache *cal_cache)
+{
+       cal_cache->priv = G_TYPE_INSTANCE_GET_PRIVATE (cal_cache, E_TYPE_CAL_CACHE, ECalCachePrivate);
+       cal_cache->priv->loaded_timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
cal_cache_free_zone);
+
+       g_mutex_init (&cal_cache->priv->loaded_timezones_lock);
+}
diff --git a/src/calendar/libedata-cal/e-cal-cache.h b/src/calendar/libedata-cal/e-cal-cache.h
new file mode 100644
index 0000000..2058b2b
--- /dev/null
+++ b/src/calendar/libedata-cal/e-cal-cache.h
@@ -0,0 +1,278 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__LIBEDATA_CAL_H_INSIDE__) && !defined (LIBEDATA_CAL_COMPILATION)
+#error "Only <libedata-cal/libedata-cal.h> should be included directly."
+#endif
+
+#ifndef E_CAL_CACHE_H
+#define E_CAL_CACHE_H
+
+#include <libebackend/libebackend.h>
+#include <libecal/libecal.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_CACHE \
+       (e_cal_cache_get_type ())
+#define E_CAL_CACHE(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_CAL_CACHE, ECalCache))
+#define E_CAL_CACHE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_CAL_CACHE, ECalCacheClass))
+#define E_IS_CAL_CACHE(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_CAL_CACHE))
+#define E_IS_CAL_CACHE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_CAL_CACHE))
+#define E_CAL_CACHE_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_CAL_CACHE, ECalCacheClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalCache ECalCache;
+typedef struct _ECalCacheClass ECalCacheClass;
+typedef struct _ECalCachePrivate ECalCachePrivate;
+
+/**
+ * ECalCacheSearchData:
+ * @uid: The UID of this component
+ * @rid: (nullable): The Recurrence-ID of this component
+ * @object: The component string
+ * @extra: Any extra data associated with the component
+ *
+ * This structure is used to represent components returned
+ * by the #ECalCache from various functions
+ * such as e_cal_cache_search().
+ *
+ * The @extra parameter will contain any data which was
+ * previously passed for this component in e_cal_cache_add_component()
+ * or set with e_cal_cache_set_component_extra().
+ *
+ * These should be freed with e_cal_cache_search_data_free().
+ *
+ * Since: 3.26
+ **/
+typedef struct {
+       gchar *uid;
+       gchar *rid;
+       gchar *object;
+       gchar *extra;
+} ECalCacheSearchData;
+
+#define E_TYPE_CAL_CACHE_SEARCH_DATA (e_cal_cache_search_data_get_type ())
+
+GType          e_cal_cache_search_data_get_type
+                                               (void) G_GNUC_CONST;
+ECalCacheSearchData *
+               e_cal_cache_search_data_new     (const gchar *uid,
+                                                const gchar *rid,
+                                                const gchar *object,
+                                                const gchar *extra);
+ECalCacheSearchData *
+               e_cal_cache_search_data_copy    (const ECalCacheSearchData *data);
+void           e_cal_cache_search_data_free    (/* ECalCacheSearchData * */ gpointer data);
+
+/**
+ * ECalCacheSearchFunc:
+ * @cache: an #ECache
+ * @uid: a unique object identifier
+ * @rid: (nullable): an optional Recurrence-ID of the object
+ * @revision: the object revision
+ * @object: the object itself
+ * @extra: extra data stored with the object
+ * @offline_state: objects offline state, one of #EOfflineState
+ * @user_data: user data, as used in e_cache_cache_search_with_callback()
+ *
+ * A callback called for each object row when using
+ * e_cal_cache_search_with_callback() function.
+ *
+ * Returns: %TRUE to continue, %FALSE to stop walk through.
+ *
+ * Since: 3.26
+ **/
+typedef gboolean (* ECalCacheSearchFunc)       (ECache *cache,
+                                                const gchar *uid,
+                                                const gchar *rid,
+                                                const gchar *revision,
+                                                const gchar *object,
+                                                const gchar *extra,
+                                                EOfflineState offline_state,
+                                                gpointer user_data);
+
+/**
+ * ECalCache:
+ *
+ * Contains only private data that should be read and manipulated using
+ * the functions below.
+ *
+ * Since: 3.26
+ **/
+struct _ECalCache {
+       /*< private >*/
+       ECache parent;
+       ECalCachePrivate *priv;
+};
+
+/**
+ * ECalCacheClass:
+ *
+ * Class structure for the #ECalCache class.
+ *
+ * Since: 3.26
+ */
+struct _ECalCacheClass {
+       /*< private >*/
+       ECacheClass parent_class;
+
+       /* Signals */
+       gchar *         (* dup_component_revision)
+                                               (ECalCache *cal_cache,
+                                                ECalComponent *component);
+
+       /* Padding for future expansion */
+       gpointer reserved[10];
+};
+
+GType          e_cal_cache_get_type            (void) G_GNUC_CONST;
+
+ECalCache *    e_cal_cache_new                 (const gchar *filename,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gchar *                e_cal_cache_dup_component_revision
+                                               (ECalCache *cal_cache,
+                                                ECalComponent *component);
+gboolean       e_cal_cache_put_component       (ECalCache *cal_cache,
+                                                ECalComponent *component,
+                                                const gchar *extra,
+                                                ECacheOfflineFlag offline_flag,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_put_components      (ECalCache *cal_cache,
+                                                const GSList *components, /* ECalComponent * */
+                                                const GSList *extras, /* gchar * */
+                                                ECacheOfflineFlag offline_flag,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_remove_component    (ECalCache *cal_cache,
+                                                const gchar *uid,
+                                                const gchar *rid,
+                                                ECacheOfflineFlag offline_flag,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_remove_components   (ECalCache *cal_cache,
+                                                const GSList *ids, /* ECalComponentId * */
+                                                ECacheOfflineFlag offline_flag,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_get_component       (ECalCache *cal_cache,
+                                                const gchar *uid,
+                                                const gchar *rid,
+                                                ECalComponent **out_component, /* ECalComponent * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_get_component_as_string
+                                               (ECalCache *cal_cache,
+                                                const gchar *uid,
+                                                const gchar *rid,
+                                                gchar **out_icalstring, /* gchar * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_set_component_extra (ECalCache *cal_cache,
+                                                const gchar *uid,
+                                                const gchar *rid,
+                                                const gchar *extra,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_get_component_extra (ECalCache *cal_cache,
+                                                const gchar *uid,
+                                                const gchar *rid,
+                                                gchar **out_extra,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_get_components_by_uid
+                                               (ECalCache *cal_cache,
+                                                const gchar *uid,
+                                                GSList **out_components, /* ECalComponent * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_get_components_by_uid_as_strings
+                                               (ECalCache *cal_cache,
+                                                const gchar *uid,
+                                                GSList **out_icalstrings, /* gchar * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_get_components_in_range
+                                               (ECalCache *cal_cache,
+                                                time_t range_start,
+                                                time_t range_end,
+                                                GSList **out_components, /* ECalComponent * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_get_components_in_range_as_strings
+                                               (ECalCache *cal_cache,
+                                                time_t range_start,
+                                                time_t range_end,
+                                                GSList **out_icalstrings, /* gchar * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_search              (ECalCache *cal_cache,
+                                                const gchar *sexp,
+                                                GSList **out_components, /* ECalComponent * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_search_as_strings   (ECalCache *cal_cache,
+                                                const gchar *sexp,
+                                                GSList **out_icalstrings, /* gchar * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_search_ids          (ECalCache *cal_cache,
+                                                const gchar *sexp,
+                                                GSList **out_ids, /* ECalComponentId * */
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_search_with_callback
+                                               (ECalCache *cal_cache,
+                                                const gchar *sexp,
+                                                ECalCacheSearchFunc func,
+                                                gpointer user_data,
+                                                GCancellable *cancellable,
+                                                GError **error);
+
+gboolean       e_cal_cache_put_timezone        (ECalCache *cal_cache,
+                                                icaltimezone *zone,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_get_timezone        (ECalCache *cal_cache,
+                                                const gchar *tzid,
+                                                icaltimezone **out_zone,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_dup_timezone_as_string
+                                               (ECalCache *cal_cache,
+                                                const gchar *tzid,
+                                                gchar **out_zone_string,
+                                                GCancellable *cancellable,
+                                                GError **error);
+gboolean       e_cal_cache_list_timezones      (ECalCache *cal_cache,
+                                                GList **out_timezones,
+                                                GCancellable *cancellable,
+                                                GError **error);
+
+#endif /* E_CAL_CACHE_H */
diff --git a/src/calendar/libedata-cal/libedata-cal.h b/src/calendar/libedata-cal/libedata-cal.h
index c37bef9..c93fdac 100644
--- a/src/calendar/libedata-cal/libedata-cal.h
+++ b/src/calendar/libedata-cal/libedata-cal.h
@@ -31,6 +31,7 @@
 #include <libedata-cal/e-cal-backend-store.h>
 #include <libedata-cal/e-cal-backend-sync.h>
 #include <libedata-cal/e-cal-backend-util.h>
+#include <libedata-cal/e-cal-cache.h>
 #include <libedata-cal/e-data-cal-factory.h>
 #include <libedata-cal/e-data-cal.h>
 #include <libedata-cal/e-data-cal-view.h>
diff --git a/src/libebackend/e-cache.c b/src/libebackend/e-cache.c
index 47a5832..9238b1b 100644
--- a/src/libebackend/e-cache.c
+++ b/src/libebackend/e-cache.c
@@ -78,10 +78,281 @@ G_DEFINE_QUARK (e-cache-error-quark, e_cache_error)
 
 G_DEFINE_ABSTRACT_TYPE (ECache, e_cache, G_TYPE_OBJECT)
 
+G_DEFINE_BOXED_TYPE (ECacheColumnValues, e_cache_column_values, e_cache_column_values_copy, 
e_cache_column_values_free)
 G_DEFINE_BOXED_TYPE (ECacheOfflineChange, e_cache_offline_change, e_cache_offline_change_copy, 
e_cache_offline_change_free)
 G_DEFINE_BOXED_TYPE (ECacheColumnInfo, e_cache_column_info, e_cache_column_info_copy, 
e_cache_column_info_free)
 
 /**
+ * e_cache_column_values_new:
+ *
+ * Creates a new #ECacheColumnValues to store values for additional columns.
+ * The column names are compared case insensitively.
+ *
+ * Returns: (transfer full): a new #ECacheColumnValues. Free with e_cache_column_values_free(),
+ *    when no longer needed.
+ *
+ * Since: 3.26
+ **/
+ECacheColumnValues *
+e_cache_column_values_new (void)
+{
+       return (ECacheColumnValues *) g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, 
g_free);
+}
+
+/**
+ * e_cache_column_values_copy:
+ * @other_columns: (nullable): an #ECacheColumnValues
+ *
+ * Returns: (transfer full): Copy of the @other_columns. Free with
+ *    e_cache_column_values_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+ECacheColumnValues *
+e_cache_column_values_copy (ECacheColumnValues *other_columns)
+{
+       GHashTableIter iter;
+       gpointer name, value;
+       ECacheColumnValues *copy;
+
+       if (!other_columns)
+               return NULL;
+
+       copy = e_cache_column_values_new ();
+
+       e_cache_column_values_init_iter (other_columns, &iter);
+       while (g_hash_table_iter_next (&iter, &name, &value)) {
+               e_cache_column_values_put (copy, name, value);
+       }
+
+       return copy;
+}
+
+/**
+ * e_cache_column_values_free:
+ * @other_columns: (nullable): an #ECacheColumnValues
+ *
+ * Frees previously allocated @other_columns with
+ * e_cache_column_values_new() or e_cache_column_values_copy().
+ *
+ * Since: 3.26
+ **/
+void
+e_cache_column_values_free (ECacheColumnValues *other_columns)
+{
+       if (other_columns)
+               g_hash_table_destroy ((GHashTable *) other_columns);
+}
+
+/**
+ * e_cache_column_values_put:
+ * @other_columns: an #ECacheColumnValues
+ * @name: a column name
+ * @value: (nullable): a column value
+ *
+ * Puts the @value for column @name. If contains a value for the same
+ * column, then it is replaced. This creates a copy of both @name
+ * and @value.
+ *
+ * Since: 3.26
+ **/
+void
+e_cache_column_values_put (ECacheColumnValues *other_columns,
+                          const gchar *name,
+                          const gchar *value)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_if_fail (other_columns != NULL);
+       g_return_if_fail (name != NULL);
+
+       g_hash_table_insert (hash_table, g_strdup (name), g_strdup (value));
+}
+
+/**
+ * e_cache_column_values_take_value:
+ * @other_columns: an #ECacheColumnValues
+ * @name: a column name
+ * @value: (nullable) (in) (transfer full): a column value
+ *
+ * Puts the @value for column @name. If contains a value for the same
+ * column, then it is replaced. This creates a copy of the @name, but
+ * takes owner ship of the @value.
+ *
+ * Since: 3.26
+ **/
+void
+e_cache_column_values_take_value (ECacheColumnValues *other_columns,
+                                 const gchar *name,
+                                 gchar *value)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_if_fail (other_columns != NULL);
+       g_return_if_fail (name != NULL);
+
+       g_hash_table_insert (hash_table, g_strdup (name), value);
+}
+
+/**
+ * e_cache_column_values_take:
+ * @other_columns: an #ECacheColumnValues
+ * @name: (in) (transfer full): a column name
+ * @value: (nullable) (in) (transfer full): a column value
+ *
+ * Puts the @value for column @name. If contains a value for the same
+ * column, then it is replaced. This creates takes ownership of both
+ * the @name and the @value.
+ *
+ * Since: 3.26
+ **/
+void
+e_cache_column_values_take (ECacheColumnValues *other_columns,
+                           gchar *name,
+                           gchar *value)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_if_fail (other_columns != NULL);
+       g_return_if_fail (name != NULL);
+
+       g_hash_table_insert (hash_table, name, value);
+}
+
+/**
+ * e_cache_column_values_contains:
+ * @other_columns: an #ECacheColumnValues
+ * @name: a column name
+ *
+ * Returns: Whether @other_columns contains column named @name.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cache_column_values_contains (ECacheColumnValues *other_columns,
+                               const gchar *name)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_val_if_fail (other_columns != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       return g_hash_table_contains (hash_table, name);
+}
+
+/**
+ * e_cache_column_values_remove:
+ * @other_columns: an #ECacheColumnValues
+ * @name: a column name
+ *
+ * Removes value for the column named @name from @other_columns.
+ *
+ * Returns: Whether such column existed and had been removed.
+ *
+ * Since: 3.26
+ **/
+gboolean
+e_cache_column_values_remove (ECacheColumnValues *other_columns,
+                             const gchar *name)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_val_if_fail (other_columns != NULL, FALSE);
+       g_return_val_if_fail (name != NULL, FALSE);
+
+       return g_hash_table_remove (hash_table, name);
+}
+
+/**
+ * e_cache_column_values_remove_all:
+ * @other_columns: an #ECacheColumnValues
+ *
+ * Removes all values from the @other_columns, leaving it empty.
+ *
+ * Since: 3.26
+ **/
+void
+e_cache_column_values_remove_all (ECacheColumnValues *other_columns)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_if_fail (other_columns != NULL);
+
+       g_hash_table_remove_all (hash_table);
+}
+
+/**
+ * e_cache_column_values_lookup:
+ * @other_columns: an #ECacheColumnValues
+ * @name: a column name
+ *
+ * Looks up currently stored value for the column named @name.
+ * As the values can be %NULL one cannot distinguish between
+ * a column which doesn't have stored any value and a column
+ * which has stored %NULL value. Use e_cache_column_values_contains()
+ * to check whether such column exitst in the @other_columns.
+ * The returned pointer is owned by @other_columns and is valid until
+ * the value is overwritten of the @other_columns freed.
+ *
+ * Returns: Stored value for the column named @name, or %NULL, if
+ *    no such column values is stored.
+ *
+ * Since: 3.26
+ **/
+const gchar *
+e_cache_column_values_lookup (ECacheColumnValues *other_columns,
+                             const gchar *name)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_val_if_fail (other_columns != NULL, NULL);
+       g_return_val_if_fail (name != NULL, NULL);
+
+       return g_hash_table_lookup (hash_table, name);
+}
+
+/**
+ * e_cache_column_values_get_size:
+ * @other_columns: an #ECacheColumnValues
+ *
+ * Returns: How many columns are stored in the @other_columns.
+ *
+ * Since: 3.26
+ **/
+guint
+e_cache_column_values_get_size (ECacheColumnValues *other_columns)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_val_if_fail (other_columns != NULL, 0);
+
+       return g_hash_table_size (hash_table);
+}
+
+/**
+ * e_cache_column_values_init_iter:
+ * @other_columns: an #ECacheColumnValues
+ * @iter: a #GHashTableIter
+ *
+ * Initialized the @iter, thus the @other_columns can be traversed
+ * with g_hash_table_iter_next(). The key is a column name and
+ * the value is the corresponding column value.
+ *
+ * Since: 3.26
+ **/
+void
+e_cache_column_values_init_iter (ECacheColumnValues *other_columns,
+                                GHashTableIter *iter)
+{
+       GHashTable *hash_table = (GHashTable *) other_columns;
+
+       g_return_if_fail (other_columns != NULL);
+       g_return_if_fail (iter != NULL);
+
+       g_hash_table_iter_init (iter, hash_table);
+}
+
+/**
  * e_cache_offline_change_new:
  * @uid: a unique object identifier
  * @revision: (nullable): a revision of the object
@@ -394,7 +665,7 @@ e_cache_set_key_internal (ECache *cache,
 
        if (value) {
                success = e_cache_sqlite_exec_printf (cache,
-                       "INSERT or REPLACE INTO " E_CACHE_TABLE_KEYS " (key, value) values (%Q, %Q)",
+                       "INSERT or REPLACE INTO " E_CACHE_TABLE_KEYS " (key, value) VALUES (%Q, %Q)",
                        NULL, NULL, NULL, error,
                        usekey, value);
        } else {
@@ -882,7 +1153,7 @@ e_cache_contains (ECache *cache,
 struct GetObjectData {
        gchar *object;
        gchar **out_revision;
-       GHashTable **out_other_columns;
+       ECacheColumnValues **out_other_columns;
 };
 
 static gboolean
@@ -910,9 +1181,9 @@ e_cache_get_object_cb (ECache *cache,
                        gd->object = g_strdup (column_values[ii]);
                } else if (gd->out_other_columns) {
                        if (!*gd->out_other_columns)
-                               *gd->out_other_columns = g_hash_table_new_full (camel_strcase_hash, 
camel_strcase_equal, g_free, g_free);
+                               *gd->out_other_columns = e_cache_column_values_new ();
 
-                       g_hash_table_insert (*gd->out_other_columns, g_strdup (column_names[ii]), g_strdup 
(column_values[ii]));
+                       e_cache_column_values_put (*gd->out_other_columns, column_names[ii], 
column_values[ii]);
                } else if (gd->object && (!gd->out_revision || *gd->out_revision)) {
                        /* Short-break the cycle when the other columns are not requested and
                           the object/revision values were already read. */
@@ -929,8 +1200,8 @@ e_cache_get_object_cb (ECache *cache,
  * @uid: a unique identifier of an object
  * @out_revision: (out) (nullable) (transfer full): an out variable for a revision
  *    of the object, or %NULL to ignore
- * @out_other_columns: (out) (nullable) (transfer full) (element-type utf8 utf8): an out
- *    variable for other columns, as defined when crating the @cache, or %NULL to ignore
+ * @out_other_columns: (out) (nullable) (transfer full): an out
+ *    variable for #ECacheColumnValues other columns, as defined when creating the @cache, or %NULL to ignore
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
@@ -939,7 +1210,8 @@ e_cache_get_object_cb (ECache *cache,
  * Free it with g_free() when no longer needed. Similarly the @out_other_columns
  * contains a column name to column value strings for additional columns which had
  * been requested when calling e_cache_initialize_sync(), if not %NULL.
- * Free the returned #GHashTable with g_hash_table_unref(), when no longer needed.
+ * Free the returned #ECacheColumnValues with e_cache_column_values_free(), when
+ * no longer needed.
  *
  * Returns: (nullable) (transfer full): An object with the given @uid. Free it
  *    with g_free(), when no longer needed. Returns %NULL on error, like when
@@ -951,7 +1223,7 @@ gchar *
 e_cache_get (ECache *cache,
             const gchar *uid,
             gchar **out_revision,
-            GHashTable **out_other_columns,
+            ECacheColumnValues **out_other_columns,
             GCancellable *cancellable,
             GError **error)
 {
@@ -987,13 +1259,13 @@ e_cache_put_locked (ECache *cache,
                    const gchar *uid,
                    const gchar *revision,
                    const gchar *object,
-                   GHashTable *other_columns,
+                   ECacheColumnValues *other_columns,
                    EOfflineState offline_state,
                    gboolean is_replace,
                    GCancellable *cancellable,
                    GError **error)
 {
-       GHashTable *my_other_columns = NULL;
+       ECacheColumnValues *my_other_columns = NULL;
        gboolean success = TRUE;
 
        g_return_val_if_fail (E_IS_CACHE (cache), FALSE);
@@ -1001,7 +1273,7 @@ e_cache_put_locked (ECache *cache,
        g_return_val_if_fail (object != NULL, FALSE);
 
        if (!other_columns) {
-               my_other_columns = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, 
g_free);
+               my_other_columns = e_cache_column_values_new ();
                other_columns = my_other_columns;
        }
 
@@ -1022,8 +1294,7 @@ e_cache_put_locked (ECache *cache,
                success = klass->put_locked (cache, uid, revision, object, other_columns, offline_state, 
is_replace, cancellable, error);
        }
 
-       if (my_other_columns)
-               g_hash_table_unref (my_other_columns);
+       e_cache_column_values_free (my_other_columns);
 
        return success;
 }
@@ -1034,7 +1305,7 @@ e_cache_put_locked (ECache *cache,
  * @uid: a unique identifier of an object
  * @revision: (nullable): a revision of the object
  * @object: the object itself
- * @other_columns: (nullable) (element-type utf8 utf8): what other columns to set; can be %NULL
+ * @other_columns: (nullable): an #ECacheColumnValues with other columns to set; can be %NULL
  * @offline_flag: one of #ECacheOfflineFlag, whether putting this object in offline
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
@@ -1055,7 +1326,7 @@ e_cache_put (ECache *cache,
             const gchar *uid,
             const gchar *revision,
             const gchar *object,
-            GHashTable *other_columns,
+            ECacheColumnValues *other_columns,
             ECacheOfflineFlag offline_flag,
             GCancellable *cancellable,
             GError **error)
@@ -1749,7 +2020,7 @@ e_cache_foreach_update (ECache *cache,
                                        gchar *new_revision = NULL;
                                        gchar *new_object = NULL;
                                        EOfflineState new_offline_state = fr->offline_state;
-                                       GHashTable *new_other_columns = NULL;
+                                       ECacheColumnValues *new_other_columns = NULL;
 
                                        success = func (cache, fr->uid, fr->revision, fr->object, 
fr->offline_state,
                                                fr->ncols, (const gchar **) fu.column_names->pdata,
@@ -1761,7 +2032,7 @@ e_cache_foreach_update (ECache *cache,
                                            (new_revision && g_strcmp0 (new_revision, fr->revision) != 0) ||
                                            (new_object && g_strcmp0 (new_object, fr->object) != 0) ||
                                            (new_offline_state != fr->offline_state) ||
-                                           (new_other_columns && g_hash_table_size (new_other_columns) > 
0))) {
+                                           (new_other_columns && e_cache_column_values_get_size 
(new_other_columns) > 0))) {
                                                success = e_cache_put_locked (cache,
                                                        fr->uid,
                                                        new_revision ? new_revision : fr->revision,
@@ -1773,8 +2044,7 @@ e_cache_foreach_update (ECache *cache,
 
                                        g_free (new_revision);
                                        g_free (new_object);
-                                       if (new_other_columns)
-                                               g_hash_table_unref (new_other_columns);
+                                       e_cache_column_values_free (new_other_columns);
 
                                        if (!g_slist_next (link)) {
                                                g_free (uid);
@@ -2372,7 +2642,7 @@ e_cache_put_locked_default (ECache *cache,
                            const gchar *uid,
                            const gchar *revision,
                            const gchar *object,
-                           GHashTable *other_columns,
+                           ECacheColumnValues *other_columns,
                            EOfflineState offline_state,
                            gboolean is_replace,
                            GCancellable *cancellable,
@@ -2398,7 +2668,7 @@ e_cache_put_locked_default (ECache *cache,
                GHashTableIter iter;
                gpointer key, value;
 
-               g_hash_table_iter_init (&iter, (GHashTable *) other_columns);
+               e_cache_column_values_init_iter (other_columns, &iter);
                while (g_hash_table_iter_next (&iter, &key, &value)) {
                        if (!other_names)
                                other_names = g_string_new ("");
@@ -2538,7 +2808,7 @@ e_cache_before_put_default (ECache *cache,
                            const gchar *uid,
                            const gchar *revision,
                            const gchar *object,
-                           GHashTable *other_columns,
+                           ECacheColumnValues *other_columns,
                            gboolean is_replace,
                            GCancellable *cancellable,
                            GError **error)
diff --git a/src/libebackend/e-cache.h b/src/libebackend/e-cache.h
index 7c113a4..0b83aee 100644
--- a/src/libebackend/e-cache.h
+++ b/src/libebackend/e-cache.h
@@ -94,6 +94,35 @@ typedef enum {
        E_CACHE_ERROR_LOAD
 } ECacheError;
 
+typedef struct _ECacheColumnValues ECacheColumnValues;
+
+#define E_TYPE_CACHE_COLUMN_VALUES (e_cache_column_values_get_type ())
+GType          e_cache_column_values_get_type  (void) G_GNUC_CONST;
+ECacheColumnValues *
+               e_cache_column_values_new       (void);
+ECacheColumnValues *
+               e_cache_column_values_copy      (ECacheColumnValues *other_columns);
+void           e_cache_column_values_free      (ECacheColumnValues *other_columns);
+void           e_cache_column_values_put       (ECacheColumnValues *other_columns,
+                                                const gchar *name,
+                                                const gchar *value);
+void           e_cache_column_values_take_value(ECacheColumnValues *other_columns,
+                                                const gchar *name,
+                                                gchar *value);
+void           e_cache_column_values_take      (ECacheColumnValues *other_columns,
+                                                gchar *name,
+                                                gchar *value);
+gboolean       e_cache_column_values_contains  (ECacheColumnValues *other_columns,
+                                                const gchar *name);
+gboolean       e_cache_column_values_remove    (ECacheColumnValues *other_columns,
+                                                const gchar *name);
+void           e_cache_column_values_remove_all(ECacheColumnValues *other_columns);
+const gchar *  e_cache_column_values_lookup    (ECacheColumnValues *other_columns,
+                                                const gchar *name);
+guint          e_cache_column_values_get_size  (ECacheColumnValues *other_columns);
+void           e_cache_column_values_init_iter (ECacheColumnValues *other_columns,
+                                                GHashTableIter *iter);
+
 typedef struct {
        gchar *uid;
        gchar *revision;
@@ -233,7 +262,7 @@ typedef gboolean (* ECacheForeachFunc)      (ECache *cache,
  * @out_revision: (out): the new object revision to set; keep it untouched to not change
  * @out_object: (out): the new object to set; keep it untouched to not change
  * @out_offline_state: (out): the offline state to set; the default is the same as @offline_state
- * @out_other_columns: (out) (element-type utf8 utf8) (transfer full): other columns to set; keep it 
untouched to not change any
+ * @out_other_columns: (out) (transfer full): an #ECacheColumnValues with other columns to set; keep it 
untouched to not change any
  * @user_data: user data, as used in e_cache_foreach_update()
  *
  * A callback called for each object row when using e_cache_foreach_update() function.
@@ -254,7 +283,7 @@ typedef gboolean (* ECacheUpdateFunc)       (ECache *cache,
                                         gchar **out_revision,
                                         gchar **out_object,
                                         EOfflineState *out_offline_state,
-                                        GHashTable **out_other_columns,
+                                        ECacheColumnValues **out_other_columns,
                                         gpointer user_data);
 
 /**
@@ -300,7 +329,7 @@ struct _ECacheClass {
                                                 const gchar *uid,
                                                 const gchar *revision,
                                                 const gchar *object,
-                                                GHashTable *other_columns,
+                                                ECacheColumnValues *other_columns,
                                                 EOfflineState offline_state,
                                                 gboolean is_replace,
                                                 GCancellable *cancellable,
@@ -324,7 +353,7 @@ struct _ECacheClass {
                                                 const gchar *uid,
                                                 const gchar *revision,
                                                 const gchar *object,
-                                                GHashTable *other_columns,
+                                                ECacheColumnValues *other_columns,
                                                 gboolean is_replace,
                                                 GCancellable *cancellable,
                                                 GError **error);
@@ -358,14 +387,14 @@ gboolean  e_cache_contains                (ECache *cache,
 gchar *                e_cache_get                     (ECache *cache,
                                                 const gchar *uid,
                                                 gchar **out_revision,
-                                                GHashTable **out_other_columns,
+                                                ECacheColumnValues **out_other_columns,
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_cache_put                     (ECache *cache,
                                                 const gchar *uid,
                                                 const gchar *revision,
                                                 const gchar *object,
-                                                GHashTable *other_columns,
+                                                ECacheColumnValues *other_columns,
                                                 ECacheOfflineFlag offline_flag,
                                                 GCancellable *cancellable,
                                                 GError **error);
diff --git a/src/libedataserver/e-data-server-util.c b/src/libedataserver/e-data-server-util.c
index b68c0d1..6f57699 100644
--- a/src/libedataserver/e-data-server-util.c
+++ b/src/libedataserver/e-data-server-util.c
@@ -505,6 +505,52 @@ e_util_utf8_remove_accents (const gchar *str)
 }
 
 /**
+ * e_util_utf8_decompose:
+ * @text: a UTF-8 string
+ *
+ * Converts the @text into a decomposed variant and strips it, which
+ * allows also cheap case insensitive comparision afterwards. This
+ * produces an output as being used in e_util_utf8_strstrcasedecomp().
+ *
+ * Returns: (transfer full): A newly allocated string, a decomposed
+ *    variant of the @text. Free with g_free(), when no longer needed.
+ *
+ * Since: 3.26
+ **/
+gchar *
+e_util_utf8_decompose (const gchar *text)
+{
+       gunichar unival;
+       const gchar *p;
+       gchar utf8[12];
+       GString *decomp;
+
+       if (!text)
+               return NULL;
+
+       decomp = g_string_sized_new (strlen (text) + 1);
+
+       for (p = e_util_unicode_get_utf8 (text, &unival);
+            p && unival;
+            p = e_util_unicode_get_utf8 (p, &unival)) {
+               gunichar sc;
+               sc = stripped_char (unival);
+               if (sc) {
+                       gint ulen = g_unichar_to_utf8 (sc, utf8);
+                       g_string_append_len (decomp, utf8, ulen);
+               }
+       }
+
+       /* NULL means there was illegal utf-8 sequence */
+       if (!p || !decomp->len) {
+               g_string_free (decomp, TRUE);
+               return NULL;
+       }
+
+       return g_string_free (decomp, FALSE);
+}
+
+/**
  * e_util_utf8_make_valid:
  * @str: a UTF-8 string
  *
diff --git a/src/libedataserver/e-data-server-util.h b/src/libedataserver/e-data-server-util.h
index 6b26f12..886fe51 100644
--- a/src/libedataserver/e-data-server-util.h
+++ b/src/libedataserver/e-data-server-util.h
@@ -55,6 +55,7 @@ const gchar * e_util_utf8_strstrcasedecomp    (const gchar *haystack,
 gint           e_util_utf8_strcasecmp          (const gchar *s1,
                                                 const gchar *s2);
 gchar *                e_util_utf8_remove_accents      (const gchar *str);
+gchar *                e_util_utf8_decompose           (const gchar *text);
 gchar *                e_util_utf8_make_valid          (const gchar *str);
 gchar *                e_util_utf8_data_make_valid     (const gchar *data,
                                                 gsize data_bytes);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]