/*
 * e-book-client.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/>
 *
 *
 * Copyright (C) 2011 Red Hat, Inc. (www.redhat.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib/gi18n-lib.h>
#include <gio/gio.h>

/* Private D-Bus classes. */
#include <e-dbus-address-book.h>
#include <e-dbus-address-book-factory.h>
#include <e-dbus-direct-book.h>

#include <libedataserver/libedataserver.h>
#include <libedataserver/e-client-private.h>

#include <libebackend/libebackend.h>
#include <libedata-book/libedata-book.h>

#include "e-book-client.h"

#define E_BOOK_CLIENT_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_BOOK_CLIENT, EBookClientPrivate))

/* Set this to a sufficiently large value
 * to cover most long-running operations. */
#define DBUS_PROXY_TIMEOUT_MS (3 * 60 * 1000)  /* 3 minutes */

typedef struct _AsyncContext AsyncContext;
typedef struct _SignalClosure SignalClosure;
typedef struct _ConnectClosure ConnectClosure;
typedef struct _RunInThreadClosure RunInThreadClosure;

struct _EBookClientPrivate {
	EDBusAddressBook *dbus_proxy;
	EDataBook *direct_book;
	guint name_watcher_id;

	gulong dbus_proxy_error_handler_id;
	gulong dbus_proxy_notify_handler_id;
};

struct _AsyncContext {
	EContact *contact;
	EBookClientView *client_view;
	GSList *object_list;
	GSList *string_list;
	gchar *sexp;
	gchar *uid;
};

struct _SignalClosure {
	GWeakRef client;
	gchar *property_name;
	gchar *error_message;
};

struct _ConnectClosure {
	ESource *source;
	GCancellable *cancellable;
};

struct _RunInThreadClosure {
	GSimpleAsyncThreadFunc func;
	GSimpleAsyncResult *simple;
	GCancellable *cancellable;
};

/* Forward Declarations */
static void	e_book_client_initable_init
					(GInitableIface *interface);
static void	e_book_client_async_initable_init
					(GAsyncInitableIface *interface);

G_DEFINE_TYPE_WITH_CODE (
	EBookClient,
	e_book_client,
	E_TYPE_CLIENT,
	G_IMPLEMENT_INTERFACE (
		G_TYPE_INITABLE,
		e_book_client_initable_init)
	G_IMPLEMENT_INTERFACE (
		G_TYPE_ASYNC_INITABLE,
		e_book_client_async_initable_init))

static void
async_context_free (AsyncContext *async_context)
{
	if (async_context->contact != NULL)
		g_object_unref (async_context->contact);

	if (async_context->client_view != NULL)
		g_object_unref (async_context->client_view);

	g_slist_free_full (
		async_context->object_list,
		(GDestroyNotify) g_object_unref);

	g_slist_free_full (
		async_context->string_list,
		(GDestroyNotify) g_free);

	g_free (async_context->sexp);
	g_free (async_context->uid);

	g_slice_free (AsyncContext, async_context);
}

static void
signal_closure_free (SignalClosure *signal_closure)
{
	g_weak_ref_set (&signal_closure->client, NULL);

	g_free (signal_closure->property_name);
	g_free (signal_closure->error_message);

	g_slice_free (SignalClosure, signal_closure);
}

static void
connect_closure_free (ConnectClosure *connect_closure)
{
	if (connect_closure->source != NULL)
		g_object_unref (connect_closure->source);

	if (connect_closure->cancellable != NULL)
		g_object_unref (connect_closure->cancellable);

	g_slice_free (ConnectClosure, connect_closure);
}

static void
run_in_thread_closure_free (RunInThreadClosure *run_in_thread_closure)
{
	if (run_in_thread_closure->simple != NULL)
		g_object_unref (run_in_thread_closure->simple);

	if (run_in_thread_closure->cancellable != NULL)
		g_object_unref (run_in_thread_closure->cancellable);

	g_slice_free (RunInThreadClosure, run_in_thread_closure);
}

static void
weak_ref_free (GWeakRef *weak_ref)
{
	g_weak_ref_set (weak_ref, NULL);
	g_slice_free (GWeakRef, weak_ref);
}

/*
 * Well-known book backend properties:
 * @BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS: Retrieves comma-separated list
 *   of required fields by the backend. Use e_client_util_parse_comma_strings()
 *   to parse returned string value into a #GSList. These fields are required
 *   to be filled in for all contacts.
 * @BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS: Retrieves comma-separated list
 *   of supported fields by the backend. Use e_client_util_parse_comma_strings()
 *   to parse returned string value into a #GSList. These fields can be
 *   stored for contacts.
 *
 * See also: @CLIENT_BACKEND_PROPERTY_OPENED, @CLIENT_BACKEND_PROPERTY_OPENING,
 *   @CLIENT_BACKEND_PROPERTY_ONLINE, @CLIENT_BACKEND_PROPERTY_READONLY
 *   @CLIENT_BACKEND_PROPERTY_CACHE_DIR, @CLIENT_BACKEND_PROPERTY_CAPABILITIES
 */

static gpointer
book_client_dbus_thread (gpointer user_data)
{
	GMainContext *main_context = user_data;
	GMainLoop *main_loop;

	g_main_context_push_thread_default (main_context);

	main_loop = g_main_loop_new (main_context, FALSE);
	g_main_loop_run (main_loop);
	g_main_loop_unref (main_loop);

	g_main_context_pop_thread_default (main_context);

	g_main_context_unref (main_context);

	return NULL;
}

static gpointer
book_client_dbus_thread_init (gpointer unused)
{
	GMainContext *main_context;

	main_context = g_main_context_new ();

	/* This thread terminates when the process itself terminates, so
	 * no need to worry about unreferencing the returned GThread. */
	g_thread_new (
		"book-client-dbus-thread",
		book_client_dbus_thread,
		g_main_context_ref (main_context));

	return main_context;
}

static GMainContext *
book_client_ref_dbus_main_context (void)
{
	static GOnce book_client_dbus_thread_once = G_ONCE_INIT;

	g_once (
		&book_client_dbus_thread_once,
		book_client_dbus_thread_init, NULL);

	return g_main_context_ref (book_client_dbus_thread_once.retval);
}

static gboolean
book_client_run_in_dbus_thread_idle_cb (gpointer user_data)
{
	RunInThreadClosure *closure = user_data;
	GObject *source_object;
	GAsyncResult *result;

	result = G_ASYNC_RESULT (closure->simple);
	source_object = g_async_result_get_source_object (result);

	closure->func (
		closure->simple,
		source_object,
		closure->cancellable);

	if (source_object != NULL)
		g_object_unref (source_object);

	g_simple_async_result_complete_in_idle (closure->simple);

	return FALSE;
}

static void
book_client_run_in_dbus_thread (GSimpleAsyncResult *simple,
                                GSimpleAsyncThreadFunc func,
                                gint io_priority,
                                GCancellable *cancellable)
{
	RunInThreadClosure *closure;
	GMainContext *main_context;
	GSource *idle_source;

	main_context = book_client_ref_dbus_main_context ();

	closure = g_slice_new0 (RunInThreadClosure);
	closure->func = func;
	closure->simple = g_object_ref (simple);

	if (G_IS_CANCELLABLE (cancellable))
		closure->cancellable = g_object_ref (cancellable);

	idle_source = g_idle_source_new ();
	g_source_set_priority (idle_source, io_priority);
	g_source_set_callback (
		idle_source, book_client_run_in_dbus_thread_idle_cb,
		closure, (GDestroyNotify) run_in_thread_closure_free);
	g_source_attach (idle_source, main_context);
	g_source_unref (idle_source);

	g_main_context_unref (main_context);
}

static gboolean
book_client_emit_backend_died_idle_cb (gpointer user_data)
{
	SignalClosure *signal_closure = user_data;
	EClient *client;

	client = g_weak_ref_get (&signal_closure->client);

	if (client != NULL) {
		g_signal_emit_by_name (client, "backend-died");
		g_object_unref (client);
	}

	return FALSE;
}

static gboolean
book_client_emit_backend_error_idle_cb (gpointer user_data)
{
	SignalClosure *signal_closure = user_data;
	EClient *client;

	client = g_weak_ref_get (&signal_closure->client);

	if (client != NULL) {
		g_signal_emit_by_name (
			client, "backend-error",
			signal_closure->error_message);
		g_object_unref (client);
	}

	return FALSE;
}

static gboolean
book_client_emit_backend_property_changed_idle_cb (gpointer user_data)
{
	SignalClosure *signal_closure = user_data;
	EClient *client;

	client = g_weak_ref_get (&signal_closure->client);

	if (client != NULL) {
		gchar *prop_value = NULL;

		/* XXX Despite appearances, this function does not block. */
		e_client_get_backend_property_sync (
			client,
			signal_closure->property_name,
			&prop_value, NULL, NULL);

		if (prop_value != NULL) {
			g_signal_emit_by_name (
				client,
				"backend-property-changed",
				signal_closure->property_name,
				prop_value);
			g_free (prop_value);
		}

		g_object_unref (client);
	}

	return FALSE;
}

