/* Evolution calendar - iCalendar file backend
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 * Copyright (C) 2003 Gergő Érdi
 *
 * This library 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.
 *
 * This library 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 this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Federico Mena-Quintero <federico@ximian.com>
 *          Rodrigo Moya <rodrigo@ximian.com>
 *          Gergő Érdi <cactus@cactus.rulez.org>
 */

#include "evolution-data-server-config.h"

#include <string.h>

#include "e-cal-backend-contacts.h"

#include <glib/gi18n-lib.h>

#include <libebook/libebook.h>
#include <libedataserver/libedataserver.h>

#define EC_ERROR(_code) e_client_error_create (_code, NULL)
#define ECC_ERROR(_code) e_cal_client_error_create (_code, NULL)

/* Private part of the ECalBackendContacts structure */
struct _ECalBackendContactsPrivate {

	GRecMutex rec_mutex;		/* guards 'addressbooks' */
	GHashTable   *addressbooks;	/* UID -> BookRecord */
	gboolean      addressbook_loaded;

	GHashTable *tracked_contacts;	/* UID -> ContactRecord */
	GRecMutex tracked_contacts_lock;

	/* properties related to track alarm settings for this backend */
	GSettings *settings;
	guint notifyid;
	guint update_alarms_id;
	gboolean alarm_enabled;
	gint alarm_interval;
	ECalIntervalUnits alarm_units;

	ESourceRegistryWatcher *registry_watcher;
};

typedef struct _BookRecord {
	volatile gint ref_count;

	GMutex lock;
	ECalBackendContacts *cbc;
	EBookClient *book_client;
	EBookClientView *book_view;
	GCancellable *cancellable;
} BookRecord;

typedef struct _ContactRecord {
	ECalBackendContacts *cbc;
	EBookClient *book_client; /* where it comes from */
	EContact *contact;
	ECalComponent *comp_anniversary;
	ECalComponent *comp_birthday;
	ECalComponent *comp_deathday;
} ContactRecord;

G_DEFINE_TYPE_WITH_PRIVATE (
	ECalBackendContacts,
	e_cal_backend_contacts,
	E_TYPE_CAL_BACKEND_SYNC)

#define d(x)

#define ANNIVERSARY_UID_EXT "-anniversary"
#define BIRTHDAY_UID_EXT   "-birthday"
#define DEATHDAY_UID_EXT "-deathday"

static ECalComponent *
		create_birthday			(ECalBackendContacts *cbc,
						 const gchar *base_uid,
						 EContactDate *date,
						 const gchar *name);
static ECalComponent *
		create_anniversary		(ECalBackendContacts *cbc,
						 const gchar *base_uid,
						 EContactDate *date,
						 const gchar *name);
static ECalComponent *
		create_deathday			(ECalBackendContacts *cbc,
						 const gchar *base_uid,
						 EContactDate *date,
						 const gchar *name);
static void	contacts_modified_cb		(EBookClientView *book_view,
						 const GSList *contacts,
						 gpointer user_data);
static void	contacts_added_cb		(EBookClientView *book_view,
						 const GSList *contacts,
						 gpointer user_data);
static void	contacts_removed_cb		(EBookClientView *book_view,
						 const GSList *contact_ids,
						 gpointer user_data);
static void	e_cal_backend_contacts_add_timezone
						(ECalBackendSync *backend,
						 EDataCal *cal,
						 GCancellable *cancellable,
						 const gchar *tzobj,
						 GError **perror);
static void	setup_alarm			(ECalBackendContacts *cbc,
						 ECalComponent *comp);
static void	book_client_connected_cb	(GObject *source_object,
						 GAsyncResult *result,
						 gpointer user_data);

static gboolean
remove_by_book (gpointer key,
                gpointer value,
                gpointer user_data)
{
	ContactRecord *cr = value;
	EBookClient *book_client = user_data;

	return (cr && cr->book_client == book_client);
}

static void
create_book_record (ECalBackendContacts *cbc,
                    ESource *source)
{
	BookRecord *br;

	br = g_slice_new0 (BookRecord);
	br->ref_count = 1;
	g_mutex_init (&br->lock);
	br->cbc = g_object_ref (cbc);
	br->cancellable = g_cancellable_new ();

	e_book_client_connect (source, 30, br->cancellable, book_client_connected_cb, br);
}

static BookRecord *
book_record_ref (BookRecord *br)
{
	g_return_val_if_fail (br != NULL, NULL);
	g_return_val_if_fail (br->ref_count > 0, NULL);

	g_atomic_int_inc (&br->ref_count);

	return br;
}

static void
book_record_unref (BookRecord *br)
{
	g_return_if_fail (br != NULL);
	g_return_if_fail (br->ref_count > 0);

	if (g_atomic_int_dec_and_test (&br->ref_count)) {
		g_cancellable_cancel (br->cancellable);

		if (br->book_client) {
			g_rec_mutex_lock (&br->cbc->priv->tracked_contacts_lock);
			g_hash_table_foreach_remove (
				br->cbc->priv->tracked_contacts,
				remove_by_book, br->book_client);
			g_rec_mutex_unlock (&br->cbc->priv->tracked_contacts_lock);
		}

		g_mutex_lock (&br->lock);

		g_clear_object (&br->cbc);
		g_clear_object (&br->cancellable);
		g_clear_object (&br->book_client);
		g_clear_object (&br->book_view);

		g_mutex_unlock (&br->lock);
		g_mutex_clear (&br->lock);

		g_slice_free (BookRecord, br);
	}
}

static void
cancel_and_unref_book_record (BookRecord *br)
{
	g_return_if_fail (br != NULL);

	if (br->cancellable)
		g_cancellable_cancel (br->cancellable);
	book_record_unref (br);
}

static void
book_record_set_book_view (BookRecord *br,
                           EBookClientView *book_view)
{
	g_return_if_fail (br != NULL);

	g_mutex_lock (&br->lock);

	if (book_view != NULL)
		g_object_ref (book_view);

	if (br->book_view != NULL)
		g_object_unref (br->book_view);

	br->book_view = book_view;

	g_mutex_unlock (&br->lock);
}

