[evolution-data-server] Bug #655190 - Sluggish performance interacting with calendar/tasks



commit 549a80da23e1c6879ced6a277c547fb94a2a7e11
Author: Milan Crha <mcrha redhat com>
Date:   Tue Aug 2 15:12:14 2011 +0200

    Bug #655190 - Sluggish performance interacting with calendar/tasks

 addressbook/libebook/e-book-client.c       |   92 ++++-
 addressbook/libedata-book/e-book-backend.c |   30 ++
 addressbook/libedata-book/e-book-backend.h |    1 +
 addressbook/libedata-book/e-data-book.c    |   19 +
 addressbook/libedata-book/e-data-book.h    |    1 +
 addressbook/libegdbus/e-gdbus-book.c       |   32 +-
 addressbook/libegdbus/e-gdbus-book.h       |    2 +
 calendar/libecal/e-cal-client.c            |  682 +++++++++++++++++++++++-----
 calendar/libecal/e-cal-client.h            |    6 +-
 calendar/libedata-cal/e-cal-backend.c      |   30 ++
 calendar/libedata-cal/e-cal-backend.h      |    1 +
 calendar/libedata-cal/e-data-cal.c         |   19 +
 calendar/libedata-cal/e-data-cal.h         |    1 +
 calendar/libegdbus/e-gdbus-cal.c           |   52 +--
 calendar/libegdbus/e-gdbus-cal.h           |    2 +
 configure.ac                               |   10 +-
 libedataserver/e-client-private.h          |    7 +
 libedataserver/e-client.c                  |  126 +++++
 libedataserver/e-client.h                  |    1 +
 libedataserver/e-gdbus-marshallers.list    |    1 +
 libedataserver/e-gdbus-templates.c         |   34 ++-
 libedataserver/e-gdbus-templates.h         |    3 +
 libedataserverui/e-client-utils.c          |   59 +++-
 23 files changed, 1033 insertions(+), 178 deletions(-)
---
diff --git a/addressbook/libebook/e-book-client.c b/addressbook/libebook/e-book-client.c
index 4899a58..a2fd03f 100644
--- a/addressbook/libebook/e-book-client.c
+++ b/addressbook/libebook/e-book-client.c
@@ -392,6 +392,25 @@ opened_cb (EGdbusBook *object, const gchar * const *error_strv, EBookClient *cli
 		g_error_free (error);
 }
 
+static void
+backend_property_changed_cb (EGdbusBook *object, const gchar * const *name_value_strv, EBookClient *client)
+{
+	gchar *prop_name = NULL, *prop_value = NULL;
+
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (E_IS_BOOK_CLIENT (client));
+	g_return_if_fail (name_value_strv != NULL);
+	g_return_if_fail (e_gdbus_templates_decode_two_strings (name_value_strv, &prop_name, &prop_value));
+	g_return_if_fail (prop_name != NULL);
+	g_return_if_fail (*prop_name);
+	g_return_if_fail (prop_value != NULL);
+
+	e_client_emit_backend_property_changed (E_CLIENT (client), prop_name, prop_value);
+
+	g_free (prop_name);
+	g_free (prop_value);
+}
+
 /**
  * e_book_client_new:
  * @source: An #ESource pointer
@@ -496,6 +515,7 @@ e_book_client_new (ESource *source, GError **error)
 	g_signal_connect (client->priv->gdbus_book, "online", G_CALLBACK (online_cb), client);
 	g_signal_connect (client->priv->gdbus_book, "auth-required", G_CALLBACK (auth_required_cb), client);
 	g_signal_connect (client->priv->gdbus_book, "opened", G_CALLBACK (opened_cb), client);
+	g_signal_connect (client->priv->gdbus_book, "backend-property-changed", G_CALLBACK (backend_property_changed_cb), client);
 
 	return client;
 }
@@ -884,24 +904,76 @@ e_book_client_is_self (EContact *contact)
 	return is_self;
 }
 
+static gboolean
+book_client_get_backend_property_from_cache_finish (EClient *client, GAsyncResult *result, gchar **prop_value, GError **error)
+{
+	GSimpleAsyncResult *simple;
+	GError *local_error = NULL;
+
+	g_return_val_if_fail (client != NULL, FALSE);
+	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
+	g_return_val_if_fail (result != NULL, FALSE);
+	g_return_val_if_fail (prop_value != NULL, FALSE);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (client), book_client_get_backend_property_from_cache_finish), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	if (g_simple_async_result_propagate_error (simple, &local_error)) {
+		e_client_unwrap_dbus_error (client, local_error, error);
+		return FALSE;
+	}
+
+	*prop_value = g_strdup (g_simple_async_result_get_op_res_gpointer (simple));
+
+	return *prop_value != NULL;
+}
+
 static void
 book_client_get_backend_property (EClient *client, const gchar *prop_name, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
 {
-	e_client_proxy_call_string (client, prop_name, cancellable, callback, user_data, book_client_get_backend_property,
+	gchar *prop_value;
+
+	prop_value = e_client_get_backend_property_from_cache (client, prop_name);
+	if (prop_value) {
+		e_client_finish_async_without_dbus (client, cancellable, callback, user_data, book_client_get_backend_property_from_cache_finish, prop_value, g_free);
+	} else {
+		e_client_proxy_call_string_with_res_op_data (client, prop_name, cancellable, callback, user_data, book_client_get_backend_property, prop_name,
 			e_gdbus_book_call_get_backend_property,
 			NULL, NULL, e_gdbus_book_call_get_backend_property_finish, NULL, NULL);
+	}
 }
 
 static gboolean
 book_client_get_backend_property_finish (EClient *client, GAsyncResult *result, gchar **prop_value, GError **error)
 {
-	return e_client_proxy_call_finish_string (client, result, prop_value, error, book_client_get_backend_property);
+	gchar *str = NULL;
+	gboolean res;
+
+	g_return_val_if_fail (prop_value != NULL, FALSE);
+
+	if (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)) == book_client_get_backend_property_from_cache_finish) {
+		res = book_client_get_backend_property_from_cache_finish (client, result, &str, error);
+	} else {
+		res = e_client_proxy_call_finish_string (client, result, &str, error, book_client_get_backend_property);
+		if (res && str) {
+			const gchar *prop_name = g_object_get_data (G_OBJECT (result), "res-op-data");
+
+			if (prop_name && *prop_name)
+				e_client_update_backend_property_cache (client, prop_name, str);
+		}
+	}
+
+	*prop_value = str;
+
+	return res;
 }
 
 static gboolean
 book_client_get_backend_property_sync (EClient *client, const gchar *prop_name, gchar **prop_value, GCancellable *cancellable, GError **error)
 {
 	EBookClient *book_client;
+	gchar *prop_val;
+	gboolean res;
 
 	g_return_val_if_fail (client != NULL, FALSE);
 	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
@@ -915,7 +987,21 @@ book_client_get_backend_property_sync (EClient *client, const gchar *prop_name,
 		return FALSE;
 	}
 
-	return e_client_proxy_call_sync_string__string (client, prop_name, prop_value, cancellable, error, e_gdbus_book_call_get_backend_property_sync);
+	prop_val = e_client_get_backend_property_from_cache (client, prop_name);
+	if (prop_val) {
+		g_return_val_if_fail (prop_value != NULL, FALSE);
+
+		*prop_value = prop_val;
+
+		return TRUE;
+	}
+
+	res = e_client_proxy_call_sync_string__string (client, prop_name, prop_value, cancellable, error, e_gdbus_book_call_get_backend_property_sync);
+
+	if (res && prop_value)
+		e_client_update_backend_property_cache (client, prop_name, *prop_value);
+
+	return res;
 }
 
 static void
diff --git a/addressbook/libedata-book/e-book-backend.c b/addressbook/libedata-book/e-book-backend.c
index 49d7359..d4b345e 100644
--- a/addressbook/libedata-book/e-book-backend.c
+++ b/addressbook/libedata-book/e-book-backend.c
@@ -1270,6 +1270,36 @@ e_book_backend_notify_opened (EBookBackend *backend, GError *error)
 }
 
 /**
+ * e_book_backend_notify_property_changed:
+ * @backend: an #EBookBackend
+ * @prop_name: property name, which changed
+ * @prop_value: new property value
+ *
+ * Notifies clients about property value change.
+ **/
+void
+e_book_backend_notify_property_changed (EBookBackend *backend, const gchar *prop_name, const gchar *prop_value)
+{
+	EBookBackendPrivate *priv;
+	GSList *clients;
+
+	g_return_if_fail (backend != NULL);
+	g_return_if_fail (E_IS_BOOK_BACKEND (backend));
+	g_return_if_fail (backend->priv != NULL);
+	g_return_if_fail (prop_name != NULL);
+	g_return_if_fail (*prop_name != '\0');
+	g_return_if_fail (prop_value != NULL);
+
+	priv = backend->priv;
+	g_mutex_lock (priv->clients_mutex);
+
+	for (clients = priv->clients; clients != NULL; clients = g_slist_next (clients))
+		e_data_book_report_backend_property_changed (E_DATA_BOOK (clients->data), prop_name, prop_value);
+
+	g_mutex_unlock (priv->clients_mutex);
+}
+
+/**
  * e_book_backend_respond_opened:
  * @backend: an #EBookBackend
  * @book: an #EDataBook
diff --git a/addressbook/libedata-book/e-book-backend.h b/addressbook/libedata-book/e-book-backend.h
index a9f81d3..f034730 100644
--- a/addressbook/libedata-book/e-book-backend.h
+++ b/addressbook/libedata-book/e-book-backend.h
@@ -130,6 +130,7 @@ void		e_book_backend_notify_readonly	(EBookBackend *backend, gboolean is_readonl
 void		e_book_backend_notify_online	(EBookBackend *backend, gboolean is_online);
 void		e_book_backend_notify_auth_required (EBookBackend *backend, gboolean is_self, const ECredentials *credentials);
 void		e_book_backend_notify_opened	(EBookBackend *backend, GError *error);
+void		e_book_backend_notify_property_changed (EBookBackend *backend, const gchar *prop_name, const gchar *prop_value);
 
 void		e_book_backend_sync		(EBookBackend *backend);
 
diff --git a/addressbook/libedata-book/e-data-book.c b/addressbook/libedata-book/e-data-book.c
index 26c10ab..5c44a06 100644
--- a/addressbook/libedata-book/e-data-book.c
+++ b/addressbook/libedata-book/e-data-book.c
@@ -1047,6 +1047,25 @@ e_data_book_report_opened (EDataBook *book, const GError *error)
 	g_strfreev (strv_error);
 }
 
+/* Notifies client about certain property value change */
+void
+e_data_book_report_backend_property_changed (EDataBook *book, const gchar *prop_name, const gchar *prop_value)
+{
+	gchar **strv;
+
+	g_return_if_fail (book != NULL);
+	g_return_if_fail (prop_name != NULL);
+	g_return_if_fail (*prop_name != '\0');
+	g_return_if_fail (prop_value != NULL);
+
+	strv = e_gdbus_templates_encode_two_strings (prop_name, prop_value);
+	g_return_if_fail (strv != NULL);
+
+	e_gdbus_book_emit_backend_property_changed (book->priv->gdbus_object, (const gchar * const *) strv);
+
+	g_strfreev (strv);
+}
+
 /**
  * e_data_book_register_gdbus_object:
  *
diff --git a/addressbook/libedata-book/e-data-book.h b/addressbook/libedata-book/e-data-book.h
index 5185eba..f875d5c 100644
--- a/addressbook/libedata-book/e-data-book.h
+++ b/addressbook/libedata-book/e-data-book.h
@@ -149,6 +149,7 @@ void		e_data_book_report_readonly			(EDataBook *book, gboolean readonly);
 void		e_data_book_report_online			(EDataBook *book, gboolean is_online);
 void		e_data_book_report_auth_required		(EDataBook *book, const ECredentials *credentials);
 void		e_data_book_report_opened			(EDataBook *book, const GError *error);
+void		e_data_book_report_backend_property_changed	(EDataBook *book, const gchar *prop_name, const gchar *prop_value);
 
 gchar *		e_data_book_string_slist_to_comma_string	(const GSList *strings);
 
diff --git a/addressbook/libegdbus/e-gdbus-book.c b/addressbook/libegdbus/e-gdbus-book.c
index b115be2..1096692 100644
--- a/addressbook/libegdbus/e-gdbus-book.c
+++ b/addressbook/libegdbus/e-gdbus-book.c
@@ -40,6 +40,7 @@ enum
 	__ONLINE_SIGNAL,
 	__AUTH_REQUIRED_SIGNAL,
 	__OPENED_SIGNAL,
+	__BACKEND_PROPERTY_CHANGED_SIGNAL,
 	__OPEN_METHOD,
 	__OPEN_DONE_SIGNAL,
 	__REMOVE_METHOD,
@@ -118,6 +119,7 @@ E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_BOOLEAN (GDBUS_BOOK_INTERFACE_NAME, readonl
 E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_BOOLEAN (GDBUS_BOOK_INTERFACE_NAME, online)
 E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_STRV    (GDBUS_BOOK_INTERFACE_NAME, auth_required)
 E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_STRV    (GDBUS_BOOK_INTERFACE_NAME, opened)
+E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_STRV    (GDBUS_BOOK_INTERFACE_NAME, backend_property_changed)
 
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_VOID	(GDBUS_BOOK_INTERFACE_NAME, open)
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_VOID	(GDBUS_BOOK_INTERFACE_NAME, remove)
@@ -147,6 +149,7 @@ e_gdbus_book_default_init (EGdbusBookIface *iface)
 	E_INIT_GDBUS_SIGNAL_BOOLEAN		(EGdbusBookIface, "online",		online,		__ONLINE_SIGNAL)
 	E_INIT_GDBUS_SIGNAL_STRV   		(EGdbusBookIface, "auth_required", 	auth_required,	__AUTH_REQUIRED_SIGNAL)
 	E_INIT_GDBUS_SIGNAL_STRV   		(EGdbusBookIface, "opened", 		opened,		__OPENED_SIGNAL)
+	E_INIT_GDBUS_SIGNAL_STRV   		(EGdbusBookIface, "backend_property_changed", 	backend_property_changed,	__BACKEND_PROPERTY_CHANGED_SIGNAL)
 
 	/* GObject signals definitions for D-Bus methods: */
 	E_INIT_GDBUS_METHOD_ASYNC_BOOLEAN__VOID	(EGdbusBookIface, "open",			open, __OPEN_METHOD, __OPEN_DONE_SIGNAL)