static void
book_client_dbus_proxy_error_cb (EDBusAddressBook *dbus_proxy,
                                 const gchar *error_message,
                                 EClient *client)
{
	GSource *idle_source;
	GMainContext *main_context;
	SignalClosure *signal_closure;

	signal_closure = g_slice_new0 (SignalClosure);
	g_weak_ref_set (&signal_closure->client, client);
	signal_closure->error_message = g_strdup (error_message);

	main_context = e_client_ref_main_context (client);

	idle_source = g_idle_source_new ();
	g_source_set_callback (
		idle_source,
		book_client_emit_backend_error_idle_cb,
		signal_closure,
		(GDestroyNotify) signal_closure_free);
	g_source_attach (idle_source, main_context);
	g_source_unref (idle_source);

	g_main_context_unref (main_context);
}

static void
book_client_dbus_proxy_notify_cb (EDBusAddressBook *dbus_proxy,
                                  GParamSpec *pspec,
                                  EClient *client)
{
	const gchar *backend_prop_name = NULL;

	if (g_str_equal (pspec->name, "cache-dir")) {
		backend_prop_name = CLIENT_BACKEND_PROPERTY_CACHE_DIR;
	}

	if (g_str_equal (pspec->name, "capabilities")) {
		gchar **strv;
		gchar *csv = NULL;

		backend_prop_name = CLIENT_BACKEND_PROPERTY_CAPABILITIES;

		strv = e_dbus_address_book_dup_capabilities (dbus_proxy);
		if (strv != NULL) {
			csv = g_strjoinv (",", strv);
			g_strfreev (strv);
		}
		e_client_set_capabilities (client, csv);
		g_free (csv);
	}

	if (g_str_equal (pspec->name, "online")) {
		gboolean online;

		backend_prop_name = CLIENT_BACKEND_PROPERTY_ONLINE;

		online = e_dbus_address_book_get_online (dbus_proxy);
		e_client_set_online (client, online);
	}

	if (g_str_equal (pspec->name, "required-fields")) {
		backend_prop_name = BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS;
	}

	if (g_str_equal (pspec->name, "revision")) {
		backend_prop_name = CLIENT_BACKEND_PROPERTY_REVISION;
	}

	if (g_str_equal (pspec->name, "supported-fields")) {
		backend_prop_name = BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS;
	}

	if (g_str_equal (pspec->name, "writable")) {
		gboolean writable;

		backend_prop_name = CLIENT_BACKEND_PROPERTY_READONLY;

		writable = e_dbus_address_book_get_writable (dbus_proxy);
		e_client_set_readonly (client, !writable);
	}

	if (backend_prop_name != NULL) {
		GSource *idle_source;
		GMainContext *main_context;
		SignalClosure *signal_closure;

		signal_closure = g_slice_new0 (SignalClosure);
		g_weak_ref_set (&signal_closure->client, client);
		signal_closure->property_name = g_strdup (backend_prop_name);

		main_context = e_client_ref_main_context (client);

		idle_source = g_idle_source_new ();
		g_source_set_callback (
			idle_source,
			book_client_emit_backend_property_changed_idle_cb,
			signal_closure,
			(GDestroyNotify) signal_closure_free);
		g_source_attach (idle_source, main_context);
		g_source_unref (idle_source);

		g_main_context_unref (main_context);
	}
}

static void
book_client_name_vanished_cb (GDBusConnection *connection,
                              const gchar *name,
                              GWeakRef *client_weak_ref)
{
	EClient *client;

	client = g_weak_ref_get (client_weak_ref);

	if (client != NULL) {
		GSource *idle_source;
		GMainContext *main_context;
		SignalClosure *signal_closure;

		signal_closure = g_slice_new0 (SignalClosure);
		g_weak_ref_set (&signal_closure->client, client);

		main_context = e_client_ref_main_context (client);

		idle_source = g_idle_source_new ();
		g_source_set_callback (
			idle_source,
			book_client_emit_backend_died_idle_cb,
			signal_closure,
			(GDestroyNotify) signal_closure_free);
		g_source_attach (idle_source, main_context);
		g_source_unref (idle_source);

		g_main_context_unref (main_context);

		g_object_unref (client);
	}
}

static void
book_client_dispose (GObject *object)
{
	EBookClientPrivate *priv;

	priv = E_BOOK_CLIENT_GET_PRIVATE (object);

	if (priv->dbus_proxy_error_handler_id > 0) {
		g_signal_handler_disconnect (
			priv->dbus_proxy,
			priv->dbus_proxy_error_handler_id);
		priv->dbus_proxy_error_handler_id = 0;
	}

	if (priv->dbus_proxy_notify_handler_id > 0) {
		g_signal_handler_disconnect (
			priv->dbus_proxy,
			priv->dbus_proxy_notify_handler_id);
		priv->dbus_proxy_notify_handler_id = 0;
	}

	if (priv->dbus_proxy != NULL) {
		/* Call close() asynchronously so we don't block dispose().
		 * Also omit a callback function, so the GDBusMessage uses
		 * G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED. */
		e_dbus_address_book_call_close (
			priv->dbus_proxy, NULL, NULL, NULL);
		g_object_unref (priv->dbus_proxy);
		priv->dbus_proxy = NULL;
	}

	if (priv->direct_book != NULL) {
		e_data_book_close_sync (priv->direct_book, NULL, NULL);
		g_object_unref (priv->direct_book);
		priv->direct_book = NULL;
	}

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (e_book_client_parent_class)->dispose (object);
}

static void
book_client_finalize (GObject *object)
{
	EBookClientPrivate *priv;

	priv = E_BOOK_CLIENT_GET_PRIVATE (object);

	if (priv->name_watcher_id > 0)
		g_bus_unwatch_name (priv->name_watcher_id);

	/* Chain up to parent's finalize() method. */
	G_OBJECT_CLASS (e_book_client_parent_class)->finalize (object);
}

static GDBusProxy *
book_client_get_dbus_proxy (EClient *client)
{
	EBookClientPrivate *priv;

	priv = E_BOOK_CLIENT_GET_PRIVATE (client);

	return G_DBUS_PROXY (priv->dbus_proxy);
}

static gboolean
book_client_get_backend_property_sync (EClient *client,
                                       const gchar *prop_name,
                                       gchar **prop_value,
                                       GCancellable *cancellable,
                                       GError **error)
{
	EBookClient *book_client;
	EDBusAddressBook *dbus_proxy;
	gchar **strv;

	book_client = E_BOOK_CLIENT (client);
	dbus_proxy = book_client->priv->dbus_proxy;

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_OPENED)) {
		*prop_value = g_strdup ("TRUE");
		return TRUE;
	}

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_OPENING)) {
		*prop_value = g_strdup ("FALSE");
		return TRUE;
	}

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_ONLINE)) {
		if (e_dbus_address_book_get_online (dbus_proxy))
			*prop_value = g_strdup ("TRUE");
		else
			*prop_value = g_strdup ("FALSE");
		return TRUE;
	}

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_READONLY)) {
		if (e_dbus_address_book_get_writable (dbus_proxy))
			*prop_value = g_strdup ("FALSE");
		else
			*prop_value = g_strdup ("TRUE");
		return TRUE;
	}

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CACHE_DIR)) {
		*prop_value = e_dbus_address_book_dup_cache_dir (dbus_proxy);
		return TRUE;
	}

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_REVISION)) {
		*prop_value = e_dbus_address_book_dup_revision (dbus_proxy);
		return TRUE;
	}

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
		strv = e_dbus_address_book_dup_capabilities (dbus_proxy);
		if (strv != NULL)
			*prop_value = g_strjoinv (",", strv);
		else
			*prop_value = g_strdup ("");
		g_strfreev (strv);
		return TRUE;
	}

	if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
		strv = e_dbus_address_book_dup_required_fields (dbus_proxy);
		if (strv != NULL)
			*prop_value = g_strjoinv (",", strv);
		else
			*prop_value = g_strdup ("");
		g_strfreev (strv);
		return TRUE;
	}

	if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
		strv = e_dbus_address_book_dup_supported_fields (dbus_proxy);
		if (strv != NULL)
			*prop_value = g_strjoinv (",", strv);
		else
			*prop_value = g_strdup ("");
		g_strfreev (strv);
		return TRUE;
	}

	g_set_error (
		error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED,
		_("Unknown book property '%s'"), prop_name);

	return TRUE;
}

static gboolean
book_client_set_backend_property_sync (EClient *client,
                                       const gchar *prop_name,
                                       const gchar *prop_value,
                                       GCancellable *cancellable,
                                       GError **error)
{
	g_set_error (
		error, E_CLIENT_ERROR,
		E_CLIENT_ERROR_NOT_SUPPORTED,
		_("Cannot change value of book property '%s'"),
		prop_name);

	return FALSE;
}

static gboolean
book_client_open_sync (EClient *client,
                       gboolean only_if_exists,
                       GCancellable *cancellable,
                       GError **error)
{
	EBookClient *book_client;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);

	book_client = E_BOOK_CLIENT (client);

	e_dbus_address_book_call_open_sync (
		book_client->priv->dbus_proxy, cancellable, &local_error);

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		return FALSE;
	}

	return TRUE;
}

