[evolution-data-server] Bug 794108 - ETimezoneCache only grows



commit 0ef4b50bc4bf71b26f51b1801a306b231e897ad9
Author: Milan Crha <mcrha redhat com>
Date:   Wed Jun 6 18:22:37 2018 +0200

    Bug 794108 - ETimezoneCache only grows

 CMakeLists.txt                                 |   2 +-
 src/calendar/libedata-cal/e-cal-backend.c      |  13 +
 src/calendar/libedata-cal/e-cal-cache.c        | 634 ++++++++++++++++++++++---
 src/calendar/libedata-cal/e-cal-cache.h        |  10 +-
 src/calendar/libedata-cal/e-cal-meta-backend.c | 111 +++--
 src/libebackend/e-cache.c                      |  97 +++-
 src/libebackend/e-cache.h                      |   7 +
 tests/libedata-cal/test-cal-meta-backend.c     | 299 ++++++++++--
 8 files changed, 1007 insertions(+), 166 deletions(-)
---
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b8cf258d9..e85ec73e1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,7 +77,7 @@ set(LIBECAL_CURRENT 19)
 set(LIBECAL_REVISION 0)
 set(LIBECAL_AGE 0)
 
-set(LIBEDATACAL_CURRENT 28)
+set(LIBEDATACAL_CURRENT 29)
 set(LIBEDATACAL_REVISION 0)
 set(LIBEDATACAL_AGE 0)
 
diff --git a/src/calendar/libedata-cal/e-cal-backend.c b/src/calendar/libedata-cal/e-cal-backend.c
index e5819b68c..3029f2dc5 100644
--- a/src/calendar/libedata-cal/e-cal-backend.c
+++ b/src/calendar/libedata-cal/e-cal-backend.c
@@ -782,6 +782,19 @@ cal_backend_shutdown (ECalBackend *backend)
                e_source_get_display_name (source));
 }
 
+/* Private function, not meant to be part of the public API */
+void _e_cal_backend_remove_cached_timezones (ECalBackend *cal_backend);
+
+void
+_e_cal_backend_remove_cached_timezones (ECalBackend *cal_backend)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND (cal_backend));
+
+       g_mutex_lock (&cal_backend->priv->zone_cache_lock);
+       g_hash_table_remove_all (cal_backend->priv->zone_cache);
+       g_mutex_unlock (&cal_backend->priv->zone_cache_lock);
+}
+
 static void
 cal_backend_add_cached_timezone (ETimezoneCache *cache,
                                  icaltimezone *zone)
diff --git a/src/calendar/libedata-cal/e-cal-cache.c b/src/calendar/libedata-cal/e-cal-cache.c
index d6e2d55dc..72d8c1859 100644
--- a/src/calendar/libedata-cal/e-cal-cache.c
+++ b/src/calendar/libedata-cal/e-cal-cache.c
@@ -42,7 +42,7 @@
 
 #include "e-cal-cache.h"
 
-#define E_CAL_CACHE_VERSION            2
+#define E_CAL_CACHE_VERSION            3
 
 #define ECC_TABLE_TIMEZONES            "timezones"
 
