/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            e-cal-backend-kolab.c
 *
 *  Thu Jun 10 18:25:26 2010
 *  Copyright  2010  Christian Hilberg
 *  <hilberg@kernelconcepts.de>
 *  and Silvan Marco Fin <silvan@kernelconcepts.de> in 2011
 ****************************************************************************/

/*
 * 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.1 of the License, or (at your option) any later version.
 *
 * 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/*----------------------------------------------------------------------------*/

#include <glib.h>
#include <gio/gio.h>

#include <libedata-cal/e-cal-backend-cache.h>
#include <libedata-cal/e-data-cal-types.h>
#include <libecal/e-cal.h>

#include <libical/ical.h>

#include <libekolab/kolab-mail-access.h>
#include <libekolab/kolab-settings-handler.h>
#include <libekolab/kolab-mail-handle.h>
#include <libekolabutil/kolab-util-http.h>
#include <libekolabutil/kolab-util-glib.h>
#include <libekolabutil/kolab-util-camel.h>
#include <libekolabutil/kolab-util-cal-freebusy.h>

#include "kolab-util-calendar.h"
#include "kolab-util-calendar-cache.h"
#include "e-cal-backend-kolab.h"

/*----------------------------------------------------------------------------*/
/* table of KolabMailAccess objects */

static GHashTable *koma_objects = NULL;
static GMutex *koma_objects_lock = NULL;
static GMutex *active_cal_views_lock = NULL;

/*----------------------------------------------------------------------------*/
/* forward declarations */

static void
e_cal_backend_kolab_add_timezone (ECalBackendSync *backend,
                                  EDataCal *cal,
                                  GCancellable *cancellable,
                                  const gchar *tzobject,
                                  GError **error);

static void
e_cal_backend_kolab_open (ECalBackendSync *backend,
                          EDataCal *cal,
                          GCancellable *cancellable,
                          gboolean only_if_exists,
                          GError **error);

/*----------------------------------------------------------------------------*/

#define E_CAL_BACKEND_KOLAB_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_TYPE_CAL_BACKEND_KOLAB, ECalBackendKolabPrivate))

G_DEFINE_TYPE (ECalBackendKolab, e_cal_backend_kolab, E_TYPE_CAL_BACKEND_SYNC)

/* private structures for Kolab calendar class ********************************/

/* TODO check what is appropriate for Kolab here */

typedef struct {
	GCond *cond;
	GMutex *mutex;
	gboolean exit;
} SyncDelta;

/* Private part of the ECalBackendKolab structure */
typedef struct _ECalBackendKolabPrivate ECalBackendKolabPrivate;
struct _ECalBackendKolabPrivate {
	CalMode cal_mode;
	KolabMailAccess *cal_koma;
	ECalBackendCache *cal_cache;
	gchar *user_email;
	ECalComponent *default_zone;
	ECalSourceType source_type;
	gchar *cal_uri;
	GError *mode_switch_err;
};

/*----------------------------------------------------------------------------*/
/* internal statics */

static void
cal_backend_kolab_signal_online_cb (GObject *object)
{
	EBackend *backend = NULL;
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	gboolean online = FALSE;

	g_return_if_fail (E_IS_CAL_BACKEND_KOLAB (object));

	backend = E_BACKEND (object);
	self = E_CAL_BACKEND_KOLAB (object);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	online = e_backend_get_online (backend);

	g_debug ("%s()[%u] backend mode: %i",
	         __func__, __LINE__, online);

	/* FIXME
	 *
	 * This callback is for online state notifications only.
	 * Doing I/O, which can fail, is the wrong thing to do
	 * here since we do not have a proper way to let the user
	 * cancel the I/O. Getting KolabMailAccess online can hang.
	 * Thus, cancellation would be needed.
	 *
	 * e_backend_get_online (backend) could be called in the
	 * "other" operations and the KolabMailAccess state changed
	 * if needed, Problem: KolabMailAccess online/offline state
	 * change involves synchronization with the server, which
	 * may take long time and is not expected by the user if
	 * they e.g. just add a contact or event.
	 *
	 * For now, we'll leave the cancellation issue aside
	 * (we could not have cancelled in 2.30 either), but
	 * at least bind a GError to our backend object, if we
	 * have one. We can then propagate this error in the
	 * first "other" operation the user triggers.
	 *
	 * It has been supposed by upstream to use a global static
	 * GCancellable for the cancellation issue, but it will
	 * not help us here since the Evolution online/offline
	 * toggle button does not seem to expect heavy I/O to
	 * be triggered. Our local GCancellabe would need transfer
	 * to Evo and be registered there so the online/offline
	 * switching in evo-kolab could be cancelled from the
	 * frontend. Moreover, there is no telling how long a
	 * synchronization run with the Kolab server may take,
	 * so we also cannot set a sensible timeout here.
	 *
	 */

	(void) kolab_util_backend_deploy_mode_by_backend (priv->cal_koma,
	                                                  online,
	                                                  NULL, /* GCancellable (see above ) */
	                                                  &(priv->mode_switch_err));
	if (priv->mode_switch_err) {
		g_warning ("%s()[%u]: Online mode switching error pending, (%s), Domain %s Code %i",
		           __func__, __LINE__,
		           priv->mode_switch_err->message,
		           g_quark_to_string (priv->mode_switch_err->domain),
		           priv->mode_switch_err->code);
		g_error_free (priv->mode_switch_err);
		priv->mode_switch_err = NULL;
	}
}

static gboolean
cal_backend_kolab_notify_opened (ECalBackendKolab *self,
                                 GError **err)
{
	ECalBackend *cbackend = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	KolabMailAccessOpmodeID tmp_mode = KOLAB_MAIL_ACCESS_OPMODE_INVAL;
	GError *tmp_err = NULL;

	g_assert (E_IS_CAL_BACKEND_KOLAB (self));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);
	cbackend = E_CAL_BACKEND (self);

	tmp_mode = kolab_mail_access_get_opmode (priv->cal_koma, &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	e_cal_backend_notify_online (cbackend,
	                             tmp_mode == KOLAB_MAIL_ACCESS_OPMODE_ONLINE ? TRUE : FALSE);
	e_cal_backend_notify_readonly (cbackend, FALSE);
	e_cal_backend_notify_opened (cbackend, NULL);

	return TRUE;
}

static void
cal_backend_kolab_notify_removed (ECalBackendKolab *self)
{
	ECalBackend *cbackend = NULL;

	g_assert (E_IS_CAL_BACKEND_KOLAB (self));

	cbackend = E_CAL_BACKEND (self);

	e_cal_backend_notify_readonly (cbackend, TRUE);
	e_cal_backend_notify_online (cbackend, FALSE);
}

/*----------------------------------------------------------------------------*/
/* class functions */

/**
 * e_cal_backend_kolab_open:
 * @backend: An ECalBackendSync object.
 * @cal: An EDataCal object.
 * @cancellable: A cancellation stack.
 * @only_if_exists: Whether to open the calendar if and only if it already exists
 * or just create it when it does not exist.
 * @err: A GError placeholder.
 *
 * Opens a calendar backend with data from a calendar stored at the specified URI.
 */
