/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

#include <config.h>

#include <pthread.h>

#include <string.h>

#include "e-book.h"
#include "e-vcard.h"

#include <bonobo-activation/bonobo-activation.h>

#include <bonobo/bonobo-exception.h>
#include <bonobo/bonobo-main.h>

#include <libgnome/gnome-i18n.h>

#include "e-book-marshal.h"
#include "e-book-listener.h"
#include "addressbook.h"
#include "e-util/e-component-listener.h"
#include "e-util/e-msgport.h"

static GObjectClass *parent_class;

#define CARDSERVER_OAF_ID "OAFIID:GNOME_Evolution_Wombat_ServerFactory"

#define e_return_error_if_fail(expr,error_code)	G_STMT_START{		\
     if G_LIKELY(expr) { } else						\
       {								\
	 g_log (G_LOG_DOMAIN,						\
		G_LOG_LEVEL_CRITICAL,					\
		"file %s: line %d (%s): assertion `%s' failed",		\
		__FILE__,						\
		__LINE__,						\
		__PRETTY_FUNCTION__,					\
		#expr);							\
	 g_set_error (error, E_BOOK_ERROR, (error_code),                \
		"file %s: line %d (%s): assertion `%s' failed",		\
		__FILE__,						\
		__LINE__,						\
		__PRETTY_FUNCTION__,					\
		#expr);							\
	 return FALSE;							\
       };				}G_STMT_END

/* XXX we need a better error message here */
#define E_BOOK_CHECK_STATUS(status,error) G_STMT_START{			\
	if ((status) == E_BOOK_ERROR_OK) {				\
		return TRUE;						\
	}								\
	else {								\
		g_set_error ((error), E_BOOK_ERROR, (status), "EBookStatus returned %d", (status));	\
		return FALSE;						\
	}				}G_STMT_END

enum {
	OPEN_PROGRESS,
	WRITABLE_STATUS,
	BACKEND_DIED,
	LAST_SIGNAL
};

static guint e_book_signals [LAST_SIGNAL];

typedef struct {
	EMutex *mutex;
	pthread_cond_t cond;
	EBookStatus status;

	char *id;
	GList *list;
	EContact *contact;

	EBookView *view;
	EBookViewListener *listener;
} EBookOp;

typedef enum {
	E_BOOK_URI_NOT_LOADED,
	E_BOOK_URI_LOADING,
	E_BOOK_URI_LOADED
} EBookLoadState;

struct _EBookPrivate {
	GList *book_factories;
	GList *iter;

	/* cached capabilites */
	char *cap;
	gboolean cap_queried;

	/* cached writable status */
	gboolean writable;

	EBookListener         *listener;
	EComponentListener    *comp_listener;

	GNOME_Evolution_Addressbook_Book         corba_book;

	EBookLoadState         load_state;


	EBookOp *current_op;

	EMutex *mutex;

	gchar *uri;

	gulong listener_signal;
	gulong died_signal;
};


/* Error quark */
GQuark
e_book_error_quark (void)
{
  static GQuark q = 0;
  if (q == 0)
    q = g_quark_from_static_string ("e-book-error-quark");

  return q;
}



/* EBookOp calls */

static EBookOp*
e_book_new_op (EBook *book)
{
	EBookOp *op = g_new0 (EBookOp, 1);

	op->mutex = e_mutex_new (E_MUTEX_SIMPLE);
	pthread_cond_init (&op->cond, 0);

	book->priv->current_op = op;

	return op;
}

static EBookOp*
e_book_get_op (EBook *book)
{
	if (!book->priv->current_op) {
		g_warning ("unexpected response");
		return NULL;
	}
		
	return book->priv->current_op;
}

static void
e_book_op_free (EBookOp *op)
{
	/* XXX more stuff here */
	pthread_cond_destroy (&op->cond);
	e_mutex_destroy (op->mutex);
	g_free (op);
}

static void
e_book_op_remove (EBook *book,
		  EBookOp *op)
{
	if (book->priv->current_op != op)
		g_warning ("cannot remove op, it's not current");

	book->priv->current_op = NULL;
}

static void
e_book_clear_op (EBook *book,
		 EBookOp *op)
{
	e_book_op_remove (book, op);
	e_mutex_unlock (op->mutex);
	e_book_op_free (op);
}