@@ -68,6 +68,8 @@
 #define ECC_COLUMN_EXTRA               "bdata"
 
 struct _ECalCachePrivate {
+       gboolean initializing;
+
        GHashTable *loaded_timezones; /* gchar *tzid ~> icaltimezone * */
        GHashTable *modified_timezones; /* gchar *tzid ~> icaltimezone * */
        GRecMutex timezones_lock;
@@ -78,11 +80,15 @@ struct _ECalCachePrivate {
 
 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,
@@ -1707,7 +1713,8 @@ ecc_init_aux_tables (ECalCache *cal_cache,
 
        stmt = e_cache_sqlite_stmt_printf ("CREATE TABLE IF NOT EXISTS %Q ("
                "tzid TEXT PRIMARY KEY, "
-               "zone TEXT)",
+               "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);
@@ -1820,6 +1827,298 @@ cal_cache_gather_v1_affected_cb (ECalCache *cal_cache,
        return TRUE;
 }
 
+static icaltimezone *
+ecc_timezone_from_string (const gchar *icalstring)
+{
+       icalcomponent *component;
+
+       g_return_val_if_fail (icalstring != NULL, NULL);
+
+       component = icalcomponent_new_from_string (icalstring);
+       if (component) {
+               icaltimezone *zone;
+
+               zone = icaltimezone_new ();
+               if (!icaltimezone_set_component (zone, component)) {
+                       icalcomponent_free (component);
+                       icaltimezone_free (zone, 1);
+               } else {
+                       return zone;
+               }
+       }
+
+       return NULL;
+}
+
+static gboolean
+ecc_tzid_is_libical_builtin (const gchar *tzid)
+{
+       const gchar *matched_tzid;
+
+       if (!tzid || !*tzid || icaltimezone_get_builtin_timezone (tzid))
+               return TRUE;
+
+       matched_tzid = e_cal_match_tzid (tzid);
+       return matched_tzid && icaltimezone_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) {
+               if (tmd->zone)
+                       icaltimezone_free (tmd->zone, 1);
+               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 = icalparameter_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) {
+                       icalcomponent *zonecomp;
+
+                       zonecomp = icaltimezone_get_component (zone);
+                       if (zonecomp) {
+                               icalcomponent *clone;
+
+                               clone = icalcomponent_new_clone (zonecomp);
+                               /* icaltimezone_copy() doesn't carry over the component, thus do it this way 
*/
+                               zone = icaltimezone_new ();
+                               if (!icaltimezone_set_component (zone, clone)) {
+                                       icalcomponent_free (clone);
+                                       icaltimezone_free (zone, 1);
+                                       zone = NULL;
+                               }
+                       } else {
+                               zone = NULL;
+                       }
+               }
+
+               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 *icalcomp,
+                                  gboolean is_inc,
+                                  GCancellable *cancellable)
+{
+       g_return_if_fail (E_IS_CAL_CACHE (cal_cache));
+       g_return_if_fail (timezones != NULL);
+
+       if (icalcomp) {
+               CountTimezonesData ctd;
+
+               ctd.cal_cache = cal_cache;
+               ctd.timezones = timezones;
+               ctd.is_inc = is_inc;
+               ctd.cancellable = cancellable;
+
+               icalcomponent_foreach_tzid (icalcomp, 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 *icalcomp;
+
+               icalcomp = icalcomponent_new_from_string (objstr);
+               if (icalcomp) {
+                       ecc_count_timezones_for_component (cal_cache, timezones, icalcomp, FALSE, 
cancellable);
+                       icalcomponent_free (icalcomp);
+               }
+
+               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 = icalparameter_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,
+                         EOfflineState offline_state,
+                         gpointer user_data)
+{
+       GHashTable *timezones = user_data;
+       icalcomponent *icalcomp;
+
+       g_return_val_if_fail (timezones != NULL, FALSE);
+       g_return_val_if_fail (object != NULL, FALSE);
+
+       icalcomp = icalcomponent_new_from_string (object);
+       if (icalcomp) {
+               icalcomponent_foreach_tzid (icalcomp, ecc_count_tmd_refs_cb, timezones);
+               icalcomponent_free (icalcomp);
+       }
+
+       return TRUE;
+}
+
 static gboolean
 e_cal_cache_migrate (ECache *cache,
                     gint from_version,
@@ -1827,10 +2126,33 @@ e_cal_cache_migrate (ECache *cache,
                     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 == 1) {
+
+       if (from_version > 0 && from_version < 3) {
+               gchar *stmt;
+
+               g_rec_mutex_lock (&cal_cache->priv->timezones_lock);
+
+               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;
 
@@ -1849,6 +2171,36 @@ e_cal_cache_migrate (ECache *cache,
                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;
 }
 
@@ -1865,6 +2217,8 @@ e_cal_cache_initialize (ECalCache *cal_cache,
        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);
@@ -1893,6 +2247,8 @@ e_cal_cache_initialize (ECalCache *cal_cache,
  exit:
        g_slist_free_full (other_columns, e_cache_column_info_free);
 
+       cal_cache->priv->initializing = FALSE;
+
        return success;
 }
 
@@ -3062,16 +3418,60 @@ e_cal_cache_delete_attachments (ECalCache *cal_cache,
        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 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.
+ * 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.
  *
@@ -3080,6 +3480,7 @@ e_cal_cache_delete_attachments (ECalCache *cal_cache,
 gboolean
 e_cal_cache_put_timezone (ECalCache *cal_cache,
                          const icaltimezone *zone,
+                         guint inc_ref_counts,
                          GCancellable *cancellable,
                          GError **error)
 {
@@ -3098,6 +3499,9 @@ e_cal_cache_put_timezone (ECalCache *cal_cache,
                return FALSE;
        }
 
+       if (ecc_tzid_is_libical_builtin (icaltimezone_get_tzid ((icaltimezone *) zone)))
+               return TRUE;
+
        component = icaltimezone_get_component ((icaltimezone *) zone);
        if (!component) {
                g_set_error_literal (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Cannot add timezone 
without component"));
@@ -3110,40 +3514,33 @@ e_cal_cache_put_timezone (ECalCache *cal_cache,
                return FALSE;
        }
 
-       stmt = e_cache_sqlite_stmt_printf (
-               "INSERT or REPLACE INTO " ECC_TABLE_TIMEZONES " (tzid, zone) VALUES (%Q, %Q)",
-               tzid, component_str);
+       g_rec_mutex_lock (&cal_cache->priv->timezones_lock);
 
-       success = e_cache_sqlite_exec (E_CACHE (cal_cache), stmt, cancellable, error);
+       if (inc_ref_counts > 0) {
+               gint current_refs;
 
-       e_cache_sqlite_stmt_free (stmt);
+               current_refs = e_cal_cache_get_current_timezone_refs (cal_cache, tzid, cancellable);
 
-       g_free (component_str);
+               /* Zero means keep forever */
+               if (current_refs == 0)
+                       inc_ref_counts = 0;
+               else if (current_refs > 0)
+                       inc_ref_counts += current_refs;
+       }
 
-       return success;
-}
+       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);
 
-static icaltimezone *
-ecc_timezone_from_string (const gchar *icalstring)
-{
-       icalcomponent *component;
+       success = e_cache_sqlite_exec (E_CACHE (cal_cache), stmt, cancellable, error);
 
-       g_return_val_if_fail (icalstring != NULL, NULL);
+       e_cache_sqlite_stmt_free (stmt);
 
-       component = icalcomponent_new_from_string (icalstring);
-       if (component) {
-               icaltimezone *zone;
+       g_free (component_str);
 
-               zone = icaltimezone_new ();
-               if (!icaltimezone_set_component (zone, component)) {
-                       icalcomponent_free (component);
-                       icaltimezone_free (zone, 1);
-               } else {
-                       return zone;
-               }
-       }
+       g_rec_mutex_unlock (&cal_cache->priv->timezones_lock);
 
-       return NULL;
+       return success;
 }
 
 /**
@@ -3246,6 +3643,8 @@ e_cal_cache_dup_timezone_as_string (ECalCache *cal_cache,
 
        *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);
@@ -3255,27 +3654,9 @@ e_cal_cache_dup_timezone_as_string (ECalCache *cal_cache,
 
        e_cache_sqlite_stmt_free (stmt);
 
-       return success;
-}
-
-static gboolean
-e_cal_cache_get_uint64_cb (ECache *cache,
-                          gint ncols,
-                          const gchar **column_names,
-                          const gchar **column_values,
-                          gpointer user_data)
-{
-       guint64 *pui64 = user_data;
-
-       g_return_val_if_fail (pui64 != NULL, FALSE);
-
-       if (ncols == 1) {
-               *pui64 = column_values[0] ? g_ascii_strtoull (column_values[0], NULL, 10) : 0;
-       } else {
-               *pui64 = 0;
-       }
+       g_rec_mutex_unlock (&cal_cache->priv->timezones_lock);
 
-       return TRUE;
+       return success;
 }
 
 static gboolean
@@ -3373,6 +3754,76 @@ e_cal_cache_list_timezones (ECalCache *cal_cache,
        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
@@ -3407,8 +3858,6 @@ e_cal_cache_remove_timezones (ECalCache *cal_cache,
        return success;
 }
 
-void _e_cal_cache_remove_loaded_timezones (ECalCache *cal_cache);
-
 /* Private function, not meant to be part of the public API */
 void
 _e_cal_cache_remove_loaded_timezones (ECalCache *cal_cache)
@@ -3585,6 +4034,7 @@ e_cal_cache_put_locked (ECache *cache,
                        GCancellable *cancellable,
                        GError **error)
 {
+       GHashTable *timezones = NULL; /* gchar *tzid ~> TimezoneMigrationData * */
        ECalCache *cal_cache;
        ECalComponent *comp;
        gboolean success;
@@ -3600,14 +4050,61 @@ e_cal_cache_put_locked (ECache *cache,
 
        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,
@@ -3644,20 +4141,13 @@ ecc_add_cached_timezone (ETimezoneCache *cache,
                         icaltimezone *zone)
 {
        ECalCache *cal_cache;
-       const gchar *matched_tzid;
-       const gchar *tzid;
 
        cal_cache = E_CAL_CACHE (cache);
 
-       tzid = icaltimezone_get_tzid (zone);
-       if (!tzid || !*tzid || icaltimezone_get_builtin_timezone (tzid))
+       if (!zone || ecc_tzid_is_libical_builtin (icaltimezone_get_tzid (zone)))
                return;
 
-       matched_tzid = e_cal_match_tzid (tzid);
-       if (matched_tzid && icaltimezone_get_builtin_timezone_from_tzid (matched_tzid))
-               return;
-
-       e_cal_cache_put_timezone (cal_cache, zone, NULL, NULL);
+       e_cal_cache_put_timezone (cal_cache, zone, 0, NULL, NULL);
 }
 
 static icaltimezone *
@@ -3784,6 +4274,7 @@ e_cal_cache_class_init (ECalCacheClass *klass)
 
        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;
@@ -3804,6 +4295,29 @@ e_cal_cache_class_init (ECalCacheClass *klass)
                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
diff --git a/src/calendar/libedata-cal/e-cal-cache.h b/src/calendar/libedata-cal/e-cal-cache.h
index 571ebfb6d..66f104532 100644
--- a/src/calendar/libedata-cal/e-cal-cache.h
+++ b/src/calendar/libedata-cal/e-cal-cache.h
@@ -179,9 +179,11 @@ struct _ECalCacheClass {
        gchar *         (* dup_component_revision)
                                                (ECalCache *cal_cache,
                                                 icalcomponent *icalcomp);
+       icaltimezone *  (* get_timezone)        (ECalCache *cal_cache,
+                                                const gchar *tzid);
 
        /* Padding for future expansion */
-       gpointer reserved[10];
+       gpointer reserved[9];
 };
 
 GType          e_cal_cache_get_type            (void) G_GNUC_CONST;
@@ -307,6 +309,7 @@ gboolean    e_cal_cache_delete_attachments  (ECalCache *cal_cache,
 
 gboolean       e_cal_cache_put_timezone        (ECalCache *cal_cache,
                                                 const icaltimezone *zone,
+                                                guint inc_ref_counts,
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_cal_cache_get_timezone        (ECalCache *cal_cache,
@@ -324,6 +327,11 @@ gboolean   e_cal_cache_list_timezones      (ECalCache *cal_cache,
                                                 GList **out_timezones,
                                                 GCancellable *cancellable,
                                                 GError **error);
+gboolean       e_cal_cache_remove_timezone     (ECalCache *cal_cache,
+                                                const gchar *tzid,
+                                                guint dec_ref_counts,
+                                                GCancellable *cancellable,
+                                                GError **error);
 gboolean       e_cal_cache_remove_timezones    (ECalCache *cal_cache,
                                                 GCancellable *cancellable,
                                                 GError **error);
diff --git a/src/calendar/libedata-cal/e-cal-meta-backend.c b/src/calendar/libedata-cal/e-cal-meta-backend.c
index 8920577a5..a2c82b643 100644
--- a/src/calendar/libedata-cal/e-cal-meta-backend.c
+++ b/src/calendar/libedata-cal/e-cal-meta-backend.c
@@ -73,6 +73,7 @@ struct _ECalMetaBackendPrivate {
        gulong source_changed_id;
        gulong notify_online_id;
        gulong revision_changed_id;
+       gulong get_timezone_id;
        guint refresh_timeout_id;
 
        gboolean refresh_after_authenticate;
@@ -102,6 +103,11 @@ enum {
 
 static guint signals[LAST_SIGNAL];
 
+/* To be able to call the ECalBackend implementation, which stores zones only in the memory */
+static icaltimezone *  (* ecmb_timezone_cache_parent_get_timezone) (ETimezoneCache *cache,
+                                                                    const gchar *tzid);
+static GList *         (* ecmb_timezone_cache_parent_list_timezones) (ETimezoneCache *cache);
+
 /* Forward Declarations */
 static void e_cal_meta_backend_timezone_cache_init (ETimezoneCacheInterface *iface);
 
@@ -1023,7 +1029,8 @@ ecmb_gather_timezones (ECalMetaBackend *meta_backend,
                clone = icalcomponent_new_clone (subcomp);
 
                if (icaltimezone_set_component (zone, clone)) {
-                       e_timezone_cache_add_timezone (timezone_cache, zone);
+                       if (icaltimezone_get_tzid (zone))
+                               e_timezone_cache_add_timezone (timezone_cache, zone);
                } else {
                        icalcomponent_free (clone);
                }
@@ -1070,7 +1077,7 @@ ecmb_load_component_wrapper_sync (ECalMetaBackend *meta_backend,
                icalcomponent_kind kind;
                icalcomponent *subcomp;
 
-               ecmb_gather_timezones (meta_backend, E_TIMEZONE_CACHE (cal_cache), icalcomp);
+               ecmb_gather_timezones (meta_backend, E_TIMEZONE_CACHE (meta_backend), icalcomp);
 
                kind = e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend));
 
@@ -2515,7 +2522,7 @@ ecmb_receive_objects_sync (ECalBackendSync *sync_backend,
        comps = g_slist_reverse (comps);
 
        if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT)
-               ecmb_gather_timezones (meta_backend, E_TIMEZONE_CACHE (cal_cache), icalcomp);
+               ecmb_gather_timezones (meta_backend, E_TIMEZONE_CACHE (meta_backend), icalcomp);
 
        if (icalcomponent_get_first_property (icalcomp, ICAL_METHOD_PROPERTY))
                top_method = icalcomponent_get_method (icalcomp);
@@ -2684,7 +2691,6 @@ ecmb_get_timezone_sync (ECalBackendSync *sync_backend,
                        gchar **tzobject,
                        GError **error)
 {
-       ECalCache *cal_cache;
        icaltimezone *zone;
        gchar *timezone_str = NULL;
        GError *local_error = NULL;
@@ -2696,10 +2702,7 @@ ecmb_get_timezone_sync (ECalBackendSync *sync_backend,
        if (g_cancellable_set_error_if_cancelled (cancellable, error))
                return;
 
-       cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (sync_backend));
-       g_return_if_fail (cal_cache != NULL);
-
-       zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cal_cache), tzid);
+       zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (sync_backend), tzid);
        if (zone) {
                icalcomponent *icalcomp;
 
@@ -2712,8 +2715,6 @@ ecmb_get_timezone_sync (ECalBackendSync *sync_backend,
                }
        }
 
-       g_object_unref (cal_cache);
-
        if (!local_error && !timezone_str)
                local_error = e_data_cal_create_error (ObjectNotFound, NULL);
 
@@ -2747,7 +2748,6 @@ ecmb_add_timezone_sync (ECalBackendSync *sync_backend,
            icalcomponent_isa (tz_comp) != ICAL_VTIMEZONE_COMPONENT) {
                g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
        } else {
-               ECalCache *cal_cache;
                icaltimezone *zone;
 
                zone = icaltimezone_new ();
@@ -2755,14 +2755,9 @@ ecmb_add_timezone_sync (ECalBackendSync *sync_backend,
 
                tz_comp = NULL;
 
-               cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (sync_backend));
-               if (cal_cache) {
-                       e_timezone_cache_add_timezone (E_TIMEZONE_CACHE (cal_cache), zone);
-                       icaltimezone_free (zone, 1);
-                       g_object_unref (cal_cache);
-               } else {
-                       g_warn_if_reached ();
-               }
+               /* Add it only to memory, do not store it persistently into the ECalCache */
+               e_timezone_cache_add_timezone (E_TIMEZONE_CACHE (sync_backend), zone);
+               icaltimezone_free (zone, 1);
        }
 
        if (tz_comp)
@@ -3108,20 +3103,6 @@ ecmb_maybe_wait_for_credentials (ECalMetaBackend *meta_backend,
        return got_credentials;
 }
 
-static void
-ecmb_add_cached_timezone (ETimezoneCache *cache,
-                         icaltimezone *zone)
-{
-       ECalCache *cal_cache;
-
-       cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (cache));
-       g_return_if_fail (E_IS_CAL_CACHE (cal_cache));
-
-       e_timezone_cache_add_timezone (E_TIMEZONE_CACHE (cal_cache), zone);
-
-       g_clear_object (&cal_cache);
-}
-
 static icaltimezone *
 ecmb_get_cached_timezone (ETimezoneCache *cache,
                          const gchar *tzid)
@@ -3129,6 +3110,13 @@ ecmb_get_cached_timezone (ETimezoneCache *cache,
        ECalCache *cal_cache;
        icaltimezone *zone;
 
+       if (ecmb_timezone_cache_parent_get_timezone) {
+               zone = ecmb_timezone_cache_parent_get_timezone (cache, tzid);
+
+               if (zone)
+                       return zone;
+       }
+
        cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (cache));
        g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), NULL);
 
@@ -3152,6 +3140,16 @@ ecmb_list_cached_timezones (ETimezoneCache *cache)
 
        g_clear_object (&cal_cache);
 
+       if (ecmb_timezone_cache_parent_list_timezones) {
+               GList *backend_zones;
+
+               backend_zones = ecmb_timezone_cache_parent_list_timezones (E_TIMEZONE_CACHE (cache));
+
+               /* There can be duplicates in the 'zones' GList, but let's make it no big deal */
+               if (backend_zones)
+                       zones = g_list_concat (zones, backend_zones);
+       }
+
        return zones;
 }
 
@@ -3251,6 +3249,12 @@ e_cal_meta_backend_dispose (GObject *object)
                meta_backend->priv->revision_changed_id = 0;
        }
 
+       if (meta_backend->priv->get_timezone_id) {
+               if (meta_backend->priv->cache)
+                       g_signal_handler_disconnect (meta_backend->priv->cache, 
meta_backend->priv->get_timezone_id);
+               meta_backend->priv->get_timezone_id = 0;
+       }
+
        g_hash_table_foreach (meta_backend->priv->view_cancellables, ecmb_cancel_view_cb, NULL);
 
        if (meta_backend->priv->refresh_cancellable) {
@@ -3415,7 +3419,10 @@ e_cal_meta_backend_init (ECalMetaBackend *meta_backend)
 static void
 e_cal_meta_backend_timezone_cache_init (ETimezoneCacheInterface *iface)
 {
-       iface->add_timezone = ecmb_add_cached_timezone;
+       ecmb_timezone_cache_parent_get_timezone = iface->get_timezone;
+       ecmb_timezone_cache_parent_list_timezones = iface->list_timezones;
+
+       /* leave the iface->add_timezone as it was, to have them in memory only */
        iface->get_timezone = ecmb_get_cached_timezone;
        iface->list_timezones = ecmb_list_cached_timezones;
 }
@@ -3623,6 +3630,20 @@ ecmb_cache_revision_changed_cb (ECache *cache,
        }
 }
 
+static icaltimezone *
+ecmb_cache_get_timezone_cb (ECalCache *cal_cache,
+                           const gchar *tzid,
+                           gpointer user_data)
+{
+       ECalMetaBackend *meta_backend = user_data;
+
+       g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), NULL);
+       g_return_val_if_fail (tzid != NULL, NULL);
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), NULL);
+
+       return e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (meta_backend), tzid);
+}
+
 /**
  * e_cal_meta_backend_set_cache:
  * @meta_backend: an #ECalMetaBackend
@@ -3656,6 +3677,8 @@ e_cal_meta_backend_set_cache (ECalMetaBackend *meta_backend,
        if (meta_backend->priv->cache) {
                g_signal_handler_disconnect (meta_backend->priv->cache,
                        meta_backend->priv->revision_changed_id);
+               g_signal_handler_disconnect (meta_backend->priv->cache,
+                       meta_backend->priv->get_timezone_id);
        }
 
        g_clear_object (&meta_backend->priv->cache);
@@ -3664,6 +3687,9 @@ e_cal_meta_backend_set_cache (ECalMetaBackend *meta_backend,
        meta_backend->priv->revision_changed_id = g_signal_connect_object (meta_backend->priv->cache,
                "revision-changed", G_CALLBACK (ecmb_cache_revision_changed_cb), meta_backend, 0);
 
+       meta_backend->priv->get_timezone_id = g_signal_connect_object (meta_backend->priv->cache,
+               "get-timezone", G_CALLBACK (ecmb_cache_get_timezone_cb), meta_backend, 0);
+
        g_mutex_unlock (&meta_backend->priv->property_lock);
 
        g_object_notify (G_OBJECT (meta_backend), "cache");
@@ -3719,7 +3745,7 @@ sort_master_first_cb (gconstpointer a,
 }
 
 typedef struct {
-       ECalCache *cache;
+       ETimezoneCache *timezone_cache;
        gboolean replace_tzid_with_location;
        icalcomponent *vcalendar;
        icalcomponent *icalcomp;
@@ -3745,8 +3771,8 @@ add_timezone_cb (icalparameter *param,
        tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
        if (!tz)
                tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
-       if (!tz && f_data->cache)
-               tz = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (f_data->cache), tzid);
+       if (!tz && f_data->timezone_cache)
+               tz = e_timezone_cache_get_timezone (f_data->timezone_cache, tzid);
        if (!tz)
                return;
 
@@ -3808,7 +3834,6 @@ e_cal_meta_backend_merge_instances (ECalMetaBackend *meta_backend,
                                    const GSList *instances,
                                    gboolean replace_tzid_with_location)
 {
-       ECalCache *cal_cache;
        ForeachTzidData f_data;
        icalcomponent *vcalendar;
        GSList *link, *sorted;
@@ -3816,14 +3841,11 @@ e_cal_meta_backend_merge_instances (ECalMetaBackend *meta_backend,
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), NULL);
        g_return_val_if_fail (instances != NULL, NULL);
 
-       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
-       g_return_val_if_fail (cal_cache != NULL, NULL);
-
        sorted = g_slist_sort (g_slist_copy ((GSList *) instances), sort_master_first_cb);
 
        vcalendar = e_cal_util_new_top_level ();
 
-       f_data.cache = cal_cache;
+       f_data.timezone_cache = E_TIMEZONE_CACHE (meta_backend);
        f_data.replace_tzid_with_location = replace_tzid_with_location;
        f_data.vcalendar = vcalendar;
 
@@ -3844,7 +3866,6 @@ e_cal_meta_backend_merge_instances (ECalMetaBackend *meta_backend,
                icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
        }
 
-       g_clear_object (&f_data.cache);
        g_slist_free (sorted);
 
        return vcalendar;
@@ -4059,7 +4080,7 @@ e_cal_meta_backend_store_inline_attachments_sync (ECalMetaBackend *meta_backend,
  * @error: return location for a #GError, or %NULL
  *
  * Extracts all VTIMEZONE components from the @vcalendar and adds them
- * to the cache, thus they are available when needed. The function does
+ * to the memory cache, thus they are available when needed. The function does
  * nothing when the @vcalendar doesn't hold a VCALENDAR component.
  *
  * Set the @remove_existing argument to %TRUE to remove all cached timezones
@@ -4095,7 +4116,7 @@ e_cal_meta_backend_gather_timezones_sync (ECalMetaBackend *meta_backend,
                success = e_cal_cache_remove_timezones (cal_cache, cancellable, error);
 
        if (success)
-               ecmb_gather_timezones (meta_backend, E_TIMEZONE_CACHE (cal_cache), vcalendar);
+               ecmb_gather_timezones (meta_backend, E_TIMEZONE_CACHE (meta_backend), vcalendar);
 
        e_cache_unlock (E_CACHE (cal_cache), success ? E_CACHE_UNLOCK_COMMIT : E_CACHE_UNLOCK_ROLLBACK);
 
diff --git a/src/libebackend/e-cache.c b/src/libebackend/e-cache.c
index e683da119..dc7831d1d 100644
--- a/src/libebackend/e-cache.c
+++ b/src/libebackend/e-cache.c
@@ -1328,6 +1328,51 @@ e_cache_get_object_cb (ECache *cache,
        return TRUE;
 }
 
+static gchar *
+e_cache_get_object_internal (ECache *cache,
+                            gboolean include_deleted,
+                            const gchar *uid,
+                            gchar **out_revision,
+                            ECacheColumnValues **out_other_columns,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       struct GetObjectData gd;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_CACHE (cache), NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       if (out_revision)
+               *out_revision = NULL;
+
+       if (out_other_columns)
+               *out_other_columns = NULL;
+
+       gd.object = NULL;
+       gd.out_revision = out_revision;
+       gd.out_other_columns = out_other_columns;
+
+       if (include_deleted) {
+               success = e_cache_sqlite_exec_printf (cache,
+                       "SELECT * FROM " E_CACHE_TABLE_OBJECTS
+                       " WHERE " E_CACHE_COLUMN_UID " = %Q",
+                       e_cache_get_object_cb, &gd, cancellable, error,
+                       uid);
+       } else {
+               success = e_cache_sqlite_exec_printf (cache,
+                       "SELECT * FROM " E_CACHE_TABLE_OBJECTS
+                       " WHERE " E_CACHE_COLUMN_UID " = %Q AND " E_CACHE_COLUMN_STATE " != %d",
+                       e_cache_get_object_cb, &gd, cancellable, error,
+                       uid, E_OFFLINE_STATE_LOCALLY_DELETED);
+       }
+
+       if (success && !gd.object)
+               g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object ā€œ%sā€ not found"), uid);
+
+       return gd.object;
+}
+
 /**
  * e_cache_get:
  * @cache: an #ECache
@@ -1361,31 +1406,43 @@ e_cache_get (ECache *cache,
             GCancellable *cancellable,
             GError **error)
 {
-       struct GetObjectData gd;
-
        g_return_val_if_fail (E_IS_CACHE (cache), NULL);
        g_return_val_if_fail (uid != NULL, NULL);
 
-       if (out_revision)
-               *out_revision = NULL;
-
-       if (out_other_columns)
-               *out_other_columns = NULL;
-
-       gd.object = NULL;
-       gd.out_revision = out_revision;
-       gd.out_other_columns = out_other_columns;
+       return e_cache_get_object_internal (cache, FALSE, uid, out_revision, out_other_columns, cancellable, 
error);
+}
 
-       if (e_cache_sqlite_exec_printf (cache,
-               "SELECT * FROM " E_CACHE_TABLE_OBJECTS
-               " WHERE " E_CACHE_COLUMN_UID " = %Q AND " E_CACHE_COLUMN_STATE " != %d",
-               e_cache_get_object_cb, &gd, cancellable, error,
-               uid, E_OFFLINE_STATE_LOCALLY_DELETED) &&
-           !gd.object) {
-               g_set_error (error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND, _("Object ā€œ%sā€ not found"), uid);
-       }
+/**
+ * e_cache_get_object_include_deleted:
+ * @cache: an #ECache
+ * @uid: a unique identifier of an object
+ * @out_revision: (out) (nullable) (transfer full): an out variable for a revision
+ *    of the object, or %NULL to ignore
+ * @out_other_columns: (out) (nullable) (transfer full): an out
+ *    variable for #ECacheColumnValues other columns, as defined when creating the @cache, or %NULL to ignore
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * The same as e_cache_get(), only considers also locally deleted objects.
+ *
+ * Returns: (nullable) (transfer full): An object with the given @uid. Free it
+ *    with g_free(), when no longer needed. Returns %NULL on error, like when
+ *    the object could not be found.
+ *
+ * Since: 3.30
+ **/
+gchar *
+e_cache_get_object_include_deleted (ECache *cache,
+                                   const gchar *uid,
+                                   gchar **out_revision,
+                                   ECacheColumnValues **out_other_columns,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+       g_return_val_if_fail (E_IS_CACHE (cache), NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
 
-       return gd.object;
+       return e_cache_get_object_internal (cache, TRUE, uid, out_revision, out_other_columns, cancellable, 
error);
 }
 
 static gboolean
diff --git a/src/libebackend/e-cache.h b/src/libebackend/e-cache.h
index 18b3ed732..3ff428a72 100644
--- a/src/libebackend/e-cache.h
+++ b/src/libebackend/e-cache.h
@@ -409,6 +409,13 @@ gchar *            e_cache_get                     (ECache *cache,
                                                 ECacheColumnValues **out_other_columns,
                                                 GCancellable *cancellable,
                                                 GError **error);
+gchar *                e_cache_get_object_include_deleted
+                                               (ECache *cache,
+                                                const gchar *uid,
+                                                gchar **out_revision,
+                                                ECacheColumnValues **out_other_columns,
+                                                GCancellable *cancellable,
+                                                GError **error);
 gboolean       e_cache_put                     (ECache *cache,
                                                 const gchar *uid,
                                                 const gchar *revision,
diff --git a/tests/libedata-cal/test-cal-meta-backend.c b/tests/libedata-cal/test-cal-meta-backend.c
index 6d30c9071..dc953f656 100644
--- a/tests/libedata-cal/test-cal-meta-backend.c
+++ b/tests/libedata-cal/test-cal-meta-backend.c
@@ -27,6 +27,7 @@
 #include "test-cal-cache-utils.h"
 
 void _e_cal_cache_remove_loaded_timezones (ECalCache *cal_cache); /* e-cal-cache.c, private function */
+void _e_cal_backend_remove_cached_timezones (ECalBackend *cal_backend); /* e-cal-backend.c, private function 
*/
 
 #define EXPECTED_TZID          "/freeassociation.sourceforge.net/America/New_York"
 #define EXPECTED_LOCATION      "America/New_York"
@@ -1177,7 +1178,8 @@ test_empty_cache (TCUFixture *fixture,
        #define TZID "/meta/backend/test/timezone"
        #define TZLOC "test/timezone"
 
-       const gchar *in_tzobj =
+       const gchar *in_vcalobj =
+               "BEGIN:VCALENDAR\r\n"
                "BEGIN:VTIMEZONE\r\n"
                "TZID:" TZID "\r\n"
                "X-LIC-LOCATION:" TZLOC "\r\n"
@@ -1195,7 +1197,18 @@ test_empty_cache (TCUFixture *fixture,
                "TZOFFSETFROM:-0500\r\n"
                "TZOFFSETTO:-0400\r\n"
                "END:DAYLIGHT\r\n"
-               "END:VTIMEZONE\r\n";
+               "END:VTIMEZONE\r\n"
+               "BEGIN:VEVENT\r\n"
+               "UID:test-event\r\n"
+               "DTSTAMP:20170130T000000Z\r\n"
+               "CREATED:20170216T155507Z\r\n"
+               "LAST-MODIFIED:20170216T155543Z\r\n"
+               "SEQUENCE:1\r\n"
+               "DTSTART;TZID=" TZID ":20170209T013000Z\r\n"
+               "DTEND;TZID=" TZID ":20170209T030000Z\r\n"
+               "SUMMARY:Test Event\r\n"
+               "END:VEVENT\r\n"
+               "END:VCALENDAR\r\n";
        ECalBackendSyncClass *backend_class;
        ECalMetaBackend *meta_backend;
        GList *zones;
@@ -1207,10 +1220,10 @@ test_empty_cache (TCUFixture *fixture,
 
        backend_class = E_CAL_BACKEND_SYNC_GET_CLASS (meta_backend);
        g_return_if_fail (backend_class != NULL);
-       g_return_if_fail (backend_class->add_timezone_sync != NULL);
+       g_return_if_fail (backend_class->receive_objects_sync != NULL);
 
-       /* Add timezone to the cache */
-       backend_class->add_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend), NULL, NULL, in_tzobj, &error);
+       /* This adds the object and the used timezone to the cache */
+       backend_class->receive_objects_sync (E_CAL_BACKEND_SYNC (meta_backend), NULL, NULL, in_vcalobj, 
&error);
        g_assert_no_error (error);
 
        zones = NULL;
@@ -1332,34 +1345,82 @@ test_discard_alarm (ECalMetaBackend *meta_backend)
        g_clear_error (&error);
 }
 
+static gboolean
+tcmb_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 gint
+tcmb_get_tzid_ref_count (ECalCache *cal_cache,
+                        const gchar *tzid)
+{
+       guint64 refs = 0;
+       gchar *stmt;
+       gboolean success;
+       GError *error = NULL;
+
+       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 timezones WHERE tzid=%Q", tzid);
+
+       success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt, tcmb_get_uint64_cb, &refs, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       e_cache_sqlite_stmt_free (stmt);
+
+       return (gint) refs;
+}
+
 static void
 test_timezones (ECalMetaBackend *meta_backend)
 {
-       #define TZID "/meta/backend/test/timezone"
-       #define TZLOC "test/timezone"
+       #define TZID1 "/meta/backend/test/timezone1"
+       #define TZLOC1 "test/timezone1"
+       #define TZID2 "/meta/backend/test/timezone2"
+       #define TZLOC2 "test/timezone2"
+       #define TZSTRDEF(id, loc) \
+               "BEGIN:VTIMEZONE\r\n" \
+               "TZID:" id "\r\n" \
+               "X-LIC-LOCATION:" loc "\r\n" \
+               "BEGIN:STANDARD\r\n" \
+               "TZNAME:Test-ST\r\n" \
+               "DTSTART:19701106T020000\r\n" \
+               "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11\r\n" \
+               "TZOFFSETFROM:-0400\r\n" \
+               "TZOFFSETTO:-0500\r\n" \
+               "END:STANDARD\r\n" \
+               "BEGIN:DAYLIGHT\r\n" \
+               "TZNAME:Test-DT\r\n" \
+               "DTSTART:19700313T020000\r\n" \
+               "RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3\r\n" \
+               "TZOFFSETFROM:-0500\r\n" \
+               "TZOFFSETTO:-0400\r\n" \
+               "END:DAYLIGHT\r\n" \
+               "END:VTIMEZONE\r\n"
 
-       const gchar *in_tzobj =
-               "BEGIN:VTIMEZONE\r\n"
-               "TZID:" TZID "\r\n"
-               "X-LIC-LOCATION:" TZLOC "\r\n"
-               "BEGIN:STANDARD\r\n"
-               "TZNAME:Test-ST\r\n"
-               "DTSTART:19701106T020000\r\n"
-               "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11\r\n"
-               "TZOFFSETFROM:-0400\r\n"
-               "TZOFFSETTO:-0500\r\n"
-               "END:STANDARD\r\n"
-               "BEGIN:DAYLIGHT\r\n"
-               "TZNAME:Test-DT\r\n"
-               "DTSTART:19700313T020000\r\n"
-               "RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3\r\n"
-               "TZOFFSETFROM:-0500\r\n"
-               "TZOFFSETTO:-0400\r\n"
-               "END:DAYLIGHT\r\n"
-               "END:VTIMEZONE\r\n";
+       const gchar *in_tz1obj = TZSTRDEF (TZID1, TZLOC1);
+       const gchar *in_tz2obj = TZSTRDEF (TZID2, TZLOC2);
        ECalBackendSyncClass *backend_class;
        ECalCache *cal_cache;
        icalcomponent *vcalendar;
+       ECalComponent *comp;
        gchar *tzobj = NULL;
        GList *zones;
        gboolean success;
@@ -1371,36 +1432,37 @@ test_timezones (ECalMetaBackend *meta_backend)
        g_return_if_fail (backend_class != NULL);
        g_return_if_fail (backend_class->add_timezone_sync != NULL);
        g_return_if_fail (backend_class->get_timezone_sync != NULL);
+       g_return_if_fail (backend_class->get_timezone_sync != NULL);
 
        /* Verify neither TZID, not LOCATION is in the timezone cache */
        backend_class->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
-               NULL, NULL, TZID, &tzobj, &error);
+               NULL, NULL, TZID1, &tzobj, &error);
        g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
        g_assert_null (tzobj);
        g_clear_error (&error);
 
        backend_class->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
-               NULL, NULL, TZLOC, &tzobj, &error);
+               NULL, NULL, TZLOC1, &tzobj, &error);
        g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
        g_assert_null (tzobj);
        g_clear_error (&error);
 
        /* Add it to the cache */
        backend_class->add_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
-               NULL, NULL, in_tzobj, &error);
+               NULL, NULL, in_tz1obj, &error);
        g_assert_no_error (error);
 
        /* Read it back */
        backend_class->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
-               NULL, NULL, TZID, &tzobj, &error);
+               NULL, NULL, TZID1, &tzobj, &error);
        g_assert_no_error (error);
-       g_assert_cmpstr (tzobj, ==, in_tzobj);
+       g_assert_cmpstr (tzobj, ==, in_tz1obj);
        g_free (tzobj);
        tzobj = NULL;
 
        /* As a non-built-in timezone it cannot be read with location, only with TZID */
        backend_class->get_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
-               NULL, NULL, TZLOC, &tzobj, &error);
+               NULL, NULL, TZLOC1, &tzobj, &error);
        g_assert_error (error, E_DATA_CAL_ERROR, ObjectNotFound);
        g_assert_null (tzobj);
        g_clear_error (&error);