@@ -371,31 +374,14 @@ e_gdbus_book_call_get_backend_property_sync (GDBusProxy *proxy, const gchar *in_
 gchar **
 e_gdbus_book_encode_set_backend_property (const gchar *in_prop_name, const gchar *in_prop_value)
 {
-	gchar **strv;
-
-	strv = g_new0 (gchar *, 3);
-	strv[0] = e_util_utf8_make_valid (in_prop_name ? in_prop_name : "");
-	strv[1] = e_util_utf8_make_valid (in_prop_value ? in_prop_value : "");
-	strv[2] = NULL;
-
-	return strv;
+	return e_gdbus_templates_encode_two_strings (in_prop_name, in_prop_value);
 }
 
 /* free out_prop_name and out_prop_value with g_free() */
 gboolean
 e_gdbus_book_decode_set_backend_property (const gchar * const *in_strv, gchar **out_prop_name, gchar **out_prop_value)
 {
-	g_return_val_if_fail (in_strv != NULL, FALSE);
-	g_return_val_if_fail (in_strv[0] != NULL, FALSE);
-	g_return_val_if_fail (in_strv[1] != NULL, FALSE);
-	g_return_val_if_fail (in_strv[2] == NULL, FALSE);
-	g_return_val_if_fail (out_prop_name != NULL, FALSE);
-	g_return_val_if_fail (out_prop_value != NULL, FALSE);
-
-	*out_prop_name = g_strdup (in_strv[0]);
-	*out_prop_value = g_strdup (in_strv[1]);
-
-	return TRUE;
+	return e_gdbus_templates_decode_two_strings (in_strv, out_prop_name, out_prop_value);
 }
 
 void
@@ -570,11 +556,18 @@ e_gdbus_book_emit_opened (EGdbusBook *object, const gchar * const *arg_error)
 	g_signal_emit (object, signals[__OPENED_SIGNAL], 0, arg_error);
 }
 
+void
+e_gdbus_book_emit_backend_property_changed (EGdbusBook *object, const gchar * const *arg_name_value)
+{
+	g_signal_emit (object, signals[__BACKEND_PROPERTY_CHANGED_SIGNAL], 0, arg_name_value);
+}
+
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (book, backend_error, message, "s")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (book, readonly, is_readonly, "b")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (book, online, is_online, "b")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (book, auth_required, credentials, "as")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (book, opened, error, "as")
+E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (book, backend_property_changed, name_value, "as")
 
 E_DECLARE_GDBUS_ASYNC_METHOD_1			(book, open, only_if_exists, "b")
 E_DECLARE_GDBUS_ASYNC_METHOD_0			(book, remove)
@@ -622,6 +615,7 @@ static const GDBusSignalInfo * const e_gdbus_book_signal_info_pointers[] =
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, online),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, auth_required),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, opened),
+	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, backend_property_changed),
 
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, open_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, remove_done),
diff --git a/addressbook/libegdbus/e-gdbus-book.h b/addressbook/libegdbus/e-gdbus-book.h
index 4fa97da..aea23ef 100644
--- a/addressbook/libegdbus/e-gdbus-book.h
+++ b/addressbook/libegdbus/e-gdbus-book.h
@@ -117,6 +117,7 @@ struct _EGdbusBookIface
 	void	(*online)			(EGdbusBook *object, gboolean arg_is_online);
 	void	(*auth_required)		(EGdbusBook *object, const gchar * const *arg_credentials);
 	void	(*opened)			(EGdbusBook *object, const gchar * const *arg_error);
+	void	(*backend_property_changed)	(EGdbusBook *object, const gchar * const *arg_name_value);
 
 	/* Signal handlers for handling D-Bus method calls: */
 	gboolean (*handle_open)			(EGdbusBook *object, GDBusMethodInvocation *invocation, gboolean in_only_if_exists);
@@ -268,6 +269,7 @@ void e_gdbus_book_emit_readonly		(EGdbusBook *object, gboolean arg_is_readonly);
 void e_gdbus_book_emit_online		(EGdbusBook *object, gboolean arg_is_online);
 void e_gdbus_book_emit_auth_required	(EGdbusBook *object, const gchar * const *arg_credentials);
 void e_gdbus_book_emit_opened		(EGdbusBook *object, const gchar * const *arg_error);
+void e_gdbus_book_emit_backend_property_changed	(EGdbusBook *object, const gchar * const *arg_name_value);
 
 G_END_DECLS
 
diff --git a/calendar/libecal/e-cal-client.c b/calendar/libecal/e-cal-client.c
index 64be096..7d956b3 100644
--- a/calendar/libecal/e-cal-client.c
+++ b/calendar/libecal/e-cal-client.c
@@ -479,6 +479,25 @@ free_busy_data_cb (EGdbusCal *object, const gchar * const *free_busy_strv, ECalC
 	e_client_util_free_object_slist (ecalcomps);
 }
 