/**
 * e_book_add_card:
 * @book: an #EBook
 * @contact: an #EContact
 *
 * adds @contact to @book.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_add_contact (EBook           *book,
		    EContact        *contact,
		    GError         **error)
{
	EBookOp *our_op;
	EBookStatus status;
	CORBA_Environment ev;
	char *vcard_str;

	printf ("e_book_add_contact\n");

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (contact && E_IS_CONTACT (contact), E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_add_contact called on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	/* will eventually end up calling e_book_response_add_contact */
	GNOME_Evolution_Addressbook_Book_addContact (book->priv->corba_book,
						     (const GNOME_Evolution_Addressbook_VCard) vcard_str, &ev);

	g_free (vcard_str);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::addContact call"));
		return FALSE;
	}

	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;
	e_contact_set (contact, E_CONTACT_UID, our_op->id);
	g_free (our_op->id);

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}

static void
e_book_response_add_contact (EBook       *book,
			     EBookStatus  status,
			     char        *id)
{
	EBookOp *op;

	printf ("e_book_response_add_contact\n");

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_add_contact: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;
	op->id = g_strdup (id);

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}



/**
 * e_book_commit_contact:
 * @book: an #EBook
 * @contact: an #EContact
 *
 * applies the changes made to @contact to the stored version in
 * @book.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_commit_contact (EBook           *book,
		       EContact        *contact,
		       GError         **error)
{
	EBookOp *our_op;
	EBookStatus status;
	CORBA_Environment ev;
	char *vcard_str;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (contact && E_IS_CONTACT (contact), E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_commit_contact called on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	vcard_str = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	/* will eventually end up calling _e_book_response_generic */
	GNOME_Evolution_Addressbook_Book_modifyContact (book->priv->corba_book,
							(const GNOME_Evolution_Addressbook_VCard) vcard_str, &ev);

	g_free (vcard_str);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::modifyContact call"));
		return FALSE;
	}

	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;
	e_contact_set (contact, E_CONTACT_UID, our_op->id);
	g_free (our_op->id);

	/* remove the op from the book's hash of operations */
	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}


/**
 * e_book_get_supported_fields:
 * @book: an #EBook
 * @fields: a #GList
 *
 * queries @book for the list of fields it supports.  mostly for use
 * by the contact editor so it knows what fields to sensitize.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_get_supported_fields  (EBook            *book,
			      GList           **fields,
			      GError          **error)
{
	EBookOp *our_op;
	EBookStatus status;
	CORBA_Environment ev;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (fields,                   E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_get_supported_fields on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	/* will eventually end up calling
	   _e_book_response_get_supported_fields */
	GNOME_Evolution_Addressbook_Book_getSupportedFields(book->priv->corba_book, &ev);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::getSupportedFields call"));
		return FALSE;
	}


	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;
	*fields = our_op->list;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}

static void
e_book_response_get_supported_fields (EBook       *book,
				      EBookStatus  status,
				      GList       *fields)
{
	EBookOp *op;

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_get_supported_fields: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;
	op->list = fields;

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}


/**
 * e_book_get_supported_auth_methods:
 * @book: an #EBook
 * @auth_methods: a #GList
 *
 * queries @book for the list of authentication methods it supports.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_get_supported_auth_methods (EBook            *book,
				   GList           **auth_methods,
				   GError          **error)
{
	EBookOp *our_op;
	EBookStatus status;
	CORBA_Environment ev;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (auth_methods,             E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_get_supported_auth_methods on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	/* will eventually end up calling
	   e_book_response_get_supported_fields */
	GNOME_Evolution_Addressbook_Book_getSupportedAuthMethods(book->priv->corba_book, &ev);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::getSupportedAuthMethods call"));
		return FALSE;
	}


	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;
	*auth_methods = our_op->list;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}

static void
e_book_response_get_supported_auth_methods (EBook                 *book,
					    EBookStatus            status,
					    GList                 *auth_methods)
{
	EBookOp *op;

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_get_supported_auth_methods: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;
	op->list = auth_methods;

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}



/**
 * e_book_authenticate_user:
 * @book: an #EBook
 * @user: a string
 * @passwd: a string
 * @auth_method: a string
 *
 * authenticates @user with @passwd, using the auth method
 * @auth_method.  @auth_method must be one of the authentication
 * methods returned using e_book_get_supported_auth_methods.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_authenticate_user (EBook         *book,
			  const char    *user,
			  const char    *passwd,
			  const char    *auth_method,
			  GError       **error)
{
	EBookOp *our_op;
	EBookStatus status;
	CORBA_Environment ev;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (user,                     E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (passwd,                   E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (auth_method,              E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_authenticate_user on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	/* will eventually end up calling
	   e_book_response_generic */
	GNOME_Evolution_Addressbook_Book_authenticateUser (book->priv->corba_book,
							   user, passwd,
							   auth_method,
							   &ev);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::authenticateUser call"));
		return FALSE;
	}

	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}