@@ -1472,6 +1534,10 @@ test_timezones (ECalMetaBackend *meta_backend)
        success = e_cal_cache_list_timezones (cal_cache, &zones, NULL, &error);
        g_assert_no_error (error);
        g_assert (success);
+       g_assert_cmpint (g_list_length (zones), ==, 0);
+       g_list_free (zones);
+
+       zones = e_timezone_cache_list_timezones (E_TIMEZONE_CACHE (meta_backend));
        g_assert_cmpint (g_list_length (zones), ==, 2);
        g_list_free (zones);
 
@@ -1484,6 +1550,10 @@ test_timezones (ECalMetaBackend *meta_backend)
        success = e_cal_cache_list_timezones (cal_cache, &zones, NULL, &error);
        g_assert_no_error (error);
        g_assert (success);
+       g_assert_cmpint (g_list_length (zones), ==, 0);
+       g_list_free (zones);
+
+       zones = e_timezone_cache_list_timezones (E_TIMEZONE_CACHE (meta_backend));
        g_assert_cmpint (g_list_length (zones), ==, 4);
        g_list_free (zones);
 
@@ -1492,6 +1562,41 @@ test_timezones (ECalMetaBackend *meta_backend)
        g_assert (success);
 
        _e_cal_cache_remove_loaded_timezones (cal_cache);
