/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            kolab-mail-access.c
 *
 *  Tue Dec 21 16:02:54 2010
 *  Copyright  2010  Christian Hilberg
 *  <hilberg@kernelconcepts.de>
 ****************************************************************************/

/*
 * 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 <config.h>

#include <glib/gi18n-lib.h>

#include "kolab-util-backend.h"

#include <libekolabutil/kolab-util-glib.h>

#include "kolab-mail-info-db.h"
#include "kolab-mail-imap-client.h"
#include "kolab-mail-mime-builder.h"
#include "kolab-mail-side-cache.h"
#include "kolab-mail-synchronizer.h"
#include "kolab-mail-access.h"
#include "kolab-mail-handle-friend.h" /* "kolab-mail-handle.h" must already be included */

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

typedef struct _KolabMailAccessState KolabMailAccessState;
struct _KolabMailAccessState
{
	KolabMailAccessOpmodeID opmode;
	/* latest error ? */
};

typedef struct _KolabMailAccessPrivate KolabMailAccessPrivate;
struct _KolabMailAccessPrivate
{
	KolabSettingsHandler *ksettings;

	KolabMailImapClient *client;
	KolabMailInfoDb *infodb;
	KolabMailMimeBuilder *mimebuilder;
	KolabMailSideCache *sidecache;
	KolabMailSynchronizer *synchronizer;

	KolabMailAccessState *state;
	GHashTable *stranstbl;

	GHashTable *handles; /* foldername:uid:handle */
	GMutex *big_lock;
};

#define KOLAB_MAIL_ACCESS_PRIVATE(obj)  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KOLAB_TYPE_MAIL_ACCESS, KolabMailAccessPrivate))

G_DEFINE_TYPE (KolabMailAccess, kolab_mail_access, G_TYPE_OBJECT)

/*----------------------------------------------------------------------------*/
/* object state transition */

static GHashTable* kolab_mail_access_new_strans_table (void);

static KolabMailAccessState*
kolab_mail_access_state_new (void)
{
	KolabMailAccessState *state = g_new0 (KolabMailAccessState, 1);
	state->opmode = KOLAB_MAIL_ACCESS_OPMODE_NEW;
	return state;
}

static void
kolab_mail_access_state_free (KolabMailAccessState *state)
{
	if (state == NULL)
		return;

	g_free (state);
}

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

static void
kolab_mail_access_init (KolabMailAccess *object)
{
	KolabMailAccess *self = NULL;
	KolabMailAccessPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (object));

	self = object;
	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	priv->ksettings = NULL;

	priv->client = KOLAB_MAIL_IMAP_CLIENT (g_object_new (KOLAB_TYPE_MAIL_IMAP_CLIENT, NULL));
	priv->infodb = KOLAB_MAIL_INFO_DB (g_object_new (KOLAB_TYPE_MAIL_INFO_DB, NULL));
	priv->mimebuilder = KOLAB_MAIL_MIME_BUILDER (g_object_new (KOLAB_TYPE_MAIL_MIME_BUILDER, NULL));
	priv->sidecache = KOLAB_MAIL_SIDE_CACHE (g_object_new (KOLAB_TYPE_MAIL_SIDE_CACHE, NULL));
	priv->synchronizer = KOLAB_MAIL_SYNCHRONIZER (g_object_new (KOLAB_TYPE_MAIL_SYNCHRONIZER, NULL));

	priv->state = kolab_mail_access_state_new ();
	priv->stranstbl = kolab_mail_access_new_strans_table ();

	priv->handles = NULL;
	priv->big_lock = g_mutex_new ();
}

static void
kolab_mail_access_dispose (GObject *object)
{
	KolabMailAccess *self = NULL;
	KolabMailAccessPrivate *priv = NULL;

	self = KOLAB_MAIL_ACCESS (object);
	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* TODO set flag so no other function will work */

	if (priv->client != NULL)
		g_object_unref (priv->client);

	if (priv->infodb != NULL)
		g_object_unref (priv->infodb);

	if (priv->mimebuilder != NULL)
		g_object_unref (priv->mimebuilder);

	if (priv->sidecache != NULL)
		g_object_unref (priv->sidecache);

	if (priv->synchronizer != NULL)
		g_object_unref (priv->synchronizer);

	if (priv->ksettings != NULL) /* ref'd in configure() */
		g_object_unref (priv->ksettings);

	priv->client = NULL;
	priv->infodb = NULL;
	priv->mimebuilder = NULL;
	priv->sidecache = NULL;
	priv->synchronizer = NULL;
	priv->ksettings = NULL;

	G_OBJECT_CLASS (kolab_mail_access_parent_class)->dispose (object);
}

static void
kolab_mail_access_finalize (GObject *object)
{
	KolabMailAccess *self = NULL;
	KolabMailAccessPrivate *priv = NULL;

	self = KOLAB_MAIL_ACCESS (object);
	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	kolab_mail_access_state_free (priv->state);

	if (priv->handles)
		g_hash_table_destroy (priv->handles);
	if (priv->stranstbl)
		g_hash_table_destroy (priv->stranstbl);

	g_mutex_lock (priv->big_lock);
	g_mutex_unlock (priv->big_lock);
	g_mutex_free (priv->big_lock);

	G_OBJECT_CLASS (kolab_mail_access_parent_class)->finalize (object);
}

static void
kolab_mail_access_class_init (KolabMailAccessClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	/* GObjectClass *parent_class = G_OBJECT_CLASS (klass); */

	g_type_class_add_private (klass, sizeof (KolabMailAccessPrivate));

	object_class->dispose = kolab_mail_access_dispose;
	object_class->finalize = kolab_mail_access_finalize;
}

/*----------------------------------------------------------------------------*/
/* mailobject search/retrieve/store/delete */

static gchar*
kolab_mail_access_foldername_new_from_sourcename (KolabMailAccess *self,
                                                  const gchar *sourcename,
                                                  GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	gchar *foldername = NULL;
	gboolean exists = FALSE;
	GError *tmp_err = NULL;

	/* sourcename may be NULL */
	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	if (sourcename == NULL)
		return NULL;

	/* map Evo sourcename to Kolab foldername */
	foldername = kolab_util_backend_foldername_new_from_sourcename (sourcename,
	                                                                &tmp_err);
	if (foldername == NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	/* foldername may exist in KolabMailSideCache only */
	exists = kolab_mail_info_db_exists_foldername (priv->infodb,
	                                               foldername,
	                                               &tmp_err);
	/* error checks */
	if (tmp_err != NULL) {
		g_free (foldername);
		g_propagate_error (err, tmp_err);
		return NULL;
	}
	if (! exists) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INFODB_NOFOLDER,
		             _("Folder name '%s' is unknown to internal database"),
		             foldername);
		g_free (foldername);
		return NULL;
	}

	return foldername;
}