static gboolean
book_client_refresh_sync (EClient *client,
                          GCancellable *cancellable,
                          GError **error)
{
	EBookClient *book_client;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);

	book_client = E_BOOK_CLIENT (client);

	e_dbus_address_book_call_refresh_sync (
		book_client->priv->dbus_proxy, cancellable, &local_error);

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		return FALSE;
	}

	return TRUE;
}

static void
book_client_init_in_dbus_thread (GSimpleAsyncResult *simple,
                                 GObject *source_object,
                                 GCancellable *cancellable)
{
	EBookClientPrivate *priv;
	EDBusAddressBookFactory *factory_proxy;
	GDBusConnection *connection;
	GDBusProxy *proxy;
	GWeakRef *weak_ref;
	EClient *client;
	ESource *source;
	const gchar *uid;
	gchar *object_path = NULL;
	gulong handler_id;
	GError *local_error = NULL;

	priv = E_BOOK_CLIENT_GET_PRIVATE (source_object);

	client = E_CLIENT (source_object);
	source = e_client_get_source (client);
	uid = e_source_get_uid (source);

	connection = g_bus_get_sync (
		G_BUS_TYPE_SESSION, cancellable, &local_error);

	/* Sanity check. */
	g_return_if_fail (
		((connection != NULL) && (local_error == NULL)) ||
		((connection == NULL) && (local_error != NULL)));

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_simple_async_result_take_error (simple, local_error);
		return;
	}

	factory_proxy = e_dbus_address_book_factory_proxy_new_sync (
		connection,
		G_DBUS_PROXY_FLAGS_NONE,
		ADDRESS_BOOK_DBUS_SERVICE_NAME,
		"/org/gnome/evolution/dataserver/AddressBookFactory",
		cancellable, &local_error);

	/* Sanity check. */
	g_return_if_fail (
		((factory_proxy != NULL) && (local_error == NULL)) ||
		((factory_proxy == NULL) && (local_error != NULL)));

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_simple_async_result_take_error (simple, local_error);
		g_object_unref (connection);
		return;
	}

	e_dbus_address_book_factory_call_open_address_book_sync (
		factory_proxy, uid, &object_path, cancellable, &local_error);

	g_object_unref (factory_proxy);

	/* Sanity check. */
	g_return_if_fail (
		((object_path != NULL) && (local_error == NULL)) ||
		((object_path == NULL) && (local_error != NULL)));

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_simple_async_result_take_error (simple, local_error);
		g_object_unref (connection);
		return;
	}

	priv->dbus_proxy = e_dbus_address_book_proxy_new_sync (
		connection,
		G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
		ADDRESS_BOOK_DBUS_SERVICE_NAME,
		object_path, cancellable, &local_error);

	g_free (object_path);

	/* Sanity check. */
	g_return_if_fail (
		((priv->dbus_proxy != NULL) && (local_error == NULL)) ||
		((priv->dbus_proxy == NULL) && (local_error != NULL)));

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_simple_async_result_take_error (simple, local_error);
		g_object_unref (connection);
		return;
	}

	/* Configure our new GDBusProxy. */

	proxy = G_DBUS_PROXY (priv->dbus_proxy);

	g_dbus_proxy_set_default_timeout (proxy, DBUS_PROXY_TIMEOUT_MS);

	/* XXX Wishing for g_weak_ref_new() / g_weak_ref_free() here. */
	weak_ref = g_slice_new0 (GWeakRef);
	g_weak_ref_set (weak_ref, client);

	priv->name_watcher_id = g_bus_watch_name_on_connection (
		connection,
		g_dbus_proxy_get_name (proxy),
		G_BUS_NAME_WATCHER_FLAGS_NONE,
		(GBusNameAppearedCallback) NULL,
		(GBusNameVanishedCallback) book_client_name_vanished_cb,
		weak_ref, (GDestroyNotify) weak_ref_free);

	handler_id = g_signal_connect_object (
		proxy, "error",
		G_CALLBACK (book_client_dbus_proxy_error_cb),
		client, 0);
	priv->dbus_proxy_error_handler_id = handler_id;

	handler_id = g_signal_connect_object (
		proxy, "notify",
		G_CALLBACK (book_client_dbus_proxy_notify_cb),
		client, 0);
	priv->dbus_proxy_notify_handler_id = handler_id;

	/* Initialize our public-facing GObject properties. */
	g_object_notify (G_OBJECT (proxy), "online");
	g_object_notify (G_OBJECT (proxy), "writable");
	g_object_notify (G_OBJECT (proxy), "capabilities");

	g_object_unref (connection);
}

static gboolean
book_client_initable_init (GInitable *initable,
                           GCancellable *cancellable,
                           GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	closure = e_async_closure_new ();

	g_async_initable_init_async (
		G_ASYNC_INITABLE (initable),
		G_PRIORITY_DEFAULT, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = g_async_initable_init_finish (
		G_ASYNC_INITABLE (initable), result, error);

	e_async_closure_free (closure);

	return success;
}

static void
book_client_initable_init_async (GAsyncInitable *initable,
                                 gint io_priority,
                                 GCancellable *cancellable,
                                 GAsyncReadyCallback callback,
                                 gpointer user_data)
{
	GSimpleAsyncResult *simple;

	simple = g_simple_async_result_new (
		G_OBJECT (initable), callback, user_data,
		book_client_initable_init_async);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	book_client_run_in_dbus_thread (
		simple, book_client_init_in_dbus_thread,
		io_priority, cancellable);

	g_object_unref (simple);
}

static gboolean
book_client_initable_init_finish (GAsyncInitable *initable,
                                  GAsyncResult *result,
                                  GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (initable),
		book_client_initable_init_async), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

static void
e_book_client_class_init (EBookClientClass *class)
{
	GObjectClass *object_class;
	EClientClass *client_class;

	g_type_class_add_private (class, sizeof (EBookClientPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->dispose = book_client_dispose;
	object_class->finalize = book_client_finalize;

	client_class = E_CLIENT_CLASS (class);
	client_class->get_dbus_proxy = book_client_get_dbus_proxy;
	client_class->get_backend_property_sync = book_client_get_backend_property_sync;
	client_class->set_backend_property_sync = book_client_set_backend_property_sync;
	client_class->open_sync = book_client_open_sync;
	client_class->refresh_sync = book_client_refresh_sync;
}

static void
e_book_client_initable_init (GInitableIface *interface)
{
	interface->init = book_client_initable_init;
}

static void
e_book_client_async_initable_init (GAsyncInitableIface *interface)
{
	interface->init_async = book_client_initable_init_async;
	interface->init_finish = book_client_initable_init_finish;
}

static void
e_book_client_init (EBookClient *client)
{
	client->priv = E_BOOK_CLIENT_GET_PRIVATE (client);
}

/**
 * e_book_client_connect_sync:
 * @source: an #ESource
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @error: return location for a #GError, or %NULL
 *
 * Creates a new #EBookClient for @source.  If an error occurs, the function
 * will set @error and return %FALSE.
 *
 * Unlike with e_book_client_new(), there is no need to call
 * e_client_open_sync() after obtaining the #EBookClient.
 *
 * For error handling convenience, any error message returned by this
 * function will have a descriptive prefix that includes the display
 * name of @source.
 *
 * Returns: (transfer full) (type EBookClient): a new #EBookClient, or %NULL
 *
 * Since: 3.8
 **/
EClient *
e_book_client_connect_sync (ESource *source,
                            GCancellable *cancellable,
                            GError **error)
{
	EBookClient *client;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_SOURCE (source), NULL);

	client = g_object_new (
		E_TYPE_BOOK_CLIENT,
		"source", source, NULL);

	g_initable_init (G_INITABLE (client), cancellable, &local_error);

	if (local_error == NULL)
		e_dbus_address_book_call_open_sync (
			client->priv->dbus_proxy, cancellable, &local_error);

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		g_prefix_error (
			error, _("Unable to connect to '%s': "),
			e_source_get_display_name (source));
		g_object_unref (client);
		return NULL;
	}

	return E_CLIENT (client);
}

/* Helper for e_book_client_connect() */
static void
book_client_connect_open_cb (GObject *source_object,
                             GAsyncResult *result,
                             gpointer user_data)
{
	GSimpleAsyncResult *simple;
	GError *local_error = NULL;

	simple = G_SIMPLE_ASYNC_RESULT (user_data);

	e_dbus_address_book_call_open_finish (
		E_DBUS_ADDRESS_BOOK (source_object), result, &local_error);

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_simple_async_result_take_error (simple, local_error);
	}

	g_simple_async_result_complete (simple);

	g_object_unref (simple);
}

