[evolution] Add EClientCache.



commit cefa5edf7f86eacf8c2cdb168ef193f9a8d3a777
Author: Matthew Barnes <mbarnes redhat com>
Date:   Tue Feb 12 10:57:19 2013 -0500

    Add EClientCache.
    
    New class to help reduce code duplication and centralize some EClient
    handling policies.
    
    Benefits:
    
    - EClient instances can be shared across the entire application.
    
    - Centralized rebroadcasting of "backend-died" and "backend-error"
      signals emitted from cached EClient instances.
    
    - Automatic cache invalidation when backends crash.  The EClient
      is discarded, and a new instance is created on the next request.

 doc/reference/libeutil/libeutil-docs.sgml    |    1 +
 doc/reference/libeutil/libeutil-sections.txt |   23 +
 doc/reference/libeutil/libeutil.types        |    1 +
 e-util/Makefile.am                           |    2 +
 e-util/e-client-cache.c                      | 1079 ++++++++++++++++++++++++++
 e-util/e-client-cache.h                      |  106 +++
 e-util/e-system.error.xml                    |   40 +
 e-util/e-util.h                              |    1 +
 po/POTFILES.in                               |    1 +
 9 files changed, 1254 insertions(+), 0 deletions(-)
---
diff --git a/doc/reference/libeutil/libeutil-docs.sgml b/doc/reference/libeutil/libeutil-docs.sgml
index 86eeac1..1733a48 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 574db12..6b54492 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 83e2108..1812315 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 bdcde09..738ce85 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 0000000..d99302a
--- /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 0000000..d4e3799
--- /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 c1a68c5..b28f50b 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 e71c94f..cd8f08b 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 0aed841..cc9b41a 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


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