+       _e_cal_backend_remove_cached_timezones (E_CAL_BACKEND (meta_backend));
+
+       zones = e_timezone_cache_list_timezones (E_TIMEZONE_CACHE (meta_backend));
+       g_assert_cmpint (g_list_length (zones), ==, 0);
+
+       backend_class->add_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, in_tz1obj, &error);
+       g_assert_no_error (error);
+
+       zones = e_timezone_cache_list_timezones (E_TIMEZONE_CACHE (meta_backend));
+       g_assert_cmpint (g_list_length (zones), ==, 1);
+       g_list_free (zones);
+
+       /* Remove existing and add the new */
+       success = e_cal_meta_backend_gather_timezones_sync (meta_backend, vcalendar, TRUE, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       _e_cal_cache_remove_loaded_timezones (cal_cache);
+       _e_cal_backend_remove_cached_timezones (E_CAL_BACKEND (meta_backend));
+
+       zones = e_timezone_cache_list_timezones (E_TIMEZONE_CACHE (meta_backend));
+       g_assert_cmpint (g_list_length (zones), ==, 0);
+
+       icalcomponent_free (vcalendar);
+
+       /* And now when the timezones are actually referenced, thus should be part of the persistent cache */
+
+       backend_class->add_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, in_tz1obj, &error);
+       g_assert_no_error (error);
+
+       backend_class->add_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, in_tz2obj, &error);
+       g_assert_no_error (error);
 
        zones = NULL;
        success = e_cal_cache_list_timezones (cal_cache, &zones, NULL, &error);
