From cefa5edf7f86eacf8c2cdb168ef193f9a8d3a777 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Tue, 12 Feb 2013 10:57:19 -0500 Subject: 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. --- e-util/e-client-cache.c | 1079 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1079 insertions(+) create mode 100644 e-util/e-client-cache.c (limited to 'e-util/e-client-cache.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 + * + */ + +/** + * 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 +#include + +#include +#include +#include + +#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; +} -- cgit v1.2.3