static void
e_cal_backend_kolab_open (ECalBackendSync *backend,
                          EDataCal *cal,
                          GCancellable *cancellable,
                          gboolean only_if_exists,
                          GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	ESource *esource = NULL;
	CamelURL *c_url = NULL;
	KolabSettingsHandler *ksettings = NULL;
	KolabMailAccess *tmp_koma = NULL;
	KolabSyncStrategyID sync_value = KOLAB_SYNC_STRATEGY_DEFAULT;
	icalcomponent_kind icalkind = ICAL_VEVENT_COMPONENT;
	gchar *sourcename = NULL;
	gchar *servername = NULL;
	gchar *username = NULL;
	gchar *user_at_server = NULL;
	gchar *tmp_key = NULL;
	const gchar *prop_str = NULL;
	gboolean online = FALSE;
	gboolean auth_required = FALSE;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	(void)only_if_exists; /* FIXME */

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_mutex_lock (koma_objects_lock);

	/* to trigger eds to pass along username and password, set the property
	 * "auth" to "true" in the source setting:
	 * <property name="auth" value="true"/>
	 * <property name="username" value="..."/>
	 */

	icalkind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
	/* TODO: this has to be set according to the get_kind() method */
	switch (icalkind) {
	case ICAL_VEVENT_COMPONENT:
		priv->source_type = E_CAL_SOURCE_TYPE_EVENT;
		break;
	case ICAL_VTODO_COMPONENT:
		priv->source_type = E_CAL_SOURCE_TYPE_TODO;
		break;
	case ICAL_VJOURNAL_COMPONENT:
		priv->source_type = E_CAL_SOURCE_TYPE_JOURNAL;
		break;
	default:
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_GENERIC,
		             "Unknown type used in e-cal-backend-kolab initialization");
		goto exit;
	}

	esource = e_backend_get_source (E_BACKEND (backend));
	if (! E_IS_SOURCE (esource)) {
		/* FIXME mark this as a translatable string */
		tmp_err = e_data_cal_create_error (OtherError,
		                                   "Could not get ESource for backend");
		goto exit;
	}

	priv->cal_uri = e_source_get_uri (esource);
	c_url = camel_url_new (priv->cal_uri, &tmp_err);
	if (c_url == NULL)
		goto exit;

	servername = g_strdup (c_url->host);
	username = g_strdup (c_url->user);
	g_debug ("%s()[%u] servername = %s", __func__, __LINE__, servername);
	g_debug ("%s()[%u]   username = %s", __func__, __LINE__, username);

	/* Initialize backend cache */
	if (priv->cal_cache != NULL) {
		g_object_unref (priv->cal_cache);
	}
	priv->cal_cache = e_cal_backend_cache_new (priv->cal_uri);
	ok = e_file_cache_clean (E_FILE_CACHE (priv->cal_cache));
	g_debug (" + Cal cache cleaning %s.", ok ? "was successful" : "FAILED");

	/* Prepare data from sync strategy property */
	prop_str = e_source_get_property (esource, KOLAB_SYNC_STRATEGY_PROP);
	sync_value = kolab_util_misc_sync_value_from_property (prop_str);
	sourcename = kolab_util_backend_get_relative_path_from_uri (priv->cal_uri);

	/* Check whether we have a KolabMailAccess (KoMA) instance already */
	user_at_server = g_strdup_printf ("%s@%s",
	                                  username, servername);
	ok = g_hash_table_lookup_extended (koma_objects,
	                                   user_at_server,
	                                   (gpointer *) &tmp_key,
	                                   (gpointer *) &tmp_koma);

	if (ok) {
		gchar *passwd = NULL;

		/* There already is a KoMA instance for user_at_server.
		 * Use it and return
		 */
		g_object_ref (tmp_koma);
		priv->cal_koma = tmp_koma;
		ksettings = kolab_mail_access_get_settings_handler (priv->cal_koma);
		kolab_util_backend_prepare_settings (ksettings,
		                                     NULL,
		                                     NULL,
		                                     NULL,
		                                     NULL,
		                                     sourcename,
		                                     &sync_value);
		/* We may find that we do not yet have a password set.
		 * In case we're requested to go online, we will need
		 * one, so we need to get authentication data first.
		 */
		passwd = g_strdup (kolab_settings_handler_get_char_field (ksettings,
		                                                          KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_PASSWORD,
		                                                          NULL));
		if (passwd == NULL) {
			auth_required = TRUE;
			goto exit;
		}
		g_free (passwd);

		(void) kolab_util_backend_deploy_mode_by_backend (priv->cal_koma,
		                                                  online,
		                                                  cancellable,
		                                                  &tmp_err);
		goto exit;
	}

	/* Nope, we need to setup a new KoMA instance and a settings handler */

	/* init subsystems (these are no-ops if already called before) */
	kolab_util_glib_init ();
	kolab_util_http_init ();
	/* libcamel
	 * Curl init may configure the underlying SSL lib,
	 * but as far as SSL goes, we want Camel to rule here
	 * TODO check whether Camel session needs to be initialized before or after libcurl.
	 */
	ok = kolab_util_camel_init (&tmp_err);
	if (! ok)
		goto exit;

	/* Configure settings handler */
	ksettings = KOLAB_SETTINGS_HANDLER (g_object_new (KOLAB_TYPE_SETTINGS_HANDLER, NULL));
	ok = kolab_settings_handler_configure (ksettings,
	                                       KOLAB_FOLDER_CONTEXT_CALENDAR,
	                                       &tmp_err);
	if (! ok)
		goto exit;

	ok = kolab_settings_handler_bringup (ksettings, &tmp_err);
	if (! ok)
		goto exit;

	kolab_util_backend_prepare_settings (ksettings,
	                                     esource,
	                                     servername,
	                                     username,
	                                     NULL,
	                                     sourcename,
	                                     &sync_value);

	/* create new KolabMailAccess instance */
	priv->cal_koma = KOLAB_MAIL_ACCESS (g_object_new (KOLAB_TYPE_MAIL_ACCESS, NULL));
	g_object_add_toggle_ref (G_OBJECT (priv->cal_koma),
	                         kolab_util_backend_koma_table_cleanup_cb,
	                         koma_objects);
	g_hash_table_insert (koma_objects,
	                     user_at_server,
	                     priv->cal_koma);

	/* configure and bring up KolabMailAccess instance */
	ok = kolab_mail_access_configure (priv->cal_koma,
	                                  ksettings,
	                                  &tmp_err);
	if (! ok)
		goto exit;

	ok = kolab_mail_access_bringup (priv->cal_koma,
	                                cancellable,
	                                &tmp_err);
	if (! ok)
		goto exit;

	/* TODO this is sort of hackish, we cannot be sure that
	 *      'user@server' is truly the Kolab email address
	 *      of the Kolab user
	 */
	priv->user_email = g_strdup (user_at_server);

	/* If we are here (just created a fresh KolabMailAccess),
	 * we did not yet receive authentication data. Though we
	 * do not need it if we want to stay in offline mode, once
	 * we're asked to go online, we'll need the credentials
	 * (password in our case) anyway, so we can just request
	 * it once the book gets opened.
	 */
	auth_required = TRUE;

 exit:

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

	g_mutex_unlock (koma_objects_lock);

	if (c_url != NULL)
		camel_url_free (c_url);
	if (servername != NULL)
		g_free (servername);
	if (username != NULL)
		g_free (username);
	if (user_at_server != NULL)
		g_free (user_at_server);

	/* do we have an error set? if so, propagate and return */
	if (tmp_err != NULL) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		return;
	}

	/* do we need authentication data? if so, notify and return */
	if (auth_required) {
		e_cal_backend_notify_auth_required (E_CAL_BACKEND (self), TRUE, NULL);
		return;
	}

	/* all good, notify that we're open for business */
	ok = cal_backend_kolab_notify_opened (self, &tmp_err);
	if (! ok) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
	}
}

static void
e_cal_backend_kolab_remove (ECalBackendSync *backend,
                            EDataCal *cal,
                            GCancellable *cancellable,
                            GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_warning ("%s: FIXME implement me", __func__);

#if 0 /* FIXME old */
	if (priv->cal_koma != NULL) {
		g_object_unref (priv->cal_koma);
		priv->cal_koma = NULL;
	}

	/* Remove backend cache */
	if (priv->cal_cache != NULL) {
		(void) e_file_cache_remove (E_FILE_CACHE (priv->cal_cache));
		g_object_unref (priv->cal_cache);
		priv->cal_cache = NULL;
	}
#endif

	cal_backend_kolab_notify_removed (self);
}

static void
e_cal_backend_kolab_refresh (ECalBackendSync *backend,
                             EDataCal *cal,
                             GCancellable *cancellable,
                             GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_error ("%s: FIXME implement me", __func__);
}

static gboolean
e_cal_backend_kolab_get_backend_property (ECalBackendSync *backend,
                                          EDataCal *cal,
                                          GCancellable *cancellable,
                                          const gchar *prop_name,
                                          gchar **prop_value,
                                          GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	gboolean processed = TRUE;

	/* this is a modified dupe of the respective mapi function */

	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
	e_return_data_cal_error_val_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_val_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_val_if_fail (prop_name != NULL, InvalidArg);
	e_return_data_cal_error_val_if_fail (prop_value != NULL && *prop_value == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
		*prop_value = g_strdup (CAL_STATIC_CAPABILITY_NO_ALARM_REPEAT ","
		                        CAL_STATIC_CAPABILITY_NO_AUDIO_ALARMS ","
		                        CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS ","
		                        CAL_STATIC_CAPABILITY_NO_PROCEDURE_ALARMS ","
		                        CAL_STATIC_CAPABILITY_ONE_ALARM_ONLY ","
		                        CAL_STATIC_CAPABILITY_REMOVE_ALARMS ","
		                        CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
		                        CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
		                        CAL_STATIC_CAPABILITY_CREATE_MESSAGES ","
		                        CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK ","
		                        CAL_STATIC_CAPABILITY_NO_CONV_TO_RECUR ","
		                        CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING ","
		                        CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED
		                        );
	} else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS)) {
		/* see the comment for user_email in e_cal_backend_kolab_open() ! */
		*prop_value = g_strdup (priv->user_email);
	} else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
		/* We don't support email alarms. This should not have been called. */
		*prop_value = NULL;
	} else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
		ECalComponent *comp;
		GError *tmp_err = NULL;

		comp = e_cal_component_new ();

		switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
		case ICAL_VEVENT_COMPONENT:
			e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
			break;
		case ICAL_VTODO_COMPONENT:
			e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
			break;
		case ICAL_VJOURNAL_COMPONENT:
			e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
			break;
		default:
			g_object_unref (comp);
			tmp_err = e_data_cal_create_error (ObjectNotFound, NULL);
			g_propagate_error (error, tmp_err);
			return TRUE;
		}

		*prop_value = e_cal_component_get_as_string (comp);
		g_object_unref (comp);
	} else {
		processed = FALSE; /* means chaining up the call to parent */
	}

	return processed;
}