static void
cal_backend_contacts_insert_book_record (ECalBackendContacts *cbc,
                                         ESource *source,
                                         BookRecord *br)
{
	g_rec_mutex_lock (&cbc->priv->rec_mutex);

	g_hash_table_insert (
		cbc->priv->addressbooks,
		g_object_ref (source),
		book_record_ref (br));

	g_rec_mutex_unlock (&cbc->priv->rec_mutex);
}

static gboolean
cal_backend_contacts_remove_book_record (ECalBackendContacts *cbc,
                                         ESource *source)
{
	gboolean removed;

	g_rec_mutex_lock (&cbc->priv->rec_mutex);

	removed = g_hash_table_remove (cbc->priv->addressbooks, source);

	g_rec_mutex_unlock (&cbc->priv->rec_mutex);

	return removed;
}

static gpointer
book_record_get_view_thread (gpointer user_data)
{
	BookRecord *br;
	EBookQuery *query;
	EBookClientView *book_view = NULL;
	gchar *query_sexp;
	GError *error = NULL;

	br = user_data;
	g_return_val_if_fail (br != NULL, NULL);

	book_record_set_book_view (br, NULL);

	query = e_book_query_andv (
		e_book_query_orv (
			e_book_query_field_exists (E_CONTACT_FILE_AS),
			e_book_query_field_exists (E_CONTACT_FULL_NAME),
			e_book_query_field_exists (E_CONTACT_GIVEN_NAME),
			e_book_query_field_exists (E_CONTACT_NICKNAME),
			NULL),
		e_book_query_orv (
			e_book_query_field_exists (E_CONTACT_BIRTH_DATE),
			e_book_query_field_exists (E_CONTACT_ANNIVERSARY),
			e_book_query_field_exists (E_CONTACT_DEATHDATE),
			NULL),
		NULL);
	query_sexp = e_book_query_to_string (query);
	e_book_query_unref (query);

	if (!e_book_client_get_view_sync (br->book_client, query_sexp, &book_view, br->cancellable, &error)) {

		if (!error)
			error = g_error_new_literal (
				E_CLIENT_ERROR,
				E_CLIENT_ERROR_OTHER_ERROR,
				_("Unknown error"));
	}

	/* Sanity check. */
	g_return_val_if_fail (
		((book_view != NULL) && (error == NULL)) ||
		((book_view == NULL) && (error != NULL)), NULL);

	if (error != NULL) {
		ESource *source;

		source = e_client_get_source (E_CLIENT (br->book_client));

		g_warning (
			"%s: Failed to get book view on '%s': %s",
			G_STRFUNC, e_source_get_display_name (source),
			error->message);

		g_clear_error (&error);

		goto exit;
	}

	g_signal_connect (
		book_view, "objects-added",
		G_CALLBACK (contacts_added_cb), br->cbc);
	g_signal_connect (
		book_view, "objects-removed",
		G_CALLBACK (contacts_removed_cb), br->cbc);
	g_signal_connect (
		book_view, "objects-modified",
		G_CALLBACK (contacts_modified_cb), br->cbc);

	e_book_client_view_start (book_view, NULL);

	book_record_set_book_view (br, book_view);

	g_object_unref (book_view);

exit:
	g_free (query_sexp);

	book_record_unref (br);

	return NULL;
}

static void
source_unset_last_credentials_required_args_cb (GObject *source_object,
						GAsyncResult *result,
						gpointer user_data)
{
	GError *error = NULL;

	if (!e_source_unset_last_credentials_required_arguments_finish (E_SOURCE (source_object), result, &error))
		g_debug ("%s: Failed to unset last credentials required arguments for %s: %s",
			G_STRFUNC, e_source_get_display_name (E_SOURCE (source_object)), error ? error->message : "Unknown error");

	g_clear_error (&error);
}

static void
book_client_connected_cb (GObject *source_object,
                          GAsyncResult *result,
                          gpointer user_data)
{
	EClient *client;
	ESource *source;
	GThread *thread;
	BookRecord *br = user_data;
	GError *error = NULL;

	g_return_if_fail (br != NULL);

	client = e_book_client_connect_finish (result, &error);

	/* Sanity check. */
	g_return_if_fail (
		((client != NULL) && (error == NULL)) ||
		((client == NULL) && (error != NULL)));

	if (error != NULL) {
		if (E_IS_BOOK_CLIENT (source_object)) {
			source = e_client_get_source (E_CLIENT (source_object));
			if (source)
				e_source_unset_last_credentials_required_arguments (source, NULL,
					source_unset_last_credentials_required_args_cb, NULL);
		}

		g_warning ("%s: %s", G_STRFUNC, error->message);
		g_error_free (error);
		book_record_unref (br);
		return;
	}

	source = e_client_get_source (client);
	br->book_client = g_object_ref (E_BOOK_CLIENT (client));
	cal_backend_contacts_insert_book_record (br->cbc, source, br);

	/* Let it consume the 'br' reference */
	thread = g_thread_new (NULL, book_record_get_view_thread, br);
	g_thread_unref (thread);

	g_object_unref (client);
}

