/*
* e-client-utils.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
*
*
* Copyright (C) 2011 Red Hat, Inc. (www.redhat.com)
*
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include "e-client-utils.h"
/**
* e_client_utils_new:
*
* Proxy function for e_book_client_utils_new() and e_cal_client_utils_new().
*
* Since: 3.2
**/
EClient *
e_client_utils_new (ESource *source,
EClientSourceType source_type,
GError **error)
{
EClient *res = NULL;
g_return_val_if_fail (source != NULL, NULL);
g_return_val_if_fail (E_IS_SOURCE (source), NULL);
switch (source_type) {
case E_CLIENT_SOURCE_TYPE_CONTACTS:
res = E_CLIENT (e_book_client_new (source, error));
break;
case E_CLIENT_SOURCE_TYPE_EVENTS:
res = E_CLIENT (e_cal_client_new (source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, error));
break;
case E_CLIENT_SOURCE_TYPE_MEMOS:
res = E_CLIENT (e_cal_client_new (source, E_CAL_CLIENT_SOURCE_TYPE_MEMOS, error));
break;
case E_CLIENT_SOURCE_TYPE_TASKS:
res = E_CLIENT (e_cal_client_new (source, E_CAL_CLIENT_SOURCE_TYPE_TASKS, error));
break;
default:
g_return_val_if_reached (NULL);
break;
}
return res;
}
typedef struct _EClientUtilsAsyncOpData {
GAsyncReadyCallback callback;
gpointer user_data;
GCancellable *cancellable;
ESource *source;
EClient *client;
gboolean open_finished;
GError *opened_cb_error;
guint retry_open_id;
gboolean only_if_exists;
guint pending_properties_count;
} EClientUtilsAsyncOpData;
static void
free_client_utils_async_op_data (EClientUtilsAsyncOpData *async_data)
{
g_return_if_fail (async_data != NULL);
g_return_if_fail (async_data->cancellable != NULL);
g_return_if_fail (async_data->client != NULL);
if (async_data->retry_open_id)
g_source_remove (async_data->retry_open_id);
g_signal_handlers_disconnect_matched (async_data->cancellable, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, async_data);
g_signal_handlers_disconnect_matched (async_data->client, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, async_data);
if (async_data->opened_cb_error)
g_error_free (async_data->opened_cb_error);
g_object_unref (async_data->cancellable);
g_object_unref (async_data->client);
g_object_unref (async_data->source);
g_free (async_data);
}
static gboolean
complete_async_op_in_idle_cb (gpointer user_data)
{
GSimpleAsyncResult *simple = user_data;
gint run_main_depth;
g_return_val_if_fail (simple != NULL, FALSE);
run_main_depth = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (simple), "run-main-depth"));
if (run_main_depth < 1)
run_main_depth = 1;
/* do not receive in higher level than was initially run */
if (g_main_depth () > run_main_depth) {
return TRUE;
}
g_simple_async_result_complete (simple);
g_object_unref (simple);
return FALSE;
}
#define return_async_error_if_fail(expr, callback, user_data, src, source_tag) G_STMT_START { \
if (G_LIKELY ((expr))) { } else { \
GError *error; \
\
error = g_error_new (E_CLIENT_ERROR, E_CLIENT_ERROR_INVALID_ARG, \
"%s: assertion '%s' failed", G_STRFUNC, #expr); \
\
return_async_error (error, callback, user_data, src, source_tag); \
g_error_free (error); \
return; \
} \
} G_STMT_END
static void
return_async_error (const GError *error,
GAsyncReadyCallback callback,
gpointer user_data,
ESource *source,
gpointer source_tag)
{
GSimpleAsyncResult *simple;
g_return_if_fail (error != NULL);
g_return_if_fail (source_tag != NULL);
simple = g_simple_async_result_new (G_OBJECT (source), callback, user_data, source_tag);
g_simple_async_result_set_from_error (simple, error);
g_object_set_data (G_OBJECT (simple), "run-main-depth", GINT_TO_POINTER (g_main_depth ()));
g_idle_add (complete_async_op_in_idle_cb, simple);
}
static void
client_utils_get_backend_property_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
EClient *client = E_CLIENT (source_object);
EClientUtilsAsyncOpData *async_data = user_data;
GSimpleAsyncResult *simple;
g_return_if_fail (async_data != NULL);
g_return_if_fail (async_data->client != NULL);
g_return_if_fail (async_data->client == client);
if (result) {
gchar *prop_value = NULL;
if (e_client_get_backend_property_finish (client, result, &prop_value, NULL))
g_free (prop_value);
async_data->pending_properties_count--;
if (async_data->pending_properties_count)
return;
}
simple = g_simple_async_result_new (G_OBJECT (async_data->source), async_data->callback, async_data->user_data, e_client_utils_open_new);
g_simple_async_result_set_op_res_gpointer (simple, g_object_ref (async_data->client), g_object_unref);
g_object_set_data (G_OBJECT (simple), "run-main-depth", GINT_TO_POINTER (g_main_depth ()));
g_idle_add (complete_async_op_in_idle_cb, simple);
free_client_utils_async_op_data (async_data);
}
static void
client_utils_capabilities_retrieved_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
EClient *client = E_CLIENT (source_object);
EClientUtilsAsyncOpData *async_data = user_data;
gchar *capabilities = NULL;
gboolean caps_res;
g_return_if_fail (async_data != NULL);
g_return_if_fail (async_data->client != NULL);
g_return_if_fail (async_data->client == client);
caps_res = e_client_retrieve_capabilities_finish (client, result, &capabilities, NULL);
g_free (capabilities);
if (caps_res) {
async_data->pending_properties_count = 1;
/* precache backend properties */
if (E_IS_CAL_CLIENT (client)) {
async_data->pending_properties_count += 3;
e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
} else if (E_IS_BOOK_CLIENT (client)) {
async_data->pending_properties_count += 2;
e_client_get_backend_property (async_data->client, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
e_client_get_backend_property (async_data->client, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
} else {
g_warn_if_reached ();
client_utils_get_backend_property_cb (source_object, NULL, async_data);
return;
}
e_client_get_backend_property (async_data->client, CLIENT_BACKEND_PROPERTY_CACHE_DIR, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
} else {
client_utils_get_backend_property_cb (source_object, NULL, async_data);
}
}
static void
client_utils_open_new_done (EClientUtilsAsyncOpData *async_data)
{
g_return_if_fail (async_data != NULL);
g_return_if_fail (async_data->client != NULL);
/* retrieve capabilities just to have them cached on #EClient for later use */
e_client_retrieve_capabilities (async_data->client, async_data->cancellable, client_utils_capabilities_retrieved_cb, async_data);
}
static gboolean client_utils_retry_open_timeout_cb (gpointer user_data);
static void client_utils_opened_cb (EClient *client, const GError *error, EClientUtilsAsyncOpData *async_data);
static void
finish_or_retry_open (EClientUtilsAsyncOpData *async_data,
const GError *error)
{
g_return_if_fail (async_data != NULL);
if (error && g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY)) {
/* postpone for 1/2 of a second, backend is busy now */
async_data->open_finished = FALSE;
async_data->retry_open_id = g_timeout_add (500, client_utils_retry_open_timeout_cb, async_data);
} else if (error) {
return_async_error (error, async_data->callback, async_data->user_data, async_data->source, e_client_utils_open_new);
free_client_utils_async_op_data (async_data);
} else {
client_utils_open_new_done (async_data);
}
}
static void
client_utils_opened_cb (EClient *client,
const GError *error,
EClientUtilsAsyncOpData *async_data)
{
g_return_if_fail (client != NULL);
g_return_if_fail (async_data != NULL);
g_return_if_fail (client == async_data->client);
g_signal_handlers_disconnect_by_func (client, G_CALLBACK (client_utils_opened_cb), async_data);
if (!async_data->open_finished) {
/* there can happen that the "opened" signal is received
* before the e_client_open () is finished, thus keep detailed
* error for later use, if any */
if (error)
async_data->opened_cb_error = g_error_copy (error);
} else {
finish_or_retry_open (async_data, error);
}
}
static void
client_utils_open_new_async_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
EClientUtilsAsyncOpData *async_data = user_data;
GError *error = NULL;
g_return_if_fail (source_object != NULL);
g_return_if_fail (result != NULL);
g_return_if_fail (async_data != NULL);
g_return_if_fail (async_data->callback != NULL);
g_return_if_fail (async_data->client == E_CLIENT (source_object));
async_data->open_finished = TRUE;
if (!e_client_open_finish (E_CLIENT (source_object), result, &error)
|| g_cancellable_set_error_if_cancelled (async_data->cancellable, &error)) {
finish_or_retry_open (async_data, error);
g_error_free (error);
return;
}
if (async_data->opened_cb_error) {
finish_or_retry_open (async_data, async_data->opened_cb_error);
return;
}
if (e_client_is_opened (async_data->client)) {
client_utils_open_new_done (async_data);
return;
}
/* wait for 'opened' signal, which is received in client_utils_opened_cb */
}
static gboolean
client_utils_retry_open_timeout_cb (gpointer user_data)
{
EClientUtilsAsyncOpData *async_data = user_data;
g_return_val_if_fail (async_data != NULL, FALSE);
g_signal_handlers_disconnect_matched (async_data->cancellable, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, async_data);
/* reconnect to the signal */
g_signal_connect (async_data->client, "opened", G_CALLBACK (client_utils_opened_cb), async_data);
e_client_open (async_data->client, async_data->only_if_exists, async_data->cancellable, client_utils_open_new_async_cb, async_data);
async_data->retry_open_id = 0;
return FALSE;
}
/**
* e_client_utils_open_new:
* @source: an #ESource to be opened
* @source_type: an #EClientSourceType of the @source
* @only_if_exists: if %TRUE, fail if this client doesn't already exist, otherwise create it first
* @cancellable: a #GCancellable; can be %NULL
* @callback: callback to call when a result is ready
* @user_data: user data for the @callback
*
* Begins asynchronous opening of a new #EClient corresponding
* to the @source of type @source_type. The resulting #EClient
* is fully opened and authenticated client, ready to be used.
* The opened client has also fetched capabilities.
* This call is finished by e_client_utils_open_new_finish()
* from the @callback.
*
* Since: 3.2
**/
void
e_client_utils_open_new (ESource *source,
EClientSourceType source_type,
gboolean only_if_exists,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
EClient *client;
GError *error = NULL;
EClientUtilsAsyncOpData *async_data;
g_return_if_fail (callback != NULL);
return_async_error_if_fail (source != NULL, callback, user_data, source, e_client_utils_open_new);
return_async_error_if_fail (E_IS_SOURCE (source), callback, user_data, source, e_client_utils_open_new);
client = e_client_utils_new (source, source_type, &error);
if (!client) {
return_async_error (error, callback, user_data, source, e_client_utils_open_new);
g_error_free (error);
return;
}
async_data = g_new0 (EClientUtilsAsyncOpData, 1);
async_data->callback = callback;
async_data->user_data = user_data;
async_data->source = g_object_ref (source);
async_data->client = client;
async_data->open_finished = FALSE;
async_data->only_if_exists = only_if_exists;
async_data->retry_open_id = 0;
if (cancellable)
async_data->cancellable = g_object_ref (cancellable);
else
async_data->cancellable = g_cancellable_new ();
/* wait till backend notifies about its opened state */
g_signal_connect (client, "opened", G_CALLBACK (client_utils_opened_cb), async_data);
e_client_open (async_data->client, async_data->only_if_exists, async_data->cancellable, client_utils_open_new_async_cb, async_data);
}
/**
* e_client_utils_open_new_finish:
* @source: an #ESource on which the e_client_utils_open_new() was invoked
* @result: a #GAsyncResult
* @client: (out): Return value for an #EClient
* @error: (out): a #GError to set an error, if any
*
* Finishes previous call of e_client_utils_open_new() and
* sets @client to a fully opened and authenticated #EClient.
* This @client, if not NULL, should be freed with g_object_unref().
*
* Returns: %TRUE if successful, %FALSE otherwise.
*
* Since: 3.2
**/
gboolean
e_client_utils_open_new_finish (ESource *source,
GAsyncResult *result,
EClient **client,
GError **error)
{
GSimpleAsyncResult *simple;
g_return_val_if_fail (source != NULL, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
g_return_val_if_fail (client != NULL, FALSE);
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (source), e_client_utils_open_new), FALSE);
*client = NULL;
simple = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (simple, error))
return FALSE;
*client = g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
return *client != NULL;
}