/**
 * e_book_get_contact:
 * @book: an #EBook
 * @id: a string
 * @contact: an #EContact
 *
 * Fills in @contact with the contents of the vcard in @book
 * corresponding to @id.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_get_contact (EBook       *book,
		    const char  *id,
		    EContact   **contact,
		    GError     **error)
{
	EBookOp *our_op;
	EBookStatus status;
	CORBA_Environment ev;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (id,                       E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (contact,                     E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_get_contact on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	/* will eventually end up calling e_book_response_generic */
	GNOME_Evolution_Addressbook_Book_getContact (book->priv->corba_book,
						     (const GNOME_Evolution_Addressbook_VCard) id, &ev);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::getContact call"));
		return FALSE;
	}

	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;
	*contact = our_op->contact;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}

static void
e_book_response_get_contact (EBook       *book,
			     EBookStatus  status,
			     EContact    *contact)
{
	EBookOp *op;

	printf ("e_book_response_get_contact\n");

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_get_contact: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;
	op->contact = contact;

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}


/**
 * e_book_remove_contact:
 * @book: an #EBook
 * @id: a string
 *
 * Removes the contact with id @id from @book.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_remove_contact (EBook       *book,
		       const char  *id,
		       GError     **error)
{
	GList *list;
	gboolean rv;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (id,                       E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_remove_contact on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	e_mutex_lock (book->priv->mutex);

	list = g_list_append (NULL, (char*)id);

	rv = e_book_remove_contacts (book, list, error);

	return rv;
}

/**
 * e_book_remove_contacts:
 * @book: an #EBook
 * @ids: an #GList of const char *id's
 *
 * Removes the contacts with ids from the list @ids from @book.  This is
 * always more efficient than calling e_book_remove_contact_by_id if you
 * have more than one id to remove, as some backends can implement it
 * as a batch request.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_remove_contacts (EBook    *book,
			GList    *ids,
			GError  **error)
{
	GNOME_Evolution_Addressbook_ContactIdList idlist;
	CORBA_Environment ev;
	GList *iter;
	int num_ids, i;
	EBookOp *our_op;
	EBookStatus status;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (ids,                      E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_remove_contacts on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	num_ids = g_list_length (ids);
	idlist._buffer = CORBA_sequence_GNOME_Evolution_Addressbook_ContactId_allocbuf (num_ids);
	idlist._maximum = num_ids;
	idlist._length = num_ids;

	for (iter = ids, i = 0; iter; iter = iter->next)
		idlist._buffer[i++] = CORBA_string_dup (iter->data);

	/* will eventually end up calling e_book_response_generic */
	GNOME_Evolution_Addressbook_Book_removeContacts (book->priv->corba_book, &idlist, &ev);

	CORBA_free(idlist._buffer);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::removeContacts call"));
		return FALSE;
	}
	
	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}


/**
 * e_book_get_book_view:
 * @book: an #EBook
 * @query: an #EBookQuery
 * @requested_fields a #GList containing the names of fields to return, or NULL for all
 * @max_results the maximum number of contacts to show (or 0 for all)
 *
 * need docs here..
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_get_book_view (EBook       *book,
		      EBookQuery  *query,
		      GList       *requested_fields,
		      int          max_results,
		      EBookView  **book_view,
		      GError     **error)
{
	GNOME_Evolution_Addressbook_stringlist stringlist;
	CORBA_Environment ev;
	EBookOp *our_op;
	EBookStatus status;
	int num_fields, i;
	GList *iter;
	char *query_string;

	e_return_error_if_fail (book && E_IS_BOOK (book),       E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (query,                          E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (book_view,                      E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_get_book_view on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	our_op->listener = e_book_view_listener_new();

	num_fields = g_list_length (requested_fields);

	stringlist._buffer = CORBA_sequence_CORBA_string_allocbuf (num_fields);
	stringlist._maximum = num_fields;
	stringlist._length = num_fields;

	for (i = 0, iter = requested_fields; iter; iter = iter->next, i ++) {
		stringlist._buffer[i] = CORBA_string_dup ((char*)iter->data);
	}

	query_string = e_book_query_to_string (query);

	/* will eventually end up calling e_book_response_get_book_view */
	GNOME_Evolution_Addressbook_Book_getBookView (book->priv->corba_book,
						      bonobo_object_corba_objref(BONOBO_OBJECT(our_op->listener)),
						      query_string,
						      &stringlist, max_results, &ev);

	CORBA_free(stringlist._buffer);
	g_free (query_string);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_warning ("corba exception._major = %d\n", ev._major);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::getBookView call"));
		return FALSE;
	}
	
	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;
	*book_view = our_op->view;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}

