[evolution-data-server/wip/offline-cache] Implement ECalBackend/Sync virtual methods



commit 9a32a423912a5cdfc663cb20f0ff21f8f7b8e16e
Author: Milan Crha <mcrha redhat com>
Date:   Fri Mar 3 10:45:01 2017 +0100

    Implement ECalBackend/Sync virtual methods
    
    ...by merging and adapting code used in the CalDAV and the file backends.

 src/calendar/libedata-cal/e-cal-backend.c      |   97 +-
 src/calendar/libedata-cal/e-cal-backend.h      |   24 +
 src/calendar/libedata-cal/e-cal-cache.c        |   70 +-
 src/calendar/libedata-cal/e-cal-cache.h        |    9 +-
 src/calendar/libedata-cal/e-cal-meta-backend.c | 2411 +++++++++++++++++++++++-
 src/calendar/libedata-cal/e-cal-meta-backend.h |   56 +-
 src/libebackend/e-cache.h                      |    4 +-
 src/libedataserver/e-data-server-util.c        |   44 +
 src/libedataserver/e-data-server-util.h        |    3 +
 9 files changed, 2568 insertions(+), 150 deletions(-)
---
diff --git a/src/calendar/libedata-cal/e-cal-backend.c b/src/calendar/libedata-cal/e-cal-backend.c
index 18115e8..4a81c7e 100644
--- a/src/calendar/libedata-cal/e-cal-backend.c
+++ b/src/calendar/libedata-cal/e-cal-backend.c
@@ -68,6 +68,7 @@ struct _ECalBackendPrivate {
        GQueue pending_operations;
        guint32 next_operation_id;
        GSimpleAsyncResult *blocked;
+       gboolean blocked_by_custom_op;
 };
 
 struct _AsyncContext {
@@ -102,6 +103,11 @@ struct _DispatchNode {
 
        GSimpleAsyncResult *simple;
        GCancellable *cancellable;
+
+       GWeakRef *cal_backend_weak_ref;
+       ECalBackendCustomOpFunc custom_func;
+       gpointer custom_func_user_data;
+       GDestroyNotify custom_func_user_data_free;
 };
 
 struct _SignalClosure {
@@ -176,6 +182,12 @@ dispatch_node_free (DispatchNode *dispatch_node)
        g_clear_object (&dispatch_node->simple);
        g_clear_object (&dispatch_node->cancellable);
 
+       if (dispatch_node->custom_func_user_data_free)
+               dispatch_node->custom_func_user_data_free (dispatch_node->custom_func_user_data);
+
+       if (dispatch_node->cal_backend_weak_ref)
+               e_weak_ref_free (dispatch_node->cal_backend_weak_ref);
+
        g_slice_free (DispatchNode, dispatch_node);
 }
 
@@ -223,13 +235,35 @@ cal_backend_push_operation (ECalBackend *backend,
        g_mutex_unlock (&backend->priv->operation_lock);
 }
 
+static void cal_backend_unblock_operations (ECalBackend *backend, GSimpleAsyncResult *simple);
+
 static void
 cal_backend_dispatch_thread (DispatchNode *node)
 {
        GCancellable *cancellable = node->cancellable;
        GError *local_error = NULL;
 
-       if (g_cancellable_set_error_if_cancelled (cancellable, &local_error)) {
+       if (node->custom_func) {
+               ECalBackend *cal_backend;
+
+               cal_backend = g_weak_ref_get (node->cal_backend_weak_ref);
+               if (cal_backend &&
+                   !g_cancellable_is_cancelled (cancellable)) {
+                       node->custom_func (cal_backend, node->custom_func_user_data, cancellable, 
&local_error);
+
+                       if (local_error) {
+                               if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                                       e_cal_backend_notify_error (cal_backend, local_error->message);
+
+                               g_clear_error (&local_error);
+                       }
+               }
+
+               if (cal_backend) {
+                       cal_backend_unblock_operations (cal_backend, NULL);
+                       e_util_unref_in_thread (cal_backend);
+               }
+       } else if (g_cancellable_set_error_if_cancelled (cancellable, &local_error)) {
                g_simple_async_result_take_error (node->simple, local_error);
                g_simple_async_result_complete_in_idle (node->simple);
        } else {
@@ -254,7 +288,8 @@ cal_backend_dispatch_next_operation (ECalBackend *backend)
 
        /* We can't dispatch additional operations
         * while a blocking operation is in progress. */
-       if (backend->priv->blocked != NULL) {
+       if (backend->priv->blocked != NULL ||
+           backend->priv->blocked_by_custom_op) {
                g_mutex_unlock (&backend->priv->operation_lock);
                return FALSE;
        }
@@ -268,8 +303,12 @@ cal_backend_dispatch_next_operation (ECalBackend *backend)
 
        /* If this a blocking operation, block any
         * further dispatching until this finishes. */
-       if (node->blocking_operation)
-               backend->priv->blocked = g_object_ref (node->simple);
+       if (node->blocking_operation) {
+               if (node->simple)
+                       backend->priv->blocked = g_object_ref (node->simple);
+               else
+                       backend->priv->blocked_by_custom_op = TRUE;
+       }
 
        g_mutex_unlock (&backend->priv->operation_lock);
 
@@ -291,6 +330,7 @@ cal_backend_unblock_operations (ECalBackend *backend,
        g_mutex_lock (&backend->priv->operation_lock);
        if (backend->priv->blocked == simple)
                g_clear_object (&backend->priv->blocked);
+       backend->priv->blocked_by_custom_op = FALSE;
        g_mutex_unlock (&backend->priv->operation_lock);
 
        while (cal_backend_dispatch_next_operation (backend))
@@ -4574,3 +4614,52 @@ e_cal_backend_prepare_for_completion (ECalBackend *backend,
        return simple;
 }
 
+/**
+ * e_cal_backend_schedule_custom_operation:
+ * @backend: an #ECalBackend
+ * @use_cancellable: (nullable): an optional #GCancellable to use for @func
+ * @func: a function to call in a dedicated thread
+ * @user_data: user data being passed to @func
+ * @user_data_free: (nullable): optional destroy call back for @user_data
+ *
+ * Schedules user function @func to be run in a dedicated thread as
+ * a blocking operation.
+ *
+ * The function adds its own reference to @use_cancellable, if not %NULL.
+ *
+ * The error returned from @func is propagated to client using
+ * e_cal_backend_notify_error() function. If it's not desired,
+ * then left the error unchanged and notify about errors manually.
+ *
+ * Since: 3.26
+ **/
+void
+e_cal_backend_schedule_custom_operation (ECalBackend *backend,
+                                        GCancellable *use_cancellable,
+                                        ECalBackendCustomOpFunc func,
+                                        gpointer user_data,
+                                        GDestroyNotify user_data_free)
+{
+       DispatchNode *node;
+
+       g_return_if_fail (E_IS_CAL_BACKEND (backend));
+       g_return_if_fail (func != NULL);
+
+       g_mutex_lock (&backend->priv->operation_lock);
+
+       node = g_slice_new0 (DispatchNode);
+       node->blocking_operation = TRUE;
+       node->cal_backend_weak_ref = e_weak_ref_new (backend);
+       node->custom_func = func;
+       node->custom_func_user_data = user_data;
+       node->custom_func_user_data_free = user_data_free;
+
+       if (G_IS_CANCELLABLE (use_cancellable))
+               node->cancellable = g_object_ref (use_cancellable);
+
+       g_queue_push_tail (&backend->priv->pending_operations, node);
+
+       g_mutex_unlock (&backend->priv->operation_lock);
+
+       cal_backend_dispatch_next_operation (backend);
+}
diff --git a/src/calendar/libedata-cal/e-cal-backend.h b/src/calendar/libedata-cal/e-cal-backend.h
index e7bc505..51d3c71 100644
--- a/src/calendar/libedata-cal/e-cal-backend.h
+++ b/src/calendar/libedata-cal/e-cal-backend.h
@@ -529,6 +529,30 @@ GSimpleAsyncResult *
                                                 guint opid,
                                                 GQueue **result_queue);
 
+/**
+ * ECalBackendCustomOpFunc:
+ * @cal_backend: an #ECalBackend
+ * @user_data: a function user data, as provided to e_cal_backend_schedule_custom_operation()
+ * @cancellable: an optional #GCancellable, as provided to e_cal_backend_schedule_custom_operation()
+ * @error: return location for a #GError, or %NULL
+ *
+ * A callback prototype being called in a dedicated thread, scheduled
+ * by e_cal_backend_schedule_custom_operation().
+ *
+ * Since: 3.26
+ **/
+typedef void   (* ECalBackendCustomOpFunc)     (ECalBackend *cal_backend,
+                                                gpointer user_data,
+                                                GCancellable *cancellable,
+                                                GError **error);
+
+void           e_cal_backend_schedule_custom_operation
+                                               (ECalBackend *backend,
+                                                GCancellable *use_cancellable,
+                                                ECalBackendCustomOpFunc func,
+                                                gpointer user_data,
+                                                GDestroyNotify user_data_free);
+
 G_END_DECLS
 
 #endif /* E_CAL_BACKEND_H */
diff --git a/src/calendar/libedata-cal/e-cal-cache.c b/src/calendar/libedata-cal/e-cal-cache.c
index 6b9933b..b51f848 100644
--- a/src/calendar/libedata-cal/e-cal-cache.c
+++ b/src/calendar/libedata-cal/e-cal-cache.c
@@ -1855,7 +1855,7 @@ e_cal_cache_put_components (ECalCache *cal_cache,
        const GSList *clink, *elink;
        ECache *cache;
        ECacheColumnValues *other_columns;
-       gboolean success;
+       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);
@@ -1918,9 +1918,9 @@ e_cal_cache_put_components (ECalCache *cal_cache,
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
- * Removes a component idenitified by @uid and @rid from the @cal_cache.
+ * 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 and all detached instances identified by @uid.
+ * object only, without any detached instance.
  *
  * Returns: Whether succeeded.
  *
@@ -1960,9 +1960,9 @@ e_cal_cache_remove_component (ECalCache *cal_cache,
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
- * Removes components idenitified by @uid and @rid from the @cal_cache
+ * 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 and all detached instances identified by @uid.
+ * removes the master object only, without any detached instance.
  *
  * Returns: Whether succeeded.
  *
@@ -2679,8 +2679,8 @@ e_cal_cache_search_with_callback (ECalCache *cal_cache,
  * @error: return location for a #GError, or %NULL
  *
  * Puts the @zone into the @cal_cache using its timezone ID as
- * an identificator. The function does nothing if any such already
- * exists in the @cal_cache.
+ * an identificator. The function adds a new or replaces existing,
+ * if any such already exists in the @cal_cache.
  *
  * Returns: Whether succeeded.
  *
@@ -2688,7 +2688,7 @@ e_cal_cache_search_with_callback (ECalCache *cal_cache,
  **/
 gboolean
 e_cal_cache_put_timezone (ECalCache *cal_cache,
-                         icaltimezone *zone,
+                         const icaltimezone *zone,
                          GCancellable *cancellable,
                          GError **error)
 {
@@ -2701,13 +2701,13 @@ e_cal_cache_put_timezone (ECalCache *cal_cache,
        g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
        g_return_val_if_fail (zone != NULL, FALSE);
 
-       tzid = icaltimezone_get_tzid (zone);
+       tzid = icaltimezone_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;
        }
 
-       component = icaltimezone_get_component (zone);
+       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"));
                return FALSE;
@@ -2970,6 +2970,56 @@ e_cal_cache_list_timezones (ECalCache *cal_cache,
        return success;
 }
 
+/**
+ * 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. See also
+ * e_cal_cache_resolve_timezone_simple_cb().
+ *
+ * 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_cal_cache_resolve_timezone_simple_cb (tzid, cal_cache);
+}
+
+/**
+ * e_cal_cache_resolve_timezone_simple_cb:
+ * @tzid: a timezone ID
+ * @cal_cache: an #ECalCache
+ *
+ * An #ECalRecurResolveTimezoneFn callback, which can be used
+ * with e_cal_recur_ensure_end_dates() and simialr functions.
+ * The @cal_cache is supposed to be an #ECalCache instance. See
+ * also e_cal_cache_resolve_timezone_cb().
+ *
+ * Returns: (transfer none) (nullable): the resolved icaltimezone, or %NULL, if not found
+ *
+ * Since: 3.26
+ **/
+icaltimezone *
+e_cal_cache_resolve_timezone_simple_cb (const gchar *tzid,
+                                       gpointer cal_cache)
+{
+       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_empty_aux_tables (ECache *cache,
                      GCancellable *cancellable,
diff --git a/src/calendar/libedata-cal/e-cal-cache.h b/src/calendar/libedata-cal/e-cal-cache.h
index 053c9f2..4f80a79 100644
--- a/src/calendar/libedata-cal/e-cal-cache.h
+++ b/src/calendar/libedata-cal/e-cal-cache.h
@@ -260,7 +260,7 @@ gboolean    e_cal_cache_search_with_callback
                                                 GError **error);
 
 gboolean       e_cal_cache_put_timezone        (ECalCache *cal_cache,
-                                                icaltimezone *zone,
+                                                const icaltimezone *zone,
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_cal_cache_get_timezone        (ECalCache *cal_cache,
@@ -278,6 +278,13 @@ gboolean   e_cal_cache_list_timezones      (ECalCache *cal_cache,
                                                 GList **out_timezones,
                                                 GCancellable *cancellable,
                                                 GError **error);
+icaltimezone * e_cal_cache_resolve_timezone_cb (const gchar *tzid,
+                                                gpointer cal_cache,
+                                                GCancellable *cancellable,
+                                                GError **error);
+icaltimezone * e_cal_cache_resolve_timezone_simple_cb
+                                               (const gchar *tzid,
+                                                gpointer cal_cache);
 
 G_END_DECLS
 
diff --git a/src/calendar/libedata-cal/e-cal-meta-backend.c b/src/calendar/libedata-cal/e-cal-meta-backend.c
index 7173b16..b66d5d9 100644
--- a/src/calendar/libedata-cal/e-cal-meta-backend.c
+++ b/src/calendar/libedata-cal/e-cal-meta-backend.c
@@ -38,7 +38,9 @@
 #include <glib.h>
 #include <glib/gi18n-lib.h>
 
-#include "e-cal-backend.h"
+#include "e-cal-backend-sexp.h"
+#include "e-cal-backend-sync.h"
+#include "e-cal-backend-util.h"
 #include "e-cal-meta-backend.h"
 
 #define LOCAL_PREFIX "file://"
@@ -46,6 +48,15 @@
 struct _ECalMetaBackendPrivate {
        GMutex property_lock;
        ECalCache *cache;
+       GHashTable *view_cancellables;
+       GCancellable *refresh_cancellable;      /* Set when refreshing the content */
+       GCancellable *source_changed_cancellable; /* Set when processing source changed signal */
+       GCancellable *go_offline_cancellable;   /* Set when going offline */
+       gboolean current_online_state;          /* The only state of the internal structures;
+                                                  used to detect false notifications on EBackend::online */
+       gulong source_changed_id;
+       gulong notify_online_id;
+       guint refresh_timeout_id;
 };
 
 enum {
@@ -53,10 +64,14 @@ enum {
        PROP_CACHE
 };
 
-G_DEFINE_ABSTRACT_TYPE (ECalMetaBackend, e_cal_meta_backend, E_TYPE_CAL_BACKEND)
+G_DEFINE_ABSTRACT_TYPE (ECalMetaBackend, e_cal_meta_backend, E_TYPE_CAL_BACKEND_SYNC)
 
 G_DEFINE_BOXED_TYPE (ECalMetaBackendInfo, e_cal_meta_backend_info, e_cal_meta_backend_info_copy, 
e_cal_meta_backend_info_free)
 
+static void ecmb_schedule_refresh (ECalMetaBackend *meta_backend);
+static void ecmb_schedule_source_changed (ECalMetaBackend *meta_backend);
+static void ecmb_schedule_go_offline (ECalMetaBackend *meta_backend);
+
 /**
  * e_cal_cache_search_data_new:
  * @uid: a component UID; cannot be %NULL
@@ -128,6 +143,2124 @@ e_cal_meta_backend_info_free (gpointer ptr)
        }
 }
 
+/* Unref returned cancellable with g_object_unref(), when done with it */
+static GCancellable *
+ecmb_create_view_cancellable (ECalMetaBackend *meta_backend,
+                             EDataCalView *view)
+{
+       GCancellable *cancellable;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), NULL);
+       g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
+
+       g_mutex_lock (&meta_backend->priv->property_lock);
+
+       cancellable = g_cancellable_new ();
+       g_hash_table_insert (meta_backend->priv->view_cancellables, view, g_object_ref (cancellable));
+
+       g_mutex_unlock (&meta_backend->priv->property_lock);
+
+       return cancellable;
+}
+
+static GCancellable *
+ecmb_steal_view_cancellable (ECalMetaBackend *meta_backend,
+                            EDataCalView *view)
+{
+       GCancellable *cancellable;
+
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), NULL);
+       g_return_val_if_fail (E_IS_DATA_CAL_VIEW (view), NULL);
+
+       g_mutex_lock (&meta_backend->priv->property_lock);
+
+       cancellable = g_hash_table_lookup (meta_backend->priv->view_cancellables, view);
+       if (cancellable) {
+               g_object_ref (cancellable);
+               g_hash_table_remove (meta_backend->priv->view_cancellables, view);
+       }
+
+       g_mutex_unlock (&meta_backend->priv->property_lock);
+
+       return cancellable;
+}
+
+static gboolean
+ecmb_get_changes_sync (ECalMetaBackend *meta_backend,
+                      const gchar *last_sync_tag,
+                      gchar **out_new_sync_tag,
+                      gboolean *out_repeat,
+                      GSList **out_created_objects,
+                      GSList **out_modified_objects,
+                      GSList **out_removed_objects,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       return FALSE;
+}
+
+static void
+ecmb_start_view_thread_func (ECalBackend *cal_backend,
+                            gpointer user_data,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       EDataCalView *view = user_data;
+       ECalBackendSExp *sexp;
+       ECalCache *cal_cache;
+       const gchar *expr = NULL;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (cal_backend));
+       g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       /* Fill the view with known (locally stored) components satisfying the expression */
+       sexp = e_data_cal_view_get_sexp (view);
+       if (sexp)
+               expr = e_cal_backend_sexp_text (sexp);
+
+       cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (cal_backend));
+       if (cal_cache) {
+               GSList *components = NULL;
+
+               if (e_cal_cache_search_components (cal_cache, expr, &components, cancellable, error) && 
components) {
+                       if (!g_cancellable_is_cancelled (cancellable))
+                               e_data_cal_view_notify_components_added (view, components);
+
+                       g_slist_free_full (components, g_object_unref);
+               }
+
+               g_object_unref (cal_cache);
+       }
+}
+
+static void
+ecmb_refresh_thread_func (ECalBackend *cal_backend,
+                         gpointer user_data,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       ECalMetaBackend *meta_backend;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (cal_backend));
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       meta_backend = E_CAL_META_BACKEND (cal_backend);
+
+       if (!e_backend_get_online (E_BACKEND (meta_backend)))
+               return;
+}
+
+static void
+ecmb_source_refresh_timeout_cb (ESource *source,
+                               gpointer user_data)
+{
+       GWeakRef *weak_ref = user_data;
+       ECalMetaBackend *meta_backend;
+
+       g_return_if_fail (weak_ref != NULL);
+
+       meta_backend = g_weak_ref_get (weak_ref);
+       if (meta_backend) {
+               ecmb_schedule_refresh (meta_backend);
+               g_object_unref (meta_backend);
+       }
+}
+
+static void
+ecmb_source_changed_thread_func (ECalBackend *cal_backend,
+                                gpointer user_data,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       ECalMetaBackend *meta_backend;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (cal_backend));
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       meta_backend = E_CAL_META_BACKEND (cal_backend);
+
+       g_mutex_lock (&meta_backend->priv->property_lock);
+       if (!meta_backend->priv->refresh_timeout_id) {
+               ESource *source = e_backend_get_source (E_BACKEND (meta_backend));
+
+               if (e_source_has_extension (source, E_SOURCE_EXTENSION_REFRESH)) {
+                       meta_backend->priv->refresh_timeout_id = e_source_refresh_add_timeout (source, NULL,
+                               ecmb_source_refresh_timeout_cb, e_weak_ref_new (meta_backend), 
(GDestroyNotify) e_weak_ref_free);
+               }
+       }
+       g_mutex_unlock (&meta_backend->priv->property_lock);
+
+       if (e_backend_get_online (E_BACKEND (meta_backend)) &&
+           e_cal_meta_backend_disconnect_sync (meta_backend, cancellable, error)) {
+               ecmb_schedule_refresh (meta_backend);
+       }
+}
+
+static void
+ecmb_go_offline_thread_func (ECalBackend *cal_backend,
+                            gpointer user_data,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       g_return_if_fail (E_IS_CAL_META_BACKEND (cal_backend));
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       e_cal_meta_backend_disconnect_sync (E_CAL_META_BACKEND (cal_backend), cancellable, error);
+}
+
+static ECalComponent *
+ecmb_find_in_instances (const GSList *instances, /* ECalComponent * */
+                       const gchar *uid,
+                       const gchar *rid)
+{
+       GSList *link;
+
+       for (link = (GSList *) instances; link; link = g_slist_next (link)) {
+               ECalComponent *comp = link->data;
+               ECalComponentId *id;
+
+               if (!comp)
+                       continue;
+
+               id = e_cal_component_get_id (comp);
+               if (!id)
+                       continue;
+
+               if (g_strcmp0 (id->uid, uid) == 0 &&
+                   g_strcmp0 (id->rid, rid) == 0) {
+                       e_cal_component_free_id (id);
+                       return comp;
+               }
+
+               e_cal_component_free_id (id);
+       }
+
+       return NULL;
+}
+
+static gboolean
+ecmb_maybe_remove_from_cache (ECalMetaBackend *meta_backend,
+                             ECalCache *cal_cache,
+                             ECacheOfflineFlag offline_flag,
+                             const gchar *uid,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       ECalBackend *cal_backend;
+       GSList *comps = NULL, *link;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (uid != NULL, FALSE);
+
+       if (!e_cal_cache_get_components_by_uid (cal_cache, uid, &comps, cancellable, &local_error)) {
+               if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
+                       g_clear_error (&local_error);
+                       return TRUE;
+               }
+
+               g_propagate_error (error, local_error);
+               return FALSE;
+       }
+
+       cal_backend = E_CAL_BACKEND (meta_backend);
+
+       for (link = comps; link; link = g_slist_next (link)) {
+               ECalComponent *comp = link->data;
+               ECalComponentId *id;
+
+               g_warn_if_fail (E_IS_CAL_COMPONENT (comp));
+
+               if (!E_IS_CAL_COMPONENT (comp))
+                       continue;
+
+               id = e_cal_component_get_id (comp);
+               if (id) {
+                       if (!e_cal_cache_remove_component (cal_cache, id->uid, id->rid, offline_flag, 
cancellable, error)) {
+                               e_cal_component_free_id (id);
+                               g_slist_free_full (comps, g_object_unref);
+
+                               return FALSE;
+                       }
+
+                       e_cal_backend_notify_component_removed (cal_backend, id, comp, NULL);
+                       e_cal_component_free_id (id);
+               }
+       }
+
+       g_slist_free_full (comps, g_object_unref);
+
+       return TRUE;
+}
+
+static gboolean
+ecmb_put_one_component (ECalMetaBackend *meta_backend,
+                       ECalCache *cal_cache,
+                       ECacheOfflineFlag offline_flag,
+                       ECalComponent *comp,
+                       const gchar *extra,
+                       GSList **inout_cache_instances,
+                       GCancellable *cancellable,
+                       GError **error)
+{
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (comp != NULL, FALSE);
+       g_return_val_if_fail (inout_cache_instances != NULL, FALSE);
+
+       if (e_cal_component_has_attachments (comp)) {
+               success = e_cal_meta_backend_store_inline_attachments_sync (meta_backend,
+                       e_cal_component_get_icalcomponent (comp), cancellable, error);
+               e_cal_component_rescan (comp);
+       }
+
+       success = success && e_cal_cache_put_component (cal_cache, comp, extra, offline_flag, cancellable, 
error);
+
+       if (success) {
+               ECalComponent *existing = NULL;
+               ECalComponentId *id;
+
+               id = e_cal_component_get_id (comp);
+               if (id) {
+                       existing = ecmb_find_in_instances (*inout_cache_instances, id->uid, id->rid);
+
+                       e_cal_component_free_id (id);
+               }
+
+               if (existing) {
+                       e_cal_backend_notify_component_modified (E_CAL_BACKEND (meta_backend), existing, 
comp);
+                       *inout_cache_instances = g_slist_remove (*inout_cache_instances, existing);
+
+                       g_clear_object (&existing);
+               } else {
+                       e_cal_backend_notify_component_created (E_CAL_BACKEND (meta_backend), comp);
+               }
+       }
+
+       return success;
+}
+
+static gboolean
+ecmb_put_instances (ECalMetaBackend *meta_backend,
+                   ECalCache *cal_cache,
+                   const gchar *uid,
+                   ECacheOfflineFlag offline_flag,
+                   const GSList *new_instances, /* ECalComponent * */
+                   const gchar *extra,
+                   GCancellable *cancellable,
+                   GError **error)
+{
+       GSList *cache_instances = NULL, *link;
+       gboolean success = TRUE;
+       GError *local_error = NULL;
+
+       if (!e_cal_cache_get_components_by_uid (cal_cache, uid, &cache_instances, cancellable, &local_error)) 
{
+               if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
+                       g_clear_error (&local_error);
+               } else {
+                       g_propagate_error (error, local_error);
+
+                       return FALSE;
+               }
+       }
+
+       for (link = (GSList *) new_instances; link && success; link = g_slist_next (link)) {
+               ECalComponent *comp = link->data;
+
+               success = ecmb_put_one_component (meta_backend, cal_cache, offline_flag, comp, extra, 
&cache_instances, cancellable, error);
+       }
+
+       /* What left got removed from the remote side, notify about it */
+       if (success && cache_instances) {
+               ECalBackend *cal_backend = E_CAL_BACKEND (meta_backend);
+               GSList *link;
+
+               for (link = cache_instances; link && success; link = g_slist_next (link)) {
+                       ECalComponent *comp = link->data;
+                       ECalComponentId *id;
+
+                       id = e_cal_component_get_id (comp);
+                       if (!id)
+                               continue;
+
+                       success = e_cal_cache_remove_component (cal_cache, id->uid, id->rid, offline_flag, 
cancellable, error);
+
+                       e_cal_backend_notify_component_removed (cal_backend, id, comp, NULL);
+
+                       e_cal_component_free_id (id);
+               }
+       }
+
+       g_slist_free_full (cache_instances, g_object_unref);
+
+       return success;
+}
+
+static void
+ecmb_gather_timezones (ECalMetaBackend *meta_backend,
+                      ETimezoneCache *timezone_cache,
+                      icalcomponent *icalcomp)
+{
+       icalcomponent *subcomp;
+       icaltimezone *zone;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (meta_backend));
+       g_return_if_fail (E_IS_TIMEZONE_CACHE (timezone_cache));
+       g_return_if_fail (icalcomp != NULL);
+
+       zone = icaltimezone_new ();
+
+       for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VTIMEZONE_COMPONENT);
+            subcomp;
+            subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VTIMEZONE_COMPONENT)) {
+               icalcomponent *clone;
+
+               clone = icalcomponent_new_clone (subcomp);
+
+               if (icaltimezone_set_component (zone, clone)) {
+                       e_timezone_cache_add_timezone (timezone_cache, zone);
+               } else {
+                       icalcomponent_free (clone);
+               }
+       }
+
+       icaltimezone_free (zone, TRUE);
+}
+
+static gboolean
+ecmb_load_component_wrapper_sync (ECalMetaBackend *meta_backend,
+                                 ECalCache *cal_cache,
+                                 const gchar *uid,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       ECacheOfflineFlag offline_flag = E_CACHE_IS_ONLINE;
+       icalcomponent *icalcomp = NULL;
+       GSList *new_instances = NULL;
+       gchar *extra = NULL;
+       gboolean success = TRUE;
+
+       if (!e_cal_meta_backend_load_component_sync (meta_backend, uid, &icalcomp, &extra, cancellable, 
error) ||
+           !icalcomp) {
+               g_free (extra);
+               return FALSE;
+       }
+
+       if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
+               icalcomponent_kind kind;
+               icalcomponent *subcomp;
+
+               ecmb_gather_timezones (meta_backend, E_TIMEZONE_CACHE (cal_cache), icalcomp);
+
+               kind = e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend));
+
+               for (subcomp = icalcomponent_get_first_component (icalcomp, kind);
+                    subcomp && success;
+                    subcomp = icalcomponent_get_next_component (icalcomp, kind)) {
+                       ECalComponent *comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone 
(subcomp));
+
+                       if (comp)
+                               new_instances = g_slist_prepend (new_instances, comp);
+               }
+       } else {
+               ECalComponent *comp = e_cal_component_new_from_icalcomponent (icalcomp);
+
+               icalcomp = NULL;
+
+               if (comp)
+                       new_instances = g_slist_prepend (new_instances, comp);
+       }
+
+       if (new_instances) {
+               new_instances = g_slist_reverse (new_instances);
+
+               success = ecmb_put_instances (meta_backend, cal_cache, uid, offline_flag,
+                       new_instances, extra, cancellable, error);
+       } else {
+               g_propagate_error (error, e_data_cal_create_error (InvalidObject, _("Received object is 
invalid")));
+               success = FALSE;
+       }
+
+       g_slist_free_full (new_instances, g_object_unref);
+       if (icalcomp)
+               icalcomponent_free (icalcomp);
+       g_free (extra);
+
+       return success;
+}
+
+static gboolean
+ecmb_save_component_wrapper_sync (ECalMetaBackend *meta_backend,
+                                 gboolean overwrite_existing,
+                                 EConflictResolution conflict_resolution,
+                                 const GSList *in_instances,
+                                 const gchar *extra,
+                                 gchar **out_new_uid,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       GSList *link, *instances = NULL;
+       gboolean has_attachments = FALSE, success = TRUE;
+
+       for (link = (GSList *) in_instances; link && !has_attachments; link = g_slist_next (link)) {
+               has_attachments = e_cal_component_has_attachments (link->data);
+       }
+
+       if (has_attachments) {
+               instances = g_slist_copy ((GSList *) in_instances);
+
+               for (link = instances; link; link = g_slist_next (link)) {
+                       ECalComponent *comp = link->data;
+
+                       if (success && e_cal_component_has_attachments (comp)) {
+                               comp = e_cal_component_clone (comp);
+                               link->data = comp;
+
+                               success = e_cal_meta_backend_inline_local_attachments_sync (meta_backend,
+                                       e_cal_component_get_icalcomponent (comp), cancellable, error);
+                               e_cal_component_rescan (comp);
+                       } else {
+                               g_object_ref (comp);
+                       }
+               }
+       }
+
+       success = success && e_cal_meta_backend_save_component_sync (meta_backend, overwrite_existing, 
conflict_resolution,
+               instances ? instances : in_instances, extra, out_new_uid, cancellable, error);
+
+       g_slist_free_full (instances, g_object_unref);
+
+       return success;
+}
+
+static void
+ecmb_open_sync (ECalBackendSync *sync_backend,
+               EDataCal *cal,
+               GCancellable *cancellable,
+               gboolean only_if_exists,
+               GError **error)
+{
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+
+       /* Not much to do here, just confirm and schedule refresh */
+       ecmb_schedule_refresh (E_CAL_META_BACKEND (sync_backend));
+}
+
+static void
+ecmb_refresh_sync (ECalBackendSync *sync_backend,
+                  EDataCal *cal,
+                  GCancellable *cancellable,
+                  GError **error)
+
+{
+       ECalMetaBackend *meta_backend;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+
+       meta_backend = E_CAL_META_BACKEND (sync_backend);
+
+       if (!e_backend_get_online (E_BACKEND (sync_backend)))
+               return;
+
+       if (e_cal_meta_backend_connect_sync (meta_backend, cancellable, error))
+               ecmb_schedule_refresh (meta_backend);
+}
+
+static void
+ecmb_get_object_sync (ECalBackendSync *sync_backend,
+                     EDataCal *cal,
+                     GCancellable *cancellable,
+                     const gchar *uid,
+                     const gchar *rid,
+                     gchar **calobj,
+                     GError **error)
+{
+       ECalMetaBackend *meta_backend;
+       ECalCache *cal_cache;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (uid != NULL);
+       g_return_if_fail (calobj != NULL);
+
+       meta_backend = E_CAL_META_BACKEND (sync_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+
+       if (!cal_cache) {
+               g_propagate_error (error, e_data_cal_create_error (OtherError, NULL));
+               return;
+       }
+
+       if (!e_cal_cache_get_component_as_string (cal_cache, uid, rid, calobj, cancellable, &local_error) &&
+           g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
+               gboolean found = FALSE;
+
+               g_clear_error (&local_error);
+
+               /* Ignore errors here, just try whether it's on the remote side, but not in the local cache */
+               if (e_backend_get_online (E_BACKEND (meta_backend)) &&
+                   e_cal_meta_backend_connect_sync (meta_backend, cancellable, NULL) &&
+                   ecmb_load_component_wrapper_sync (meta_backend, cal_cache, uid, cancellable, NULL)) {
+                       found = e_cal_cache_get_component_as_string (cal_cache, uid, rid, calobj, 
cancellable, NULL);
+               }
+
+               if (!found)
+                       g_propagate_error (error, e_data_cal_create_error (ObjectNotFound, NULL));
+       } else if (local_error) {
+               g_propagate_error (error, e_data_cal_create_error (OtherError, local_error->message));
+               g_clear_error (&local_error);
+       }
+
+       g_object_unref (cal_cache);
+}
+
+static void
+ecmb_get_object_list_sync (ECalBackendSync *sync_backend,
+                          EDataCal *cal,
+                          GCancellable *cancellable,
+                          const gchar *sexp,
+                          GSList **calobjs,
+                          GError **error)
+{
+       ECalCache *cal_cache;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (calobjs != NULL);
+
+       *calobjs = NULL;
+       cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (sync_backend));
+
+       if (!cal_cache) {
+               g_propagate_error (error, e_data_cal_create_error (OtherError, NULL));
+               return;
+       }
+
+       if (e_cal_cache_search (cal_cache, sexp, calobjs, cancellable, error)) {
+               GSList *link;
+
+               for (link = *calobjs; link; link = g_slist_next (link)) {
+                       ECalCacheSearchData *search_data = link->data;
+                       gchar *icalstring = NULL;
+
+                       if (search_data) {
+                               icalstring = g_strdup (search_data->object);
+                               e_cal_cache_search_data_free (search_data);
+                       }
+
+                       link->data = icalstring;
+               }
+       }
+
+       g_object_unref (cal_cache);
+}
+
+static gboolean
+ecmb_add_free_busy_instance_cb (icalcomponent *icalcomp,
+                               struct icaltimetype instance_start,
+                               struct icaltimetype instance_end,
+                               gpointer user_data,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+       icalcomponent *vfreebusy = user_data;
+       icalproperty *prop, *classification;
+       icalparameter *param;
+       struct icalperiodtype ipt;
+
+       ipt.start = instance_start;
+       ipt.end = instance_end;
+       ipt.duration = icaldurationtype_null_duration ();
+
+        /* Add busy information to the VFREEBUSY component */
+       prop = icalproperty_new (ICAL_FREEBUSY_PROPERTY);
+       icalproperty_set_freebusy (prop, ipt);
+
+       param = icalparameter_new_fbtype (ICAL_FBTYPE_BUSY);
+       icalproperty_add_parameter (prop, param);
+
+       classification = icalcomponent_get_first_property (icalcomp, ICAL_CLASS_PROPERTY);
+       if (!classification || icalproperty_get_class (classification) == ICAL_CLASS_PUBLIC) {
+               const gchar *str;
+
+               str = icalcomponent_get_summary (icalcomp);
+               if (str && *str) {
+                       param = icalparameter_new_x (str);
+                       icalparameter_set_xname (param, "X-SUMMARY");
+                       icalproperty_add_parameter (prop, param);
+               }
+
+               str = icalcomponent_get_location (icalcomp);
+               if (str && *str) {
+                       param = icalparameter_new_x (str);
+                       icalparameter_set_xname (param, "X-LOCATION");
+                       icalproperty_add_parameter (prop, param);
+               }
+       }
+
+       icalcomponent_add_property (vfreebusy, prop);
+
+       return TRUE;
+}
+
+static void
+ecmb_get_free_busy_sync (ECalBackendSync *sync_backend,
+                        EDataCal *cal,
+                        GCancellable *cancellable,
+                        const GSList *users,
+                        time_t start,
+                        time_t end,
+                        GSList **out_freebusy,
+                        GError **error)
+{
+       ECalMetaBackend *meta_backend;
+       ECalCache *cal_cache;
+       GSList *link, *components = NULL;
+       gchar *cal_email_address;
+       icalcomponent *vfreebusy, *icalcomp;
+       icalproperty *prop;
+       icaltimezone *utc_zone;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (out_freebusy != NULL);
+
+       meta_backend = E_CAL_META_BACKEND (sync_backend);
+
+       *out_freebusy = NULL;
+
+       if (!users)
+               return;
+
+       cal_email_address = e_cal_backend_get_backend_property (E_CAL_BACKEND (meta_backend), 
CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS);
+       if (!cal_email_address)
+               return;
+
+       for (link = (GSList *) users; link; link = g_slist_next (link)) {
+               const gchar *user = link->data;
+
+               if (user && g_ascii_strcasecmp (user, cal_email_address) == 0)
+                       break;
+       }
+
+       if (!link) {
+               g_free (cal_email_address);
+               return;
+       }
+
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+
+       if (!e_cal_cache_get_components_in_range (cal_cache, start, end, &components, cancellable, error)) {
+               g_clear_object (&cal_cache);
+               g_free (cal_email_address);
+               return;
+       }
+
+       vfreebusy = icalcomponent_new_vfreebusy ();
+       prop = icalproperty_new_organizer (cal_email_address);
+       if (prop != NULL)
+               icalcomponent_add_property (vfreebusy, prop);
+
+       utc_zone = icaltimezone_get_utc_timezone ();
+       icalcomponent_set_dtstart (vfreebusy, icaltime_from_timet_with_zone (start, FALSE, utc_zone));
+       icalcomponent_set_dtend (vfreebusy, icaltime_from_timet_with_zone (end, FALSE, utc_zone));
+
+       for (link = components; link; link = g_slist_next (link)) {
+               ECalComponent *comp = link->data;
+
+               if (!E_IS_CAL_COMPONENT (comp)) {
+                       g_warn_if_reached ();
+                       continue;
+               }
+
+               icalcomp = e_cal_component_get_icalcomponent (comp);
+               if (!icalcomp)
+                       continue;
+
+               /* If the event is TRANSPARENT, skip it. */
+               prop = icalcomponent_get_first_property (icalcomp, ICAL_TRANSP_PROPERTY);
+               if (prop) {
+                       icalproperty_transp transp_val = icalproperty_get_transp (prop);
+                       if (transp_val == ICAL_TRANSP_TRANSPARENT ||
+                           transp_val == ICAL_TRANSP_TRANSPARENTNOCONFLICT)
+                               continue;
+               }
+
+               if (!e_cal_recur_generate_instances_sync (icalcomp,
+                       icaltime_from_timet_with_zone (start, FALSE, NULL),
+                       icaltime_from_timet_with_zone (end, FALSE, NULL),
+                       ecmb_add_free_busy_instance_cb, vfreebusy,
+                       e_cal_cache_resolve_timezone_cb, cal_cache,
+                       utc_zone, cancellable, error)) {
+                       break;
+               }
+       }
+
+       *out_freebusy = g_slist_prepend (*out_freebusy, icalcomponent_as_ical_string_r (vfreebusy));
+
+       g_slist_free_full (components, g_object_unref);
+       icalcomponent_free (vfreebusy);
+       g_free (cal_email_address);
+}
+
+static gboolean
+ecmb_create_object_sync (ECalMetaBackend *meta_backend,
+                        ECalCache *cal_cache,
+                        ECacheOfflineFlag *offline_flag,
+                        EConflictResolution conflict_resolution,
+                        ECalComponent *comp,
+                        gchar **out_new_uid,
+                        ECalComponent **out_new_comp,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       icalcomponent *icalcomp;
+       struct icaltimetype itt;
+       const gchar *uid;
+       gboolean success, requires_put = TRUE;
+
+       g_return_val_if_fail (comp != NULL, FALSE);
+
+       icalcomp = e_cal_component_get_icalcomponent (comp);
+       if (!icalcomp) {
+               g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+               return FALSE;
+       }
+
+       uid = icalcomponent_get_uid (icalcomp);
+       if (!uid) {
+               gchar *new_uid;
+
+               new_uid = e_cal_component_gen_uid ();
+               if (!new_uid) {
+                       g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+                       return FALSE;
+               }
+
+               icalcomponent_set_uid (icalcomp, new_uid);
+               uid = icalcomponent_get_uid (icalcomp);
+
+               g_free (new_uid);
+       }
+
+       if (e_cal_cache_contains (cal_cache, uid, NULL, E_CACHE_EXCLUDE_DELETED)) {
+               g_propagate_error (error, e_data_cal_create_error (ObjectIdAlreadyExists, NULL));
+               return FALSE;
+       }
+
+       /* Set the created and last modified times on the component */
+       itt = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
+       e_cal_component_set_created (comp, &itt);
+       e_cal_component_set_last_modified (comp, &itt);
+
+       if (*offline_flag == E_CACHE_OFFLINE_UNKNOWN) {
+               if (e_backend_get_online (E_BACKEND (meta_backend)) &&
+                   e_cal_meta_backend_connect_sync (meta_backend, cancellable, NULL)) {
+                       *offline_flag = E_CACHE_IS_ONLINE;
+               } else {
+                       *offline_flag = E_CACHE_IS_OFFLINE;
+               }
+       }
+
+       if (*offline_flag == E_CACHE_IS_ONLINE) {
+               GSList *instances;
+               gchar *new_uid = NULL;
+
+               instances = g_slist_prepend (NULL, comp);
+
+               if (!ecmb_save_component_wrapper_sync (meta_backend, FALSE, conflict_resolution, instances, 
NULL, &new_uid, cancellable, error)) {
+                       g_slist_free (instances);
+                       return FALSE;
+               }
+
+               g_slist_free (instances);
+
+               if (new_uid) {
+                       if (!ecmb_load_component_wrapper_sync (meta_backend, cal_cache, new_uid, cancellable, 
error)) {
+                               g_free (new_uid);
+                               return FALSE;
+                       }
+
+                       if (g_strcmp0 (new_uid, uid) != 0 &&
+                           !ecmb_maybe_remove_from_cache (meta_backend, cal_cache, *offline_flag, uid, 
cancellable, error)) {
+                               g_free (new_uid);
+                               return FALSE;
+                       }
+
+                       g_free (new_uid);
+
+                       requires_put = FALSE;
+               }
+       }
+
+       if (requires_put) {
+               success = e_cal_cache_put_component (cal_cache, comp, NULL, *offline_flag, cancellable, 
error);
+               if (success && !out_new_comp) {
+                       e_cal_backend_notify_component_created (E_CAL_BACKEND (meta_backend), comp);
+               }
+       } else {
+               success = TRUE;
+       }
+
+       if (success) {
+               if (out_new_uid)
+                       *out_new_uid = g_strdup (icalcomponent_get_uid (e_cal_component_get_icalcomponent 
(comp)));
+               if (out_new_comp)
+                       *out_new_comp = g_object_ref (comp);
+       }
+
+       return success;
+}
+
+static void
+ecmb_create_objects_sync (ECalBackendSync *sync_backend,
+                         EDataCal *cal,
+                         GCancellable *cancellable,
+                         const GSList *calobjs,
+                         GSList **out_uids,
+                         GSList **out_new_components,
+                         GError **error)
+{
+       ECalMetaBackend *meta_backend;
+       ECalCache *cal_cache;
+       ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
+       EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+       icalcomponent_kind backend_kind;
+       GSList *link;
+       gboolean success = TRUE;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (calobjs != NULL);
+       g_return_if_fail (out_uids != NULL);
+       g_return_if_fail (out_new_components != NULL);
+
+       if (!e_cal_backend_get_writable (E_CAL_BACKEND (sync_backend))) {
+               g_propagate_error (error, e_data_cal_create_error (PermissionDenied, NULL));
+               return;
+       }
+
+       meta_backend = E_CAL_META_BACKEND (sync_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       if (!cal_cache) {
+               g_propagate_error (error, e_data_cal_create_error (OtherError, NULL));
+               return;
+       }
+
+       backend_kind = e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend));
+
+       for (link = (GSList *) calobjs; link && success; link = g_slist_next (link)) {
+               ECalComponent *comp, *new_comp = NULL;
+               gchar *new_uid = NULL;
+
+               if (g_cancellable_set_error_if_cancelled (cancellable, error))
+                       break;
+
+               comp = e_cal_component_new_from_string (link->data);
+               if (!comp ||
+                   !e_cal_component_get_icalcomponent (comp) ||
+                   backend_kind != icalcomponent_isa (e_cal_component_get_icalcomponent (comp))) {
+                       g_clear_object (&comp);
+
+                       g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+                       break;
+               }
+
+               success = ecmb_create_object_sync (meta_backend, cal_cache, &offline_flag, 
conflict_resolution,
+                       comp, &new_uid, &new_comp, cancellable, error);
+
+               if (success) {
+                       *out_uids = g_slist_prepend (*out_uids, new_uid);
+                       *out_new_components = g_slist_prepend (*out_new_components, new_comp);
+               }
+
+               g_object_unref (comp);
+       }
+
+       *out_uids = g_slist_reverse (*out_uids);
+       *out_new_components = g_slist_reverse (*out_new_components);
+
+       g_object_unref (cal_cache);
+}
+
+static gboolean
+ecmb_modify_object_sync (ECalMetaBackend *meta_backend,
+                        ECalCache *cal_cache,
+                        ECacheOfflineFlag *offline_flag,
+                        EConflictResolution conflict_resolution,
+                        ECalObjModType mod,
+                        ECalComponent *comp,
+                        ECalComponent **out_old_comp,
+                        ECalComponent **out_new_comp,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       struct icaltimetype itt;
+       ECalComponentId *id;
+       ECalComponent *old_comp = NULL, *new_comp = NULL, *master_comp, *existing_comp = NULL;
+       GSList *instances = NULL;
+       gchar *extra = NULL;
+       gboolean success = TRUE, requires_put = TRUE;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (comp != NULL, FALSE);
+
+       id = e_cal_component_get_id (comp);
+       if (!id) {
+               g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+               return FALSE;
+       }
+
+       if (!e_cal_cache_get_components_by_uid (cal_cache, id->uid, &instances, cancellable, &local_error)) {
+               if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
+                       g_clear_error (&local_error);
+                       local_error = e_data_cal_create_error (ObjectNotFound, NULL);
+               }
+
+               g_propagate_error (error, local_error);
+               e_cal_component_free_id (id);
+
+               return FALSE;
+       }
+
+       master_comp = ecmb_find_in_instances (instances, id->uid, NULL);
+       if (e_cal_component_is_instance (comp)) {
+               /* Set detached instance as the old object */
+               existing_comp = ecmb_find_in_instances (instances, id->uid, id->rid);
+       }
+
+       if (!existing_comp)
+               existing_comp = master_comp;
+
+       if (!e_cal_cache_get_component_extra (cal_cache, id->uid, id->rid, &extra, cancellable, NULL) && 
id->rid) {
+               if (!e_cal_cache_get_component_extra (cal_cache, id->uid, NULL, &extra, cancellable, NULL))
+                       extra = NULL;
+       }
+
+       /* Set the last modified time on the component */
+       itt = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
+       e_cal_component_set_last_modified (comp, &itt);
+
+       /* Remember old and new components */
+       if (out_old_comp && existing_comp)
+               old_comp = e_cal_component_clone (existing_comp);
+
+       if (out_new_comp)
+               new_comp = e_cal_component_clone (comp);
+
+       switch (mod) {
+       case E_CAL_OBJ_MOD_ONLY_THIS:
+       case E_CAL_OBJ_MOD_THIS:
+               if (e_cal_component_is_instance (comp)) {
+                       if (existing_comp != master_comp) {
+                               instances = g_slist_remove (instances, existing_comp);
+                               g_clear_object (&existing_comp);
+                       }
+               } else {
+                       instances = g_slist_remove (instances, master_comp);
+                       g_clear_object (&master_comp);
+                       existing_comp = NULL;
+               }
+
+               instances = g_slist_append (instances, e_cal_component_clone (comp));
+               break;
+       case E_CAL_OBJ_MOD_ALL:
+               e_cal_recur_ensure_end_dates (comp, TRUE, e_cal_cache_resolve_timezone_simple_cb, cal_cache);
+
+               /* Replace master object */
+               instances = g_slist_remove (instances, master_comp);
+               g_clear_object (&master_comp);
+               existing_comp = NULL;
+
+               instances = g_slist_prepend (instances, e_cal_component_clone (comp));
+               break;
+       case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
+       case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
+               if (e_cal_component_is_instance (comp) && master_comp) {
+                       struct icaltimetype rid, master_dtstart;
+                       icalcomponent *icalcomp = e_cal_component_get_icalcomponent (comp);
+                       icalcomponent *split_icalcomp;
+                       icalproperty *prop;
+
+                       rid = icalcomponent_get_recurrenceid (icalcomp);
+
+                       if (mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE &&
+                           e_cal_util_is_first_instance (master_comp, icalcomponent_get_recurrenceid 
(icalcomp),
+                               e_cal_cache_resolve_timezone_simple_cb, cal_cache)) {
+                               icalproperty *prop = icalcomponent_get_first_property (icalcomp, 
ICAL_RECURRENCEID_PROPERTY);
+
+                               if (prop)
+                                       icalcomponent_remove_property (icalcomp, prop);
+
+                               e_cal_component_rescan (comp);
+
+                               /* Then do it like for "mod_all" */
+                               e_cal_recur_ensure_end_dates (comp, TRUE, 
e_cal_cache_resolve_timezone_simple_cb, cal_cache);
+
+                               /* Replace master */
+                               instances = g_slist_remove (instances, master_comp);
+                               g_clear_object (&master_comp);
+                               existing_comp = NULL;
+
+                               instances = g_slist_prepend (instances, e_cal_component_clone (comp));
+
+                               if (out_new_comp) {
+                                       g_clear_object (&new_comp);
+                                       new_comp = e_cal_component_clone (comp);
+                               }
+                               break;
+                       }
+
+                       prop = icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY);
+                       if (prop)
+                               icalcomponent_remove_property (icalcomp, prop);
+                       e_cal_component_rescan (comp);
+
+                       master_dtstart = icalcomponent_get_dtstart (e_cal_component_get_icalcomponent 
(master_comp));
+                       split_icalcomp = e_cal_util_split_at_instance (icalcomp, rid, master_dtstart);
+                       if (split_icalcomp) {
+                               rid = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ());
+                               e_cal_util_remove_instances (e_cal_component_get_icalcomponent (master_comp), 
rid, mod);
+                               e_cal_component_rescan (master_comp);
+                               e_cal_recur_ensure_end_dates (master_comp, TRUE, 
e_cal_cache_resolve_timezone_simple_cb, cal_cache);
+
+                               if (out_new_comp) {
+                                       g_clear_object (&new_comp);
+                                       new_comp = e_cal_component_clone (master_comp);
+                               }
+                       }
+
+                       if (split_icalcomp) {
+                               gchar *new_uid;
+
+                               new_uid = e_cal_component_gen_uid ();
+                               icalcomponent_set_uid (split_icalcomp, new_uid);
+                               g_free (new_uid);
+
+                               g_warn_if_fail (e_cal_component_set_icalcomponent (comp, split_icalcomp));
+
+                               e_cal_recur_ensure_end_dates (comp, TRUE, 
e_cal_cache_resolve_timezone_simple_cb, cal_cache);
+
+                               success = ecmb_create_object_sync (meta_backend, cal_cache, offline_flag, 
E_CONFLICT_RESOLUTION_FAIL,
+                                       comp, NULL, NULL, cancellable, error);
+                       }
+               } else {
+                       /* Replace master */
+                       instances = g_slist_remove (instances, master_comp);
+                       g_clear_object (&master_comp);
+                       existing_comp = NULL;
+
+                       instances = g_slist_prepend (instances, e_cal_component_clone (comp));
+               }
+               break;
+       }
+
+       if (success && *offline_flag == E_CACHE_OFFLINE_UNKNOWN) {
+               if (e_backend_get_online (E_BACKEND (meta_backend)) &&
+                   e_cal_meta_backend_connect_sync (meta_backend, cancellable, NULL)) {
+                       *offline_flag = E_CACHE_IS_ONLINE;
+               } else {
+                       *offline_flag = E_CACHE_IS_OFFLINE;
+               }
+       }
+
+       if (success && *offline_flag == E_CACHE_IS_ONLINE) {
+               gchar *new_uid = NULL;
+
+               success = ecmb_save_component_wrapper_sync (meta_backend, TRUE, conflict_resolution,
+                       instances, extra, &new_uid, cancellable, error);
+
+               if (success && new_uid) {
+                       success = ecmb_load_component_wrapper_sync (meta_backend, cal_cache, new_uid, 
cancellable, error);
+
+                       if (success && g_strcmp0 (new_uid, id->uid) != 0)
+                           success = ecmb_maybe_remove_from_cache (meta_backend, cal_cache, *offline_flag, 
id->uid, cancellable, error);
+
+                       g_free (new_uid);
+
+                       requires_put = FALSE;
+               }
+       }
+
+       if (success && requires_put)
+               success = ecmb_put_instances (meta_backend, cal_cache, id->uid, *offline_flag, instances, 
extra, cancellable, error);
+
+       if (!success) {
+               g_clear_object (&old_comp);
+               g_clear_object (&new_comp);
+       }
+
+       if (out_old_comp)
+               *out_old_comp = old_comp;
+       if (out_new_comp)
+               *out_new_comp = new_comp;
+
+       g_slist_free_full (instances, g_object_unref);
+       e_cal_component_free_id (id);
+       g_free (extra);
+
+       return success;
+}
+
+static void
+ecmb_modify_objects_sync (ECalBackendSync *sync_backend,
+                         EDataCal *cal,
+                         GCancellable *cancellable,
+                         const GSList *calobjs,
+                         ECalObjModType mod,
+                         GSList **out_old_components,
+                         GSList **out_new_components,
+                         GError **error)
+{
+       ECalMetaBackend *meta_backend;
+       ECalCache *cal_cache;
+       ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
+       EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+       icalcomponent_kind backend_kind;
+       GSList *link;
+       gboolean success = TRUE;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (calobjs != NULL);
+       g_return_if_fail (out_old_components != NULL);
+       g_return_if_fail (out_new_components != NULL);
+
+       if (!e_cal_backend_get_writable (E_CAL_BACKEND (sync_backend))) {
+               g_propagate_error (error, e_data_cal_create_error (PermissionDenied, NULL));
+               return;
+       }
+
+       meta_backend = E_CAL_META_BACKEND (sync_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       if (!cal_cache) {
+               g_propagate_error (error, e_data_cal_create_error (OtherError, NULL));
+               return;
+       }
+
+       backend_kind = e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend));
+
+       for (link = (GSList *) calobjs; link && success; link = g_slist_next (link)) {
+               ECalComponent *comp, *old_comp = NULL, *new_comp = NULL;
+
+               if (g_cancellable_set_error_if_cancelled (cancellable, error))
+                       break;
+
+               comp = e_cal_component_new_from_string (link->data);
+               if (!comp ||
+                   !e_cal_component_get_icalcomponent (comp) ||
+                   backend_kind != icalcomponent_isa (e_cal_component_get_icalcomponent (comp))) {
+                       g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+                       break;
+               }
+
+               success = ecmb_modify_object_sync (meta_backend, cal_cache, &offline_flag, 
conflict_resolution,
+                       mod, comp, &old_comp, &new_comp, cancellable, error);
+
+               if (success) {
+                       *out_old_components = g_slist_prepend (*out_old_components, old_comp);
+                       *out_new_components = g_slist_prepend (*out_new_components, new_comp);
+               }
+
+               g_object_unref (comp);
+       }
+
+       *out_old_components = g_slist_reverse (*out_old_components);
+       *out_new_components = g_slist_reverse (*out_new_components);
+
+       g_object_unref (cal_cache);
+}
+
+static gboolean
+ecmb_remove_object_sync (ECalMetaBackend *meta_backend,
+                        ECalCache *cal_cache,
+                        ECacheOfflineFlag *offline_flag,
+                        EConflictResolution conflict_resolution,
+                        ECalObjModType mod,
+                        const gchar *uid,
+                        const gchar *rid,
+                        ECalComponent **out_old_comp,
+                        ECalComponent **out_new_comp,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       struct icaltimetype itt;
+       ECalComponent *old_comp = NULL, *new_comp = NULL, *master_comp, *existing_comp = NULL;
+       GSList *instances = NULL;
+       gboolean success = TRUE;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (uid != NULL, FALSE);
+
+       if (rid && !*rid)
+               rid = NULL;
+
+       if ((mod == E_CAL_OBJ_MOD_THIS_AND_PRIOR ||
+           mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE) && !rid) {
+               /* Require Recurrence-ID for these types */
+               g_propagate_error (error, e_data_cal_create_error (ObjectNotFound, NULL));
+               return FALSE;
+       }
+
+       if (!e_cal_cache_get_components_by_uid (cal_cache, uid, &instances, cancellable, &local_error)) {
+               if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
+                       g_clear_error (&local_error);
+                       local_error = e_data_cal_create_error (ObjectNotFound, NULL);
+               }
+
+               g_propagate_error (error, local_error);
+
+               return FALSE;
+       }
+
+       master_comp = ecmb_find_in_instances (instances, uid, NULL);
+       if (rid) {
+               /* Set detached instance as the old object */
+               existing_comp = ecmb_find_in_instances (instances, uid, rid);
+       }
+
+       if (!existing_comp)
+               existing_comp = master_comp;
+
+       /* Remember old and new components */
+       if (out_old_comp && existing_comp)
+               old_comp = e_cal_component_clone (existing_comp);
+
+       if (*offline_flag == E_CACHE_OFFLINE_UNKNOWN) {
+               if (e_backend_get_online (E_BACKEND (meta_backend)) &&
+                   e_cal_meta_backend_connect_sync (meta_backend, cancellable, NULL)) {
+                       *offline_flag = E_CACHE_IS_ONLINE;
+               } else {
+                       *offline_flag = E_CACHE_IS_OFFLINE;
+               }
+       }
+
+       switch (mod) {
+       case E_CAL_OBJ_MOD_ALL:
+               /* Will remove the whole component below */
+               break;
+       case E_CAL_OBJ_MOD_ONLY_THIS:
+       case E_CAL_OBJ_MOD_THIS:
+               if (rid) {
+                       if (existing_comp != master_comp) {
+                               instances = g_slist_remove (instances, existing_comp);
+                               g_clear_object (&existing_comp);
+                       } else if (mod == E_CAL_OBJ_MOD_ONLY_THIS) {
+                               success = FALSE;
+                               g_propagate_error (error, e_data_cal_create_error (ObjectNotFound, NULL));
+                       } else {
+                               itt = icaltime_from_string (rid);
+                               if (!itt.zone) {
+                                       ECalComponentDateTime dt;
+
+                                       e_cal_component_get_dtstart (master_comp, &dt);
+                                       if (dt.value && dt.tzid) {
+                                               icaltimezone *zone = e_cal_cache_resolve_timezone_simple_cb 
(dt.tzid, cal_cache);
+
+                                               if (zone)
+                                                       itt = icaltime_convert_to_zone (itt, zone);
+                                       }
+                                       e_cal_component_free_datetime (&dt);
+
+                                       itt = icaltime_convert_to_zone (itt, icaltimezone_get_utc_timezone 
());
+                               }
+
+                               e_cal_util_remove_instances (e_cal_component_get_icalcomponent (master_comp), 
itt, mod);
+                       }
+
+                       if (success && out_new_comp)
+                               new_comp = e_cal_component_clone (master_comp);
+               } else {
+                       mod = E_CAL_OBJ_MOD_ALL;
+               }
+               break;
+       case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
+       case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
+               if (master_comp) {
+                       time_t fromtt, instancett;
+                       GSList *link, *previous = instances;
+
+                       itt = icaltime_from_string (rid);
+                       if (!itt.zone) {
+                               ECalComponentDateTime dt;
+
+                               e_cal_component_get_dtstart (master_comp, &dt);
+                               if (dt.value && dt.tzid) {
+                                       icaltimezone *zone = e_cal_cache_resolve_timezone_simple_cb (dt.tzid, 
cal_cache);
+
+                                       if (zone)
+                                               itt = icaltime_convert_to_zone (itt, zone);
+                               }
+                               e_cal_component_free_datetime (&dt);
+
+                               itt = icaltime_convert_to_zone (itt, icaltimezone_get_utc_timezone ());
+                       }
+
+                       e_cal_util_remove_instances (e_cal_component_get_icalcomponent (master_comp), itt, 
mod);
+
+                       fromtt = icaltime_as_timet (itt);
+
+                       /* Remove detached instances */
+                       for (link = instances; link && fromtt > 0;) {
+                               ECalComponent *comp = link->data;
+                               ECalComponentRange range;
+
+                               if (!e_cal_component_is_instance (comp)) {
+                                       previous = link;
+                                       link = g_slist_next (link);
+                                       continue;
+                               }
+
+                               e_cal_component_get_recurid (comp, &range);
+                               if (range.datetime.value)
+                                       instancett = icaltime_as_timet (*range.datetime.value);
+                               else
+                                       instancett = 0;
+                               e_cal_component_free_range (&range);
+
+                               if (instancett > 0 && (
+                                   (mod == E_CAL_OBJ_MOD_THIS_AND_PRIOR && instancett <= fromtt) ||
+                                   (mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE && instancett >= fromtt))) {
+                                       GSList *prev_instances = instances;
+
+                                       instances = g_slist_remove (instances, comp);
+                                       g_clear_object (&comp);
+
+                                       /* Restart the lookup */
+                                       if (previous == prev_instances)
+                                               previous = instances;
+
+                                       link = previous;
+                               } else {
+                                       previous = link;
+                                       link = g_slist_next (link);
+                               }
+                       }
+               } else {
+                       mod = E_CAL_OBJ_MOD_ALL;
+               }
+               break;
+       }
+
+       if (success) {
+               gchar *extra = NULL;
+
+               if (!e_cal_cache_get_component_extra (cal_cache, uid, NULL, &extra, cancellable, NULL))
+                       extra = NULL;
+
+               if (mod == E_CAL_OBJ_MOD_ALL) {
+                       if (*offline_flag == E_CACHE_IS_ONLINE) {
+                               success = e_cal_meta_backend_remove_component_sync (meta_backend, 
conflict_resolution, uid, extra, cancellable, error);
+                       }
+
+                       success = success && ecmb_maybe_remove_from_cache (meta_backend, cal_cache, 
*offline_flag, uid, cancellable, error);
+               } else {
+                       gboolean requires_put = TRUE;
+
+                       if (master_comp) {
+                               icalcomponent *icalcomp = e_cal_component_get_icalcomponent (master_comp);
+
+                               icalcomponent_set_sequence (icalcomp, icalcomponent_get_sequence (icalcomp) + 
1);
+
+                               e_cal_component_rescan (master_comp);
+
+                               /* Set the last modified time on the component */
+                               itt = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
+                               e_cal_component_set_last_modified (master_comp, &itt);
+                       }
+
+                       if (*offline_flag == E_CACHE_IS_ONLINE) {
+                               gchar *new_uid = NULL;
+
+                               success = ecmb_save_component_wrapper_sync (meta_backend, TRUE, 
conflict_resolution,
+                                       instances, extra, &new_uid, cancellable, error);
+
+                               if (success && new_uid) {
+                                       success = ecmb_load_component_wrapper_sync (meta_backend, cal_cache, 
new_uid, cancellable, error);
+
+                                       if (success && g_strcmp0 (new_uid, uid) != 0)
+                                           success = ecmb_maybe_remove_from_cache (meta_backend, cal_cache, 
*offline_flag, uid, cancellable, error);
+
+                                       g_free (new_uid);
+
+                                       requires_put = FALSE;
+                               }
+                       }
+
+                       if (success && requires_put)
+                               success = ecmb_put_instances (meta_backend, cal_cache, uid, *offline_flag, 
instances, extra, cancellable, error);
+
+               }
+
+               g_free (extra);
+       }
+
+       if (!success) {
+               g_clear_object (&old_comp);
+               g_clear_object (&new_comp);
+       }
+
+       if (out_old_comp)
+               *out_old_comp = old_comp;
+       if (out_new_comp)
+               *out_new_comp = new_comp;
+
+       g_slist_free_full (instances, g_object_unref);
+
+       return success;
+}
+
+static void
+ecmb_remove_objects_sync (ECalBackendSync *sync_backend,
+                         EDataCal *cal,
+                         GCancellable *cancellable,
+                         const GSList *ids,
+                         ECalObjModType mod,
+                         GSList **out_old_components,
+                         GSList **out_new_components,
+                         GError **error)
+{
+       ECalMetaBackend *meta_backend;
+       ECalCache *cal_cache;
+       ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
+       EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+       GSList *link;
+       gboolean success = TRUE;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (ids != NULL);
+       g_return_if_fail (out_old_components != NULL);
+       g_return_if_fail (out_new_components != NULL);
+
+       if (!e_cal_backend_get_writable (E_CAL_BACKEND (sync_backend))) {
+               g_propagate_error (error, e_data_cal_create_error (PermissionDenied, NULL));
+               return;
+       }
+
+       meta_backend = E_CAL_META_BACKEND (sync_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       if (!cal_cache) {
+               g_propagate_error (error, e_data_cal_create_error (OtherError, NULL));
+               return;
+       }
+
+       for (link = (GSList *) ids; link && success; link = g_slist_next (link)) {
+               ECalComponent *old_comp = NULL, *new_comp = NULL;
+               ECalComponentId *id = link->data;
+
+               if (g_cancellable_set_error_if_cancelled (cancellable, error))
+                       break;
+
+               if (!id) {
+                       g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+                       break;
+               }
+
+               success = ecmb_remove_object_sync (meta_backend, cal_cache, &offline_flag, 
conflict_resolution,
+                       mod, id->uid, id->rid, &old_comp, &new_comp, cancellable, error);
+
+               if (success) {
+                       *out_old_components = g_slist_prepend (*out_old_components, old_comp);
+                       *out_new_components = g_slist_prepend (*out_new_components, new_comp);
+               }
+       }
+
+       *out_old_components = g_slist_reverse (*out_old_components);
+       *out_new_components = g_slist_reverse (*out_new_components);
+
+       g_object_unref (cal_cache);
+}
+
+static gboolean
+ecmb_receive_object_sync (ECalMetaBackend *meta_backend,
+                         ECalCache *cal_cache,
+                         ECacheOfflineFlag *offline_flag,
+                         EConflictResolution conflict_resolution,
+                         ECalComponent *comp,
+                         icalproperty_method method,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       ESourceRegistry *registry;
+       ECalBackend *cal_backend;
+       gboolean is_declined, is_in_cache;
+       ECalObjModType mod;
+       ECalComponentId *id;
+       gboolean success = FALSE;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
+
+       id = e_cal_component_get_id (comp);
+
+       if (!id && method == ICAL_METHOD_PUBLISH) {
+               gchar *new_uid;
+
+               new_uid = e_cal_component_gen_uid ();
+               e_cal_component_set_uid (comp, new_uid);
+               g_free (new_uid);
+
+               id = e_cal_component_get_id (comp);
+       }
+
+       if (!id) {
+               g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+               return FALSE;
+       }
+
+       cal_backend = E_CAL_BACKEND (meta_backend);
+       registry = e_cal_backend_get_registry (cal_backend);
+
+       /* just to check whether component exists in a cache */
+       is_in_cache = e_cal_cache_contains (cal_cache, id->uid, NULL, E_CACHE_EXCLUDE_DELETED) ||
+               (id->rid && *id->rid && e_cal_cache_contains (cal_cache, id->uid, id->rid, 
E_CACHE_EXCLUDE_DELETED));
+
+       mod = e_cal_component_is_instance (comp) ? E_CAL_OBJ_MOD_THIS : E_CAL_OBJ_MOD_ALL;
+
+       switch (method) {
+       case ICAL_METHOD_PUBLISH:
+       case ICAL_METHOD_REQUEST:
+       case ICAL_METHOD_REPLY:
+               is_declined = e_cal_backend_user_declined (registry, e_cal_component_get_icalcomponent 
(comp));
+               if (is_in_cache) {
+                       if (!is_declined) {
+                               success = ecmb_modify_object_sync (meta_backend, cal_cache, offline_flag, 
conflict_resolution,
+                                       mod, comp, NULL, NULL, cancellable, error);
+                       } else {
+                               success = ecmb_remove_object_sync (meta_backend, cal_cache, offline_flag, 
conflict_resolution,
+                                       mod, id->uid, id->rid, NULL, NULL, cancellable, error);
+                       }
+               } else if (!is_declined) {
+                       success = ecmb_create_object_sync (meta_backend, cal_cache, offline_flag, 
conflict_resolution,
+                               comp, NULL, NULL, cancellable, error);
+               }
+               break;
+       case ICAL_METHOD_CANCEL:
+               if (is_in_cache) {
+                       success = ecmb_remove_object_sync (meta_backend, cal_cache, offline_flag, 
conflict_resolution,
+                               E_CAL_OBJ_MOD_THIS, id->uid, id->rid, NULL, NULL, cancellable, error);
+               } else {
+                       g_propagate_error (error, e_data_cal_create_error (ObjectNotFound, NULL));
+               }
+               break;
+
+       default:
+               g_propagate_error (error, e_data_cal_create_error (UnsupportedMethod, NULL));
+               break;
+       }
+
+       e_cal_component_free_id (id);
+
+       return success;
+}
+
+static void
+ecmb_receive_objects_sync (ECalBackendSync *sync_backend,
+                          EDataCal *cal,
+                          GCancellable *cancellable,
+                          const gchar *calobj,
+                          GError **error)
+{
+       ECalMetaBackend *meta_backend;
+       ECacheOfflineFlag offline_flag = E_CACHE_OFFLINE_UNKNOWN;
+       EConflictResolution conflict_resolution = E_CONFLICT_RESOLUTION_KEEP_LOCAL;
+       ECalCache *cal_cache;
+       ECalComponent *comp;
+       icalcomponent *icalcomp, *subcomp;
+       icalcomponent_kind kind;
+       icalproperty_method top_method;
+       GSList *comps = NULL, *link;
+       gboolean success = TRUE;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (calobj != NULL);
+
+       if (!e_cal_backend_get_writable (E_CAL_BACKEND (sync_backend))) {
+               g_propagate_error (error, e_data_cal_create_error (PermissionDenied, NULL));
+               return;
+       }
+
+       meta_backend = E_CAL_META_BACKEND (sync_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       if (!cal_cache) {
+               g_propagate_error (error, e_data_cal_create_error (OtherError, NULL));
+               return;
+       }
+
+       icalcomp = icalparser_parse_string (calobj);
+       if (!icalcomp) {
+               g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+               g_object_unref (cal_cache);
+               return;
+       }
+
+       kind = e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend));
+
+       if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
+               for (subcomp = icalcomponent_get_first_component (icalcomp, kind);
+                    subcomp && success;
+                    subcomp = icalcomponent_get_next_component (icalcomp, kind)) {
+                       comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (subcomp));
+
+                       if (comp)
+                               comps = g_slist_prepend (comps, comp);
+               }
+       } else if (icalcomponent_isa (icalcomp) == kind) {
+               comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (icalcomp));
+
+               if (comp)
+                       comps = g_slist_prepend (comps, comp);
+       }
+
+       if (!comps) {
+               g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+               icalcomponent_free (icalcomp);
+               g_object_unref (cal_cache);
+               return;
+       }
+
+       comps = g_slist_reverse (comps);
+
+       if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT)
+               ecmb_gather_timezones (meta_backend, E_TIMEZONE_CACHE (cal_cache), icalcomp);
+
+       if (icalcomponent_get_first_property (icalcomp, ICAL_METHOD_PROPERTY))
+               top_method = icalcomponent_get_method (icalcomp);
+       else
+               top_method = ICAL_METHOD_PUBLISH;
+
+       for (link = comps; link && success; link = g_slist_next (link)) {
+               ECalComponent *comp = link->data;
+               icalproperty_method method;
+
+               subcomp = e_cal_component_get_icalcomponent (comp);
+
+               if (icalcomponent_get_first_property (subcomp, ICAL_METHOD_PROPERTY)) {
+                       method = icalcomponent_get_method (subcomp);
+               } else {
+                       method = top_method;
+               }
+
+               success = ecmb_receive_object_sync (meta_backend, cal_cache, &offline_flag, 
conflict_resolution,
+                       comp, method, cancellable, error);
+       }
+
+       g_slist_free_full (comps, g_object_unref);
+       icalcomponent_free (icalcomp);
+       g_object_unref (cal_cache);
+}
+
+static void
+ecmb_send_objects_sync (ECalBackendSync *sync_backend,
+                       EDataCal *cal,
+                       GCancellable *cancellable,
+                       const gchar *calobj,
+                       GSList **out_users,
+                       gchar **out_modified_calobj,
+                       GError **error)
+{
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (calobj != NULL);
+       g_return_if_fail (out_users != NULL);
+       g_return_if_fail (out_modified_calobj != NULL);
+
+       *out_users = NULL;
+       *out_modified_calobj = g_strdup (calobj);
+}
+
+static void
+ecmb_add_attachment_uris (ECalComponent *comp,
+                         GSList **out_uris)
+{
+       icalcomponent *icalcomp;
+       icalproperty *prop;
+
+       g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+       g_return_if_fail (out_uris != NULL);
+
+       icalcomp = e_cal_component_get_icalcomponent (comp);
+       g_return_if_fail (icalcomp != NULL);
+
+       for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
+            prop;
+            prop = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
+               icalattach *attach = icalproperty_get_attach (prop);
+
+               if (attach && icalattach_get_is_url (attach)) {
+                       const gchar *url;
+
+                       url = icalattach_get_url (attach);
+                       if (url) {
+                               gsize buf_size;
+                               gchar *buf;
+
+                               buf_size = strlen (url);
+                               buf = g_malloc0 (buf_size + 1);
+
+                               icalvalue_decode_ical_string (url, buf, buf_size);
+
+                               *out_uris = g_slist_prepend (*out_uris, g_strdup (buf));
+
+                               g_free (buf);
+                       }
+               }
+       }
+}
+
+static void
+ecmb_get_attachment_uris_sync (ECalBackendSync *sync_backend,
+                              EDataCal *cal,
+                              GCancellable *cancellable,
+                              const gchar *uid,
+                              const gchar *rid,
+                              GSList **out_uris,
+                              GError **error)
+{
+       ECalMetaBackend *meta_backend;
+       ECalCache *cal_cache;
+       ECalComponent *comp;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (uid != NULL);
+       g_return_if_fail (out_uris != NULL);
+
+       *out_uris = NULL;
+
+       meta_backend = E_CAL_META_BACKEND (sync_backend);
+       cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
+       if (!cal_cache) {
+               g_propagate_error (error, e_data_cal_create_error (OtherError, NULL));
+               return;
+       }
+
+       if (rid && *rid) {
+               if (e_cal_cache_get_component (cal_cache, uid, rid, &comp, cancellable, &local_error) && 
comp) {
+                       ecmb_add_attachment_uris (comp, out_uris);
+                       g_object_unref (comp);
+               }
+       } else {
+               GSList *comps = NULL, *link;
+
+               if (e_cal_cache_get_components_by_uid (cal_cache, uid, &comps, cancellable, &local_error)) {
+                       for (link = comps; link; link = g_slist_next (link)) {
+                               comp = link->data;
+
+                               ecmb_add_attachment_uris (comp, out_uris);
+                       }
+
+                       g_slist_free_full (comps, g_object_unref);
+               }
+       }
+
+       g_object_unref (cal_cache);
+
+       *out_uris = g_slist_reverse (*out_uris);
+
+       if (local_error) {
+               if (g_error_matches (local_error, E_CACHE_ERROR, E_CACHE_ERROR_NOT_FOUND)) {
+                       g_clear_error (&local_error);
+                       local_error = e_data_cal_create_error (ObjectNotFound, NULL);
+               }
+
+               g_propagate_error (error, local_error);
+       }
+}
+
+static void
+ecmb_get_timezone_sync (ECalBackendSync *sync_backend,
+                       EDataCal *cal,
+                       GCancellable *cancellable,
+                       const gchar *tzid,
+                       gchar **tzobject,
+                       GError **error)
+{
+       ECalCache *cal_cache;
+       gchar *timezone_str = NULL;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+       g_return_if_fail (tzid != NULL);
+       g_return_if_fail (tzobject != NULL);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (sync_backend));
+       if (cal_cache) {
+               icaltimezone *zone;
+
+               zone = e_timezone_cache_get_timezone (E_TIMEZONE_CACHE (cal_cache), tzid);
+               if (zone) {
+                       icalcomponent *icalcomp;
+
+                       icalcomp = icaltimezone_get_component (zone);
+
+                       if (!icalcomp) {
+                               local_error = e_data_cal_create_error (InvalidObject, NULL);
+                       } else {
+                               timezone_str = icalcomponent_as_ical_string_r (icalcomp);
+                       }
+               }
+
+               g_object_unref (cal_cache);
+       }
+
+       if (!local_error && !timezone_str)
+               local_error = e_data_cal_create_error (ObjectNotFound, NULL);
+
+       *tzobject = timezone_str;
+
+       g_propagate_error (error, local_error);
+}
+
+static void
+ecmb_add_timezone_sync (ECalBackendSync *sync_backend,
+                       EDataCal *cal,
+                       GCancellable *cancellable,
+                       const gchar *tzobject,
+                       GError **error)
+{
+       icalcomponent *tz_comp;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (sync_backend));
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return;
+
+       if (!tzobject || !*tzobject) {
+               g_propagate_error (error, e_data_cal_create_error (InvalidObject, NULL));
+               return;
+       }
+
+       tz_comp = icalparser_parse_string (tzobject);
+       if (!tz_comp ||
+           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 ();
+               icaltimezone_set_component (zone, tz_comp);
+
+               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);
+               }
+       }
+
+       if (tz_comp)
+               icalcomponent_free (tz_comp);
+}
+
+static gchar *
+ecmb_get_backend_property (ECalBackend *cal_backend,
+                          const gchar *prop_name)
+{
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND (cal_backend), NULL);
+       g_return_val_if_fail (prop_name != NULL, NULL);
+
+       if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_REVISION)) {
+               ECalCache *cal_cache;
+               gchar *revision = NULL;
+
+               cal_cache = e_cal_meta_backend_ref_cache (E_CAL_META_BACKEND (cal_backend));
+               if (cal_cache) {
+                       revision = e_cache_dup_revision (E_CACHE (cal_cache));
+                       g_object_unref (cal_cache);
+               }
+
+               return revision;
+       } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
+               ECalComponent *comp;
+               gchar *prop_value;
+
+               comp = e_cal_component_new ();
+
+               switch (e_cal_backend_get_kind (cal_backend)) {
+               case ICAL_VEVENT_COMPONENT:
+                       e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
+                       break;
+               case ICAL_VTODO_COMPONENT:
+                       e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
+                       break;
+               case ICAL_VJOURNAL_COMPONENT:
+                       e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
+                       break;
+               default:
+                       g_object_unref (comp);
+                       return NULL;
+               }
+
+               prop_value = e_cal_component_get_as_string (comp);
+
+               g_object_unref (comp);
+
+               return prop_value;
+       }
+
+       /* Chain up to parent's method. */
+       return E_CAL_BACKEND_CLASS (e_cal_meta_backend_parent_class)->get_backend_property (cal_backend, 
prop_name);
+}
+
+static void
+ecmb_start_view (ECalBackend *cal_backend,
+                EDataCalView *view)
+{
+       GCancellable *cancellable;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (cal_backend));
+
+       cancellable = ecmb_create_view_cancellable (E_CAL_META_BACKEND (cal_backend), view);
+
+       e_cal_backend_schedule_custom_operation (cal_backend, cancellable,
+               ecmb_start_view_thread_func, g_object_ref (view), g_object_unref);
+
+       g_object_unref (cancellable);
+}
+
+static void
+ecmb_stop_view (ECalBackend *cal_backend,
+               EDataCalView *view)
+{
+       GCancellable *cancellable;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (cal_backend));
+
+       cancellable = ecmb_steal_view_cancellable (E_CAL_META_BACKEND (cal_backend), view);
+       if (cancellable) {
+               g_cancellable_cancel (cancellable);
+               g_object_unref (cancellable);
+       }
+}
+
+static void
+ecmb_schedule_refresh (ECalMetaBackend *meta_backend)
+{
+       GCancellable *cancellable;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (meta_backend));
+
+       g_mutex_lock (&meta_backend->priv->property_lock);
+
+       if (meta_backend->priv->refresh_cancellable) {
+               /* Already refreshing the content */
+               g_mutex_unlock (&meta_backend->priv->property_lock);
+               return;
+       }
+
+       cancellable = g_cancellable_new ();
+       meta_backend->priv->refresh_cancellable = g_object_ref (cancellable);
+
+       g_mutex_unlock (&meta_backend->priv->property_lock);
+
+       e_cal_backend_schedule_custom_operation (E_CAL_BACKEND (meta_backend), cancellable,
+               ecmb_refresh_thread_func, NULL, NULL);
+
+       g_object_unref (cancellable);
+}
+
+static void
+ecmb_schedule_source_changed (ECalMetaBackend *meta_backend)
+{
+       GCancellable *cancellable;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (meta_backend));
+
+       g_mutex_lock (&meta_backend->priv->property_lock);
+
+       if (meta_backend->priv->source_changed_cancellable) {
+               /* Already updating */
+               g_mutex_unlock (&meta_backend->priv->property_lock);
+               return;
+       }
+
+       cancellable = g_cancellable_new ();
+       meta_backend->priv->source_changed_cancellable = g_object_ref (cancellable);
+
+       g_mutex_unlock (&meta_backend->priv->property_lock);
+
+       e_cal_backend_schedule_custom_operation (E_CAL_BACKEND (meta_backend), cancellable,
+               ecmb_source_changed_thread_func, NULL, NULL);
+
+       g_object_unref (cancellable);
+}
+
+static void
+ecmb_schedule_go_offline (ECalMetaBackend *meta_backend)
+{
+       GCancellable *cancellable;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (meta_backend));
+
+       g_mutex_lock (&meta_backend->priv->property_lock);
+
+       /* Cancel anything ongoing now, but disconnect in a dedicated thread */
+       if (meta_backend->priv->refresh_cancellable)
+               g_cancellable_cancel (meta_backend->priv->refresh_cancellable);
+
+       if (meta_backend->priv->source_changed_cancellable)
+               g_cancellable_cancel (meta_backend->priv->source_changed_cancellable);
+
+       if (meta_backend->priv->go_offline_cancellable) {
+               /* Already going offline */
+               g_mutex_unlock (&meta_backend->priv->property_lock);
+               return;
+       }
+
+       cancellable = g_cancellable_new ();
+       meta_backend->priv->go_offline_cancellable = g_object_ref (cancellable);
+
+       g_mutex_unlock (&meta_backend->priv->property_lock);
+
+       e_cal_backend_schedule_custom_operation (E_CAL_BACKEND (meta_backend), cancellable,
+               ecmb_go_offline_thread_func, NULL, NULL);
+
+       g_object_unref (cancellable);
+}
+
+static void
+ecmb_notify_online_cb (GObject *object,
+                      GParamSpec *param,
+                      gpointer user_data)
+{
+       ECalMetaBackend *meta_backend = user_data;
+       gboolean new_value;
+
+       g_return_if_fail (E_IS_CAL_META_BACKEND (meta_backend));
+
+       new_value = e_backend_get_online (E_BACKEND (meta_backend));
+       if (!new_value == !meta_backend->priv->current_online_state)
+               return;
+
+       meta_backend->priv->current_online_state = new_value;
+
+       if (new_value)
+               ecmb_schedule_refresh (meta_backend);
+       else
+               ecmb_schedule_go_offline (meta_backend);
+}
+
+static void
+ecmb_cancel_view_cb (gpointer key,
+                    gpointer value,
+                    gpointer user_data)
+{
+       GCancellable *cancellable = value;
+
+       g_return_if_fail (G_IS_CANCELLABLE (cancellable));
+
+       g_cancellable_cancel (cancellable);
+}
+
 static void
 e_cal_meta_backend_set_property (GObject *object,
                                 guint property_id,
@@ -164,8 +2297,68 @@ e_cal_meta_backend_get_property (GObject *object,
 }
 
 static void
+e_cal_meta_backend_constructed (GObject *object)
+{
+       ECalMetaBackend *meta_backend = E_CAL_META_BACKEND (object);
+       ESource *source;
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_cal_meta_backend_parent_class)->constructed (object);
+
+       meta_backend->priv->current_online_state = e_backend_get_online (E_BACKEND (meta_backend));
+
+       source = e_backend_get_source (E_BACKEND (meta_backend));
+       meta_backend->priv->source_changed_id = g_signal_connect_swapped (source, "changed",
+               G_CALLBACK (ecmb_schedule_source_changed), meta_backend);
+
+       meta_backend->priv->notify_online_id = g_signal_connect (meta_backend, "notify::online",
+               G_CALLBACK (ecmb_notify_online_cb), meta_backend);
+}
+
+static void
 e_cal_meta_backend_dispose (GObject *object)
 {
+       ECalMetaBackend *meta_backend = E_CAL_META_BACKEND (object);
+       ESource *source = e_backend_get_source (E_BACKEND (meta_backend));
+
+       g_mutex_lock (&meta_backend->priv->property_lock);
+
+       if (meta_backend->priv->refresh_timeout_id) {
+               if (source)
+                       e_source_refresh_remove_timeout (source, meta_backend->priv->refresh_timeout_id);
+               meta_backend->priv->refresh_timeout_id = 0;
+       }
+
+       if (meta_backend->priv->source_changed_id) {
+               if (source)
+                       g_signal_handler_disconnect (source, meta_backend->priv->source_changed_id);
+               meta_backend->priv->source_changed_id = 0;
+       }
+
+       if (meta_backend->priv->notify_online_id) {
+               g_signal_handler_disconnect (meta_backend, meta_backend->priv->notify_online_id);
+               meta_backend->priv->notify_online_id = 0;
+       }
+
+       g_hash_table_foreach (meta_backend->priv->view_cancellables, ecmb_cancel_view_cb, NULL);
+
+       if (meta_backend->priv->refresh_cancellable) {
+               g_cancellable_cancel (meta_backend->priv->refresh_cancellable);
+               g_clear_object (&meta_backend->priv->refresh_cancellable);
+       }
+
+       if (meta_backend->priv->source_changed_cancellable) {
+               g_cancellable_cancel (meta_backend->priv->source_changed_cancellable);
+               g_clear_object (&meta_backend->priv->source_changed_cancellable);
+       }
+
+       if (meta_backend->priv->go_offline_cancellable) {
+               g_cancellable_cancel (meta_backend->priv->go_offline_cancellable);
+               g_clear_object (&meta_backend->priv->go_offline_cancellable);
+       }
+
+       g_mutex_unlock (&meta_backend->priv->property_lock);
+
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (e_cal_meta_backend_parent_class)->dispose (object);
 }