/* Helper for e_book_client_connect() */
static void
book_client_connect_init_cb (GObject *source_object,
                             GAsyncResult *result,
                             gpointer user_data)
{
	GSimpleAsyncResult *simple;
	EBookClientPrivate *priv;
	ConnectClosure *closure;
	GError *local_error = NULL;

	simple = G_SIMPLE_ASYNC_RESULT (user_data);

	g_async_initable_init_finish (
		G_ASYNC_INITABLE (source_object), result, &local_error);

	if (local_error != NULL) {
		g_simple_async_result_take_error (simple, local_error);
		g_simple_async_result_complete (simple);
		goto exit;
	}

	/* Note, we're repurposing some function parameters. */

	result = G_ASYNC_RESULT (simple);
	source_object = g_async_result_get_source_object (result);
	closure = g_simple_async_result_get_op_res_gpointer (simple);

	priv = E_BOOK_CLIENT_GET_PRIVATE (source_object);

	e_dbus_address_book_call_open (
		priv->dbus_proxy,
		closure->cancellable,
		book_client_connect_open_cb,
		g_object_ref (simple));

	g_object_unref (source_object);

exit:
	g_object_unref (simple);
}

/**
 * e_book_client_connect:
 * @source: an #ESource
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @callback: (scope async): a #GAsyncReadyCallback to call when the request
 *            is satisfied
 * @user_data: (closure): data to pass to the callback function
 *
 * Asynchronously creates a new #EBookClient for @source.
 *
 * Unlike with e_book_client_new(), there is no need to call e_client_open()
 * after obtaining the #EBookClient.
 *
 * When the operation is finished, @callback will be called.  You can then
 * call e_book_client_connect_finish() to get the result of the operation.
 *
 * Since: 3.8
 **/
void
e_book_client_connect (ESource *source,
                       GCancellable *cancellable,
                       GAsyncReadyCallback callback,
                       gpointer user_data)
{
	GSimpleAsyncResult *simple;
	ConnectClosure *closure;
	EBookClient *client;

	g_return_if_fail (E_IS_SOURCE (source));

	/* Two things with this: 1) instantiate the client object
	 * immediately to make sure the thread-default GMainContext
	 * gets plucked, and 2) do not call the D-Bus open() method
	 * from our designated D-Bus thread -- it may take a long
	 * time and block other clients from receiving signals. */

	closure = g_slice_new0 (ConnectClosure);
	closure->source = g_object_ref (source);

	if (G_IS_CANCELLABLE (cancellable))
		closure->cancellable = g_object_ref (cancellable);

	client = g_object_new (
		E_TYPE_BOOK_CLIENT,
		"source", source, NULL);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback,
		user_data, e_book_client_connect);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, closure, (GDestroyNotify) connect_closure_free);

	g_async_initable_init_async (
		G_ASYNC_INITABLE (client),
		G_PRIORITY_DEFAULT, cancellable,
		book_client_connect_init_cb,
		g_object_ref (simple));

	g_object_unref (simple);
	g_object_unref (client);
}

/**
 * e_book_client_connect_finish:
 * @result: a #GAsyncResult
 * @error: return location for a #GError, or %NULL
 *
 * Finishes the operation started with e_book_client_connect().  If an
 * error occurs in connecting to the D-Bus service, the function sets
 * @error and returns %NULL.
 *
 * For error handling convenience, any error message returned by this
 * function will have a descriptive prefix that includes the display
 * name of the #ESource passed to e_book_client_connect().
 *
 * Returns: (transfer full) (type EBookClient): a new #EBookClient, or %NULL
 *
 * Since: 3.8
 **/
EClient *
e_book_client_connect_finish (GAsyncResult *result,
                              GError **error)
{
	GSimpleAsyncResult *simple;
	ConnectClosure *closure;
	gpointer source_tag;

	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	closure = g_simple_async_result_get_op_res_gpointer (simple);

	source_tag = g_simple_async_result_get_source_tag (simple);
	g_return_val_if_fail (source_tag == e_book_client_connect, NULL);

	if (g_simple_async_result_propagate_error (simple, error)) {
		g_prefix_error (
			error, _("Unable to connect to '%s': "),
			e_source_get_display_name (closure->source));
		return NULL;
	}

	return E_CLIENT (g_async_result_get_source_object (result));
}

/**
 * e_book_client_new:
 * @source: An #ESource pointer
 * @error: A #GError pointer
 *
 * Creates a new #EBookClient corresponding to the given source.  There are
 * only two operations that are valid on this book at this point:
 * e_client_open(), and e_client_remove().
 *
 * Returns: a new but unopened #EBookClient.
 *
 * Since: 3.2
 *
 * Deprecated: 3.8: It covertly makes synchronous D-Bus calls, with no
 *                  way to cancel.  Use e_book_client_connect() instead,
 *                  which combines e_book_client_new() and e_client_open()
 *                  into one step.
 **/
EBookClient *
e_book_client_new (ESource *source,
                   GError **error)
{
	g_return_val_if_fail (E_IS_SOURCE (source), NULL);

	return g_initable_new (
		E_TYPE_BOOK_CLIENT, NULL, error,
		"source", source, NULL);
}

/**
 * e_book_client_connect_direct_sync:
 * @registry: an #ESourceRegistry
 * @source: an #ESource
 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
 * @error: return location for a #GError, or %NULL
 *
 * Like e_book_client_connect_sync(), except creates the book client for
 * direct read access to the underlying addressbook.
 *
 * Returns: (transfer full) (type EBookClient): a new but unopened #EBookClient.
 *
 * Since: 3.8
 **/
EClient *
e_book_client_connect_direct_sync (ESourceRegistry *registry,
                                   ESource *source,
                                   GCancellable *cancellable,
                                   GError **error)
{
	EClient *client;
	EBookClientPrivate *priv;
	EDBusDirectBook *direct_config;
	const gchar *backend_name, *backend_path, *config;
	GError *local_error = NULL;

	client = e_book_client_connect_sync (source, cancellable, error);

	if (!client)
		return NULL;

	priv = E_BOOK_CLIENT_GET_PRIVATE (client);

	direct_config = e_dbus_direct_book_proxy_new_sync (
		g_dbus_proxy_get_connection (G_DBUS_PROXY (priv->dbus_proxy)),
		G_DBUS_PROXY_FLAGS_NONE,
		ADDRESS_BOOK_DBUS_SERVICE_NAME,
		g_dbus_proxy_get_object_path (G_DBUS_PROXY (priv->dbus_proxy)),
		NULL, NULL);

	backend_path = e_dbus_direct_book_get_backend_path (direct_config);
	backend_name = e_dbus_direct_book_get_backend_name (direct_config);
	config = e_dbus_direct_book_get_backend_config (direct_config);

	if (backend_path && backend_path[0] &&
	    backend_name && backend_name[0]) {
		priv->direct_book = e_data_book_new_direct (
			registry, source,
			backend_path,
			backend_name,
			config, &local_error);
		if (!priv->direct_book) {
			g_warning (
				"Failed to open addressbook in direct read "
				"access mode, falling back to normal read "
				"access mode. Reason: %s",
				local_error->message);
			g_error_free (local_error);
		}

	} else {
		g_warning (
			"Direct read access mode not supported by the given "
			"backend, falling back to normal read access mode");
	}

	g_object_unref (direct_config);

	/* We have to perform the opeining of the direct book separately
	 * from the EClient->open() implementation, because the direct
	 * book does not exist yet
	 */
	if (priv->direct_book &&
	    !e_data_book_open_sync (priv->direct_book, cancellable, error)) {
		g_warning (
			"Unable to open direct read access book, "
			"falling back to normal read access mode");
		g_clear_object (&priv->direct_book);
	}

	return client;

}

#define SELF_UID_PATH_ID "org.gnome.evolution-data-server.addressbook"
#define SELF_UID_KEY "self-contact-uid"

static EContact *
make_me_card (void)
{
	GString *vcard;
	const gchar *s;
	EContact *contact;

	vcard = g_string_new ("BEGIN:VCARD\nVERSION:3.0\n");

	s = g_get_user_name ();
	if (s)
		g_string_append_printf (vcard, "NICKNAME:%s\n", s);

	s = g_get_real_name ();
	if (s && strcmp (s, "Unknown") != 0) {
		ENameWestern *western;

		g_string_append_printf (vcard, "FN:%s\n", s);

		western = e_name_western_parse (s);
		g_string_append_printf (
			vcard, "N:%s;%s;%s;%s;%s\n",
			western->last ? western->last : "",
			western->first ? western->first : "",
			western->middle ? western->middle : "",
			western->prefix ? western->prefix : "",
			western->suffix ? western->suffix : "");
		e_name_western_free (western);
	}
	g_string_append (vcard, "END:VCARD");

	contact = e_contact_new_from_vcard (vcard->str);

	g_string_free (vcard, TRUE);

	return contact;
}