static void
e_book_response_get_book_view (EBook       *book,
			       EBookStatus  status,
			       GNOME_Evolution_Addressbook_BookView corba_book_view)
{

	EBookOp *op;

	printf ("e_book_response_get_book_view\n");

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_get_book_view: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;
	op->view = e_book_view_new (corba_book_view, op->listener);

	bonobo_object_ref(BONOBO_OBJECT(op->listener));

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}



/**
 * e_book_get_contacts:
 * @book: an #EBook
 * @query: an #EBookQuery
 *
 * need docs here..
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_get_contacts (EBook       *book,
		     EBookQuery  *query,
		     GList      **contacts,
		     GError     **error)
{
	CORBA_Environment ev;
	EBookOp *our_op;
	EBookStatus status;
	char *query_string;

	e_return_error_if_fail (book && E_IS_BOOK (book),       E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (query,                          E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (contacts,                       E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_get_contacts on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	query_string = e_book_query_to_string (query);

	/* will eventually end up calling e_book_response_get_contacts */
	GNOME_Evolution_Addressbook_Book_getContactList (book->priv->corba_book, query_string, &ev);

	g_free (query_string);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_warning ("corba exception._major = %d\n", ev._major);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::getContactList call"));
		return FALSE;
	}
	
	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;
	*contacts = our_op->list;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}

static void
e_book_response_get_contacts (EBook       *book,
			      EBookStatus  status,
			      GList       *contact_list)
{

	EBookOp *op;

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_get_contacts: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;
	op->list = contact_list;

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}


gboolean
e_book_get_changes (EBook       *book,
		    char        *changeid,
		    GList      **changes,
		    GError     **error)
{
	CORBA_Environment ev;
	EBookOp *our_op;
	EBookStatus status;

	e_return_error_if_fail (book && E_IS_BOOK (book),       E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (changeid,                       E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (changes,                        E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_get_changes on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	/* will eventually end up calling e_book_response_get_changes */
	GNOME_Evolution_Addressbook_Book_getChanges (book->priv->corba_book, changeid, &ev);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_warning ("corba exception._major = %d\n", ev._major);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::getChanges call"));
		return FALSE;
	}
	
	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;
	*changes = our_op->list;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}

static void
e_book_response_get_changes (EBook       *book,
			     EBookStatus  status,
			     GList       *change_list)
{

	EBookOp *op;

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_get_contacts: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;
	op->list = change_list;

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}

void
e_book_free_change_list (GList *change_list)
{
	GList *l;
	for (l = change_list; l; l = l->next) {
		EBookChange *change = l->data;

		g_object_unref (change->contact);
		g_free (change);
	}

	g_list_free (change_list);
}



static void
e_book_response_generic (EBook       *book,
			 EBookStatus  status)
{
	EBookOp *op;

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_generic: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}

/**
 * e_book_cancel:
 * @book: an #EBook
 *
 * Used to cancel an already running operation on @book.  This
 * function makes a synchronous CORBA to the backend telling it to
 * cancel the operation.  If the operation wasn't cancellable (either
 * transiently or permanently) or had already comopleted on the wombat
 * side, this function will return E_BOOK_STATUS_COULD_NOT_CANCEL, and
 * the operation will continue uncancelled.  If the operation could be
 * cancelled, this function will return E_BOOK_ERROR_OK, and the
 * blocked e_book function corresponding to current operation will
 * return with a status of E_BOOK_STATUS_CANCELLED.
 *
 * Return value: a #EBookStatus value.
 **/
gboolean
e_book_cancel (EBook   *book,
	       GError **error)
{
	EBookOp *op;
	EBookStatus status;
	gboolean rv;
	CORBA_Environment ev;

	e_mutex_lock (book->priv->mutex);

	if (book->priv->current_op == NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_COULD_NOT_CANCEL,
			     _("e_book_cancel: there is no current operation"));
		return FALSE;
	}

	op = book->priv->current_op;

	e_mutex_lock (op->mutex);

	e_mutex_unlock (book->priv->mutex);

	status = GNOME_Evolution_Addressbook_Book_cancelOperation(book->priv->corba_book, &ev);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_mutex_unlock (op->mutex);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::cancelOperation call"));
		return FALSE;
	}

	CORBA_exception_free (&ev);

	if (status == E_BOOK_ERROR_OK) {
		op->status = E_BOOK_ERROR_CANCELLED;

		pthread_cond_signal (&op->cond);

		rv = TRUE;
	}
	else {
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_COULD_NOT_CANCEL,
			     _("e_book_cancel: couldn't cancel"));
		rv = FALSE;
	}

	e_mutex_unlock (op->mutex);

	return rv;
}