@@ -1499,9 +1604,27 @@ test_timezones (ECalMetaBackend *meta_backend)
        g_assert (success);
        g_assert_cmpint (g_list_length (zones), ==, 0);
 
-       backend_class->add_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
-               NULL, NULL, in_tzobj, &error);
+       /* Uses TZID1 twice */
+       comp = e_cal_component_new_from_string (
+               "BEGIN:VEVENT\r\n"
+               "UID:tz1\r\n"
+               "DTSTAMP:20170130T000000Z\r\n"
+               "CREATED:20170216T155507Z\r\n"
+               "LAST-MODIFIED:20170216T155543Z\r\n"
+               "SEQUENCE:1\r\n"
+               "DTSTART;TZID=" TZID1 ":20170209T013000\r\n"
+               "DTEND;TZID=" TZID1 ":20170209T030000\r\n"
+               "SUMMARY:tz1\r\n"
+               "END:VEVENT\r\n");
+       g_assert_nonnull (comp);
+
+       /* Add a component which uses TZID1, thus it's in the cache */
+       success = e_cal_cache_put_component (cal_cache, comp, NULL, E_CACHE_IS_ONLINE, NULL, &error);
        g_assert_no_error (error);
+       g_assert (success);
+
+       g_object_unref (comp);
+       _e_cal_cache_remove_loaded_timezones (cal_cache);
 
        zones = NULL;
        success = e_cal_cache_list_timezones (cal_cache, &zones, NULL, &error);
