aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/reference/libeutil/libeutil-docs.sgml1
-rw-r--r--doc/reference/libeutil/libeutil-sections.txt23
-rw-r--r--doc/reference/libeutil/libeutil.types1
-rw-r--r--e-util/Makefile.am2
-rw-r--r--e-util/e-client-cache.c1079
-rw-r--r--e-util/e-client-cache.h106
-rw-r--r--e-util/e-system.error.xml40
-rw-r--r--e-util/e-util.h1
-rw-r--r--po/POTFILES.in1
9 files changed, 1254 insertions, 0 deletions
diff --git a/doc/reference/libeutil/libeutil-docs.sgml b/doc/reference/libeutil/libeutil-docs.sgml
index 86eeac1b0f..1733a48eea 100644
--- a/doc/reference/libeutil/libeutil-docs.sgml
+++ b/doc/reference/libeutil/libeutil-docs.sgml
@@ -226,6 +226,7 @@
<xi:include href="xml/e-calendar.xml"/>
<xi:include href="xml/e-cell-renderer-color.xml"/>
<xi:include href="xml/e-charset-combo-box.xml"/>
+ <xi:include href="xml/e-client-cache.xml"/>
<xi:include href="xml/e-contact-store.xml"/>
<xi:include href="xml/e-dateedit.xml"/>
<xi:include href="xml/e-destination-store.xml"/>
diff --git a/doc/reference/libeutil/libeutil-sections.txt b/doc/reference/libeutil/libeutil-sections.txt
index 574db12958..6b5449224e 100644
--- a/doc/reference/libeutil/libeutil-sections.txt
+++ b/doc/reference/libeutil/libeutil-sections.txt
@@ -1251,6 +1251,29 @@ ECharsetComboBoxPrivate
</SECTION>
<SECTION>
+<FILE>e-client-cache</FILE>
+<TITLE>EClientCache</TITLE>
+EClientCache
+e_client_cache_new
+e_client_cache_ref_registry
+e_client_cache_get_client_sync
+e_client_cache_get_client
+e_client_cache_get_client_finish
+e_client_cache_ref_cached_client
+<SUBSECTION Standard>
+E_CLIENT_CACHE
+E_IS_CLIENT_CACHE
+E_TYPE_CLIENT_CACHE
+E_CLIENT_CACHE_CLASS
+E_IS_CLIENT_CACHE_CLASS
+E_CLIENT_CACHE_GET_CLASS
+EClientCacheClass
+e_client_cache_get_type
+<SUBSECTION Private>
+EClientCachePrivate
+</SECTION>
+
+<SECTION>
<FILE>e-config</FILE>
<TITLE>EConfig</TITLE>
EConfig
diff --git a/doc/reference/libeutil/libeutil.types b/doc/reference/libeutil/libeutil.types
index 83e2108bf1..181231561e 100644
--- a/doc/reference/libeutil/libeutil.types
+++ b/doc/reference/libeutil/libeutil.types
@@ -53,6 +53,7 @@ e_cell_toggle_get_type
e_cell_tree_get_type
e_cell_vbox_get_type
e_charset_combo_box_get_type
+e_client_cache_get_type
e_config_get_type
e_config_hook_get_type
e_contact_store_get_type
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index bdcde0929c..738ce8559d 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -158,6 +158,7 @@ eutilinclude_HEADERS = \
e-cell.h \
e-charset-combo-box.h \
e-charset.h \
+ e-client-cache.h \
e-config.h \
e-contact-store.h \
e-dateedit.h \
@@ -402,6 +403,7 @@ libeutil_la_SOURCES = \
e-cell.c \
e-charset-combo-box.c \
e-charset.c \
+ e-client-cache.c \
e-config.c \
e-contact-store.c \
e-dateedit.c \
diff --git a/e-util/e-client-cache.c b/e-util/e-client-cache.c
new file mode 100644
index 0000000000..d99302a1b5
--- /dev/null
+++ b/e-util/e-client-cache.c
@@ -0,0 +1,1079 @@
+/*
+ * e-client-cache.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-client-cache
+ * @include: e-util/e-util.h
+ * @short_description: Shared #EClient instances
+ *
+ * #EClientCache provides for application-wide sharing of #EClient
+ * instances and centralized rebroadcasting of #EClient::backend-died
+ * and #EClient::backend-error signals from cached #EClient instances.
+ *
+ * #EClientCache automatically invalidates cache entries in response to
+ * #EClient::backend-died signals. The #EClient instance is discarded,
+ * and a new instance is created on the next request.
+ **/
+
+#include "e-client-cache.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libecal/libecal.h>
+#include <libebook/libebook.h>
+#include <libebackend/libebackend.h>
+
+#define E_CLIENT_CACHE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CLIENT_CACHE, EClientCachePrivate))
+
+typedef struct _ClientData ClientData;
+typedef struct _SignalClosure SignalClosure;
+
+struct _EClientCachePrivate {
+ GWeakRef registry;
+
+ GHashTable *client_ht;
+ GMutex client_ht_lock;
+
+ /* For signal emissions. */
+ GMainContext *main_context;
+};
+
+struct _ClientData {
+ volatile gint ref_count;
+ GMutex lock;
+ GWeakRef cache;
+ EClient *client;
+ GQueue connecting;
+ gulong client_died_handler_id;
+ gulong client_error_handler_id;
+};
+
+struct _SignalClosure {
+ EClientCache *cache;
+ EClient *client;
+ gchar *error_message;
+};
+
+G_DEFINE_TYPE_WITH_CODE (
+ EClientCache,
+ e_client_cache,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EXTENSIBLE, NULL))
+
+enum {
+ PROP_0,
+ PROP_REGISTRY
+};
+
+enum {
+ BACKEND_DIED,
+ BACKEND_ERROR,
+ CLIENT_CREATED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static ClientData *
+client_data_new (EClientCache *cache)
+{
+ ClientData *client_data;
+
+ client_data = g_slice_new0 (ClientData);
+ client_data->ref_count = 1;
+ g_mutex_init (&client_data->lock);
+ g_weak_ref_set (&client_data->cache, cache);
+
+ return client_data;
+}
+
+static ClientData *
+client_data_ref (ClientData *client_data)
+{
+ g_return_val_if_fail (client_data != NULL, NULL);
+ g_return_val_if_fail (client_data->ref_count > 0, NULL);
+
+ g_atomic_int_inc (&client_data->ref_count);
+
+ return client_data;
+}
+
+static void
+client_data_unref (ClientData *client_data)
+{
+ g_return_if_fail (client_data != NULL);
+ g_return_if_fail (client_data->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&client_data->ref_count)) {
+
+ /* The signal handlers hold a reference on client_data,
+ * so we should not be here unless the signal handlers
+ * have already been disconnected. */
+ g_warn_if_fail (client_data->client_died_handler_id == 0);
+ g_warn_if_fail (client_data->client_error_handler_id == 0);
+
+ g_mutex_clear (&client_data->lock);
+ g_clear_object (&client_data->client);
+ g_weak_ref_set (&client_data->cache, NULL);
+
+ /* There should be no connect() operations in progress. */
+ g_warn_if_fail (g_queue_is_empty (&client_data->connecting));
+
+ g_slice_free (ClientData, client_data);
+ }
+}
+
+static void
+client_data_dispose (ClientData *client_data)
+{
+ g_mutex_lock (&client_data->lock);
+
+ if (client_data->client != NULL) {
+ g_signal_handler_disconnect (
+ client_data->client,
+ client_data->client_died_handler_id);
+ client_data->client_died_handler_id = 0;
+
+ g_signal_handler_disconnect (
+ client_data->client,
+ client_data->client_error_handler_id);
+ client_data->client_error_handler_id = 0;
+
+ g_clear_object (&client_data->client);
+ }
+
+ g_mutex_unlock (&client_data->lock);
+
+ client_data_unref (client_data);
+}
+
+static void
+signal_closure_free (SignalClosure *signal_closure)
+{
+ g_clear_object (&signal_closure->cache);
+ g_clear_object (&signal_closure->client);
+
+ g_free (signal_closure->error_message);
+
+ g_slice_free (SignalClosure, signal_closure);
+}
+
+static ClientData *
+client_ht_lookup (EClientCache *cache,
+ ESource *source,
+ const gchar *extension_name)
+{
+ GHashTable *client_ht;
+ GHashTable *inner_ht;
+ ClientData *client_data = NULL;
+
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+ g_return_val_if_fail (extension_name != NULL, NULL);
+
+ client_ht = cache->priv->client_ht;
+
+ g_mutex_lock (&cache->priv->client_ht_lock);
+
+ /* We pre-load the hash table with supported extension names,
+ * so lookup failures indicate an unsupported extension name. */
+ inner_ht = g_hash_table_lookup (client_ht, extension_name);
+ if (inner_ht != NULL) {
+ client_data = g_hash_table_lookup (inner_ht, source);
+ if (client_data == NULL) {
+ g_object_ref (source);
+ client_data = client_data_new (cache);
+ g_hash_table_insert (inner_ht, source, client_data);
+ }
+ client_data_ref (client_data);
+ }
+
+ g_mutex_unlock (&cache->priv->client_ht_lock);
+
+ return client_data;
+}
+
+static gchar *
+client_cache_build_source_description (EClientCache *cache,
+ ESource *source)
+{
+ GString *description;
+ ESourceRegistry *registry;
+ gchar *display_name;
+
+ description = g_string_sized_new (128);
+
+ registry = e_client_cache_ref_registry (cache);
+ if (registry != NULL) {
+ ESource *parent;
+ gchar *parent_uid;
+
+ parent_uid = e_source_dup_parent (source);
+ parent = e_source_registry_ref_source (registry, parent_uid);
+ g_free (parent_uid);
+
+ if (parent != NULL) {
+ display_name = e_source_dup_display_name (parent);
+ g_string_append (description, display_name);
+ g_string_append (description, " / ");
+ g_free (display_name);
+
+ g_object_unref (parent);
+ }
+
+ g_object_unref (registry);
+ }
+
+ display_name = e_source_dup_display_name (source);
+ g_string_append (description, display_name);
+ g_free (display_name);
+
+ return g_string_free (description, FALSE);
+}
+
+static gboolean
+client_cache_emit_backend_died_idle_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ EAlert *alert;
+ ESource *source;
+ const gchar *alert_id = NULL;
+ const gchar *extension_name;
+ gchar *description;
+
+ source = e_client_get_source (signal_closure->client);
+
+ extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+ if (e_source_has_extension (source, extension_name))
+ alert_id = "system:address-book-backend-died";
+
+ extension_name = E_SOURCE_EXTENSION_CALENDAR;
+ if (e_source_has_extension (source, extension_name))
+ alert_id = "system:calendar-backend-died";
+
+ extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
+ if (e_source_has_extension (source, extension_name))
+ alert_id = "system:memo-list-backend-died";
+
+ extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+ if (e_source_has_extension (source, extension_name))
+ alert_id = "system:task-list-backend-died";
+
+ g_return_val_if_fail (alert_id != NULL, FALSE);
+
+ description = client_cache_build_source_description (
+ signal_closure->cache, source);
+ alert = e_alert_new (alert_id, description, NULL);
+ g_free (description);
+
+ g_signal_emit (
+ signal_closure->cache,
+ signals[BACKEND_DIED], 0,
+ signal_closure->client,
+ alert);
+
+ g_object_unref (alert);
+
+ return FALSE;
+}
+
+static gboolean
+client_cache_emit_backend_error_idle_cb (gpointer user_data)
+{
+ SignalClosure *signal_closure = user_data;
+ EAlert *alert;
+ ESource *source;
+ const gchar *alert_id = NULL;
+ const gchar *extension_name;
+ gchar *description;
+
+ source = e_client_get_source (signal_closure->client);
+
+ extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+ if (e_source_has_extension (source, extension_name))
+ alert_id = "system:address-book-backend-error";
+
+ extension_name = E_SOURCE_EXTENSION_CALENDAR;
+ if (e_source_has_extension (source, extension_name))
+ alert_id = "system:calendar-backend-error";
+
+ extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
+ if (e_source_has_extension (source, extension_name))
+ alert_id = "system:memo-list-backend-error";
+
+ extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+ if (e_source_has_extension (source, extension_name))
+ alert_id = "system:task-list-backend-error";
+
+ g_return_val_if_fail (alert_id != NULL, FALSE);
+
+ description = client_cache_build_source_description (
+ signal_closure->cache, source);
+ alert = e_alert_new (
+ alert_id, description,
+ signal_closure->error_message, NULL);
+ g_free (description);
+
+ g_signal_emit (
+ signal_closure->cache,
+ signals[BACKEND_ERROR], 0,
+ signal_closure->client,
+ alert);
+
+ g_object_unref (alert);
+
+ return FALSE;
+}
+
+static void
+client_cache_backend_died_cb (EClient *client,
+ ClientData *client_data)
+{
+ EClientCache *cache;
+
+ cache = g_weak_ref_get (&client_data->cache);
+
+ if (cache != NULL) {
+ GSource *idle_source;
+ SignalClosure *signal_closure;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ signal_closure->cache = g_object_ref (cache);
+ signal_closure->client = g_object_ref (client);
+
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (
+ idle_source,
+ client_cache_emit_backend_died_idle_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+ g_source_attach (idle_source, cache->priv->main_context);
+ g_source_unref (idle_source);
+
+ g_object_unref (cache);
+ }
+}
+
+static void
+client_cache_backend_error_cb (EClient *client,
+ const gchar *error_message,
+ ClientData *client_data)
+{
+ EClientCache *cache;
+
+ cache = g_weak_ref_get (&client_data->cache);
+
+ if (cache != NULL) {
+ GSource *idle_source;
+ SignalClosure *signal_closure;
+
+ signal_closure = g_slice_new0 (SignalClosure);
+ signal_closure->cache = g_object_ref (cache);
+ signal_closure->client = g_object_ref (client);
+ signal_closure->error_message = g_strdup (error_message);
+
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (
+ idle_source,
+ client_cache_emit_backend_error_idle_cb,
+ signal_closure,
+ (GDestroyNotify) signal_closure_free);
+ g_source_attach (idle_source, cache->priv->main_context);
+ g_source_unref (idle_source);
+
+ g_object_unref (cache);
+ }
+}
+
+static void
+client_cache_process_results (ClientData *client_data,
+ EClient *client,
+ const GError *error)
+{
+ GQueue queue = G_QUEUE_INIT;
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((client != NULL) && (error == NULL)) ||
+ ((client == NULL) && (error != NULL)));
+
+ g_mutex_lock (&client_data->lock);
+
+ /* Complete async operations outside the lock. */
+ e_queue_transfer (&client_data->connecting, &queue);
+
+ if (client != NULL) {
+ EClientCache *cache;
+
+ client_data->client = g_object_ref (client);
+
+ cache = g_weak_ref_get (&client_data->cache);
+
+ /* If the EClientCache has been disposed already,
+ * there's no point in connecting signal handlers. */
+ if (cache != NULL) {
+ gulong handler_id;
+
+ /* client_data_dispose() will break the
+ * reference cycles we're creating here. */
+
+ handler_id = g_signal_connect_data (
+ client, "backend-died",
+ G_CALLBACK (client_cache_backend_died_cb),
+ client_data_ref (client_data),
+ (GClosureNotify) client_data_unref,
+ 0);
+ client_data->client_died_handler_id = handler_id;
+
+ handler_id = g_signal_connect_data (
+ client, "backend-error",
+ G_CALLBACK (client_cache_backend_error_cb),
+ client_data_ref (client_data),
+ (GClosureNotify) client_data_unref,
+ 0);
+ client_data->client_error_handler_id = handler_id;
+
+ g_signal_emit (
+ cache, signals[CLIENT_CREATED], 0, client);
+
+ g_object_unref (cache);
+ }
+ }
+
+ g_mutex_unlock (&client_data->lock);
+
+ while (!g_queue_is_empty (&queue)) {
+ GSimpleAsyncResult *simple;
+
+ simple = g_queue_pop_head (&queue);
+ if (client != NULL)
+ g_simple_async_result_set_op_res_gpointer (
+ simple, g_object_ref (client),
+ (GDestroyNotify) g_object_unref);
+ if (error != NULL)
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ }
+}
+
+static void
+client_cache_book_connect_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ClientData *client_data = user_data;
+ EClient *client;
+ GError *error = NULL;
+
+ client = e_book_client_connect_finish (result, &error);
+
+ client_cache_process_results (client_data, client, error);
+
+ if (client != NULL)
+ g_object_unref (client);
+
+ if (error != NULL)
+ g_error_free (error);
+
+ client_data_unref (client_data);
+}
+
+static void
+client_cache_cal_connect_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ClientData *client_data = user_data;
+ EClient *client;
+ GError *error = NULL;
+
+ client = e_cal_client_connect_finish (result, &error);
+
+ client_cache_process_results (client_data, client, error);
+
+ if (client != NULL)
+ g_object_unref (client);
+
+ if (error != NULL)
+ g_error_free (error);
+
+ client_data_unref (client_data);
+}
+
+static void
+client_cache_set_registry (EClientCache *cache,
+ ESourceRegistry *registry)
+{
+ g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+
+ g_weak_ref_set (&cache->priv->registry, registry);
+}
+
+static void
+client_cache_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_REGISTRY:
+ client_cache_set_registry (
+ E_CLIENT_CACHE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+client_cache_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_REGISTRY:
+ g_value_take_object (
+ value,
+ e_client_cache_ref_registry (
+ E_CLIENT_CACHE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+client_cache_dispose (GObject *object)
+{
+ EClientCachePrivate *priv;
+
+ priv = E_CLIENT_CACHE_GET_PRIVATE (object);
+
+ g_weak_ref_set (&priv->registry, NULL);
+
+ g_hash_table_remove_all (priv->client_ht);
+
+ if (priv->main_context != NULL) {
+ g_main_context_unref (priv->main_context);
+ priv->main_context = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_client_cache_parent_class)->dispose (object);
+}
+
+static void
+client_cache_finalize (GObject *object)
+{
+ EClientCachePrivate *priv;
+
+ priv = E_CLIENT_CACHE_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->client_ht);
+ g_mutex_clear (&priv->client_ht_lock);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_client_cache_parent_class)->finalize (object);
+}
+
+static void
+client_cache_constructed (GObject *object)
+{
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_client_cache_parent_class)->constructed (object);
+
+ e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static void
+e_client_cache_class_init (EClientCacheClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EClientCachePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = client_cache_set_property;
+ object_class->get_property = client_cache_get_property;
+ object_class->dispose = client_cache_dispose;
+ object_class->finalize = client_cache_finalize;
+ object_class->constructed = client_cache_constructed;
+
+ /**
+ * EClientCache:registry:
+ *
+ * The #ESourceRegistry manages #ESource instances.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_REGISTRY,
+ g_param_spec_object (
+ "registry",
+ "Registry",
+ "Data source registry",
+ E_TYPE_SOURCE_REGISTRY,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EClientCache::backend-died:
+ * @cache: the #EClientCache that received the signal
+ * @client: the #EClient that received the D-Bus notification
+ * @alert: an #EAlert with a user-friendly error description
+ *
+ * Rebroadcasts a #EClient::backend-died signal emitted by @client,
+ * along with a pre-formatted #EAlert.
+ *
+ * As a convenience to signal handlers, this signal is always emitted
+ * from the #GMainContext that was thread-default when the @cache was
+ * created.
+ **/
+ signals[BACKEND_DIED] = g_signal_new (
+ "backend-died",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EClientCacheClass, backend_died),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ E_TYPE_CLIENT,
+ E_TYPE_ALERT);
+
+ /**
+ * EClientCache::backend-error:
+ * @cache: the #EClientCache that received the signal
+ * @client: the #EClient that received the D-Bus notification
+ * @alert: an #EAlert with a user-friendly error description
+ *
+ * Rebroadcasts a #EClient::backend-error signal emitted by @client,
+ * along with a pre-formatted #EAlert.
+ *
+ * As a convenience to signal handlers, this signal is always emitted
+ * from the #GMainContext that was thread-default when the @cache was
+ * created.
+ **/
+ signals[BACKEND_ERROR] = g_signal_new (
+ "backend-error",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EClientCacheClass, backend_error),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ E_TYPE_CLIENT,
+ E_TYPE_ALERT);
+
+ /**
+ * EClientCache::client-created:
+ * @cache: the #EClientCache the received the signal
+ * @client: the newly-created #EClient
+ *
+ * This signal is emitted when a call to e_client_cache_get_client()
+ * triggers the creation of a new #EClient instance.
+ **/
+ signals[CLIENT_CREATED] = g_signal_new (
+ "client-created",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (EClientCacheClass, client_created),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ E_TYPE_CLIENT);
+}
+
+static void
+e_client_cache_init (EClientCache *cache)
+{
+ GHashTable *client_ht;
+ gint ii;
+
+ const gchar *extension_names[] = {
+ E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ E_SOURCE_EXTENSION_CALENDAR,
+ E_SOURCE_EXTENSION_MEMO_LIST,
+ E_SOURCE_EXTENSION_TASK_LIST
+ };
+
+ client_ht = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_hash_table_unref);
+
+ cache->priv = E_CLIENT_CACHE_GET_PRIVATE (cache);
+
+ cache->priv->main_context = g_main_context_ref_thread_default ();
+ cache->priv->client_ht = client_ht;
+
+ g_mutex_init (&cache->priv->client_ht_lock);
+
+ /* Pre-load the extension names that can be used to instantiate
+ * EClients. Then we can validate an extension name by testing
+ * for a matching hash table key. */
+
+ for (ii = 0; ii < G_N_ELEMENTS (extension_names); ii++) {
+ GHashTable *inner_ht;
+
+ inner_ht = g_hash_table_new_full (
+ (GHashFunc) e_source_hash,
+ (GEqualFunc) e_source_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) client_data_dispose);
+
+ g_hash_table_insert (
+ client_ht,
+ g_strdup (extension_names[ii]),
+ g_hash_table_ref (inner_ht));
+
+ g_hash_table_unref (inner_ht);
+ }
+}
+
+/**
+ * e_client_cache_new:
+ * @registry: an #ESourceRegistry
+ *
+ * Creates a new #EClientCache instance.
+ *
+ * Returns: an #EClientCache
+ **/
+EClientCache *
+e_client_cache_new (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return g_object_new (
+ E_TYPE_CLIENT_CACHE,
+ "registry", registry, NULL);
+}
+
+/**
+ * e_client_cache_ref_registry:
+ * @cache: an #EClientCache
+ *
+ * Returns the #ESourceRegistry passed to e_client_cache_new().
+ *
+ * The returned #ESourceRegistry is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: an #ESourceRegistry
+ **/
+ESourceRegistry *
+e_client_cache_ref_registry (EClientCache *cache)
+{
+ g_return_val_if_fail (E_IS_CLIENT_CACHE (cache), NULL);
+
+ return g_weak_ref_get (&cache->priv->registry);
+}
+
+/**
+ * e_client_cache_get_client_sync:
+ * @cache: an #EClientCache
+ * @source: an #ESource
+ * @extension_name: an extension name
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Obtains a shared #EClient instance for @source, or else creates a new
+ * #EClient instance to be shared.
+ *
+ * The @extension_name determines the type of #EClient to obtain. Valid
+ * @extension_name values are:
+ *
+ * #E_SOURCE_EXTENSION_ADDRESS_BOOK will obtain an #EBookClient.
+ *
+ * #E_SOURCE_EXTENSION_CALENDAR will obtain an #ECalClient with a
+ * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_EVENTS.
+ *
+ * #E_SOURCE_EXTENSION_MEMO_LIST will obtain an #ECalClient with a
+ * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_MEMOS.
+ *
+ * #E_SOURCE_EXTENSION_TASK_LIST will obtain an #ECalClient with a
+ * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_TASKS.
+ *
+ * The @source must already have an #ESourceExtension by that name
+ * for this function to work. All other @extension_name values will
+ * result in an error.
+ *
+ * If a request for the same @source and @extension_name is already in
+ * progress when this function is called, this request will "piggyback"
+ * on the in-progress request such that they will both succeed or fail
+ * simultaneously.
+ *
+ * Unreference the returned #EClient with g_object_unref() when finished
+ * with it. If an error occurs, the function will set @error and return
+ * %NULL.
+ *
+ * Returns: an #EClient, or %NULL
+ **/
+EClient *
+e_client_cache_get_client_sync (EClientCache *cache,
+ ESource *source,
+ const gchar *extension_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ EAsyncClosure *closure;
+ GAsyncResult *result;
+ EClient *client;
+
+ g_return_val_if_fail (E_IS_CLIENT_CACHE (cache), NULL);
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+ g_return_val_if_fail (extension_name != NULL, NULL);
+
+ closure = e_async_closure_new ();
+
+ e_client_cache_get_client (
+ cache, source, extension_name, cancellable,
+ e_async_closure_callback, closure);
+
+ result = e_async_closure_wait (closure);
+
+ client = e_client_cache_get_client_finish (cache, result, error);
+
+ e_async_closure_free (closure);
+
+ return client;
+}
+
+/**
+ * e_client_cache_get_client:
+ * @cache: an #EClientCache
+ * @source: an #ESource
+ * @extension_name: an extension name
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously obtains a shared #EClient instance for @source, or else
+ * creates a new #EClient instance to be shared.
+ *
+ * The @extension_name determines the type of #EClient to obtain. Valid
+ * @extension_name values are:
+ *
+ * #E_SOURCE_EXTENSION_ADDRESS_BOOK will obtain an #EBookClient.
+ *
+ * #E_SOURCE_EXTENSION_CALENDAR will obtain an #ECalClient with a
+ * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_EVENTS.
+ *
+ * #E_SOURCE_EXTENSION_MEMO_LIST will obtain an #ECalClient with a
+ * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_MEMOS.
+ *
+ * #E_SOURCE_EXTENSION_TASK_LIST will obtain an #ECalClient with a
+ * #ECalClient:source-type of #E_CAL_CLIENT_SOURCE_TYPE_TASKS.
+ *
+ * The @source must already have an #ESourceExtension by that name
+ * for this function to work. All other @extension_name values will
+ * result in an error.
+ *
+ * If a request for the same @source and @extension_name is already in
+ * progress when this function is called, this request will "piggyback"
+ * on the in-progress request such that they will both succeed or fail
+ * simultaneously.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call e_client_cache_get_client_finish() to get the result of the
+ * operation.
+ **/
+void
+e_client_cache_get_client (EClientCache *cache,
+ ESource *source,
+ const gchar *extension_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ ClientData *client_data;
+ EClient *client = NULL;
+ gboolean connect_in_progress = FALSE;
+
+ g_return_if_fail (E_IS_CLIENT_CACHE (cache));
+ g_return_if_fail (E_IS_SOURCE (source));
+ g_return_if_fail (extension_name != NULL);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (cache), callback,
+ user_data, e_client_cache_get_client);
+
+ g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+ client_data = client_ht_lookup (cache, source, extension_name);
+
+ if (client_data == NULL) {
+ g_simple_async_result_set_error (
+ simple, G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Cannot create a client object from "
+ "extension name '%s'"), extension_name);
+ g_simple_async_result_complete_in_idle (simple);
+ goto exit;
+ }
+
+ g_mutex_lock (&client_data->lock);
+
+ if (client_data->client != NULL) {
+ client = g_object_ref (client_data->client);
+ } else {
+ GQueue *connecting = &client_data->connecting;
+ connect_in_progress = !g_queue_is_empty (connecting);
+ g_queue_push_tail (connecting, g_object_ref (simple));
+ }
+
+ g_mutex_unlock (&client_data->lock);
+
+ /* If a cached EClient already exists, we're done. */
+ if (client != NULL) {
+ g_simple_async_result_set_op_res_gpointer (
+ simple, client, (GDestroyNotify) g_object_unref);
+ g_simple_async_result_complete_in_idle (simple);
+ goto exit;
+ }
+
+ /* If an EClient connection attempt is already in progress, our
+ * cache request will complete when it finishes, so now we wait. */
+ if (connect_in_progress)
+ goto exit;
+
+ /* Create an appropriate EClient instance for the extension
+ * name. The client_ht_lookup() call above ensures us that
+ * one of these options will match. */
+
+ if (g_str_equal (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
+ e_book_client_connect (
+ source, cancellable,
+ client_cache_book_connect_cb,
+ client_data_ref (client_data));
+ goto exit;
+ }
+
+ if (g_str_equal (extension_name, E_SOURCE_EXTENSION_CALENDAR)) {
+ e_cal_client_connect (
+ source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
+ cancellable, client_cache_cal_connect_cb,
+ client_data_ref (client_data));
+ goto exit;
+ }
+
+ if (g_str_equal (extension_name, E_SOURCE_EXTENSION_MEMO_LIST)) {
+ e_cal_client_connect (
+ source, E_CAL_CLIENT_SOURCE_TYPE_MEMOS,
+ cancellable, client_cache_cal_connect_cb,
+ client_data_ref (client_data));
+ goto exit;
+ }
+
+ if (g_str_equal (extension_name, E_SOURCE_EXTENSION_TASK_LIST)) {
+ e_cal_client_connect (
+ source, E_CAL_CLIENT_SOURCE_TYPE_TASKS,
+ cancellable, client_cache_cal_connect_cb,
+ client_data_ref (client_data));
+ goto exit;
+ }
+
+ g_warn_if_reached (); /* Should never happen. */
+
+exit:
+ g_object_unref (simple);
+}
+
+/**
+ * e_client_cache_get_client_finish:
+ * @cache: an #EClientCache
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_client_cache_get_client().
+ *
+ * Unreference the returned #EClient with g_object_unref() when finished
+ * with it. If an error occurred, the function will set @error and return
+ * %NULL.
+ *
+ * Returns: an #EClient, or %NULL
+ **/
+EClient *
+e_client_cache_get_client_finish (EClientCache *cache,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ EClient *client;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (cache),
+ e_client_cache_get_client), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ client = g_simple_async_result_get_op_res_gpointer (simple);
+ g_return_val_if_fail (client != NULL, NULL);
+
+ return g_object_ref (client);
+}
+
+/**
+ * e_client_cache_ref_cached_client:
+ * @cache: an #EClientCache
+ * @source: an #ESource
+ * @extension_name: an extension name
+ *
+ * Returns a shared #EClient instance for @source and @extension_name if
+ * such an instance is already cached, or else %NULL. This function does
+ * not create a new #EClient instance, and therefore does not block.
+ *
+ * See e_client_cache_get_client() for valid @extension_name values.
+ *
+ * The returned #EClient is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: an #EClient, or %NULL
+ **/
+EClient *
+e_client_cache_ref_cached_client (EClientCache *cache,
+ ESource *source,
+ const gchar *extension_name)
+{
+ ClientData *client_data;
+ EClient *client = NULL;
+
+ g_return_val_if_fail (E_IS_CLIENT_CACHE (cache), NULL);
+ g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+ g_return_val_if_fail (extension_name != NULL, NULL);
+
+ client_data = client_ht_lookup (cache, source, extension_name);
+
+ if (client_data != NULL) {
+ g_mutex_lock (&client_data->lock);
+ if (client_data->client != NULL)
+ client = g_object_ref (client_data->client);
+ g_mutex_unlock (&client_data->lock);
+
+ client_data_unref (client_data);
+ }
+
+ return client;
+}
diff --git a/e-util/e-client-cache.h b/e-util/e-client-cache.h
new file mode 100644
index 0000000000..d4e3799161
--- /dev/null
+++ b/e-util/e-client-cache.h
@@ -0,0 +1,106 @@
+/*
+ * e-client-cache.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CLIENT_CACHE_H
+#define E_CLIENT_CACHE_H
+
+#include <e-util/e-alert.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CLIENT_CACHE \
+ (e_client_cache_get_type ())
+#define E_CLIENT_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_CLIENT_CACHE, EClientCache))
+#define E_CLIENT_CACHE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_CLIENT_CACHE, EClientCacheClass))
+#define E_IS_CLIENT_CACHE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_CLIENT_CACHE))
+#define E_IS_CLIENT_CACHE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_CLIENT_CACHE))
+#define E_CLIENT_CACHE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_CLIENT_CACHE, EClientCacheClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EClientCache EClientCache;
+typedef struct _EClientCacheClass EClientCacheClass;
+typedef struct _EClientCachePrivate EClientCachePrivate;
+
+/**
+ * EClientCache:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ **/
+struct _EClientCache {
+ GObject parent;
+ EClientCachePrivate *priv;
+};
+
+struct _EClientCacheClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*backend_died) (EClientCache *cache,
+ EClient *client,
+ EAlert *alert);
+ void (*backend_error) (EClientCache *cache,
+ EClient *client,
+ EAlert *alert);
+ void (*client_created) (EClientCache *cache,
+ EClient *client);
+};
+
+GType e_client_cache_get_type (void) G_GNUC_CONST;
+EClientCache * e_client_cache_new (ESourceRegistry *registry);
+ESourceRegistry *
+ e_client_cache_ref_registry (EClientCache *cache);
+EClient * e_client_cache_get_client_sync (EClientCache *cache,
+ ESource *source,
+ const gchar *extension_name,
+ GCancellable *cancellable,
+ GError **error);
+void e_client_cache_get_client (EClientCache *cache,
+ ESource *source,
+ const gchar *extension_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+EClient * e_client_cache_get_client_finish
+ (EClientCache *cache,
+ GAsyncResult *result,
+ GError **error);
+EClient * e_client_cache_ref_cached_client
+ (EClientCache *cache,
+ ESource *source,
+ const gchar *extension_name);
+
+G_END_DECLS
+
+#endif /* E_CLIENT_CACHE_H */
+
diff --git a/e-util/e-system.error.xml b/e-util/e-system.error.xml
index c1a68c5881..b28f50bc26 100644
--- a/e-util/e-system.error.xml
+++ b/e-util/e-system.error.xml
@@ -45,4 +45,44 @@
<_secondary>The reported error was &quot;{1}&quot;.</_secondary>
</error>
+ <error id="address-book-backend-died" type="error">
+ <_primary>The address book backend servicing &quot;{0}&quot; has quit unexpectedly.</_primary>
+ <_secondary>Some of your contacts may not be available until Evolution is restarted.</_secondary>
+ </error>
+
+ <error id="calendar-backend-died" type="error">
+ <_primary>The calendar backend servicing &quot;{0}&quot; has quit unexpectedly.</_primary>
+ <_secondary>Some of your appointments may not be available until Evolution is restarted.</_secondary>
+ </error>
+
+ <error id="memo-list-backend-died" type="error">
+ <_primary>The memo list backend servicing &quot;{0}&quot; has quit unexpectedly.</_primary>
+ <_secondary>Some of your memos may not be available until Evolution is restarted.</_secondary>
+ </error>
+
+ <error id="task-list-backend-died" type="error">
+ <_primary>The task list backend servicing &quot;{0}&quot; has quit unexpectedly.</_primary>
+ <_secondary>Some of your tasks may not be available until Evolution is restarted.</_secondary>
+ </error>
+
+ <error id="address-book-backend-error" type="warning">
+ <_primary>The address book backend servicing &quot;{0}&quot; encountered an error.</_primary>
+ <_secondary>The reported error was &quot;{1}&quot;.</_secondary>
+ </error>
+
+ <error id="calendar-backend-error" type="warning">
+ <_primary>The calendar backend servicing &quot;{0}&quot; encountered an error.</_primary>
+ <_secondary>The reported error was &quot;{1}&quot;.</_secondary>
+ </error>
+
+ <error id="memo-list-backend-error" type="warning">
+ <_primary>The memo list backend servicing &quot;{0}&quot; encountered an error.</_primary>
+ <_secondary>The reported error was &quot;{1}&quot;.</_secondary>
+ </error>
+
+ <error id="task-list-backend-error" type="warning">
+ <_primary>The task list backend servicing &quot;{0}&quot; encountered an error.</_primary>
+ <_secondary>The reported error was &quot;{1}&quot;.</_secondary>
+ </error>
+
</error-list>
diff --git a/e-util/e-util.h b/e-util/e-util.h
index e71c94f208..cd8f08b5b8 100644
--- a/e-util/e-util.h
+++ b/e-util/e-util.h
@@ -80,6 +80,7 @@
#include <e-util/e-cell.h>
#include <e-util/e-charset-combo-box.h>
#include <e-util/e-charset.h>
+#include <e-util/e-client-cache.h>
#include <e-util/e-config.h>
#include <e-util/e-contact-store.h>
#include <e-util/e-dateedit.h>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0aed841520..cc9b41abd9 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -218,6 +218,7 @@ e-util/e-cell-pixbuf.c
e-util/e-cell-text.c
e-util/e-charset-combo-box.c
e-util/e-charset.c
+e-util/e-client-cache.c
e-util/e-dateedit.c
e-util/e-datetime-format.c
e-util/e-dialog-utils.c