static void
e_book_response_open (EBook       *book,
		      EBookStatus  status)
{
	EBookOp *op;

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_open: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}



gboolean
e_book_remove (EBook   *book,
	       GError **error)
{
	CORBA_Environment ev;
	EBookOp *our_op;
	EBookStatus status;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);

	e_mutex_lock (book->priv->mutex);

	if (book->priv->load_state != E_BOOK_URI_LOADED) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_URI_NOT_LOADED,
			     _("e_book_remove on book before e_book_load_uri"));
		return FALSE;
	}

	if (book->priv->current_op != NULL) {
		e_mutex_unlock (book->priv->mutex);
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_BUSY,
			     _("book busy"));
		return FALSE;
	}

	our_op = e_book_new_op (book);

	e_mutex_lock (our_op->mutex);

	e_mutex_unlock (book->priv->mutex);

	CORBA_exception_init (&ev);

	/* will eventually end up calling e_book_response_remove */
	GNOME_Evolution_Addressbook_Book_remove (book->priv->corba_book, &ev);

	if (ev._major != CORBA_NO_EXCEPTION) {

		e_book_clear_op (book, our_op);

		CORBA_exception_free (&ev);

		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_CORBA_EXCEPTION,
			     _("Corba exception making Book::remove call"));
		return FALSE;
	}

	CORBA_exception_free (&ev);

	/* wait for something to happen (both cancellation and a
	   successful response will notity us via our cv */
	e_mutex_cond_wait (&our_op->cond, our_op->mutex);

	status = our_op->status;

	e_book_clear_op (book, our_op);

	E_BOOK_CHECK_STATUS (status, error);
}

static void
e_book_response_remove (EBook       *book,
			EBookStatus  status)
{
	EBookOp *op;

	printf ("e_book_response_remove\n");

	op = e_book_get_op (book);

	if (op == NULL) {
	  g_warning ("e_book_response_remove: Cannot find operation ");
	  return;
	}

	e_mutex_lock (op->mutex);

	op->status = status;

	pthread_cond_signal (&op->cond);

	e_mutex_unlock (op->mutex);
}



static void
e_book_handle_response (EBookListener *listener, EBookListenerResponse *resp, EBook *book)
{
	switch (resp->op) {
	case CreateContactResponse:
		e_book_response_add_contact (book, resp->status, resp->id);
		break;
	case RemoveContactResponse:
	case ModifyContactResponse:
	case AuthenticationResponse:
		e_book_response_generic (book, resp->status);
		break;
	case GetContactResponse: {
		EContact *contact = e_contact_new_from_vcard (resp->vcard);
		e_book_response_get_contact (book, resp->status, contact);
		break;
	}
	case GetContactListResponse:
		e_book_response_get_contacts (book, resp->status, resp->list);
		break;
	case GetBookViewResponse:
		e_book_response_get_book_view(book, resp->status, resp->book_view);
		break;
	case GetChangesResponse:
		e_book_response_get_changes(book, resp->status, resp->list);
		break;
	case OpenBookResponse:
		e_book_response_open (book, resp->status);
		break;
	case RemoveBookResponse:
		e_book_response_remove (book, resp->status);
		break;
	case GetSupportedFieldsResponse:
		e_book_response_get_supported_fields (book, resp->status, resp->list);
		break;
	case GetSupportedAuthMethodsResponse:
		e_book_response_get_supported_auth_methods (book, resp->status, resp->list);
		break;
	case WritableStatusEvent:
		book->priv->writable = resp->writable;
		g_signal_emit (book, e_book_signals [WRITABLE_STATUS], 0, resp->writable);
		break;
	default:
		g_error ("EBook: Unknown response code %d!\n",
			 resp->op);
	}
}



gboolean
e_book_unload_uri (EBook   *book,
		   GError **error)
{
	CORBA_Environment ev;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (book->priv->load_state != E_BOOK_URI_NOT_LOADED, E_BOOK_ERROR_URI_NOT_LOADED);

	/* Release the remote GNOME_Evolution_Addressbook_Book in the PAS. */
	CORBA_exception_init (&ev);

	bonobo_object_release_unref  (book->priv->corba_book, &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		g_warning ("e_book_unload_uri: Exception releasing "
			   "remote book interface!\n");
	}

	CORBA_exception_free (&ev);

	e_book_listener_stop (book->priv->listener);
	bonobo_object_unref (BONOBO_OBJECT (book->priv->listener));

	book->priv->listener   = NULL;
	book->priv->load_state = E_BOOK_URI_NOT_LOADED;
	g_free (book->priv->cap);
	book->priv->cap = NULL;
	book->priv->writable = FALSE;

	return TRUE;
}



