[evolution-data-server/wip/offline-cache] Add unfinished ECalCache descendant of ECache
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/wip/offline-cache] Add unfinished ECalCache descendant of ECache
- Date: Tue, 21 Mar 2017 17:32:08 +0000 (UTC)
commit f48a9b605ab009a962afb2757184fdc2dcddb0a6
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]