Files
evolution/e-util/e-client-cache.c
Matthew Barnes d84b67875c EClientCache: Keep a strong reference on the ESourceRegistry.
Changed my mind.  There's some scenarios where a function depends on an
ESourceRegistry but could also utilize an EClientCache, and it would be
nice to just pass the EClientCache and extract the ESourceRegistry from
it, so we need to make sure the ESourceRegistry will be there when it's
needed and not quietly disappear.
2013-02-17 14:29:49 -05:00

1079 lines
29 KiB
C

/*
* 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 {
ESourceRegistry *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)
{
ESourceRegistry *registry;
ESource *parent;
GString *description;
gchar *display_name;
gchar *parent_uid;
description = g_string_sized_new (128);
registry = e_client_cache_ref_registry (cache);
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);
}
display_name = e_source_dup_display_name (source);
g_string_append (description, display_name);
g_free (display_name);
g_object_unref (registry);
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_return_if_fail (cache->priv->registry == NULL);
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);
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)
{
/* 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_object_ref (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;
}