/**
 * e_book_load_uri:
 */

static void
backend_died_cb (EComponentListener *cl, gpointer user_data)
{
	EBook *book = user_data;
                                                                                                                              
	book->priv->load_state = E_BOOK_URI_NOT_LOADED;
        g_signal_emit (book, e_book_signals [BACKEND_DIED], 0);
}

static GList *
activate_factories_for_uri (EBook *book, const char *uri)
{
	CORBA_Environment ev;
	Bonobo_ServerInfoList *info_list = NULL;
	int i;
	char *protocol, *query, *colon;
	GList *factories = NULL;

	colon = strchr (uri, ':');
	if (!colon) {
		g_warning ("e_book_load_uri: Unable to determine protocol in the URI\n");
		return FALSE;
	}

	protocol = g_strndup (uri, colon-uri);
	query = g_strdup_printf ("repo_ids.has ('IDL:GNOME/Evolution/BookFactory:1.0')"
				 " AND addressbook:supported_protocols.has ('%s')", protocol
				 );

	CORBA_exception_init (&ev);
	
	info_list = bonobo_activation_query (query, NULL, &ev);

	if (ev._major != CORBA_NO_EXCEPTION) {
		g_warning ("Eeek!  Cannot perform bonobo-activation query for book factories.");
		CORBA_exception_free (&ev);
		goto done;
		return NULL;
	}

	if (info_list->_length == 0) {
		g_warning ("Can't find installed BookFactory that handles protocol '%s'.", protocol);
		CORBA_exception_free (&ev);
		goto done;
	}

	CORBA_exception_free (&ev);

	for (i = 0; i < info_list->_length; i ++) {
		const Bonobo_ServerInfo *info;
		GNOME_Evolution_Addressbook_BookFactory factory;

		info = info_list->_buffer + i;

		factory = bonobo_activation_activate_from_id (info->iid, 0, NULL, NULL);

		if (factory == CORBA_OBJECT_NIL)
			g_warning ("e_book_construct: Could not obtain a handle "
				   "to the Personal Addressbook Server with IID `%s'\n", info->iid);
		else
			factories = g_list_append (factories, factory);
	}

 done:
	if (info_list)
		CORBA_free (info_list);
	g_free (query);
	g_free (protocol);

	return factories;
}

gboolean
e_book_load_uri (EBook        *book,
		 const char   *uri,
		 gboolean      only_if_exists,
		 GError      **error)
{
	GList *factories;
	GList *l;
	gboolean rv = FALSE;
	GNOME_Evolution_Addressbook_Book corba_book = CORBA_OBJECT_NIL;

	e_return_error_if_fail (book && E_IS_BOOK (book), E_BOOK_ERROR_INVALID_ARG);
	e_return_error_if_fail (uri,                      E_BOOK_ERROR_INVALID_ARG);

	/* XXX this needs to happen while holding the book's lock i would think... */
	e_return_error_if_fail (book->priv->load_state == E_BOOK_URI_NOT_LOADED, E_BOOK_ERROR_URI_ALREADY_LOADED);

	/* try to find a list of factories that can handle the protocol */
	if (! (factories = activate_factories_for_uri (book, uri))) {
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_PROTOCOL_NOT_SUPPORTED,
			     _("e_book_load_uri: no factories available for uri `%s'"), uri);
		return FALSE;
	}


	book->priv->load_state = E_BOOK_URI_LOADING;

	/*
	 * Create our local BookListener interface.
	 */
	book->priv->listener = e_book_listener_new ();
	if (book->priv->listener == NULL) {
		g_warning ("e_book_load_uri: Could not create EBookListener!\n");
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_OTHER_ERROR,
			     _("e_book_load_uri: Could not create EBookListener"));
		return FALSE;
	}
	book->priv->listener_signal = g_signal_connect (book->priv->listener, "response",
							G_CALLBACK (e_book_handle_response), book);

	g_free (book->priv->uri);
	book->priv->uri = g_strdup (uri);

	for (l = factories; l; l = l->next) {
		GNOME_Evolution_Addressbook_BookFactory factory = l->data;
		EBookOp *our_op;
		CORBA_Environment ev;
		EBookStatus status;

		our_op = e_book_new_op (book);

		e_mutex_lock (our_op->mutex);

		CORBA_exception_init (&ev);

		corba_book = GNOME_Evolution_Addressbook_BookFactory_getBook (factory, book->priv->uri,
								      bonobo_object_corba_objref (BONOBO_OBJECT (book->priv->listener)),
								      &ev);

		if (ev._major != CORBA_NO_EXCEPTION) {
			e_book_clear_op (book, our_op);

			CORBA_exception_free (&ev);
			continue;
		}

		GNOME_Evolution_Addressbook_Book_open (corba_book,
						       only_if_exists,
						       &ev);

		if (ev._major != CORBA_NO_EXCEPTION) {
			/* kill the listener so the book will die */
			g_signal_handler_disconnect (book->priv->listener, book->priv->listener_signal);
			bonobo_object_unref (book->priv->listener);
			book->priv->listener = NULL;

			e_book_clear_op (book, our_op);

			CORBA_exception_free (&ev);
			continue;
		}

		CORBA_exception_free (&ev);

		/* wait for something to happen (both cancellation and a
		   successful response will notity us via our cv */
		e_mutex_cond_wait (&our_op->cond, our_op->mutex);

		status = our_op->status;

		/* remove the op from the book's hash of operations */
		e_book_clear_op (book, our_op);

		if (status == E_BOOK_ERROR_CANCELLED
		    || status == E_BOOK_ERROR_OK) {
			rv = TRUE;
			break;
		}
	}

	/* free up the factories */
	for (l = factories; l; l = l->next)
		CORBA_Object_release ((CORBA_Object)l->data, NULL);

	if (rv == TRUE) {
		book->priv->corba_book = corba_book;
		book->priv->load_state = E_BOOK_URI_LOADED;
		book->priv->comp_listener = e_component_listener_new (book->priv->corba_book);
		book->priv->died_signal = g_signal_connect (book->priv->comp_listener, "component_died",
							    G_CALLBACK (backend_died_cb), book);
		return TRUE;
	}
	else {
		g_set_error (error, E_BOOK_ERROR, E_BOOK_ERROR_PROTOCOL_NOT_SUPPORTED,
			     _("e_book_load_uri: no factories available for uri `%s'"), uri);
		return FALSE;
	}
		
	return rv;
}