/**
 * e_book_client_get_self:
 * @registry: an #ESourceRegistry
 * @out_contact: (out): an #EContact pointer to set
 * @out_client: (out): an #EBookClient pointer to set
 * @error: a #GError to set on failure
 *
 * Get the #EContact referring to the user of the address book
 * and set it in @out_contact and @out_client.
 *
 * Returns: %TRUE if successful, otherwise %FALSE.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_self (ESourceRegistry *registry,
                        EContact **out_contact,
                        EBookClient **out_client,
                        GError **error)
{
	EBookClient *book_client;
	ESource *source;
	EContact *contact = NULL;
	GSettings *settings;
	gchar *uid;
	gboolean success;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
	g_return_val_if_fail (out_contact != NULL, FALSE);
	g_return_val_if_fail (out_client != NULL, FALSE);

	source = e_source_registry_ref_builtin_address_book (registry);
	book_client = e_book_client_new (source, error);
	g_object_unref (source);

	if (book_client == NULL)
		return FALSE;

	success = e_client_open_sync (
		E_CLIENT (book_client), FALSE, NULL, error);
	if (!success) {
		g_object_unref (book_client);
		return FALSE;
	}

	*out_client = book_client;

	settings = g_settings_new (SELF_UID_PATH_ID);
	uid = g_settings_get_string (settings, SELF_UID_KEY);
	g_object_unref (settings);

	if (uid) {
		/* Don't care about errors because
		 * we'll create a new card on failure. */
		e_book_client_get_contact_sync (
			book_client, uid, &contact, NULL, NULL);
		g_free (uid);

		if (contact != NULL) {
			*out_client = book_client;
			*out_contact = contact;
			return TRUE;
		}
	}

	uid = NULL;
	contact = make_me_card ();
	success = e_book_client_add_contact_sync (
		book_client, contact, &uid, NULL, error);
	if (!success) {
		g_object_unref (book_client);
		g_object_unref (contact);
		return FALSE;
	}

	if (uid != NULL) {
		e_contact_set (contact, E_CONTACT_UID, uid);
		g_free (uid);
	}

	e_book_client_set_self (book_client, contact, NULL);

	*out_client = book_client;
	*out_contact = contact;

	return TRUE;
}

/**
 * e_book_client_set_self:
 * @client: an #EBookClient
 * @contact: an #EContact
 * @error: a #GError to set on failure
 *
 * Specify that @contact residing in @client is the #EContact that
 * refers to the user of the address book.
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_set_self (EBookClient *client,
                        EContact *contact,
                        GError **error)
{
	GSettings *settings;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (contact != NULL, FALSE);
	g_return_val_if_fail (
		e_contact_get_const (contact, E_CONTACT_UID) != NULL, FALSE);

	settings = g_settings_new (SELF_UID_PATH_ID);
	g_settings_set_string (
		settings, SELF_UID_KEY,
		e_contact_get_const (contact, E_CONTACT_UID));
	g_object_unref (settings);

	return TRUE;
}

/**
 * e_book_client_is_self:
 * @contact: an #EContact
 *
 * Check if @contact is the user of the address book.
 *
 * Returns: %TRUE if @contact is the user, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_is_self (EContact *contact)
{
	static GSettings *settings;
	static GMutex mutex;
	const gchar *contact_uid;
	gchar *uid;
	gboolean is_self;

	g_return_val_if_fail (contact && E_IS_CONTACT (contact), FALSE);

	/*
	 * It would be nice to attach this instance to the EBookClient
	 * instance so that it can be free again later, but
	 * unfortunately the API doesn't allow that.
	 */
	g_mutex_lock (&mutex);
	if (!settings)
		settings = g_settings_new (SELF_UID_PATH_ID);
	uid = g_settings_get_string (settings, SELF_UID_KEY);
	g_mutex_unlock (&mutex);

	contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
	is_self = (uid != NULL) && (g_strcmp0 (uid, contact_uid) == 0);

	g_free (uid);

	return is_self;
}

/* Helper for e_book_client_add_contact() */
static void
book_client_add_contact_thread (GSimpleAsyncResult *simple,
                                GObject *source_object,
                                GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_add_contact_sync (
		E_BOOK_CLIENT (source_object),
		async_context->contact,
		&async_context->uid,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_add_contact:
 * @client: an #EBookClient
 * @contact: an #EContact
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Adds @contact to @client.
 * The call is finished by e_book_client_add_contact_finish()
 * from the @callback.
 *
 * Since: 3.2
 **/
void
e_book_client_add_contact (EBookClient *client,
                           EContact *contact,
                           GCancellable *cancellable,
                           GAsyncReadyCallback callback,
                           gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (E_IS_CONTACT (contact));

	async_context = g_slice_new0 (AsyncContext);
	async_context->contact = g_object_ref (contact);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_add_contact);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_add_contact_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_add_contact_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @out_added_uid: (out): UID of a newly added contact; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_add_contact() and
 * sets @out_added_uid to a UID of a newly added contact.
 * This string should be freed with g_free().
 *
 * Note: This is not modifying original #EContact.
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_add_contact_finish (EBookClient *client,
                                  GAsyncResult *result,
                                  gchar **out_added_uid,
                                  GError **error)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_add_contact), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	g_return_val_if_fail (async_context->uid != NULL, FALSE);

	if (out_added_uid != NULL) {
		*out_added_uid = async_context->uid;
		async_context->uid = NULL;
	}

	return TRUE;
}

/**
 * e_book_client_add_contact_sync:
 * @client: an #EBookClient
 * @contact: an #EContact
 * @out_added_uid: (out): UID of a newly added contact; can be %NULL
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Adds @contact to @client and
 * sets @out_added_uid to a UID of a newly added contact.
 * This string should be freed with g_free().
 *
 * Note: This is not modifying original @contact, thus if it's needed,
 * then use e_contact_set (contact, E_CONTACT_UID, new_uid).
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_add_contact_sync (EBookClient *client,
                                EContact *contact,
                                gchar **out_added_uid,
                                GCancellable *cancellable,
                                GError **error)
{
	GSList link = { contact, NULL };
	GSList *uids = NULL;
	gboolean success;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);

	success = e_book_client_add_contacts_sync (
		client, &link, &uids, cancellable, error);

	/* Sanity check. */
	g_return_val_if_fail (
		(success && (uids != NULL)) ||
		(!success && (uids == NULL)), FALSE);

	if (uids != NULL) {
		if (out_added_uid != NULL)
			*out_added_uid = g_strdup (uids->data);

		g_slist_free_full (uids, (GDestroyNotify) g_free);
	}

	return success;
}

/* Helper for e_book_client_add_contacts() */
static void
book_client_add_contacts_thread (GSimpleAsyncResult *simple,
                                 GObject *source_object,
                                 GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_add_contacts_sync (
		E_BOOK_CLIENT (source_object),
		async_context->object_list,
		&async_context->string_list,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_add_contacts:
 * @client: an #EBookClient
 * @contacts: (element-type EContact): a #GSList of #EContact objects to add
 * @cancellable: (allow-none): a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Adds @contacts to @client.
 * The call is finished by e_book_client_add_contacts_finish()
 * from the @callback.
 *
 * Since: 3.4
 **/
void
e_book_client_add_contacts (EBookClient *client,
                            GSList *contacts,
                            GCancellable *cancellable,
                            GAsyncReadyCallback callback,
                            gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (contacts != NULL);

	async_context = g_slice_new0 (AsyncContext);
	async_context->object_list = g_slist_copy_deep (
		contacts, (GCopyFunc) g_object_ref, NULL);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_add_contacts);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_add_contacts_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_add_contacts_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @out_added_uids: (out) (element-type utf8) (allow-none): UIDs of
 *                  newly added contacts; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_add_contacts() and
 * sets @out_added_uids to the UIDs of newly added contacts if successful.
 * This #GSList should be freed with e_client_util_free_string_slist().
 *
 * If any of the contacts cannot be inserted, all of the insertions will be
 * reverted and this method will return %FALSE.
 *
 * Note: This is not modifying original #EContact objects.
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.4
 **/
gboolean
e_book_client_add_contacts_finish (EBookClient *client,
                                   GAsyncResult *result,
                                   GSList **out_added_uids,
                                   GError **error)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_add_contacts), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (out_added_uids != NULL) {
		*out_added_uids = async_context->string_list;
		async_context->string_list = NULL;
	}

	return TRUE;
}

/**
 * e_book_client_add_contacts_sync:
 * @client: an #EBookClient
 * @contacts: (element-type EContact): a #GSList of #EContact objects to add
 * @out_added_uids: (out) (element-type utf8) (allow-none): UIDs of newly
 *                  added contacts; can be %NULL
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Adds @contacts to @client and
 * sets @out_added_uids to the UIDs of newly added contacts if successful.
 * This #GSList should be freed with e_client_util_free_string_slist().
 *
 * If any of the contacts cannot be inserted, all of the insertions will be
 * reverted and this method will return %FALSE.
 *
 * Note: This is not modifying original @contacts, thus if it's needed,
 * then use e_contact_set (contact, E_CONTACT_UID, new_uid).
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.4
 **/