+static void
+backend_property_changed_cb (EGdbusCal *object, const gchar * const *name_value_strv, ECalClient *client)
+{
+	gchar *prop_name = NULL, *prop_value = NULL;
+
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (E_IS_CAL_CLIENT (client));
+	g_return_if_fail (name_value_strv != NULL);
+	g_return_if_fail (e_gdbus_templates_decode_two_strings (name_value_strv, &prop_name, &prop_value));
+	g_return_if_fail (prop_name != NULL);
+	g_return_if_fail (*prop_name);
+	g_return_if_fail (prop_value != NULL);
+
+	e_client_emit_backend_property_changed (E_CLIENT (client), prop_name, prop_value);
+
+	g_free (prop_name);
+	g_free (prop_value);
+}
+
 static EDataCalObjType
 convert_type (ECalClientSourceType type)
 {
@@ -613,6 +632,7 @@ e_cal_client_new (ESource *source, ECalClientSourceType source_type, GError **er
 	g_signal_connect (client->priv->gdbus_cal, "auth-required", G_CALLBACK (auth_required_cb), client);
 	g_signal_connect (client->priv->gdbus_cal, "opened", G_CALLBACK (opened_cb), client);
 	g_signal_connect (client->priv->gdbus_cal, "free-busy-data", G_CALLBACK (free_busy_data_cb), client);
+	g_signal_connect (client->priv->gdbus_cal, "backend-property-changed", G_CALLBACK (backend_property_changed_cb), client);
 
 	return client;
 }
@@ -903,7 +923,9 @@ e_cal_client_get_local_attachment_store (ECalClient *client)
 	if (client->priv->cache_dir || !client->priv->gdbus_cal)
 		return client->priv->cache_dir;
 
-	e_gdbus_cal_call_get_backend_property_sync (client->priv->gdbus_cal, CLIENT_BACKEND_PROPERTY_CACHE_DIR, &cache_dir, NULL, &error);
+	cache_dir = e_client_get_backend_property_from_cache (E_CLIENT (client), CLIENT_BACKEND_PROPERTY_CACHE_DIR);
+	if (!cache_dir)
+		e_gdbus_cal_call_get_backend_property_sync (client->priv->gdbus_cal, CLIENT_BACKEND_PROPERTY_CACHE_DIR, &cache_dir, NULL, &error);
 
 	if (error == NULL) {
 		client->priv->cache_dir = cache_dir;
@@ -1252,6 +1274,8 @@ process_detached_instances (GSList *instances, GSList *detached_instances)
 		ECalComponentRange recur_id, instance_recur_id;
 
 		processed = FALSE;
+		recur_id.type = E_CAL_COMPONENT_RANGE_SINGLE;
+		instance_recur_id.type = E_CAL_COMPONENT_RANGE_SINGLE;
 
 		cid = dl->data;
 		e_cal_component_get_uid (cid->comp, &uid);
@@ -1343,63 +1367,18 @@ process_detached_instances (GSList *instances, GSList *detached_instances)
 }
 
 static void
-generate_instances (ECalClient *client, time_t start, time_t end, const gchar *uid,
+generate_instances (ECalClient *client, time_t start, time_t end, GSList *objects, GCancellable *cancellable,
 		    ECalRecurInstanceFn cb, gpointer cb_data)
 {
-	GSList *objects = NULL;
 	GSList *instances, *detached_instances = NULL;
 	GSList *l;
-	gchar *query;
-	gchar *iso_start, *iso_end;
 	ECalClientPrivate *priv;
 
 	priv = client->priv;
 
-	/* Generate objects */
-	if (uid && *uid) {
-		GError *error = NULL;
-		gint tries = 0;
-
- try_again:
-		if (!e_cal_client_get_objects_for_uid_sync (client, uid, &objects, NULL, &error)) {
-			if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY) && tries >= 10) {
-				tries++;
-				g_usleep (500);
-				g_clear_error (&error);
-
-				goto try_again;
-			}
-
-			unwrap_dbus_error (error, &error);
-			g_message ("Failed to get recurrence objects for uid %s \n", error ? error->message : "Unknown error");
-			g_clear_error (&error);
-			return;
-		}
-	} else {
-		iso_start = isodate_from_time_t (start);
-		if (!iso_start)
-			return;
-
-		iso_end = isodate_from_time_t (end);
-		if (!iso_end) {
-			g_free (iso_start);
-			return;
-		}
-
-		query = g_strdup_printf ("(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))",
-					 iso_start, iso_end);
-		g_free (iso_start);
-		g_free (iso_end);
-		if (!e_cal_client_get_object_list_as_comps_sync (client, query, &objects, NULL, NULL)) {
-			g_free (query);
-			return;
-		}
-		g_free (query);
-	}
-
 	instances = NULL;
 
-	for (l = objects; l; l = l->next) {
+	for (l = objects; l && !g_cancellable_is_cancelled (cancellable); l = l->next) {
 		ECalComponent *comp;
 		icaltimezone *default_zone;
 
@@ -1416,7 +1395,7 @@ generate_instances (ECalClient *client, time_t start, time_t end, const gchar *u
 
 			/* keep the detached instances apart */
 			ci = g_new0 (struct comp_instance, 1);
-			ci->comp = comp;
+			ci->comp = g_object_ref (comp);
 
 			e_cal_component_get_dtstart (comp, &dtstart);
 			e_cal_component_get_dtend (comp, &dtend);
@@ -1469,7 +1448,7 @@ generate_instances (ECalClient *client, time_t start, time_t end, const gchar *u
 			/* Get the start timezone */
 			e_cal_component_get_dtstart (comp, &datetime);
 			if (datetime.tzid)
-				e_cal_client_get_timezone_sync (client, datetime.tzid, &start_zone, NULL, NULL);
+				e_cal_client_get_timezone_sync (client, datetime.tzid, &start_zone, cancellable, NULL);
 			else
 				start_zone = NULL;
 			e_cal_component_free_datetime (&datetime);
@@ -1483,18 +1462,20 @@ generate_instances (ECalClient *client, time_t start, time_t end, const gchar *u
 							default_zone);
 
 			g_free (instances_hold);
-			g_object_unref (comp);
 		}
 	}
 
+	g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
 	g_slist_free (objects);
 
 	/* Generate instances and spew them out */
 
-	instances = g_slist_sort (instances, compare_comp_instance);
-	instances = process_detached_instances (instances, detached_instances);
+	if (!g_cancellable_is_cancelled (cancellable)) {
+		instances = g_slist_sort (instances, compare_comp_instance);
+		instances = process_detached_instances (instances, detached_instances);
+	}
 
-	for (l = instances; l; l = l->next) {
+	for (l = instances; l && !g_cancellable_is_cancelled (cancellable); l = l->next) {
 		struct comp_instance *ci;
 		gboolean result;
 
@@ -1529,11 +1510,305 @@ generate_instances (ECalClient *client, time_t start, time_t end, const gchar *u
 	g_slist_free (detached_instances);
 }
 
+static GSList *
+get_objects_sync (ECalClient *client, time_t start, time_t end, const gchar *uid)
+{
+	GSList *objects = NULL;
+
+	/* Generate objects */
+	if (uid && *uid) {
+		GError *error = NULL;
+		gint tries = 0;
+
+ try_again:
+		if (!e_cal_client_get_objects_for_uid_sync (client, uid, &objects, NULL, &error)) {
+			if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY) && tries >= 10) {
+				tries++;
+				g_usleep (500);
+				g_clear_error (&error);
+
+				goto try_again;
+			}
+
+			unwrap_dbus_error (error, &error);
+			g_message ("Failed to get recurrence objects for uid %s \n", error ? error->message : "Unknown error");
+			g_clear_error (&error);
+			return NULL;
+		}
+	} else {
+		gchar *iso_start, *iso_end;
+		gchar *query;
+
+		iso_start = isodate_from_time_t (start);
+		if (!iso_start)
+			return NULL;
+
+		iso_end = isodate_from_time_t (end);
+		if (!iso_end) {
+			g_free (iso_start);
+			return NULL;
+		}
+
+		query = g_strdup_printf ("(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))",
+					 iso_start, iso_end);
+		g_free (iso_start);
+		g_free (iso_end);
+		if (!e_cal_client_get_object_list_as_comps_sync (client, query, &objects, NULL, NULL)) {
+			g_free (query);
+			return NULL;
+		}
+		g_free (query);
+	}
+
+	return objects;
+}
+
+struct get_objects_async_data
+{
+	GCancellable *cancellable;
+	ECalClient *client;
+	time_t start;
+	time_t end;
+	ECalRecurInstanceFn cb;
+	gpointer cb_data;
+	GDestroyNotify destroy_cb_data;
+	gchar *uid;
+	gchar *query;
+	guint tries;
+	void (* ready_cb) (struct get_objects_async_data *goad, GSList *objects);
+	icaltimezone *start_zone;
+	ECalComponent *comp;
+};
+
+static void
+free_get_objects_async_data (struct get_objects_async_data *goad)
+{
+	if (!goad)
+		return;
+
+	if (goad->cancellable) {
+		g_cancellable_cancel (goad->cancellable);
+		g_object_unref (goad->cancellable);
+	}
+	if (goad->destroy_cb_data)
+		goad->destroy_cb_data (goad->cb_data);
+	if (goad->client)
+		g_object_unref (goad->client);
+	if (goad->comp)
+		g_object_unref (goad->comp);
+	g_free (goad->query);
+	g_free (goad->uid);
+	g_free (goad);
+}
+
+static gboolean repeat_get_objects_for_uid_timeout_cb (gpointer user_data);
+
+static void
+got_objects_for_uid_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
+{
+	struct get_objects_async_data *goad = user_data;
+	GSList *objects = NULL;
+	GError *error = NULL;
+
+	g_return_if_fail (source_object != NULL);
+	g_return_if_fail (result != NULL);
+	g_return_if_fail (goad != NULL);
+	g_return_if_fail (goad->client == E_CAL_CLIENT (source_object));
+
+	if (!e_cal_client_get_objects_for_uid_finish (goad->client, result, &objects, &error)) {
+		if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
+		    g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			free_get_objects_async_data (goad);
+			g_clear_error (&error);
+			return;
+		}
+
+		if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY) && goad->tries < 10) {
+			goad->tries++;
+			g_timeout_add (250, repeat_get_objects_for_uid_timeout_cb, goad);
+			g_clear_error (&error);
+			return;
+		}
+
+		g_clear_error (&error);
+		objects = NULL;
+	}
+
+	g_return_if_fail (goad->ready_cb != NULL);
+
+	/* takes care of the objects and goad */
+	goad->ready_cb (goad, objects);
+}
+
+static gboolean
+repeat_get_objects_for_uid_timeout_cb (gpointer user_data)
+{
+	struct get_objects_async_data *goad = user_data;
+
+	g_return_val_if_fail (goad != NULL, FALSE);
+
+	e_cal_client_get_objects_for_uid (goad->client, goad->uid, goad->cancellable, got_objects_for_uid_cb, goad);
+
+	return FALSE;
+}
+
+static gboolean repeat_get_object_list_as_comps_timeout_cb (gpointer user_data);
+
+static void
+got_object_list_as_comps_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
+{
+	struct get_objects_async_data *goad = user_data;
+	GSList *objects = NULL;
+	GError *error = NULL;
+
+	g_return_if_fail (source_object != NULL);
+	g_return_if_fail (result != NULL);
+	g_return_if_fail (goad != NULL);
+	g_return_if_fail (goad->client == E_CAL_CLIENT (source_object));
+
+	if (!e_cal_client_get_object_list_as_comps_finish (goad->client, result, &objects, &error)) {
+		if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
+		    g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			free_get_objects_async_data (goad);
+			g_clear_error (&error);
+			return;
+		}
+
+		if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY) && goad->tries < 10) {
+			goad->tries++;
+			g_timeout_add (250, repeat_get_object_list_as_comps_timeout_cb, goad);
+			g_clear_error (&error);
+			return;
+		}
+
+		g_clear_error (&error);
+		objects = NULL;
+	}
+
+	g_return_if_fail (goad->ready_cb != NULL);
+
+	/* takes care of the objects and goad */
+	goad->ready_cb (goad, objects);
+}
+
+static gboolean
+repeat_get_object_list_as_comps_timeout_cb (gpointer user_data)
+{
+	struct get_objects_async_data *goad = user_data;
+
+	g_return_val_if_fail (goad != NULL, FALSE);
+
+	e_cal_client_get_object_list_as_comps (goad->client, goad->query, goad->cancellable, got_object_list_as_comps_cb, goad);
+
+	return FALSE;
+}
+
+/* ready_cb may take care of both arguments, goad and objects; objects can be also NULL */
+static void
+get_objects_async (void (* ready_cb)(struct get_objects_async_data *goad, GSList *objects), struct get_objects_async_data *goad)
+{
+	g_return_if_fail (ready_cb != NULL);
+	g_return_if_fail (goad != NULL);
+
+	goad->ready_cb = ready_cb;
+
+	if (goad->uid && *goad->uid) {
+		e_cal_client_get_objects_for_uid (goad->client, goad->uid, goad->cancellable, got_objects_for_uid_cb, goad);
+	} else {
+		gchar *iso_start, *iso_end;
+
+		iso_start = isodate_from_time_t (goad->start);
+		if (!iso_start) {
+			free_get_objects_async_data (goad);
+			return;
+		}
+
+		iso_end = isodate_from_time_t (goad->end);
+		if (!iso_end) {
+			g_free (iso_start);
+			free_get_objects_async_data (goad);
+			return;
+		}
+
+		goad->query = g_strdup_printf ("(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))", iso_start, iso_end);
+
+		g_free (iso_start);
+		g_free (iso_end);
+
+		e_cal_client_get_object_list_as_comps (goad->client, goad->query, goad->cancellable, got_object_list_as_comps_cb, goad);
+	}
+}
+
+static void
+generate_instances_got_objects_cb (struct get_objects_async_data *goad, GSList *objects)
+{
+	g_return_if_fail (goad != NULL);
+
+	/* generate_instaces () frees 'objects' slist */
+	if (objects)
+		generate_instances (goad->client, goad->start, goad->end, objects, goad->cancellable, goad->cb, goad->cb_data);
+
+	free_get_objects_async_data (goad);
+}
+
 /**
  * e_cal_client_generate_instances:
  * @client: A calendar client.
  * @start: Start time for query.
  * @end: End time for query.
+ * @cancellable: a #GCancellable; can be %NULL
+ * @cb: Callback for each generated instance.
+ * @cb_data: Closure data for the callback.
+ * @destroy_cb_data: Function to call when the processing is done, to free @cb_data; can be %NULL.
+ *
+ * Does a combination of #e_cal_client_get_object_list () and
+ * #e_cal_client_recur_generate_instances(). Unlike #e_cal_client_generate_instances_sync (),
+ * this returns immediately and the @cb callback is called asynchronously.
+ *
+ * The callback function should do a g_object_ref() of the calendar component
+ * it gets passed if it intends to keep it around, since it will be unref'ed
+ * as soon as the callback returns.
+ *
+ * Since: 3.2
+ **/
+void
+e_cal_client_generate_instances (ECalClient *client, time_t start, time_t end, GCancellable *cancellable, ECalRecurInstanceFn cb, gpointer cb_data, GDestroyNotify destroy_cb_data)
+{
+	struct get_objects_async_data *goad;
+	GCancellable *use_cancellable;
+
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (E_IS_CAL_CLIENT (client));
+	g_return_if_fail (e_client_is_opened (E_CLIENT (client)));
+
+	g_return_if_fail (start >= 0);
+	g_return_if_fail (end >= 0);
+	g_return_if_fail (cb != NULL);
+
+	use_cancellable = cancellable;
+	if (!use_cancellable)
+		use_cancellable = g_cancellable_new ();
+
+	goad = g_new0 (struct get_objects_async_data, 1);
+	goad->cancellable = g_object_ref (use_cancellable);
+	goad->client = g_object_ref (client);
+	goad->start = start;
+	goad->end = end;
+	goad->cb = cb;
+	goad->cb_data = cb_data;
+	goad->destroy_cb_data = destroy_cb_data;
+
+	get_objects_async (generate_instances_got_objects_cb, goad);
+
+	if (use_cancellable != cancellable)
+		g_object_unref (use_cancellable);
+}
+
+/**
+ * e_cal_client_generate_instances_sync:
+ * @client: A calendar client.
+ * @start: Start time for query.
+ * @end: End time for query.
  * @cb: Callback for each generated instance.
  * @cb_data: Closure data for the callback.
  *
@@ -1543,10 +1818,14 @@ generate_instances (ECalClient *client, time_t start, time_t end, const gchar *u
  * The callback function should do a g_object_ref() of the calendar component
  * it gets passed if it intends to keep it around, since it will be unref'ed
  * as soon as the callback returns.
+ *
+ * Since: 3.2
  **/
 void