/* ContactRecord methods */
static ContactRecord *
contact_record_new (ECalBackendContacts *cbc,
                    EBookClient *book_client,
                    EContact *contact)
{
	EContactDate *anniversary, *birthday, *deathday;
	ContactRecord *cr;
	const gchar *base_uid, *name;

	anniversary = e_contact_get (contact, E_CONTACT_ANNIVERSARY);
	birthday = e_contact_get (contact, E_CONTACT_BIRTH_DATE);
	deathday = e_contact_get (contact, E_CONTACT_DEATHDATE);

	if (!anniversary && !birthday && !deathday)
		return NULL;

	base_uid = e_contact_get_const (contact, E_CONTACT_UID);

	name = e_contact_get_const (contact, E_CONTACT_FILE_AS);
	if (!name || !*name)
		name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
	if (!name || !*name)
		name = e_contact_get_const (contact, E_CONTACT_NICKNAME);
	if (!name)
		name = "";

	cr = g_new0 (ContactRecord, 1);
	cr->cbc = cbc;
	cr->book_client = book_client;
	cr->contact = contact;
	cr->comp_anniversary = create_anniversary (cbc, base_uid, anniversary, name);
	cr->comp_birthday = create_birthday (cbc, base_uid, birthday, name);
	cr->comp_deathday = create_deathday (cbc, base_uid, deathday, name);

	g_clear_pointer (&anniversary, e_contact_date_free);
	g_clear_pointer (&birthday, e_contact_date_free);
	g_clear_pointer (&deathday, e_contact_date_free);

	if (cr->comp_anniversary)
		e_cal_backend_notify_component_created (E_CAL_BACKEND (cbc), cr->comp_anniversary);

	if (cr->comp_birthday)
		e_cal_backend_notify_component_created (E_CAL_BACKEND (cbc), cr->comp_birthday);

	if (cr->comp_deathday)
		e_cal_backend_notify_component_created (E_CAL_BACKEND (cbc), cr->comp_deathday);

	g_object_ref (G_OBJECT (contact));

	return cr;
}

static void
contact_record_free (ContactRecord *cr)
{
	ECalComponentId *id;

	g_object_unref (G_OBJECT (cr->contact));

	/* Remove the birthday event */
	if (cr->comp_birthday) {
		id = e_cal_component_get_id (cr->comp_birthday);
		e_cal_backend_notify_component_removed (E_CAL_BACKEND (cr->cbc), id, cr->comp_birthday, NULL);

		e_cal_component_id_free (id);
		g_object_unref (G_OBJECT (cr->comp_birthday));
	}

	/* Remove the anniversary event */
	if (cr->comp_anniversary) {
		id = e_cal_component_get_id (cr->comp_anniversary);

		e_cal_backend_notify_component_removed (E_CAL_BACKEND (cr->cbc), id, cr->comp_anniversary, NULL);

		e_cal_component_id_free (id);
		g_object_unref (G_OBJECT (cr->comp_anniversary));
	}

	if (cr->comp_deathday) {
		id = e_cal_component_get_id (cr->comp_deathday);

		e_cal_backend_notify_component_removed (E_CAL_BACKEND (cr->cbc), id, cr->comp_deathday, NULL);

		e_cal_component_id_free (id);
		g_object_unref (G_OBJECT (cr->comp_deathday));
	}

	g_free (cr);
}

/* ContactRecordCB methods */
typedef struct _ContactRecordCB {
        ECalBackendContacts *cbc;
        ECalBackendSExp     *sexp;
	gboolean	     as_string;
        GSList              *result;
} ContactRecordCB;

static ContactRecordCB *
contact_record_cb_new (ECalBackendContacts *cbc,
                       ECalBackendSExp *sexp,
                       gboolean as_string)
{
	ContactRecordCB *cb_data = g_new (ContactRecordCB, 1);

	cb_data->cbc = cbc;
	cb_data->sexp = sexp;
	cb_data->as_string = as_string;
	cb_data->result = NULL;

	return cb_data;
}

static void
contact_record_cb_free (ContactRecordCB *cb_data,
                        gboolean can_free_result)
{
	if (can_free_result) {
		if (cb_data->as_string)
			g_slist_foreach (cb_data->result, (GFunc) g_free, NULL);
		g_slist_free (cb_data->result);
	}

	g_free (cb_data);
}

static void
contact_record_cb (gpointer key,
                   gpointer value,
                   gpointer user_data)
{
	ETimezoneCache *timezone_cache;
	ContactRecordCB *cb_data = user_data;
	ContactRecord   *record = value;
	gpointer data;

	timezone_cache = E_TIMEZONE_CACHE (cb_data->cbc);

	if (record->comp_birthday && e_cal_backend_sexp_match_comp (cb_data->sexp, record->comp_birthday, timezone_cache)) {
		if (cb_data->as_string)
			data = e_cal_component_get_as_string (record->comp_birthday);
		else
			data = record->comp_birthday;

		cb_data->result = g_slist_prepend (cb_data->result, data);
	}

	if (record->comp_anniversary && e_cal_backend_sexp_match_comp (cb_data->sexp, record->comp_anniversary, timezone_cache)) {
		if (cb_data->as_string)
			data = e_cal_component_get_as_string (record->comp_anniversary);
		else
			data = record->comp_anniversary;

		cb_data->result = g_slist_prepend (cb_data->result, data);
	}

	if (record->comp_deathday && e_cal_backend_sexp_match_comp (cb_data->sexp, record->comp_deathday, timezone_cache)) {
		if (cb_data->as_string)
			data = e_cal_component_get_as_string (record->comp_deathday);
		else
			data = record->comp_deathday;

		cb_data->result = g_slist_prepend (cb_data->result, data);
	}
}

static gboolean
ecb_contacts_watcher_filter_cb (ESourceRegistryWatcher *watcher,
				ESource *source,
				gpointer user_data)
{
	ESourceContacts *extension;

	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);

	extension = e_source_get_extension (source, E_SOURCE_EXTENSION_CONTACTS_BACKEND);

	return extension && e_source_contacts_get_include_me (extension);
}

static void
ecb_contacts_watcher_appeared_cb (ESourceRegistryWatcher *watcher,
				  ESource *source,
				  gpointer user_data)
{
	ECalBackendContacts *cbcontacts = user_data;

	g_return_if_fail (E_IS_SOURCE (source));
	g_return_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbcontacts));

	cal_backend_contacts_remove_book_record (cbcontacts, source);
	create_book_record (cbcontacts, source);
}

static void
ecb_contacts_watcher_disappeared_cb (ESourceRegistryWatcher *watcher,
				     ESource *source,
				     gpointer user_data)
{
	ECalBackendContacts *cbcontacts = user_data;

	g_return_if_fail (E_IS_SOURCE (source));
	g_return_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbcontacts));

	cal_backend_contacts_remove_book_record (cbcontacts, source);
}