gboolean
e_book_client_add_contacts_sync (EBookClient *client,
                                 GSList *contacts,
                                 GSList **out_added_uids,
                                 GCancellable *cancellable,
                                 GError **error)
{
	GSList *link;
	gchar **strv;
	gchar **uids = NULL;
	gint ii = 0;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (contacts != NULL, FALSE);

	/* Build a string array, ensuring each element is valid UTF-8. */
	strv = g_new0 (gchar *, g_slist_length (contacts) + 1);
	for (link = contacts; link != NULL; link = g_slist_next (link)) {
		EVCard *vcard;
		gchar *string;

		vcard = E_VCARD (link->data);
		string = e_vcard_to_string (vcard, EVC_FORMAT_VCARD_30);
		strv[ii++] = e_util_utf8_make_valid (string);
		g_free (string);
	}

	e_dbus_address_book_call_create_contacts_sync (
		client->priv->dbus_proxy,
		(const gchar * const *) strv,
		&uids, cancellable, &local_error);

	g_strfreev (strv);

	/* Sanity check. */
	g_return_val_if_fail (
		((uids != NULL) && (local_error == NULL)) ||
		((uids == NULL) && (local_error != NULL)), FALSE);

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		return FALSE;
	}

	/* XXX We should have passed the string array directly
	 *     back to the caller instead of building a linked
	 *     list.  This is unnecessary work. */
	if (out_added_uids != NULL) {
		GSList *tmp = NULL;
		gint ii;

		/* Take ownership of the string array elements. */
		for (ii = 0; uids[ii] != NULL; ii++) {
			tmp = g_slist_prepend (tmp, uids[ii]);
			uids[ii] = NULL;
		}

		*out_added_uids = g_slist_reverse (tmp);
	}

	g_strfreev (uids);

	return TRUE;
}