-e_cal_client_generate_instances (ECalClient *client, time_t start, time_t end, ECalRecurInstanceFn cb, gpointer cb_data)
+e_cal_client_generate_instances_sync (ECalClient *client, time_t start, time_t end, ECalRecurInstanceFn cb, gpointer cb_data)
 {
+	GSList *objects = NULL;
+
 	g_return_if_fail (client != NULL);
 	g_return_if_fail (E_IS_CAL_CLIENT (client));
 	g_return_if_fail (e_client_is_opened (E_CLIENT (client)));
@@ -1555,7 +1834,76 @@ e_cal_client_generate_instances (ECalClient *client, time_t start, time_t end, E
 	g_return_if_fail (end >= 0);
 	g_return_if_fail (cb != NULL);
 
-	generate_instances (client, start, end, NULL, cb, cb_data);
+	objects = get_objects_sync (client, start, end, NULL);
+	if (!objects)
+		return;
+
+	/* generate_instaces frees 'objects' slist */
+	generate_instances (client, start, end, objects, NULL, cb, cb_data);
+}
+
+/* also frees 'instances' GSList */
+static void
+process_instances (ECalComponent *comp, GSList *instances, ECalRecurInstanceFn cb, gpointer cb_data)
+{
+	gchar *rid;
+	gboolean result;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (cb != NULL);
+
+	rid = e_cal_component_get_recurid_as_string (comp);
+
+	/* now only return back the instances for the given object */
+	result = TRUE;
+	while (instances != NULL) {
+		struct comp_instance *ci;
+		gchar *instance_rid = NULL;
+
+		ci = instances->data;
+
+		if (result) {
+			instance_rid = e_cal_component_get_recurid_as_string (ci->comp);
+
+			if (rid && *rid) {
+				if (instance_rid && *instance_rid && strcmp (rid, instance_rid) == 0)
+					result = (* cb) (ci->comp, ci->start, ci->end, cb_data);
+			} else
+				result = (* cb)  (ci->comp, ci->start, ci->end, cb_data);
+		}
+
+		/* remove instance from list */
+		instances = g_slist_remove (instances, ci);
+		g_object_unref (ci->comp);
+		g_free (ci);
+		g_free (instance_rid);
+	}
+
+	/* clean up */
+	g_free (rid);
+}
+
+static void
+generate_instances_for_object_got_objects_cb (struct get_objects_async_data *goad, GSList *objects)
+{
+	struct instances_info *instances_hold;
+	GSList *instances = NULL;
+
+	g_return_if_fail (goad != NULL);
+
+	instances_hold = g_new0 (struct instances_info, 1);
+	instances_hold->instances = &instances;
+	instances_hold->start_zone = goad->start_zone;
+
+	/* generate all instances in the given time range */
+	generate_instances (goad->client, goad->start, goad->end, objects, goad->cancellable, add_instance, instances_hold);
+
+	/* it also frees 'instances' GSList */
+	process_instances (goad->comp, *(instances_hold->instances), goad->cb, goad->cb_data);
+
+	/* clean up */
+	free_get_objects_async_data (goad);
+	g_free (instances_hold);
 }
 
 /**
@@ -1564,24 +1912,116 @@ e_cal_client_generate_instances (ECalClient *client, time_t start, time_t end, E
  * @icalcomp: Object to generate instances from.
  * @start: Start time for query.
  * @end: End time for query.
+ * @cancellable: a #GCancellable; can be %NULL
  * @cb: Callback for each generated instance.
  * @cb_data: Closure data for the callback.
+ * @destroy_cb_data: Function to call when the processing is done, to free @cb_data; can be %NULL.
  *
  * Does a combination of #e_cal_client_get_object_list () and
  * #e_cal_client_recur_generate_instances(), like #e_cal_client_generate_instances(), but
+ * for a single object. Unlike #e_cal_client_generate_instances_for_object_sync (),
+ * this returns immediately and the @cb callback is called asynchronously.
+ *
+ * The callback function should do a g_object_ref() of the calendar component
+ * it gets passed if it intends to keep it around, since it will be unref'ed
+ * as soon as the callback returns.
+ *
+ * Since: 3.2
+ **/
+void
+e_cal_client_generate_instances_for_object (ECalClient *client, icalcomponent *icalcomp, time_t start, time_t end, GCancellable *cancellable, ECalRecurInstanceFn cb, gpointer cb_data, GDestroyNotify destroy_cb_data)
+{
+	ECalComponent *comp;
+	const gchar *uid;
+	ECalComponentDateTime datetime;
+	icaltimezone *start_zone = NULL;
+	gboolean is_single_instance = FALSE;
+	struct get_objects_async_data *goad;
+	GCancellable *use_cancellable;
+
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (E_IS_CAL_CLIENT (client));
+	g_return_if_fail (e_client_is_opened (E_CLIENT (client)));
+
+	g_return_if_fail (start >= 0);
+	g_return_if_fail (end >= 0);
+	g_return_if_fail (cb != NULL);
+
+	comp = e_cal_component_new ();
+	e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
+
+	if (!e_cal_component_has_recurrences (comp))
+		is_single_instance = TRUE;
+
+	/* If the backend stores it as individual instances and does not
+	 * have a master object - do not expand */
+	if (is_single_instance || e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER)) {
+		/* return the same instance */
+		(* cb)  (comp, icaltime_as_timet_with_zone (icalcomponent_get_dtstart (icalcomp), client->priv->default_zone),
+				icaltime_as_timet_with_zone (icalcomponent_get_dtend (icalcomp), client->priv->default_zone), cb_data);
+		g_object_unref (comp);
+
+		if (destroy_cb_data)
+			destroy_cb_data (cb_data);
+		return;
+	}
+
+	e_cal_component_get_uid (comp, &uid);
+
+	/* Get the start timezone */
+	e_cal_component_get_dtstart (comp, &datetime);
+	if (datetime.tzid)
+		e_cal_client_get_timezone_sync (client, datetime.tzid, &start_zone, NULL, NULL);
+	else
+		start_zone = NULL;
+	e_cal_component_free_datetime (&datetime);
+
+	use_cancellable = cancellable;
+	if (!use_cancellable)
+		use_cancellable = g_cancellable_new ();
+
+	goad = g_new0 (struct get_objects_async_data, 1);
+	goad->cancellable = g_object_ref (use_cancellable);
+	goad->client = g_object_ref (client);
+	goad->start = start;
+	goad->end = end;
+	goad->cb = cb;
+	goad->cb_data = cb_data;
+	goad->destroy_cb_data = destroy_cb_data;
+	goad->start_zone = start_zone;
+	goad->comp = comp;
+	goad->uid = g_strdup (uid);
+
+	get_objects_async (generate_instances_for_object_got_objects_cb, goad);
+
+	if (use_cancellable != cancellable)
+		g_object_unref (use_cancellable);
+}
+
+/**
+ * e_cal_client_generate_instances_for_object_sync:
+ * @client: A calendar client.
+ * @icalcomp: Object to generate instances from.
+ * @start: Start time for query.
+ * @end: End time for query.
+ * @cb: Callback for each generated instance.
+ * @cb_data: Closure data for the callback.
+ *
+ * Does a combination of #e_cal_client_get_object_list () and
+ * #e_cal_client_recur_generate_instances(), like #e_cal_client_generate_instances_sync(), but
  * for a single object.
  *
  * The callback function should do a g_object_ref() of the calendar component
  * it gets passed if it intends to keep it around, since it will be unref'ed
  * as soon as the callback returns.
+ *
+ * Since: 3.2
  **/
 void