static gboolean
cal_backend_contacts_load_sources (gpointer user_data)
{
	ECalBackendContacts *cbcontacts = user_data;

	g_return_val_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbcontacts), FALSE);

	e_source_registry_watcher_reclaim (cbcontacts->priv->registry_watcher);

	return FALSE;
}

/************************************************************************************/

static void
contacts_modified_cb (EBookClientView *book_view,
                      const GSList *contacts,
                      gpointer user_data)
{
	ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
	EBookClient *book_client;
	const GSList *ii;

	book_client = e_book_client_view_ref_client (book_view);
	if (book_client == NULL)
		return;

	g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);

	for (ii = contacts; ii; ii = ii->next) {
		EContact *contact = E_CONTACT (ii->data);
		const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID);
		ContactRecord *cr;

		/* Because this is a change of contact, then always remove old tracked data
		 * and if possible, add with (possibly) new values.
		*/
		g_hash_table_remove (cbc->priv->tracked_contacts, (gchar *) uid);

		cr = contact_record_new (cbc, book_client, contact);
		if (cr)
			g_hash_table_insert (cbc->priv->tracked_contacts, g_strdup (uid), cr);
	}

	g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);

	g_object_unref (book_client);
}

static void
contacts_added_cb (EBookClientView *book_view,
                   const GSList *contacts,
                   gpointer user_data)
{
	ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
	EBookClient *book_client;
	const GSList *ii;

	book_client = e_book_client_view_ref_client (book_view);
	if (book_client == NULL)
		return;

	g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);

	/* See if any new contacts have BIRTHDAY or ANNIVERSARY fields */
	for (ii = contacts; ii; ii = ii->next) {
		EContact *contact = E_CONTACT (ii->data);
		ContactRecord *cr;

		cr = contact_record_new (cbc, book_client, contact);
		if (cr) {
			const gchar *uid = e_contact_get_const (contact, E_CONTACT_UID);

			g_hash_table_insert (cbc->priv->tracked_contacts, g_strdup (uid), cr);
		}
	}

	g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);

	g_object_unref (book_client);
}

static void
contacts_removed_cb (EBookClientView *book_view,
                     const GSList *contact_ids,
                     gpointer user_data)
{
	ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (user_data);
	const GSList *ii;

	g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);

	/* Stop tracking these */
	for (ii = contact_ids; ii; ii = ii->next)
		g_hash_table_remove (cbc->priv->tracked_contacts, ii->data);

	g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);
}

/************************************************************************************/
static ICalTime *
cdate_to_icaltime (EContactDate *cdate)
{
	ICalTime *ret = i_cal_time_new_null_time ();

	i_cal_time_set_year (ret, cdate->year);
	i_cal_time_set_month (ret, cdate->month);
	i_cal_time_set_day (ret, cdate->day);
	i_cal_time_set_hour (ret, 0);
	i_cal_time_set_minute (ret, 0);
	i_cal_time_set_second (ret, 0);
	i_cal_time_set_timezone (ret, NULL);
	i_cal_time_set_is_daylight (ret, FALSE);
	i_cal_time_set_is_date (ret, TRUE);

	return ret;
}

static void
manage_comp_alarm_update (ECalBackendContacts *cbc,
                          ECalComponent *comp)
{
	gchar *old_comp_str, *new_comp_str;
	ECalComponent *old_comp;

	g_return_if_fail (cbc != NULL);
	g_return_if_fail (comp != NULL);

	old_comp = e_cal_component_clone (comp);
	setup_alarm (cbc, comp);

	old_comp_str = e_cal_component_get_as_string (old_comp);
	new_comp_str = e_cal_component_get_as_string (comp);

	/* check if component changed and notify if so */
	if (old_comp_str && new_comp_str && !g_str_equal (old_comp_str, new_comp_str))
		e_cal_backend_notify_component_modified (E_CAL_BACKEND (cbc), old_comp, comp);

	g_free (old_comp_str);
	g_free (new_comp_str);
	g_object_unref (old_comp);
}

static void
update_alarm_cb (gpointer key,
                 gpointer value,
                 gpointer user_data)
{
	ECalBackendContacts *cbc = user_data;
	ContactRecord   *record = value;

	g_return_if_fail (cbc != NULL);
	g_return_if_fail (record != NULL);

	if (record->comp_birthday)
		manage_comp_alarm_update (cbc, record->comp_birthday);

	if (record->comp_anniversary)
		manage_comp_alarm_update (cbc, record->comp_anniversary);

	if (record->comp_deathday)
		manage_comp_alarm_update (cbc, record->comp_deathday);
}

static gboolean
update_tracked_alarms_cb (gpointer user_data)
{
	ECalBackendContacts *cbc = user_data;

	g_return_val_if_fail (cbc != NULL, FALSE);

	g_rec_mutex_lock (&cbc->priv->tracked_contacts_lock);
	g_hash_table_foreach (cbc->priv->tracked_contacts, update_alarm_cb, cbc);
	g_rec_mutex_unlock (&cbc->priv->tracked_contacts_lock);

	cbc->priv->update_alarms_id = 0;

	return FALSE;
}

#define BA_CONF_ENABLED		"contacts-reminder-enabled"
#define BA_CONF_INTERVAL	"contacts-reminder-interval"
#define BA_CONF_UNITS		"contacts-reminder-units"

static void
alarm_config_changed_cb (GSettings *settings,
                         const gchar *key,
                         gpointer user_data)
{
	ECalBackendContacts *cbc = user_data;

	g_return_if_fail (cbc != NULL);

	if (g_strcmp0 (key, BA_CONF_ENABLED) != 0 &&
	    g_strcmp0 (key, BA_CONF_INTERVAL) != 0 &&
	    g_strcmp0 (key, BA_CONF_UNITS) != 0)
		return;

	setup_alarm (cbc, NULL);

	if (!cbc->priv->update_alarms_id)
		cbc->priv->update_alarms_id = g_idle_add (update_tracked_alarms_cb, cbc);
}