/* Helper for e_book_client_modify_contact() */
static void
book_client_modify_contact_thread (GSimpleAsyncResult *simple,
                                   GObject *source_object,
                                   GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_modify_contact_sync (
		E_BOOK_CLIENT (source_object),
		async_context->contact,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_modify_contact:
 * @client: an #EBookClient
 * @contact: an #EContact
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Applies the changes made to @contact to the stored version in @client.
 * The call is finished by e_book_client_modify_contact_finish()
 * from the @callback.
 *
 * Since: 3.2
 **/
void
e_book_client_modify_contact (EBookClient *client,
                              EContact *contact,
                              GCancellable *cancellable,
                              GAsyncReadyCallback callback,
                              gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (E_IS_CONTACT (contact));

	async_context = g_slice_new0 (AsyncContext);
	async_context->contact = g_object_ref (contact);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_modify_contact);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_modify_contact_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_modify_contact_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_modify_contact().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_modify_contact_finish (EBookClient *client,
                                     GAsyncResult *result,
                                     GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_modify_contact), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

/**
 * e_book_client_modify_contact_sync:
 * @client: an #EBookClient
 * @contact: an #EContact
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Applies the changes made to @contact to the stored version in @client.
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_modify_contact_sync (EBookClient *client,
                                   EContact *contact,
                                   GCancellable *cancellable,
                                   GError **error)
{
	GSList link = { contact, NULL };

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);

	return e_book_client_modify_contacts_sync (
		client, &link, cancellable, error);
}

/* Helper for e_book_client_modify_contacts() */
static void
book_client_modify_contacts_thread (GSimpleAsyncResult *simple,
                                    GObject *source_object,
                                    GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_modify_contacts_sync (
		E_BOOK_CLIENT (source_object),
		async_context->object_list,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_modify_contacts:
 * @client: an #EBookClient
 * @contacts: (element-type EContact): a #GSList of #EContact objects
 * @cancellable: (allow-none): a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Applies the changes made to @contacts to the stored versions in @client.
 * The call is finished by e_book_client_modify_contacts_finish()
 * from the @callback.
 *
 * Since: 3.4
 **/
void
e_book_client_modify_contacts (EBookClient *client,
                               GSList *contacts,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (contacts != NULL);

	async_context = g_slice_new0 (AsyncContext);
	async_context->object_list = g_slist_copy_deep (
		contacts, (GCopyFunc) g_object_ref, NULL);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_modify_contacts);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_modify_contacts_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_modify_contacts_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_modify_contacts().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.4
 **/
gboolean
e_book_client_modify_contacts_finish (EBookClient *client,
                                      GAsyncResult *result,
                                      GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_modify_contacts), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

/**
 * e_book_client_modify_contacts_sync:
 * @client: an #EBookClient
 * @contacts: (element-type EContact): a #GSList of #EContact objects
 * @cancellable: (allow-none): a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Applies the changes made to @contacts to the stored versions in @client.
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.4
 **/
gboolean
e_book_client_modify_contacts_sync (EBookClient *client,
                                    GSList *contacts,
                                    GCancellable *cancellable,
                                    GError **error)
{
	GSList *link;
	gchar **strv;
	gint ii = 0;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (contacts != NULL, FALSE);

	/* Build a string array, ensuring each element is valid UTF-8. */
	strv = g_new0 (gchar *, g_slist_length (contacts) + 1);
	for (link = contacts; link != NULL; link = g_slist_next (link)) {
		EVCard *vcard;
		gchar *string;

		vcard = E_VCARD (link->data);
		string = e_vcard_to_string (vcard, EVC_FORMAT_VCARD_30);
		strv[ii++] = e_util_utf8_make_valid (string);
		g_free (string);
	}

	e_dbus_address_book_call_modify_contacts_sync (
		client->priv->dbus_proxy,
		(const gchar * const *) strv,
		cancellable, &local_error);

	g_strfreev (strv);

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		return FALSE;
	}

	return TRUE;
}

/* Helper for e_book_client_remove_contact() */
static void
book_client_remove_contact_thread (GSimpleAsyncResult *simple,
                                   GObject *source_object,
                                   GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_remove_contact_sync (
		E_BOOK_CLIENT (source_object),
		async_context->contact,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_remove_contact:
 * @client: an #EBookClient
 * @contact: an #EContact
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Removes @contact from the @client.
 * The call is finished by e_book_client_remove_contact_finish()
 * from the @callback.
 *
 * Since: 3.2
 **/
void
e_book_client_remove_contact (EBookClient *client,
                              /* const */ EContact *contact,
                              GCancellable *cancellable,
                              GAsyncReadyCallback callback,
                              gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (E_IS_CONTACT (contact));

	async_context = g_slice_new0 (AsyncContext);
	async_context->contact = g_object_ref (contact);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_remove_contact);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_remove_contact_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_remove_contact_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_remove_contact().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_remove_contact_finish (EBookClient *client,
                                     GAsyncResult *result,
                                     GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_remove_contact), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

/**
 * e_book_client_remove_contact_sync:
 * @client: an #EBookClient
 * @contact: an #EContact
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Removes @contact from the @client.
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_remove_contact_sync (EBookClient *client,
                                   EContact *contact,
                                   GCancellable *cancellable,
                                   GError **error)
{
	const gchar *uid;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);

	uid = e_contact_get_const (contact, E_CONTACT_UID);
	g_return_val_if_fail (uid != NULL, FALSE);

	return e_book_client_remove_contact_by_uid_sync (
		client, uid, cancellable, error);
}

/* Helper for e_book_client_remove_contact_by_uid() */
static void
book_client_remove_contact_by_uid_thread (GSimpleAsyncResult *simple,
                                          GObject *source_object,
                                          GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_remove_contact_by_uid_sync (
		E_BOOK_CLIENT (source_object),
		async_context->uid,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_remove_contact_by_uid:
 * @client: an #EBookClient
 * @uid: a UID of a contact to remove
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Removes contact with @uid from the @client.
 * The call is finished by e_book_client_remove_contact_by_uid_finish()
 * from the @callback.
 *
 * Since: 3.2
 **/
void
e_book_client_remove_contact_by_uid (EBookClient *client,
                                     const gchar *uid,
                                     GCancellable *cancellable,
                                     GAsyncReadyCallback callback,
                                     gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (uid != NULL);

	async_context = g_slice_new0 (AsyncContext);
	async_context->uid = g_strdup (uid);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_remove_contact_by_uid);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_remove_contact_by_uid_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_remove_contact_by_uid_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_remove_contact_by_uid().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_remove_contact_by_uid_finish (EBookClient *client,
                                            GAsyncResult *result,
                                            GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_remove_contact_by_uid), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

/**
 * e_book_client_remove_contact_by_uid_sync:
 * @client: an #EBookClient
 * @uid: a UID of a contact to remove
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Removes contact with @uid from the @client.
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_remove_contact_by_uid_sync (EBookClient *client,
                                          const gchar *uid,
                                          GCancellable *cancellable,
                                          GError **error)
{
	GSList link = { (gpointer) uid, NULL };

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (uid != NULL, FALSE);

	return e_book_client_remove_contacts_sync (
		client, &link, cancellable, error);
}

/* Helper for e_book_client_remove_contacts() */
static void
book_client_remove_contacts_thread (GSimpleAsyncResult *simple,
                                    GObject *source_object,
                                    GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_remove_contacts_sync (
		E_BOOK_CLIENT (source_object),
		async_context->string_list,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_remove_contacts:
 * @client: an #EBookClient
 * @uids: (element-type utf8): a #GSList of UIDs to remove
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Removes the contacts with uids from the list @uids from @client.  This is
 * always more efficient than calling e_book_client_remove_contact() if you
 * have more than one uid to remove, as some backends can implement it
 * as a batch request.
 * The call is finished by e_book_client_remove_contacts_finish()
 * from the @callback.
 *
 * Since: 3.2
 **/
void
e_book_client_remove_contacts (EBookClient *client,
                               const GSList *uids,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (uids != NULL);

	async_context = g_slice_new0 (AsyncContext);
	async_context->string_list = g_slist_copy_deep (
		(GSList *) uids, (GCopyFunc) g_strdup, NULL);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_remove_contacts);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_remove_contacts_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_remove_contacts_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_remove_contacts().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_remove_contacts_finish (EBookClient *client,
                                      GAsyncResult *result,
                                      GError **error)
{
	GSimpleAsyncResult *simple;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_remove_contacts), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);

	/* Assume success unless a GError is set. */
	return !g_simple_async_result_propagate_error (simple, error);
}

/**
 * e_book_client_remove_contacts_sync:
 * @client: an #EBookClient
 * @uids: (element-type utf8): a #GSList of UIDs to remove
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Removes the contacts with uids from the list @uids from @client.  This is
 * always more efficient than calling e_book_client_remove_contact() if you
 * have more than one uid to remove, as some backends can implement it
 * as a batch request.
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_remove_contacts_sync (EBookClient *client,
                                    const GSList *uids,
                                    GCancellable *cancellable,
                                    GError **error)
{
	gchar **strv;
	gint ii = 0;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (uids != NULL, FALSE);

	strv = g_new0 (gchar *, g_slist_length ((GSList *) uids) + 1);
	while (uids != NULL) {
		strv[ii++] = e_util_utf8_make_valid (uids->data);
		uids = g_slist_next (uids);
	}

	e_dbus_address_book_call_remove_contacts_sync (
		client->priv->dbus_proxy,
		(const gchar * const *) strv,
		cancellable, &local_error);

	g_strfreev (strv);

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		return FALSE;
	}

	return TRUE;
}

/* Helper for e_book_client_get_contact() */
static void
book_client_get_contact_thread (GSimpleAsyncResult *simple,
                                GObject *source_object,
                                GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_get_contact_sync (
		E_BOOK_CLIENT (source_object),
		async_context->uid,
		&async_context->contact,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_get_contact:
 * @client: an #EBookClient
 * @uid: a unique string ID specifying the contact
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Receive #EContact from the @client for the gived @uid.
 * The call is finished by e_book_client_get_contact_finish()
 * from the @callback.
 *
 * Since: 3.2
 **/
void
e_book_client_get_contact (EBookClient *client,
                           const gchar *uid,
                           GCancellable *cancellable,
                           GAsyncReadyCallback callback,
                           gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (uid != NULL);

	async_context = g_slice_new0 (AsyncContext);
	async_context->uid  = g_strdup (uid);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_get_contact);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_get_contact_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_get_contact_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @out_contact: (out): an #EContact for previously given uid
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_get_contact().
 * If successful, then the @out_contact is set to newly allocated
 * #EContact, which should be freed with g_object_unref().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_contact_finish (EBookClient *client,
                                  GAsyncResult *result,
                                  EContact **out_contact,
                                  GError **error)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_get_contact), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	g_return_val_if_fail (async_context->contact != NULL, FALSE);

	if (out_contact != NULL)
		*out_contact = g_object_ref (async_context->contact);

	return TRUE;
}

/**
 * e_book_client_get_contact_sync:
 * @client: an #EBookClient
 * @uid: a unique string ID specifying the contact
 * @out_contact: (out): an #EContact for given @uid
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Receive #EContact from the @client for the gived @uid.
 * If successful, then the @out_contact is set to newly allocated
 * #EContact, which should be freed with g_object_unref().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_contact_sync (EBookClient *client,
                                const gchar *uid,
                                EContact **out_contact,
                                GCancellable *cancellable,
                                GError **error)
{
	gchar *utf8_uid;
	gchar *vcard = NULL;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (uid != NULL, FALSE);
	g_return_val_if_fail (out_contact != NULL, FALSE);

	if (client->priv->direct_book != NULL) {
		/* Direct backend is not using D-Bus (obviously),
		 * so no need to strip D-Bus info from the error. */
		return e_data_book_get_contact_sync (
			client->priv->direct_book, uid,
			out_contact, cancellable, error);
	}

	utf8_uid = e_util_utf8_make_valid (uid);

	e_dbus_address_book_call_get_contact_sync (
		client->priv->dbus_proxy, utf8_uid,
		&vcard, cancellable, &local_error);

	/* Sanity check. */
	g_return_val_if_fail (
		((vcard != NULL) && (local_error == NULL)) ||
		((vcard == NULL) && (local_error != NULL)), FALSE);

	if (vcard != NULL) {
		*out_contact =
			e_contact_new_from_vcard_with_uid (vcard, utf8_uid);
		g_free (vcard);
	}

	g_free (utf8_uid);

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		return FALSE;
	}

	return TRUE;
}

/* Helper for e_book_client_get_contacts() */
static void
book_client_get_contacts_thread (GSimpleAsyncResult *simple,
                                 GObject *source_object,
                                 GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_get_contacts_sync (
		E_BOOK_CLIENT (source_object),
		async_context->sexp,
		&async_context->object_list,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_get_contacts:
 * @client: an #EBookClient
 * @sexp: an S-expression representing the query
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Query @client with @sexp, receiving a list of contacts which
 * matched. The call is finished by e_book_client_get_contacts_finish()
 * from the @callback.
 *
 * Note: @sexp can be obtained through #EBookQuery, by converting it
 * to a string with e_book_query_to_string().
 *
 * Since: 3.2
 **/
void
e_book_client_get_contacts (EBookClient *client,
                            const gchar *sexp,
                            GCancellable *cancellable,
                            GAsyncReadyCallback callback,
                            gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (sexp != NULL);

	async_context = g_slice_new0 (AsyncContext);
	async_context->sexp = g_strdup (sexp);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_get_contacts);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_get_contacts_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_get_contacts_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @out_contacts: (element-type EContact) (out) (transfer full): a #GSList
 *                of matched #EContact-s
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_get_contacts().
 * If successful, then the @out_contacts is set to newly allocated list of
 * #EContact-s, which should be freed with e_client_util_free_object_slist().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_contacts_finish (EBookClient *client,
                                   GAsyncResult *result,
                                   GSList **out_contacts,
                                   GError **error)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_get_contacts), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (out_contacts != NULL) {
		*out_contacts = async_context->object_list;
		async_context->object_list = NULL;
	}

	return TRUE;
}

/**
 * e_book_client_get_contacts_sync:
 * @client: an #EBookClient
 * @sexp: an S-expression representing the query
 * @out_contacts: (element-type EContact) (out): a #GSList of matched
 *                #EContact-s
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Query @client with @sexp, receiving a list of contacts which matched.
 * If successful, then the @out_contacts is set to newly allocated #GSList of
 * #EContact-s, which should be freed with e_client_util_free_object_slist().
 *
 * Note: @sexp can be obtained through #EBookQuery, by converting it
 * to a string with e_book_query_to_string().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_contacts_sync (EBookClient *client,
                                 const gchar *sexp,
                                 GSList **out_contacts,
                                 GCancellable *cancellable,
                                 GError **error)
{
	gchar *utf8_sexp;
	gchar **vcards = NULL;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (sexp != NULL, FALSE);
	g_return_val_if_fail (out_contacts != NULL, FALSE);

	if (client->priv->direct_book != NULL) {
		/* Direct backend is not using D-Bus (obviously),
		 * so no need to strip D-Bus info from the error. */
		return e_data_book_get_contacts_sync (
			client->priv->direct_book,
			sexp, out_contacts, cancellable, error);
	}

	utf8_sexp = e_util_utf8_make_valid (sexp);

	e_dbus_address_book_call_get_contact_list_sync (
		client->priv->dbus_proxy, utf8_sexp,
		&vcards, cancellable, &local_error);

	g_free (utf8_sexp);

	/* Sanity check. */
	g_return_val_if_fail (
		((vcards != NULL) && (local_error == NULL)) ||
		((vcards == NULL) && (local_error != NULL)), FALSE);

	if (vcards != NULL) {
		EContact *contact;
		GSList *tmp = NULL;
		gint ii;

		for (ii = 0; vcards[ii] != NULL; ii++) {
			contact = e_contact_new_from_vcard (vcards[ii]);
			tmp = g_slist_prepend (tmp, contact);
		}

		*out_contacts = g_slist_reverse (tmp);

		g_strfreev (vcards);
	}

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		return FALSE;
	}

	return TRUE;
}

/* Helper for e_book_client_get_contacts_uids() */
static void
book_client_get_contacts_uids_thread (GSimpleAsyncResult *simple,
                                      GObject *source_object,
                                      GCancellable *cancellable)
{
	AsyncContext *async_context;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	e_book_client_get_contacts_uids_sync (
		E_BOOK_CLIENT (source_object),
		async_context->sexp,
		&async_context->string_list,
		cancellable, &local_error);

	if (local_error != NULL)
		g_simple_async_result_take_error (simple, local_error);
}

/**
 * e_book_client_get_contacts_uids:
 * @client: an #EBookClient
 * @sexp: an S-expression representing the query
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Query @client with @sexp, receiving a list of contacts UIDs which
 * matched. The call is finished by e_book_client_get_contacts_uids_finish()
 * from the @callback.
 *
 * Note: @sexp can be obtained through #EBookQuery, by converting it
 * to a string with e_book_query_to_string().
 *
 * Since: 3.2
 **/
