/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2017 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 . */ /** * 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 #include #include #include #include #include "e-cal-backend-sexp.h" #include "e-cal-cache.h" #define E_CAL_CACHE_VERSION 3 #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_PERCENT_COMPLETE "percent_complete" #define ECC_COLUMN_CATEGORIES "categories" #define ECC_COLUMN_HAS_ALARM "has_alarm" #define ECC_COLUMN_HAS_ATTACHMENT "has_attachment" #define ECC_COLUMN_HAS_START "has_start" #define ECC_COLUMN_HAS_RECURRENCES "has_recurrences" #define ECC_COLUMN_EXTRA "bdata" #define ECC_COLUMN_CUSTOM_FLAGS "custom_flags" struct _ECalCachePrivate { gboolean initializing; GHashTable *loaded_timezones; /* gchar *tzid ~> ICalTimezone * */ GHashTable *modified_timezones; /* gchar *tzid ~> ICalTimezone * */ GRecMutex timezones_lock; GHashTable *sexps; /* gint ~> ECalBackendSExp * */ GMutex sexps_lock; }; enum { DUP_COMPONENT_REVISION, GET_TIMEZONE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; /* Private function, not meant to be part of the public API */ void _e_cal_cache_remove_loaded_timezones (ECalCache *cal_cache); static void ecc_timezone_cache_init (ETimezoneCacheInterface *iface); G_DEFINE_TYPE_WITH_CODE (ECalCache, e_cal_cache, E_TYPE_CACHE, G_ADD_PRIVATE (ECalCache) G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL) G_IMPLEMENT_INTERFACE (E_TYPE_TIMEZONE_CACHE, ecc_timezone_cache_init)) G_DEFINE_BOXED_TYPE (ECalCacheOfflineChange, e_cal_cache_offline_change, e_cal_cache_offline_change_copy, e_cal_cache_offline_change_free) 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_offline_change_new: * @uid: a unique component identifier * @rid: (nullable): a Recurrence-ID of the component * @revision: (nullable): a revision of the component * @object: (nullable): component itself * @state: an #EOfflineState * * Creates a new #ECalCacheOfflineChange with the offline @state * information for the given @uid. * * Returns: (transfer full): A new #ECalCacheOfflineChange. Free it with * e_cal_cache_offline_change_free() when no longer needed. * * Since: 3.26 **/ ECalCacheOfflineChange * e_cal_cache_offline_change_new (const gchar *uid, const gchar *rid, const gchar *revision, const gchar *object, EOfflineState state) { ECalCacheOfflineChange *change; g_return_val_if_fail (uid != NULL, NULL); change = g_new0 (ECalCacheOfflineChange, 1); change->uid = g_strdup (uid); change->rid = g_strdup (rid); change->revision = g_strdup (revision); change->object = g_strdup (object); change->state = state; return change; } /** * e_cal_cache_offline_change_copy: * @change: (nullable): a source #ECalCacheOfflineChange to copy, or %NULL * * Returns: (transfer full): Copy of the given @change. Free it with * e_cal_cache_offline_change_free() when no longer needed. * If the @change is %NULL, then returns %NULL as well. * * Since: 3.26 **/ ECalCacheOfflineChange * e_cal_cache_offline_change_copy (const ECalCacheOfflineChange *change) { if (!change) return NULL; return e_cal_cache_offline_change_new (change->uid, change->rid, change->revision, change->object, change->state); } /** * e_cal_cache_offline_change_free: * @change: (nullable): an #ECalCacheOfflineChange * * Frees the @change structure, previously allocated with e_cal_cache_offline_change_new() * or e_cal_cache_offline_change_copy(). * * Since: 3.26 **/ void e_cal_cache_offline_change_free (gpointer change) { ECalCacheOfflineChange *chng = change; if (chng) { g_free (chng->uid); g_free (chng->rid); g_free (chng->revision); g_free (chng->object); g_free (chng); } } /** * 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: * @ptr: (nullable): an #ECalCacheSearchData * * Frees the @ptr 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 gint ecc_take_sexp_object (ECalCache *cal_cache, ECalBackendSExp *sexp) { gint sexp_id; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), 0); g_return_val_if_fail (E_IS_CAL_BACKEND_SEXP (sexp), 0); g_mutex_lock (&cal_cache->priv->sexps_lock); sexp_id = GPOINTER_TO_INT (sexp); while (g_hash_table_contains (cal_cache->priv->sexps, GINT_TO_POINTER (sexp_id))) { sexp_id++; } g_hash_table_insert (cal_cache->priv->sexps, GINT_TO_POINTER (sexp_id), sexp); g_mutex_unlock (&cal_cache->priv->sexps_lock); return sexp_id; } static void ecc_free_sexp_object (ECalCache *cal_cache, gint sexp_id) { g_return_if_fail (E_IS_CAL_CACHE (cal_cache)); g_mutex_lock (&cal_cache->priv->sexps_lock); g_warn_if_fail (g_hash_table_remove (cal_cache->priv->sexps, GINT_TO_POINTER (sexp_id))); g_mutex_unlock (&cal_cache->priv->sexps_lock); } static ECalBackendSExp * ecc_ref_sexp_object (ECalCache *cal_cache, gint sexp_id) { ECalBackendSExp *sexp; g_mutex_lock (&cal_cache->priv->sexps_lock); sexp = g_hash_table_lookup (cal_cache->priv->sexps, GINT_TO_POINTER (sexp_id)); if (sexp) g_object_ref (sexp); g_mutex_unlock (&cal_cache->priv->sexps_lock); return sexp; } /* check_sexp(sexp_id, icalstring) */ static void ecc_check_sexp_func (sqlite3_context *context, gint argc, sqlite3_value **argv) { ECalCache *cal_cache; ECalBackendSExp *sexp_obj; gint sexp_id; const gchar *icalstring; g_return_if_fail (context != NULL); g_return_if_fail (argc == 2); cal_cache = sqlite3_user_data (context); sexp_id = sqlite3_value_int (argv[0]); icalstring = (const gchar *) sqlite3_value_text (argv[1]); if (!E_IS_CAL_CACHE (cal_cache) || !icalstring || !*icalstring) { sqlite3_result_int (context, 0); return; } sexp_obj = ecc_ref_sexp_object (cal_cache, sexp_id); if (!sexp_obj) { sqlite3_result_int (context, 0); return; } if (e_cal_backend_sexp_match_object (sexp_obj, icalstring, E_TIMEZONE_CACHE (cal_cache))) sqlite3_result_int (context, 1); else sqlite3_result_int (context, 0); g_object_unref (sexp_obj); } /* negate(x) */ static void ecc_negate_func (sqlite3_context *context, gint argc, sqlite3_value **argv) { gint val; g_return_if_fail (context != NULL); g_return_if_fail (argc == 1); val = sqlite3_value_int (argv[0]); sqlite3_result_int (context, !val); } 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 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 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_PERCENT_COMPLETE, "INTEGER", NULL); add_column (ECC_COLUMN_CATEGORIES, "TEXT", NULL); add_column (ECC_COLUMN_HAS_ALARM, "INTEGER", NULL); add_column (ECC_COLUMN_HAS_ATTACHMENT, "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); add_column (ECC_COLUMN_CUSTOM_FLAGS, "INTEGER", 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", 2); 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 gboolean e_cal_cache_get_ids (ECache *cache, gint ncols, const gchar **column_names, const gchar **column_values, gpointer user_data) { GSList **out_ids = user_data; gchar *uid = NULL, *rid = NULL; 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 (out_ids != NULL, FALSE); if (ecc_decode_id_sql (column_values[0], &uid, &rid)) { *out_ids = g_slist_prepend (*out_ids, e_cal_component_id_new (uid, rid)); g_free (uid); g_free (rid); } return TRUE; } static ICalTimezone * ecc_resolve_tzid_cb (const gchar *tzid, gpointer user_data, GCancellable *cancellable, GError **error) { 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, cancellable, NULL) && zone) return zone; zone = i_cal_timezone_get_builtin_timezone (tzid); if (!zone) zone = i_cal_timezone_get_builtin_timezone_from_tzid (tzid); if (!zone) { tzid = e_cal_match_tzid (tzid); zone = i_cal_timezone_get_builtin_timezone (tzid); } if (!zone) zone = i_cal_timezone_get_builtin_timezone_from_tzid (tzid); return zone; } static gchar * ecc_encode_itt_to_sql (ICalTime *itt) { if (!itt) return g_strdup ("00000000000000"); return g_strdup_printf ("%04d%02d%02d%02d%02d%02d", i_cal_time_get_year (itt), i_cal_time_get_month (itt), i_cal_time_get_day (itt), i_cal_time_get_hour (itt), i_cal_time_get_minute (itt), i_cal_time_get_second (itt)); } static gchar * ecc_encode_time_to_sql (ECalCache *cal_cache, const ECalComponentDateTime *dt) { ICalTime *itt; ICalTimezone *zone = NULL; if (!dt || !e_cal_component_datetime_get_value (dt)) return NULL; itt = e_cal_component_datetime_get_value (dt); if (i_cal_time_is_date (itt) && !i_cal_time_is_utc (itt) && e_cal_component_datetime_get_tzid (dt)) zone = ecc_resolve_tzid_cb (e_cal_component_datetime_get_tzid (dt), cal_cache, NULL, NULL); i_cal_time_convert_timezone (itt, zone, i_cal_timezone_get_utc_timezone ()); return ecc_encode_itt_to_sql (itt); } static gchar * ecc_encode_timet_to_sql (ECalCache *cal_cache, time_t tt) { ICalTime *itt; gchar *res; if (tt <= 0) return NULL; itt = i_cal_time_new_from_timet_with_zone (tt, FALSE, i_cal_timezone_get_utc_timezone ()); res = ecc_encode_itt_to_sql (itt); g_clear_object (&itt); return res; } 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 && e_cal_component_text_get_value (text)) { gchar *str; str = e_util_utf8_decompose (e_cal_component_text_get_value (text)); 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; gchar *value; g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL); list = e_cal_component_get_comments (comp); value = ecc_extract_text_list (list); g_slist_free_full (list, e_cal_component_text_free); return value; } static gchar * ecc_extract_description (ECalComponent *comp) { GSList *list; gchar *value; g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL); list = e_cal_component_get_descriptions (comp); value = ecc_extract_text_list (list); g_slist_free_full (list, e_cal_component_text_free); 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, *link; GString *value; g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL); attendees = e_cal_component_get_attendees (comp); 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, e_cal_component_attendee_get_cn (att), e_cal_component_attendee_get_value (att)); } g_slist_free_full (attendees, e_cal_component_attendee_free); if (value->len) { /* This way it is encoded as: <\n> <\t> <\n> <\t> <\n> ... */ g_string_prepend_c (value, '\n'); g_string_append_c (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); org = e_cal_component_get_organizer (comp); if (!org || !e_cal_component_organizer_get_value (org)) { e_cal_component_organizer_free (org); return NULL; } value = g_string_new (""); ecc_encode_mail (value, e_cal_component_organizer_get_cn (org), e_cal_component_organizer_get_value (org)); e_cal_component_organizer_free (org); 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); categories = e_cal_component_get_categories_list (comp); 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); } } g_slist_free_full (categories, g_free); if (value->len) { /* This way it is encoded as: <\n> <\n> <\n> ... which allows to search for exact category with: LIKE "%\ncategory\n%" */ g_string_prepend_c (value, '\n'); g_string_append_c (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 (ICalPropertyStatus status) { switch (status) { case I_CAL_STATUS_NONE: return "not started"; case I_CAL_STATUS_COMPLETED: return "completed"; case I_CAL_STATUS_CANCELLED: return "cancelled"; case I_CAL_STATUS_INPROCESS: return "in progress"; case I_CAL_STATUS_NEEDSACTION: return "needs action"; case I_CAL_STATUS_TENTATIVE: return "tentative"; case I_CAL_STATUS_CONFIRMED: return "confirmed"; case I_CAL_STATUS_DRAFT: return "draft"; case I_CAL_STATUS_FINAL: return "final"; case I_CAL_STATUS_SUBMITTED: return "submitted"; case I_CAL_STATUS_PENDING: return "pending"; case I_CAL_STATUS_FAILED: return "failed"; case I_CAL_STATUS_DELETED: return "deleted"; case I_CAL_STATUS_X: break; } return NULL; } static void ecc_fill_other_columns (ECalCache *cal_cache, ECacheColumnValues *other_columns, ECalComponent *comp) { ECalComponentDateTime *dt; ECalComponentText *text; ECalComponentClassification classification = E_CAL_COMPONENT_CLASS_PUBLIC; ICalComponent *icomp; ICalPropertyStatus status = I_CAL_STATUS_NONE; ICalTime *itt; time_t occur_start = -1, occur_end = -1; gchar *str = NULL; gint nint; 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) icomp = e_cal_component_get_icalcomponent (comp); e_cal_util_get_component_occur_times ( comp, &occur_start, &occur_end, ecc_resolve_tzid_cb, cal_cache, i_cal_timezone_get_utc_timezone (), i_cal_component_isa (icomp)); dt = e_cal_component_get_dtstart (comp); add_value (ECC_COLUMN_OCCUR_START, dt && e_cal_component_datetime_get_value (dt) && (e_cal_component_datetime_get_tzid (dt) || i_cal_time_is_utc (e_cal_component_datetime_get_value (dt))) ? ecc_encode_timet_to_sql (cal_cache, occur_start) : NULL); has = dt && e_cal_component_datetime_get_value (dt); add_value (ECC_COLUMN_HAS_START, g_strdup (has ? "1" : "0")); e_cal_component_datetime_free (dt); dt = e_cal_component_get_dtend (comp); add_value (ECC_COLUMN_OCCUR_END, dt && e_cal_component_datetime_get_value (dt) && (e_cal_component_datetime_get_tzid (dt) || i_cal_time_is_utc (e_cal_component_datetime_get_value (dt))) ? ecc_encode_timet_to_sql (cal_cache, occur_end) : NULL); e_cal_component_datetime_free (dt); dt = e_cal_component_get_due (comp); add_value (ECC_COLUMN_DUE, ecc_encode_time_to_sql (cal_cache, dt)); e_cal_component_datetime_free (dt); itt = e_cal_component_get_completed (comp); add_value (ECC_COLUMN_COMPLETED, itt ? ecc_encode_itt_to_sql (itt) : NULL); g_clear_object (&itt); text = e_cal_component_get_summary (comp); add_value (ECC_COLUMN_SUMMARY, text && e_cal_component_text_get_value (text) ? e_util_utf8_decompose (e_cal_component_text_get_value (text)) : NULL); e_cal_component_text_free (text); str = e_cal_component_get_location (comp); add_value (ECC_COLUMN_LOCATION, str ? e_util_utf8_decompose (str) : NULL); g_free (str); classification = e_cal_component_get_classification (comp); add_value (ECC_COLUMN_CLASSIFICATION, g_strdup (ecc_get_classification_as_string (classification))); status = e_cal_component_get_status (comp); add_value (ECC_COLUMN_STATUS, g_strdup (ecc_get_status_as_string (status))); nint = e_cal_component_get_priority (comp); add_value (ECC_COLUMN_PRIORITY, nint != -1 ? g_strdup_printf ("%d", nint) : NULL); nint = e_cal_component_get_percent_complete (comp); add_value (ECC_COLUMN_PERCENT_COMPLETE, nint != -1 ? g_strdup_printf ("%d", nint) : NULL); has = e_cal_component_has_alarms (comp); add_value (ECC_COLUMN_HAS_ALARM, g_strdup (has ? "1" : "0")); has = e_cal_component_has_attachments (comp); add_value (ECC_COLUMN_HAS_ATTACHMENT, g_strdup (has ? "1" : "0")); 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 gchar * ecc_range_as_where_clause (const gchar *start_str, const gchar *end_str) { GString *stmt; if (!start_str && !end_str) return NULL; stmt = g_string_sized_new (64); if (start_str) { e_cache_sqlite_stmt_append_printf (stmt, "(" ECC_COLUMN_OCCUR_END " IS NULL OR " ECC_COLUMN_OCCUR_END ">=%Q)", start_str); } if (end_str) { if (start_str) { g_string_prepend_c (stmt, '('); g_string_append (stmt, " AND "); } e_cache_sqlite_stmt_append_printf (stmt, "(" ECC_COLUMN_OCCUR_START " IS NULL OR " ECC_COLUMN_OCCUR_START "<=%Q)", end_str); if (start_str) g_string_append_c (stmt, ')'); } return g_string_free (stmt, FALSE); } typedef struct _SExpToSqlContext { ECalCache *cal_cache; guint not_level; gboolean requires_check_sexp; } SExpToSqlContext; static ESExpResult * ecc_sexp_func_and_or (ESExp *esexp, gint argc, ESExpTerm **argv, gpointer user_data, const gchar *oper) { SExpToSqlContext *ctx = user_data; ESExpResult *result, *r1; GString *stmt; gint ii; g_return_val_if_fail (ctx != NULL, NULL); stmt = g_string_new ("("); for (ii = 0; ii < argc; ii++) { r1 = e_sexp_term_eval (esexp, argv[ii]); if (r1 && r1->type == ESEXP_RES_STRING && r1->value.string) { if (stmt->len > 1) g_string_append_printf (stmt, " %s ", oper); g_string_append_printf (stmt, "(%s)", r1->value.string); } else { ctx->requires_check_sexp = TRUE; } e_sexp_result_free (esexp, r1); } if (stmt->len == 1 && !ctx->not_level) { if (g_str_equal (oper, "AND")) g_string_append_c (stmt, '1'); else g_string_append_c (stmt, '0'); } g_string_append_c (stmt, ')'); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_string_free (stmt, stmt->len <= 2); return result; } static ESExpResult * ecc_sexp_func_and (ESExp *esexp, gint argc, ESExpTerm **argv, gpointer user_data) { return ecc_sexp_func_and_or (esexp, argc, argv, user_data, "AND"); } static ESExpResult * ecc_sexp_func_or (ESExp *esexp, gint argc, ESExpTerm **argv, gpointer user_data) { return ecc_sexp_func_and_or (esexp, argc, argv, user_data, "OR"); } static ESExpResult * ecc_sexp_func_not (ESExp *esexp, gint argc, ESExpTerm **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result, *r1; g_return_val_if_fail (ctx != NULL, NULL); if (argc != 1) return NULL; result = e_sexp_result_new (esexp, ESEXP_RES_STRING); ctx->not_level++; r1 = e_sexp_term_eval (esexp, argv[0]); ctx->not_level--; if (r1 && r1->type == ESEXP_RES_STRING && r1->value.string) { result->value.string = g_strdup_printf ("negate(%s)", r1->value.string); } else { ctx->requires_check_sexp = TRUE; } e_sexp_result_free (esexp, r1); return result; } static ESExpResult * ecc_sexp_func_uid (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; const gchar *uid; g_return_val_if_fail (ctx != NULL, NULL); if (argc != 1 || argv[0]->type != ESEXP_RES_STRING) { return NULL; } uid = argv[0]->value.string; result = e_sexp_result_new (esexp, ESEXP_RES_STRING); if (!uid) { result->value.string = g_strdup (E_CACHE_COLUMN_UID " IS NULL"); } else { gchar *stmt; stmt = e_cache_sqlite_stmt_printf (E_CACHE_COLUMN_UID "=%Q OR " E_CACHE_COLUMN_UID " LIKE '%q\n%%'", uid, uid); result->value.string = g_strdup (stmt); e_cache_sqlite_stmt_free (stmt); } return result; } static ESExpResult * ecc_sexp_func_occur_in_time_range (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); if ((argc != 2 && argc != 3) || argv[0]->type != ESEXP_RES_TIME || argv[1]->type != ESEXP_RES_TIME || (argc == 3 && argv[2]->type != ESEXP_RES_STRING)) { return NULL; } result = e_sexp_result_new (esexp, ESEXP_RES_STRING); if (!ctx->not_level) { ICalTime *itt_start, *itt_end; gchar *start_str, *end_str; /* The default zone argument, if any, is ignored here */ itt_start = i_cal_time_new_from_timet_with_zone (argv[0]->value.time, 0, NULL); itt_end = i_cal_time_new_from_timet_with_zone (argv[1]->value.time, 0, NULL); start_str = ecc_encode_itt_to_sql (itt_start); end_str = ecc_encode_itt_to_sql (itt_end); result->value.string = ecc_range_as_where_clause (start_str, end_str); if (!result->value.string) result->value.string = g_strdup ("1=1"); g_clear_object (&itt_start); g_clear_object (&itt_end); g_free (start_str); g_free (end_str); } else { result->value.string = NULL; } ctx->requires_check_sexp = TRUE; return result; } static ESExpResult * ecc_sexp_func_due_in_time_range (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; gchar *start_str, *end_str; g_return_val_if_fail (ctx != NULL, NULL); if (argc != 2 || argv[0]->type != ESEXP_RES_TIME || argv[1]->type != ESEXP_RES_TIME) { return NULL; } start_str = ecc_encode_timet_to_sql (ctx->cal_cache, argv[0]->value.time); end_str = ecc_encode_timet_to_sql (ctx->cal_cache, argv[1]->value.time); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_strdup_printf ("(%s NOT NULL AND %s>='%s' AND %s<='%s')", ECC_COLUMN_DUE, ECC_COLUMN_DUE, start_str, ECC_COLUMN_DUE, end_str); g_free (start_str); g_free (end_str); return result; } static ESExpResult * ecc_sexp_func_contains (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; const gchar *field, *column = NULL; gchar *str; g_return_val_if_fail (ctx != NULL, NULL); if (argc != 2 || argv[0]->type != ESEXP_RES_STRING || argv[1]->type != ESEXP_RES_STRING) { return NULL; } field = argv[0]->value.string; str = e_util_utf8_decompose (argv[1]->value.string); if (g_str_equal (field, "comment")) column = ECC_COLUMN_COMMENT; else if (g_str_equal (field, "description")) column = ECC_COLUMN_DESCRIPTION; else if (g_str_equal (field, "summary")) column = ECC_COLUMN_SUMMARY; else if (g_str_equal (field, "location")) column = ECC_COLUMN_LOCATION; else if (g_str_equal (field, "attendee")) column = ECC_COLUMN_ATTENDEES; else if (g_str_equal (field, "organizer")) column = ECC_COLUMN_ORGANIZER; else if (g_str_equal (field, "classification")) column = ECC_COLUMN_CLASSIFICATION; else if (g_str_equal (field, "status")) column = ECC_COLUMN_STATUS; else if (g_str_equal (field, "priority")) column = ECC_COLUMN_PRIORITY; result = e_sexp_result_new (esexp, ESEXP_RES_STRING); /* everything matches an empty string */ if (!str || !*str) { result->value.string = g_strdup ("1=1"); } else if (column) { gchar *stmt; if (g_str_equal (column, ECC_COLUMN_PRIORITY)) { if (g_ascii_strcasecmp (str, "UNDEFINED") == 0) stmt = e_cache_sqlite_stmt_printf ("%s IS NULL", column); else if (g_ascii_strcasecmp (str, "HIGH") == 0) stmt = e_cache_sqlite_stmt_printf ("%s<=4", column); else if (g_ascii_strcasecmp (str, "NORMAL") == 0) stmt = e_cache_sqlite_stmt_printf ("%s=5", column); else if (g_ascii_strcasecmp (str, "LOW") == 0) stmt = e_cache_sqlite_stmt_printf ("%s>5", column); else stmt = e_cache_sqlite_stmt_printf ("%s IS NOT NULL", column); } else if (g_str_equal (column, ECC_COLUMN_CLASSIFICATION) || g_str_equal (column, ECC_COLUMN_STATUS)) { stmt = e_cache_sqlite_stmt_printf ("%s='%q'", column, str); } else { stmt = e_cache_sqlite_stmt_printf ("%s LIKE '%%%q%%'", column, str); } result->value.string = g_strdup (stmt); e_cache_sqlite_stmt_free (stmt); } else if (g_str_equal (field, "any")) { GString *stmt; stmt = g_string_new (""); e_cache_sqlite_stmt_append_printf (stmt, "(%s LIKE '%%%q%%'", ECC_COLUMN_COMMENT, str); e_cache_sqlite_stmt_append_printf (stmt, " OR %s LIKE '%%%q%%'", ECC_COLUMN_DESCRIPTION, str); e_cache_sqlite_stmt_append_printf (stmt, " OR %s LIKE '%%%q%%'", ECC_COLUMN_SUMMARY, str); e_cache_sqlite_stmt_append_printf (stmt, " OR %s LIKE '%%%q%%')", ECC_COLUMN_LOCATION, str); result->value.string = g_string_free (stmt, FALSE); } else { ctx->requires_check_sexp = TRUE; } g_free (str); return result; } static ESExpResult * ecc_sexp_func_has_start (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_strdup_printf ("(%s NOT NULL AND %s=1)", ECC_COLUMN_HAS_START, ECC_COLUMN_HAS_START); return result; } static ESExpResult * ecc_sexp_func_has_alarms (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_strdup_printf ("(%s NOT NULL AND %s=1)", ECC_COLUMN_HAS_ALARM, ECC_COLUMN_HAS_ALARM); return result; } static ESExpResult * ecc_sexp_func_has_alarms_in_range (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); ctx->requires_check_sexp = TRUE; if (!ctx->not_level) return ecc_sexp_func_has_alarms (esexp, argc, argv, user_data); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = NULL; return result; } static ESExpResult * ecc_sexp_func_has_recurrences (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_strdup_printf ("(%s NOT NULL AND %s=1)", ECC_COLUMN_HAS_RECURRENCES, ECC_COLUMN_HAS_RECURRENCES); return result; } /* (has-categories? STR+) * (has-categories? #f) */ static ESExpResult * ecc_sexp_func_has_categories (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; gboolean unfiled; g_return_val_if_fail (ctx != NULL, NULL); if (argc < 1) return NULL; unfiled = argc == 1 && argv[0]->type == ESEXP_RES_BOOL; result = e_sexp_result_new (esexp, ESEXP_RES_STRING); if (unfiled) { result->value.string = g_strdup_printf ("%s IS NULL", ECC_COLUMN_CATEGORIES); } else { GString *tmp; gint ii; tmp = g_string_new ("(" ECC_COLUMN_CATEGORIES " NOT NULL"); for (ii = 0; ii < argc; ii++) { if (argv[ii]->type != ESEXP_RES_STRING) { g_warn_if_reached (); continue; } e_cache_sqlite_stmt_append_printf (tmp, " AND " ECC_COLUMN_CATEGORIES " LIKE '%%\n%q\n%%'", argv[ii]->value.string); } g_string_append_c (tmp, ')'); result->value.string = g_string_free (tmp, FALSE); } return result; } static ESExpResult * ecc_sexp_func_is_completed (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_strdup_printf ("%s NOT NULL OR (%s NOT NULL AND %s='%s')", ECC_COLUMN_COMPLETED, ECC_COLUMN_STATUS, ECC_COLUMN_STATUS, ecc_get_status_as_string (I_CAL_STATUS_COMPLETED)); return result; } static ESExpResult * ecc_sexp_func_completed_before (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; gchar *tmp; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); if (argc != 1 || argv[0]->type != ESEXP_RES_TIME) { return NULL; } tmp = ecc_encode_timet_to_sql (ctx->cal_cache, argv[0]->value.time); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_strdup_printf ("(%s NOT NULL AND %s<'%s')", ECC_COLUMN_COMPLETED, ECC_COLUMN_COMPLETED, tmp); g_free (tmp); return result; } static ESExpResult * ecc_sexp_func_has_attachment (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_strdup_printf ("(%s NOT NULL AND %s=1)", ECC_COLUMN_HAS_ATTACHMENT, ECC_COLUMN_HAS_ATTACHMENT); return result; } static ESExpResult * ecc_sexp_func_percent_complete (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = g_strdup (ECC_COLUMN_PERCENT_COMPLETE); return result; } /* check_sexp(sexp_id, icalstring); that's a fallback for anything not being part of the summary */ static ESExpResult * ecc_sexp_func_check_sexp (ESExp *esexp, gint argc, ESExpResult **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = NULL; ctx->requires_check_sexp = TRUE; return result; } static ESExpResult * ecc_sexp_func_icheck_sexp (ESExp *esexp, gint argc, ESExpTerm **argv, gpointer user_data) { SExpToSqlContext *ctx = user_data; ESExpResult *result; g_return_val_if_fail (ctx != NULL, NULL); result = e_sexp_result_new (esexp, ESEXP_RES_STRING); result->value.string = NULL; ctx->requires_check_sexp = TRUE; return result; } static struct { const gchar *name; gpointer func; gint type; /* 1 for term-function, 0 for result-function */ } symbols[] = { { "and", ecc_sexp_func_and, 1 }, { "or", ecc_sexp_func_or, 1 }, { "not", ecc_sexp_func_not, 1 }, { "<", ecc_sexp_func_icheck_sexp, 1 }, { ">", ecc_sexp_func_icheck_sexp, 1 }, { "=", ecc_sexp_func_icheck_sexp, 1 }, { "+", ecc_sexp_func_check_sexp, 0 }, { "-", ecc_sexp_func_check_sexp, 0 }, { "cast-int", ecc_sexp_func_check_sexp, 0 }, { "cast-string", ecc_sexp_func_check_sexp, 0 }, { "if", ecc_sexp_func_icheck_sexp, 1 }, { "begin", ecc_sexp_func_icheck_sexp, 1 }, /* Time-related functions */ { "time-now", e_cal_backend_sexp_func_time_now, 0 }, { "make-time", e_cal_backend_sexp_func_make_time, 0 }, { "time-add-day", e_cal_backend_sexp_func_time_add_day, 0 }, { "time-day-begin", e_cal_backend_sexp_func_time_day_begin, 0 }, { "time-day-end", e_cal_backend_sexp_func_time_day_end, 0 }, /* Component-related functions */ { "uid?", ecc_sexp_func_uid, 0 }, { "occur-in-time-range?", ecc_sexp_func_occur_in_time_range, 0 }, { "due-in-time-range?", ecc_sexp_func_due_in_time_range, 0 }, { "contains?", ecc_sexp_func_contains, 0 }, { "has-start?", ecc_sexp_func_has_start, 0 }, { "has-alarms?", ecc_sexp_func_has_alarms, 0 }, { "has-alarms-in-range?", ecc_sexp_func_has_alarms_in_range, 0 }, { "has-recurrences?", ecc_sexp_func_has_recurrences, 0 }, { "has-categories?", ecc_sexp_func_has_categories, 0 }, { "is-completed?", ecc_sexp_func_is_completed, 0 }, { "completed-before?", ecc_sexp_func_completed_before, 0 }, { "has-attachments?", ecc_sexp_func_has_attachment, 0 }, { "percent-complete?", ecc_sexp_func_percent_complete, 0 }, { "occurrences-count?", ecc_sexp_func_check_sexp, 0 } }; static gboolean ecc_convert_sexp_to_sql (ECalCache *cal_cache, const gchar *sexp_str, gint sexp_id, gchar **out_where_clause, GCancellable *cancellable, GError **error) { SExpToSqlContext ctx; ESExp *sexp_parser; gint esexp_error, ii; gboolean success = FALSE; g_return_val_if_fail (out_where_clause != NULL, FALSE); *out_where_clause = NULL; /* Include everything */ if (!sexp_str || !*sexp_str || g_strcmp0 (sexp_str, "#t") == 0) return TRUE; ctx.cal_cache = cal_cache; ctx.not_level = 0; ctx.requires_check_sexp = FALSE; sexp_parser = e_sexp_new (); for (ii = 0; ii < G_N_ELEMENTS (symbols); ii++) { if (symbols[ii].type == 1) { e_sexp_add_ifunction (sexp_parser, 0, symbols[ii].name, symbols[ii].func, &ctx); } else { e_sexp_add_function (sexp_parser, 0, symbols[ii].name, symbols[ii].func, &ctx); } } e_sexp_input_text (sexp_parser, sexp_str, strlen (sexp_str)); esexp_error = e_sexp_parse (sexp_parser); if (esexp_error != -1) { ESExpResult *result; result = e_sexp_eval (sexp_parser); if (result) { if (result->type == ESEXP_RES_STRING) { if (ctx.requires_check_sexp) { if (result->value.string) { *out_where_clause = g_strdup_printf ("((%s) AND check_sexp(%d,%s))", result->value.string, sexp_id, E_CACHE_COLUMN_OBJECT); } else { *out_where_clause = g_strdup_printf ("check_sexp(%d,%s)", sexp_id, E_CACHE_COLUMN_OBJECT); } } else { /* Just steal the string from the ESExpResult */ *out_where_clause = result->value.string; result->value.string = NULL; } success = TRUE; } } e_sexp_result_free (sexp_parser, result); } g_object_unref (sexp_parser); if (!success) { g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY, _("Invalid query: %s"), sexp_str); } return success; } typedef struct { gint extra_idx; gint custom_flags_idx; ECalCacheSearchFunc func; gpointer func_user_data; } SearchContext; static gboolean ecc_search_foreach_cb (ECache *cache, const gchar *uid, const gchar *revision, const gchar *object, EOfflineState offline_state, gint ncols, const gchar *column_names[], const gchar *column_values[], gpointer user_data) { SearchContext *ctx = user_data; gchar *comp_uid = NULL, *comp_rid = NULL; guint32 custom_flags = 0; gboolean can_continue; g_return_val_if_fail (ctx != NULL, FALSE); g_return_val_if_fail (ctx->func != NULL, FALSE); if (ctx->extra_idx == -1 || ctx->custom_flags_idx == -1) { gint ii; for (ii = 0; ii < ncols && (ctx->extra_idx == -1 || ctx->custom_flags_idx == -1); ii++) { if (column_names[ii]) { if (g_ascii_strcasecmp (column_names[ii], ECC_COLUMN_EXTRA) == 0) ctx->extra_idx = ii; else if (g_ascii_strcasecmp (column_names[ii], ECC_COLUMN_CUSTOM_FLAGS) == 0) ctx->custom_flags_idx = ii; } } } g_return_val_if_fail (ctx->extra_idx != -1, FALSE); g_return_val_if_fail (ctx->custom_flags_idx != -1, FALSE); g_warn_if_fail (ecc_decode_id_sql (uid, &comp_uid, &comp_rid)); if (ctx->custom_flags_idx >= 0 && column_values[ctx->custom_flags_idx]) custom_flags = g_ascii_strtoull (column_values[ctx->custom_flags_idx], NULL, 10); /* This type-cast for performance reason */ can_continue = ctx->func ((ECalCache *) cache, comp_uid, comp_rid, revision, object, column_values[ctx->extra_idx], custom_flags, offline_state, ctx->func_user_data); g_free (comp_uid); g_free (comp_rid); return can_continue; } static gboolean ecc_search_internal (ECalCache *cal_cache, const gchar *sexp_str, gint sexp_id, ECalCacheSearchFunc func, gpointer user_data, GCancellable *cancellable, GError **error) { gchar *where_clause = NULL; SearchContext ctx; gboolean success; if (!ecc_convert_sexp_to_sql (cal_cache, sexp_str, sexp_id, &where_clause, cancellable, error)) { return FALSE; } ctx.extra_idx = -1; ctx.custom_flags_idx = -1; ctx.func = func; ctx.func_user_data = user_data; success = e_cache_foreach (E_CACHE (cal_cache), E_CACHE_EXCLUDE_DELETED, where_clause, ecc_search_foreach_cb, &ctx, cancellable, error); g_free (where_clause); return success; } 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 KEY, " "zone TEXT, " "refs INTEGER)", 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 ecc_init_sqlite_functions (ECalCache *cal_cache, GCancellable *cancellable, GError **error) { gint ret; gpointer sqlitedb; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); sqlitedb = e_cache_get_sqlitedb (E_CACHE (cal_cache)); g_return_val_if_fail (sqlitedb != NULL, FALSE); /* check_sexp(sexp_id, icalstring) */ ret = sqlite3_create_function (sqlitedb, "check_sexp", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, cal_cache, ecc_check_sexp_func, NULL, NULL); if (ret == SQLITE_OK) { /* negate(x) */ ret = sqlite3_create_function (sqlitedb, "negate", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL, ecc_negate_func, NULL, NULL); } if (ret != SQLITE_OK) { const gchar *errmsg = sqlite3_errmsg (sqlitedb); g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_ENGINE, _("Failed to create SQLite function, error code “%d”: %s"), ret, errmsg ? errmsg : _("Unknown error")); return FALSE; } return TRUE; } typedef struct _ComponentInfo { GSList *online_comps; /* ECalComponent * */ GSList *online_extras; /* gchar * */ GSList *online_custom_flags; /* guint32 */ GSList *offline_comps; /* ECalComponent * */ GSList *offline_extras; /* gchar * */ GSList *offline_custom_flags; /* guint32 */ } ComponentInfo; static void component_info_clear (ComponentInfo *ci) { if (ci) { g_slist_free_full (ci->online_comps, g_object_unref); g_slist_free_full (ci->online_extras, g_free); g_slist_free (ci->online_custom_flags); g_slist_free_full (ci->offline_comps, g_object_unref); g_slist_free_full (ci->offline_extras, g_free); g_slist_free (ci->offline_custom_flags); } } static gboolean cal_cache_gather_v1_affected_cb (ECalCache *cal_cache, const gchar *uid, const gchar *rid, const gchar *revision, const gchar *object, const gchar *extra, guint32 custom_flags, EOfflineState offline_state, gpointer user_data) { ComponentInfo *ci = user_data; ECalComponent *comp; ECalComponentDateTime *dt; g_return_val_if_fail (object != NULL, FALSE); g_return_val_if_fail (ci != NULL, FALSE); if (offline_state == E_OFFLINE_STATE_LOCALLY_DELETED) return TRUE; comp = e_cal_component_new_from_string (object); if (!comp) return TRUE; dt = e_cal_component_get_due (comp); if (dt && e_cal_component_datetime_get_value (dt) && !i_cal_time_is_utc (e_cal_component_datetime_get_value (dt)) && !e_cal_component_datetime_get_tzid (dt)) { GSList **pcomps, **pextras, **pcustom_flags; if (offline_state == E_OFFLINE_STATE_SYNCED) { pcomps = &ci->online_comps; pextras = &ci->online_extras; pcustom_flags = &ci->online_custom_flags; } else { pcomps = &ci->offline_comps; pextras = &ci->offline_extras; pcustom_flags = &ci->offline_custom_flags; } *pcomps = g_slist_prepend (*pcomps, g_object_ref (comp)); *pextras = g_slist_prepend (*pextras, g_strdup (extra)); *pcustom_flags = g_slist_prepend (*pcustom_flags, GUINT_TO_POINTER (custom_flags)); } e_cal_component_datetime_free (dt); g_object_unref (comp); return TRUE; } static ICalTimezone * ecc_timezone_from_string (const gchar *icalstring) { ICalComponent *component; g_return_val_if_fail (icalstring != NULL, NULL); component = i_cal_component_new_from_string (icalstring); if (component) { ICalTimezone *zone; zone = i_cal_timezone_new (); if (!i_cal_timezone_set_component (zone, component)) { g_object_unref (component); g_object_unref (zone); } else { g_object_unref (component); return zone; } } return NULL; } static gboolean ecc_tzid_is_libical_builtin (const gchar *tzid) { const gchar *matched_tzid; if (!tzid || !*tzid || i_cal_timezone_get_builtin_timezone (tzid)) return TRUE; matched_tzid = e_cal_match_tzid (tzid); return matched_tzid && i_cal_timezone_get_builtin_timezone_from_tzid (matched_tzid); } typedef struct _TimezoneMigrationData { ICalTimezone *zone; guint refs; gboolean is_deref; /* TRUE when should dereference, instead of reference, the timezone with refs references */ } TimezoneMigrationData; static void timezone_migration_data_free (gpointer ptr) { TimezoneMigrationData *tmd = ptr; if (tmd) { g_clear_object (&tmd->zone); g_free (tmd); } } typedef struct _CountTimezonesData { ECalCache *cal_cache; GHashTable *timezones; gboolean is_inc; GCancellable *cancellable; } CountTimezonesData; static void ecc_count_timezones_in_icalcomp_cb (ICalParameter *param, gpointer user_data) { CountTimezonesData *ctd = user_data; TimezoneMigrationData *tmd; const gchar *tzid; g_return_if_fail (ctd != NULL); tzid = i_cal_parameter_get_tzid (param); if (!tzid) return; tmd = g_hash_table_lookup (ctd->timezones, tzid); if (tmd) { if (ctd->is_inc) { if (tmd->is_deref) { if (!tmd->refs) { tmd->refs++; tmd->is_deref = FALSE; } else { tmd->refs--; } } else { tmd->refs++; } } else { if (tmd->is_deref) { tmd->refs++; } else { if (!tmd->refs) { tmd->refs++; tmd->is_deref = TRUE; } else { tmd->refs--; } } } } else if (!ecc_tzid_is_libical_builtin (tzid)) { ICalTimezone *zone = NULL; g_signal_emit (ctd->cal_cache, signals[GET_TIMEZONE], 0, tzid, &zone); if (!zone && !e_cal_cache_get_timezone (ctd->cal_cache, tzid, &zone, ctd->cancellable, NULL)) zone = NULL; /* Make a copy of it, it's not owned by the caller, but by the originator */ if (zone) zone = e_cal_util_copy_timezone (zone); if (zone) { tmd = g_new0 (TimezoneMigrationData, 1); tmd->is_deref = !ctd->is_inc; tmd->refs = 1; tmd->zone = zone; g_hash_table_insert (ctd->timezones, g_strdup (tzid), tmd); } } } static void ecc_count_timezones_for_component (ECalCache *cal_cache, GHashTable *timezones, ICalComponent *icomp, gboolean is_inc, GCancellable *cancellable) { g_return_if_fail (E_IS_CAL_CACHE (cal_cache)); g_return_if_fail (timezones != NULL); if (icomp) { CountTimezonesData ctd; ctd.cal_cache = cal_cache; ctd.timezones = timezones; ctd.is_inc = is_inc; ctd.cancellable = cancellable; i_cal_component_foreach_tzid (icomp, ecc_count_timezones_in_icalcomp_cb, &ctd); } } static void ecc_count_timezones_for_old_component (ECalCache *cal_cache, GHashTable *timezones, const gchar *uid_in_table, GCancellable *cancellable) { gchar *objstr; g_return_if_fail (E_IS_CAL_CACHE (cal_cache)); g_return_if_fail (timezones != NULL); g_return_if_fail (uid_in_table != NULL); objstr = e_cache_get_object_include_deleted (E_CACHE (cal_cache), uid_in_table, NULL, NULL, cancellable, NULL); if (objstr) { ICalComponent *icomp; icomp = i_cal_component_new_from_string (objstr); if (icomp) { ecc_count_timezones_for_component (cal_cache, timezones, icomp, FALSE, cancellable); g_object_unref (icomp); } g_free (objstr); } } static gboolean ecc_update_timezones_table (ECalCache *cal_cache, GHashTable *timezones, GCancellable *cancellable, GError **error) { GHashTableIter iter; gpointer key, value; gboolean success = TRUE; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); g_return_val_if_fail (timezones != NULL, FALSE); g_hash_table_iter_init (&iter, timezones); while (success && g_hash_table_iter_next (&iter, &key, &value)) { const gchar *tzid = key; TimezoneMigrationData *tmd = value; if (!tzid || !tmd || !tmd->refs) continue; if (tmd->is_deref) { success = e_cal_cache_remove_timezone (cal_cache, tzid, tmd->refs, cancellable, error); } else { success = e_cal_cache_put_timezone (cal_cache, tmd->zone, tmd->refs, cancellable, error); } } return success; } static gboolean e_cal_cache_fill_tmd_cb (ECache *cache, gint ncols, const gchar *column_names[], const gchar *column_values[], gpointer user_data) { GHashTable *timezones = user_data; /* gchar *tzid ~> TimezoneMigrationData * */ g_return_val_if_fail (timezones != NULL, FALSE); g_return_val_if_fail (ncols == 2, FALSE); /* Verify the timezone is not provided twice */ if (!g_hash_table_lookup (timezones, column_values[0])) { ICalTimezone *zone; zone = ecc_timezone_from_string (column_values[1]); if (zone) { TimezoneMigrationData *tmd; tmd = g_new0 (TimezoneMigrationData, 1); tmd->zone = zone; tmd->refs = 0; g_hash_table_insert (timezones, g_strdup (column_values[0]), tmd); } } return TRUE; } static void ecc_count_tmd_refs_cb (ICalParameter *param, gpointer user_data) { GHashTable *timezones = user_data; const gchar *tzid; TimezoneMigrationData *tmd; tzid = i_cal_parameter_get_tzid (param); if (!tzid || !timezones) return; tmd = g_hash_table_lookup (timezones, tzid); if (tmd) tmd->refs++; } static gboolean cal_cache_count_tmd_refs (ECalCache *cal_cache, const gchar *uid, const gchar *rid, const gchar *revision, const gchar *object, const gchar *extra, guint32 custom_flags, EOfflineState offline_state, gpointer user_data) { GHashTable *timezones = user_data; ICalComponent *icomp; g_return_val_if_fail (timezones != NULL, FALSE); g_return_val_if_fail (object != NULL, FALSE); icomp = i_cal_component_new_from_string (object); if (icomp) { i_cal_component_foreach_tzid (icomp, ecc_count_tmd_refs_cb, timezones); g_object_unref (icomp); } return TRUE; } static gboolean e_cal_cache_table_refs_column_exists_cb (ECache *cache, gint ncols, const gchar *column_names[], const gchar *column_values[], gpointer user_data) { gboolean *prefs_column_exists = user_data; gint ii; g_return_val_if_fail (prefs_column_exists != NULL, FALSE); g_return_val_if_fail (column_names != NULL, FALSE); g_return_val_if_fail (column_values != NULL, FALSE); for (ii = 0; ii < ncols && !*prefs_column_exists; ii++) { if (column_names[ii] && camel_strcase_equal (column_names[ii], "name")) { if (column_values[ii]) *prefs_column_exists = camel_strcase_equal (column_values[ii], "refs"); break; } } return TRUE; } static gboolean e_cal_cache_migrate (ECache *cache, gint from_version, GCancellable *cancellable, GError **error) { ECalCache *cal_cache = E_CAL_CACHE (cache); GHashTable *timezones = NULL; /* gchar *tzid ~> TimezoneMigrationData * */ gboolean success = TRUE; /* Add any version-related changes here (E_CAL_CACHE_VERSION) */ if (from_version > 0 && from_version < 3) { gboolean refs_column_exists = FALSE; gchar *stmt; g_rec_mutex_lock (&cal_cache->priv->timezones_lock); /* In case an older version modified the local cache version, then the ALTER TABLE command can fail due to duplicate 'refs' column */ success = e_cache_sqlite_select (E_CACHE (cal_cache), "PRAGMA table_info (" ECC_TABLE_TIMEZONES ")", e_cal_cache_table_refs_column_exists_cb, &refs_column_exists, cancellable, NULL); if (!success || !refs_column_exists) { stmt = e_cache_sqlite_stmt_printf ("ALTER TABLE %Q ADD COLUMN refs INTEGER", ECC_TABLE_TIMEZONES); success = e_cache_sqlite_exec (E_CACHE (cal_cache), stmt, cancellable, error); e_cache_sqlite_stmt_free (stmt); } if (success) { timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, timezone_migration_data_free); 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_fill_tmd_cb, timezones, cancellable, error); e_cache_sqlite_stmt_free (stmt); } g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); } if (success && from_version == 1) { /* Version 1 incorrectly stored DATE-only DUE values */ ComponentInfo ci; memset (&ci, 0, sizeof (ComponentInfo)); if (e_cal_cache_search_with_callback (cal_cache, NULL, cal_cache_gather_v1_affected_cb, &ci, cancellable, NULL)) { gboolean success = TRUE; if (ci.online_comps) success = e_cal_cache_put_components (cal_cache, ci.online_comps, ci.online_extras, ci.online_custom_flags, E_CACHE_IS_ONLINE, cancellable, NULL); if (success && ci.offline_comps) e_cal_cache_put_components (cal_cache, ci.offline_comps, ci.offline_extras, ci.offline_custom_flags, E_CACHE_IS_OFFLINE, cancellable, NULL); } component_info_clear (&ci); } if (success && timezones) { g_rec_mutex_lock (&cal_cache->priv->timezones_lock); success = e_cal_cache_remove_timezones (cal_cache, cancellable, error); if (success) { _e_cal_cache_remove_loaded_timezones (cal_cache); success = e_cal_cache_search_with_callback (cal_cache, NULL, cal_cache_count_tmd_refs, timezones, cancellable, error); } if (success) { GHashTableIter iter; gpointer value; g_hash_table_iter_init (&iter, timezones); while (success && g_hash_table_iter_next (&iter, NULL, &value)) { TimezoneMigrationData *tmd = value; if (tmd && tmd->refs > 0) success = e_cal_cache_put_timezone (cal_cache, tmd->zone, tmd->refs, cancellable, error); } } g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); } if (timezones) g_hash_table_destroy (timezones); 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); cal_cache->priv->initializing = TRUE; 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); success = success && ecc_init_sqlite_functions (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); cal_cache->priv->initializing = FALSE; 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 * @icomp: an #ICalComponent * * Returns the @icomp 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 @icomp. * * Since: 3.26 **/ gchar * e_cal_cache_dup_component_revision (ECalCache *cal_cache, ICalComponent *icomp) { gchar *revision = NULL; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), NULL); g_return_val_if_fail (icomp != NULL, NULL); g_signal_emit (cal_cache, signals[DUP_COMPONENT_REVISION], 0, icomp, &revision); return revision; } /** * e_cal_cache_contains: * @cal_cache: an #ECalCache * @uid: component UID * @rid: (nullable): optional component Recurrence-ID or %NULL * @deleted_flag: one of #ECacheDeletedFlag enum * * Checkes whether the @cal_cache contains an object with * the given @uid and @rid. The @rid can be an empty string * or %NULL to search for the master object, otherwise the check * is done for a detached instance, not for a recurrence instance. * * Returns: Whether the the object had been found. * * Since: 3.26 **/ gboolean e_cal_cache_contains (ECalCache *cal_cache, const gchar *uid, const gchar *rid, ECacheDeletedFlag deleted_flag) { gchar *id; gboolean found; 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); found = e_cache_contains (E_CACHE (cal_cache), id, deleted_flag); g_free (id); return found; } /** * 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 * @custom_flags: custom flags for the @component, not interpreted by the @cal_cache * @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, guint32 custom_flags, ECacheOfflineFlag offline_flag, GCancellable *cancellable, GError **error) { GSList *components = NULL; GSList *extras = NULL; GSList *custom_flags_lst = 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); custom_flags_lst = g_slist_prepend (custom_flags_lst, GUINT_TO_POINTER (custom_flags)); success = e_cal_cache_put_components (cal_cache, components, extras, custom_flags_lst, offline_flag, cancellable, error); g_slist_free (custom_flags_lst); 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): optional extra data to store in association with the @components * @custom_flags: (nullable) (element-type guint32): optional custom flags to use for 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, its length should be the same as the length * of the @components. Similarly the non-NULL @custom_flags 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, const GSList *custom_flags, ECacheOfflineFlag offline_flag, GCancellable *cancellable, GError **error) { const GSList *clink, *elink, *flink; ECache *cache; ECacheColumnValues *other_columns; gboolean success = TRUE; 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); g_return_val_if_fail (custom_flags == NULL || g_slist_length ((GSList *) components) == g_slist_length ((GSList *) custom_flags), FALSE); cache = E_CACHE (cal_cache); other_columns = e_cache_column_values_new (); e_cache_lock (cache, E_CACHE_LOCK_WRITE); e_cache_freeze_revision_change (cache); for (clink = components, elink = extras, flink = custom_flags; clink; (clink = g_slist_next (clink)), (elink = g_slist_next (elink)), (flink = g_slist_next (flink))) { ECalComponent *component = clink->data; const gchar *extra = elink ? elink->data : NULL; guint32 custom_flags_val = flink ? GPOINTER_TO_UINT (flink->data) : 0; 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)); e_cache_column_values_take_value (other_columns, ECC_COLUMN_CUSTOM_FLAGS, g_strdup_printf ("%u", custom_flags_val)); id = e_cal_component_get_id (component); if (id) { uid = ecc_encode_id_sql (e_cal_component_id_get_uid (id), e_cal_component_id_get_rid (id)); } else { g_warn_if_reached (); uid = g_strdup (""); } e_cal_component_id_free (id); rev = e_cal_cache_dup_component_revision (cal_cache, e_cal_component_get_icalcomponent (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_thaw_revision_change (cache); 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 * @custom_flags: custom flags for the component, not interpreted by the @cal_cache * @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 identified by @uid and @rid from the @cal_cache. * When the @rid is %NULL, or an empty string, then removes the master * object only, without any detached instance. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_cal_cache_remove_component (ECalCache *cal_cache, const gchar *uid, const gchar *rid, guint32 custom_flags, ECacheOfflineFlag offline_flag, GCancellable *cancellable, GError **error) { ECalComponentId *id; GSList *ids = NULL; GSList *custom_flags_lst = NULL; gboolean success; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); id = e_cal_component_id_new (uid, rid); ids = g_slist_prepend (ids, id); custom_flags_lst = g_slist_prepend (custom_flags_lst, GUINT_TO_POINTER (custom_flags)); success = e_cal_cache_remove_components (cal_cache, ids, custom_flags_lst, offline_flag, cancellable, error); g_slist_free_full (ids, e_cal_component_id_free); g_slist_free (custom_flags_lst); return success; } /** * e_cal_cache_remove_components: * @cal_cache: an #ECalCache * @ids: (element-type ECalComponentId): a #GSList of components to remove * @custom_flags: (element-type guint32) (nullable): an optional #GSList of custom flags for the @ids * @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 identified 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 only, without any detached instance. * The @custom_flags is used, if not %NULL, only if the @offline_flag * is %E_CACHE_IS_OFFLINE. Otherwise it's ignored. The length of * the @custom_flags should match the length of @ids, when not %NULL. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_cal_cache_remove_components (ECalCache *cal_cache, const GSList *ids, const GSList *custom_flags, ECacheOfflineFlag offline_flag, GCancellable *cancellable, GError **error) { ECache *cache; const GSList *link, *flink; gboolean success = TRUE; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); g_return_val_if_fail (custom_flags == NULL || g_slist_length ((GSList *) ids) == g_slist_length ((GSList *) custom_flags), FALSE); cache = E_CACHE (cal_cache); e_cache_lock (cache, E_CACHE_LOCK_WRITE); e_cache_freeze_revision_change (cache); for (link = ids, flink = custom_flags; success && link; (link = g_slist_next (link)), (flink = g_slist_next (flink))) { const ECalComponentId *id = link->data; guint32 custom_flags_val = flink ? GPOINTER_TO_UINT (flink->data) : 0; gchar *uid; g_warn_if_fail (id != NULL); if (!id) continue; if (offline_flag == E_CACHE_IS_OFFLINE && flink) { success = e_cal_cache_set_component_custom_flags (cal_cache, e_cal_component_id_get_uid (id), e_cal_component_id_get_rid (id), custom_flags_val, cancellable, error); if (!success) break; } uid = ecc_encode_id_sql (e_cal_component_id_get_uid (id), e_cal_component_id_get_rid (id)); success = e_cache_remove (cache, uid, offline_flag, cancellable, error); g_free (uid); } e_cache_thaw_revision_change (cache); 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_custom_flags: * @cal_cache: an #ECalCache * @uid: a UID of the component * @rid: (nullable): an optional Recurrence-ID * @custom_flags: the custom flags to set for the component * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Sets or replaces the custom flags associated with a component * identified by @uid and optionally @rid. * * Returns: Whether succeeded. * * Since: 3.34 **/ gboolean e_cal_cache_set_component_custom_flags (ECalCache *cal_cache, const gchar *uid, const gchar *rid, guint32 custom_flags, 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 ( "UPDATE " E_CACHE_TABLE_OBJECTS " SET " ECC_COLUMN_CUSTOM_FLAGS "=%u" " WHERE " E_CACHE_COLUMN_UID "=%Q", custom_flags, 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_custom_flags: * @cal_cache: an #ECalCache * @uid: a UID of the component * @rid: (nullable): an optional Recurrence-ID * @out_custom_flags: (out): return location to store the custom flags * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Gets the custom flags previously set for @uid and @rid, either with * e_cal_cache_set_component_custom_flags(), when adding components or * when removing components in offline. * * Returns: Whether succeeded. * * Since: 3.34 **/ gboolean e_cal_cache_get_component_custom_flags (ECalCache *cal_cache, const gchar *uid, const gchar *rid, guint32 *out_custom_flags, GCancellable *cancellable, GError **error) { gchar *id, *stmt; guint64 value = 0; 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_CUSTOM_FLAGS " 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_uint64_cb, &value, cancellable, error); e_cache_sqlite_stmt_free (stmt); g_free (id); if (out_custom_flags) *out_custom_flags = (guint32) value; return success; } /** * 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; } /** * e_cal_cache_get_ids_with_extra: * @cal_cache: an #ECalCache * @extra: an extra column value to search for * @out_ids: (out) (transfer full) (element-type ECalComponentId): return location to store the ids to * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Gets all the ID-s the @extra data is set for. * * The @out_ids should be freed with * g_slist_free_full (ids, (GDestroyNotify) e_cal_component_id_free); * when no longer needed. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_cal_cache_get_ids_with_extra (ECalCache *cal_cache, const gchar *extra, GSList **out_ids, 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 (extra != NULL, FALSE); g_return_val_if_fail (out_ids != NULL, FALSE); *out_ids = NULL; stmt = e_cache_sqlite_stmt_printf ( "SELECT " E_CACHE_COLUMN_UID " FROM " E_CACHE_TABLE_OBJECTS " WHERE " ECC_COLUMN_EXTRA "=%Q", extra); success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt, e_cal_cache_get_ids, out_ids, cancellable, error); e_cache_sqlite_stmt_free (stmt); if (success && !*out_ids) { g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object with extra “%s” not found"), extra); success = FALSE; } else { *out_ids = g_slist_reverse (*out_ids); } 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_string (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_string: * @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_string (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; } static gboolean ecc_search_icalstrings_cb (ECalCache *cal_cache, const gchar *uid, const gchar *rid, const gchar *revision, const gchar *object, const gchar *extra, guint32 custom_flags, 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; } /** * 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 *sexp; ICalTime *itt_start, *itt_end; 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; itt_start = i_cal_time_new_from_timet_with_zone (range_start, FALSE, NULL); itt_end = i_cal_time_new_from_timet_with_zone (range_end, FALSE, NULL); sexp = g_strdup_printf ("(occur-in-time-range? (make-time \"%04d%02d%02dT%02d%02d%02dZ\") (make-time \"%04d%02d%02dT%02d%02d%02dZ\"))", i_cal_time_get_year (itt_start), i_cal_time_get_month (itt_start), i_cal_time_get_day (itt_start), i_cal_time_get_hour (itt_start), i_cal_time_get_minute (itt_start), i_cal_time_get_second (itt_start), i_cal_time_get_year (itt_end), i_cal_time_get_month (itt_end), i_cal_time_get_day (itt_end), i_cal_time_get_hour (itt_end), i_cal_time_get_minute (itt_end), i_cal_time_get_second (itt_end)); g_clear_object (&itt_start); g_clear_object (&itt_end); success = e_cal_cache_search_with_callback (cal_cache, sexp, ecc_search_icalstrings_cb, out_icalstrings, cancellable, error); g_free (sexp); if (success) { *out_icalstrings = g_slist_reverse (*out_icalstrings); } else { g_slist_free_full (*out_icalstrings, g_free); *out_icalstrings = NULL; } return success; } static gboolean ecc_search_data_cb (ECalCache *cal_cache, const gchar *uid, const gchar *rid, const gchar *revision, const gchar *object, const gchar *extra, guint32 custom_flags, EOfflineState offline_state, gpointer user_data) { GSList **out_data = user_data; g_return_val_if_fail (out_data != NULL, FALSE); g_return_val_if_fail (object != NULL, FALSE); *out_data = g_slist_prepend (*out_data, e_cal_cache_search_data_new (uid, rid, object, extra)); return TRUE; } static gboolean ecc_search_components_cb (ECalCache *cal_cache, const gchar *uid, const gchar *rid, const gchar *revision, const gchar *object, const gchar *extra, guint32 custom_flags, 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_ids_cb (ECalCache *cal_cache, const gchar *uid, const gchar *rid, const gchar *revision, const gchar *object, const gchar *extra, guint32 custom_flags, 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_data: (out) (transfer full) (element-type ECalCacheSearchData): stored components, as search data, 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 as a #GSList of #ECalCacheSearchData. * The @out_data should be freed with * g_slist_free_full (data, e_cal_cache_search_data_free); * when no longer needed. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_cal_cache_search (ECalCache *cal_cache, const gchar *sexp, GSList **out_data, GCancellable *cancellable, GError **error) { gboolean success; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); g_return_val_if_fail (out_data != NULL, FALSE); *out_data = NULL; success = e_cal_cache_search_with_callback (cal_cache, sexp, ecc_search_data_cb, out_data, cancellable, error); if (success) { *out_data = g_slist_reverse (*out_data); } else { g_slist_free_full (*out_data, e_cal_cache_search_data_free); *out_data = NULL; } return success; } /** * e_cal_cache_search_components: * @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_components (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_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 (ids, (GDestroyNotify) e_cal_component_id_free); * 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, (GDestroyNotify) e_cal_component_id_free); *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: (scope call): an #ECalCacheSearchFunc callback to call for each row which satisfies @sexp * @user_data: (closure func): 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) { ECalBackendSExp *bsexp = NULL; gint sexp_id = -1; gboolean success; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); g_return_val_if_fail (func != NULL, FALSE); if (sexp && *sexp && g_strcmp0 (sexp, "#t") != 0) { bsexp = e_cal_backend_sexp_new (sexp); if (!bsexp) { g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_INVALID_QUERY, _("Invalid query: %s"), sexp); return FALSE; } sexp_id = ecc_take_sexp_object (cal_cache, bsexp); } else { sexp = NULL; } success = ecc_search_internal (cal_cache, sexp, sexp_id, func, user_data, cancellable, error); if (bsexp) ecc_free_sexp_object (cal_cache, sexp_id); return success; } /** * e_cal_cache_get_offline_changes: * @cal_cache: an #ECalCache * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * The same as e_cache_get_offline_changes(), only splits the saved UID * into UID and RID and saved the data into #ECalCacheOfflineChange structure. * * Returns: (transfer full) (element-type ECalCacheOfflineChange): A newly allocated list of all * offline changes. Free it with g_slist_free_full (slist, e_cal_cache_offline_change_free); * when no longer needed. * * Since: 3.26 **/ GSList * e_cal_cache_get_offline_changes (ECalCache *cal_cache, GCancellable *cancellable, GError **error) { GSList *changes, *link; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), NULL); changes = e_cache_get_offline_changes (E_CACHE (cal_cache), cancellable, error); for (link = changes; link; link = g_slist_next (link)) { ECacheOfflineChange *cache_change = link->data; ECalCacheOfflineChange *cal_change; gchar *uid = NULL, *rid = NULL; if (!cache_change || !ecc_decode_id_sql (cache_change->uid, &uid, &rid)) { g_warn_if_reached (); e_cache_offline_change_free (cache_change); link->data = NULL; continue; } cal_change = e_cal_cache_offline_change_new (uid, rid, cache_change->revision, cache_change->object, cache_change->state); link->data = cal_change; e_cache_offline_change_free (cache_change); g_free (uid); g_free (rid); } return changes; } /** * e_cal_cache_get_offline_state: * @cal_cache: an #ECalCache * @uid: a UID of the component * @rid: (nullable): an optional Recurrence-ID * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * This is a wrapper of e_cache_get_offline_state(), ensuring that * a correct #ECache UID will be used. * * Returns: Current offline state #EOfflineState for the given component. * It returns %E_OFFLINE_STATE_UNKNOWN when the component could not be * found or other error happened. * * Since: 3.34 **/ EOfflineState e_cal_cache_get_offline_state (ECalCache *cal_cache, const gchar *uid, const gchar *rid, GCancellable *cancellable, GError **error) { EOfflineState res; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), E_OFFLINE_STATE_UNKNOWN); g_return_val_if_fail (uid != NULL, E_OFFLINE_STATE_UNKNOWN); if (rid && *rid) { gchar *id; id = ecc_encode_id_sql (uid, rid); res = e_cache_get_offline_state (E_CACHE (cal_cache), id, cancellable, error); g_free (id); } else { res = e_cache_get_offline_state (E_CACHE (cal_cache), uid, cancellable, error); } return res; } /** * e_cal_cache_delete_attachments: * @cal_cache: an #ECalCache * @component: an #ICalComponent * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Deletes all locally stored attachments beside the cache file from the disk. * This doesn't modify the @component. It's usually called before the @component * is being removed from the @cal_cache. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_cal_cache_delete_attachments (ECalCache *cal_cache, ICalComponent *component, GCancellable *cancellable, GError **error) { ICalProperty *prop; gchar *cache_dirname = NULL; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); g_return_val_if_fail (component != NULL, FALSE); for (prop = i_cal_component_get_first_property (component, I_CAL_ATTACH_PROPERTY); prop; g_object_unref (prop), prop = i_cal_component_get_next_property (component, I_CAL_ATTACH_PROPERTY)) { ICalAttach *attach = i_cal_property_get_attach (prop); if (attach && i_cal_attach_get_is_url (attach)) { const gchar *url; url = i_cal_attach_get_url (attach); if (url) { gchar *buf; buf = i_cal_value_decode_ical_string (url); if (g_str_has_prefix (buf, "file://")) { gchar *filename; filename = g_filename_from_uri (buf, NULL, NULL); if (filename) { if (!cache_dirname) cache_dirname = g_path_get_dirname (e_cache_get_filename (E_CACHE (cal_cache))); if (g_str_has_prefix (filename, cache_dirname) && g_unlink (filename) == -1) { /* Ignore these errors */ } g_free (filename); } } g_free (buf); } } g_clear_object (&attach); } g_free (cache_dirname); return TRUE; } static gint e_cal_cache_get_current_timezone_refs (ECalCache *cal_cache, const gchar *tzid, GCancellable *cancellable) { guint64 existing_refs = -1; gchar *stmt; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), -1); g_return_val_if_fail (tzid != NULL, -1); stmt = e_cache_sqlite_stmt_printf ("SELECT refs FROM " ECC_TABLE_TIMEZONES " WHERE tzid=%Q", tzid); if (!e_cache_sqlite_select (E_CACHE (cal_cache), stmt, e_cal_cache_get_uint64_cb, &existing_refs, cancellable, NULL)) existing_refs = -1; e_cache_sqlite_stmt_free (stmt); return (gint) existing_refs; } /** * e_cal_cache_put_timezone: * @cal_cache: an #ECalCache * @zone: an #ICalTimezone to put * @inc_ref_counts: how many refs to add, or 0 to have it stored forever * @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 adds a new or replaces existing, * if any such already exists in the @cal_cache. The function does * nothing and returns %TRUE, when the passed-in @zone is libical * builtin timezone. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_cal_cache_put_timezone (ECalCache *cal_cache, const ICalTimezone *zone, guint inc_ref_counts, 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 = i_cal_timezone_get_tzid ((ICalTimezone *) zone); if (!tzid) { g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Cannot add timezone without tzid")); return FALSE; } if (ecc_tzid_is_libical_builtin (tzid)) return TRUE; component = i_cal_timezone_get_component ((ICalTimezone *) 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 = i_cal_component_as_ical_string (component); g_clear_object (&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; } g_rec_mutex_lock (&cal_cache->priv->timezones_lock); if (inc_ref_counts > 0) { gint current_refs; current_refs = e_cal_cache_get_current_timezone_refs (cal_cache, tzid, cancellable); /* Zero means keep forever */ if (current_refs == 0) inc_ref_counts = 0; else if (current_refs > 0) inc_ref_counts += current_refs; } stmt = e_cache_sqlite_stmt_printf ( "INSERT or REPLACE INTO " ECC_TABLE_TIMEZONES " (tzid, zone, refs) VALUES (%Q, %Q, %u)", tzid, component_str, inc_ref_counts); success = e_cache_sqlite_exec (E_CACHE (cal_cache), stmt, cancellable, error); e_cache_sqlite_stmt_free (stmt); g_free (component_str); g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); return success; } /** * 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_rec_mutex_lock (&cal_cache->priv->timezones_lock); *out_zone = g_hash_table_lookup (cal_cache->priv->loaded_timezones, tzid); if (*out_zone) { g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); return TRUE; } *out_zone = g_hash_table_lookup (cal_cache->priv->modified_timezones, tzid); if (*out_zone) { g_rec_mutex_unlock (&cal_cache->priv->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_rec_mutex_unlock (&cal_cache->priv->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; g_rec_mutex_lock (&cal_cache->priv->timezones_lock); 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) && *out_zone_string != NULL; e_cache_sqlite_stmt_free (stmt); g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); return success; } 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. * * Note: The list can contain timezones previously stored * in the cache, but removed from it since they were loaded, * because these are freed only when also the @cal_cache is freed. * * 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_rec_mutex_lock (&cal_cache->priv->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)) { if (n_stored == 0) { g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); *out_timezones = NULL; return TRUE; } 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) { GList *loaded, *modified; loaded = g_hash_table_get_values (cal_cache->priv->loaded_timezones); modified = g_hash_table_get_values (cal_cache->priv->modified_timezones); if (loaded && modified) *out_timezones = g_list_concat (loaded, modified); else *out_timezones = loaded ? loaded : modified; } g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); return success; } /** * e_cal_cache_remove_timezone: * @cal_cache: an #ECalCache * @tzid: timezone ID to remove/dereference * @dec_ref_counts: reference counts to drop, 0 to remove it regardless of the current reference count * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Dereferences use count of the time zone with ID @tzid by @dec_ref_counts * and removes the timezone from the cache when the reference count reaches * zero. Special case is with @dec_ref_counts being zero, in which case * the corresponding timezone is removed regardless of the current reference * count. * * It's not an error when the timezone doesn't exist in the cache. * * Returns: Whether succeeded. * * Since: 3.30 **/ gboolean e_cal_cache_remove_timezone (ECalCache *cal_cache, const gchar *tzid, guint dec_ref_counts, 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); e_cache_lock (E_CACHE (cal_cache), E_CACHE_LOCK_WRITE); g_rec_mutex_lock (&cal_cache->priv->timezones_lock); if (dec_ref_counts) { gint current_refs; current_refs = e_cal_cache_get_current_timezone_refs (cal_cache, tzid, cancellable); if (current_refs <= 0) { g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); e_cache_unlock (E_CACHE (cal_cache), E_CACHE_UNLOCK_COMMIT); return TRUE; } if (current_refs >= dec_ref_counts) dec_ref_counts = current_refs - dec_ref_counts; else dec_ref_counts = 0; } if (dec_ref_counts) stmt = e_cache_sqlite_stmt_printf ("UPDATE " ECC_TABLE_TIMEZONES " SET refs=%u WHERE tzid=%Q", dec_ref_counts, tzid); else stmt = e_cache_sqlite_stmt_printf ("DELETE FROM " ECC_TABLE_TIMEZONES " WHERE tzid=%Q", tzid); success = e_cache_sqlite_exec (E_CACHE (cal_cache), stmt, cancellable, error); e_cache_sqlite_stmt_free (stmt); g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); e_cache_unlock (E_CACHE (cal_cache), success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK); return success; } /** * e_cal_cache_remove_timezones: * @cal_cache: an #ECalCache * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Removes all stored timezones from the @cal_cache. * * Returns: Whether succeeded. * * Since: 3.26 **/ gboolean e_cal_cache_remove_timezones (ECalCache *cal_cache, GCancellable *cancellable, GError **error) { gboolean success; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); e_cache_lock (E_CACHE (cal_cache), E_CACHE_LOCK_WRITE); g_rec_mutex_lock (&cal_cache->priv->timezones_lock); success = e_cache_sqlite_exec (E_CACHE (cal_cache), "DELETE FROM " ECC_TABLE_TIMEZONES, cancellable, error); g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); e_cache_unlock (E_CACHE (cal_cache), success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK); return success; } /* Private function, not meant to be part of the public API */ void _e_cal_cache_remove_loaded_timezones (ECalCache *cal_cache) { g_return_if_fail (E_IS_CAL_CACHE (cal_cache)); g_rec_mutex_lock (&cal_cache->priv->timezones_lock); g_hash_table_remove_all (cal_cache->priv->loaded_timezones); g_hash_table_remove_all (cal_cache->priv->modified_timezones); g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); } /** * e_cal_cache_resolve_timezone_cb: * @tzid: a timezone ID * @cal_cache: an #ECalCache * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * An #ECalRecurResolveTimezoneCb callback, which can be used * with e_cal_recur_generate_instances_sync(). The @cal_cache * is supposed to be an #ECalCache instance. * * Returns: (transfer none) (nullable): the resolved #ICalTimezone, or %NULL, if not found * * Since: 3.26 **/ ICalTimezone * e_cal_cache_resolve_timezone_cb (const gchar *tzid, gpointer cal_cache, GCancellable *cancellable, GError **error) { g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), NULL); return e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cal_cache), tzid); } static gboolean ecc_search_delete_attachment_cb (ECalCache *cal_cache, const gchar *uid, const gchar *rid, const gchar *revision, const gchar *object, const gchar *extra, guint32 custom_flags, EOfflineState offline_state, gpointer user_data) { ICalComponent *icomp; GCancellable *cancellable = user_data; GError *local_error = NULL; g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE); g_return_val_if_fail (object != NULL, FALSE); icomp = i_cal_component_new_from_string (object); if (!icomp) return TRUE; if (!e_cal_cache_delete_attachments (cal_cache, icomp, cancellable, &local_error)) { if (rid && !*rid) rid = NULL; g_debug ("%s: Failed to remove attachments for '%s%s%s': %s", G_STRFUNC, uid, rid ? "|" : "", rid ? rid : "", local_error ? local_error->message : "Unknown error"); g_clear_error (&local_error); } g_object_unref (icomp); return !g_cancellable_is_cancelled (cancellable); } 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 "-" "-" */ static gchar * ecc_dup_component_revision (ECalCache *cal_cache, ICalComponent *icomp) { ICalTime *itt; ICalProperty *prop; GString *revision; g_return_val_if_fail (icomp != NULL, NULL); revision = g_string_sized_new (48); itt = i_cal_component_get_dtstamp (icomp); if (!itt || i_cal_time_is_null_time (itt) || !i_cal_time_is_valid_time (itt)) { g_string_append_c (revision, 'x'); } else { g_string_append_printf (revision, "%04d%02d%02d%02d%02d%02d", i_cal_time_get_year (itt), i_cal_time_get_month (itt), i_cal_time_get_day (itt), i_cal_time_get_hour (itt), i_cal_time_get_minute (itt), i_cal_time_get_second (itt)); } g_clear_object (&itt); g_string_append_c (revision, '-'); prop = i_cal_component_get_first_property (icomp, I_CAL_LASTMODIFIED_PROPERTY); if (prop) itt = i_cal_property_get_lastmodified (prop); if (!prop || !itt || i_cal_time_is_null_time (itt) || !i_cal_time_is_valid_time (itt)) { g_string_append_c (revision, 'x'); } else { g_string_append_printf (revision, "%04d%02d%02d%02d%02d%02d", i_cal_time_get_year (itt), i_cal_time_get_month (itt), i_cal_time_get_day (itt), i_cal_time_get_hour (itt), i_cal_time_get_minute (itt), i_cal_time_get_second (itt)); } g_clear_object (&prop); g_clear_object (&itt); g_string_append_c (revision, '-'); prop = i_cal_component_get_first_property (icomp, I_CAL_SEQUENCE_PROPERTY); if (!prop) { g_string_append_c (revision, 'x'); } else { g_string_append_printf (revision, "%d", i_cal_property_get_sequence (prop)); } g_clear_object (&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) { GHashTable *timezones = NULL; /* gchar *tzid ~> TimezoneMigrationData * */ 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); if (!cal_cache->priv->initializing) { timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, timezone_migration_data_free); ecc_count_timezones_for_component (cal_cache, timezones, e_cal_component_get_icalcomponent (comp), TRUE, cancellable); if (is_replace) ecc_count_timezones_for_old_component (cal_cache, timezones, uid, cancellable); } success = E_CACHE_CLASS (e_cal_cache_parent_class)->put_locked (cache, uid, revision, object, other_columns, offline_state, is_replace, cancellable, error); if (success) success = ecc_update_timezones_table (cal_cache, timezones, cancellable, error); if (timezones) g_hash_table_destroy (timezones); g_clear_object (&comp); return success; } static gboolean e_cal_cache_remove_locked (ECache *cache, const gchar *uid, GCancellable *cancellable, GError **error) { GHashTable *timezones = NULL; /* gchar *tzid ~> TimezoneMigrationData * */ ECalCache *cal_cache; gboolean success; g_return_val_if_fail (E_IS_CAL_CACHE (cache), FALSE); g_return_val_if_fail (uid != NULL, FALSE); cal_cache = E_CAL_CACHE (cache); if (!cal_cache->priv->initializing) { timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, timezone_migration_data_free); ecc_count_timezones_for_old_component (cal_cache, timezones, uid, cancellable); } success = E_CACHE_CLASS (e_cal_cache_parent_class)->remove_locked (cache, uid, cancellable, error); if (success) success = ecc_update_timezones_table (cal_cache, timezones, cancellable, error); if (timezones) g_hash_table_destroy (timezones); 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 and priv->modified_timezones, because those can be used anywhere */ success = ecc_empty_aux_tables (cache, cancellable, error) && e_cal_cache_search_with_callback (E_CAL_CACHE (cache), NULL, ecc_search_delete_attachment_cb, cancellable, 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) g_object_unref (zone); } static void ecc_add_cached_timezone (ETimezoneCache *cache, ICalTimezone *zone) { ECalCache *cal_cache; cal_cache = E_CAL_CACHE (cache); if (!zone || ecc_tzid_is_libical_builtin (i_cal_timezone_get_tzid (zone))) return; e_cal_cache_put_timezone (cal_cache, zone, 0, NULL, NULL); } static ICalTimezone * ecc_get_cached_timezone (ETimezoneCache *cache, const gchar *tzid) { ECalCache *cal_cache; ICalTimezone *zone = NULL; ICalTimezone *builtin_zone = NULL; ICalComponent *icomp, *clone; ICalProperty *prop; const gchar *builtin_tzid; cal_cache = E_CAL_CACHE (cache); if (g_str_equal (tzid, "UTC")) return i_cal_timezone_get_utc_timezone (); g_rec_mutex_lock (&cal_cache->priv->timezones_lock); /* See if we already have it in the cache. */ zone = g_hash_table_lookup (cal_cache->priv->loaded_timezones, tzid); if (zone) goto exit; zone = g_hash_table_lookup (cal_cache->priv->modified_timezones, tzid); if (zone) goto exit; /* Try the location first */ /*zone = i_cal_timezone_get_builtin_timezone (tzid); if (zone) goto exit;*/ /* Try to replace the original time zone with a more complete * and/or potentially updated built-in time zone. Note this also * applies to TZIDs which match built-in time zones exactly: they * are extracted via i_cal_timezone_get_builtin_timezone_from_tzid(). */ builtin_tzid = e_cal_match_tzid (tzid); if (builtin_tzid) builtin_zone = i_cal_timezone_get_builtin_timezone_from_tzid (builtin_tzid); if (!builtin_zone) { e_cal_cache_get_timezone (cal_cache, tzid, &zone, NULL, NULL); goto exit; } /* Use the built-in time zone *and* rename it. Likely the caller * is asking for a specific TZID because it has an event with such * a TZID. Returning an i_cal_timezone with a different TZID would * lead to broken VCALENDARs in the caller. */ icomp = i_cal_timezone_get_component (builtin_zone); clone = i_cal_component_clone (icomp); g_object_unref (icomp); icomp = clone; for (prop = i_cal_component_get_first_property (icomp, I_CAL_ANY_PROPERTY); prop; g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ANY_PROPERTY)) { if (i_cal_property_isa (prop) == I_CAL_TZID_PROPERTY) { i_cal_property_set_value_from_string (prop, tzid, "NO"); g_object_unref (prop); break; } } zone = i_cal_timezone_new (); if (i_cal_timezone_set_component (zone, icomp)) { tzid = i_cal_timezone_get_tzid (zone); g_hash_table_insert (cal_cache->priv->modified_timezones, g_strdup (tzid), zone); } else { g_clear_object (&zone); } g_clear_object (&icomp); exit: g_rec_mutex_unlock (&cal_cache->priv->timezones_lock); return zone; } static GList * ecc_list_cached_timezones (ETimezoneCache *cache) { GList *timezones = NULL; if (!e_cal_cache_list_timezones (E_CAL_CACHE (cache), &timezones, NULL, NULL)) return NULL; return timezones; } 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_hash_table_destroy (cal_cache->priv->modified_timezones); g_hash_table_destroy (cal_cache->priv->sexps); g_rec_mutex_clear (&cal_cache->priv->timezones_lock); g_mutex_clear (&cal_cache->priv->sexps_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; 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_locked = e_cal_cache_remove_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 ICalComponent. * 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, G_TYPE_POINTER); /** * ECalCache:get-timezone: * @cal_cache: an #ECalCache * @tzid: timezone ID * * A signal being called to get timezone when putting component * into the cache. It's used to make sure the cache contains * all timezones which are needed by the component. The returned * ICalTimezone will not be freed. * * Since: 3.30 **/ signals[GET_TIMEZONE] = g_signal_new ( "get-timezone", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (ECalCacheClass, get_timezone), g_signal_accumulator_first_wins, NULL, g_cclosure_marshal_generic, G_TYPE_POINTER, 1, G_TYPE_STRING); } static void ecc_timezone_cache_init (ETimezoneCacheInterface *iface) { iface->tzcache_add_timezone = ecc_add_cached_timezone; iface->tzcache_get_timezone = ecc_get_cached_timezone; iface->tzcache_list_timezones = ecc_list_cached_timezones; } static void e_cal_cache_init (ECalCache *cal_cache) { cal_cache->priv = e_cal_cache_get_instance_private (cal_cache); cal_cache->priv->loaded_timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, cal_cache_free_zone); cal_cache->priv->modified_timezones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, cal_cache_free_zone); cal_cache->priv->sexps = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); g_rec_mutex_init (&cal_cache->priv->timezones_lock); g_mutex_init (&cal_cache->priv->sexps_lock); }