@@ -176,8 +2369,12 @@ e_cal_meta_backend_finalize (GObject *object)
        ECalMetaBackend *meta_backend = E_CAL_META_BACKEND (object);
 
        g_clear_object (&meta_backend->priv->cache);
+       g_clear_object (&meta_backend->priv->refresh_cancellable);
+       g_clear_object (&meta_backend->priv->source_changed_cancellable);
+       g_clear_object (&meta_backend->priv->go_offline_cancellable);
 
        g_mutex_clear (&meta_backend->priv->property_lock);
+       g_hash_table_destroy (meta_backend->priv->view_cancellables);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (e_cal_meta_backend_parent_class)->finalize (object);
@@ -187,12 +2384,37 @@ static void
 e_cal_meta_backend_class_init (ECalMetaBackendClass *klass)
 {
        GObjectClass *object_class;
+       ECalBackendClass *cal_backend_class;
+       ECalBackendSyncClass *cal_backend_sync_class;
 
        g_type_class_add_private (klass, sizeof (ECalMetaBackendPrivate));
 
+       klass->get_changes_sync = ecmb_get_changes_sync;
+
+       cal_backend_sync_class = E_CAL_BACKEND_SYNC_CLASS (klass);
+       cal_backend_sync_class->open_sync = ecmb_open_sync;
+       cal_backend_sync_class->refresh_sync = ecmb_refresh_sync;
+       cal_backend_sync_class->get_object_sync = ecmb_get_object_sync;
+       cal_backend_sync_class->get_object_list_sync = ecmb_get_object_list_sync;
+       cal_backend_sync_class->get_free_busy_sync = ecmb_get_free_busy_sync;
+       cal_backend_sync_class->create_objects_sync = ecmb_create_objects_sync;
+       cal_backend_sync_class->modify_objects_sync = ecmb_modify_objects_sync;
+       cal_backend_sync_class->remove_objects_sync = ecmb_remove_objects_sync;
+       cal_backend_sync_class->receive_objects_sync = ecmb_receive_objects_sync;
+       cal_backend_sync_class->send_objects_sync = ecmb_send_objects_sync;
+       cal_backend_sync_class->get_attachment_uris_sync = ecmb_get_attachment_uris_sync;
+       cal_backend_sync_class->get_timezone_sync = ecmb_get_timezone_sync;
+       cal_backend_sync_class->add_timezone_sync = ecmb_add_timezone_sync;
+
+       cal_backend_class = E_CAL_BACKEND_CLASS (klass);
+       cal_backend_class->get_backend_property = ecmb_get_backend_property;
+       cal_backend_class->start_view = ecmb_start_view;
+       cal_backend_class->stop_view = ecmb_stop_view;
+
        object_class = G_OBJECT_CLASS (klass);
        object_class->set_property = e_cal_meta_backend_set_property;
        object_class->get_property = e_cal_meta_backend_get_property;
+       object_class->constructed = e_cal_meta_backend_constructed;
        object_class->dispose = e_cal_meta_backend_dispose;
        object_class->finalize = e_cal_meta_backend_finalize;
 
@@ -219,6 +2441,28 @@ e_cal_meta_backend_init (ECalMetaBackend *meta_backend)
        meta_backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (meta_backend, E_TYPE_CAL_META_BACKEND, 
ECalMetaBackendPrivate);
 
        g_mutex_init (&meta_backend->priv->property_lock);
+
+       meta_backend->priv->view_cancellables = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, 
g_object_unref);
+       meta_backend->priv->current_online_state = FALSE;
+}
+
+/**
+ * e_cal_meta_backend_get_capabilities:
+ * @meta_backend: an #ECalMetaBackend
+ *
+ * Returns: an #ECalBackend::capabilities property to be used by
+ *    the descendant in conjunction to the descendant's capabilities
+ *    in the result of e_cal_backend_get_backend_property() with
+ *    #CLIENT_BACKEND_PROPERTY_CAPABILITIES.
+ *
+ * Since: 3.26
+ **/
+const gchar *
+e_cal_meta_backend_get_capabilities (ECalMetaBackend *meta_backend)
+{
+       g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), NULL);
+
+       return CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED;
 }
 
 /**
@@ -694,6 +2938,9 @@ e_cal_meta_backend_disconnect_sync (ECalMetaBackend *meta_backend,
 /**
  * e_cal_meta_backend_get_changes_sync:
  * @meta_backend: an #ECalMetaBackend
+ * @last_sync_tag: (nullable): optional sync tag from the last check
+ * @out_new_sync_tag: (out) (transfer full): new sync tag to store on success
+ * @out_repeat: (out): whether to repeat this call again; default is %FALSE
  * @out_created_objects: (out) (element-type ECalMetaBackendInfo) (transfer full):
  *    a #GSList of #ECalMetaBackendInfo object infos which had been created since
  *    the last check
@@ -709,6 +2956,16 @@ e_cal_meta_backend_disconnect_sync (ECalMetaBackend *meta_backend,
  * Gathers the changes since the last check which had been done
  * on the remote side.
  *
+ * The @last_sync_tag can be used as a tag of the last check. This can be %NULL,
+ * when there was no previous call or when the descendant doesn't store any
+ * such tags. The @out_new_sync_tag can be populated with a value to be stored
+ * and used the next time.
+ *
+ * The @out_repeat can be set to %TRUE when the descendant didn't finish
+ * read of all the changes. In that case the @meta_backend calls this
+ * function again with the @out_new_sync_tag as the @last_sync_tag, but also
+ * notifies about the found changes immediately.
+ *
  * It is optional to implement this virtual method by the descendant.
  * The default implementation calls e_cal_meta_backend_list_existing_sync()
  * and then compares the list with the current content of the local cache
@@ -724,6 +2981,9 @@ e_cal_meta_backend_disconnect_sync (ECalMetaBackend *meta_backend,
  **/
 gboolean
 e_cal_meta_backend_get_changes_sync (ECalMetaBackend *meta_backend,
+                                    const gchar *last_sync_tag,
+                                    gchar **out_new_sync_tag,
+                                    gboolean *out_repeat,
                                     GSList **out_created_objects,
                                     GSList **out_modified_objects,
                                     GSList **out_removed_objects,
@@ -733,6 +2993,9 @@ e_cal_meta_backend_get_changes_sync (ECalMetaBackend *meta_backend,
        ECalMetaBackendClass *klass;
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
+       g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
+       g_return_val_if_fail (out_repeat != NULL, FALSE);
+       g_return_val_if_fail (out_created_objects != NULL, FALSE);
        g_return_val_if_fail (out_created_objects != NULL, FALSE);
        g_return_val_if_fail (out_modified_objects != NULL, FALSE);
        g_return_val_if_fail (out_removed_objects != NULL, FALSE);
@@ -741,18 +3004,28 @@ e_cal_meta_backend_get_changes_sync (ECalMetaBackend *meta_backend,
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->get_changes_sync != NULL, FALSE);
 
-       return klass->get_changes_sync (meta_backend, out_created_objects, out_modified_objects, 
out_removed_objects, cancellable, error);
+       return klass->get_changes_sync (meta_backend,
+               last_sync_tag,
+               out_new_sync_tag,
+               out_repeat,
+               out_created_objects,
+               out_modified_objects,
+               out_removed_objects,
+               cancellable,
+               error);
 }
 
 /**
  * e_cal_meta_backend_list_existing_sync:
  * @meta_backend: an #ECalMetaBackend
+ * @out_new_sync_tag: (out) (transfer full): optional return location for a new sync tag
  * @out_existing_objects: (out) (element-type ECalMetaBackendInfo) (transfer full):
  *    a #GSList of #ECalMetaBackendInfo object infos which are stored on the remote side
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
- * Used to get list of all existing objects on the remote side.
+ * Used to get list of all existing objects on the remote side. The descendant
+ * can optionally provide @out_new_sync_tag, which will be stored if not %NULL.
  *
  * It is mandatory to implement this virtual method by the descendant.
  *
@@ -766,6 +3039,7 @@ e_cal_meta_backend_get_changes_sync (ECalMetaBackend *meta_backend,
  **/
 gboolean
 e_cal_meta_backend_list_existing_sync (ECalMetaBackend *meta_backend,
+                                      gchar **out_new_sync_tag,
                                       GSList **out_existing_objects,
                                       GCancellable *cancellable,
                                       GError **error)
@@ -779,7 +3053,7 @@ e_cal_meta_backend_list_existing_sync (ECalMetaBackend *meta_backend,
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->list_existing_sync != NULL, FALSE);
 
-       return klass->list_existing_sync (meta_backend, out_existing_objects, cancellable, error);
+       return klass->list_existing_sync (meta_backend, out_new_sync_tag, out_existing_objects, cancellable, 
error);
 }
 
 /**
@@ -788,6 +3062,8 @@ e_cal_meta_backend_list_existing_sync (ECalMetaBackend *meta_backend,
  * @overwrite_existing: %TRUE when can overwrite existing components, %FALSE otherwise
  * @conflict_resolution: one of #EConflictResolution, what to do on conflicts
  * @instances: (element-type ECalComponent): instances of the component to save
+ * @extra: (nullable): extra data saved with the components in an #ECalCache
+ * @out_new_uid: (out) (transfer full): return location for the UID of the saved component
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
@@ -806,6 +3082,10 @@ e_cal_meta_backend_list_existing_sync (ECalMetaBackend *meta_backend,
  * into inline attachments, thus it's not needed to call
  * e_cal_meta_backend_inline_local_attachments_sync() by the descendant.
  *
+ * The @out_new_uid can be populated with a UID of the saved component as the server
+ * assigned it to it. This UID, if set, is loaded from the remote side afterwards,
+ * also to see whether any changes had been made to the component by the remote side.
+ *
  * The descendant can use an #E_CLIENT_ERROR_OUT_OF_SYNC error to indicate that
  * the save failed due to made changes on the remote side, and let the @meta_backend
  * to resolve this conflict based on the @conflict_resolution on its own.
@@ -823,6 +3103,8 @@ e_cal_meta_backend_save_component_sync (ECalMetaBackend *meta_backend,
                                        gboolean overwrite_existing,
                                        EConflictResolution conflict_resolution,
                                        const GSList *instances,
+                                       const gchar *extra,
+                                       gchar **out_new_uid,
                                        GCancellable *cancellable,
                                        GError **error)
 {
@@ -830,26 +3112,26 @@ e_cal_meta_backend_save_component_sync (ECalMetaBackend *meta_backend,
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (instances != NULL, FALSE);
+       g_return_val_if_fail (out_new_uid != NULL, FALSE);
 
        klass = E_CAL_META_BACKEND_GET_CLASS (meta_backend);
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->save_component_sync != NULL, FALSE);
 
-       return klass->save_component_sync (meta_backend, overwrite_existing, conflict_resolution, instances, 
cancellable, error);
+       return klass->save_component_sync (meta_backend, overwrite_existing, conflict_resolution, instances, 
extra, out_new_uid, cancellable, error);
 }
 
 /**
  * e_cal_meta_backend_load_component_sync:
  * @meta_backend: an #ECalMetaBackend
  * @uid: a component UID
- * @rid: (nullable): optional component Recurrence-ID, or %NULL
  * @out_component: (out) (transfer full): a loaded component, as icalcomponent
+ * @out_extra: (out) (transfer full): an extra data to store to #ECalCache with this component
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
- * Loads one component from the remote side. In case the descendant's storage
- * doesn't allow to store detached instances separately it can ignore the @rid
- * and return whole component. The @out_component can be either
+ * Loads a component from the remote side. Any detached instances should be
+ * returned together with the master object. The @out_component can be either
  * a VCALENDAR component, which would contain the master object and all of
  * its detached instances, eventually also used time zones, or the requested
  * component of type VEVENT, VJOURNAL or VTODO.
@@ -866,8 +3148,8 @@ e_cal_meta_backend_save_component_sync (ECalMetaBackend *meta_backend,
 gboolean
 e_cal_meta_backend_load_component_sync (ECalMetaBackend *meta_backend,
                                        const gchar *uid,
-                                       const gchar *rid,
                                        icalcomponent **out_component,
+                                       gchar **out_extra,
                                        GCancellable *cancellable,
                                        GError **error)
 {
@@ -876,125 +3158,48 @@ e_cal_meta_backend_load_component_sync (ECalMetaBackend *meta_backend,
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
        g_return_val_if_fail (out_component != NULL, FALSE);
+       g_return_val_if_fail (out_extra != NULL, FALSE);
 
        klass = E_CAL_META_BACKEND_GET_CLASS (meta_backend);
        g_return_val_if_fail (klass != NULL, FALSE);
        g_return_val_if_fail (klass->load_component_sync != NULL, FALSE);
 
-       return klass->load_component_sync (meta_backend, uid, rid, out_component, cancellable, error);
+       return klass->load_component_sync (meta_backend, uid, out_component, out_extra, cancellable, error);
 }
 
 /**
- * e_cal_meta_backend_get_free_busy_sync:
- * @meta_backend: an #ECalMetaBackend
- * @users: (element-type utf8): a #GSList of user mail addresses
- * @start: time range start
- * @end: time range end
- * @out_freebusy: (out) (element-type utf8): a #GSList of iCalendar strings
- * @cancellable: optional #GCancellable object, or %NULL
- * @error: return location for a #GError, or %NULL
- *
- * Gets Free/Busy information for all the mail addresses specified
- * in the @users list for the given time range. The descendant can
- * use e_cal_meta_backend_notify_free_busy() to let the client side
- * know about Free/Busy information asynchronously, but it should
- * also report all found Free/Busy data in @out_freebusy #GSList.
- *
- * It is optional to implement this virtual method by the descendant.
- * The default implementation checks whether any of the users
- * equals to #CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS and if so, then
- * it returns the Free/Busy information for this user according to
- * the information in the local cache.
- *
- * Returns: Whether succeeded.
- *
- * Since: 3.26
- **/
-gboolean
-e_cal_meta_backend_get_free_busy_sync (ECalMetaBackend *meta_backend,
-                                      const GSList *users,
-                                      time_t start,
-                                      time_t end,
-                                      GSList **out_freebusy,
-                                      GCancellable *cancellable,
-                                      GError **error)
-{
-       ECalMetaBackendClass *klass;
-
-       g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
-       g_return_val_if_fail (users != NULL, FALSE);
-       g_return_val_if_fail (out_freebusy != NULL, FALSE);
-
-       klass = E_CAL_META_BACKEND_GET_CLASS (meta_backend);
-       g_return_val_if_fail (klass != NULL, FALSE);
-       g_return_val_if_fail (klass->get_free_busy_sync != NULL, FALSE);
-
-       return klass->get_free_busy_sync (meta_backend, users, start, end, out_freebusy, cancellable, error);
-}
-
-/**
- * e_cal_meta_backend_notify_free_busy:
- * @meta_backend: an #ECalMetaBackend
- * @freebusy: (element-type utf8): a #GSList of iCalendar strings
- *
- * Notifies the client side about Free/Busy information asynchronously.
- * This is usually used within e_cal_meta_backend_get_free_busy_sync().
- *
- * Since: 3.26
- **/
-void
-e_cal_meta_backend_notify_free_busy (ECalMetaBackend *meta_backend,
-                                    const GSList *freebusy)
-{
-       g_return_if_fail (E_IS_CAL_META_BACKEND (meta_backend));
-
-       if (freebusy) {
-               EDataCal *data_cal;
-
-               data_cal = e_cal_backend_ref_data_cal (E_CAL_BACKEND (meta_backend));
-               if (data_cal) {
-                       e_data_cal_report_free_busy_data (data_cal, freebusy);
-                       g_object_unref (data_cal);
-               }
-       }
-}
-
-/**
- * e_cal_meta_backend_discard_alarm_sync:
+ * e_cal_meta_backend_remove_component_sync:
  * @meta_backend: an #ECalMetaBackend
+ * @conflict_resolution: an #EConflictResolution to use
  * @uid: a component UID
- * @rid: (nullable): optional component Recurrence-ID, or %NULL
- * @auid: alarm UID to discard
+ * @extra: (nullable): extra data being saved with the component in the local cache, or %NULL
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
- * Discards alarm identified by @auid for component identified
- * by @uid and eventually @rid.
+ * Removes a component from the remote side, with all its detached instances.
  *
- * It is optional to implement this virtual method by the descendant.
- * The default implementation does nothing.
+ * It is mandatory to implement this virtual method by the descendant.
  *
  * Returns: Whether succeeded.
  *
  * Since: 3.26
  **/
 gboolean