static gboolean
kolab_mail_access_local_handle_attach_summary (KolabMailAccess *self,
                                               KolabMailHandle *kmailhandle,
                                               GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	const KolabMailSummary *summary = NULL;
	KolabMailSummary *new_summary = NULL;
	const gchar *uid = NULL;
	const gchar *foldername = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	summary = kolab_mail_handle_get_summary (kmailhandle);
	if (summary != NULL)
		return TRUE;

	uid = kolab_mail_handle_get_uid (kmailhandle);
	if (uid == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_DATATYPE_INTERNAL,
		             _("Internal inconsistency detected: Invalid PIM Object handle, cannot get any UID"));
		return FALSE;
	}
	foldername = kolab_mail_handle_get_foldername (kmailhandle);
	if (foldername == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_DATATYPE_INTERNAL,
		             _("Internal inconsistency detected: Invalid PIM Object handle, cannot get foldername, UID '%s'"),
		             uid);
		return FALSE;
	}

	new_summary = kolab_mail_info_db_query_mail_summary (priv->infodb,
	                                                     uid,
	                                                     foldername,
	                                                     &tmp_err);
	if (tmp_err != NULL) {
		kolab_mail_summary_free (new_summary);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	if (new_summary == NULL) {
		/* in case the UID is a new one, we don't have a summary yet
		 * in InfoDb - but there could be one set on the MailHandle
		 * already which we must not delete by setting it NULL
		 */
		g_debug ("%s: UID (%s) no summary info found in DB",
		         __func__, uid);
		return TRUE;

	}

	ok = kolab_mail_handle_set_summary (kmailhandle,
	                                    new_summary,
	                                    &tmp_err);
	if (! ok) {
		kolab_mail_summary_free (new_summary);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

static gboolean
kolab_mail_access_update_handles_from_infodb (KolabMailAccess *self,
                                              const gchar *foldername,
                                              const gchar *sexp,
                                              GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GHashTable *handles_tbl = NULL;
	GList *changed_uids_lst = NULL;
	GList *changed_uids_lst_ptr = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (foldername != NULL);
	/* sexp may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* get handles table for folder (create if not existing) */
	handles_tbl = g_hash_table_lookup (priv->handles, foldername);
	if (handles_tbl == NULL) {
		handles_tbl = g_hash_table_new_full (g_str_hash,
		                                     g_str_equal,
		                                     g_free,
		                                     kolab_mail_handle_gdestroy);
		g_hash_table_insert (priv->handles,
		                     g_strdup (foldername),
		                     handles_tbl);
	}

	/* get list of all changed uids from InfoDb */
	changed_uids_lst = kolab_mail_info_db_query_changed_uids (priv->infodb,
	                                                          foldername,
	                                                          sexp,
	                                                          FALSE,
	                                                          &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/*
	 * - E<Cal|Book>BackendCache will be thrown away on EDS (re)start
	 * - deleted uids need to be reported once only
	 * - changed uids info can be aggregated any time
	 */

	changed_uids_lst_ptr = changed_uids_lst;
	while (changed_uids_lst_ptr != NULL) {
		const gchar *uid = (const gchar*)(changed_uids_lst_ptr->data);
		g_hash_table_remove (handles_tbl, uid);
		changed_uids_lst_ptr = g_list_next (changed_uids_lst_ptr);
	}

	kolab_util_glib_glist_free (changed_uids_lst);
	return TRUE;
}

static KolabMailHandle*
kolab_mail_access_local_handle_get_by_uid (KolabMailAccess *self,
                                           const gchar *uid,
                                           const gchar *foldername,
                                           gboolean err_if_not_exists,
                                           GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GHashTable *handles_tbl = NULL;
	KolabMailHandle *local_handle = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (uid != NULL);
	g_assert (foldername != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* get handles table (create if not existing) */
	handles_tbl = g_hash_table_lookup (priv->handles, foldername);
	if (handles_tbl == NULL) {
		handles_tbl = g_hash_table_new_full (g_str_hash,
		                                     g_str_equal,
		                                     g_free,
		                                     kolab_mail_handle_gdestroy);
		g_hash_table_insert (priv->handles,
		                     g_strdup (foldername),
		                     handles_tbl);
	}

	local_handle = g_hash_table_lookup (handles_tbl, uid);
	if (local_handle == NULL) {
		/* lookup UID in InfoDb, create new handle */
		local_handle = kolab_mail_synchronizer_handle_new_from_infodb (priv->synchronizer,
		                                                               uid,
		                                                               foldername,
		                                                               &tmp_err);
		if (tmp_err != NULL) {
			g_propagate_error (err, tmp_err);
			return NULL;
		}
	}
	if (err_if_not_exists && (local_handle == NULL)) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_NOTFOUND,
		             _("PIM Object not found in internal database, UID '%s', Folder '%s'"),
		             uid, foldername);
		return NULL;
	}

	if (local_handle == NULL) {
		g_debug ("%s: UID (%s) Folder (%s) not found in database",
		         __func__, uid, foldername);
	}

	return local_handle;
}

static KolabMailHandle*
kolab_mail_access_local_handle_get (KolabMailAccess *self,
                                    const KolabMailHandle *kmailhandle,
                                    GError **err)
{
	KolabMailHandle *local_handle = NULL;
	const gchar *uid = NULL;
	const gchar *foldername = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	uid = kolab_mail_handle_get_uid (kmailhandle);
	if (uid == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_DATATYPE_INTERNAL,
		             _("Internal inconsistency detected: Invalid PIM Object handle, cannot get any UID"));
		return NULL;
	}

	foldername = kolab_mail_handle_get_foldername (kmailhandle);
	if (foldername == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_DATATYPE_INTERNAL,
		             _("Internal inconsistency detected: Invalid PIM Object handle, cannot get foldername, UID '%s'"),
		             uid);
		return NULL;
	}

	local_handle = kolab_mail_access_local_handle_get_by_uid (self,
	                                                          uid,
	                                                          foldername,
	                                                          FALSE,
	                                                          &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	return local_handle;
}

/*----------------------------------------------------------------------------*/
/* mail/folder store/delete */

static gboolean
kolab_mail_access_local_store (KolabMailAccess *self,
                               KolabMailHandle *kmailhandle,
                               const gchar *foldername,
                               GCancellable *cancellable,
                               GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	KolabMailHandle *local_handle = NULL;
	KolabMailSummary *summary = NULL;
	KolabMailInfoDbRecord *record = NULL;
	gboolean online_fail = FALSE;
	gboolean offline_fail = FALSE;
	KolabMailAccessOpmodeID sync_opmode = KOLAB_MAIL_ACCESS_OPMODE_OFFLINE;
	GHashTable *imap_summaries = NULL;
	const gchar *uid = NULL;
	gboolean ok = FALSE;
	gboolean do_store = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	if (kmailhandle != NULL)
		g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* check whether we should create a new folder instead of storing a handle */
	if (kmailhandle == NULL) {
		g_warning ("%s: creating new folders not yet implemented",
		           __func__);
		g_assert_not_reached ();
	}

	/* get (and replace) local handle */
	local_handle = kolab_mail_access_local_handle_get (self,
	                                                   kmailhandle,
	                                                   &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	uid = kolab_mail_handle_get_uid (kmailhandle);
	if (local_handle != NULL) {
		GHashTable *handles_tbl = NULL;
		handles_tbl = g_hash_table_lookup (priv->handles,
		                                   foldername);
		if (handles_tbl == NULL) {
			handles_tbl = g_hash_table_new_full (g_str_hash,
			                                     g_str_equal,
			                                     g_free,
			                                     kolab_mail_handle_gdestroy);
			g_hash_table_insert (priv->handles,
			                     g_strdup (foldername),
			                     handles_tbl);
		}
		g_hash_table_replace (handles_tbl,
		                      g_strdup (uid),
		                      kmailhandle);
	}
	local_handle = kmailhandle;

	/* convert handle payload data to Kolab_conv_mail */
	ok = kolab_mail_handle_convert_eds_to_kconvmail (local_handle, &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* TODO can we switch from full summaries table to simple UID list here ? */
	imap_summaries = kolab_mail_imap_client_query_summaries (priv->client,
	                                                         foldername,
	                                                         NULL,
	                                                         TRUE, /* need to update folder */
	                                                         cancellable,
	                                                         &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	do_store = kolab_mail_synchronizer_transaction_prepare (priv->synchronizer,
	                                                        priv->state->opmode,
	                                                        KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_STORE,
	                                                        local_handle,
	                                                        foldername,
	                                                        imap_summaries,
	                                                        &record,
	                                                        cancellable,
	                                                        &tmp_err);
	if (imap_summaries != NULL) {
		g_hash_table_destroy (imap_summaries);
		imap_summaries = NULL;
	}
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* store transaction start */
	ok = kolab_mail_synchronizer_transaction_start (priv->synchronizer,
	                                                priv->state->opmode,
	                                                KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_STORE,
	                                                local_handle,
	                                                foldername,
	                                                record,
	                                                &tmp_err);
	if (! ok) {
		kolab_mail_info_db_record_free (record);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* offline store operation (also done in online mode, serves as a
	 * data loss failsafe guard in case online operation fails. If online
	 * operation completes successfully, this item is removed from side
	 * cache by transaction finalization
	 */

	if (do_store) {
		ok = kolab_mail_side_cache_store (priv->sidecache,
		                                  local_handle,
		                                  foldername,
		                                  &tmp_err);
		if (ok) {
			kolab_mail_handle_set_cache_location (local_handle,
			                                      KOLAB_OBJECT_CACHE_LOCATION_SIDE);
		} else {
			g_warning ("%s offline mode failure: %s",
			           __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
			offline_fail = TRUE;
		}
	}

	/* online store operation */
	if (do_store && (! offline_fail) && (priv->state->opmode == KOLAB_MAIL_ACCESS_OPMODE_ONLINE)) {
		ok = kolab_mail_imap_client_store (priv->client,
		                                   local_handle,
		                                   foldername,
		                                   FALSE, /* folder already updated */
		                                   cancellable,
		                                   &tmp_err);
		if (ok) {
			sync_opmode = KOLAB_MAIL_ACCESS_OPMODE_ONLINE;
			kolab_mail_handle_set_cache_location (local_handle,
			                                      KOLAB_OBJECT_CACHE_LOCATION_IMAP);
		} else {
			g_warning ("%s online mode failure: %s",
			           __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
			online_fail = TRUE;
		}
	}

	/* set handle incomplete */
	/* TODO check whether this is the right place */
	summary = kolab_mail_handle_get_summary_nonconst (local_handle);
	g_assert (summary != NULL);
	kolab_mail_summary_set_bool_field (summary,
	                                   KOLAB_MAIL_SUMMARY_BOOL_FIELD_COMPLETE,
	                                   FALSE);

	/* transaction finalization */
	if (offline_fail || online_fail) {
		ok = kolab_mail_synchronizer_transaction_abort (priv->synchronizer,
		                                                sync_opmode,
		                                                KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_STORE,
		                                                local_handle,
		                                                foldername,
		                                                record,
		                                                &tmp_err);
	} else {
		ok = kolab_mail_synchronizer_transaction_commit (priv->synchronizer,
		                                                 sync_opmode,
		                                                 KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_STORE,
		                                                 local_handle,
		                                                 foldername,
		                                                 record,
		                                                 &tmp_err);
	}

	kolab_mail_info_db_record_free (record);

	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	if (! do_store) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_SYNC_NOTSTORED,
		             _("PIM Object not stored due to selected synchronization strategy, UID '%s', Folder '%s'"),
		             uid, foldername);
		return FALSE;
	}

	return TRUE;
}

static gboolean
kolab_mail_access_local_delete (KolabMailAccess *self,
                                KolabMailHandle *kmailhandle,
                                const gchar *foldername,
                                GCancellable *cancellable,
                                GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	KolabObjectCacheLocation location = KOLAB_OBJECT_CACHE_LOCATION_NONE;
	KolabObjectCacheStatus status = KOLAB_OBJECT_CACHE_STATUS_INVAL;
	KolabMailAccessOpmodeID sync_opmode = KOLAB_MAIL_ACCESS_OPMODE_OFFLINE;
	KolabMailInfoDbRecord *record = NULL;
	GHashTable *imap_summaries = NULL;
	const gchar *uid = NULL;
	gchar *foldername_del = NULL;
	gboolean online_ok = TRUE;
	gboolean offline_ok = TRUE;
	gboolean do_del = TRUE;
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	if (kmailhandle != NULL)
		g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	if (kmailhandle != NULL) {
		/* check whether we have a summary for the mail handle */
		ok = kolab_mail_access_local_handle_attach_summary (self,
		                                                    kmailhandle,
		                                                    &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
		/* get handle details */
		uid = kolab_mail_handle_get_uid (kmailhandle);
		location = kolab_mail_handle_get_cache_location (kmailhandle);
		foldername_del = g_strdup (kolab_mail_handle_get_foldername (kmailhandle));
		if (g_strcmp0 (foldername, foldername_del) != 0) {
			g_set_error (err,
			             KOLAB_BACKEND_ERROR,
			             KOLAB_BACKEND_ERROR_DATATYPE_INTERNAL,
			             _("Internal inconsistency detected: PIM Object handle, UID %s, has folder name '%s' set, but supplied folder name to delete it from is '%s'"),
			             uid, foldername_del, foldername);
			if (foldername_del != NULL)
				g_free (foldername_del);
			return FALSE;
		}
	} else {
		/* get folder location bits */
		KolabFolderSummary *summary = NULL;
		summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
		                                                   foldername,
		                                                   &tmp_err);
		if (summary == NULL) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}

		location = kolab_folder_summary_get_uint_field (summary,
		                                                KOLAB_FOLDER_SUMMARY_UINT_FIELD_CACHE_LOCATION);
		kolab_folder_summary_free (summary);
		foldername_del = g_strdup (foldername);
	}

	/* TODO can we switch from full summaries table to simple UID list here ? */
	imap_summaries = kolab_mail_imap_client_query_summaries (priv->client,
	                                                         foldername,
	                                                         NULL,
	                                                         TRUE, /* need to update folder here */
	                                                         cancellable,
	                                                         &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	/* delete transaction preparation */
	do_del = kolab_mail_synchronizer_transaction_prepare (priv->synchronizer,
	                                                      priv->state->opmode,
	                                                      KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_DELETE,
	                                                      kmailhandle,
	                                                      foldername_del,
	                                                      imap_summaries,
	                                                      &record,
	                                                      cancellable,
	                                                      &tmp_err);
	if (imap_summaries != NULL) {
		g_hash_table_destroy (imap_summaries);
		imap_summaries = NULL;
	}
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* delete transaction start */
	ok = kolab_mail_synchronizer_transaction_start (priv->synchronizer,
	                                                priv->state->opmode,
	                                                KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_DELETE,
	                                                kmailhandle,
	                                                foldername_del,
	                                                record,
	                                                &tmp_err);
	if (! ok) {
		if (foldername_del != NULL)
			g_free (foldername_del);
		kolab_mail_info_db_record_free (record);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	if (do_del) {
		/* online delete operation */
		if ((location & KOLAB_OBJECT_CACHE_LOCATION_IMAP) &&
		    (priv->state->opmode == KOLAB_MAIL_ACCESS_OPMODE_ONLINE)) {
			if (kmailhandle != NULL) {
				status = kolab_mail_summary_get_uint_field (record->summary,
				                                            KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS);
				if (! (status & KOLAB_OBJECT_CACHE_STATUS_CHANGED)) {
					/* Delete on the server only if the object is unchanged.
					 * In case of server side changes, delete the local object
					 * and mark it as 'changed', so the server side object
					 * gets fetched anew from the server (this is to comply
					 * with Kontact reference behaviour)
					 */
					ok = kolab_mail_imap_client_delete (priv->client,
					                                    kmailhandle,
					                                    FALSE, /* try not only by IMAP UID */
					                                    FALSE, /* folder already updated */
					                                    cancellable,
					                                    &tmp_err);
				} else {
					ok = TRUE;
				}
			} else {
				/* TODO check whether we really want to delete
				 *      folders which have changes on the server
				 *      side (this will become an issue once we
				 *      really delete folders on the server)
				 */
				ok = kolab_mail_imap_client_delete_folder (priv->client,
				                                           foldername_del,
				                                           cancellable,
				                                           &tmp_err);
			}
			if (ok) {
				sync_opmode = KOLAB_MAIL_ACCESS_OPMODE_ONLINE;
				if (kmailhandle != NULL)
					kolab_mail_handle_unset_cache_location (kmailhandle,
					                                        KOLAB_OBJECT_CACHE_LOCATION_IMAP);
			} else {
				g_warning ("%s: online mode failure: %s",
				           __func__, tmp_err->message);
				g_error_free (tmp_err);
				tmp_err = NULL;
				online_ok = FALSE;
			}
		}
		/* offline delete operation (not done if online operation failed before) */
		if (online_ok && (location & KOLAB_OBJECT_CACHE_LOCATION_SIDE)) {
			if (kmailhandle != NULL)
				ok = kolab_mail_side_cache_delete (priv->sidecache,
				                                   kmailhandle,
				                                   &tmp_err);
			else
				ok = kolab_mail_side_cache_delete_folder (priv->sidecache,
				                                          foldername_del,
				                                          &tmp_err);
			if (ok) {
				if (kmailhandle != NULL)
					kolab_mail_handle_unset_cache_location (kmailhandle,
					                                        KOLAB_OBJECT_CACHE_LOCATION_SIDE);
			} else {
				g_warning ("%s offline mode failure: %s",
				           __func__, tmp_err->message);
				g_error_free (tmp_err);
				tmp_err = NULL;
				offline_ok = FALSE;
			}
		}
	}

	/* transaction finalization */
	if (online_ok && offline_ok)
		ok = kolab_mail_synchronizer_transaction_commit (priv->synchronizer,
		                                                 sync_opmode,
		                                                 KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_DELETE,
		                                                 kmailhandle,
		                                                 foldername_del,
		                                                 record,
		                                                 &tmp_err);
	else
		ok = kolab_mail_synchronizer_transaction_abort (priv->synchronizer,
		                                                sync_opmode,
		                                                KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_DELETE,
		                                                kmailhandle,
		                                                foldername_del,
		                                                record,
		                                                &tmp_err);
	kolab_mail_info_db_record_free (record);
	if (foldername_del != NULL)
		g_free (foldername_del);

	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* object state transition table and functions */

typedef gboolean (*KolabMailAccessStateTransitionFunc) (KolabMailAccess *self, GCancellable *cancellable, GError **err);

typedef struct _KolabMailAccessSTFWrap KolabMailAccessSTFWrap;
struct _KolabMailAccessSTFWrap {
	KolabMailAccessStateTransitionFunc fn;
};

/* this must be kept in sync with KolabMailAccessOpmodeID */
static gint opmode_values[] = {
	KOLAB_MAIL_ACCESS_OPMODE_INVAL,
	KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN,
	KOLAB_MAIL_ACCESS_OPMODE_NEW,
	KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED,
	KOLAB_MAIL_ACCESS_OPMODE_OFFLINE,
	KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
	KOLAB_MAIL_ACCESS_LAST_OPMODE
};

static gboolean
kolab_mail_access_strans_configured_offline (KolabMailAccess *self,
                                             GCancellable *cancellable,
                                             GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GList *folders_lst = NULL;
	GList *folders_lst_ptr = NULL;
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* safeguarding (so we can aggregate calls) */
	if (priv->state->opmode != KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_STATE_WRONG_FOR_OP,
		             _("Backend not in CONFIGURED state"));
		return FALSE;
	}

	/* set up local data structures */
	if (priv->handles != NULL)
		g_hash_table_destroy (priv->handles);
	priv->handles = g_hash_table_new_full (g_str_hash,
	                                       g_str_equal,
	                                       g_free,
	                                       kolab_util_glib_ghashtable_gdestroy);

	g_debug ("KolabMailAccess: changing state: configured->offline");

	/* bring up infrastructure */

	/* KolabMailImapClient *client */
	ok = kolab_mail_imap_client_bringup (priv->client,
	                                     cancellable,
	                                     &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* KolabMailInfoDb *infodb */
	ok = kolab_mail_info_db_bringup (priv->infodb,
	                                 &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* KolabMailMimeBuilder *mimebuilder */
	ok = kolab_mail_mime_builder_bringup (priv->mimebuilder,
	                                      &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* KolabMailSideCache *sidecache */
	ok = kolab_mail_side_cache_bringup (priv->sidecache,
	                                    &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* KolabMailSynchronizer *synchronizer */
	ok = kolab_mail_synchronizer_bringup (priv->synchronizer,
	                                      &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	ok = kolab_mail_synchronizer_info_sync (priv->synchronizer,
	                                        KOLAB_MAIL_ACCESS_OPMODE_OFFLINE,
	                                        NULL,
	                                        cancellable,
	                                        &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	folders_lst = kolab_mail_info_db_query_foldernames (priv->infodb,
	                                                    &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	folders_lst_ptr = folders_lst;
	while (folders_lst_ptr != NULL) {
		gchar *foldername = (gchar *)(folders_lst_ptr->data);
		ok = kolab_mail_access_update_handles_from_infodb (self,
		                                                   foldername,
		                                                   NULL, /* sexp */
		                                                   &tmp_err);
		if (! ok)
			break;
		folders_lst_ptr = g_list_next (folders_lst_ptr);
	}
	kolab_util_glib_glist_free (folders_lst);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	g_debug ("KolabMailAccess: new state: offline");

	priv->state->opmode = KOLAB_MAIL_ACCESS_OPMODE_OFFLINE;
	return TRUE;
}

static gboolean
kolab_mail_access_strans_offline_online (KolabMailAccess *self,
                                         GCancellable *cancellable,
                                         GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GList *folders_lst = NULL;
	GList *folders_lst_ptr = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* safeguarding (so we can aggregate calls) */
	if (priv->state->opmode != KOLAB_MAIL_ACCESS_OPMODE_OFFLINE) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_STATE_WRONG_FOR_OP,
		             _("Backend not in OFFLINE state"));
		return FALSE;
	}

	g_debug ("KolabMailAccess: changing state: offline->online");

	/* get infrastructure into online operation */

	/* get IMAP client into online mode */
	ok = kolab_mail_imap_client_go_online (priv->client,
	                                       cancellable,
	                                       &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* sync pim metadata */
	ok = kolab_mail_synchronizer_info_sync (priv->synchronizer,
	                                        KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
	                                        NULL,
	                                        cancellable,
	                                        &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* push all objects from SideCache to server,
	 * execute deferred offline deletion operations
	 */
	ok = kolab_mail_synchronizer_full_sync (priv->synchronizer,
	                                        KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
	                                        NULL,
	                                        cancellable,
	                                        &tmp_err);
	if (! ok) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	/* update hash table of KolabMailHandles */
	folders_lst = kolab_mail_info_db_query_foldernames (priv->infodb,
	                                                    &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	folders_lst_ptr = folders_lst;
	while (folders_lst_ptr != NULL) {
		gchar *foldername = (gchar *)(folders_lst_ptr->data);
		ok = kolab_mail_access_update_handles_from_infodb (self,
		                                                   foldername,
		                                                   NULL, /* sexp */
		                                                   &tmp_err);
		if (! ok)
			break;
		folders_lst_ptr = g_list_next (folders_lst_ptr);
	}
	kolab_util_glib_glist_free (folders_lst);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	g_debug ("KolabMailAccess: new state: online");
	priv->state->opmode = KOLAB_MAIL_ACCESS_OPMODE_ONLINE;

	return TRUE;
}

static gboolean
kolab_mail_access_strans_configured_online (KolabMailAccess *self,
                                            GCancellable *cancellable,
                                            GError **err)
{
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = kolab_mail_access_strans_configured_offline (self,
	                                                  cancellable,
	                                                  &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	ok = kolab_mail_access_strans_offline_online (self,
	                                              cancellable,
	                                              &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

static gboolean
kolab_mail_access_strans_online_offline (KolabMailAccess *self,
                                         GCancellable *cancellable,
                                         GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GList *folders_lst = NULL;
	GList *folders_lst_ptr = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* safeguarding (so we can aggregate calls) */
	if (priv->state->opmode != KOLAB_MAIL_ACCESS_OPMODE_ONLINE) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_STATE_WRONG_FOR_OP,
		             _("Backend not in ONLINE state"));
		return FALSE;
	}

	g_debug ("KolabMailAccess: changing state: online->offline");

	/* get infrastructure into offline operation */

	/* TODO KolabMailSynchronizer needs to sync
	 *      - KolabMailInfoDb
	 *      with
	 *      - KolabMailImapClient (still online) - folders, uids
	 *      - KolabMailSideCache - folders, uids
	 *
	 *      Action: slurp everything available for offline operation
	 *              first, then do push SideCache (should be empty,
	 *              but may contain objects which we were unable to
	 *	        push to the server before, so we try again here)
	 *      Goal:   empty SideCache (exception: objects which could
	 *	        not be pushed onto the server remain in SideCache)
	 *      Todo:   When to drop objects from SideCache which we
	 *	        permanently fail to push onto the server?
	 */
	ok = kolab_mail_synchronizer_info_sync (priv->synchronizer,
	                                        KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
	                                        NULL,
	                                        cancellable,
	                                        &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	/* push all objects from SideCache to server (though SideCache should
	 * be empty by now, it could contain objects for which the online
	 * operation failed before so we try again here to get them pushed
	 * onto the server)
	 */
	ok = kolab_mail_synchronizer_full_sync (priv->synchronizer,
	                                        KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
	                                        NULL,
	                                        cancellable,
	                                        &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* get IMAP client into offline mode */
	ok = kolab_mail_imap_client_go_offline (priv->client,
	                                        cancellable,
	                                        &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	folders_lst = kolab_mail_info_db_query_foldernames (priv->infodb,
	                                                    &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* update hash table of KolabMailHandles */
	folders_lst_ptr = folders_lst;
	while (folders_lst_ptr != NULL) {
		gchar *foldername = (gchar *)(folders_lst_ptr->data);
		ok = kolab_mail_access_update_handles_from_infodb (self,
		                                                   foldername,
		                                                   NULL, /* sexp */
		                                                   &tmp_err);
		if (! ok)
			break;
		folders_lst_ptr = g_list_next (folders_lst_ptr);
	}
	kolab_util_glib_glist_free (folders_lst);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	g_debug ("KolabMailAccess: new state: offline");

	priv->state->opmode = KOLAB_MAIL_ACCESS_OPMODE_OFFLINE;
	return TRUE;
}

static gboolean
kolab_mail_access_strans_offline_shutdown (KolabMailAccess *self,
                                           GCancellable *cancellable,
                                           GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* safeguarding (so we can aggregate calls) */
	if (priv->state->opmode != KOLAB_MAIL_ACCESS_OPMODE_OFFLINE) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_STATE_WRONG_FOR_OP,
		             _("Backend not in OFFLINE state"));
		return FALSE;
	}

	g_debug ("KolabMailAccess: changing state: offline->shutdown");

	/* shut down infrastructure */

	/* TODO KolabMailSynchronizer needs to sync
	 *      - KolabMailInfoDb
	 *      with
	 *      - KolabMailImapClient (offline) - folders, uids
	 *      - KolabMailSideCache - folders, uids
	 *
	 *      KolabMailInfoDb, KolabMailImapClient and
	 *      KolabMailSideCache still need to be up when
	 *      KolabMailSynchronizer is shut down.
	 *
	 *      Before shutdown operation, there should have been
	 *      a full_sync via the online->offline transition,
	 *      so an info_sync should suffice here
	 */
	ok = kolab_mail_synchronizer_shutdown (priv->synchronizer,
	                                       &tmp_err);
	if (! ok) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	/* KolabMailSideCache *sidecache */
	ok = kolab_mail_side_cache_shutdown (priv->sidecache,
	                                     &tmp_err);
	if (! ok) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	/* KolabMailMimeBuilder *mimebuilder */
	ok = kolab_mail_mime_builder_shutdown (priv->mimebuilder,
	                                       &tmp_err);
	if (! ok) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	/* KolabMailInfoDb *infodb */
	ok = kolab_mail_info_db_shutdown (priv->infodb,
	                                  &tmp_err);
	if (! ok) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	/* KolabMailImapClient *client */
	ok = kolab_mail_imap_client_shutdown (priv->client,
	                                      cancellable,
	                                      &tmp_err);
	if (! ok) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	g_debug ("KolabMailAccess: new state: shutdown");

	priv->state->opmode = KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN;
	return TRUE;
}

static gboolean
kolab_mail_access_strans_online_shutdown (KolabMailAccess *self,
                                          GCancellable *cancellable,
                                          GError **err)
{
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = kolab_mail_access_strans_online_offline (self,
	                                              cancellable,
	                                              &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	ok = kolab_mail_access_strans_offline_shutdown (self,
	                                                cancellable,
	                                                &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

static gboolean
kolab_mail_access_strans_shutdown_configured (KolabMailAccess *self,
                                              GCancellable *cancellable,
                                              GError **err)
{
	KolabMailAccessPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	(void)cancellable; /* FIXME */ /* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* safeguarding (so we can aggregate calls) */
	if (priv->state->opmode != KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_STATE_WRONG_FOR_OP,
		             _("Backend not in SHUTDOWN state"));
		return FALSE;
	}

	g_debug ("KolabMailAccess: changing state: shutdown->configured");

	/* TODO implement me */

	g_debug ("KolabMailAccess: new state: configured");

	priv->state->opmode = KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED;
	return TRUE;
}

static GHashTable*
kolab_mail_access_new_strans_table (void)
{
	GHashTable *stranstbl = NULL;
	GHashTable *tmp_tbl = NULL;
	KolabMailAccessSTFWrap *tmp_stf = NULL;

	/* state transition function table */
	stranstbl = g_hash_table_new_full (g_int_hash,
	                                   g_int_equal,
	                                   NULL,
	                                   kolab_util_glib_ghashtable_gdestroy);

	/* --- transitions from CONFIGURED state --- */
	tmp_tbl = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, g_free);
	/* configured -> offline */
	tmp_stf = g_new0 (KolabMailAccessSTFWrap, 1);
	tmp_stf->fn = kolab_mail_access_strans_configured_offline;
	g_hash_table_insert (tmp_tbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_OFFLINE],
	                     tmp_stf);
	/* configured -> online */
	tmp_stf = g_new0 (KolabMailAccessSTFWrap, 1);
	tmp_stf->fn = kolab_mail_access_strans_configured_online;
	g_hash_table_insert (tmp_tbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_ONLINE],
	                     tmp_stf);
	/* insert CONFIGURED */
	g_hash_table_insert (stranstbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED],
	                     tmp_tbl);

	/* --- transitions from OFFLINE state --- */
	tmp_tbl = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, g_free);
	/* offline -> online */
	tmp_stf = g_new0 (KolabMailAccessSTFWrap, 1);
	tmp_stf->fn = kolab_mail_access_strans_offline_online;
	g_hash_table_insert (tmp_tbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_ONLINE],
	                     tmp_stf);
	/* offline -> shutdown */
	tmp_stf = g_new0 (KolabMailAccessSTFWrap, 1);
	tmp_stf->fn = kolab_mail_access_strans_offline_shutdown;
	g_hash_table_insert (tmp_tbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN],
	                     tmp_stf);
	/* insert OFFLINE */
	g_hash_table_insert (stranstbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_OFFLINE],
	                     tmp_tbl);

	/* --- transitions from ONLINE state --- */
	tmp_tbl = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, g_free);
	/* online -> offline */
	tmp_stf = g_new0 (KolabMailAccessSTFWrap, 1);
	tmp_stf->fn = kolab_mail_access_strans_online_offline;
	g_hash_table_insert (tmp_tbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_OFFLINE],
	                     tmp_stf);
	/* online -> shutdown */
	tmp_stf = g_new0 (KolabMailAccessSTFWrap, 1);
	tmp_stf->fn = kolab_mail_access_strans_online_shutdown;
	g_hash_table_insert (tmp_tbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN],
	                     tmp_stf);
	/* insert ONLINE */
	g_hash_table_insert (stranstbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_ONLINE],
	                     tmp_tbl);

	/* --- transitions from SHUTDOWN state --- */
	tmp_tbl = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, g_free);
	/* shutdown -> configured */
	tmp_stf = g_new0 (KolabMailAccessSTFWrap, 1);
	tmp_stf->fn = kolab_mail_access_strans_shutdown_configured;
	g_hash_table_insert (tmp_tbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED],
	                     tmp_stf);
	/* insert SHUTDOWN */
	g_hash_table_insert (stranstbl,
	                     &opmode_values[KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN],
	                     tmp_tbl);

	return stranstbl;
}

static KolabMailAccessStateTransitionFunc
kolab_mail_access_get_strans_func (KolabMailAccess *self,
                                   KolabMailAccessOpmodeID opmode,
                                   GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	KolabMailAccessSTFWrap *stf = NULL;
	GHashTable *tbl = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert ((opmode > KOLAB_MAIL_ACCESS_OPMODE_INVAL) &&
	          (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	/* lookup transition table for current KolabMailAccess opmode */
	tbl = g_hash_table_lookup (priv->stranstbl,
	                           &opmode_values[priv->state->opmode]);
	if (tbl == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_STATE_WRONG_FOR_OP,
		             _("No transition function defined from operational mode %i"),
		             priv->state->opmode);
		return NULL;
	}

	/* lookup transition function for (current opmode)->(opmode) */
	stf = (KolabMailAccessSTFWrap*) g_hash_table_lookup (tbl, &opmode_values[opmode]);
	if (stf == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_STATE_WRONG_FOR_OP,
		             _("No transition function defined from operational mode %i to %i"),
		             priv->state->opmode,
		             opmode);
	}

	return stf->fn;
}

/*----------------------------------------------------------------------------*/
/* object config/status */

/**
 * kolab_mail_access_configure:
 * @self: a #KolabMailAccess instance
 * @ksettings: an already-configured #KolabSettingsHandler instance
 * @err: a #GError object (or NULL)
 *
 * Configures the object for operation. Must be called once after object
 * instantiation and before any other operation. On success, changes the
 * object status (see #KolabMailAccessOpmodeID).
 * The #KolabSettingsHandler is passed to all subordinate main objects.
 *
 * <itemizedlist>
 *   <listitem>Opmode precondition: %KOLAB_MAIL_ACCESS_OPMODE_NEW</listitem>
 *   <listitem>Opmode postcondition: %KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED</listitem>
 * </itemizedlist>
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_mail_access_configure (KolabMailAccess *self,
                             KolabSettingsHandler *ksettings,
                             GError **err)
{
	/* KolabSettingsHandler needs to live for the lifetime of
	 * KolabMailAccess
	 */

	KolabMailAccessPrivate *priv = NULL;
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode == KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_STATE_WRONG_FOR_OP,
		             _("Backend is shutting down"));
		ok = FALSE;
		goto exit;
	}

	/* cannot reconfigure a KolabMailAccess, create a new object instead */
	if (priv->state->opmode > KOLAB_MAIL_ACCESS_OPMODE_NEW)
		goto exit;

	if (priv->ksettings == NULL) {
		g_object_ref (ksettings); /* unref'd in dispose() */
		priv->ksettings = ksettings;
	}

	ok = kolab_mail_info_db_configure (priv->infodb,
	                                   priv->ksettings,
	                                   &tmp_err);
	if (! ok)
		goto exit;

	ok = kolab_mail_mime_builder_configure (priv->mimebuilder,
	                                        priv->ksettings,
	                                        &tmp_err);
	if (! ok)
		goto exit;

	ok = kolab_mail_imap_client_configure (priv->client,
	                                       priv->ksettings,
	                                       priv->mimebuilder,
	                                       &tmp_err);
	if (! ok)
		goto exit;

	ok = kolab_mail_side_cache_configure (priv->sidecache,
	                                      priv->ksettings,
	                                      priv->mimebuilder,
	                                      &tmp_err);
	if (! ok)
		goto exit;

	ok = kolab_mail_synchronizer_configure (priv->synchronizer,
	                                        priv->ksettings,
	                                        priv->client,
	                                        priv->infodb,
	                                        priv->sidecache,
	                                        priv->mimebuilder,
	                                        &tmp_err);
	if (! ok)
		goto exit;

	priv->state->opmode = KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED;

 exit:
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		ok = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return ok;
}


/**
 * kolab_mail_access_get_settings_handler:
 * @self: a #KolabMailAccess instance
 *
 * Gets the #KolabMailAccess instance's #KolabSettingsHandler member. The
 * #KolabSettingsHandler must have been set on the #KolabMailAccess via it's
 * kolab_mail_access_configure() instance. New values set on the settings
 * handler are seen by #KolabMailAccess and all of it's main subobjects.
 *
 * Call g_object_unref() on the #KolabSettingsHandler instance returned by
 * this function, once it is no longer needed.
 *
 * Returns: the #KolabSettingsHandler member of the #KolabMailAccess instance,
 *          or NULL (if kolab_mail_access_configure() has not been called before)
 */
KolabSettingsHandler*
kolab_mail_access_get_settings_handler (KolabMailAccess *self)
{
	KolabMailAccessPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	if (priv->state->opmode < KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		return NULL;

	/* if we're configured, the settings handler must be valid */

	g_object_ref (priv->ksettings);
	return priv->ksettings;
}

/**
 * kolab_mail_access_bringup:
 * @self: a #KolabMailAccess instance
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Gets the #KolabMailAccess object into operational (offline) mode,
 * bringing up all underlying infrastructure (databases, reality-checks,
 * metadata synchronization etc).
 * Must be called once after object configuration and before any further
 * operation. On success, changes the object status (see #KolabMailAccessOpmodeID).
 *
 * This is a shortcut for kolab_mail_access_set_opmode().
 *
 * <itemizedlist>
 *   <listitem>Opmode precondition: %KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED</listitem>
 *   <listitem>Opmode postcondition: %KOLAB_MAIL_ACCESS_OPMODE_OFFLINE</listitem>
 * </itemizedlist>
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_mail_access_bringup (KolabMailAccess *self,
                           GCancellable *cancellable,
                           GError **err)
{
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = kolab_mail_access_set_opmode (self,
	                                   KOLAB_MAIL_ACCESS_OPMODE_OFFLINE,
	                                   cancellable,
	                                   &tmp_err);
	if (tmp_err != NULL)
		g_propagate_error (err, tmp_err);

	return ok;
}

/**
 * kolab_mail_access_shutdown:
 * @self: a #KolabMailAccess instance
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Shuts down the #KolabMailAccess object from any operational mode,
 * bringing down and synchronizing the entire underlying infrastructure.
 * Must be called before object destruction. No further operation on the
 * object is valid unless kolab_mail_access_bringup() is called again.
 * On success, changes the object status (see #KolabMailAccessOpmodeID).
 *
 * This is a shortcut for kolab_mail_access_set_opmode().
 *
 * <itemizedlist>
 *   <listitem>Opmode precondition: %KOLAB_MAIL_ACCESS_OPMODE_ONLINE or %KOLAB_MAIL_ACCESS_OPMODE_OFFLINE</listitem>
 *   <listitem>Opmode postcondition: %KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN</listitem>
 * </itemizedlist>
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_mail_access_shutdown (KolabMailAccess *self,
                            GCancellable *cancellable,
                            GError **err)
{
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

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

	ok = kolab_mail_access_set_opmode (self,
	                                   KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN,
	                                   cancellable,
	                                   &tmp_err);
	if (tmp_err != NULL)
		g_propagate_error (err, tmp_err);

	return ok;
}

/*----------------------------------------------------------------------------*/
/* opmode getter/setter */

/**
 * kolab_mail_access_set_opmode:
 * @self: a #KolabMailAccess instance
 * @opmode: The operational mode to switch to (see #KolabMailAccessOpmodeID)
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Sets the operational mode for the object. To set an operational mode, an
 * internal opmode transition function is called. If the transition went on
 * successfully, the object's state is changed. The transition functions do
 * have side effects (changing state of the underlying infrastructure).
 * On success, changes the object status (see #KolabMailAccessOpmodeID).
 *
 * Setting an opmode #KolabMailAccess is already in is a no-op and the
 * function returns successfully.
 *
 * Allowed transitions are:
 * <itemizedlist>
 *   <listitem>%KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED --> %KOLAB_MAIL_ACCESS_OPMODE_OFFLINE</listitem>
 *   <listitem>%KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED --> %KOLAB_MAIL_ACCESS_OPMODE_ONLINE</listitem>
 *   <listitem>%KOLAB_MAIL_ACCESS_OPMODE_OFFLINE --> %KOLAB_MAIL_ACCESS_OPMODE_ONLINE</listitem>
 *   <listitem>%KOLAB_MAIL_ACCESS_OPMODE_ONLINE --> %KOLAB_MAIL_ACCESS_OPMODE_OFFLINE</listitem>
 *   <listitem>%KOLAB_MAIL_ACCESS_OPMODE_ONLINE --> %KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN</listitem>
 *   <listitem>%KOLAB_MAIL_ACCESS_OPMODE_OFFLINE --> %KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN</listitem>
 *   <listitem>%KOLAB_MAIL_ACCESS_OPMODE_SHUTDOWN --> %KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED</listitem>
 * </itemizedlist>
 *
 * (The 'SHUTDOWN --> CONFIGURED' transition type is not currently implemented.)
 *
 * Returns: TRUE on success,
 *          FALSE otherwise (with @err set)
 */
gboolean
kolab_mail_access_set_opmode (KolabMailAccess *self,
                              KolabMailAccessOpmodeID opmode,
                              GCancellable *cancellable,
                              GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	KolabMailAccessStateTransitionFunc strans_fn = NULL;
	gboolean strans_ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert ((opmode > KOLAB_MAIL_ACCESS_OPMODE_INVAL) &&
	          (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	/* check whether we are in required opmode already */
	if (opmode == priv->state->opmode)
		goto exit;

	/* get transition function for (current opmode) -> (opmode) */
	strans_fn = kolab_mail_access_get_strans_func (self, opmode, &tmp_err);
	if (strans_fn == NULL) {
		strans_ok = FALSE;
		goto exit;
	}

	/* call opmode state transition function */
	strans_ok = strans_fn (self,
	                       cancellable,
	                       &tmp_err);
	if (! strans_ok)
		goto exit;

	/* TODO implement me
	 *
	 * object(s) state
	 * - shutdown implies offline?
	 * - offline ->disconnect
	 * - online ->reconnect (->capability-check)
	 */

	/* TODO safeguard
	 *
	 * - must be set to KOLAB_BACKEND_STATE_SHUTDOWN
	 *   before disposal
	 * - shutdown state must
	 *   - disconnect IMAPX/Session
	 *   - flush everything in CamelKolabIMAPXStore to disk
	 *   - make sure no API function starts new work
	 * - check that this state is reached before disposing
	 *   CamelKolabSession and CamelKolabIMAPXStore
	 */

 exit:
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		strans_ok = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return strans_ok;
}

/**
 * kolab_mail_access_get_opmode:
 * @self: a #KolabMailAccess instance
 * @err: a #GError object (or NULL)
 *
 * Gets the operational mode the object is currently in
 * (see #KolabMailAccessOpmodeID).
 *
 * Returns: The current opmode of the object on success,
 *	    %KOLAB_MAIL_ACCESS_OPMODE_INVAL otherwise (with @err set)
 */
KolabMailAccessOpmodeID
kolab_mail_access_get_opmode (KolabMailAccess *self,
                              GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	KolabMailAccessOpmodeID opmode = KOLAB_MAIL_ACCESS_OPMODE_INVAL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_return_val_if_fail (err == NULL || *err == NULL, KOLAB_MAIL_ACCESS_OPMODE_INVAL);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	opmode =  priv->state->opmode;

	g_mutex_unlock (priv->big_lock);

	return opmode;
}


/*----------------------------------------------------------------------------*/
/* UID/KolabMailHandle API functions */

/**
 * kolab_mail_access_query_uids:
 * @self: a #KolabMailAccess instance
 * @sourcename: the name of an address book or calendar (or NULL)
 * @sexp: an EDS search expression string (or NULL)
 * @err: a #GError object (or NULL)
 *
 * Aggregates a list of UID strings known to #KolabMailAccess. If
 * @sexp is NULL, all UIDs are returned. Otherwise, only the UIDs
 * matching the search expression are returned. Any list generated
 * by some previous query will be invalidated.
 * Any handle storing or deletion will also invalidate the list returned.
 * Get a fresh list each time you need to operate on one.
 * Free the list via a call to kolab_util_glib_glist_free() when no longer needed.
 *
 * <itemizedlist>
 *   <listitem>List precondition: none</listitem>
 *   <listitem>List postcondition: any previously generated list is invalid</listitem>
 * </itemizedlist>
 *
 * (Searching by @sexp is not yet implemented, just supply NULL for now.)
 *
 * Returns: A list of UID strings on success,
 *          NULL otherwise (with @err set)
 */
GList* /* of gchar *kolab_uid */
kolab_mail_access_query_uids (KolabMailAccess *self,
                              const gchar *sourcename,
                              const gchar *sexp,
                              GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GList *uids = NULL;
	gchar *foldername = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (sourcename != NULL);
	/* sexp may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	foldername = kolab_mail_access_foldername_new_from_sourcename (self,
	                                                               sourcename,
	                                                               &tmp_err);
	if (foldername == NULL)
		goto exit;

	/* query KolabMailInfoDb for UIDs */
	uids = kolab_mail_info_db_query_uids (priv->infodb,
	                                      foldername,
	                                      sexp,
	                                      FALSE,
	                                      FALSE,
	                                      &tmp_err);
 exit:
	if (foldername != NULL)
		g_free (foldername);

	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		if (uids != NULL) {
			kolab_util_glib_glist_free (uids);
			uids = NULL;
		}
	}

	g_mutex_unlock (priv->big_lock);

	return uids;
}

/**
 * kolab_mail_access_query_changed_uids:
 * @self: a #KolabMailAccess instance
 * @sourcename: the name of an address book or calendar (or NULL)
 * @sexp: an EDS search expression string (or NULL)
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Aggregates a list of UID strings known to #KolabMailAccess for which
 * the PIM data has changed. Changed UIDs will each be reported once only,
 * so make sure to take proper care of these before freeing the list. However,
 * Free the list via a call to kolab_util_glib_glist_free() when no longer
 * needed.
 *
 * UID changes include deletion of PIM objects, so a call to
 * kolab_mail_access_get_handle() for a UID reported by this function may
 * yield NULL (which indicates that the UID in question no longer exists).
 *
 * <itemizedlist>
 *   <listitem>List precondition: none</listitem>
 *   <listitem>List postcondition: any previously generated list is invalid</listitem>
 * </itemizedlist>
 *
 * Returns: A list of UID strings or NULL on success (without @err set),
 *          NULL otherwise (with @err set)
 */
GList*
kolab_mail_access_query_changed_uids (KolabMailAccess *self,
                                      const gchar *sourcename,
                                      const gchar *sexp,
                                      GCancellable *cancellable,
                                      GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GList *changed_uids_lst = NULL;
	gchar *foldername = NULL;
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (sourcename != NULL);
	/* sexp may be NULL */
	(void)cancellable; /* FIXME */ /* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	foldername = kolab_mail_access_foldername_new_from_sourcename (self,
	                                                               sourcename,
	                                                               &tmp_err);
	if (tmp_err != NULL)
		goto exit;

	if (priv->state->opmode > KOLAB_MAIL_ACCESS_OPMODE_OFFLINE) {
		ok = kolab_mail_synchronizer_info_sync (priv->synchronizer,
		                                        KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
		                                        foldername,
		                                        cancellable,
		                                        &tmp_err);
		if (! ok)
			goto exit;

		ok = kolab_mail_access_update_handles_from_infodb (self,
		                                                   foldername,
		                                                   sexp,
		                                                   &tmp_err);
		if (! ok)
			goto exit;
	}

	changed_uids_lst = kolab_mail_info_db_query_changed_uids (priv->infodb,
	                                                          foldername,
	                                                          sexp,
	                                                          TRUE,
	                                                          &tmp_err);
 exit:
	if (foldername != NULL)
		g_free (foldername);

	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		if (changed_uids_lst != NULL) {
			kolab_util_glib_glist_free (changed_uids_lst);
			changed_uids_lst = NULL;
		}
	}

	g_mutex_unlock (priv->big_lock);

	return changed_uids_lst;
}

/**
 * kolab_mail_access_get_handle:
 * @self: a #KolabMailAccess instance
 * @uid: the UID to get the #KolabMailHandle for
 * @sourcename: the name of an address book or calendar (or NULL)
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Gets the #KolabMailHandle representing the PIM email associated with @uid.
 * The handle returned is owned by #KolabMailAccess at any time. It may not
 * yet be completed. Some operations work on completed mail handles only. Call
 * the kolab_mail_access_retrieve_handle() function to complete an incomplete
 * handle (that is, attach the actual PIM data to it).
 *
 * If a @sourcename is supplied, the @uid is searched for in the named source
 * only, so the return value may be NULL in that case, even if the @uid in
 * question exists (but in another source).
 *
 * <itemizedlist>
 *   <listitem>Handle precondition: none</listitem>
 *   <listitem>Handle postcondition: may be incomplete</listitem>
 * </itemizedlist>
 *
 * Returns: A (possibly incomplete) #KolabMailHandle on success,
 *          NULL otherwise (with @err set)
 */
const KolabMailHandle*
kolab_mail_access_get_handle (KolabMailAccess *self,
                              const gchar *uid,
                              const gchar *sourcename,
                              GCancellable *cancellable,
                              GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GHashTable *handles_tbl = NULL;
	KolabMailHandle *handle = NULL;
	gchar *foldername = NULL;
	const gchar *s_fn = NULL;
	gboolean handle_is_new = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (uid != NULL);
	g_assert (sourcename != NULL);
	(void)cancellable; /* FIXME */ /* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	/* map Evo calendar name to Kolab folder name */
	foldername = kolab_mail_access_foldername_new_from_sourcename (self,
	                                                               sourcename,
	                                                               &tmp_err);
	if (tmp_err != NULL)
		goto exit;

	handles_tbl = g_hash_table_lookup (priv->handles, foldername);
	if (handles_tbl == NULL) {
		handles_tbl = g_hash_table_new_full (g_str_hash,
		                                     g_str_equal,
		                                     g_free,
		                                     kolab_mail_handle_gdestroy);
		g_hash_table_insert (priv->handles,
		                     g_strdup (foldername),
		                     handles_tbl);
	}

	handle = g_hash_table_lookup (handles_tbl, uid);

	if (handle == NULL) {
		handle = kolab_mail_synchronizer_handle_new_from_infodb (priv->synchronizer,
		                                                         uid,
		                                                         foldername,
		                                                         &tmp_err);
		if (tmp_err != NULL)
			goto exit;

		handle_is_new = TRUE;
	}

	if (handle == NULL) {
		g_debug ("%s: UID (%s) Folder (%s) unknown",
		         __func__, uid, foldername);
		goto exit;
	}

	if (handle_is_new) {
		g_debug ("%s: UID (%s) created new handle from InfoDb",
		         __func__, uid);
		g_assert (KOLAB_IS_MAIL_HANDLE (handle));
		g_hash_table_insert (handles_tbl, g_strdup (uid), handle);
	}

	/* read summary foldername field */
	s_fn = kolab_mail_handle_get_foldername (handle);
	/* check whether foldernames match, if handle has one set */
	if (s_fn != NULL) {
		if (g_strcmp0 (foldername, s_fn) != 0) {
			handle = NULL;
		}
	}

 exit:
	if (foldername != NULL)
		g_free (foldername);

	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		handle = NULL;
	}

	g_mutex_unlock (priv->big_lock);

	return handle;
}

/**
 * kolab_mail_access_store_handle:
 * @self: a #KolabMailAccess instance
 * @kmailhandle: the #KolabMailHandle to store
 * @sourcename: the name of an address book or calendar (or NULL)
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Persistently stores the the PIM email data represented by #KolabMailHandle.
 * If #KolabMailAccess is in online operational mode, the data gets stored on
 * the Kolab server right away. If that fails or if #KolabMailAccess is in offline
 * operational mode, the data gets cached locally for later synchronization with
 * the server.
 *
 * To store a newly created #KolabMailHandle, a @sourcename must be supplied as
 * the initial destination for the handle.
 *
 * If a @sourcename is supplied, the PIM data gets stored in the named source. In case
 * the #KolabMailHandle already carries source information which differs from the
 * one supplied with @sourcename, it means we're moving to a different address
 * book or calendar. The UID in question will be removed from the previously
 * associated source in this case. The destination source must already exist for
 * this function to complete. If the source does not yet exist, create it with
 * kolab_mail_access_create_source(). If the operation went on successfully,
 * the #KolabMailHandle carries the new @sourcename.
 *
 * In order to store changes in PIM data, you'll need to get the Evolution data
 * from the handle, apply the changes, create a new handle from that, and store
 * that new handle. Existing data will then be dropped by #KolabMailAccess,
 * just make sure to keep the UID intact (otherwise, the object will be stored
 * as new).
 *
 * Within this operation, Evolution/EDS payload data is converted to Kolab format.
 * This means that this function may return unsuccessfully with a conversion
 * error set.
 *
 * On success, #KolabMailAccess takes ownership of the #KolabMailHandle supplied.
 * Do not try to g_object_unref() a mail handle which #KolabMailAccess has taken ownership of.
 * A successfully stored-away handle becomes invalid (this is, an invalid pointer).
 * Be VERY sure to get a new handle via kolab_mail_access_get_handle() before
 * trying any subsequent operation on it!
 *
 * <itemizedlist>
 *   <listitem>Handle precondition: must be complete</listitem>
 *   <listitem>Handle postcondition: now INVALID and owned by #KolabMailAccess</listitem>
 * </itemizedlist>
 *
 * Returns: TRUE on success,
 *          FALSE if local caching or conversion of the data failed (with @err set)
 */
gboolean
kolab_mail_access_store_handle (KolabMailAccess *self,
                                KolabMailHandle *kmailhandle,
                                const gchar *sourcename,
                                GCancellable *cancellable,
                                GError **err)
{
	/* we take ownership of KolabMailHandle here */

	KolabMailAccessPrivate *priv = NULL;
	GHashTable *handles_tbl = NULL;
	const KolabMailSummary *summary = NULL;
	gchar *uid = NULL;
	const gchar *handle_fn = NULL;
	KolabFolderTypeID handle_ft = KOLAB_FOLDER_TYPE_INVAL;
	gchar *sourcename_fn = NULL;
	KolabFolderTypeID sourcename_ft = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderSummary *folder_summary = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_assert (sourcename != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	/* folder check */

	/* sourcename data */
	sourcename_fn = kolab_mail_access_foldername_new_from_sourcename (self,
	                                                                  sourcename,
	                                                                  &tmp_err);
	if (sourcename_fn == NULL)
		goto exit;

	/* handle data */
	handle_fn = kolab_mail_handle_get_foldername (kmailhandle);
	if (handle_fn == NULL) {
		kolab_mail_handle_set_foldername (kmailhandle, sourcename_fn);
		handle_fn = kolab_mail_handle_get_foldername (kmailhandle);
	}

	summary = kolab_mail_handle_get_summary (kmailhandle);
	handle_ft = kolab_mail_summary_get_uint_field (summary,
	                                               KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_TYPE);

	folder_summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
	                                                          sourcename_fn,
	                                                          &tmp_err);
	if (folder_summary == NULL)
		goto exit;

	sourcename_ft = kolab_folder_summary_get_uint_field (folder_summary,
	                                                     KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_TYPE);
	/* folder type check */
	if ((sourcename_ft < handle_ft) || (sourcename_ft > (handle_ft + 1))) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("PIM Object handle, UID '%s', which has PIM type %i, cannot be stored in folder of mismatching PIM type %i"),
		             kolab_mail_handle_get_uid (kmailhandle),
		             handle_ft,
		             sourcename_ft);
		goto exit;
	}

	/* store operation */
	ok = kolab_mail_access_local_store (self,
	                                    kmailhandle,
	                                    sourcename_fn,
	                                    cancellable,
	                                    &tmp_err);
	if (! ok)
		goto exit;

	/* handle is now no longer 'complete', Kolab_conv_mail part
	 * and summary have been ripped out and stored in SideCache/ImapClient
	 * and InfoDb, respectively
	 */

	/* shrink priv->handles -- re-get the handle next time it's needed
	 * the handle is now invalid
	 */
	uid = g_strdup (kolab_mail_handle_get_uid (kmailhandle));
	handles_tbl = g_hash_table_lookup (priv->handles, handle_fn);
	if (handles_tbl != NULL)
		g_hash_table_remove (handles_tbl, uid);
	g_free (uid);

 exit:
	if (sourcename_fn != NULL)
		g_free (sourcename_fn);
	if (folder_summary != NULL)
		kolab_folder_summary_free (folder_summary);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		ok = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return ok;
}

/**
 * kolab_mail_access_retrieve_handle:
 * @self: a #KolabMailAccess instance
 * @kmailhandle: the #KolabMailHandle to retrieve PIM data for
 * @bulk: Whether or not this is a mass operation
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Retrieves the actual PIM data for a #KolabMailHandle. The PIM data is read
 * from the #KolabMailAccess offline caches. (TODO need to check whether we
 * should poll the server for updated PIM data for this UID if in online mode.)
 * The retrieved PIM data is attached to the mail handle in Kolab format.
 *
 * Within this operation, Kolab payload data is converted to Evolution/EDS format.
 * This means that this function may return unsuccessfully with a conversion
 * error set.
 *
 * <itemizedlist>
 *   <listitem>Handle precondition: none</listitem>
 *   <listitem>Handle postcondition: complete</listitem>
 * </itemizedlist>
 *
 * Returns: TRUE on success,
 *          FALSE if reading or converting the PIM data failed (with @err set)
 */
gboolean
kolab_mail_access_retrieve_handle (KolabMailAccess *self,
                                   const KolabMailHandle *kmailhandle,
                                   gboolean bulk,
                                   GCancellable *cancellable,
                                   GError **err)
{
	/* TODO in online mode, do we re-check with the server for
	 *      updated PIM data? Or should this be done at the
	 *      sync points (offline->online, online->offline)
	 *      only?
	 */

	KolabMailAccessPrivate *priv = NULL;
	KolabMailHandle *local_handle = NULL;
	KolabObjectCacheLocation location = KOLAB_OBJECT_CACHE_LOCATION_NONE;
	const gchar *uid = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	/* lookup mail handle in local databases */
	local_handle = kolab_mail_access_local_handle_get (self,
	                                                   kmailhandle,
	                                                   &tmp_err);
	if (tmp_err != NULL)
		goto exit;

	if (local_handle == NULL) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Internal inconsistency detected: Cannot get local PIM Object handle"));
		goto exit;
	}

	/* check whether we have a summary for the mail handle */
	ok = kolab_mail_access_local_handle_attach_summary (self,
	                                                    local_handle,
	                                                    &tmp_err);
	if (! ok)
		goto exit;

	uid = kolab_mail_handle_get_uid (local_handle);

	/* check cache location */
	location = kolab_mail_handle_get_cache_location (local_handle);

	/* if the handle is not in cache, it might have been freshly created
	 * by one of the new() functions. In this case, it will be complete
	 * and we're done
	 */
	if ((location == KOLAB_OBJECT_CACHE_LOCATION_NONE) &&
	    kolab_mail_handle_is_complete (local_handle)) {
		ok = TRUE;
		goto exit;
	}

	/* if the handle is cached in the SideCache, we'll retrieve
	 * this local data (no ImapClient lookup in this case, though
	 * the data might be stored there as well - we'll need a full-sync
	 * to see any server-side changes anyways...)
	 */
	if (location & KOLAB_OBJECT_CACHE_LOCATION_SIDE) {
		ok = kolab_mail_side_cache_retrieve (priv->sidecache,
		                                     local_handle,
		                                     &tmp_err);
		if (! ok)
			goto exit;

		ok = kolab_mail_handle_convert_kconvmail_to_eds (local_handle,
		                                                 &tmp_err);
		if (! ok)
			goto exit;

		g_debug ("%s: UID (%s) data retrieved from side cache",
		         __func__, uid);

		goto exit;
	}

	/* if the handle is in the ImapClient cache only, we use this data.
	 * no online operation necessary, unless the email has never been
	 * retrieved before
	 */
	if ((location & KOLAB_OBJECT_CACHE_LOCATION_IMAP) ||
	    (location == KOLAB_OBJECT_CACHE_LOCATION_NONE)) {
		gboolean update = !bulk; /* do not update IMAP folder on bulk operations */
		ok = kolab_mail_imap_client_retrieve (priv->client,
		                                      local_handle,
		                                      update,
		                                      cancellable,
		                                      &tmp_err);
		if (! ok)
			goto exit;

		/* FIXME update InfoDb, we may have a changed folder type
		 *       (default vs. non-default)
		 */
		ok = kolab_mail_handle_convert_kconvmail_to_eds (local_handle,
		                                                 &tmp_err);
		if (! ok)
			goto exit;

		g_debug ("%s: UID (%s) data retrieved from imap client",
		         __func__, uid);

		goto exit;
	}

	/* more cache location bit checks could go here */

	g_assert_not_reached (); /* TODO better handling of location bits here */

 exit:
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		ok = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return ok;
}

/**
 * kolab_mail_access_delete_handle:
 * @self: a #KolabMailAccess instance
 * @kmailhandle: the #KolabMailHandle to delete
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Deletes the PIM data associated with this #KolabMailHandle. The handle does
 * not need to be complete. If in online operational mode, the PIM data is deleted
 * from the server as well as from the local caches. In offline mode, the
 * data is marked for deletion and the UID is no longer returned in any query.
 * Trying to kolab_mail_access_get_handle() a deleted UID will result in a
 * NULL return value (with an error set), same holds true for all other
 * operations on that handle, including another attempt to delete the same
 * handle. Make sure to drop all references to a deleted handle you hold
 * (working with non-temporary handle references is not advisable anyway).
 *
 * In case of online failure, we fall back to offline behaviour. UIDs marked
 * as deleted in the local cache will be deleted from the server at the next
 * chance, that is, when a sync operation is triggerred (i.e. at online->offline
 * or offline->online operational mode switching).
 *
 * <itemizedlist>
 *   <listitem>Handle precondition: none</listitem>
 *   <listitem>Handle postcondition: invalid</listitem>
 * </itemizedlist>
 *
 * Returns: TRUE on success,
 *          FALSE if offline deletion failed (with @err set)
 */
gboolean
kolab_mail_access_delete_handle (KolabMailAccess *self,
                                 const KolabMailHandle *kmailhandle,
                                 GCancellable *cancellable,
                                 GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GHashTable *handles_tbl = NULL;
	KolabMailHandle *local_handle = NULL;
	gchar *uid = NULL;
	gchar *foldername = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	/* lookup mail handle in local databases */
	local_handle = kolab_mail_access_local_handle_get (self,
	                                                   kmailhandle,
	                                                   &tmp_err);
	if (tmp_err != NULL)
		goto exit;

	if (local_handle == NULL) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Internal inconsistency detected: Cannot get local PIM Object handle"));
		goto exit;
	}

	uid = g_strdup (kolab_mail_handle_get_uid (local_handle));
	foldername = g_strdup (kolab_mail_handle_get_foldername (kmailhandle));

	/* delete local handle */
	ok = kolab_mail_access_local_delete (self,
	                                     local_handle,
	                                     foldername,
	                                     cancellable,
	                                     &tmp_err);
	if (! ok) {
		g_warning ("%s: UID (%s) Folder (%s) error destroying: %s",
		           __func__, uid, foldername, tmp_err->message);
		goto exit;
	}

	handles_tbl = g_hash_table_lookup (priv->handles, foldername);
	if (handles_tbl != NULL)
		g_hash_table_remove (handles_tbl, uid);

	/* TODO should the deleted UID be recorded in changed_uids_lst? */

 exit:
	if (uid != NULL)
		g_free (uid);
	if (foldername != NULL)
		g_free (foldername);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		ok = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return ok;
}

/**
 * kolab_mail_access_delete_by_uid:
 * @self: a #KolabMailAccess instance
 * @uid: the UID of the #KolabMailHandle to delete
 * @sourcename: the name of the source to delete an object from
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Looks up a #KolabMailHandle by it's @uid and deletes the PIM data
 * associated with it. For operational details, see
 * kolab_mail_access_delete_handle().
 *
 * <itemizedlist>
 *   <listitem>Handle precondition: none</listitem>
 *   <listitem>Handle postcondition: invalid</listitem>
 * </itemizedlist>
 *
 * Returns: TRUE on success,
 *          FALSE if no PIM object found for @uid or
 *                if offline deletion failed (each with @err set)
 */
gboolean
kolab_mail_access_delete_by_uid (KolabMailAccess *self,
                                 const gchar *uid,
                                 const gchar *sourcename,
                                 GCancellable *cancellable,
                                 GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GHashTable *handles_tbl = NULL;
	KolabMailHandle *local_handle = NULL;
	gchar *foldername = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (uid != NULL);
	g_assert (sourcename != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	foldername = kolab_mail_access_foldername_new_from_sourcename (self,
	                                                               sourcename,
	                                                               &tmp_err);
	if (tmp_err != NULL)
		goto exit;

	/* lookup mail handle in local databases */
	local_handle = kolab_mail_access_local_handle_get_by_uid (self,
	                                                          uid,
	                                                          foldername,
	                                                          FALSE,
	                                                          &tmp_err);
	if (tmp_err != NULL)
		goto exit;

	if (local_handle == NULL) {
		g_debug ("%s: UID (%s) no longer exists", __func__, uid);
		ok = TRUE;
		goto exit;
	}

	/* delete local handle */
	ok = kolab_mail_access_local_delete (self,
	                                     local_handle,
	                                     foldername,
	                                     cancellable,
	                                     &tmp_err);
	if (! ok)
		goto exit;

	handles_tbl = g_hash_table_lookup (priv->handles, foldername);
	if (handles_tbl != NULL)
		g_hash_table_remove (handles_tbl, uid);

	/* TODO should the deleted UID be recorded in changed_uids_lst? */

 exit:
	if (foldername != NULL)
		g_free (foldername);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		ok = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return ok;
}

/*----------------------------------------------------------------------------*/
/* book/calendar query/create/delete */

/**
 * kolab_mail_access_query_sources:
 * @self: a #KolabMailAccess instance
 * @err: a #GError object (or NULL)
 *
 * Aggregates a list of source name strings known to #KolabMailAccess. Only
 * the source names of the configured context are returned.
 * Any list generated by some previous query will be invalidated.
 * Any source creation or deletion will also invalidate the list returned.
 * Get a fresh list each time you need to operate on one, unless you can be
 * sure there has been no change.
 *
 * Free the list via a call to kolab_util_glib_glist_free() when no longer
 * needed.
 *
 * <itemizedlist>
 *   <listitem>List precondition: none</listitem>
 *   <listitem>List postcondition: any previously generated list is invalid</listitem>
 * </itemizedlist>
 *
 * Returns: A list of source name strings on success,
 *          NULL otherwise (with @err set)
 *
 */
GList*
kolab_mail_access_query_sources (KolabMailAccess *self,
                                 GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	GList *sources = NULL;
	GList *folders = NULL;
	GList *folders_ptr = NULL;
	gchar *tmp_sn = NULL;
	const gchar *tmp_fn = NULL;
	GError *tmp_err = NULL;

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

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	/* query KolabMailInfoDb for existing foldernames in current context */
	folders = kolab_mail_info_db_query_foldernames (priv->infodb,
	                                                &tmp_err);
	if (tmp_err != NULL)
		goto exit;

	folders_ptr = folders;
	while (folders_ptr != NULL) {
		tmp_fn = (gchar*)(folders_ptr->data);
		if (tmp_fn == NULL) {
			g_warning ("%s: got NULL data pointer in folders list!",
			           __func__);
			goto folder_skip;
		}
		tmp_sn = kolab_util_backend_sourcename_new_from_foldername (tmp_fn,
		                                                            &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
			goto folder_skip;
		}
		if (tmp_sn == NULL) {
			g_warning ("%s: no sourcename for folder [%s]",
			           __func__, tmp_fn);
			goto folder_skip;
		}

		sources = g_list_prepend (sources, tmp_sn);

	folder_skip:
		folders_ptr = g_list_next (folders_ptr);
	}

 exit:

	if (folders != NULL)
		kolab_util_glib_glist_free (folders);

	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		if (sources != NULL) {
			kolab_util_glib_glist_free (sources);
			sources = NULL;
		}
	}

	g_mutex_unlock (priv->big_lock);

	return sources;
}

/**
 * kolab_mail_access_create_source:
 * @self: a #KolabMailAccess instance
 * @sourcename: the name of the source to create
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Creates a source (address book or calendar) of the given name. A source
 * needs to be created using this function prior to storing a #KolabMailHandle
 * into this source. If the @sourcename supplied already exists, the function
 * simply returns successfully.
 *
 * If #KolabMailAccess is in online operational mode, the source gets created on
 * the Kolab server right away. If that fails or if #KolabMailAccess is in offline
 * operational mode, the source gets created in the local cache only and is
 * synchronized with the server at the next synchronization point. If the source
 * was created locally only, you can nonetheless store #KolabMailHandle objects
 * in it.
 *
 * Returns: TRUE on success,
 *          FALSE if offline creation failed (with @err set)
 */
gboolean
kolab_mail_access_create_source (KolabMailAccess *self,
                                 const gchar *sourcename,
                                 GCancellable *cancellable,
                                 GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	gchar *foldername = NULL;
	gboolean exists = FALSE;
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (sourcename != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	/* map Evo sourcename to Kolab foldername */
	foldername = kolab_util_backend_foldername_new_from_sourcename (sourcename,
	                                                                &tmp_err);
	if (foldername == NULL)
		goto exit;

	/* check whether folder exists */
	exists = kolab_mail_info_db_exists_foldername (priv->infodb,
	                                               foldername,
	                                               &tmp_err);
	if (tmp_err != NULL) {
		ok = FALSE;
		goto exit;
	}

	if (exists)
		goto exit;

	/* create folder */
	ok = kolab_mail_access_local_store (self,
	                                    NULL,
	                                    foldername,
	                                    cancellable,
	                                    &tmp_err);
 exit:
	if (foldername != NULL)
		g_free (foldername);

	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		ok = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return ok;
}

/**
 * kolab_mail_access_delete_source:
 * @self: a #KolabMailAccess instance
 * @sourcename: the name of the source to delete
 * @cancellable: A cancellation stack
 * @err: a #GError object (or NULL)
 *
 * Deletes a source (address book or calendar) of the given name.
 * If the @sourcename supplied does not exist (within the current context),
 * the function returns with an error.
 *
 * CAUTION: Deleting a source means deleting all PIM data within it!
 * A deletion transaction which has been triggered successfully CANNOT be
 * cancelled!
 *
 * If #KolabMailAccess is in online operational mode, the source gets deleted on
 * the Kolab server right away. If that fails or if #KolabMailAccess is in offline
 * operational mode, the source gets marked for deletion in the local cache
 * and is not reported as existing from that moment on any longer. All locally
 * cached PIM data within this source will be lost, no matter the current
 * operational mode. At the next synchronization point, the source will be
 * deleted from the server. There is presently no way of aborting this
 * procedure, once it has been started.
 *
 * Returns: TRUE on success,
 *          FALSE if the @sourcename supplied does not exist or
 *                if offline deletion failed (both with @err set)
 */
gboolean
kolab_mail_access_delete_source (KolabMailAccess *self,
                                 const gchar *sourcename,
                                 GCancellable *cancellable,
                                 GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	gchar *foldername = NULL;
	gboolean exists = FALSE;
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (sourcename != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED)
		goto exit;

	/* map Evo sourcename to Kolab foldername */
	foldername = kolab_util_backend_foldername_new_from_sourcename (sourcename,
	                                                                &tmp_err);
	if (foldername == NULL) {
		ok = FALSE;
		goto exit;
	}

	/* check whether folder exists */
	exists = kolab_mail_info_db_exists_foldername (priv->infodb,
	                                               foldername,
	                                               &tmp_err);
	if (tmp_err != NULL) {
		ok = FALSE;
		goto exit;
	}

	if (! exists)
		goto exit;

	/* delete folder */
	ok = kolab_mail_access_local_delete (self,
	                                     NULL,
	                                     foldername,
	                                     cancellable,
	                                     &tmp_err);
 exit:
	if (foldername != NULL)
		g_free (foldername);

	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		ok = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return ok;
}

/**
 * kolab_mail_access_delete_source:
 * @self: a #KolabMailAccess instance
 * @sourcename: the name of the source to delete
 * @err: a #GError object (or NULL)
 *
 * Whether or not a free/busy generation trigger needs to be
 * sent to the Kolab server for a given source. A trigger is
 * needed in any event of a change to an event folder (other
 * folders do not need to be triggered since they do not provide
 * free/busy information). Sending the trigger may have been
 * delayed due to offline operation. When going online, triggers
 * for event folders which have seen offline changes need to
 * be sent.
 *
 * Returns: TRUE if a trigger needs to be sent for a given source,
 *          FALSE otherwise
 */
gboolean
kolab_mail_access_source_fbtrigger_needed (KolabMailAccess *self,
                                           const gchar *sourcename,
                                           GError **err)
{
	KolabMailAccessPrivate *priv = NULL;
	KolabFolderSummary *summary = NULL;
	KolabFolderTypeID folder_type = KOLAB_FOLDER_TYPE_INVAL;
	gchar *foldername = NULL;
	gboolean trigger_needed = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_ACCESS (self));
	g_assert (sourcename != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_ACCESS_PRIVATE (self);

	g_mutex_lock (priv->big_lock);

	/* do not try to trigger if in offline mode */
	if (priv->state->opmode <= KOLAB_MAIL_ACCESS_OPMODE_OFFLINE)
		goto exit;

	/* map Evo sourcename to Kolab foldername */
	foldername = kolab_util_backend_foldername_new_from_sourcename (sourcename,
	                                                                &tmp_err);
	if (foldername == NULL)
		goto exit;

	/* get folder type */
	summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
	                                                   foldername,
	                                                   &tmp_err);
	if (summary == NULL)
		goto exit;

	folder_type = kolab_folder_summary_get_uint_field (summary,
	                                                   KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_TYPE);
	/* F/B triggers only needed for event folders */
	if (! ((folder_type == KOLAB_FOLDER_TYPE_EVENT) ||
	       (folder_type == KOLAB_FOLDER_TYPE_EVENT_DEFAULT)))
		goto exit;

	/* TODO check whether the folder had offline objects which
	 *      have been synced onto the server (i.e. a trigger
	 *      does not need to be sent if this is not the case)
	 */

	trigger_needed = TRUE;

 exit:
	if (foldername != NULL)
		g_free (foldername);

	if (summary != NULL)
		kolab_folder_summary_free (summary);

	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		trigger_needed = FALSE;
	}

	g_mutex_unlock (priv->big_lock);

	return trigger_needed;
}

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