gboolean
e_book_load_local_addressbook (EBook   *book,
			       GError **error)
{
	char *filename;
	char *uri;
	gboolean rv;

	filename = g_build_filename (g_get_home_dir(),
				     "evolution/local/Contacts",
				     NULL);
	uri = g_strdup_printf ("file://%s", filename);

	g_free (filename);
	
	rv = e_book_load_uri (book, uri, TRUE, error);
	
	g_free (uri);

	return rv;
}

const char *
e_book_get_uri (EBook *book)
{
	return book->priv->uri;
}

const char *
e_book_get_static_capabilities (EBook   *book,
				GError **error)
{
	if (!book->priv->cap_queried) {
		CORBA_Environment ev;
		char *temp;

		CORBA_exception_init (&ev);

		if (book->priv->load_state != E_BOOK_URI_LOADED) {
			g_warning ("e_book_unload_uri: No URI is loaded!\n");
			return g_strdup("");
		}

		temp = GNOME_Evolution_Addressbook_Book_getStaticCapabilities(book->priv->corba_book, &ev);

		if (ev._major != CORBA_NO_EXCEPTION) {
			g_warning ("e_book_get_static_capabilities: Exception "
				   "during get_static_capabilities!\n");
			CORBA_exception_free (&ev);
			return g_strdup("");
		}

		book->priv->cap = g_strdup(temp);
		book->priv->cap_queried = TRUE;

		CORBA_free(temp);

		CORBA_exception_free (&ev);
	}

	return book->priv->cap;
}

gboolean
e_book_check_static_capability (EBook *book,
				const char  *cap)
{
	const char *caps = e_book_get_static_capabilities (book, NULL);

	/* XXX this is an inexact test but it works for our use */
	if (caps && strstr (caps, cap))
		return TRUE;

	return FALSE;
}

gboolean
e_book_is_writable (EBook *book)
{
	return book->priv->writable;
}




gboolean
e_book_get_self (EContact **contact, EBook **book, GError **error)
{
	GError *e = NULL;

	if (!e_book_get_default_addressbook (book, &e)) {
		g_propagate_error (error, e);
		return FALSE;
	}

#if notyet
	EBook *b;
	char *self_uri, *self_uid;

	/* XXX get the setting for the self book and self uid from gconf */

	b = e_book_new();
	if (! e_book_load_uri (b, self_uri, TRUE, error)) {
		g_object_unref (b);
		return FALSE;
	}

	if (! e_book_get_contact (b, self_uid,
				  contact, error)) {
		g_object_unref (b);
		return FALSE;
	}

	if (book)
		*book = b;
	else
		g_object_unref (b);
	return TRUE;
#endif
}

gboolean
e_book_set_self (EBook *book, const char *id, GError **error)
{
}