@@ -1510,11 +1633,32 @@ test_timezones (ECalMetaBackend *meta_backend)
        g_assert_cmpint (g_list_length (zones), ==, 1);
        g_list_free (zones);
 
-       /* Remove existing and add the new */
-       success = e_cal_meta_backend_gather_timezones_sync (meta_backend, vcalendar, TRUE, NULL, &error);
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID1), ==, 2);
+
+       /* Uses TZID1 and TZID2 */
+       comp = e_cal_component_new_from_string (
+               "BEGIN:VEVENT\r\n"
+               "UID:tz2\r\n"
+               "DTSTAMP:20170130T000000Z\r\n"
+               "CREATED:20170216T155507Z\r\n"
+               "LAST-MODIFIED:20170216T155543Z\r\n"
+               "SEQUENCE:1\r\n"
+               "DTSTART;TZID=" TZID2 ":20170209T013000\r\n"
+               "DTEND;TZID=" TZID1 ":20170209T030000\r\n"
+               "SUMMARY:tz2\r\n"
+               "END:VEVENT\r\n");
+       g_assert_nonnull (comp);
+
+       backend_class->add_timezone_sync (E_CAL_BACKEND_SYNC (meta_backend),
+               NULL, NULL, in_tz2obj, &error);
+       g_assert_no_error (error);
+
+       /* Add a component which uses TZID1 and TZID2, thus it's in the cache */
+       success = e_cal_cache_put_component (cal_cache, comp, NULL, E_CACHE_IS_ONLINE, NULL, &error);
        g_assert_no_error (error);
        g_assert (success);
 