/* When called with NULL, then just refresh local static variables on setup change from the user. */
static void
setup_alarm (ECalBackendContacts *cbc,
             ECalComponent *comp)
{
	ECalComponentAlarm *alarm;
	ECalComponentAlarmTrigger *trigger;
	ECalComponentText *summary;
	ICalDuration *duration;

	g_return_if_fail (cbc != NULL);

	if (!comp || cbc->priv->alarm_interval == -1) {
		if (cbc->priv->alarm_interval == -1) {
			/* initial setup, hook callback for changes too */
			cbc->priv->notifyid = g_signal_connect (cbc->priv->settings,
				"changed", G_CALLBACK (alarm_config_changed_cb), cbc);
		}

		cbc->priv->alarm_enabled = g_settings_get_boolean (cbc->priv->settings, BA_CONF_ENABLED);
		cbc->priv->alarm_interval = g_settings_get_int (cbc->priv->settings, BA_CONF_INTERVAL);
		cbc->priv->alarm_units = g_settings_get_enum (cbc->priv->settings, BA_CONF_UNITS);

		if (cbc->priv->alarm_interval <= 0)
			cbc->priv->alarm_interval = 1;

		if (!comp)
			return;
	}

	/* ensure no alarms left */
	e_cal_component_remove_all_alarms (comp);

	/* do not want alarms, return */
	if (!cbc->priv->alarm_enabled)
		return;

	alarm = e_cal_component_alarm_new ();
	summary = e_cal_component_get_summary (comp);
	e_cal_component_alarm_take_description (alarm, summary);
	e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);

	duration = i_cal_duration_new_null_duration ();
	i_cal_duration_set_is_neg (duration, TRUE);

	switch (cbc->priv->alarm_units) {
	case E_CAL_INTERVAL_UNIT_MINUTES:
		i_cal_duration_set_minutes (duration, cbc->priv->alarm_interval);
		break;

	case E_CAL_INTERVAL_UNIT_HOURS:
		i_cal_duration_set_hours (duration, cbc->priv->alarm_interval);
		break;

	case E_CAL_INTERVAL_UNIT_DAYS:
		i_cal_duration_set_days (duration, cbc->priv->alarm_interval);
		break;

	default:
		g_warning ("%s: wrong units %d\n", G_STRFUNC, cbc->priv->alarm_units);
		e_cal_component_alarm_free (alarm);
		return;
	}

	trigger = e_cal_component_alarm_trigger_new_relative (E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START, duration);

	g_object_unref (duration);

	e_cal_component_alarm_take_trigger (alarm, trigger);
	e_cal_component_add_alarm (comp, alarm);
	e_cal_component_alarm_free (alarm);
}

#undef BA_CONF_ENABLED
#undef BA_CONF_INTERVAL
#undef BA_CONF_UNITS

/* Contact -> Event creator */
static ECalComponent *
create_component (ECalBackendContacts *cbc,
                  const gchar *uid,
                  EContactDate *cdate,
                  const gchar *summary)
{
	ECalComponent *cal_comp;
	ECalComponentText *comp_summary;
	ECalComponentDateTime *dt;
	ICalComponent *icomp;
	ICalTime *itt;
	ICalRecurrence *rt;
	gchar *since_year;
	GSList *recur_list;
	gboolean is_leap_day;

	g_return_val_if_fail (E_IS_CAL_BACKEND_CONTACTS (cbc), NULL);

	if (!cdate)
		return NULL;

	icomp = i_cal_component_new (I_CAL_VEVENT_COMPONENT);

	if (cdate->year != 0) {
		since_year = g_strdup_printf ("%04d", cdate->year);
		e_cal_util_component_set_x_property (icomp, "X-EVOLUTION-SINCE-YEAR", since_year);
		g_free (since_year);
	}

	if (!cdate->year || !cdate->month || !cdate->day) {
		if (!cdate->year) {
			time_t tt = time (NULL);
			struct tm *tm;

			tm = localtime (&tt);
			if (tm)
				cdate->year = tm->tm_year + 1900; /* default to the current year */
			else
				cdate->year = 2025; /* when cannot get, then let it be some "random" value */
		}

		if (!cdate->month)
			cdate->month = 1; /* default to January */

		if (!cdate->day)
			cdate->day = 1; /* default to the first day of the month */
	}

	/* Create the event object */
	cal_comp = e_cal_component_new_from_icalcomponent (icomp);

	/* Set uid */
	d (g_message ("Creating UID: %s", uid));
	e_cal_component_set_uid (cal_comp, uid);

	/* Set all-day event's date from contact data */
	itt = cdate_to_icaltime (cdate);

	i_cal_time_normalize_inplace (itt);
	is_leap_day = i_cal_time_get_day (itt) == 29 && i_cal_time_get_month (itt) == 2;

	dt = e_cal_component_datetime_new_take (itt, NULL);
	e_cal_component_set_dtstart (cal_comp, dt);
	e_cal_component_datetime_free (dt);

	itt = cdate_to_icaltime (cdate);
	/* We have to add 1 day to DTEND, as it is not inclusive. */
	i_cal_time_adjust (itt, 1, 0, 0, 0);
	dt = e_cal_component_datetime_new_take (itt, NULL);
	e_cal_component_set_dtend (cal_comp, dt);
	e_cal_component_datetime_free (dt);

	/* Create yearly recurrence */
	rt = i_cal_recurrence_new ();
	i_cal_recurrence_set_freq (rt, I_CAL_YEARLY_RECURRENCE);
	i_cal_recurrence_set_interval (rt, 1);
	if (is_leap_day) {
		#ifdef HAVE_I_CAL_RECURRENCE_GET_BY
		i_cal_recurrence_resize_by_array (rt, I_CAL_BY_MONTH_DAY, 1);
		i_cal_recurrence_set_by (rt, I_CAL_BY_MONTH_DAY, 0, -1);
		#else
		i_cal_recurrence_set_by_month_day (rt, 0, -1);
		#endif
	}
	recur_list = g_slist_prepend (NULL, rt);
	e_cal_component_set_rrules (cal_comp, recur_list);
	g_slist_free_full (recur_list, g_object_unref);

	/* Create summary */
	comp_summary = e_cal_component_text_new (summary, NULL);
	e_cal_component_set_summary (cal_comp, comp_summary);
	e_cal_component_text_free (comp_summary);

	/* Set category and visibility */
	if (g_str_has_suffix (uid, ANNIVERSARY_UID_EXT))
		e_cal_component_set_categories (cal_comp, _("Anniversary"));
	else if (g_str_has_suffix (uid, BIRTHDAY_UID_EXT))
		e_cal_component_set_categories (cal_comp, _("Birthday"));
	else if (g_str_has_suffix (uid, DEATHDAY_UID_EXT))
		e_cal_component_set_categories (cal_comp, _("Deathday"));

	e_cal_component_set_classification (cal_comp, E_CAL_COMPONENT_CLASS_PRIVATE);

	/* Birthdays/anniversaries are shown as free time */
	e_cal_component_set_transparency (cal_comp, E_CAL_COMPONENT_TRANSP_TRANSPARENT);

	/* setup alarms if required */
	setup_alarm (cbc, cal_comp);

	/* Don't forget to call commit()! */
	e_cal_component_commit_sequence (cal_comp);

	return cal_comp;
}