gboolean
e_book_get_default_addressbook (EBook **book, GError **error)
{
	/* XXX for now just load the local ~/evolution/local/Contacts */
	char *path, *uri;
	gboolean rv;

	*book = e_book_new ();

	path = g_build_filename (g_get_home_dir (),
				 "evolution/local/Contacts",
				 NULL);
	uri = g_strdup_printf ("file://%s", path);
	g_free (path);

	rv = e_book_load_uri (*book, uri, FALSE, error);

	g_free (uri);

	if (!rv) {
		g_object_unref (*book);
		*book = NULL;
	}

	return rv;
#if notyet
	EConfigListener *listener = e_config_listener_new ();
	ESourceList *sources = ...;
	ESource *default_source;

	default_source = e_source_list_peek_source_by_uid (sources,
							   "default_");
#endif
}

#if notyet
ESourceList*
e_book_get_addressbooks (GError **error)
{
}
#endif


static void*
startup_mainloop (void *arg)
{
	bonobo_main();
	return NULL;
}

/* one-time start up for libebook */
static void
e_book_activate()
{
	static GStaticMutex e_book_lock = G_STATIC_MUTEX_INIT;
	static gboolean activated = FALSE;

	g_static_mutex_lock (&e_book_lock);
	if (!activated) {
		pthread_t ebook_mainloop_thread;
		activated = TRUE;
		pthread_create(&ebook_mainloop_thread, NULL, startup_mainloop, NULL);
	}
	g_static_mutex_unlock (&e_book_lock);
}



EBook*
e_book_new (void)
{
	e_book_activate ();
	return g_object_new (E_TYPE_BOOK, NULL);
}


static void
e_book_init (EBook *book)
{
	book->priv             = g_new0 (EBookPrivate, 1);
	book->priv->load_state = E_BOOK_URI_NOT_LOADED;
	book->priv->uri        = NULL;
	book->priv->mutex      = e_mutex_new (E_MUTEX_REC);
}

static void
e_book_dispose (GObject *object)
{
	EBook             *book = E_BOOK (object);

	if (book->priv) {
		CORBA_Environment  ev;
		GList *l;

		if (book->priv->comp_listener) {
			g_signal_handler_disconnect (book->priv->comp_listener, book->priv->died_signal);
			g_object_unref (book->priv->comp_listener);
			book->priv->comp_listener = NULL;
		}

		if (book->priv->load_state == E_BOOK_URI_LOADED)
			e_book_unload_uri (book, NULL);

		CORBA_exception_init (&ev);

		for (l = book->priv->book_factories; l; l = l->next) {
			CORBA_Object_release ((CORBA_Object)l->data, &ev);
			if (ev._major != CORBA_NO_EXCEPTION) {
				g_warning ("EBook: Exception while releasing BookFactory\n");

				CORBA_exception_free (&ev);
				CORBA_exception_init (&ev);
			}
		}
		
		CORBA_exception_free (&ev);

		if (book->priv->listener) {
			g_signal_handler_disconnect (book->priv->listener, book->priv->listener_signal);
			bonobo_object_unref (book->priv->listener);
			book->priv->listener = NULL;
		}
		
		g_free (book->priv->cap);

		g_free (book->priv->uri);

		g_free (book->priv);
		book->priv = NULL;
	}

	if (G_OBJECT_CLASS (parent_class)->dispose)
		G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
e_book_class_init (EBookClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	parent_class = g_type_class_ref (G_TYPE_OBJECT);

	e_book_signals [WRITABLE_STATUS] =
		g_signal_new ("writable_status",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (EBookClass, writable_status),
			      NULL, NULL,
			      e_book_marshal_NONE__BOOL,
			      G_TYPE_NONE, 1,
			      G_TYPE_BOOLEAN);

	e_book_signals [BACKEND_DIED] =
		g_signal_new ("backend_died",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (EBookClass, backend_died),
			      NULL, NULL,
			      e_book_marshal_NONE__NONE,
			      G_TYPE_NONE, 0);

	object_class->dispose = e_book_dispose;
}

/**
 * e_book_get_type:
 */
GType
e_book_get_type (void)
{
	static GType type = 0;

	if (! type) {
		GTypeInfo info = {
			sizeof (EBookClass),
			NULL, /* base_class_init */
			NULL, /* base_class_finalize */
			(GClassInitFunc)  e_book_class_init,
			NULL, /* class_finalize */
			NULL, /* class_data */
			sizeof (EBook),
			0,    /* n_preallocs */
			(GInstanceInitFunc) e_book_init
		};

		type = g_type_register_static (G_TYPE_OBJECT, "EBook", &info, 0);
	}

	return type;
}