#if 0 /* probably not needed */
static gboolean
e_cal_backend_kolab_set_backend_property (ECalBackendSync *backend,
                                          EDataCal *cal,
                                          GCancellable *cancellable,
                                          const gchar *prop_name,
                                          const gchar *prop_value,
                                          GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;

	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
	e_return_data_cal_error_val_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_val_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_val_if_fail (prop_name != NULL, InvalidArg);
	e_return_data_cal_error_val_if_fail (prop_value != NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_error ("%s: FIXME implement me", __func__);

	return FALSE;
}
#endif

/**
 * e_cal_backend_kolab_get_object:
 * @backend: An ECalBackendSync object.
 * @cal: An EDataCal object.
 * @cancellable: A cancellation stack.
 * @uid: UID of the object to get.
 * @rid: Recurrence ID of the specific instance to get, or NULL if getting the
 * master object.
 * @calobject: Placeholder for returned object.
 * @err: GError placeholder.
 *
 * Queries a calendar backend for a calendar object based on its unique
 * identifier and its recurrence ID (if a recurrent appointment).
 */
static void
e_cal_backend_kolab_get_object (ECalBackendSync *backend,
                                EDataCal *cal,
                                GCancellable *cancellable,
                                const gchar *uid,
                                const gchar *rid,
                                gchar **calobj,
                                GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	ECalComponent *ecalcomp = NULL;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (uid != NULL, InvalidArg);
	(void)rid; /* FIXME */ /* rid may be NULL */
	e_return_data_cal_error_if_fail (calobj != NULL && *calobj == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	ecalcomp = kolab_util_calendar_cache_get_object (priv->cal_cache,
	                                                 priv->cal_koma,
	                                                 priv->cal_uri,
	                                                 uid,
	                                                 FALSE,
	                                                 cancellable,
	                                                 &tmp_err);
	if (tmp_err != NULL) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		return;
	}
	if (ecalcomp != NULL) {
		*calobj = (gchar *) e_cal_component_get_as_string (ecalcomp);
		g_object_unref (ecalcomp);
	}
}

static void
e_cal_backend_kolab_get_object_list (ECalBackendSync *backend,
                                     EDataCal *cal,
                                     GCancellable *cancellable,
                                     const gchar *sexp,
                                     GSList **calobjs,
                                     GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	GList *uid_list = NULL;
	GList *it = NULL;
	gchar *sourcename = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	/* sexp may be NULL */
	e_return_data_cal_error_if_fail (calobjs != NULL && *calobjs == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	ok = kolab_util_calendar_cache_update_on_query (priv->cal_cache,
	                                                priv->cal_koma,
	                                                sexp,
	                                                priv->cal_uri,
	                                                cancellable,
	                                                &tmp_err);
	if (! ok) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		return;
	}

	sourcename = kolab_util_backend_get_relative_path_from_uri (priv->cal_uri);
	uid_list = kolab_mail_access_query_uids (priv->cal_koma,
	                                         sourcename,
	                                         sexp,
	                                         &tmp_err);
	g_free (sourcename);
	if (tmp_err != NULL) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		return;
	}

	for (it = uid_list; it != NULL; it = g_list_next (it)) {
		gchar *uid = it->data;
		ECalComponent *ecalcomp = NULL;
		ecalcomp = kolab_util_calendar_cache_get_object (priv->cal_cache,
		                                                 priv->cal_koma,
		                                                 priv->cal_uri,
		                                                 uid,
		                                                 TRUE,
		                                                 cancellable,
		                                                 &tmp_err);
		/* have we been cancelled? */
		if (g_cancellable_is_cancelled (cancellable)) {
			kolab_util_glib_glist_free (uid_list);
			if (ecalcomp != NULL)
				g_object_unref (ecalcomp);
			kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
			g_error_free (tmp_err);
			return;
		}
		/* not cancelled, other error */
		if (tmp_err != NULL) {
			/* TODO Can we sensibly propagate an error here?
			 *      Failure for one does not necessarily imply
			 *      failure for the others
			 */
			g_warning ("%s()[%u]: %s",
			           __func__, __LINE__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		if (ecalcomp != NULL) {
			*calobjs = g_slist_append (*calobjs,
			                           e_cal_component_get_as_string (ecalcomp));
			g_object_unref (ecalcomp);
		}
	}

	kolab_util_glib_glist_free (uid_list);
}

static void
e_cal_backend_kolab_get_free_busy (ECalBackendSync *backend,
                                   EDataCal *cal,
                                   GCancellable *cancellable,
                                   const GSList *users,
                                   time_t start,
                                   time_t end,
                                   GSList **freebusyobjs,
                                   GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	KolabSettingsHandler *ksettings = NULL;
	GSList *it = NULL;
	gboolean internal_state = TRUE;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (users != NULL, InvalidArg);
	(void)start; /* FIXME */
	(void)end; /* FIXME */
	e_return_data_cal_error_if_fail (freebusyobjs != NULL && *freebusyobjs == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	ksettings = kolab_mail_access_get_settings_handler (priv->cal_koma);
	if (ksettings == NULL) {
		/* TODO add a translatable string as custom message */
		tmp_err = e_data_cal_create_error (OtherError, NULL);
	}

	/* receive the xfb information */
	for (it = (GSList *) users; it != NULL; it = g_slist_next (it)) {
		KolabUtilHttpJob *job = NULL;
		ECalComponent *ecalcomp = NULL;

		job = kolab_util_calendar_retrieve_xfb (ksettings, (gchar *) it->data, &tmp_err);
		if (job != NULL) {
			ecalcomp = kolabconv_cal_util_freebusy_ecalcomp_new_from_ics ((gchar *) (job->buffer->data),
			                                                              job->nbytes,
			                                                              &tmp_err);
			kolab_util_http_job_free (job);
		}
		if (tmp_err != NULL) {
			/* TODO Can we sensibly propagate an error
			 *      here? Failure for one does not
			 *      necessarily imply failure for all
			 */
			g_warning ("%s()[%u] %s", __func__, __LINE__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
			internal_state = FALSE;
		}

		if (ecalcomp == NULL) {
			/* this is not an error, but there's no F/B
			 * information available.
			 */
			g_object_unref (ksettings);
			tmp_err = e_data_cal_create_error (ObjectNotFound, NULL);
			g_propagate_error (error, tmp_err);
			return;
		}
		else {
			*freebusyobjs = g_slist_append (*freebusyobjs,
			                                g_strdup (e_cal_component_get_as_string (ecalcomp)));
			g_object_unref (ecalcomp);
		}
	}
	g_object_unref (ksettings);
	if (! internal_state) {
		/* TODO add a translatable string as custom message here,
		 *      could use a saved tmp_err->message (e.g. latest)
		 */
		tmp_err = e_data_cal_create_error (OtherError, NULL);
		g_propagate_error (error, tmp_err);
	}
}

/**
 * e_cal_backend_kolab_create_object:
 * @backend: (sync) kolab calendar backend object.
 * @cal: An EDataCal object.
 * @cancellable: A cancellation stack
 * @calobj: Contains the event to be created in the calendar as iCalendar string.
 * @uid: Return value of the created events uid.
 * @new_component: The newly created component, serialized as ical component string.
 * @err: A GError placeholder.
 *
 * Creates a new calendar entry in the Kolab calendar. This method gets called
 * when Evolution requests a new entry to be saved in the calendar.
 */
static void
e_cal_backend_kolab_create_object (ECalBackendSync *backend,
                                   EDataCal *cal,
                                   GCancellable *cancellable,
                                   const gchar *calobj,
                                   gchar **uid,
                                   ECalComponent **new_component,
                                   GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	ECalComponent *ecalcomp = NULL;
	ECalComponent *tzcomp = NULL;
	const gchar *tmp_uid = NULL;
	GError *tmp_err = NULL;
	gboolean ok = FALSE;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (calobj != NULL, InvalidArg);
	e_return_data_cal_error_if_fail (uid != NULL && *uid == NULL, InvalidArg);
	e_return_data_cal_error_if_fail (new_component != NULL && *new_component == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	/* What do we have here? There is an ECalComponent, unable to hold
	 * timezone information and an calendar entry at the same time.
	 * So Kolab glue code expects two ECalComponents, one for the calendar entry
	 * one for the timezone.
	 */
	ecalcomp = e_cal_component_new_from_string (calobj);
	if (ecalcomp == NULL) {
		g_warning (" + **calobj could not be parsed into ECalComponent: %s",
		           calobj);
		/* TODO add translatable string as custom message */
		tmp_err = e_data_cal_create_error (InvalidObject, NULL);
		g_propagate_error (error, tmp_err);
		return;
	}
	tzcomp = kolab_util_calendar_cache_get_tz (priv->cal_cache,
	                                           ecalcomp);

	if (tzcomp == NULL) {
		gchar *tzid = NULL;
		tzid = kolab_util_calendar_get_tzid (ecalcomp,
		                                     E_CAL_COMPONENT_FIELD_DTSTART);
		/* If tzid is UTC, everything is fine */
		if (g_strcmp0 ("UTC", tzid) == 0) {
			g_free (tzid);
			tzid = NULL;
		}
		if (tzid != NULL) {
			/* TZID is set, but no timezone is available. Currently
			 * this may happen, if an object is copied to an yet unused/empty
			 * calendar. We do not want such crap in the kolab database.
			 */
			g_free (tzid);
			g_object_unref (ecalcomp);
			/* FIXME mark this as a translatable string */
			tmp_err = e_data_cal_create_error (InvalidObject,
			                                   "TZID is set, but no timezone is available. Currently this may happen if an object is copied to an empty calendar.");
			g_propagate_error (error, tmp_err);
			return;
		}
	}


	ok = kolab_util_calendar_cache_assure_uid_on_ecalcomponent (priv->cal_cache,
	                                                            priv->cal_koma,
	                                                            ecalcomp,
	                                                            priv->cal_uri,
	                                                            FALSE,
	                                                            cancellable,
	                                                            &tmp_err);
	if (! ok) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		g_object_unref (ecalcomp);
		if (tzcomp != NULL)
			g_object_unref (tzcomp);
		return;
	}

	ok = kolab_util_calendar_store (ecalcomp,
	                                tzcomp,
	                                priv->default_zone,
	                                priv->cal_koma,
	                                priv->cal_uri,
	                                cancellable,
	                                &tmp_err);
	if (! ok) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		g_object_unref (ecalcomp);
		if (tzcomp != NULL)
			g_object_unref (tzcomp);
		return;
	}

	e_cal_component_get_uid (ecalcomp, &tmp_uid);
	*uid = g_strdup (tmp_uid);
	*new_component = ecalcomp;

	if (tzcomp != NULL)
		g_object_unref (tzcomp);
}

/**
 * e_cal_backend_kolab_modify_object:
 * @backend: An ECalBackendSync object.
 * @cal: An EDataCal object.
 * @cancellable: A cancellation stack.
 * @calobj: Object to be modified.
 * @mod: Type of modification to be done.
 * @old_component: Placeholder for returning the old object as it was stored on the
 * backend.
 * @new_component: Placeholder for returning the new object as it has been stored
 * on the backend.
 * @err: A GError placeholder.
 *
 * Requests the calendar backend to modify an existing object. If the object
 * does not exist on the calendar, it will be issued as new event.
 */
static void
e_cal_backend_kolab_modify_object (ECalBackendSync *backend,
                                   EDataCal *cal,
                                   GCancellable *cancellable,
                                   const gchar *calobj,
                                   CalObjModType mod,
                                   ECalComponent **old_component,
                                   ECalComponent **new_component,
                                   GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	ECalComponent *ecalcomp = NULL;
	ECalComponent *oldcomp = NULL;
	ECalComponent *ecaltz = NULL;
	const gchar *uid = NULL;
	gboolean is_instance = FALSE;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (calobj != NULL, InvalidArg);
	e_return_data_cal_error_if_fail (old_component != NULL && *old_component == NULL, InvalidArg);
	e_return_data_cal_error_if_fail (new_component != NULL && *new_component == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	/* calobj contains the modifications to be performed.
	 * ecalcomp is the ECalComponent created from calobj.
	 * scenarios:
	 *  a) ecalcomp is no part of a recurring event:
	 *     read oldobj by means of ecalcomps uid
	 *     store ecalcomp and we are done.
	 *  b) ecalcomp is a modification to all instances of a recurring event:
	 *     should be handled like (a)
	 *  c) ecalcomp is a single instance of a recurring event:
	 *     Kolab can not handle detached recurrences. Remove the instance
	 *     from the recurrence and create a new single event für ecalcomp.
	 */

	ecalcomp = e_cal_component_new_from_string (calobj);
	if (ecalcomp == NULL) {
		tmp_err = e_data_cal_create_error (InvalidObject, NULL);
		g_propagate_error (error, tmp_err);
		return;
	}

	e_cal_component_get_uid (ecalcomp, &uid);

	oldcomp = kolab_util_calendar_cache_get_object (priv->cal_cache,
	                                                priv->cal_koma,
	                                                priv->cal_uri,
	                                                uid,
	                                                FALSE,
	                                                cancellable,
	                                                &tmp_err);
	if (tmp_err != NULL) {
		/* TODO: how to handle request to modify non existing objects? */
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		g_object_unref (ecalcomp);
		if (oldcomp != NULL)
			g_object_unref (oldcomp);
		return;
	}

	*old_component = oldcomp;
	/* Is this correct? Perhaps _get_tz (..., oldcomp) is needed.
	 * This depends on wether evolution sends the timezone, if modification
	 * to the timezone has been made. In any other case the timezone
	 * information from ecalcomp and oldcomp should be all the same.
	 */
	ecaltz = kolab_util_calendar_cache_get_tz (priv->cal_cache,
	                                           ecalcomp);
	is_instance = e_cal_component_is_instance (ecalcomp);
	if (is_instance) {
		/* Handling of (c) */
		ECalComponent *newcomp = NULL;
		ECalComponent *oldtz = NULL;
		gchar *rid = NULL;

		oldtz = kolab_util_calendar_cache_get_tz (priv->cal_cache,
		                                          oldcomp);
		rid = e_cal_component_get_recurid_as_string (ecalcomp);
		/* This removes the recurrence from ecalcomp. */
		e_cal_component_set_recurid (ecalcomp, NULL);
		/* This removes ecalcomp from the recurrence. */
		newcomp = kolab_util_calendar_cache_remove_instance (priv->cal_cache,
		                                                     CALOBJ_MOD_THIS,
		                                                     oldcomp,
		                                                     uid,
		                                                     rid);
		ok = kolab_util_calendar_cache_assure_uid_on_ecalcomponent (priv->cal_cache,
		                                                            priv->cal_koma,
		                                                            ecalcomp,
		                                                            priv->cal_uri,
		                                                            TRUE,
		                                                            cancellable,
		                                                            &tmp_err);
		if (! ok) {
			kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
			g_error_free (tmp_err);
			if (oldtz != NULL)
				g_object_unref (oldtz);
			g_object_unref (ecalcomp);
			if (ecaltz != NULL)
				g_object_unref (ecaltz);
			g_object_unref (newcomp);
			g_object_unref (oldcomp);
			*old_component = NULL;
			return;
		}

		ok = kolab_util_calendar_store (newcomp,
		                                oldtz,
		                                priv->default_zone,
		                                priv->cal_koma,
		                                priv->cal_uri,
		                                cancellable,
		                                &tmp_err);
		if (! ok) {
			kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
			g_error_free (tmp_err);
			if (oldtz != NULL)
				g_object_unref (oldtz);
			g_object_unref (ecalcomp);
			if (ecaltz != NULL)
				g_object_unref (ecaltz);
			g_object_unref (newcomp);
			g_object_unref (oldcomp);
			*old_component = NULL;
			return;
		}
		e_cal_backend_notify_component_modified (E_CAL_BACKEND (backend),
		                                         *old_component,
		                                         newcomp);
		g_object_unref (newcomp);
		if (oldtz != NULL)
			g_object_unref (oldtz);
	}
	ok = kolab_util_calendar_store (ecalcomp,
	                                ecaltz,
	                                priv->default_zone,
	                                priv->cal_koma,
	                                priv->cal_uri,
	                                cancellable,
	                                &tmp_err);
	if (! ok) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		g_object_unref (ecalcomp);
		return;
	}

	*new_component = ecalcomp;
	if (is_instance) {
		e_cal_backend_notify_component_created (E_CAL_BACKEND (backend),
		                                        ecalcomp);
	}

	if (ecaltz != NULL)
		g_object_unref (ecaltz);
}

/**
 * e_cal_backend_kolab_remove_object:
 * @backend: An ECalBackendSync object.
 * @cal: An EDataCal object.
 * @cancellable: A cancellation stack.
 * @uid: UID of the object to remove.
 * @rid: Recurrence ID of the instance to remove, or NULL if removing the
 * whole object.
 * @mod: Type of removal.
 * @old_component: Placeholder for returning the old object as it was stored on the
 * backend.
 * @new_component: Placeholder for returning the object after it has been modified (when
 * removing individual instances). If removing the whole object, this will be
 * NULL.
 * @err: A GError placeholder.
 *
 * Removes an (a part of a [recurring] ) object from a calendar backend. The
 * backend will notify all of its clients about the change.
 */
static void
e_cal_backend_kolab_remove_object (ECalBackendSync *backend,
                                   EDataCal *cal,
                                   GCancellable *cancellable,
                                   const gchar *uid,
                                   const gchar *rid,
                                   CalObjModType mod,
                                   ECalComponent **old_component,
                                   ECalComponent **new_component,
                                   GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	ECalComponent *oldcomp = NULL;
	ECalComponent *newcomp = NULL;
	ECalComponent *oldtz = NULL;
	ECalComponentId *id = NULL;
	KolabSettingsHandler *ksettings = NULL;
	gchar *sourcename = NULL;
	GError *tmp_err = NULL;
	gboolean trigger_needed = FALSE;
	gboolean ok = FALSE;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (uid != NULL, InvalidArg);
	/* rid may be NULL */
	e_return_data_cal_error_if_fail (old_component != NULL && *old_component == NULL, InvalidArg);
	e_return_data_cal_error_if_fail (new_component != NULL && *new_component == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	if (rid == NULL)
		mod = CALOBJ_MOD_ALL;

	oldcomp = kolab_util_calendar_cache_get_object (priv->cal_cache,
	                                                priv->cal_koma,
	                                                priv->cal_uri,
	                                                uid,
	                                                FALSE,
	                                                cancellable,
	                                                &tmp_err);
	if (tmp_err != NULL) {
		switch (tmp_err->code) {
		case KOLAB_BACKEND_ERROR_NOTFOUND:
			(void) e_cal_backend_cache_remove_component (priv->cal_cache,
			                                             uid,
			                                             NULL);
			/* Don't bother if this worked */
			id = g_new0 (ECalComponentId, 1);
			id->uid = g_strdup (uid);
			id->rid = g_strdup (rid);
			e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend),
			                                     id,
			                                     NULL,
			                                     NULL);
			g_free (id);
			break;
		default:
			kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		}
		g_error_free (tmp_err);
		return;
	}

	*old_component = oldcomp;
	id = e_cal_component_get_id (oldcomp);

	switch (mod) {
	case CALOBJ_MOD_THIS:
		oldtz = kolab_util_calendar_cache_get_tz (priv->cal_cache,
		                                          oldcomp);
		newcomp = kolab_util_calendar_cache_remove_instance (priv->cal_cache,
		                                                     CALOBJ_MOD_THIS,
		                                                     oldcomp,
		                                                     uid,
		                                                     rid);
		/* newcomp now contains an exception rule for the rid */
		ok = kolab_util_calendar_store (newcomp,
		                                oldtz,
		                                priv->default_zone,
		                                priv->cal_koma,
		                                priv->cal_uri,
		                                cancellable,
		                                &tmp_err);
		if (oldtz != NULL)
			g_object_unref (oldtz);
		if (! ok) {
			kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
			g_error_free (tmp_err);
			e_cal_component_free_id (id);
			g_object_unref (newcomp);
			g_object_unref (oldcomp);
			*old_component = NULL;
			return;
		}
		*new_component = newcomp;
		break;
	case CALOBJ_MOD_THISANDPRIOR: /* not supported by backend, should not be reached */
		g_warning ("%s()[%u]: removing this and prior not supported.",
		           __func__, __LINE__);
		break;
	case CALOBJ_MOD_THISANDFUTURE: /* not supported by backend, should not be reached */
		g_warning ("%s()[%u]: removing this and future not supported.",
		           __func__, __LINE__);
		break;
	case CALOBJ_MOD_ALL:
	default:
		sourcename = kolab_util_backend_get_relative_path_from_uri (priv->cal_uri);
		ok = kolab_mail_access_delete_by_uid (priv->cal_koma,
		                                      uid,
		                                      sourcename,
		                                      cancellable,
		                                      &tmp_err);
		g_free (sourcename);
		(void) e_cal_backend_cache_remove_component (priv->cal_cache,
		                                             uid,
		                                             NULL);
		if (! ok) {
			kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
			g_error_free (tmp_err);
			e_cal_component_free_id (id);
			g_object_unref (oldcomp);
			*old_component = NULL;
			return;
		}
	}

	sourcename = kolab_util_backend_get_relative_path_from_uri (priv->cal_uri);
	trigger_needed = kolab_mail_access_source_fbtrigger_needed (priv->cal_koma,
	                                                            sourcename,
	                                                            &tmp_err);
	if (tmp_err != NULL) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		e_cal_component_free_id (id);
		g_free (sourcename);
		g_object_unref (oldcomp);
		*old_component = NULL;
		return;
	}
	if (trigger_needed) {
		ksettings = kolab_mail_access_get_settings_handler (priv->cal_koma);
		ok = kolab_util_calendar_toggle_pfb_trigger (ksettings,
		                                             sourcename,
		                                             &tmp_err);
		g_object_unref (ksettings);
		if (! ok) {
			g_warning ("%s()[%u] %s", __func__, __LINE__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
	}

	e_cal_backend_notify_component_removed (E_CAL_BACKEND (backend),
	                                        id,
	                                        *old_component,
	                                        *new_component);
	e_cal_component_free_id (id);
}

/**
 * e_cal_backend_kolab_receive_objects:
 * @backend: An ECalBackendSync object.
 * @cal: An EDataCal object.
 * @cancellable: A cancellation stack.
 * @calobj: iCalendar object to receive.
 * @err: A GError placeholder.
 *
 * Submits a (series of) calendar event(s) through iCalendar object(s).
 */
static void
e_cal_backend_kolab_receive_objects (ECalBackendSync *backend,
                                     EDataCal *cal,
                                     GCancellable *cancellable,
                                     const gchar *calobj,
                                     GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	ECalComponent *ecalcomp = NULL;
	ECalComponent *tzcomp = NULL;
	icalcomponent *icalcomp = NULL;
	gchar *tzstr = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (calobj != NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	/* Workflow:
	 * Incoming calobj is supposed to be a vcalendar string.
	 * KolabMailHandle is the data structure we want to
	 * generate and KolabMailHandle takes an ECalComponent and a timezone
	 * (as ECalComponent). The task at hand: convert calobj into an
	 * icalcomponent ( #1 ). Afterwards process this icalcomponent and extract
	 * the relevant information an ECalComponent can handle ( #2 ). Then
	 * create the ECalComponent, generate a timezone and stuff it
	 * into the KolabMailAccess object. ( #3 )
	 */

	icalcomp = icalparser_parse_string (calobj);
	if (icalcomp == NULL) {
		g_warning ("%s()[%u] calobj could not be parsed into an icalcomponent: %s",
		           __func__, __LINE__, calobj);
		/* TODO add a translatable string as custom error message */
		tmp_err = e_data_cal_create_error (InvalidObject, NULL);
		g_propagate_error (error, tmp_err);
		return;
	}
	/* now this better should be an vcalendar entry */
	if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) {
		g_warning ("%s()[%u] calobj does not represent a vcalendar entry: %s",
		           __func__, __LINE__, calobj);
		icalcomponent_free (icalcomp);
		tmp_err = e_data_cal_create_error (InvalidObject, NULL);
		g_propagate_error (error, tmp_err);
		return;
	}

	ok = kolab_util_calendar_extract (icalcomp,
	                                  priv->source_type,
	                                  &ecalcomp,
	                                  &tzcomp,
	                                  &tmp_err);
	icalcomponent_free (icalcomp);
	if (! ok) {
		g_propagate_error (error, tmp_err);
		return;
	}

	ok = kolab_util_calendar_store (ecalcomp,
	                                tzcomp,
	                                priv->default_zone,
	                                priv->cal_koma,
	                                priv->cal_uri,
	                                cancellable,
	                                &tmp_err);
	if (! ok) {
		kolab_util_calendar_err_to_edb_err (error,
		                                    tmp_err,
		                                    __func__,
		                                    __LINE__);
		g_error_free (tmp_err);
		g_object_unref (ecalcomp);
		if (tzcomp != NULL)
			g_object_unref (tzcomp);
		return;
	}

	if (tzcomp != NULL) {
		tzstr = e_cal_component_get_as_string (tzcomp);
		g_object_unref (tzcomp);
		e_cal_backend_kolab_add_timezone (backend,
		                                  cal,
		                                  cancellable,
		                                  tzstr,
		                                  &tmp_err);
		if (tmp_err != NULL) {
			kolab_util_calendar_err_to_edb_err (error,
			                                    tmp_err,
			                                    __func__,
			                                    __LINE__);
			g_error_free (tmp_err);
			g_object_unref (ecalcomp);
			return;
		}
	}
	e_cal_backend_notify_component_created (E_CAL_BACKEND (backend),
	                                        ecalcomp);
	g_object_unref (ecalcomp);
}

static void
e_cal_backend_kolab_send_objects (ECalBackendSync *backend,
                                  EDataCal *cal,
                                  GCancellable *cancellable,
                                  const gchar *calobj,
                                  GSList **users,
                                  gchar **modified_calobj,
                                  GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (calobj != NULL, InvalidArg);
	e_return_data_cal_error_if_fail (users != NULL && *users == NULL, InvalidArg);
	e_return_data_cal_error_if_fail (modified_calobj != NULL && *modified_calobj == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	*users = NULL;
	*modified_calobj = g_strdup (calobj);
}

static void
e_cal_backend_kolab_get_attachment_uris (ECalBackendSync *backend,
                                         EDataCal *cal,
                                         GCancellable *cancellable,
                                         const gchar *uid,
                                         const gchar *rid,
                                         GSList **attachments,
                                         GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (uid != NULL, InvalidArg);
	e_return_data_cal_error_if_fail (rid != NULL, InvalidArg);
	e_return_data_cal_error_if_fail (attachments != NULL && *attachments == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_error ("%s: FIXME implement me", __func__);
}

static void
e_cal_backend_kolab_discard_alarm (ECalBackendSync *backend,
                                   EDataCal *cal,
                                   GCancellable *cancellable,
                                   const gchar *uid,
                                   const gchar *rid,
                                   const gchar *auid,
                                   GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (uid != NULL, InvalidArg);
	e_return_data_cal_error_if_fail (rid != NULL, InvalidArg);
	e_return_data_cal_error_if_fail (auid != NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_error ("%s: FIXME implement me", __func__);
}

static void
e_cal_backend_kolab_get_timezone (ECalBackendSync *backend,
                                  EDataCal *cal,
                                  GCancellable *cancellable,
                                  const gchar *tzid,
                                  gchar **tzobject,
                                  GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	ECalComponent *ecaltz = NULL;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	(void)cancellable; /* unused */ /* cancellable may be NULL */
	e_return_data_cal_error_if_fail (tzid != NULL, InvalidArg);
	e_return_data_cal_error_if_fail (tzobject != NULL && *tzobject == NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	ecaltz = kolab_util_calendar_cache_get_tz_by_id (priv->cal_cache,
	                                                 tzid);
	if (ecaltz == NULL) {
		tmp_err = e_data_cal_create_error (ObjectNotFound, NULL);
		g_propagate_error (error, tmp_err);
		return;
	}

	*tzobject = e_cal_component_get_as_string (ecaltz);
	g_object_unref (ecaltz);
}

/**
 * e_cal_backend_kolab_add_timezone:
 * @backend: An ECalBackendSync object.
 * @cal: An EDataCal object.
 * @cancellable: A cancellation stack.
 * @tzobject: VTIMEZONE object to be added.
 * @err: A GError placeholder.
 *
 * Add a timezone object to the given backend.
 */
static void
e_cal_backend_kolab_add_timezone (ECalBackendSync *backend,
                                  EDataCal *cal,
                                  GCancellable *cancellable,
                                  const gchar *tzobject,
                                  GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	icalcomponent *icalcomp = NULL;
	icaltimezone *icaltz = NULL;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	e_return_data_cal_error_if_fail (E_IS_DATA_CAL (cal), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (tzobject != NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	icalcomp = icalparser_parse_string (tzobject);
	if (icalcomp == NULL) {
		tmp_err = e_data_cal_create_error (InvalidObject, NULL);
		g_propagate_error (error, tmp_err);
		return;
	}

	if (icalcomponent_isa (icalcomp) == ICAL_VTIMEZONE_COMPONENT) {

		icaltz = icaltimezone_new ();
		icaltimezone_set_component (icaltz,
		                            icalcomp);

		if (! e_cal_backend_cache_put_timezone (priv->cal_cache, icaltz)) {
			g_warning ("%s()[%u]: Putting timezone in cache failed.",
			           __func__, __LINE__);
			icaltimezone_free (icaltz, 1);
			/* TODO add a translatable string as custom message */
			tmp_err = e_data_cal_create_error (OtherError, NULL);
			return;
		}
		icaltimezone_free (icaltz, 1);
	}
}

static void
e_cal_backend_kolab_authenticate_user (ECalBackendSync *backend,
                                       GCancellable *cancellable,
                                       ECredentials *credentials,
                                       GError **error)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	KolabSettingsHandler *ksettings = NULL;
	const gchar *cred_user = NULL;
	const gchar *cred_pwd  = NULL;
	const gchar *kset_user = NULL;
	gboolean online = FALSE;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_return_if_fail (error == NULL || *error == NULL);
	e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_KOLAB (backend), InvalidArg);
	/* cancellable may be NULL */
	e_return_data_cal_error_if_fail (credentials != NULL, InvalidArg);

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_debug ("%s()[%u] called.", __func__, __LINE__);

	ksettings = kolab_mail_access_get_settings_handler (priv->cal_koma);

	/* warn about a possible inconsistency in user names */
	kset_user = kolab_settings_handler_get_char_field (ksettings,
	                                                   KOLAB_SETTINGS_HANDLER_CHAR_FIELD_KOLAB_USER_NAME,
	                                                   &tmp_err);
	if (tmp_err != NULL) {
		g_object_unref (ksettings);
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		return;
	}
	cred_user = e_credentials_peek (credentials,
	                                E_CREDENTIALS_KEY_USERNAME);
	if (g_strcmp0 (kset_user, cred_user) != 0) {
		g_warning ("%s()[%u] username from argument and username in "
		           "KolabSettingsHandler do not match: %s vs. %s",
		           __func__, __LINE__, cred_user, kset_user);
	}

	cred_pwd = e_credentials_peek (credentials,
	                               E_CREDENTIALS_KEY_PASSWORD);

	kolab_util_backend_prepare_settings (ksettings,
	                                     NULL,
	                                     NULL,
	                                     NULL,
	                                     cred_pwd,
	                                     NULL,
	                                     NULL);
	g_object_unref (ksettings);

	online = e_backend_get_online (E_BACKEND (backend));
	(void) kolab_util_backend_deploy_mode_by_backend (priv->cal_koma,
	                                                  online,
	                                                  cancellable,
	                                                  &tmp_err);
	if (tmp_err != NULL) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
		return;
	}

	ok = cal_backend_kolab_notify_opened (self, &tmp_err);
	if (! ok) {
		kolab_util_calendar_err_to_edb_err (error, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
	}
}

/**
 * e_cal_backend_kolab_start_view:
 * @backend: A calendar backend.
 * @view: The view to be started.
 *
 * Starts a new live query on the given backend.
 */
static void
e_cal_backend_kolab_start_view (ECalBackend *backend,
                                EDataCalView *view)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;
	GList *uid_list = NULL;
	GList *it = NULL;
	GSList *iCal_objects = NULL;
	const gchar *query = NULL;
	gchar *sourcename = NULL;
	GError *tmp_err = NULL;
	GError *view_err = NULL;
	gboolean ok = FALSE;

	g_return_if_fail (E_IS_CAL_BACKEND_KOLAB (backend));
	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_mutex_lock (active_cal_views_lock);

	g_object_ref (view); /* unref()d in stop_view() */

	query = e_data_cal_view_get_text (view);

	sourcename = kolab_util_backend_get_relative_path_from_uri (priv->cal_uri);

	ok = kolab_util_calendar_cache_update_on_query (priv->cal_cache,
	                                                priv->cal_koma,
	                                                query,
	                                                priv->cal_uri,
	                                                NULL, /* FIXME: GCancellable */
	                                                &tmp_err);
	if (! ok)
		goto exit;

	uid_list = kolab_mail_access_query_uids (priv->cal_koma,
	                                         sourcename,
	                                         query,
	                                         &tmp_err);
	if (tmp_err != NULL)
		goto exit;

	for (it = uid_list; it != NULL; it = g_list_next (it)) {
		gchar *uid = it->data;
		ECalComponent *ecalcomp = NULL;
		ecalcomp = kolab_util_calendar_cache_get_object (priv->cal_cache,
		                                                 priv->cal_koma,
		                                                 priv->cal_uri,
		                                                 uid,
		                                                 TRUE,
		                                                 NULL, /* FIXME GCancellable */
		                                                 &tmp_err);
		if (tmp_err != NULL) {
			/* TODO Can we sensibly report a view error here?
			 *      Failure for one object does not necessarily
			 *      mean the next one will also fail. May need
			 *      to differentiate error types here
			 */
			g_warning ("%s()[%u]: %s",
			           __func__, __LINE__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		if (ecalcomp != NULL) {
			iCal_objects = g_slist_append (iCal_objects,
			                               e_cal_component_get_as_string (ecalcomp));
			g_object_unref (ecalcomp);
		}
	}

	e_data_cal_view_notify_objects_added (view,
	                                      iCal_objects);

 exit:

	if (tmp_err != NULL) {
		kolab_util_calendar_err_to_edb_err (&view_err, tmp_err, __func__, __LINE__);
		g_error_free (tmp_err);
	}

	e_data_cal_view_notify_complete (view, view_err);

	g_mutex_unlock (active_cal_views_lock);

	if (view_err != NULL)
		g_error_free (view_err);

	if (iCal_objects != NULL) {
		g_slist_foreach (iCal_objects, (GFunc) g_free, NULL);
		g_slist_free (iCal_objects);
	}
	if (sourcename != NULL)
		g_free (sourcename);
	if (uid_list != NULL)
		kolab_util_glib_glist_free (uid_list);

}

static void
e_cal_backend_kolab_stop_view (ECalBackend *backend,
                               EDataCalView *view)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;

	g_return_if_fail (E_IS_CAL_BACKEND_KOLAB (backend));
	g_return_if_fail (E_IS_DATA_CAL_VIEW (view));

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_mutex_lock (active_cal_views_lock);

	e_data_cal_view_notify_complete (view, NULL);

	g_object_unref (view); /* ref()d in start_view() */

	g_mutex_unlock (active_cal_views_lock);
}

static void
e_cal_backend_kolab_last_client_gone (ECalBackend *backend)
{
	ECalBackendKolab *self = NULL;
	ECalBackendKolabPrivate *priv = NULL;

	g_return_if_fail (E_IS_CAL_BACKEND_KOLAB (backend));

	self = E_CAL_BACKEND_KOLAB (backend);
	priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	/* FIXME implement me
	 *       This could be the place to clean
	 *       up the KolabMailAccess tables
	 */
	g_warning ("%s: FIXME implement me", __func__);
}

/*----------------------------------------------------------------------------*/
/* object/class init */

static void
e_cal_backend_kolab_init (ECalBackendKolab *backend)
{
	ECalBackendKolab *self = E_CAL_BACKEND_KOLAB (backend);
	ECalBackendKolabPrivate *priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	/* We're inside a thread, and each of our siblings
	 * (other configured ECalBackendKolab instances) is
	 * so as well. Depending on how the threads are
	 * started, checking for lock existence (and creating
	 * one if it is not) in *this* place can still be
	 * racy. Let's hope the threads are started one
	 * at a time, in which case this solution will work.
	 * The whole handling of living KolabMailAccess
	 * instances would better be moved to a separate
	 * library...
	 *
	 */
	if (koma_objects_lock == NULL)
		koma_objects_lock = g_mutex_new ();
	g_mutex_lock (koma_objects_lock);
	if (koma_objects == NULL)
		koma_objects = g_hash_table_new_full (g_direct_hash,
		                                      g_direct_equal,
		                                      g_free,
		                                      g_object_unref);
	g_mutex_unlock (koma_objects_lock);
	/* active cal views are shared in the same way,
	 * so we do the locking for them analogous
	 * to the KolabMailAccess instances
	 */
	if (active_cal_views_lock == NULL)
		active_cal_views_lock = g_mutex_new ();

	g_debug ("%s()[%u] called.", __func__, __LINE__);

	priv->cal_mode = 0;
	priv->cal_koma = NULL;
	priv->cal_cache = NULL;
	priv->user_email = NULL;
	priv->default_zone = NULL;
	priv->source_type = 0;
	priv->cal_uri = NULL;
	priv->mode_switch_err = NULL;

	g_signal_connect (E_BACKEND (backend), "notify::online", G_CALLBACK (cal_backend_kolab_signal_online_cb), NULL);
} /* e_cal_backend_kolab_init () */

static void
e_cal_backend_kolab_dispose (GObject *object)
{
	ECalBackendKolab *self = E_CAL_BACKEND_KOLAB (object);
	ECalBackendKolabPrivate *priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_debug ("%s()[%u] called.", __func__, __LINE__);

	g_warning ("%s()[%u] FIXME KolabMailAccess (table) disposal", __func__, __LINE__);
#if 0 /* FIXME (check g_object_toggle_ref cleanup callback) */
		if (priv->cal_koma != NULL) {
			g_object_unref (priv->cal_koma);
			priv->cal_koma = NULL;
		}
#endif
	if (priv->default_zone != NULL) {
		g_object_unref (priv->default_zone);
		priv->default_zone = NULL;
	}
	if (priv->cal_cache != NULL) {
		g_object_unref (priv->cal_cache);
		priv->cal_cache = NULL;
	}

	G_OBJECT_CLASS (e_cal_backend_kolab_parent_class)->dispose (object);
} /* e_cal_backend_kolab_dispose () */

static void
e_cal_backend_kolab_finalize (GObject *object)
{
	ECalBackendKolab *self = E_CAL_BACKEND_KOLAB (object);
	ECalBackendKolabPrivate *priv = E_CAL_BACKEND_KOLAB_PRIVATE (self);

	g_debug ("%s()[%u] called.", __func__, __LINE__);

	if (priv->user_email != NULL)
		g_free (priv->user_email);
	if (priv->cal_uri != NULL)
		g_free (priv->cal_uri);

	if (priv->mode_switch_err != NULL) {
		g_warning ("%s()[%u]: %s",
		           __func__, __LINE__, priv->mode_switch_err->message);
		g_error_free (priv->mode_switch_err);
	}

	G_OBJECT_CLASS (e_cal_backend_kolab_parent_class)->finalize (object);
} /* e_book_backend_kolab_finalize () */

/**
 * e_cal_backend_kolab_class_init:
 * @klass: An ECalBackendKolabClass type.
 *
 * Class initialisation function for the Kolab backend.
 */
static void
e_cal_backend_kolab_class_init (ECalBackendKolabClass *klass)
{
	GObjectClass* object_class = G_OBJECT_CLASS (klass);
	ECalBackendClass *backend_class = E_CAL_BACKEND_CLASS (klass);
	ECalBackendSyncClass* sync_class = E_CAL_BACKEND_SYNC_CLASS (klass);

	g_type_class_add_private (klass, sizeof (ECalBackendKolabPrivate));

	object_class->dispose = e_cal_backend_kolab_dispose;
	object_class->finalize = e_cal_backend_kolab_finalize;

	/* Backend parent class methods methods not covered in the sync backend part */
	backend_class->start_view = e_cal_backend_kolab_start_view;
	backend_class->stop_view = e_cal_backend_kolab_stop_view;
	backend_class->last_client_gone = e_cal_backend_kolab_last_client_gone;

	/* Sync backend class functions */
	sync_class->open_sync = e_cal_backend_kolab_open;
	sync_class->remove_sync = e_cal_backend_kolab_remove;
	sync_class->refresh_sync = e_cal_backend_kolab_refresh;
	sync_class->get_backend_property_sync = e_cal_backend_kolab_get_backend_property;
#if 0 /* probably not needed */
	sync_class->set_backend_property_sync = e_cal_backend_kolab_set_backend_property;
#endif
	sync_class->get_object_sync = e_cal_backend_kolab_get_object;
	sync_class->get_object_list_sync = e_cal_backend_kolab_get_object_list;
	sync_class->get_free_busy_sync = e_cal_backend_kolab_get_free_busy;
	sync_class->create_object_sync = e_cal_backend_kolab_create_object;
	sync_class->modify_object_sync = e_cal_backend_kolab_modify_object;
	sync_class->remove_object_sync = e_cal_backend_kolab_remove_object;
	sync_class->receive_objects_sync = e_cal_backend_kolab_receive_objects;
	sync_class->send_objects_sync = e_cal_backend_kolab_send_objects;
	sync_class->get_attachment_uris_sync = e_cal_backend_kolab_get_attachment_uris;
	sync_class->discard_alarm_sync = e_cal_backend_kolab_discard_alarm;
	sync_class->get_timezone_sync = e_cal_backend_kolab_get_timezone;
	sync_class->add_timezone_sync = e_cal_backend_kolab_add_timezone;
	sync_class->authenticate_user_sync = e_cal_backend_kolab_authenticate_user;

} /* e_cal_backend_kolab_class_init () */


/*----------------------------------------------------------------------------*/

#if 0 /* FIXME old */

/**
 * e_cal_backend_kolab_get_default_object:
 * @backend: An ECalBackendSync object.
 * @cal: An EDataCal object.
 * @object: Placeholder for returned object.
 *
 * Returns the default object via object placeholder.
 *
 * Returns: Status code.
 */
static ECalBackendSyncStatus
e_cal_backend_kolab_get_default_object (ECalBackendSync *backend,
                                        EDataCal *cal,
                                        gchar **object)
{
	/* FIXME: Remove experimental code related to summary*/
	ECalComponent *comp;
	ECalComponentText *summary = NULL;
	ECalComponentText *desc = NULL;
	GSList *desclist = NULL;
	gchar *tmpsum = g_strdup ("");
	gchar *tmploc = g_strdup ("");
	gchar *tmpdesc1 = g_strdup ("");
	(void) cal;

	summary = g_new0 (ECalComponentText, 1);
	summary->value=tmpsum;
	desc = g_new0 (ECalComponentText, 1);
	desc->value=tmpdesc1;
	desclist = g_slist_append(desclist, desc);

	comp = e_cal_component_new ();

	switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
	case ICAL_VEVENT_COMPONENT:
		e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
		e_cal_component_set_summary (comp, summary);
		e_cal_component_set_description_list (comp, desclist);
		e_cal_component_set_location (comp, tmploc);
		break;
	default:
		g_object_unref (comp);
		return GNOME_Evolution_Calendar_ObjectNotFound;
	}

	*object = e_cal_component_get_as_string (comp);

	if (comp)
		g_object_unref (comp);

	g_free (summary);
	g_free (tmploc);
	g_free (tmpdesc1);
	g_slist_foreach (desclist, (GFunc) g_free, NULL);
	g_slist_free (desclist);

	return GNOME_Evolution_Calendar_Success;
} /* e_cal_backend_kolab_get_default_object () */

/**
 * e_cal_backend_kolab_set_default_zone:
 * @backend: An ECalBackendSync object.
 * @cal: An EDataCal object.
 * @tz: Timezone object as string.
 *
 * Sets the default timezone for the calendar, which is used to resolve DATE and
 * floating DATE-TIME values.
 *
 * Returns: Status code.
 */
static ECalBackendSyncStatus
e_cal_backend_kolab_set_default_zone (ECalBackendSync *backend,
                                      EDataCal *cal,
                                      const gchar *tzobj)
{
	ECalBackendKolab *kolab = E_CAL_BACKEND_KOLAB (backend);
	ECalBackendKolabPrivate *priv = E_CAL_BACKEND_KOLAB_PRIVATE (kolab);
	ECalComponent *ecaltz = NULL;
	(void) cal;

	ecaltz = e_cal_component_new_from_string (tzobj);
	if (ecaltz == NULL)
		return GNOME_Evolution_Calendar_InvalidObject;

	if (priv->default_zone != NULL)
		g_object_unref (priv->default_zone);
	priv->default_zone = ecaltz;
	return GNOME_Evolution_Calendar_Success;
} /* e_cal_backend_kolab_set_default_zone () */

/**
 * e_cal_backend_internal_get_default_timezone:
 * @backend: A calendar backend.
 *
 * Requests the default timezone from the backend.
 *
 * Returns: The icaltimezone representation of the default timezone.
 */
static icaltimezone*
e_cal_backend_kolab_internal_get_default_timezone (ECalBackend *backend)
{
	ECalBackendKolab *kolab = E_CAL_BACKEND_KOLAB (backend);
	ECalBackendKolabPrivate *priv = E_CAL_BACKEND_KOLAB_PRIVATE (kolab);
	icaltimezone *icaltz = NULL;
	icalcomponent *icalcomp = NULL;

	icaltz = icaltimezone_new ();
	icalcomp = e_cal_component_get_icalcomponent (priv->default_zone);
	if (icalcomp == NULL)
		return NULL;
	icaltimezone_set_component (icaltz, icalcomp);

	return icaltz;
} /* e_cal_backend_kolab_internal_get_default_timezone () */

/**
 * e_cal_backend_kolab_get_mode:
 * @backend: A Kolab calendar backend.
 *
 * Queries the current calendar backend mode (local/remote).
 *
 * Returns: The current mode, the calendar is in.
 */
static CalMode
e_cal_backend_kolab_get_mode (ECalBackend *backend)
{
	ECalBackendKolab *kolab = E_CAL_BACKEND_KOLAB (backend);
	ECalBackendKolabPrivate *priv = E_CAL_BACKEND_KOLAB_PRIVATE (kolab);
	KolabMailAccessOpmodeID koma_mode = KOLAB_MAIL_ACCESS_OPMODE_INVAL;
	GError *error = NULL;

	if (priv->cal_koma == NULL) {
		g_warning ("%s()[%u]: KolabMailAccess object not existent.",
		           __func__, __LINE__);
		return CAL_MODE_INVALID;
	}
	koma_mode = kolab_mail_access_get_opmode (priv->cal_koma, &error);
	if (error != NULL) {
		g_warning ("%s()[%u]: %s",
		           __func__, __LINE__, error->message);
		g_error_free (error);
		return CAL_MODE_INVALID;
	}

	switch (koma_mode) {
	case KOLAB_MAIL_ACCESS_OPMODE_OFFLINE:
		return CAL_MODE_LOCAL;
		break;
	case KOLAB_MAIL_ACCESS_OPMODE_ONLINE:
		return CAL_MODE_REMOTE;
		break;
	case KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN:
	case KOLAB_MAIL_ACCESS_OPMODE_NEW:
	case KOLAB_MAIL_ACCESS_OPMODE_INVAL:
	case KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED:
	default:
		g_warning ("%s()[%u]: KolabMailAccess object's state unknown: %u.",
		           __func__, __LINE__, koma_mode);
		return CAL_MODE_INVALID;
	}
} /* e_cal_backend_kolab_get_mode () */

/**
 * e_cal_backend_kolab_set_mode:
 * @backend: A Kolab calendar backend.
 * @mode: Mode to change to.
 *
 * Sets the mode of the calendar backend to the given mode.
 */
static void
e_cal_backend_kolab_set_mode (ECalBackend *backend,
                              CalMode mode)
{
	ECalBackendKolab *kolab = E_CAL_BACKEND_KOLAB (backend);
	ECalBackendKolabPrivate *priv = E_CAL_BACKEND_KOLAB_PRIVATE (kolab);
	GError *error = NULL;
	KolabMailAccessOpmodeID koma_mode = KOLAB_MAIL_ACCESS_OPMODE_INVAL;
	KolabMailAccessOpmodeID tmp_mode = KOLAB_MAIL_ACCESS_OPMODE_INVAL;
	gchar *sourcename = NULL;
	KolabSettingsHandler *ksettings = NULL;
	gboolean ok;

	switch (mode) {
	case CAL_MODE_LOCAL:
		priv->cal_mode = CAL_MODE_LOCAL;
		koma_mode = KOLAB_MAIL_ACCESS_OPMODE_OFFLINE;
		break;
	case CAL_MODE_REMOTE:
		priv->cal_mode = CAL_MODE_REMOTE;
		koma_mode = KOLAB_MAIL_ACCESS_OPMODE_ONLINE;
		break;
	case CAL_MODE_ANY:
	case CAL_MODE_INVALID:
	default:
		g_assert_not_reached();
	}
	if (priv->cal_koma == NULL) {
		g_warning ("%s()[%u]: KolabMailAccess object not existent.",
		           __func__, __LINE__);
		return;
	}
	tmp_mode = kolab_mail_access_get_opmode (priv->cal_koma,
	                                         &error);
	if (error != NULL)
		{
			g_warning ("%s()[%u]: error getting mode: %s",
			           __func__, __LINE__, error->message);
			g_error_free (error);
			return;
		}

	if (tmp_mode < KOLAB_MAIL_ACCESS_OPMODE_OFFLINE) {
		return;
	}
	ok = kolab_mail_access_set_opmode (priv->cal_koma,
	                                   koma_mode,
	                                   &error);
	if (error != NULL)
		{
			g_warning ("%s()[%u]: error setting mode: %s",
			           __func__, __LINE__, error->message);
			g_error_free (error);
			error = NULL;
			return;
		}

	if (ok && (koma_mode < KOLAB_MAIL_ACCESS_OPMODE_ONLINE))
		return;

	sourcename = kolab_util_backend_get_relative_path_from_uri (priv->cal_uri);
	ok = kolab_mail_access_source_fbtrigger_needed (priv->cal_koma,
	                                                sourcename,
	                                                &error);
	if (error != NULL)
		{
			g_warning ("%s()[%u]: %s",
			           __func__, __LINE__, error->message);
			g_error_free (error);
			error = NULL;
			goto cleanup;
		}
	if (ok) {
		ksettings = kolab_mail_access_get_settings_handler (priv->cal_koma);
		kolab_util_calendar_toggle_pfb_trigger (ksettings,
		                                        sourcename);
		g_object_unref (ksettings);
	}
 cleanup:
	g_free (sourcename);
} /* e_cal_backend_kolab_set_mode () */

/**
 * e_cal_backend_kolab_set_koma_table:
 * @kolab: An ECalBackendKolab object.
 * @koma_objects: A GHashTable to contain the required KoMA instances.
 *
 * This method has to be called before any other method which accesses
 * Kolab infrastructure. In this case it should be called from
 * e-cal-backend-kolab-factory during creation of a new ECalBackendKolab
 * instance.
 */
void
e_cal_backend_kolab_set_koma_table (ECalBackendKolab *kolab,
                                    GHashTable *koma_objects)
{
	ECalBackendKolabPrivate *priv = E_CAL_BACKEND_KOLAB_PRIVATE (kolab);

	g_hash_table_ref (koma_objects);
	priv->koma_table = koma_objects;
}

#endif