static ECalComponent *
create_birthday (ECalBackendContacts *cbc,
		 const gchar *base_uid,
		 EContactDate *date,
		 const gchar *name)
{
	ECalComponent *cal_comp;
	gchar *summary;
	gchar *uid;

	if (!date)
		return NULL;

	uid = g_strdup_printf ("%s%s", base_uid, BIRTHDAY_UID_EXT);
	/* Translators: The %s is replaced with a contact name, used in the Contacts calendar, beside "Anniversary: %s" and "Death day: %s" strings */
	summary = g_strdup_printf (_("Birthday: %s"), name);

	cal_comp = create_component (cbc, uid, date, summary);

	g_free (uid);
	g_free (summary);

	return cal_comp;
}

static ECalComponent *
create_anniversary (ECalBackendContacts *cbc,
		    const gchar *base_uid,
		    EContactDate *date,
		    const gchar *name)
{
	ECalComponent *cal_comp;
	gchar *summary;
	gchar *uid;

	if (!date)
		return NULL;

	uid = g_strdup_printf ("%s%s", base_uid, ANNIVERSARY_UID_EXT);
	/* Translators: The %s is replaced with a contact name, used in the Contacts calendar, beside "Birthday: %s" and "Death day: %s" strings */
	summary = g_strdup_printf (_("Anniversary: %s"), name);

	cal_comp = create_component (cbc, uid, date, summary);

	g_free (uid);
	g_free (summary);

	return cal_comp;
}

static ECalComponent *
create_deathday (ECalBackendContacts *cbc,
		 const gchar *base_uid,
		 EContactDate *date,
		 const gchar *name)
{
	ECalComponent *cal_comp;
	gchar *summary;
	gchar *uid;

	if (!date)
		return NULL;

	uid = g_strdup_printf ("%s%s", base_uid, DEATHDAY_UID_EXT);
	/* Translators: The %s is replaced with a contact name, used in the Contacts calendar, beside "Birthday: %s" and "Anniversary: %s" strings */
	summary = g_strdup_printf (_("Death day: %s"), name);

	cal_comp = create_component (cbc, uid, date, summary);

	g_free (uid);
	g_free (summary);

	return cal_comp;
}

/************************************************************************************/
/* Calendar backend method implementations */

/* First the empty stubs */

static gchar *
e_cal_backend_contacts_get_backend_property (ECalBackend *backend,
                                             const gchar *prop_name)
{
	g_return_val_if_fail (prop_name != NULL, FALSE);

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
		return NULL;

	} else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
		   g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
		/* A contact backend has no particular email address associated
		 * with it (although that would be a useful feature some day).
		 */
		return NULL;

	} else if (g_str_equal (prop_name, E_CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
		return NULL;
	}

	/* Chain up to parent's method. */
	return E_CAL_BACKEND_CLASS (e_cal_backend_contacts_parent_class)->impl_get_backend_property (backend, prop_name);
}

