/* * 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, * #EClient::backend-error and #GObject::notify 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 { ESourceRegistry *registry; gulong source_removed_handler_id; gulong source_disabled_handler_id; GHashTable *client_ht; GMutex client_ht_lock; /* For signal emissions. */ GMainContext *main_context; }; struct _ClientData { volatile gint ref_count; GMutex lock; GWeakRef client_cache; EClient *client; GQueue connecting; gboolean dead_backend; gulong backend_died_handler_id; gulong backend_error_handler_id; gulong notify_handler_id; }; struct _SignalClosure { EClientCache *client_cache; EClient *client; GParamSpec *pspec; 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, CLIENT_NOTIFY, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static ClientData * client_data_new (EClientCache *client_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->client_cache, client_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->backend_died_handler_id == 0); g_warn_if_fail (client_data->backend_error_handler_id == 0); g_warn_if_fail (client_data->notify_handler_id == 0); g_mutex_clear (&client_data->lock); g_clear_object (&client_data->client); g_weak_ref_set (&client_data->client_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->backend_died_handler_id); client_data->backend_died_handler_id = 0; g_signal_handler_disconnect ( client_data->client, client_data->backend_error_handler_id); client_data->backend_error_handler_id = 0; g_signal_handler_disconnect ( client_data->client, client_data->notify_handler_id); client_data->notify_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->client_cache); g_clear_object (&signal_closure->client); if (signal_closure->pspec != NULL) g_param_spec_unref (signal_closure->pspec); g_free (signal_closure->error_message); g_slice_free (SignalClosure, signal_closure); } static ClientData * client_ht_lookup (EClientCache *client_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 = client_cache->priv->client_ht; g_mutex_lock (&client_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 (client_cache); g_hash_table_insert (inner_ht, source, client_data); } client_data_ref (client_data); } g_mutex_unlock (&client_cache->priv->client_ht_lock); return client_data; } static gboolean client_ht_remove (EClientCache *client_cache, ESource *source) { GHashTable *client_ht; GHashTableIter client_ht_iter; gpointer inner_ht; gboolean removed = FALSE; g_return_val_if_fail (E_IS_SOURCE (source), FALSE); client_ht = client_cache->priv->client_ht; g_mutex_lock (&client_cache->priv->client_ht_lock); g_hash_table_iter_init (&client_ht_iter, client_ht); while (g_hash_table_iter_next (&client_ht_iter, NULL, &inner_ht)) removed |= g_hash_table_remove (inner_ht, source); g_mutex_unlock (&client_cache->priv->client_ht_lock); return removed; } static gboolean client_cache_emit_backend_died_idle_cb (gpointer user_data) { SignalClosure *signal_closure = user_data; ESourceRegistry *registry; EAlert *alert; ESource *source; const gchar *alert_id = NULL; const gchar *extension_name; gchar *display_name = NULL; source = e_client_get_source (signal_closure->client); registry = e_client_cache_ref_registry (signal_closure->client_cache); extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK; if (e_source_has_extension (source, extension_name)) { alert_id = "system:address-book-backend-died"; display_name = e_source_registry_dup_unique_display_name ( registry, source, extension_name); } extension_name = E_SOURCE_EXTENSION_CALENDAR; if (e_source_has_extension (source, extension_name)) { alert_id = "system:calendar-backend-died"; display_name = e_source_registry_dup_unique_display_name ( registry, source, extension_name); } extension_name = E_SOURCE_EXTENSION_MEMO_LIST; if (e_source_has_extension (source, extension_name)) { alert_id = "system:memo-list-backend-died"; display_name = e_source_registry_dup_unique_display_name ( registry, source, extension_name); } extension_name = E_SOURCE_EXTENSION_TASK_LIST; if (e_source_has_extension (source, extension_name)) { alert_id = "system:task-list-backend-died"; display_name = e_source_registry_dup_unique_display_name ( registry, source, extension_name); } g_object_unref (registry); g_return_val_if_fail (alert_id != NULL, FALSE); g_return_val_if_fail (display_name != NULL, FALSE); alert = e_alert_new (alert_id, display_name, NULL); g_signal_emit ( signal_closure->client_cache, signals[BACKEND_DIED], 0, signal_closure->client, alert); g_object_unref (alert); g_free (display_name); return FALSE; } static gboolean client_cache_emit_backend_error_idle_cb (gpointer user_data) { SignalClosure *signal_closure = user_data; ESourceRegistry *registry; EAlert *alert; ESource *source; const gchar *alert_id = NULL; const gchar *extension_name; gchar *display_name = NULL; source = e_client_get_source (signal_closure->client); registry = e_client_cache_ref_registry (signal_closure->client_cache); extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK; if (e_source_has_extension (source, extension_name)) { alert_id = "system:address-book-backend-error"; display_name = e_source_registry_dup_unique_display_name ( registry, source, extension_name); } extension_name = E_SOURCE_EXTENSION_CALENDAR; if (e_source_has_extension (source, extension_name)) { alert_id = "system:calendar-backend-error"; display_name = e_source_registry_dup_unique_display_name ( registry, source, extension_name); } extension_name = E_SOURCE_EXTENSION_MEMO_LIST; if (e_source_has_extension (source, extension_name)) { alert_id = "system:memo-list-backend-error"; display_name = e_source_registry_dup_unique_display_name ( registry, source, extension_name); } extension_name = E_SOURCE_EXTENSION_TASK_LIST; if (e_source_has_extension (source, extension_name)) { alert_id = "system:task-list-backend-error"; display_name = e_source_registry_dup_unique_display_name ( registry, source, extension_name); } g_object_unref (registry); g_return_val_if_fail (alert_id != NULL, FALSE); g_return_val_if_fail (display_name != NULL, FALSE); alert = e_alert_new ( alert_id, display_name, signal_closure->error_message, NULL); g_signal_emit ( signal_closure->client_cache, signals[BACKEND_ERROR], 0, signal_closure->client, alert); g_object_unref (alert); g_free (display_name); return FALSE; } static gboolean client_cache_emit_client_notify_idle_cb (gpointer user_data) { SignalClosure *signal_closure = user_data; const gchar *name; name = g_param_spec_get_name (signal_closure->pspec); g_signal_emit ( signal_closure->client_cache, signals[CLIENT_NOTIFY], g_quark_from_string (name), signal_closure->client, signal_closure->pspec); return FALSE; } static gboolean client_cache_emit_client_created_idle_cb (gpointer user_data) { SignalClosure *signal_closure = user_data; g_signal_emit ( signal_closure->client_cache, signals[CLIENT_CREATED], 0, signal_closure->client); return FALSE; } static void client_cache_backend_died_cb (EClient *client, ClientData *client_data) { EClientCache *client_cache; client_cache = g_weak_ref_get (&client_data->client_cache); if (client_cache != NULL) { GSource *idle_source; SignalClosure *signal_closure; signal_closure = g_slice_new0 (SignalClosure); signal_closure->client_cache = g_object_ref (client_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, client_cache->priv->main_context); g_source_unref (idle_source); g_object_unref (client_cache); } /* Discard the EClient and tag the backend as * dead until we create a replacement EClient. */ g_mutex_lock (&client_data->lock); g_clear_object (&client_data->client); client_data->dead_backend = TRUE; g_mutex_unlock (&client_data->lock); } static void client_cache_backend_error_cb (EClient *client, const gchar *error_message, ClientData *client_data) { EClientCache *client_cache; client_cache = g_weak_ref_get (&client_data->client_cache); if (client_cache != NULL) { GSource *idle_source; SignalClosure *signal_closure; signal_closure = g_slice_new0 (SignalClosure); signal_closure->client_cache = g_object_ref (client_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, client_cache->priv->main_context); g_source_unref (idle_source); g_object_unref (client_cache); } } static void client_cache_notify_cb (EClient *client, GParamSpec *pspec, ClientData *client_data) { EClientCache *client_cache; client_cache = g_weak_ref_get (&client_data->client_cache); if (client_cache != NULL) { GSource *idle_source; SignalClosure *signal_closure; signal_closure = g_slice_new0 (SignalClosure); signal_closure->client_cache = g_object_ref (client_cache); signal_closure->client = g_object_ref (client); signal_closure->pspec = g_param_spec_ref (pspec); idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, client_cache_emit_client_notify_idle_cb, signal_closure, (GDestroyNotify) signal_closure_free); g_source_attach ( idle_source, client_cache->priv->main_context); g_source_unref (idle_source); g_object_unref (client_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 *client_cache; /* Make sure we're not leaking a reference. */ g_warn_if_fail (client_data->client == NULL); client_data->client = g_object_ref (client); client_data->dead_backend = FALSE; client_cache = g_weak_ref_get (&client_data->client_cache); /* If the EClientCache has been disposed already, * there's no point in connecting signal handlers. */ if (client_cache != NULL) { GSource *idle_source; SignalClosure *signal_closure; 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->backend_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->backend_error_handler_id = handler_id; handler_id = g_signal_connect_data ( client, "notify", G_CALLBACK (client_cache_notify_cb), client_data_ref (client_data), (GClosureNotify) client_data_unref, 0); client_data->notify_handler_id = handler_id; signal_closure = g_slice_new0 (SignalClosure); signal_closure->client_cache = g_object_ref (client_cache); signal_closure->client = g_object_ref (client); idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, client_cache_emit_client_created_idle_cb, signal_closure, (GDestroyNotify) signal_closure_free); g_source_attach ( idle_source, client_cache->priv->main_context); g_source_unref (idle_source); g_object_unref (client_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_in_idle (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_source_removed_cb (ESourceRegistry *registry, ESource *source, GWeakRef *weak_ref) { EClientCache *client_cache; client_cache = g_weak_ref_get (weak_ref); if (client_cache != NULL) { client_ht_remove (client_cache, source); g_object_unref (client_cache); } } static void client_cache_source_disabled_cb (ESourceRegistry *registry, ESource *source, GWeakRef *weak_ref) { EClientCache *client_cache; client_cache = g_weak_ref_get (weak_ref); if (client_cache != NULL) { client_ht_remove (client_cache, source); g_object_unref (client_cache); } } static void client_cache_set_registry (EClientCache *client_cache, ESourceRegistry *registry) { g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); g_return_if_fail (client_cache->priv->registry == NULL); client_cache->priv->registry = g_object_ref (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); if (priv->source_removed_handler_id > 0) { g_signal_handler_disconnect ( priv->registry, priv->source_removed_handler_id); priv->source_removed_handler_id = 0; } if (priv->source_disabled_handler_id > 0) { g_signal_handler_disconnect ( priv->registry, priv->source_disabled_handler_id); priv->source_disabled_handler_id = 0; } g_clear_object (&priv->registry); 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) { EClientCache *client_cache; ESourceRegistry *registry; gulong handler_id; client_cache = E_CLIENT_CACHE (object); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_client_cache_parent_class)->constructed (object); registry = e_client_cache_ref_registry (client_cache); handler_id = g_signal_connect_data ( registry, "source-removed", G_CALLBACK (client_cache_source_removed_cb), e_weak_ref_new (client_cache), (GClosureNotify) e_weak_ref_free, 0); client_cache->priv->source_removed_handler_id = handler_id; handler_id = g_signal_connect_data ( registry, "source-disabled", G_CALLBACK (client_cache_source_disabled_cb), e_weak_ref_new (client_cache), (GClosureNotify) e_weak_ref_free, 0); client_cache->priv->source_disabled_handler_id = handler_id; g_object_unref (registry); 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: * @client_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 @client_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: * @client_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 @client_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: * @client_cache: the #EClientCache that 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); /** * EClientCache::client-notify: * @client_cache: the #EClientCache that received the signal * @client: the #EClient whose property changed * @pspec: the #GParamSpec of the property that changed * * Rebroadcasts a #GObject::notify signal emitted by @client. * * This signal supports "::detail" appendices to the signal name * just like the #GObject::notify signal, so you can connect to * change notification signals for specific #EClient properties. * * As a convenience to signal handlers, this signal is always * emitted from the #GMainContext that was thread-default when * the @client_cache was created. **/ signals[CLIENT_NOTIFY] = g_signal_new ( "client-notify", G_TYPE_FROM_CLASS (class), /* same flags as GObject::notify */ G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS | G_SIGNAL_ACTION, G_STRUCT_OFFSET (EClientCacheClass, client_notify), NULL, NULL, NULL, G_TYPE_NONE, 2, E_TYPE_CLIENT, G_TYPE_PARAM); } static void e_client_cache_init (EClientCache *client_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); client_cache->priv = E_CLIENT_CACHE_GET_PRIVATE (client_cache); client_cache->priv->main_context = g_main_context_ref_thread_default (); client_cache->priv->client_ht = client_ht; g_mutex_init (&client_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: * @client_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 *client_cache) { g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), NULL); return g_object_ref (client_cache->priv->registry); } /** * e_client_cache_get_client_sync: * @client_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 *client_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 (client_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 ( client_cache, source, extension_name,cancellable, e_async_closure_callback, closure); result = e_async_closure_wait (closure); client = e_client_cache_get_client_finish ( client_cache, result, error); e_async_closure_free (closure); return client; } /** * e_client_cache_get_client: * @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 *client_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 (client_cache)); g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (extension_name != NULL); simple = g_simple_async_result_new ( G_OBJECT (client_cache), callback, user_data, e_client_cache_get_client); g_simple_async_result_set_check_cancellable (simple, cancellable); client_data = client_ht_lookup (client_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: * @client_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 *client_cache, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; EClient *client; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (client_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: * @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 *client_cache, ESource *source, const gchar *extension_name) { ClientData *client_data; EClient *client = NULL; g_return_val_if_fail (E_IS_CLIENT_CACHE (client_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 (client_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; } /** * e_client_cache_is_backend_dead: * @client_cache: an #EClientCache * @source: an #ESource * @extension_name: an extension name * * Returns %TRUE if an #EClient instance for @source and @extension_name * was recently discarded after having emitted a #EClient::backend-died * signal, and a replacement #EClient instance has not yet been created. * * Returns: whether the backend for @source and @extension_name died **/ gboolean e_client_cache_is_backend_dead (EClientCache *client_cache, ESource *source, const gchar *extension_name) { ClientData *client_data; gboolean dead_backend = FALSE; g_return_val_if_fail (E_IS_CLIENT_CACHE (client_cache), FALSE); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); g_return_val_if_fail (extension_name != NULL, FALSE); client_data = client_ht_lookup (client_cache, source, extension_name); if (client_data != NULL) { dead_backend = client_data->dead_backend; client_data_unref (client_data); } return dead_backend; }