-e_cal_client_generate_instances_for_object (ECalClient *client, icalcomponent *icalcomp, time_t start, time_t end, ECalRecurInstanceFn cb, gpointer cb_data)
+e_cal_client_generate_instances_for_object_sync (ECalClient *client, icalcomponent *icalcomp, time_t start, time_t end, ECalRecurInstanceFn cb, gpointer cb_data)
 {
 	ECalComponent *comp;
 	const gchar *uid;
-	gchar *rid;
-	gboolean result;
 	GSList *instances = NULL;
 	ECalComponentDateTime datetime;
 	icaltimezone *start_zone = NULL;
@@ -1606,14 +2046,13 @@ e_cal_client_generate_instances_for_object (ECalClient *client, icalcomponent *i
 	 * have a master object - do not expand */
 	if (is_single_instance || e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER)) {
 		/* return the same instance */
-		result = (* cb)  (comp, icaltime_as_timet_with_zone (icalcomponent_get_dtstart (icalcomp), client->priv->default_zone),
+		(* cb)  (comp, icaltime_as_timet_with_zone (icalcomponent_get_dtstart (icalcomp), client->priv->default_zone),
 				icaltime_as_timet_with_zone (icalcomponent_get_dtend (icalcomp), client->priv->default_zone), cb_data);
 		g_object_unref (comp);
 		return;
 	}
 
 	e_cal_component_get_uid (comp, &uid);
-	rid = e_cal_component_get_recurid_as_string (comp);
 
 	/* Get the start timezone */
 	e_cal_component_get_dtstart (comp, &datetime);
@@ -1628,38 +2067,14 @@ e_cal_client_generate_instances_for_object (ECalClient *client, icalcomponent *i
 	instances_hold->start_zone = start_zone;
 
 	/* generate all instances in the given time range */
-	generate_instances (client, start, end, uid, add_instance, instances_hold);
+	generate_instances (client, start, end, get_objects_sync (client, start, end, uid), NULL, add_instance, instances_hold);
 
-	instances = *(instances_hold->instances);
-	/* now only return back the instances for the given object */
-	result = TRUE;
-	while (instances != NULL) {
-		struct comp_instance *ci;
-		gchar *instance_rid = NULL;
-
-		ci = instances->data;
-
-		if (result) {
-			instance_rid = e_cal_component_get_recurid_as_string (ci->comp);
-
-			if (rid && *rid) {
-				if (instance_rid && *instance_rid && strcmp (rid, instance_rid) == 0)
-					result = (* cb) (ci->comp, ci->start, ci->end, cb_data);
-			} else
-				result = (* cb)  (ci->comp, ci->start, ci->end, cb_data);
-		}
-
-		/* remove instance from list */
-		instances = g_slist_remove (instances, ci);
-		g_object_unref (ci->comp);
-		g_free (ci);
-		g_free (instance_rid);
-	}
+	/* it also frees 'instances' GSList */
+	process_instances (comp, *(instances_hold->instances), cb, cb_data);
 
 	/* clean up */
 	g_object_unref (comp);
 	g_free (instances_hold);
-	g_free (rid);
 }
 
 typedef struct _ForeachTZIDCallbackData ForeachTZIDCallbackData;
@@ -1789,24 +2204,76 @@ e_cal_client_get_component_as_string (ECalClient *client, icalcomponent *icalcom
 	return obj_string;
 }
 
+static gboolean
+cal_client_get_backend_property_from_cache_finish (EClient *client, GAsyncResult *result, gchar **prop_value, GError **error)
+{
+	GSimpleAsyncResult *simple;
+	GError *local_error = NULL;
+
+	g_return_val_if_fail (client != NULL, FALSE);
+	g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
+	g_return_val_if_fail (result != NULL, FALSE);
+	g_return_val_if_fail (prop_value != NULL, FALSE);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (client), cal_client_get_backend_property_from_cache_finish), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	if (g_simple_async_result_propagate_error (simple, &local_error)) {
+		e_client_unwrap_dbus_error (client, local_error, error);
+		return FALSE;
+	}
+
+	*prop_value = g_strdup (g_simple_async_result_get_op_res_gpointer (simple));
+
+	return *prop_value != NULL;
+}
+
 static void
 cal_client_get_backend_property (EClient *client, const gchar *prop_name, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
 {
-	e_client_proxy_call_string (client, prop_name, cancellable, callback, user_data, cal_client_get_backend_property,
+	gchar *prop_value;
+
+	prop_value = e_client_get_backend_property_from_cache (client, prop_name);
+	if (prop_value) {
+		e_client_finish_async_without_dbus (client, cancellable, callback, user_data, cal_client_get_backend_property_from_cache_finish, prop_value, g_free);
+	} else {
+		e_client_proxy_call_string_with_res_op_data (client, prop_name, cancellable, callback, user_data, cal_client_get_backend_property, prop_name,
 			e_gdbus_cal_call_get_backend_property,
 			NULL, NULL, e_gdbus_cal_call_get_backend_property_finish, NULL, NULL);
+	}
 }
 
 static gboolean
 cal_client_get_backend_property_finish (EClient *client, GAsyncResult *result, gchar **prop_value, GError **error)
 {
-	return e_client_proxy_call_finish_string (client, result, prop_value, error, cal_client_get_backend_property);
+	gchar *str = NULL;
+	gboolean res;
+
+	g_return_val_if_fail (prop_value != NULL, FALSE);
+
+	if (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)) == cal_client_get_backend_property_from_cache_finish) {
+		res = cal_client_get_backend_property_from_cache_finish (client, result, &str, error);
+	} else {
+		res = e_client_proxy_call_finish_string (client, result, &str, error, cal_client_get_backend_property);
+		if (res && str) {
+			const gchar *prop_name = g_object_get_data (G_OBJECT (result), "res-op-data");
+
+			if (prop_name && *prop_name)
+				e_client_update_backend_property_cache (client, prop_name, str);
+		}
+	}
+
+	*prop_value = str;
+
+	return res;
 }
 
 static gboolean
 cal_client_get_backend_property_sync (EClient *client, const gchar *prop_name, gchar **prop_value, GCancellable *cancellable, GError **error)
 {
 	ECalClient *cal_client;
+	gchar *prop_val;
+	gboolean res;
 
 	g_return_val_if_fail (client != NULL, FALSE);
 	g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
@@ -1820,7 +2287,21 @@ cal_client_get_backend_property_sync (EClient *client, const gchar *prop_name, g
 		return FALSE;
 	}
 
-	return e_client_proxy_call_sync_string__string (client, prop_name, prop_value, cancellable, error, e_gdbus_cal_call_get_backend_property_sync);
+	prop_val = e_client_get_backend_property_from_cache (client, prop_name);
+	if (prop_val) {
+		g_return_val_if_fail (prop_value != NULL, FALSE);
+
+		*prop_value = prop_val;
+
+		return TRUE;
+	}
+
+	res = e_client_proxy_call_sync_string__string (client, prop_name, prop_value, cancellable, error, e_gdbus_cal_call_get_backend_property_sync);
+
+	if (res && prop_value)
+		e_client_update_backend_property_cache (client, prop_name, *prop_value);
+
+	return res;
 }
 
 static void
@@ -3810,24 +4291,7 @@ e_cal_client_get_timezone (ECalClient *client, const gchar *tzid, GCancellable *
 
 	zone = cal_client_get_timezone_from_cache (client, tzid);
 	if (zone) {
-		GCancellable *use_cancellable = cancellable;
-		guint32 opid;
-
-		if (!use_cancellable)
-			use_cancellable = g_cancellable_new ();
-
-		opid = e_client_register_op (E_CLIENT (client), use_cancellable);
-		if (opid) {
-			GSimpleAsyncResult *simple;
-
-			simple = g_simple_async_result_new (G_OBJECT (client), callback, user_data, cal_client_get_timezone_from_cache);
-			g_simple_async_result_set_op_res_gpointer (simple, zone, NULL);
-			g_simple_async_result_complete_in_idle (simple);
-			g_object_unref (simple);
-		}
-
-		if (use_cancellable != cancellable)
-			g_object_unref (use_cancellable);
+		e_client_finish_async_without_dbus (E_CLIENT (client), cancellable, callback, user_data, cal_client_get_timezone_from_cache, zone, NULL);
 	} else {
 		e_client_proxy_call_string (E_CLIENT (client), e_util_ensure_gdbus_string (tzid, &gdbus_tzid), cancellable, callback, user_data, e_cal_client_get_timezone,
 				e_gdbus_cal_call_get_timezone,
diff --git a/calendar/libecal/e-cal-client.h b/calendar/libecal/e-cal-client.h
index 5010b5e..e04491a 100644
--- a/calendar/libecal/e-cal-client.h
+++ b/calendar/libecal/e-cal-client.h
@@ -116,8 +116,10 @@ void		e_cal_client_free_icalcomp_slist		(GSList *icalcomps);
 void		e_cal_client_free_ecalcomp_slist		(GSList *ecalcomps);
 
 icaltimezone *	e_cal_client_resolve_tzid_cb			(const gchar *tzid, gpointer data);
-void		e_cal_client_generate_instances			(ECalClient *client, time_t start, time_t end, ECalRecurInstanceFn cb, gpointer cb_data);
-void		e_cal_client_generate_instances_for_object	(ECalClient *client, icalcomponent *icalcomp, time_t start, time_t end, ECalRecurInstanceFn cb, gpointer cb_data);
+void		e_cal_client_generate_instances			(ECalClient *client, time_t start, time_t end, GCancellable *cancellable, ECalRecurInstanceFn cb, gpointer cb_data, GDestroyNotify destroy_cb_data);
+void		e_cal_client_generate_instances_sync		(ECalClient *client, time_t start, time_t end, ECalRecurInstanceFn cb, gpointer cb_data);
+void		e_cal_client_generate_instances_for_object	(ECalClient *client, icalcomponent *icalcomp, time_t start, time_t end, GCancellable *cancellable, ECalRecurInstanceFn cb, gpointer cb_data, GDestroyNotify destroy_cb_data);
+void		e_cal_client_generate_instances_for_object_sync	(ECalClient *client, icalcomponent *icalcomp, time_t start, time_t end, ECalRecurInstanceFn cb, gpointer cb_data);
 gchar *		e_cal_client_get_component_as_string		(ECalClient *client, icalcomponent *icalcomp);
 
 /* Calendar methods */
diff --git a/calendar/libedata-cal/e-cal-backend.c b/calendar/libedata-cal/e-cal-backend.c
index 277a6f1..d79e4e2 100644
--- a/calendar/libedata-cal/e-cal-backend.c
+++ b/calendar/libedata-cal/e-cal-backend.c
@@ -1784,6 +1784,36 @@ e_cal_backend_notify_opened (ECalBackend *backend, GError *error)
 }
 
 /**
+ * e_cal_backend_notify_property_changed:
+ * @backend: an #ECalBackend
+ * @prop_name: property name, which changed
+ * @prop_value: new property value
+ *
+ * Notifies client about property value change.
+ **/
+void
+e_cal_backend_notify_property_changed (ECalBackend *backend, const gchar *prop_name, const gchar *prop_value)
+{
+	ECalBackendPrivate *priv;
+	GSList *clients;
+
+	g_return_if_fail (backend != NULL);
+	g_return_if_fail (E_IS_CAL_BACKEND (backend));
+	g_return_if_fail (backend->priv != NULL);
+	g_return_if_fail (prop_name != NULL);
+	g_return_if_fail (*prop_name != '\0');
+	g_return_if_fail (prop_value != NULL);
+
+	priv = backend->priv;
+	g_mutex_lock (priv->clients_mutex);
+
+	for (clients = priv->clients; clients != NULL; clients = g_slist_next (clients))
+		e_data_cal_report_backend_property_changed (E_DATA_CAL (clients->data), prop_name, prop_value);
+
+	g_mutex_unlock (priv->clients_mutex);
+}
+
+/**
  * e_cal_backend_respond_opened:
  * @backend: an #ECalBackend
  * @cal: an #EDataCal
diff --git a/calendar/libedata-cal/e-cal-backend.h b/calendar/libedata-cal/e-cal-backend.h
index 360c4e4..840b092 100644
--- a/calendar/libedata-cal/e-cal-backend.h
+++ b/calendar/libedata-cal/e-cal-backend.h
@@ -159,6 +159,7 @@ void		e_cal_backend_notify_readonly		(ECalBackend *backend, gboolean is_readonly
 void		e_cal_backend_notify_online		(ECalBackend *backend, gboolean is_online);
 void		e_cal_backend_notify_auth_required	(ECalBackend *backend, gboolean is_self, const ECredentials *credentials);
 void		e_cal_backend_notify_opened		(ECalBackend *backend, GError *error);
+void		e_cal_backend_notify_property_changed	(ECalBackend *backend, const gchar *prop_name, const gchar *prop_value);
 
 void		e_cal_backend_empty_cache		(ECalBackend *backend, struct _ECalBackendCache *cache);
 
diff --git a/calendar/libedata-cal/e-data-cal.c b/calendar/libedata-cal/e-data-cal.c
index c895fc5..6eeaa96 100644
--- a/calendar/libedata-cal/e-data-cal.c
+++ b/calendar/libedata-cal/e-data-cal.c
@@ -1409,6 +1409,25 @@ e_data_cal_report_free_busy_data (EDataCal *cal, const GSList *freebusy)
 	g_strfreev (strv_freebusy);
 }
 
+/* Notifies client about certain property value change */
+void
+e_data_cal_report_backend_property_changed (EDataCal *cal, const gchar *prop_name, const gchar *prop_value)
+{
+	gchar **strv;
+
+	g_return_if_fail (cal != NULL);
+	g_return_if_fail (prop_name != NULL);
+	g_return_if_fail (*prop_name != '\0');
+	g_return_if_fail (prop_value != NULL);
+
+	strv = e_gdbus_templates_encode_two_strings (prop_name, prop_value);
+	g_return_if_fail (strv != NULL);
+
+	e_gdbus_cal_emit_backend_property_changed (cal->priv->gdbus_object, (const gchar * const *) strv);
+
+	g_strfreev (strv);
+}
+
 /* Instance init */
 static void
 e_data_cal_init (EDataCal *ecal)
diff --git a/calendar/libedata-cal/e-data-cal.h b/calendar/libedata-cal/e-data-cal.h
index c202f11..cb97e01 100644
--- a/calendar/libedata-cal/e-data-cal.h
+++ b/calendar/libedata-cal/e-data-cal.h
@@ -154,6 +154,7 @@ void		e_data_cal_report_online			(EDataCal *cal, gboolean is_online);
 void		e_data_cal_report_auth_required			(EDataCal *cal, const ECredentials *credentials);
 void		e_data_cal_report_opened			(EDataCal *cal, const GError *error);
 void		e_data_cal_report_free_busy_data		(EDataCal *cal, const GSList *freebusy);
+void		e_data_cal_report_backend_property_changed	(EDataCal *cal, const gchar *prop_name, const gchar *prop_value);
 
 G_END_DECLS
 
diff --git a/calendar/libegdbus/e-gdbus-cal.c b/calendar/libegdbus/e-gdbus-cal.c
index b213cd2..9e747c3 100644
--- a/calendar/libegdbus/e-gdbus-cal.c
+++ b/calendar/libegdbus/e-gdbus-cal.c
@@ -42,6 +42,7 @@ enum
 	__AUTH_REQUIRED_SIGNAL,
 	__OPENED_SIGNAL,
 	__FREE_BUSY_DATA_SIGNAL,
+	__BACKEND_PROPERTY_CHANGED_SIGNAL,
 	__OPEN_METHOD,
 	__OPEN_DONE_SIGNAL,
 	__REMOVE_METHOD,
@@ -133,6 +134,7 @@ E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_BOOLEAN (GDBUS_CAL_INTERFACE_NAME, online)
 E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_STRV    (GDBUS_CAL_INTERFACE_NAME, auth_required)
 E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_STRV    (GDBUS_CAL_INTERFACE_NAME, opened)
 E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_STRV    (GDBUS_CAL_INTERFACE_NAME, free_busy_data)
+E_DECLARE_GDBUS_SIGNAL_EMISSION_HOOK_STRV    (GDBUS_CAL_INTERFACE_NAME, backend_property_changed)
 
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_VOID	(GDBUS_CAL_INTERFACE_NAME, open)
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_VOID	(GDBUS_CAL_INTERFACE_NAME, remove)
@@ -169,6 +171,7 @@ e_gdbus_cal_default_init (EGdbusCalIface *iface)
 	E_INIT_GDBUS_SIGNAL_STRV   		(EGdbusCalIface, "auth_required", 	auth_required,	__AUTH_REQUIRED_SIGNAL)
 	E_INIT_GDBUS_SIGNAL_STRV   		(EGdbusCalIface, "opened", 		opened,		__OPENED_SIGNAL)
 	E_INIT_GDBUS_SIGNAL_STRV   		(EGdbusCalIface, "free_busy_data", 	free_busy_data,	__FREE_BUSY_DATA_SIGNAL)
+	E_INIT_GDBUS_SIGNAL_STRV   		(EGdbusCalIface, "backend_property_changed", 	backend_property_changed,	__BACKEND_PROPERTY_CHANGED_SIGNAL)
 
 	/* GObject signals definitions for D-Bus methods: */
 	E_INIT_GDBUS_METHOD_ASYNC_BOOLEAN__VOID	(EGdbusCalIface, "open",			open, __OPEN_METHOD, __OPEN_DONE_SIGNAL)
@@ -195,35 +198,6 @@ e_gdbus_cal_default_init (EGdbusCalIface *iface)
 	E_INIT_GDBUS_METHOD_VOID		(EGdbusCalIface, "close",			close, __CLOSE_METHOD)
 }
 
-static gchar **
-encode_string_string (const gchar *str1, const gchar *str2)
-{
-	gchar **strv;
-
-	strv = g_new0 (gchar *, 3);
-	strv[0] = e_util_utf8_make_valid (str1 ? str1 : "");
-	strv[1] = e_util_utf8_make_valid (str2 ? str2 : "");
-	strv[2] = NULL;
-
-	return strv;
-}
-
-static gboolean
-decode_string_string (const gchar * const *in_strv, gchar **out_str1, gchar **out_str2)
-{
-	g_return_val_if_fail (in_strv != NULL, FALSE);
-	g_return_val_if_fail (in_strv[0] != NULL, FALSE);
-	g_return_val_if_fail (in_strv[1] != NULL, FALSE);
-	g_return_val_if_fail (in_strv[2] == NULL, FALSE);
-	g_return_val_if_fail (out_str1 != NULL, FALSE);
-	g_return_val_if_fail (out_str2 != NULL, FALSE);
-
-	*out_str1 = g_strdup (in_strv[0]);
-	*out_str2 = g_strdup (in_strv[1]);
-
-	return TRUE;
-}
-
 void
 e_gdbus_cal_call_open (GDBusProxy *proxy, gboolean in_only_if_exists, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
 {
@@ -308,14 +282,14 @@ e_gdbus_cal_call_get_backend_property_sync (GDBusProxy *proxy, const gchar *in_p
 gchar **
 e_gdbus_cal_encode_set_backend_property (const gchar *in_prop_name, const gchar *in_prop_value)
 {
-	return encode_string_string (in_prop_name, in_prop_value);
+	return e_gdbus_templates_encode_two_strings (in_prop_name, in_prop_value);
 }
 
 /* free out_prop_name and out_prop_value with g_free() */
 gboolean
 e_gdbus_cal_decode_set_backend_property (const gchar * const *in_strv, gchar **out_prop_name, gchar **out_prop_value)
 {
-	return decode_string_string (in_strv, out_prop_name, out_prop_value);
+	return e_gdbus_templates_decode_two_strings (in_strv, out_prop_name, out_prop_value);
 }
 
 void
@@ -342,14 +316,14 @@ e_gdbus_cal_call_set_backend_property_sync (GDBusProxy *proxy, const gchar * con
 gchar **
 e_gdbus_cal_encode_get_object (const gchar *in_uid, const gchar *in_rid)
 {
-	return encode_string_string (in_uid, in_rid);
+	return e_gdbus_templates_encode_two_strings (in_uid, in_rid);
 }
 
 /* free out_uid and out_rid with g_free() */
 gboolean
 e_gdbus_cal_decode_get_object (const gchar * const *in_strv, gchar **out_uid, gchar **out_rid)
 {
-	return decode_string_string (in_strv, out_uid, out_rid);
+	return e_gdbus_templates_decode_two_strings (in_strv, out_uid, out_rid);
 }
 
 void
@@ -676,14 +650,14 @@ e_gdbus_cal_call_send_objects_sync (GDBusProxy *proxy, const gchar *in_calobj, g
 gchar **
 e_gdbus_cal_encode_get_attachment_uris (const gchar *in_uid, const gchar *in_rid)
 {
-	return encode_string_string (in_uid, in_rid);
+	return e_gdbus_templates_encode_two_strings (in_uid, in_rid);
 }
 
 /* free out_uid and out_rid with g_free() */
 gboolean
 e_gdbus_cal_decode_get_attachment_uris (const gchar * const *in_strv, gchar **out_uid, gchar **out_rid)
 {
-	return decode_string_string (in_strv, out_uid, out_rid);
+	return e_gdbus_templates_decode_two_strings (in_strv, out_uid, out_rid);
 }
 
 void
@@ -965,12 +939,19 @@ e_gdbus_cal_emit_free_busy_data (EGdbusCal *object, const gchar * const *arg_fre
 	g_signal_emit (object, signals[__FREE_BUSY_DATA_SIGNAL], 0, arg_free_busy);
 }
 
+void
+e_gdbus_cal_emit_backend_property_changed (EGdbusCal *object, const gchar * const *arg_name_value)
+{
+	g_signal_emit (object, signals[__BACKEND_PROPERTY_CHANGED_SIGNAL], 0, arg_name_value);
+}
+
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (cal, backend_error, message, "s")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (cal, readonly, is_readonly, "b")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (cal, online, is_online, "b")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (cal, auth_required, credentials, "as")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (cal, opened, error, "as")
 E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (cal, free_busy_data, free_busy_data, "as")
+E_DECLARE_GDBUS_NOTIFY_SIGNAL_1 (cal, backend_property_changed, name_value, "as")
 
 E_DECLARE_GDBUS_ASYNC_METHOD_1			(cal, open, only_if_exists, "b")
 E_DECLARE_GDBUS_ASYNC_METHOD_0			(cal, remove)
@@ -1031,6 +1012,7 @@ static const GDBusSignalInfo * const e_gdbus_cal_signal_info_pointers[] =
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, auth_required),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, opened),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, free_busy_data),
+	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, backend_property_changed),
 
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, open_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, remove_done),
diff --git a/calendar/libegdbus/e-gdbus-cal.h b/calendar/libegdbus/e-gdbus-cal.h
index 21e2c7d..695d13b 100644
--- a/calendar/libegdbus/e-gdbus-cal.h
+++ b/calendar/libegdbus/e-gdbus-cal.h
@@ -109,6 +109,7 @@ struct _EGdbusCalIface
 	void	(*auth_required)			(EGdbusCal *object, const gchar * const *arg_credentials);
 	void	(*opened)				(EGdbusCal *object, const gchar * const *arg_error);
 	void	(*free_busy_data)			(EGdbusCal *object, const gchar * const *arg_free_busy);
+	void	(*backend_property_changed)		(EGdbusCal *object, const gchar * const *arg_name_value);
 
 	/* Signal handlers for handling D-Bus method calls: */
 	gboolean (*handle_open)				(EGdbusCal *object, GDBusMethodInvocation *invocation, gboolean in_only_if_exists);
@@ -327,6 +328,7 @@ void e_gdbus_cal_emit_online		(EGdbusCal *object, gint arg_is_online);
 void e_gdbus_cal_emit_auth_required	(EGdbusCal *object, const gchar * const *arg_credentials);
 void e_gdbus_cal_emit_opened		(EGdbusCal *object, const gchar * const *arg_error);
 void e_gdbus_cal_emit_free_busy_data	(EGdbusCal *object, const gchar * const *arg_free_busy);
+void e_gdbus_cal_emit_backend_property_changed	(EGdbusCal *object, const gchar * const *arg_name_value);
 
 G_END_DECLS
 
diff --git a/configure.ac b/configure.ac
index 8952ba5..0773f6f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -77,7 +77,7 @@ AC_SUBST(CALENDAR_DBUS_SERVICE_NAME)
 dnl ******************************
 dnl Libtool versioning
 dnl ******************************
-LIBEDATASERVER_CURRENT=14
+LIBEDATASERVER_CURRENT=15
 LIBEDATASERVER_REVISION=0
 LIBEDATASERVER_AGE=0
 
@@ -85,19 +85,19 @@ LIBEDATASERVERUI_CURRENT=1
 LIBEDATASERVERUI_REVISION=0
 LIBEDATASERVERUI_AGE=0
 
-LIBECAL_CURRENT=11
+LIBECAL_CURRENT=12
 LIBECAL_REVISION=2
 LIBECAL_AGE=2
 
-LIBEDATACAL_CURRENT=12
+LIBEDATACAL_CURRENT=13
 LIBEDATACAL_REVISION=0
 LIBEDATACAL_AGE=0
 
-LIBEDATABOOK_CURRENT=10
+LIBEDATABOOK_CURRENT=11
 LIBEDATABOOK_REVISION=0
 LIBEDATABOOK_AGE=0
 
-LIBEBOOK_CURRENT=14
+LIBEBOOK_CURRENT=15
 LIBEBOOK_REVISION=1
 LIBEBOOK_AGE=3
 
diff --git a/libedataserver/e-client-private.h b/libedataserver/e-client-private.h
index 59b451d..23736d1 100644
--- a/libedataserver/e-client-private.h
+++ b/libedataserver/e-client-private.h
@@ -42,12 +42,17 @@ gboolean	e_client_emit_authenticate	(EClient *client, ECredentials *credentials)
 void		e_client_emit_opened		(EClient *client, const GError *error);
 void		e_client_emit_backend_error	(EClient *client, const gchar *error_msg);
 void		e_client_emit_backend_died	(EClient *client);
+void		e_client_emit_backend_property_changed   (EClient *client, const gchar *prop_name, const gchar *prop_value);
+
+void		e_client_update_backend_property_cache   (EClient *client, const gchar *prop_name, const gchar *prop_value);
+gchar *		e_client_get_backend_property_from_cache (EClient *client, const gchar *prop_name);
 
 ESource *	e_client_util_get_system_source	(ESourceList *source_list);
 gboolean	e_client_util_set_default	(ESourceList *source_list, ESource *source);
 ESource *	e_client_util_get_source_for_uri (ESourceList *source_list, const gchar *uri);
 
 /* protected functions simplifying sync/async calls */
+void		e_client_finish_async_without_dbus (EClient *client, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, gpointer soure_tag, gpointer op_res, GDestroyNotify destroy_op_res);
 GDBusProxy *	e_client_get_dbus_proxy		(EClient *client);
 
 void		e_client_proxy_return_async_error	(EClient *client, const GError *error, GAsyncReadyCallback callback, gpointer user_data, gpointer source_tag);
@@ -83,6 +88,8 @@ void		e_client_proxy_call_string	(EClient *client, const gchar *in_string, GCanc
 void		e_client_proxy_call_strv	(EClient *client, const gchar * const *in_strv, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, gpointer source_tag, void (*func) (GDBusProxy *proxy, const gchar * const * in_strv, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data), EClientProxyFinishVoidFunc finish_void, EClientProxyFinishBooleanFunc finish_boolean, EClientProxyFinishStringFunc finish_string, EClientProxyFinishStrvFunc finish_strv, EClientProxyFinishUintFunc finish_uint);
 void		e_client_proxy_call_uint	(EClient *client, guint in_uint, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, gpointer source_tag, void (*func) (GDBusProxy *proxy, guint in_uint, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data), EClientProxyFinishVoidFunc finish_void, EClientProxyFinishBooleanFunc finish_boolean, EClientProxyFinishStringFunc finish_string, EClientProxyFinishStrvFunc finish_strv, EClientProxyFinishUintFunc finish_uint);
 
+void		e_client_proxy_call_string_with_res_op_data (EClient *client, const gchar *in_string, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, gpointer source_tag, const gchar *res_op_data, void (*func) (GDBusProxy *proxy, const gchar * in_string, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data), EClientProxyFinishVoidFunc finish_void, EClientProxyFinishBooleanFunc finish_boolean, EClientProxyFinishStringFunc finish_string, EClientProxyFinishStrvFunc finish_strv, EClientProxyFinishUintFunc finish_uint);
+
 gboolean	e_client_proxy_call_finish_void		(EClient *client, GAsyncResult *result, GError **error, gpointer source_tag);
 gboolean	e_client_proxy_call_finish_boolean	(EClient *client, GAsyncResult *result, gboolean *out_boolean, GError **error, gpointer source_tag);
 gboolean	e_client_proxy_call_finish_string	(EClient *client, GAsyncResult *result, gchar **out_string, GError **error, gpointer source_tag);
diff --git a/libedataserver/e-client.c b/libedataserver/e-client.c
index 7a5ec29..f7fe39b 100644
--- a/libedataserver/e-client.c
+++ b/libedataserver/e-client.c
@@ -43,6 +43,8 @@ struct _EClientPrivate
 	gboolean capabilities_retrieved;
 	GSList *capabilities;
 
+	GHashTable *backend_property_cache;
+
 	GStaticRecMutex ops_mutex;
 	guint32 last_opid;
 	GHashTable *ops; /* opid to GCancellable */
@@ -62,6 +64,7 @@ enum {
 	OPENED,
 	BACKEND_ERROR,
 	BACKEND_DIED,
+	BACKEND_PROPERTY_CHANGED,
 	LAST_SIGNAL
 };
 
@@ -178,6 +181,7 @@ e_client_init (EClient *client)
 	client->priv->readonly = TRUE;
 
 	g_static_rec_mutex_init (&client->priv->prop_mutex);
+	client->priv->backend_property_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 
 	g_static_rec_mutex_init (&client->priv->ops_mutex);
 	client->priv->last_opid = 0;
@@ -229,6 +233,11 @@ client_finalize (GObject *object)
 		priv->capabilities = NULL;
 	}
 
+	if (priv->backend_property_cache) {
+		g_hash_table_destroy (priv->backend_property_cache);
+		priv->backend_property_cache = NULL;
+	}
+
 	if (priv->ops) {
 		g_hash_table_destroy (priv->ops);
 		priv->ops = NULL;
@@ -386,6 +395,15 @@ e_client_class_init (EClientClass *klass)
 		NULL, NULL,
 		g_cclosure_marshal_VOID__VOID,
 		G_TYPE_NONE, 0);
+
+	signals[BACKEND_PROPERTY_CHANGED] = g_signal_new (
+		"backend-property-changed",
+		G_OBJECT_CLASS_TYPE (klass),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EClientClass, backend_property_changed),
+		NULL, NULL,
+		e_gdbus_marshallers_VOID__STRING_STRING,
+		G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
 }
 
 static void
@@ -925,6 +943,60 @@ e_client_emit_backend_died (EClient *client)
 	g_signal_emit (client, signals[BACKEND_DIED], 0);
 }
 