static void
e_cal_backend_contacts_get_object (ECalBackendSync *backend,
                                   EDataCal *cal,
                                   GCancellable *cancellable,
                                   const gchar *uid,
                                   const gchar *rid,
                                   gchar **object,
                                   GError **perror)
{
	ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
	ECalBackendContactsPrivate *priv = cbc->priv;
	ContactRecord *record;
	gchar *real_uid;

	if (!uid) {
		g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
		return;
	} else if (g_str_has_suffix (uid, ANNIVERSARY_UID_EXT))
		real_uid = g_strndup (uid, strlen (uid) - strlen (ANNIVERSARY_UID_EXT));
	else if (g_str_has_suffix (uid, BIRTHDAY_UID_EXT))
		real_uid = g_strndup (uid, strlen (uid) - strlen (BIRTHDAY_UID_EXT));
	else if (g_str_has_suffix (uid, DEATHDAY_UID_EXT))
		real_uid = g_strndup (uid, strlen (uid) - strlen (DEATHDAY_UID_EXT));
	else {
		g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
		return;
	}

	g_rec_mutex_lock (&priv->tracked_contacts_lock);
	record = g_hash_table_lookup (priv->tracked_contacts, real_uid);
	g_free (real_uid);

	if (!record) {
		g_rec_mutex_unlock (&priv->tracked_contacts_lock);
		g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
		return;
	}

	if (record->comp_birthday && g_str_has_suffix (uid, BIRTHDAY_UID_EXT)) {
		*object = e_cal_component_get_as_string (record->comp_birthday);
		g_rec_mutex_unlock (&priv->tracked_contacts_lock);

		d (g_message ("Return birthday: %s", *object));
		return;
	}

	if (record->comp_anniversary && g_str_has_suffix (uid, ANNIVERSARY_UID_EXT)) {
		*object = e_cal_component_get_as_string (record->comp_anniversary);
		g_rec_mutex_unlock (&priv->tracked_contacts_lock);

		d (g_message ("Return anniversary: %s", *object));
		return;
	}

	if (record->comp_deathday && g_str_has_suffix (uid, DEATHDAY_UID_EXT)) {
		*object = e_cal_component_get_as_string (record->comp_deathday);
		g_rec_mutex_unlock (&priv->tracked_contacts_lock);

		d (g_message ("Return deathday: %s", *object));
		return;
	}

	g_rec_mutex_unlock (&priv->tracked_contacts_lock);

	d (g_message ("Returning nothing for uid: %s", uid));

	g_propagate_error (perror, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
}

static void
e_cal_backend_contacts_get_free_busy (ECalBackendSync *backend,
                                      EDataCal *cal,
                                      GCancellable *cancellable,
                                      const GSList *users,
                                      time_t start,
                                      time_t end,
                                      GSList **freebusy,
                                      GError **perror)
{
	/* Birthdays/anniversaries don't count as busy time */

	ICalComponent *vfb = i_cal_component_new_vfreebusy ();
	ICalTimezone *utc_zone = i_cal_timezone_get_utc_timezone ();
	ICalTime *itt;
	gchar *calobj;

	itt = i_cal_time_new_from_timet_with_zone (start, FALSE, utc_zone);
	i_cal_component_set_dtstart (vfb, itt);
	g_object_unref (itt);

	itt = i_cal_time_new_from_timet_with_zone (end, FALSE, utc_zone);
	i_cal_component_set_dtend (vfb, itt);
	g_object_unref (itt);

	calobj = i_cal_component_as_ical_string (vfb);
	*freebusy = g_slist_append (NULL, calobj);
	g_object_unref (vfb);
}

static void
e_cal_backend_contacts_receive_objects (ECalBackendSync *backend,
                                        EDataCal *cal,
                                        GCancellable *cancellable,
                                        const gchar *calobj,
					ECalOperationFlags opflags,
                                        GError **perror)
{
	g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_PERMISSION_DENIED));
}

static void
e_cal_backend_contacts_send_objects (ECalBackendSync *backend,
                                     EDataCal *cal,
                                     GCancellable *cancellable,
                                     const gchar *calobj,
				     ECalOperationFlags opflags,
                                     GSList **users,
                                     gchar **modified_calobj,
                                     GError **perror)
{
	*users = NULL;
	*modified_calobj = NULL;
	/* TODO: Investigate this */
	g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_PERMISSION_DENIED));
}

/* Then the real implementations */

static void
e_cal_backend_contacts_notify_online_cb (ECalBackend *backend,
                                         GParamSpec *pspec)
{
	e_cal_backend_set_writable (backend, FALSE);
}

static void
e_cal_backend_contacts_open (ECalBackendSync *backend,
                             EDataCal *cal,
                             GCancellable *cancellable,
                             GError **perror)
{
	ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
	ECalBackendContactsPrivate *priv = cbc->priv;

	if (priv->addressbook_loaded)
		return;

	/* Local source is always connected. */
	e_source_set_connection_status (e_backend_get_source (E_BACKEND (backend)),
		E_SOURCE_CONNECTION_STATUS_CONNECTED);

	priv->addressbook_loaded = TRUE;
	e_cal_backend_set_writable (E_CAL_BACKEND (backend), FALSE);
	e_backend_set_online (E_BACKEND (backend), TRUE);
}

/* Add_timezone handler for the file backend */
static void
e_cal_backend_contacts_add_timezone (ECalBackendSync *backend,
                                     EDataCal *cal,
                                     GCancellable *cancellable,
                                     const gchar *tzobj,
                                     GError **error)
{
	ICalComponent *tz_comp;
	ICalTimezone *zone;

	tz_comp = i_cal_parser_parse_string (tzobj);
	if (!tz_comp) {
		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
		return;
	}

	if (i_cal_component_isa (tz_comp) != I_CAL_VTIMEZONE_COMPONENT) {
		g_object_unref (tz_comp);
		g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_INVALID_OBJECT));
		return;
	}

	zone = i_cal_timezone_new ();
	if (i_cal_timezone_set_component (zone, tz_comp))
		e_timezone_cache_add_timezone (E_TIMEZONE_CACHE (backend), zone);

	g_object_unref (zone);
	g_object_unref (tz_comp);
}

static void
e_cal_backend_contacts_get_object_list (ECalBackendSync *backend,
                                        EDataCal *cal,
                                        GCancellable *cancellable,
                                        const gchar *sexp_string,
                                        GSList **objects,
                                        GError **perror)
{
	ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
	ECalBackendContactsPrivate *priv = cbc->priv;
	ECalBackendSExp *sexp = e_cal_backend_sexp_new (sexp_string);
	ContactRecordCB *cb_data;

	if (!sexp) {
		g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY));
		return;
	}

	cb_data = contact_record_cb_new (cbc, sexp, TRUE);

	g_rec_mutex_lock (&priv->tracked_contacts_lock);
	g_hash_table_foreach (priv->tracked_contacts, contact_record_cb, cb_data);
	g_rec_mutex_unlock (&priv->tracked_contacts_lock);

	*objects = cb_data->result;

	contact_record_cb_free (cb_data, FALSE);
}

static void
e_cal_backend_contacts_start_view (ECalBackend *backend,
                                   EDataCalView *query)
{
	ECalBackendContacts *cbc = E_CAL_BACKEND_CONTACTS (backend);
	ECalBackendContactsPrivate *priv = cbc->priv;
	ECalBackendSExp *sexp;
	ContactRecordCB *cb_data;

	sexp = e_data_cal_view_get_sexp (query);
	if (!sexp) {
		GError *error = EC_ERROR (E_CLIENT_ERROR_INVALID_QUERY);
		e_data_cal_view_notify_complete (query, error);
		g_error_free (error);
		return;
	}

	cb_data = contact_record_cb_new (cbc, sexp, FALSE);

	g_rec_mutex_lock (&priv->tracked_contacts_lock);
	g_hash_table_foreach (priv->tracked_contacts, contact_record_cb, cb_data);
	e_data_cal_view_notify_components_added (query, cb_data->result);
	g_rec_mutex_unlock (&priv->tracked_contacts_lock);

	contact_record_cb_free (cb_data, TRUE);

	e_data_cal_view_notify_complete (query, NULL /* Success */);
}