-e_cal_meta_backend_discard_alarm_sync (ECalMetaBackend *meta_backend,
-                                      const gchar *uid,
-                                      const gchar *rid,
-                                      const gchar *auid,
-                                      GCancellable *cancellable,
-                                      GError **error)
+e_cal_meta_backend_remove_component_sync (ECalMetaBackend *meta_backend,
+                                         EConflictResolution conflict_resolution,
+                                         const gchar *uid,
+                                         const gchar *extra,
+                                         GCancellable *cancellable,
+                                         GError **error)
 {
        ECalMetaBackendClass *klass;
 
        g_return_val_if_fail (E_IS_CAL_META_BACKEND (meta_backend), FALSE);
        g_return_val_if_fail (uid != NULL, FALSE);
-       g_return_val_if_fail (auid != NULL, FALSE);
 
        klass = E_CAL_META_BACKEND_GET_CLASS (meta_backend);
        g_return_val_if_fail (klass != NULL, FALSE);
-       g_return_val_if_fail (klass->discard_alarm_sync != NULL, FALSE);
+       g_return_val_if_fail (klass->remove_component_sync != NULL, FALSE);
 
-       return klass->discard_alarm_sync (meta_backend, uid, rid, auid, cancellable, error);
+       return klass->remove_component_sync (meta_backend, conflict_resolution, uid, extra, cancellable, 
error);
 }