+       g_object_unref (comp);
        _e_cal_cache_remove_loaded_timezones (cal_cache);
 
        zones = NULL;
@@ -1524,11 +1668,88 @@ test_timezones (ECalMetaBackend *meta_backend)
        g_assert_cmpint (g_list_length (zones), ==, 2);
        g_list_free (zones);
 
-       icalcomponent_free (vcalendar);
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID1), ==, 3);
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID2), ==, 1);
+
+       /* Remove in offline doesn't modify timezone cache, because the component is still there */
+       success = e_cal_cache_remove_component (cal_cache, "tz1", NULL, E_CACHE_IS_OFFLINE, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID1), ==, 3);
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID2), ==, 1);
+
+       /* Remove in online modifies timezone cache */
+       success = e_cal_cache_remove_component (cal_cache, "tz1", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       _e_cal_cache_remove_loaded_timezones (cal_cache);
+
+       zones = NULL;
+       success = e_cal_cache_list_timezones (cal_cache, &zones, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+       g_assert_cmpint (g_list_length (zones), ==, 2);
+       g_list_free (zones);
+
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID1), ==, 1);
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID2), ==, 1);
+
+       /* Modify tz2 to use only TZID2, TZID1 is removed */
+       comp = e_cal_component_new_from_string (
+               "BEGIN:VEVENT\r\n"
+               "UID:tz2\r\n"
+               "DTSTAMP:20170130T000000Z\r\n"
+               "CREATED:20170216T155507Z\r\n"
+               "LAST-MODIFIED:20170216T155544Z\r\n"
+               "SEQUENCE:2\r\n"
+               "DTSTART;TZID=" TZID2 ":20170209T013000\r\n"
+               "DTEND:20170209T030000Z\r\n"
+               "SUMMARY:tz2\r\n"
+               "END:VEVENT\r\n");
+       g_assert_nonnull (comp);
+
+       success = e_cal_cache_put_component (cal_cache, comp, NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       g_object_unref (comp);
+       _e_cal_cache_remove_loaded_timezones (cal_cache);
+
+       zones = NULL;
+       success = e_cal_cache_list_timezones (cal_cache, &zones, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+       g_assert_cmpint (g_list_length (zones), ==, 1);
+       g_list_free (zones);
+
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID1), ==, 0);
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID2), ==, 1);
+
+       /* Finally remove component straight in online, which removed the only one timezone too */
+       success = e_cal_cache_remove_component (cal_cache, "tz2", NULL, E_CACHE_IS_ONLINE, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       _e_cal_cache_remove_loaded_timezones (cal_cache);
+
+       zones = NULL;
+       success = e_cal_cache_list_timezones (cal_cache, &zones, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+       g_assert_cmpint (g_list_length (zones), ==, 0);
+
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID1), ==, 0);
+       g_assert_cmpint (tcmb_get_tzid_ref_count (cal_cache, TZID2), ==, 0);
+
        g_object_unref (cal_cache);
 
-       #undef TZLOC
-       #undef TZID
+       #undef TZSTRDEF
+       #undef TZLOC2
+       #undef TZID2
+       #undef TZLOC1
+       #undef TZID1
 }
 
 static void


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