+void
+e_client_emit_backend_property_changed (EClient *client, const gchar *prop_name, const gchar *prop_value)
+{
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (E_IS_CLIENT (client));
+	g_return_if_fail (client->priv != NULL);
+	g_return_if_fail (prop_name != NULL);
+	g_return_if_fail (*prop_name);
+	g_return_if_fail (prop_value != NULL);
+
+	e_client_update_backend_property_cache (client, prop_name, prop_value);
+
+	g_signal_emit (client, signals[BACKEND_PROPERTY_CHANGED], 0, prop_name, prop_value);
+}
+
+void
+e_client_update_backend_property_cache (EClient *client, const gchar *prop_name, const gchar *prop_value)
+{
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (E_IS_CLIENT (client));
+	g_return_if_fail (client->priv != NULL);
+	g_return_if_fail (prop_name != NULL);
+	g_return_if_fail (*prop_name);
+	g_return_if_fail (prop_value != NULL);
+
+	g_static_rec_mutex_lock (&client->priv->prop_mutex);
+
+	if (client->priv->backend_property_cache)
+		g_hash_table_insert (client->priv->backend_property_cache, g_strdup (prop_name), g_strdup (prop_value));
+
+	g_static_rec_mutex_unlock (&client->priv->prop_mutex);
+}
+
+gchar *
+e_client_get_backend_property_from_cache (EClient *client, const gchar *prop_name)
+{
+	gchar *prop_value = NULL;
+
+	g_return_val_if_fail (client != NULL, NULL);
+	g_return_val_if_fail (E_IS_CLIENT (client), NULL);
+	g_return_val_if_fail (client->priv != NULL, NULL);
+	g_return_val_if_fail (prop_name != NULL, NULL);
+	g_return_val_if_fail (*prop_name, NULL);
+
+	g_static_rec_mutex_lock (&client->priv->prop_mutex);
+
+	if (client->priv->backend_property_cache)
+		prop_value = g_strdup (g_hash_table_lookup (client->priv->backend_property_cache, prop_name));
+
+	g_static_rec_mutex_unlock (&client->priv->prop_mutex);
+
+	return prop_value;
+}
+
 /**
  * e_client_retrieve_capabilities:
  * @client: an #EClient
@@ -1842,6 +1914,34 @@ e_client_util_get_source_for_uri (ESourceList *source_list, const gchar *uri)
 	return source;
 }
 
+void
+e_client_finish_async_without_dbus (EClient *client, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, gpointer source_tag, gpointer op_res, GDestroyNotify destroy_op_res)
+{
+	GCancellable *use_cancellable;
+	GSimpleAsyncResult *simple;
+	guint32 opid;
+
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (E_IS_CLIENT (client));
+	g_return_if_fail (callback != NULL);
+	g_return_if_fail (source_tag != NULL);
+
+	use_cancellable = cancellable;
+	if (!use_cancellable)
+		use_cancellable = g_cancellable_new ();
+
+	opid = e_client_register_op (client, use_cancellable);
+	g_return_if_fail (opid > 0);
+
+	simple = g_simple_async_result_new (G_OBJECT (client), callback, user_data, source_tag);
+	g_simple_async_result_set_op_res_gpointer (simple, op_res, destroy_op_res);
+	g_simple_async_result_complete_in_idle (simple);
+	g_object_unref (simple);
+
+	if (use_cancellable != cancellable)
+		g_object_unref (use_cancellable);
+}
+
 GDBusProxy *
 e_client_get_dbus_proxy (EClient *client)
 {
@@ -1971,6 +2071,7 @@ typedef struct _EClientAsyncOpData
 	guint32 opid;
 
 	gpointer source_tag;
+	gchar *res_op_data; /* optional string to set on a GAsyncResult object as "res-op-data" user data */
 	GAsyncReadyCallback callback;
 	gpointer user_data;
 