void
e_book_client_get_contacts_uids (EBookClient *client,
                                 const gchar *sexp,
                                 GCancellable *cancellable,
                                 GAsyncReadyCallback callback,
                                 gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (sexp != NULL);

	async_context = g_slice_new0 (AsyncContext);
	async_context->sexp = g_strdup (sexp);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_get_contacts_uids);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	g_simple_async_result_run_in_thread (
		simple, book_client_get_contacts_uids_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_get_contacts_uids_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @out_contact_uids: (element-type utf8) (out): a #GSList of matched
 *                    contact UIDs stored as strings
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_get_contacts_uids().
 * If successful, then the @out_contact_uids is set to newly allocated list
 * of UID strings, which should be freed with e_client_util_free_string_slist().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_contacts_uids_finish (EBookClient *client,
                                        GAsyncResult *result,
                                        GSList **out_contact_uids,
                                        GError **error)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_get_contacts_uids), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	if (out_contact_uids != NULL) {
		*out_contact_uids = async_context->string_list;
		async_context->string_list = NULL;
	}

	return TRUE;
}

/**
 * e_book_client_get_contacts_uids_sync:
 * @client: an #EBookClient
 * @sexp: an S-expression representing the query
 * @out_contact_uids: (element-type utf8) (out): a #GSList of matched
 *                    contacts UIDs stored as strings
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Query @client with @sexp, receiving a list of contacts UIDs which matched.
 * If successful, then the @out_contact_uids is set to newly allocated list
 * of UID strings, which should be freed with e_client_util_free_string_slist().
 *
 * Note: @sexp can be obtained through #EBookQuery, by converting it
 * to a string with e_book_query_to_string().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_contacts_uids_sync (EBookClient *client,
                                      const gchar *sexp,
                                      GSList **out_contact_uids,
                                      GCancellable *cancellable,
                                      GError **error)
{
	gchar *utf8_sexp;
	gchar **uids = NULL;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (sexp != NULL, FALSE);
	g_return_val_if_fail (out_contact_uids != NULL, FALSE);

	if (client->priv->direct_book != NULL) {
		/* Direct backend is not using D-Bus (obviously),
		 * so no need to strip D-Bus info from the error. */
		return e_data_book_get_contacts_uids_sync (
			client->priv->direct_book, sexp,
			out_contact_uids, cancellable, error);
	}

	utf8_sexp = e_util_utf8_make_valid (sexp);

	e_dbus_address_book_call_get_contact_list_uids_sync (
		client->priv->dbus_proxy, utf8_sexp,
		&uids, cancellable, &local_error);

	g_free (utf8_sexp);

	/* Sanity check. */
	g_return_val_if_fail (
		((uids != NULL) && (local_error == NULL)) ||
		((uids == NULL) && (local_error != NULL)), FALSE);

	/* XXX We should have passed the string array directly
	 *     back to the caller instead of building a linked
	 *     list.  This is unnecessary work. */
	if (uids != NULL) {
		GSList *tmp = NULL;
		gint ii;

		/* Take ownership of the string array elements. */
		for (ii = 0; uids[ii] != NULL; ii++) {
			tmp = g_slist_prepend (tmp, uids[ii]);
			uids[ii] = NULL;
		}

		*out_contact_uids = g_slist_reverse (tmp);

		g_free (uids);
	}

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_propagate_error (error, local_error);
		return FALSE;
	}

	return TRUE;
}

/* Helper for e_book_client_get_view() */
static void
book_client_get_view_in_dbus_thread (GSimpleAsyncResult *simple,
                                     GObject *source_object,
                                     GCancellable *cancellable)
{
	EBookClient *client = E_BOOK_CLIENT (source_object);
	AsyncContext *async_context;
	gchar *utf8_sexp;
	gchar *object_path = NULL;
	GError *local_error = NULL;

	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	utf8_sexp = e_util_utf8_make_valid (async_context->sexp);

	e_dbus_address_book_call_get_view_sync (
		client->priv->dbus_proxy, utf8_sexp,
		&object_path, cancellable, &local_error);

	g_free (utf8_sexp);

	/* Sanity check. */
	g_return_if_fail (
		((object_path != NULL) && (local_error == NULL)) ||
		((object_path == NULL) && (local_error != NULL)));

	if (object_path != NULL) {
		GDBusConnection *connection;
		EBookClientView *client_view;

		connection = g_dbus_proxy_get_connection (
			G_DBUS_PROXY (client->priv->dbus_proxy));

		client_view = g_initable_new (
			E_TYPE_BOOK_CLIENT_VIEW,
			cancellable, &local_error,
			"client", client,
			"connection", connection,
			"object-path", object_path,
			"direct-book", client->priv->direct_book,
			NULL);

		/* Sanity check. */
		g_return_if_fail (
			((client_view != NULL) && (local_error == NULL)) ||
			((client_view == NULL) && (local_error != NULL)));

		async_context->client_view = client_view;

		g_free (object_path);
	}

	if (local_error != NULL) {
		g_dbus_error_strip_remote_error (local_error);
		g_simple_async_result_take_error (simple, local_error);
	}
}

/**
 * e_book_client_get_view:
 * @client: an #EBookClient
 * @sexp: an S-expression representing the query
 * @cancellable: a #GCancellable; can be %NULL
 * @callback: callback to call when a result is ready
 * @user_data: user data for the @callback
 *
 * Query @client with @sexp, creating an #EBookClientView.
 * The call is finished by e_book_client_get_view_finish()
 * from the @callback.
 *
 * Note: @sexp can be obtained through #EBookQuery, by converting it
 * to a string with e_book_query_to_string().
 *
 * Since: 3.2
 **/
void
e_book_client_get_view (EBookClient *client,
                        const gchar *sexp,
                        GCancellable *cancellable,
                        GAsyncReadyCallback callback,
                        gpointer user_data)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_if_fail (E_IS_BOOK_CLIENT (client));
	g_return_if_fail (sexp != NULL);

	async_context = g_slice_new0 (AsyncContext);
	async_context->sexp = g_strdup (sexp);

	simple = g_simple_async_result_new (
		G_OBJECT (client), callback, user_data,
		e_book_client_get_view);

	g_simple_async_result_set_check_cancellable (simple, cancellable);

	g_simple_async_result_set_op_res_gpointer (
		simple, async_context, (GDestroyNotify) async_context_free);

	book_client_run_in_dbus_thread (
		simple, book_client_get_view_in_dbus_thread,
		G_PRIORITY_DEFAULT, cancellable);

	g_object_unref (simple);
}

/**
 * e_book_client_get_view_finish:
 * @client: an #EBookClient
 * @result: a #GAsyncResult
 * @out_view: (out): an #EBookClientView
 * @error: (out): a #GError to set an error, if any
 *
 * Finishes previous call of e_book_client_get_view().
 * If successful, then the @out_view is set to newly allocated
 * #EBookClientView, which should be freed with g_object_unref().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_view_finish (EBookClient *client,
                               GAsyncResult *result,
                               EBookClientView **out_view,
                               GError **error)
{
	GSimpleAsyncResult *simple;
	AsyncContext *async_context;

	g_return_val_if_fail (
		g_simple_async_result_is_valid (
		result, G_OBJECT (client),
		e_book_client_get_view), FALSE);

	simple = G_SIMPLE_ASYNC_RESULT (result);
	async_context = g_simple_async_result_get_op_res_gpointer (simple);

	if (g_simple_async_result_propagate_error (simple, error))
		return FALSE;

	g_return_val_if_fail (async_context->client_view != NULL, FALSE);

	if (out_view != NULL)
		*out_view = g_object_ref (async_context->client_view);

	return TRUE;
}

/**
 * e_book_client_get_view_sync:
 * @client: an #EBookClient
 * @sexp: an S-expression representing the query
 * @out_view: (out) an #EBookClientView
 * @cancellable: a #GCancellable; can be %NULL
 * @error: (out): a #GError to set an error, if any
 *
 * Query @client with @sexp, creating an #EBookClientView.
 * If successful, then the @out_view is set to newly allocated
 * #EBookClientView, which should be freed with g_object_unref().
 *
 * Note: @sexp can be obtained through #EBookQuery, by converting it
 * to a string with e_book_query_to_string().
 *
 * Returns: %TRUE if successful, %FALSE otherwise.
 *
 * Since: 3.2
 **/
gboolean
e_book_client_get_view_sync (EBookClient *client,
                             const gchar *sexp,
                             EBookClientView **out_view,
                             GCancellable *cancellable,
                             GError **error)
{
	EAsyncClosure *closure;
	GAsyncResult *result;
	gboolean success;

	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
	g_return_val_if_fail (sexp != NULL, FALSE);
	g_return_val_if_fail (out_view != NULL, FALSE);

	closure = e_async_closure_new ();

	e_book_client_get_view (
		client, sexp, cancellable,
		e_async_closure_callback, closure);

	result = e_async_closure_wait (closure);

	success = e_book_client_get_view_finish (
		client, result, out_view, error);

	e_async_closure_free (closure);

	return success;
}