/***********************************************************************************
 */

/* Finalize handler for the contacts backend */
static void
e_cal_backend_contacts_finalize (GObject *object)
{
	ECalBackendContactsPrivate *priv;

	priv = E_CAL_BACKEND_CONTACTS (object)->priv;

	if (priv->update_alarms_id) {
		g_source_remove (priv->update_alarms_id);
		priv->update_alarms_id = 0;
	}

	g_hash_table_destroy (priv->addressbooks);
	g_hash_table_destroy (priv->tracked_contacts);
	if (priv->notifyid)
		g_signal_handler_disconnect (priv->settings, priv->notifyid);

	g_object_unref (priv->settings);
	g_rec_mutex_clear (&priv->rec_mutex);
	g_rec_mutex_clear (&priv->tracked_contacts_lock);

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

static void
e_cal_backend_contacts_dispose (GObject *object)
{
	ECalBackendContacts *cbcontacts = E_CAL_BACKEND_CONTACTS (object);

	g_clear_object (&cbcontacts->priv->registry_watcher);

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

static void
e_cal_backend_contacts_constructed (GObject *object)
{
	ECalBackendContacts *cbcontacts = E_CAL_BACKEND_CONTACTS (object);
	ESourceRegistry *registry;

	/* Chain up to parent's constructed() method. */
	G_OBJECT_CLASS (e_cal_backend_contacts_parent_class)->constructed (object);

	registry = e_cal_backend_get_registry (E_CAL_BACKEND (cbcontacts));

	cbcontacts->priv->registry_watcher = e_source_registry_watcher_new (registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);

	g_signal_connect (cbcontacts->priv->registry_watcher, "filter",
		G_CALLBACK (ecb_contacts_watcher_filter_cb), cbcontacts);

	g_signal_connect (cbcontacts->priv->registry_watcher, "appeared",
		G_CALLBACK (ecb_contacts_watcher_appeared_cb), cbcontacts);

	g_signal_connect (cbcontacts->priv->registry_watcher, "disappeared",
		G_CALLBACK (ecb_contacts_watcher_disappeared_cb), cbcontacts);

	/* Load address book sources from an idle callback
	 * to avoid deadlocking e_data_factory_ref_backend(). */
	g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, cal_backend_contacts_load_sources,
		g_object_ref (object), g_object_unref);
}

/* Object initialization function for the contacts backend */
static void
e_cal_backend_contacts_init (ECalBackendContacts *cbc)
{
	cbc->priv = e_cal_backend_contacts_get_instance_private (cbc);

	g_rec_mutex_init (&cbc->priv->rec_mutex);
	g_rec_mutex_init (&cbc->priv->tracked_contacts_lock);

	cbc->priv->addressbooks = g_hash_table_new_full (
		(GHashFunc) e_source_hash,
		(GEqualFunc) e_source_equal,
		(GDestroyNotify) g_object_unref,
		(GDestroyNotify) cancel_and_unref_book_record);

	cbc->priv->tracked_contacts = g_hash_table_new_full (
		(GHashFunc) g_str_hash,
		(GEqualFunc) g_str_equal,
		(GDestroyNotify) g_free,
		(GDestroyNotify) contact_record_free);

	cbc->priv->settings = g_settings_new ("org.gnome.evolution-data-server.calendar");
	cbc->priv->notifyid = 0;
	cbc->priv->update_alarms_id = 0;
	cbc->priv->alarm_enabled = FALSE;
	cbc->priv->alarm_interval = -1;
	cbc->priv->alarm_units = E_CAL_INTERVAL_UNIT_MINUTES;

	g_signal_connect (
		cbc, "notify::online",
		G_CALLBACK (e_cal_backend_contacts_notify_online_cb), NULL);
}

static void
e_cal_backend_contacts_create_objects (ECalBackendSync *backend,
                                       EDataCal *cal,
                                       GCancellable *cancellable,
                                       const GSList *calobjs,
				       ECalOperationFlags opflags,
                                       GSList **uids,
                                       GSList **new_components,
                                       GError **perror)
{
	g_propagate_error (perror, EC_ERROR (E_CLIENT_ERROR_PERMISSION_DENIED));
}

/* Class initialization function for the contacts backend */
static void
e_cal_backend_contacts_class_init (ECalBackendContactsClass *class)
{
	GObjectClass *object_class;
	ECalBackendClass *backend_class;
	ECalBackendSyncClass *sync_class;

	object_class = (GObjectClass *) class;
	backend_class = (ECalBackendClass *) class;
	sync_class = (ECalBackendSyncClass *) class;

	object_class->finalize = e_cal_backend_contacts_finalize;
	object_class->dispose = e_cal_backend_contacts_dispose;
	object_class->constructed = e_cal_backend_contacts_constructed;

	/* Execute one method at a time. */
	backend_class->use_serial_dispatch_queue = TRUE;

	backend_class->impl_get_backend_property = e_cal_backend_contacts_get_backend_property;
	backend_class->impl_start_view = e_cal_backend_contacts_start_view;

	sync_class->open_sync = e_cal_backend_contacts_open;
	sync_class->create_objects_sync = e_cal_backend_contacts_create_objects;
	sync_class->receive_objects_sync = e_cal_backend_contacts_receive_objects;
	sync_class->send_objects_sync = e_cal_backend_contacts_send_objects;
	sync_class->get_object_sync = e_cal_backend_contacts_get_object;
	sync_class->get_object_list_sync = e_cal_backend_contacts_get_object_list;
	sync_class->add_timezone_sync = e_cal_backend_contacts_add_timezone;
	sync_class->get_free_busy_sync = e_cal_backend_contacts_get_free_busy;

	/* Register our ESource extension. */
	E_TYPE_SOURCE_CONTACTS;
}