@@ -2005,6 +2106,7 @@ async_data_free (EClientAsyncOpData *async_data)
 		g_strfreev (async_data->out.val_strv);
 
 	g_object_unref (async_data->client);
+	g_free (async_data->res_op_data);
 	g_free (async_data);
 }
 
@@ -2043,6 +2145,9 @@ finish_async_op (EClientAsyncOpData *async_data, const GError *error, gboolean i
 	simple = g_simple_async_result_new (G_OBJECT (async_data->client), async_data->callback, async_data->user_data, async_data->source_tag);
 	g_simple_async_result_set_op_res_gpointer (simple, async_data, (GDestroyNotify) async_data_free);
 
+	if (async_data->res_op_data)
+		g_object_set_data_full (G_OBJECT (simple), "res-op-data", g_strdup (async_data->res_op_data), g_free);
+
 	if (error != NULL)
 		g_simple_async_result_set_from_error (simple, error);
 
@@ -2246,6 +2351,27 @@ e_client_proxy_call_string (EClient *client, const gchar *in_string, GCancellabl
 }
 
 void
+e_client_proxy_call_string_with_res_op_data (EClient *client, const gchar *in_string, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, gpointer source_tag, const gchar *res_op_data, void (*func) (GDBusProxy *proxy, const gchar * in_string, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data), EClientProxyFinishVoidFunc finish_void, EClientProxyFinishBooleanFunc finish_boolean, EClientProxyFinishStringFunc finish_string, EClientProxyFinishStrvFunc finish_strv, EClientProxyFinishUintFunc finish_uint)
+{
+	EClientAsyncOpData *async_data;
+	GDBusProxy *proxy = NULL;
+
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (E_IS_CLIENT (client));
+	g_return_if_fail (callback != NULL);
+	g_return_if_fail (source_tag != NULL);
+	e_client_return_async_if_fail (func != NULL, client, callback, user_data, source_tag);
+	e_client_return_async_if_fail (in_string != NULL, client, callback, user_data, source_tag);
+
+	async_data = prepare_async_data (client, cancellable, callback, user_data, source_tag, FALSE, finish_void, finish_boolean, finish_string, finish_strv, finish_uint, &proxy, &cancellable);
+	e_client_return_async_if_fail (async_data != NULL, client, callback, user_data, source_tag);
+
+	async_data->res_op_data = g_strdup (res_op_data);
+
+	func (proxy, in_string, cancellable, async_result_ready_cb, async_data);
+}
+
+void
 e_client_proxy_call_strv (EClient *client, const gchar * const *in_strv, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, gpointer source_tag, void (*func) (GDBusProxy *proxy, const gchar * const * in_strv, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data), EClientProxyFinishVoidFunc finish_void, EClientProxyFinishBooleanFunc finish_boolean, EClientProxyFinishStringFunc finish_string, EClientProxyFinishStrvFunc finish_strv, EClientProxyFinishUintFunc finish_uint)
 {
 	EClientAsyncOpData *async_data;
diff --git a/libedataserver/e-client.h b/libedataserver/e-client.h
index de78aa3..d17599e 100644
--- a/libedataserver/e-client.h
+++ b/libedataserver/e-client.h
@@ -122,6 +122,7 @@ struct _EClientClass {
 	void		(* opened) (EClient *client, const GError *error);
 	void		(* backend_error) (EClient *client, const gchar *error_msg);
 	void		(* backend_died) (EClient *client);
+	void		(* backend_property_changed) (EClient *client, const gchar *prop_name, const gchar *prop_value);
 };
 
 GType		e_client_get_type			(void);
diff --git a/libedataserver/e-gdbus-marshallers.list b/libedataserver/e-gdbus-marshallers.list
index 9bf81c4..cce982a 100644
--- a/libedataserver/e-gdbus-marshallers.list
+++ b/libedataserver/e-gdbus-marshallers.list
@@ -8,6 +8,7 @@ VOID:UINT,BOXED
 VOID:UINT,BOXED,STRING
 VOID:UINT,BOXED,BOXED
 VOID:UINT,STRING
+VOID:STRING,STRING
 
 VOID:BOXED
 VOID:STRING
diff --git a/libedataserver/e-gdbus-templates.c b/libedataserver/e-gdbus-templates.c
index 1494222..b5c6ec2 100644
--- a/libedataserver/e-gdbus-templates.c
+++ b/libedataserver/e-gdbus-templates.c
@@ -918,7 +918,8 @@ gdbus_proxy_async_method_done (guint e_gdbus_type, gconstpointer out_value, EGdb
 
 	op_data = g_hash_table_lookup (pending_ops, GUINT_TO_POINTER (arg_opid));
 	if (!op_data) {
-		g_debug ("%s: Operation %d gone before got done signal for it", G_STRFUNC, arg_opid);
+		/* it happens for cancelled operations, thus rather than track cancelled ops disable the debug warning */
+		/* g_debug ("%s: Operation %d gone before got done signal for it", G_STRFUNC, arg_opid); */
 		return;
 	}
 
@@ -1728,3 +1729,34 @@ e_gdbus_templates_decode_error (const gchar * const *in_strv, GError **out_error
 
 	return TRUE;
 }
+
+/* free returned pointer with g_strfreev() */
+gchar **
+e_gdbus_templates_encode_two_strings (const gchar *in_str1, const gchar *in_str2)
+{
+	gchar **strv;
+
+	strv = g_new0 (gchar *, 3);
+	strv[0] = e_util_utf8_make_valid (in_str1 ? in_str1 : "");
+	strv[1] = e_util_utf8_make_valid (in_str2 ? in_str2 : "");
+	strv[2] = NULL;
+
+	return strv;
+}
+
+/* free *out_str1 and *out_str2 with g_free() */
+gboolean
+e_gdbus_templates_decode_two_strings (const gchar * const *in_strv, gchar **out_str1, gchar **out_str2)
+{
+	g_return_val_if_fail (in_strv != NULL, FALSE);
+	g_return_val_if_fail (in_strv[0] != NULL, FALSE);
+	g_return_val_if_fail (in_strv[1] != NULL, FALSE);
+	g_return_val_if_fail (in_strv[2] == NULL, FALSE);
+	g_return_val_if_fail (out_str1 != NULL, FALSE);
+	g_return_val_if_fail (out_str2 != NULL, FALSE);
+
+	*out_str1 = g_strdup (in_strv[0]);
+	*out_str2 = g_strdup (in_strv[1]);
+
+	return TRUE;
+}
diff --git a/libedataserver/e-gdbus-templates.h b/libedataserver/e-gdbus-templates.h
index b6e68ef..c9bc205 100644
--- a/libedataserver/e-gdbus-templates.h
+++ b/libedataserver/e-gdbus-templates.h
@@ -745,6 +745,9 @@ gboolean e_gdbus_proxy_method_call_sync_strv__string	(const gchar *method_name,
 gchar ** e_gdbus_templates_encode_error	(const GError *in_error);
 gboolean e_gdbus_templates_decode_error	(const gchar * const *in_strv, GError **out_error);
 
+gchar ** e_gdbus_templates_encode_two_strings (const gchar *in_str1, const gchar *in_str2);
+gboolean e_gdbus_templates_decode_two_strings (const gchar * const *in_strv, gchar **out_str1, gchar **out_str2);
+
 G_END_DECLS
 
 #endif /* E_GDBUS_TEMPLATES_H */
diff --git a/libedataserverui/e-client-utils.c b/libedataserverui/e-client-utils.c
index 48334dd..422947c 100644
--- a/libedataserverui/e-client-utils.c
+++ b/libedataserverui/e-client-utils.c
@@ -277,6 +277,7 @@ typedef struct _EClientUtilsAsyncOpData
 	GError *opened_cb_error;
 	guint retry_open_id;
 	gboolean only_if_exists;
+	guint pending_properties_count;
 } EClientUtilsAsyncOpData;
 
 static void
@@ -354,19 +355,26 @@ return_async_error (const GError *error, GAsyncReadyCallback async_cb, gpointer
 }
 
 static void
-client_utils_capabilities_retrieved_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
+client_utils_get_backend_property_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
 {
 	EClient *client = E_CLIENT (source_object);
 	EClientUtilsAsyncOpData *async_data = user_data;
-	gchar *capabilities = NULL;
 	GSimpleAsyncResult *simple;
 
 	g_return_if_fail (async_data != NULL);
 	g_return_if_fail (async_data->client != NULL);
 	g_return_if_fail (async_data->client == client);
 
-	e_client_retrieve_capabilities_finish (client, result, &capabilities, NULL);
-	g_free (capabilities);
+	if (result) {
+		gchar *prop_value = NULL;
+		
+		if (e_client_get_backend_property_finish (client, result, &prop_value, NULL))
+			g_free (prop_value);
+
+		async_data->pending_properties_count--;
+		if (async_data->pending_properties_count)
+			return;
+	}
 
 	/* keep the initial auth_handler connected directly, thus it will be able
 	   to answer any later authentication requests, for reconnection, for example
@@ -384,6 +392,49 @@ client_utils_capabilities_retrieved_cb (GObject *source_object, GAsyncResult *re
 }
 
 static void
+client_utils_capabilities_retrieved_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
+{
+	EClient *client = E_CLIENT (source_object);
+	EClientUtilsAsyncOpData *async_data = user_data;
+	gchar *capabilities = NULL;
+	gboolean caps_res;
+
+	g_return_if_fail (async_data != NULL);
+	g_return_if_fail (async_data->client != NULL);
+	g_return_if_fail (async_data->client == client);
+
+	caps_res = e_client_retrieve_capabilities_finish (client, result, &capabilities, NULL);
+	g_free (capabilities);
+
+	if (caps_res) {
+		async_data->pending_properties_count = 1;
+
+		/* precache backend properties */
+		if (E_IS_CAL_CLIENT (client)) {
+			async_data->pending_properties_count += 3;
+
+			e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+			e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+			e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+		} else if (E_IS_BOOK_CLIENT (client)) {
+			async_data->pending_properties_count += 3;
+
+			e_client_get_backend_property (async_data->client, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+			e_client_get_backend_property (async_data->client, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+			e_client_get_backend_property (async_data->client, BOOK_BACKEND_PROPERTY_SUPPORTED_AUTH_METHODS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+		} else {
+			g_warn_if_reached ();
+			client_utils_get_backend_property_cb (source_object, NULL, async_data);
+			return;
+		}
+
+		e_client_get_backend_property (async_data->client, CLIENT_BACKEND_PROPERTY_CACHE_DIR, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+	} else {
+		client_utils_get_backend_property_cb (source_object, NULL, async_data);
+	}
+}
+
+static void
 client_utils_open_new_done (EClientUtilsAsyncOpData *async_data)
 {
 	g_return_if_fail (async_data != NULL);



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