diff --git a/src/calendar/libedata-cal/e-cal-meta-backend.h b/src/calendar/libedata-cal/e-cal-meta-backend.h
index f1d64ae..4873069 100644
--- a/src/calendar/libedata-cal/e-cal-meta-backend.h
+++ b/src/calendar/libedata-cal/e-cal-meta-backend.h
@@ -80,7 +80,7 @@ typedef struct _ECalMetaBackendPrivate ECalMetaBackendPrivate;
  **/
 struct _ECalMetaBackend {
        /*< private >*/
-       ECalBackend parent;
+       ECalBackendSync parent;
        ECalMetaBackendPrivate *priv;
 };
 
@@ -93,7 +93,7 @@ struct _ECalMetaBackend {
  */
 struct _ECalMetaBackendClass {
        /*< private >*/
-       ECalBackendClass parent_class;
+       ECalBackendSyncClass parent_class;
 
        /* Virtual methods */
        gboolean        (* connect_sync)        (ECalMetaBackend *meta_backend,
@@ -104,12 +104,16 @@ struct _ECalMetaBackendClass {
                                                 GError **error);
 
        gboolean        (* get_changes_sync)    (ECalMetaBackend *meta_backend,
+                                                const gchar *last_sync_tag,
+                                                gchar **out_new_sync_tag,
+                                                gboolean *out_repeat,
                                                 GSList **out_created_objects, /* ECalMetaBackendInfo * */
                                                 GSList **out_modified_objects, /* ECalMetaBackendInfo * */
                                                 GSList **out_removed_objects, /* ECalMetaBackendInfo * */
                                                 GCancellable *cancellable,
                                                 GError **error);
        gboolean        (* list_existing_sync)  (ECalMetaBackend *meta_backend,
+                                                gchar **out_new_sync_tag,
                                                 GSList **out_existing_objects, /* ECalMetaBackendInfo * */
                                                 GCancellable *cancellable,
                                                 GError **error);
@@ -118,27 +122,21 @@ struct _ECalMetaBackendClass {
                                                 gboolean overwrite_existing,
                                                 EConflictResolution conflict_resolution,
                                                 const GSList *instances, /* ECalComponent * */
+                                                const gchar *extra,
+                                                gchar **out_new_uid,
                                                 GCancellable *cancellable,
                                                 GError **error);
        gboolean        (* load_component_sync) (ECalMetaBackend *meta_backend,
                                                 const gchar *uid,
-                                                const gchar *rid,
                                                 icalcomponent **out_instances,
+                                                gchar **out_extra,
                                                 GCancellable *cancellable,
                                                 GError **error);
-
-       /* Optional methods from ECalBackend */
-       gboolean        (* get_free_busy_sync)  (ECalMetaBackend *meta_backend,
-                                                const GSList *users,
-                                                time_t start,
-                                                time_t end,
-                                                GSList **out_freebusy, /* gchar * (iCal strings) */
-                                                GCancellable *cancellable,
-                                                GError **error);
-       gboolean        (* discard_alarm_sync)  (ECalMetaBackend *meta_backend,
+       gboolean        (* remove_component_sync)
+                                               (ECalMetaBackend *meta_backend,
+                                                EConflictResolution conflict_resolution,
                                                 const gchar *uid,
-                                                const gchar *rid,
-                                                const gchar *auid,
+                                                const gchar *extra,
                                                 GCancellable *cancellable,
                                                 GError **error);
 
@@ -148,6 +146,8 @@ struct _ECalMetaBackendClass {
 
 GType          e_cal_meta_backend_get_type     (void) G_GNUC_CONST;
 
+const gchar *  e_cal_meta_backend_get_capabilities
+                                               (ECalMetaBackend *meta_backend);
 void           e_cal_meta_backend_set_cache    (ECalMetaBackend *meta_backend,
                                                 ECalCache *cache);
 ECalCache *    e_cal_meta_backend_ref_cache    (ECalMetaBackend *meta_backend);
@@ -174,6 +174,9 @@ gboolean    e_cal_meta_backend_disconnect_sync
                                                 GError **error);
 gboolean       e_cal_meta_backend_get_changes_sync
                                                (ECalMetaBackend *meta_backend,
+                                                const gchar *last_sync_tag,
+                                                gchar **out_new_sync_tag,
+                                                gboolean *out_repeat,
                                                 GSList **out_created_objects, /* ECalMetaBackendInfo * */
                                                 GSList **out_modified_objects, /* ECalMetaBackendInfo * */
                                                 GSList **out_removed_objects, /* ECalMetaBackendInfo * */
@@ -181,6 +184,7 @@ gboolean    e_cal_meta_backend_get_changes_sync
                                                 GError **error);
 gboolean       e_cal_meta_backend_list_existing_sync
                                                (ECalMetaBackend *meta_backend,
+                                                gchar **out_new_sync_tag,
                                                 GSList **out_existing_objects, /* ECalMetaBackendInfo * */
                                                 GCancellable *cancellable,
                                                 GError **error);
@@ -189,32 +193,22 @@ gboolean  e_cal_meta_backend_save_component_sync
                                                 gboolean overwrite_existing,
                                                 EConflictResolution conflict_resolution,
                                                 const GSList *instances, /* ECalComponent * */
+                                                const gchar *extra,
+                                                gchar **out_new_uid,
                                                 GCancellable *cancellable,
                                                 GError **error);
 gboolean       e_cal_meta_backend_load_component_sync
                                                (ECalMetaBackend *meta_backend,
                                                 const gchar *uid,
-                                                const gchar *rid,
                                                 icalcomponent **out_component,
+                                                gchar **out_extra,
                                                 GCancellable *cancellable,
                                                 GError **error);
-
-gboolean       e_cal_meta_backend_get_free_busy_sync
-                                               (ECalMetaBackend *meta_backend,
-                                                const GSList *users,
-                                                time_t start,
-                                                time_t end,
-                                                GSList **out_freebusy, /* gchar * */
-                                                GCancellable *cancellable,
-                                                GError **error);
-void           e_cal_meta_backend_notify_free_busy
-                                               (ECalMetaBackend *meta_backend,
-                                                const GSList *freebusy); /* gchar * */
-gboolean       e_cal_meta_backend_discard_alarm_sync
+gboolean       e_cal_meta_backend_remove_component_sync
                                                (ECalMetaBackend *meta_backend,
+                                                EConflictResolution conflict_resolution,
                                                 const gchar *uid,
-                                                const gchar *rid,
-                                                const gchar *auid,
+                                                const gchar *extra,
                                                 GCancellable *cancellable,
                                                 GError **error);
 
diff --git a/src/libebackend/e-cache.h b/src/libebackend/e-cache.h
index 0b83aee..90ce333 100644
--- a/src/libebackend/e-cache.h
+++ b/src/libebackend/e-cache.h
@@ -204,15 +204,17 @@ typedef enum {
 
 /**
  * ECacheOfflineFlag:
+ * @E_CACHE_OFFLINE_UNKNOWN: Do not know current online/offline state
  * @E_CACHE_IS_ONLINE: The operation is done in online
  * @E_CACHE_IS_OFFLINE: The operation is done in offline
  *
  * Declares whether the operation is done in online or offline.
- * This influences the offline state of the related obejcts.
+ * This influences the offline state of the related objects.
  *
  * Since: 3.26
  **/
 typedef enum {
+       E_CACHE_OFFLINE_UNKNOWN = -1,
        E_CACHE_IS_ONLINE = 0,
        E_CACHE_IS_OFFLINE
 } ECacheOfflineFlag;
diff --git a/src/libedataserver/e-data-server-util.c b/src/libedataserver/e-data-server-util.c
index 6f57699..7cdae3e 100644
--- a/src/libedataserver/e-data-server-util.c
+++ b/src/libedataserver/e-data-server-util.c
@@ -2968,3 +2968,47 @@ e_util_get_source_oauth2_access_token_sync (ESource *source,
 
        return success;
 }
+
+static gpointer
+unref_object_in_thread (gpointer ptr)
+{
+       GObject *object = ptr;
+
+       g_return_val_if_fail (object != NULL, NULL);
+
+       g_object_unref (object);
+
+       return NULL;
+}
+
+/**
+ * e_util_unref_in_thread:
+ * @object: a #GObject
+ *
+ * Unrefs the given @object in a dedicated thread. This is useful when unreffing
+ * object deep in call stack when the caller might still use the object and
+ * this being the last reference to it.
+ *
+ * Since: 3.26
+ **/
+void
+e_util_unref_in_thread (gpointer object)
+{
+       GThread *thread;
+       GError *error = NULL;
+
+       if (!object)
+               return;
+
+       g_return_if_fail (G_IS_OBJECT (object));
+
+       thread = g_thread_try_new (NULL, unref_object_in_thread, object, &error);
+       if (thread) {
+               g_thread_unref (thread);
+       } else {
+               g_warning ("%s: Failed to run thread: %s", G_STRFUNC, error ? error->message : "Unknown 
error");
+               g_object_unref (object);
+       }
+
+       g_clear_error (&error);
+}
diff --git a/src/libedataserver/e-data-server-util.h b/src/libedataserver/e-data-server-util.h
index 886fe51..9dfb801 100644
--- a/src/libedataserver/e-data-server-util.h
+++ b/src/libedataserver/e-data-server-util.h
@@ -281,6 +281,9 @@ gboolean    e_util_get_source_oauth2_access_token_sync
                                                 gint *out_expires_in_seconds,
                                                 GCancellable *cancellable,
                                                 GError **error);
+
+void           e_util_unref_in_thread          (gpointer object);
+
 G_END_DECLS
 
 #endif /* E_DATA_SERVER_UTIL_H */


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