/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            kolab-mail-synchonizer.c
 *
 *  Thu Jan 20 11:49:12 2011
 *  Copyright  2011  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 <time.h>

#include <glib/gi18n-lib.h>

#include <libecal/e-cal-time-util.h>

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

#include "kolab-mail-summary.h"
#include "kolab-mail-info-db-record.h"
#include "kolab-types.h"

#include "kolab-mail-synchronizer.h"
#include "kolab-mail-handle-friend.h"

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

typedef struct _KolabMailSynchronizerPrivate KolabMailSynchronizerPrivate;
struct _KolabMailSynchronizerPrivate
{
	KolabSettingsHandler *ksettings;
	gboolean is_up;

	KolabMailImapClient *client;
	KolabMailInfoDb *infodb;
	KolabMailSideCache *sidecache;
	KolabMailMimeBuilder *mimebuilder;
};

#define KOLAB_MAIL_SYNCHRONIZER_PRIVATE(obj)  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KOLAB_TYPE_MAIL_SYNCHRONIZER, KolabMailSynchronizerPrivate))

G_DEFINE_TYPE (KolabMailSynchronizer, kolab_mail_synchronizer, G_TYPE_OBJECT)

/*----------------------------------------------------------------------------*/
/* local types */

typedef GList* (*KolabMailSynchronizerFolderQueryFunc) (gpointer, GError**);

typedef enum {
	KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_INFO = 0,
	KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_IMAP,
	KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_SIDE,
	KOLAB_MAIL_SYNCHRONIZER_FOLDER_LAST_WALK
} KolabMailSynchronizerFolderWalkID;

/*----------------------------------------------------------------------------*/
/* object init/destroy */

static void
kolab_mail_synchronizer_init (KolabMailSynchronizer *object)
{
	KolabMailSynchronizer *self = NULL;
	KolabMailSynchronizerPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (object));

	self = object;
	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	priv->ksettings = NULL;
	priv->is_up = FALSE;

	priv->client = NULL;
	priv->infodb = NULL;
	priv->sidecache = NULL;
	priv->mimebuilder = NULL;
}

static void
kolab_mail_synchronizer_dispose (GObject *object)
{
	KolabMailSynchronizer *self = NULL;
	KolabMailSynchronizerPrivate *priv = NULL;

	self = KOLAB_MAIL_SYNCHRONIZER (object);
	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

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

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

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

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

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

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

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

static void
kolab_mail_synchronizer_finalize (GObject *object)
{
	/* KolabMailSynchronizer *self = NULL; */
	/* KolabMailSynchronizerPrivate *priv = NULL; */

	/* self = KOLAB_MAIL_SYNCHRONIZER (object); */
	/* priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self); */

	/* TODO: Add deinitalization code here */

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

static void
kolab_mail_synchronizer_class_init (KolabMailSynchronizerClass *klass)
{
	GObjectClass* object_class = G_OBJECT_CLASS (klass);
	/* GObjectClass* parent_class = G_OBJECT_CLASS (klass); */

	g_type_class_add_private (klass, sizeof (KolabMailSynchronizerPrivate));

	object_class->dispose = kolab_mail_synchronizer_dispose;
	object_class->finalize = kolab_mail_synchronizer_finalize;
}

/*----------------------------------------------------------------------------*/
/* locals */

static KolabFolderTypeID
kolab_mail_synchronizer_get_foldertype (KolabMailSynchronizer *self,
                                        const gchar *info_fn,
                                        const gchar *imap_fn,
                                        GCancellable *cancellable,
                                        GError **err)

{
	KolabMailSynchronizerPrivate *priv = NULL;
	KolabFolderTypeID folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderTypeID info_folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderTypeID imap_folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderSummary *info_summary = NULL;
	KolabFolderSummary *imap_summary = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	/* info_fn may be NULL */
	/* imap_fn may be NULL */
	/* cancellable may be NULL */
	if ((info_fn != NULL) && (imap_fn != NULL))
		g_assert (g_strcmp0 (info_fn, imap_fn) == 0);
	g_return_val_if_fail (err == NULL || *err == NULL, KOLAB_FOLDER_TYPE_INVAL);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	if (info_fn != NULL)
		info_summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
		                                                        info_fn,
		                                                        &tmp_err);
	if (tmp_err != NULL) {
		if (tmp_err->code != KOLAB_BACKEND_ERROR_NOTFOUND)
			goto skip;
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	if (imap_fn != NULL)
		imap_summary = kolab_mail_imap_client_query_folder_summary (priv->client,
		                                                            imap_fn,
		                                                            cancellable,
		                                                            &tmp_err);
	if (tmp_err != NULL) {
		if (tmp_err->code != KOLAB_BACKEND_ERROR_NOTFOUND)
			goto skip;
		g_error_free (tmp_err);
		tmp_err = NULL;
	}

	if ((info_summary == NULL) && (imap_summary == NULL)) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_NOTFOUND,
		             _("Folder not found, neither in internal database as '%s', nor on the server as '%s'"),
		             info_fn, imap_fn);
		goto skip;
	}

	if (info_summary != NULL)
		info_folder_type = kolab_folder_summary_get_uint_field (info_summary,
		                                                        KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_TYPE);
	if (imap_summary != NULL)
		imap_folder_type =  kolab_folder_summary_get_uint_field (imap_summary,
		                                                         KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_TYPE);

	if ((imap_folder_type != KOLAB_FOLDER_TYPE_INVAL) &&
	    (info_folder_type != KOLAB_FOLDER_TYPE_INVAL)) {
		if (imap_folder_type != info_folder_type) {
			g_warning ("%s: Folder (%s) InfoDb/ImapClient folder type mismatch: (%u)/(%u)",
			           __func__, info_fn, info_folder_type, imap_folder_type);
		}
		/* TODO InfoDb folder type information not matching the ImapClient
		 *      information may mean that the folder was deleted on server
		 *      and re-created by the same name but with a different folder
		 *      type. While this is a corner case, we do need to take care
		 *      of it
		 */
	}

	/* TODO do proper merging here / check overwrite sequence
	 *      (compare with finding final_fn in
	 *      kolab_mail_synchronizer_folders_update_summary())
	 */
	if (imap_folder_type != KOLAB_FOLDER_TYPE_INVAL)
		folder_type = imap_folder_type;

	if (info_folder_type != KOLAB_FOLDER_TYPE_INVAL)
		folder_type = info_folder_type;

	if (folder_type == KOLAB_FOLDER_TYPE_INVAL)
		folder_type = KOLAB_FOLDER_TYPE_UNKNOWN;

 skip:
	if (info_summary != NULL)
		kolab_folder_summary_free (info_summary);
	if (imap_summary != NULL)
		kolab_folder_summary_free (imap_summary);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return KOLAB_FOLDER_TYPE_INVAL;
	}
	return folder_type;
}

static guint64
kolab_mail_synchronizer_get_uidvalidity (KolabMailSynchronizer *self,
                                         const gchar *info_fn,
                                         const gchar *imap_fn,
                                         GError **err)

{
	KolabMailSynchronizerPrivate *priv = NULL;
	guint64 uidvalidity = 0;
	guint64 info_uidvalidity = 0;
	guint64 imap_uidvalidity = 0;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	/* info_fn may be NULL */
	/* imap_fn may be NULL */
	if ((info_fn != NULL) && (imap_fn != NULL))
		g_assert (g_strcmp0 (info_fn, imap_fn) == 0);
	g_return_val_if_fail (err == NULL || *err == NULL, 0);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	/* TODO Take care of situation where both folders
	 *      do not exist (can this happen?)
	 */

	if (info_fn != NULL) {
		KolabFolderSummary *summary = NULL;
		summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
		                                                   info_fn,
		                                                   &tmp_err);
		if (tmp_err != NULL) {
			if (tmp_err->code != KOLAB_BACKEND_ERROR_NOTFOUND)
				goto skip;
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		if (summary != NULL) {
			info_uidvalidity = kolab_folder_summary_get_uint64_field (summary,
			                                                          KOLAB_FOLDER_SUMMARY_UINT64_FIELD_UIDVALIDITY);
			kolab_folder_summary_free (summary);
		} else {
			info_uidvalidity = 0;
		}
	}

	if (imap_fn != NULL) {
		KolabFolderSummary *summary = NULL;
		summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
		                                                   imap_fn,
		                                                   &tmp_err);
		if (tmp_err != NULL) {
			if (tmp_err->code != KOLAB_BACKEND_ERROR_NOTFOUND)
				goto skip;
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		if (summary != NULL) {
			imap_uidvalidity = kolab_folder_summary_get_uint64_field (summary,
			                                                          KOLAB_FOLDER_SUMMARY_UINT64_FIELD_UIDVALIDITY);
			kolab_folder_summary_free (summary);
		} else {
			imap_uidvalidity = 0;
		}
	}

	/* initial value for uidvalidity in summary is set to 0 (zero) */
	if (info_uidvalidity > 0)
		uidvalidity = info_uidvalidity;
	if (imap_uidvalidity > 0)
		uidvalidity = imap_uidvalidity;

	g_debug ("%s: InfoDb (%s) ImapClient (%s) UIDVALIDITY (%lld)",
	         __func__, info_fn, imap_fn, uidvalidity);

 skip:
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return 0;
	}
	return uidvalidity;
}

static gboolean
kolab_mail_synchronizer_folders_uidvalidity_changed (KolabMailSynchronizer *self,
                                                     KolabFolderSummary *summary)
{
	KolabObjectCacheStatus status = KOLAB_OBJECT_CACHE_STATUS_INVAL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (summary != NULL);

	status = kolab_folder_summary_get_uint_field (summary,
	                                              KOLAB_FOLDER_SUMMARY_UINT_FIELD_CACHE_STATUS);
	if (status & KOLAB_OBJECT_CACHE_STATUS_CHANGED)
		return TRUE;

	return FALSE;
}

static void
kolab_mail_synchronizer_folders_clear_changed (KolabMailSynchronizer *self,
                                               KolabFolderSummary *summary)
{
	KolabObjectCacheStatus status = KOLAB_OBJECT_CACHE_STATUS_NONE;
	guint64 uidvalidity = 0;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (summary != NULL);

	status = kolab_folder_summary_get_uint_field (summary,
	                                              KOLAB_FOLDER_SUMMARY_UINT_FIELD_CACHE_STATUS);
	uidvalidity = kolab_folder_summary_get_uint64_field (summary,
	                                                     KOLAB_FOLDER_SUMMARY_UINT64_FIELD_UIDVALIDITY);

	status = status & (~(guint)KOLAB_OBJECT_CACHE_STATUS_CHANGED);

	kolab_folder_summary_set_uint_field (summary,
	                                     KOLAB_FOLDER_SUMMARY_UINT_FIELD_CACHE_STATUS,
	                                     status);
	kolab_folder_summary_set_uint64_field (summary,
	                                       KOLAB_FOLDER_SUMMARY_UINT64_FIELD_UIDVALIDITY_SYNC,
	                                       uidvalidity);
}

static gboolean
kolab_mail_synchronizer_folders_update_summary (KolabMailSynchronizer *self,
                                                KolabMailAccessOpmodeID opmode,
                                                const gchar *info_fn,
                                                const gchar *imap_fn,
                                                const gchar *side_fn,
                                                GCancellable *cancellable,
                                                GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	KolabFolderSummary *summary = NULL;
	KolabObjectCacheLocation location = KOLAB_OBJECT_CACHE_LOCATION_NONE;
	KolabObjectCacheStatus status = KOLAB_OBJECT_CACHE_STATUS_INVAL;
	KolabFolderTypeID folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderContextID context = KOLAB_FOLDER_CONTEXT_INVAL;
	guint64 uidval_srv = 0; /* remote (IMAPX) uidvalidity */
	guint64 uidval_sum = 0; /* InfoDb summary uidvalidity */
	const gchar *final_fn = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE);
	/* info_fn may be NULL */
	/* imap_fn may be NULL */
	/* side_fn may be NULL */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	/* TODO
	 *
	 * KOLAB_MAIL_ACCESS_OPMODE_OFFLINE
	 * - sync-op is sanity-checking only (reality-check)
	 * - InfoDb, SideCache, ImapClient bits should be in sync
	 * - if not, it's an error condition --> try to recover?
	 * - InfoDb must be updated to contain only information
	 *   which is in-sync with SideCache and ImapClient
	 *
	 * KOLAB_MAIL_ACCESS_OPMODE_ONLINE
	 * - sanity-check InfoDb<->SideCache already done for offline mode
	 * - ImapClient may now hold updated information
	 *   - folder uidvalidity change
	 *     (taken care of per-uid)
	 *   - folder removal
	 */

	/* TODO check overwrite sequence
	 *      (in which other places do we do that?)
	 */
	final_fn = info_fn;
	if (final_fn == NULL)
		final_fn = imap_fn;
	if (final_fn == NULL)
		final_fn = side_fn;
	if (final_fn == NULL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_INTERNAL,
		             _("Internal inconsistency detected: Could not get a folder name, no info from internal database, write cache, or the IMAP cache"));
		return FALSE;
	}

	/* more sanity checking */
	if (info_fn != NULL)
		g_assert (g_strcmp0 (final_fn, info_fn) == 0);
	if (imap_fn != NULL)
		g_assert (g_strcmp0 (final_fn, imap_fn) == 0);
	if (side_fn != NULL)
		g_assert (g_strcmp0 (final_fn, side_fn) == 0);

	/* aggregate folder information */

	/* cache location */
	if (imap_fn != NULL)
		location |= KOLAB_OBJECT_CACHE_LOCATION_IMAP;
	if (side_fn != NULL)
		location |= KOLAB_OBJECT_CACHE_LOCATION_SIDE;
	/* can happen only in InfoDb walk, and is taken care of
	 * in kolab_mail_synchronizer_folders_update_walk(). If
	 * we see this here, it is a bug
	 */
	if (location == KOLAB_OBJECT_CACHE_LOCATION_NONE)
		g_assert_not_reached ();

	/* folder type and context */
	folder_type = kolab_mail_synchronizer_get_foldertype (self,
	                                                      info_fn,
	                                                      imap_fn,
	                                                      cancellable,
	                                                      &tmp_err);
	if (folder_type == KOLAB_FOLDER_TYPE_INVAL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	context = kolab_util_folder_type_map_to_context_id (folder_type);
	if  (context == KOLAB_FOLDER_CONTEXT_INVAL) {
		g_set_error (err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_DATATYPE_INTERNAL,
		             _("Internal inconsistency detected, Folder type %i does not map to any known folder context"),
		             folder_type);
		return FALSE;
	}

	/* folder uidvalidity change (only in online mode) */
	if (opmode > KOLAB_MAIL_ACCESS_OPMODE_OFFLINE) {
		uidval_srv = kolab_mail_synchronizer_get_uidvalidity (self,
		                                                      info_fn,
		                                                      imap_fn,
		                                                      &tmp_err);
		if (tmp_err != NULL) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	}

	/* make sure we have a folder summary */
	summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
	                                                   final_fn,
	                                                   &tmp_err);
	if (tmp_err != NULL) {
		if (tmp_err->code != KOLAB_BACKEND_ERROR_NOTFOUND) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
		g_error_free (tmp_err);
		tmp_err = NULL;
	}
	if (summary == NULL)
		summary = kolab_folder_summary_new ();

	/* set common summary data */
	kolab_folder_summary_set_char_field (summary,
	                                     KOLAB_FOLDER_SUMMARY_CHAR_FIELD_FOLDERNAME,
	                                     g_strdup (final_fn));
	kolab_folder_summary_set_uint_field (summary,
	                                     KOLAB_FOLDER_SUMMARY_UINT_FIELD_CACHE_LOCATION,
	                                     location);
	kolab_folder_summary_set_uint_field (summary,
	                                     KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_TYPE,
	                                     folder_type);
	kolab_folder_summary_set_uint_field (summary,
	                                     KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_CONTEXT,
	                                     context);

	/* update uidvalidity summary data (only in online mode) */
	if (opmode > KOLAB_MAIL_ACCESS_OPMODE_OFFLINE) {
		status = kolab_folder_summary_get_uint_field (summary,
		                                              KOLAB_FOLDER_SUMMARY_UINT_FIELD_CACHE_STATUS);
		uidval_sum = kolab_folder_summary_get_uint64_field (summary,
		                                                    KOLAB_FOLDER_SUMMARY_UINT64_FIELD_UIDVALIDITY);
		if (uidval_sum != uidval_srv)
			status |= KOLAB_OBJECT_CACHE_STATUS_CHANGED;

		kolab_folder_summary_set_uint_field (summary,
		                                     KOLAB_FOLDER_SUMMARY_UINT_FIELD_CACHE_STATUS,
		                                     status);
		kolab_folder_summary_set_uint64_field (summary,
		                                       KOLAB_FOLDER_SUMMARY_UINT64_FIELD_UIDVALIDITY,
		                                       uidval_srv);
		kolab_folder_summary_set_uint64_field (summary,
		                                       KOLAB_FOLDER_SUMMARY_UINT64_FIELD_UIDVALIDITY_SYNC,
		                                       uidval_sum);
	}

	/* update InfoDb with new folder summary data */
	ok = kolab_mail_info_db_update_folder_summary (priv->infodb,
	                                               summary,
	                                               &tmp_err);
	kolab_folder_summary_free (summary);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

static gboolean
kolab_mail_synchronizer_folders_update_walk (KolabMailSynchronizer *self,
                                             KolabMailAccessOpmodeID opmode,
                                             KolabMailSynchronizerFolderWalkID walk_id,
                                             GList *folders_lst,
                                             GHashTable *imap_folders_tbl,
                                             GHashTable *side_folders_tbl,
                                             GCancellable *cancellable,
                                             GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	GList *lst_ptr = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE);
	g_assert (walk_id < KOLAB_MAIL_SYNCHRONIZER_FOLDER_LAST_WALK);
	/* folders_lst may be NULL      */
	/* imap_folders_tbl may be NULL */
	/* side_folders_tbl may be NULL */
	/* cancellable may be NULL      */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	lst_ptr = folders_lst;
	while (lst_ptr != NULL) {
		const gchar *info_fn = NULL;
		const gchar *imap_fn = NULL;
		const gchar *side_fn = NULL;
		gboolean ok = FALSE;

		switch (walk_id) {
		case KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_INFO:
			info_fn = (const gchar *)(lst_ptr->data);
			if (imap_folders_tbl != NULL)
				imap_fn = g_hash_table_lookup (imap_folders_tbl, info_fn);
			if (side_folders_tbl != NULL)
				side_fn = g_hash_table_lookup (side_folders_tbl, info_fn);
			if ((imap_fn == NULL) && (side_fn == NULL)) {
				/* folder does no longer exist */
				ok = kolab_mail_info_db_remove_folder (priv->infodb,
				                                       info_fn,
				                                       &tmp_err);
				if (! ok) {
					g_propagate_error (err, tmp_err);
					return FALSE;
				}
				goto folder_skip;
			}
			break;
		case KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_IMAP:
			imap_fn = (const gchar *)(lst_ptr->data);
			if (side_folders_tbl != NULL)
				side_fn = g_hash_table_lookup (side_folders_tbl, imap_fn);
			break;
		case KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_SIDE:
			side_fn = (const gchar *)(lst_ptr->data);
			break;
		default:
			g_assert_not_reached ();
		}

		ok = kolab_mail_synchronizer_folders_update_summary (self,
		                                                     opmode,
		                                                     info_fn,
		                                                     imap_fn,
		                                                     side_fn,
		                                                     cancellable,
		                                                     &tmp_err);
		if (! ok) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}

		switch (walk_id) {
		case KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_INFO:
			if (imap_fn != NULL)
				g_hash_table_remove (imap_folders_tbl,
				                     imap_fn);
			/* no break */
		case KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_IMAP:
			if (side_fn != NULL)
				g_hash_table_remove (side_folders_tbl,
				                     side_fn);
			/* no break */
		case KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_SIDE:
		default:
			{ /* nop */ }
		}

	folder_skip:
		lst_ptr = g_list_next (lst_ptr);
	}

	return TRUE;
}

static gboolean
kolab_mail_synchronizer_folders_update_infodb (KolabMailSynchronizer *self,
                                               KolabMailAccessOpmodeID opmode,
                                               GCancellable *cancellable,
                                               GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	GList *info_folders_lst = NULL;
	GList *tmp_lst = NULL;
	GHashTable *imap_folders_tbl = NULL;
	GHashTable *side_folders_tbl = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

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

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	/* get ImapClient folders */
	tmp_lst = kolab_mail_imap_client_query_foldernames (priv->client,
	                                                    cancellable,
	                                                    &tmp_err);
	if (tmp_err != NULL)
		goto skip;
	imap_folders_tbl = kolab_util_glib_ghashtable_new_from_str_glist (tmp_lst);
	kolab_util_glib_glist_free (tmp_lst);
	tmp_lst = NULL;

	/* get SideCache folders */
	tmp_lst = kolab_mail_side_cache_query_foldernames (priv->sidecache,
	                                                   &tmp_err);
	if (tmp_err != NULL)
		goto skip;
	side_folders_tbl = kolab_util_glib_ghashtable_new_from_str_glist (tmp_lst);
	kolab_util_glib_glist_free (tmp_lst);
	tmp_lst = NULL;

	/* get InfoDb folders */
	info_folders_lst = kolab_mail_info_db_query_foldernames (priv->infodb,
	                                                         &tmp_err);
	if (tmp_err != NULL)
		goto skip;

	/* walk through InfoDb folders list */
	ok = kolab_mail_synchronizer_folders_update_walk (self,
	                                                  opmode,
	                                                  KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_INFO,
	                                                  info_folders_lst,
	                                                  imap_folders_tbl,
	                                                  side_folders_tbl,
	                                                  cancellable,
	                                                  &tmp_err);
	kolab_util_glib_glist_free (info_folders_lst);
	info_folders_lst = NULL;
	if (! ok)
		goto skip;

	/* walk through remaining ImapClient folders list */
	if (imap_folders_tbl != NULL) {
		tmp_lst = g_hash_table_get_keys (imap_folders_tbl);
		ok = kolab_mail_synchronizer_folders_update_walk (self,
		                                                  opmode,
		                                                  KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_IMAP,
		                                                  tmp_lst,
		                                                  imap_folders_tbl,
		                                                  side_folders_tbl,
		                                                  cancellable,
		                                                  &tmp_err);
		g_list_free (tmp_lst);
		tmp_lst = NULL;
		if (! ok)
			goto skip;
	}

	/* walk through remaining SideCache folders list */
	if (side_folders_tbl != NULL) {
		tmp_lst = g_hash_table_get_keys (side_folders_tbl);
		ok = kolab_mail_synchronizer_folders_update_walk (self,
		                                                  opmode,
		                                                  KOLAB_MAIL_SYNCHRONIZER_FOLDER_WALK_SIDE,
		                                                  tmp_lst,
		                                                  imap_folders_tbl,
		                                                  side_folders_tbl,
		                                                  cancellable,
		                                                  &tmp_err);
		g_list_free (tmp_lst);
		tmp_lst = NULL;
		if (! ok)
			goto skip;
	}

 skip:
	if (imap_folders_tbl != NULL)
		g_hash_table_destroy (imap_folders_tbl);
	if (side_folders_tbl != NULL)
		g_hash_table_destroy (side_folders_tbl);
	if (tmp_err != NULL) {
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

static GList*
kolab_mail_synchronizer_uid_combine_all (KolabMailSynchronizer *self,
                                         const gchar *foldername,
                                         GHashTable *imap_summaries_tbl,
                                         GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	GList *uids_lst = NULL;
	GList *uids_lst_ptr = NULL;
	GHashTable *client_uids_tbl = NULL;
	GList *summaries_uids_lst = NULL;
	GList *tmp_lst = NULL;
	GList *tmp_lst_ptr = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (foldername != NULL);
	/* imap_summaries_tbl may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	/* get list of Kolab UIDs from InfoDb (may be NULL) */
	uids_lst = kolab_mail_info_db_query_uids (priv->infodb,
	                                          foldername,
	                                          NULL,
	                                          FALSE,
	                                          TRUE,
	                                          &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	/* generate list of Kolab UIDs present in ImapClient but not in InfoDb
	 * (may be NULL)
	 */
	if (imap_summaries_tbl != NULL)
		summaries_uids_lst = g_hash_table_get_keys (imap_summaries_tbl);
	if (summaries_uids_lst != NULL) {
		client_uids_tbl = kolab_util_glib_ghashtable_new_from_str_glist (summaries_uids_lst);
		g_list_free (summaries_uids_lst);
	}
	if (client_uids_tbl != NULL) {
		uids_lst_ptr = uids_lst;
		while (uids_lst_ptr != NULL) {
			const gchar *uid = (const gchar *)(uids_lst_ptr->data);
			(void)g_hash_table_remove (client_uids_tbl, uid);
			uids_lst_ptr = g_list_next (uids_lst_ptr);
		}
		tmp_lst = g_hash_table_get_keys (client_uids_tbl); /* may be NULL */
	}

	/* merge the two UID lists into one */
	tmp_lst_ptr = tmp_lst;
	while (tmp_lst_ptr != NULL) {
		const gchar *uid = (const gchar *)(tmp_lst_ptr->data);
		uids_lst = g_list_prepend (uids_lst, g_strdup (uid));
		tmp_lst_ptr = g_list_next (tmp_lst_ptr);
	}
	if (tmp_lst != NULL)
		g_list_free (tmp_lst);
	if (client_uids_tbl != NULL)
		g_hash_table_destroy (client_uids_tbl);

	return uids_lst;
}

static void
kolab_mail_synchronizer_record_update_info (KolabMailSynchronizer *self,
                                            KolabMailAccessOpmodeID opmode,
                                            KolabMailInfoDbRecord *record,
                                            const KolabMailSummary *imap_summary,
                                            const gchar *uid,
                                            const gchar *foldername,
                                            KolabFolderTypeID folder_type,
                                            KolabFolderContextID folder_context,
                                            gboolean uidval_changed)
{
	/* KolabMailSynchronizerPrivate *priv = NULL; */
	KolabObjectCacheLocation location = KOLAB_OBJECT_CACHE_LOCATION_INVAL;
	KolabObjectCacheStatus status = KOLAB_OBJECT_CACHE_STATUS_INVAL;
	const gchar *imapuid_sync = NULL;
	const gchar *imapuid_client = NULL;
	gboolean do_set_changed_flag = FALSE; /* initial FALSE needed ! */
	gboolean do_imap_uid_update = FALSE; /* initial FALSE needed ! */

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert ((opmode > KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED) &&
	          (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE));
	g_assert (uid != NULL);
	g_assert (foldername != NULL);
	g_assert (folder_type < KOLAB_FOLDER_LAST_TYPE);
	g_assert (folder_context < KOLAB_FOLDER_LAST_CONTEXT);
	g_assert (record != NULL);
	/* imap_summary may be NULL */

	/* priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self); */

	/* needed at least for as long as InfoDb is not persistent */
	if (record->summary == NULL)
		record->summary = kolab_mail_summary_clone (imap_summary);
	if (record->summary == NULL)
		record->summary = kolab_mail_summary_new ();

	kolab_mail_summary_set_char_field (record->summary,
	                                   KOLAB_MAIL_SUMMARY_CHAR_FIELD_KOLAB_UID,
	                                   g_strdup (uid));
	kolab_mail_summary_set_uint_field (record->summary,
	                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_CONTEXT,
	                                   folder_context);
	kolab_mail_summary_set_uint_field (record->summary,
	                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_TYPE,
	                                   folder_type);

	if (imap_summary == NULL) {
		/* object has been deleted from ImapCache */
		location = kolab_mail_summary_get_uint_field (record->summary,
		                                              KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION);
		location &= ~((guint)KOLAB_OBJECT_CACHE_LOCATION_IMAP);
		kolab_mail_summary_set_uint_field (record->summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION,
		                                   location);
		imapuid_client = kolab_mail_summary_get_char_field (record->summary,
		                                                    KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID);
	} else {
		gint ii = 0;
		gboolean tmp = FALSE;
		/* get current ImapClient IMAP UID (to be checked for change) */
		imapuid_client = kolab_mail_summary_get_char_field (imap_summary,
		                                                    KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID);
		/* update InfoDbRecord non-EDS data from IMAP summary */
		tmp = kolab_mail_summary_get_bool_field (imap_summary,
		                                         KOLAB_MAIL_SUMMARY_BOOL_FIELD_COMPLETE);
		kolab_mail_summary_set_bool_field (record->summary,
		                                   KOLAB_MAIL_SUMMARY_BOOL_FIELD_COMPLETE,
		                                   tmp);
		/* update InfoDbRecord EDS data from IMAP summary */
		for (ii = KOLAB_MAIL_SUMMARY_CHAR_FIELD_E_SUMMARY; ii < KOLAB_MAIL_SUMMARY_CHAR_LAST_FIELD; ii++) {
			gchar *tmp_str = NULL;
			tmp_str = g_strdup (kolab_mail_summary_get_char_field (imap_summary, ii));
			kolab_mail_summary_set_char_field (record->summary, ii, tmp_str);
		}
		for (ii = KOLAB_MAIL_SUMMARY_UINT_FIELD_E_CLASSIFICATION; ii < KOLAB_MAIL_SUMMARY_UINT_LAST_FIELD; ii++) {
			guint tmp = kolab_mail_summary_get_uint_field (imap_summary, ii);
			kolab_mail_summary_set_uint_field (record->summary, ii, tmp);
		}
		for (ii = KOLAB_MAIL_SUMMARY_INT_FIELD_E_PRIORITY; ii < KOLAB_MAIL_SUMMARY_INT_LAST_FIELD; ii++) {
			gint tmp = kolab_mail_summary_get_int_field (imap_summary, ii);
			kolab_mail_summary_set_int_field (record->summary, ii, tmp);
		}
		for (ii = KOLAB_MAIL_SUMMARY_BOOL_FIELD_E_HAS_ATTENDEES; ii < KOLAB_MAIL_SUMMARY_BOOL_LAST_FIELD; ii++) {
			tmp = kolab_mail_summary_get_bool_field (imap_summary, ii);
			kolab_mail_summary_set_bool_field (record->summary, ii, tmp);
		}
	}

	if (opmode < KOLAB_MAIL_ACCESS_OPMODE_ONLINE)
		goto check_skip;

	if (uidval_changed) {
		/* in case of folder uidvalidity change, we mark the
		 * object as changed and store the new IMAP UID (the old one
		 * does us no good any longer)
		 */
		do_set_changed_flag = TRUE;
		do_imap_uid_update = TRUE;
		goto check_skip;
	}

	imapuid_sync = kolab_mail_info_db_record_get_char_field (record,
	                                                         KOLAB_MAIL_INFO_DB_RECORD_CHAR_FIELD_IMAP_UID_SYNC);

	if (imapuid_sync == NULL) {
		/* we've never seen this imap uid before, so we store
		 * it but do not mark the object as 'changed' (this
		 * implies the object is 'new', so it cannot have
		 * 'changed' yet)
		 */
		do_set_changed_flag = FALSE;
		do_imap_uid_update = TRUE;
		goto check_skip;
	}

	if (imapuid_client == NULL) {
		/* there has not yet been an IMAP UID stored in InfoDb,
		 * so we cannot see any change here
		 */
		do_set_changed_flag = FALSE;
		do_imap_uid_update = TRUE;
		goto check_skip;
	}

	if (g_strcmp0 (imapuid_client, imapuid_sync) != 0) {
		/* we have an old imap uid stored, but the one
		 * reported from the ImapClient differs. this
		 * means we update the old imap uid value to the
		 * newly reported one and set the 'changed'
		 * cache status flag on the object's summary
		 */
		do_set_changed_flag = TRUE;
		do_imap_uid_update = TRUE;
	}

 check_skip:

	/* the following is skipped in offline mode */
	if (do_set_changed_flag) {
		status = kolab_mail_summary_get_uint_field (record->summary,
		                                            KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS);
		status |= KOLAB_OBJECT_CACHE_STATUS_CHANGED;
		kolab_mail_summary_set_uint_field (record->summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS,
		                                   status);
	}
	if (do_imap_uid_update) {
		kolab_mail_summary_set_char_field (record->summary,
		                                   KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID,
		                                   g_strdup (imapuid_client));
		kolab_mail_info_db_record_set_char_field (record,
		                                          KOLAB_MAIL_INFO_DB_RECORD_CHAR_FIELD_IMAP_UID_SYNC,
		                                          g_strdup (imapuid_client));
	}
}

static gboolean
kolab_mail_synchronizer_uids_infosync (KolabMailSynchronizer *self,
                                       KolabMailAccessOpmodeID opmode,
                                       const gchar *foldername,
                                       const KolabFolderSummary *folder_summary,
                                       GList *uids_lst,
                                       GHashTable *imap_summaries_tbl,
                                       gboolean uidval_changed,
                                       GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	GList *uids_lst_ptr = NULL;
	KolabFolderTypeID folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderContextID folder_context = KOLAB_FOLDER_CONTEXT_INVAL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE);
	if (opmode == KOLAB_MAIL_ACCESS_OPMODE_OFFLINE)
		g_assert (uidval_changed == FALSE);
	g_assert (foldername != NULL);
	g_assert (folder_summary != NULL);
	g_assert (uids_lst != NULL);

	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	if (opmode < KOLAB_MAIL_ACCESS_OPMODE_ONLINE) {
		/* TODO In offline mode, all locally cached IMAP data
		 *      is unchanged. Therefore, we do not need to do
		 *      any change detection here.
		 *
		 *      We should, though, do a simple consistency check
		 *      between InfoDb and ImapClient, i.e. check that
		 *      the Kolab UID and IMAP UID data in InfoDb matches
		 *      that in ImapClient. We can operate on summaries
		 *      for this, no need to get the actual email data.
		 *
		 *      For now, we assume nothing can go wrong with our
		 *      offline data (no corruption) and just move along -
		 *      the folder checks we do should suffice for a start
		 */

		return TRUE;
	}

	if (imap_summaries_tbl == NULL)
		g_debug ("%s: empty folder (%s)", __func__, foldername);

	folder_context = kolab_folder_summary_get_uint_field (folder_summary,
	                                                      KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_CONTEXT);
	folder_type = kolab_folder_summary_get_uint_field (folder_summary,
	                                                   KOLAB_FOLDER_SUMMARY_UINT_FIELD_FOLDER_TYPE);
	uids_lst_ptr = uids_lst;
	while (uids_lst_ptr != NULL) {
		const gchar *uid = NULL;
		const KolabMailSummary *imap_summary = NULL;
		KolabMailInfoDbRecord *record = NULL;
		KolabMailInfoDbRecord *record_orig = NULL;
		gboolean records_eq = FALSE;
		gboolean ok = FALSE;
		GError *tmp_err = NULL;

		uid = (const gchar *)(uids_lst_ptr->data);

		/* get InfoDb record for UID (if exists) */
		record = kolab_mail_info_db_query_record (priv->infodb,
		                                          uid,
		                                          foldername,
		                                          &tmp_err);
		if (tmp_err != NULL) {
			if (tmp_err->code != KOLAB_BACKEND_ERROR_NOTFOUND)
				goto uid_skip;
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		if (record == NULL)
			record = kolab_mail_info_db_record_new ();

		/* get ImapClient record for UID (if exists) */
		if (imap_summaries_tbl != NULL)
			imap_summary = g_hash_table_lookup (imap_summaries_tbl, uid);

		/* update InfoDb record data */
		record_orig = kolab_mail_info_db_record_clone (record);
		kolab_mail_synchronizer_record_update_info (self,
		                                            opmode,
		                                            record,
		                                            imap_summary,
		                                            uid,
		                                            foldername,
		                                            folder_type,
		                                            folder_context,
		                                            uidval_changed);
		/* update InfoDb if record changed */
		records_eq = kolab_mail_info_db_record_equal (record,
		                                              record_orig);
		kolab_mail_info_db_record_free (record_orig);
		if (! records_eq) {
			ok = kolab_mail_info_db_update_record (priv->infodb,
			                                       record,
			                                       foldername,
			                                       &tmp_err);
			if (! ok) {
				g_warning ("%s: %s",
				           __func__, tmp_err->message);
				g_error_free (tmp_err);
				tmp_err = NULL;
			}
		}
	uid_skip:
		if (record != NULL)
			kolab_mail_info_db_record_free (record);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
		}
		uids_lst_ptr = g_list_next (uids_lst_ptr);
	}

	return TRUE;
}

static gboolean
kolab_mail_synchronizer_uids_update_infodb (KolabMailSynchronizer *self,
                                            KolabMailAccessOpmodeID opmode,
                                            const gchar *foldername,
                                            GCancellable *cancellable,
                                            GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	GList *folders_lst = NULL;
	GList *folders_lst_ptr = NULL;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE);
	/* foldername may be NULL */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	/* kolab_mail_synchronizer_folders_update_infodb() must have been
	 * run successfully before
	 */
	folders_lst = kolab_mail_info_db_query_foldernames (priv->infodb,
	                                                    &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	if (foldername != NULL) {
		GHashTable *folders_tbl = NULL;
		gchar *fn = NULL;

		folders_tbl = kolab_util_glib_ghashtable_new_from_str_glist (folders_lst);
		kolab_util_glib_glist_free (folders_lst);
		folders_lst = NULL;

		fn = g_hash_table_lookup (folders_tbl, foldername);
		if (fn == NULL) {
			g_hash_table_destroy (folders_tbl);
			g_set_error (err,
			             KOLAB_BACKEND_ERROR,
			             KOLAB_BACKEND_ERROR_INFODB_NOFOLDER,
			             _("Folder does not exist in internal database: '%s'"),
			             foldername);
			return FALSE;
		}

		folders_lst = g_list_prepend (folders_lst, g_strdup (fn));
		g_hash_table_destroy (folders_tbl);
	}

	folders_lst_ptr = folders_lst;
	while (folders_lst_ptr != NULL) {
		const gchar *foldername = NULL; /* shadows foldername function param */
		GHashTable *imap_summaries_tbl = NULL;
		GList *uids_lst = NULL;
		KolabFolderSummary *folder_summary = NULL;
		gboolean ok = FALSE;
		gboolean uidval_chg = FALSE;
		GError *tmp_err = NULL;

		foldername = (const gchar*)(folders_lst_ptr->data);

		imap_summaries_tbl = kolab_mail_imap_client_query_summaries (priv->client,
		                                                             foldername,
		                                                             NULL,
		                                                             TRUE, /* do folder update */
		                                                             cancellable,
		                                                             &tmp_err);
		if (tmp_err != NULL)
			goto folder_skip;

		uids_lst = kolab_mail_synchronizer_uid_combine_all (self,
		                                                    foldername,
		                                                    imap_summaries_tbl,
		                                                    &tmp_err);
		if (tmp_err != NULL)
			goto folder_skip;

		/* UID list is NULL if the folder does no longer exist
		 * in ImapClient or SideCache. The folder data (and it's
		 * uid data) has already been deleted from InfoDb in this case
		 */
		if (uids_lst == NULL)
			goto folder_skip;

		folder_summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
		                                                          foldername,
		                                                          &tmp_err);
		if (tmp_err != NULL)
			goto folder_skip;

		uidval_chg = kolab_mail_synchronizer_folders_uidvalidity_changed (self,
		                                                                  folder_summary);
		ok = kolab_mail_synchronizer_uids_infosync (self,
		                                            opmode,
		                                            foldername,
		                                            folder_summary,
		                                            uids_lst,
		                                            imap_summaries_tbl,
		                                            uidval_chg,
		                                            &tmp_err);
		if (! ok)
			goto folder_skip;

		/* if we had a folder uidvalidity change, clear change bits,
		 * now that we're done walking through that folder's kolab_uids
		 */
		if (uidval_chg) {
			kolab_mail_synchronizer_folders_clear_changed (self,
			                                               folder_summary);
			ok = kolab_mail_info_db_update_folder_summary (priv->infodb,
			                                               folder_summary,
			                                               &tmp_err);
			if (! ok)
				goto folder_skip;
		}

	folder_skip:
		if (imap_summaries_tbl != NULL)
			g_hash_table_destroy (imap_summaries_tbl);
		if (uids_lst != NULL)
			kolab_util_glib_glist_free (uids_lst);
		if (folder_summary != NULL)
			kolab_folder_summary_free (folder_summary);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
		}
		folders_lst_ptr = g_list_next (folders_lst_ptr);
	}

	kolab_util_glib_glist_free (folders_lst);

	return TRUE;
}

static gchar*
kolab_mail_synchronizer_new_free_uid (KolabMailSynchronizer *self,
                                      GHashTable *summaries,
                                      GHashTable *changed_uids)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	KolabMailSummary *summary = NULL;
	gchar *changed_uid = NULL;
	gchar *uid = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	/* summaries may be NULL */
	/* changed_uids may be NULL */

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* TODO if deleted uids are held in a separate list,
	 *      take care of it here as well
	 */

	uid = e_cal_component_gen_uid ();

	if ((summaries == NULL) && (changed_uids == NULL))
		return uid;

	for (;;) {
		/* make sure the new uid does exist neither in folder
		 * nor in the list of changed/deleted uids
		 */
		if (summaries != NULL)
			summary = g_hash_table_lookup (summaries, uid);
		if (changed_uids != NULL)
			changed_uid = g_hash_table_lookup (changed_uids, uid);

		if ((summary == NULL) &&
		    (changed_uid == NULL))
			break;

		g_free (uid);
		uid = e_cal_component_gen_uid ();
	}

	return uid;
}

static gboolean
kolab_mail_synchronizer_act_on_econtact (EContact *econtact_loc,
                                         EContact *econtact_srv,
                                         const gchar *uid,
                                         const gchar *foldername)
{
	gchar *modtime_loc = NULL;
	gchar *modtime_srv = NULL;
	struct icaltimetype itt_loc;
	struct icaltimetype itt_srv;
	gboolean can_act = FALSE;

	g_assert (E_IS_CONTACT (econtact_loc));
	g_assert (E_IS_CONTACT (econtact_srv));
	g_assert (uid != NULL);
	g_assert (foldername != NULL);

	modtime_loc = e_contact_get (econtact_loc, E_CONTACT_REV);
	modtime_srv = e_contact_get (econtact_srv, E_CONTACT_REV);

	if (modtime_loc == NULL) {
		g_warning ("%s: %s %s",
		           __func__,
		           "no valid last modification time set on local ECalComponent,",
		           "won't overwrite server");
		return FALSE;
	}

	if (modtime_srv == NULL) {
		/* if no modtime is set, assume local object is newer */
		return TRUE;
	}

	itt_loc = icaltime_from_string (modtime_loc);
	if (icaltime_is_null_time (itt_loc)) {
		/* TODO extract ical error number and message */
		g_warning ("%s: UID (%s) Folder (%s) local object modification time parse error",
		           __func__, uid, foldername);
		return FALSE;
	}

	itt_srv = icaltime_from_string (modtime_srv);
	if (icaltime_is_null_time (itt_srv)) {
		/* TODO extract ical error number and message */
		g_warning ("%s: UID (%s) Folder (%s) remote object modification time parse error",
		           __func__, uid, foldername);
		return TRUE;
	}

	/* do timestamp comparison */
	if (icaltime_compare (itt_loc, itt_srv) > 0)
		can_act = TRUE; /* local is newer */
	else
		can_act = FALSE; /* same, or server is newer */

	return can_act;
}

static gboolean
kolab_mail_synchronizer_act_on_ecalcomp (ECalComponent *ecalcomp_loc,
                                         ECalComponent *ecalcomp_srv,
                                         const gchar *uid,
                                         const gchar *foldername)
{
	struct icaltimetype itt_loc, *lm_itt_loc = NULL;
	struct icaltimetype itt_srv, *lm_itt_srv = NULL;
	gboolean can_act = FALSE;

	g_assert (E_IS_CAL_COMPONENT (ecalcomp_loc));
	g_assert (E_IS_CAL_COMPONENT (ecalcomp_srv));
	g_assert (uid != NULL);
	g_assert (foldername != NULL);

	/* get the modification times from the objects */
	e_cal_component_get_last_modified (ecalcomp_loc, &lm_itt_loc);
	e_cal_component_get_last_modified (ecalcomp_srv, &lm_itt_srv);

	/* libicalify data */
	if (lm_itt_loc != NULL) {
		itt_loc = *lm_itt_loc;
		e_cal_component_free_icaltimetype (lm_itt_loc);
	}
	if (lm_itt_srv != NULL) {
		itt_srv = *lm_itt_srv;
		e_cal_component_free_icaltimetype (lm_itt_srv);
	}

	/* no modtime set on local object (this is an error) */
	if (lm_itt_loc == NULL) {
		g_warning ("%s: %s %s",
		           __func__,
		           "no valid last modification time set on local ECalComponent,",
		           "won't overwrite server");
		return FALSE;
	}

	/* no modtime set on remote object (assume local is newer) */
	if (lm_itt_srv == NULL) {
		g_debug ("%s: remote object has no modtime set, assuming local is newer",
		         __func__);
		return TRUE;
	}

	/* explicitly set UTC as timezone info */
	itt_loc = icaltime_set_timezone (&itt_loc, icaltimezone_get_utc_timezone ());
	itt_srv = icaltime_set_timezone (&itt_srv, icaltimezone_get_utc_timezone ());

	/* do timestamp comparison */
	if (icaltime_compare (itt_loc, itt_srv) > 0)
		can_act = TRUE; /* local is newer */
	else
		can_act = FALSE; /* same, or server is newer */

	return can_act;
}

static KolabMailHandle*
kolab_mail_synchronizer_handle_new_from_record (KolabMailSynchronizer *self,
                                                const gchar *uid,
                                                const gchar *foldername,
                                                const KolabMailInfoDbRecord *record,
                                                GError **err)
{
	/* KolabMailSynchronizerPrivate *priv = NULL; */
	KolabMailHandle *local_handle = NULL;
	KolabMailSummary *summary = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (uid != NULL);
	g_assert (foldername != NULL);
	g_assert (record != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	/* priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self); */

	summary = kolab_mail_summary_clone (record->summary);

	if (summary == NULL)
		/* UID has never been stored before */
		return NULL;

	local_handle = kolab_mail_handle_new_shallow (uid, foldername);

	ok = kolab_mail_handle_set_summary (local_handle,
	                                    summary,
	                                    &tmp_err);
	if (! ok) {
		kolab_mail_summary_free (summary);
		g_object_unref (local_handle);
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	return local_handle;
}

static gboolean
kolab_mail_synchronizer_push_sidecache (KolabMailSynchronizer *self,
                                        const gchar *foldername,
                                        GCancellable *cancellable,
                                        GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	GHashTable *imap_summaries =  NULL;
	GList *uids_lst = NULL;
	GList *uids_lst_ptr = NULL;
	GError *tmp_err = NULL;

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

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* get UIDs of objects stored in SideCache */
	uids_lst = kolab_mail_info_db_query_uids (priv->infodb,
	                                          foldername,
	                                          NULL, /* sexp */
	                                          TRUE, /* report SideCache only */
	                                          TRUE, /* include deleted uids */
	                                          &tmp_err);
	if (tmp_err != NULL) {
		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,
	                                                         FALSE, /* update folder done in infosync */
	                                                         cancellable,
	                                                         &tmp_err);
	if (tmp_err != NULL) {
		g_propagate_error (err, tmp_err);
		kolab_util_glib_glist_free (uids_lst);
		return FALSE;
	}

	/* push all SideCache objects in folder, delete objects marked so, check collisions */
	uids_lst_ptr = uids_lst;
	while (uids_lst_ptr != NULL) {
		KolabMailHandle *kmailhandle = NULL;
		KolabMailSummary *summary = NULL;
		KolabMailInfoDbRecord *record = NULL;
		KolabObjectCacheStatus status = KOLAB_OBJECT_CACHE_STATUS_INVAL;
		KolabObjectCacheLocation location = KOLAB_OBJECT_CACHE_LOCATION_INVAL;
		KolabObjectCacheLocation location_side = KOLAB_OBJECT_CACHE_LOCATION_SIDE;
		gchar *uid = (gchar *)(uids_lst_ptr->data);
		gboolean do_act = FALSE;
		gboolean ok = TRUE;

		/* get the InfoDb record for the object, create handle */
		record = kolab_mail_info_db_query_record (priv->infodb,
		                                          uid,
		                                          foldername,
		                                          &tmp_err);
		if (tmp_err != NULL)
			goto uid_cleanup;
		kmailhandle = kolab_mail_synchronizer_handle_new_from_record (self,
		                                                              uid,
		                                                              foldername,
		                                                              record,
		                                                              &tmp_err);
		if (kmailhandle == NULL)
			goto uid_cleanup;

		/* check object cache status and cache location */
		location = kolab_mail_summary_get_uint_field (record->summary,
		                                              KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION);
		status = kolab_mail_summary_get_uint_field (record->summary,
		                                            KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS);

		/* if the object is in SideCache and not deleted, retrieve it */
		if ((location & (guint)KOLAB_OBJECT_CACHE_LOCATION_SIDE) &&
		    (!(status & (guint)KOLAB_OBJECT_CACHE_STATUS_DELETED))) {
			ok = kolab_mail_side_cache_retrieve (priv->sidecache,
			                                     kmailhandle,
			                                     &tmp_err);
			if (! ok) {
				if (tmp_err->code != KOLAB_BACKEND_ERROR_NOTFOUND) {
					goto uid_cleanup;
				}
				/* if the UID has been marked as deleted, there is no
				 * entry in the SideCache for it - in this case, we have
				 * an InfoDb/SideCache inconsistency here
				 * (object marked as being in SideCache, but not found
				 * there), so we unset the SideCache cache location bit
				 * in InfoDb
				 */
				g_error_free (tmp_err);
				tmp_err = NULL;
				location = location & (~location_side);
				kolab_mail_summary_set_uint_field (record->summary,
				                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION,
				                                   location);
				ok = kolab_mail_info_db_update_record (priv->infodb,
				                                       record,
				                                       foldername,
				                                       &tmp_err);
				if (! ok)
					goto uid_cleanup;
			}
		}

		/* check whether or not to store/delete the object */
		do_act = kolab_mail_synchronizer_transaction_prepare (self,
		                                                      KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
		                                                      KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_PUSH,
		                                                      kmailhandle,
		                                                      foldername,
		                                                      imap_summaries,
		                                                      &record,
		                                                      cancellable,
		                                                      &tmp_err);
		if (tmp_err != NULL)
			goto uid_cleanup;

		/* store transaction start */
		ok = kolab_mail_synchronizer_transaction_start (self,
		                                                KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
		                                                KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_PUSH,
		                                                kmailhandle,
		                                                foldername,
		                                                record,
		                                                &tmp_err);
		if (tmp_err != NULL)
			goto uid_cleanup;

		if (do_act) {
			/* status bit may have been changed by transaction function, re-read it */
			status = kolab_mail_summary_get_uint_field (record->summary,
			                                            KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS);
			if (status & (guint)KOLAB_OBJECT_CACHE_STATUS_DELETED) {
				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,
					                                    TRUE, /* try only IMAP UID */
					                                    FALSE, /* folder already updated */
					                                    cancellable,
					                                    &tmp_err);
				} else {
					ok = TRUE;
				}
			} else {
				ok = kolab_mail_imap_client_store (priv->client,
				                                   kmailhandle,
				                                   foldername,
				                                   FALSE, /* folder already updated */
				                                   cancellable,
				                                   &tmp_err);
			}
		} else {
			g_debug ("%s: UID (%s) Folder (%s) not pushed to / deleted from server due to selected sync strategy",
			         __func__, uid, foldername);
		}

		summary = kolab_mail_handle_get_summary_nonconst (kmailhandle);
		g_assert (summary != NULL);
		kolab_mail_summary_set_bool_field (summary,
		                                   KOLAB_MAIL_SUMMARY_BOOL_FIELD_COMPLETE,
		                                   FALSE);
		if (ok)
			ok = kolab_mail_synchronizer_transaction_commit (self,
			                                                 KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
			                                                 KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_PUSH,
			                                                 kmailhandle,
			                                                 foldername,
			                                                 record,
			                                                 &tmp_err);
		else
			ok = kolab_mail_synchronizer_transaction_abort (self,
			                                                KOLAB_MAIL_ACCESS_OPMODE_ONLINE,
			                                                KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_PUSH,
			                                                kmailhandle,
			                                                foldername,
			                                                record,
			                                                &tmp_err);
	uid_cleanup:
		kolab_mail_info_db_record_free (record);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		if (kmailhandle != NULL)
			g_object_unref (kmailhandle);
		uids_lst_ptr = g_list_next (uids_lst_ptr);
	}

	if (imap_summaries != NULL)
		g_hash_table_destroy (imap_summaries);
	kolab_util_glib_glist_free (uids_lst);
	return TRUE;
}

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

gboolean
kolab_mail_synchronizer_configure  (KolabMailSynchronizer *self,
                                    KolabSettingsHandler *ksettings,
                                    KolabMailImapClient *client,
                                    KolabMailInfoDb *infodb,
                                    KolabMailSideCache *sidecache,
                                    KolabMailMimeBuilder *mimebuilder,
                                    GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert (KOLAB_IS_SETTINGS_HANDLER (ksettings));
	g_assert (KOLAB_IS_MAIL_IMAP_CLIENT (client));
	g_assert (KOLAB_IS_MAIL_INFO_DB (infodb));
	g_assert (KOLAB_IS_MAIL_SIDE_CACHE (sidecache));
	g_assert (KOLAB_IS_MAIL_MIME_BUILDER (mimebuilder));
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	if (priv->ksettings != NULL)
		return TRUE;

	/* unref'd in dispose() */
	g_object_ref (ksettings);
	g_object_ref (client);
	g_object_ref (infodb);
	g_object_ref (sidecache);
	g_object_ref (mimebuilder);

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

	return TRUE;
}

gboolean
kolab_mail_synchronizer_bringup (KolabMailSynchronizer *self,
                                 GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;

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

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	if (priv->is_up == TRUE)
		return TRUE;

	priv->is_up = TRUE;
	return TRUE;
}

gboolean
kolab_mail_synchronizer_shutdown (KolabMailSynchronizer *self,
                                  GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;

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

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	if (priv->is_up == FALSE)
		return TRUE;

	/* TODO implement me */

	priv->is_up = FALSE;
	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* auxiliary functions */

KolabMailHandle*
kolab_mail_synchronizer_handle_new_from_infodb (KolabMailSynchronizer *self,
                                                const gchar *uid,
                                                const gchar *foldername,
                                                GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	KolabMailHandle *local_handle = NULL;
	KolabMailSummary *summary = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

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

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

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

	if (summary == NULL)
		/* UID has never been stored before */
		return NULL;

	local_handle = kolab_mail_handle_new_shallow (uid, foldername);

	ok = kolab_mail_handle_set_summary (local_handle,
	                                    summary,
	                                    &tmp_err);
	if (! ok) {
		kolab_mail_summary_free (summary);
		g_object_unref (local_handle);
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	return local_handle;
}

/*----------------------------------------------------------------------------*/
/* synchronizer transaction management (single handle operation) */

gboolean
kolab_mail_synchronizer_transaction_prepare (KolabMailSynchronizer *self,
                                             KolabMailAccessOpmodeID opmode,
                                             KolabMailSynchronizerTransactionTypeID ttid,
                                             KolabMailHandle *kmailhandle,
                                             const gchar *foldername,
                                             GHashTable *imap_summaries,
                                             KolabMailInfoDbRecord **record,
                                             GCancellable *cancellable,
                                             GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	ECalComponent *ecalcomp_loc = NULL;
	ECalComponent *ecalcomp_srv = NULL;
	EContact *econtact_loc = NULL;
	EContact *econtact_srv = NULL;
	KolabMailInfoDbRecord *old_record = NULL;
	KolabMailHandle *kmh_srv = NULL;
	KolabMailHandle *kmh_loc = NULL;
	KolabMailSummary *imap_summary = NULL;
	const KolabMailSummary *kmh_summary = NULL;
	KolabMailSummary *kmh_summary_nonconst = NULL;
	KolabSyncStrategyID sync_id = KOLAB_SYNC_STRATEGY_DEFAULT;
	KolabFolderSummary *folder_summary = NULL;
	KolabFolderTypeID folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderContextID folder_context = KOLAB_FOLDER_CONTEXT_INVAL;
	KolabObjectCacheStatus status = KOLAB_OBJECT_CACHE_STATUS_INVAL;
	KolabObjectCacheStatus new_status = KOLAB_OBJECT_CACHE_STATUS_INVAL;
	gconstpointer sync_id_ptr = NULL;
	GHashTable *changed_uids_tbl = NULL;
	GList *changed_uids_lst = NULL;
	gchar *uid = NULL;
	gchar *new_uid = NULL;
	gchar *sourcename = NULL;
	gboolean record_locally_created = FALSE;
	gboolean uidval_changed = FALSE;
	gboolean can_act = FALSE;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert ((opmode > KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED) &&
	          (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE));
	g_assert ((ttid > KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_INVAL) &&
	          (ttid < KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_LAST_TYPE));
	if (kmailhandle != NULL)
		g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_assert (foldername != NULL);
	/* imap_summaries may be NULL */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	uid = g_strdup (kolab_mail_handle_get_uid (kmailhandle));
	if (uid == NULL) {
		g_set_error (&tmp_err,
		             KOLAB_BACKEND_ERROR,
		             KOLAB_BACKEND_ERROR_DATATYPE_INTERNAL,
		             _("Internal inconsistency detected: PIM Object handle has no Kolab UID set"));
		goto cleanup;
	}
	kmh_summary = kolab_mail_handle_get_summary (kmailhandle);

	/* read InfoDb record for the UID in question
	 * (if we don't have it already)
	 */
	if (*record == NULL) {
		record_locally_created = TRUE;
		*record = kolab_mail_info_db_query_record (priv->infodb,
		                                           uid,
		                                           foldername,
		                                           &tmp_err);
		if (tmp_err != NULL)
			goto cleanup;
	}
	if (*record == NULL)
		/* new handle */
		*record = kolab_mail_info_db_record_new ();
	if ((*record)->summary == NULL)
		(*record)->summary = kolab_mail_summary_clone (kmh_summary);

	/* TODO
	 *
	 * - do we need to actually *do* anything here when in
	 *   offline mode?
	 *
	 */
	if (opmode < KOLAB_MAIL_ACCESS_OPMODE_ONLINE) {
		can_act = TRUE;
		goto cleanup;
	}

	if (imap_summaries == NULL) {
		/* empty server folder, no conflict possible */
		can_act = TRUE;
		goto cleanup;
	}

	sourcename = kolab_util_backend_sourcename_new_from_foldername (foldername,
	                                                                &tmp_err);
	if (sourcename == NULL)
		goto cleanup;
	sync_id_ptr = kolab_settings_handler_get_value (priv->ksettings,
	                                                KOLAB_SETTINGS_HANDLER_TBL_SYNCSTRATEGY,
	                                                sourcename,
	                                                &tmp_err);
	if (tmp_err != NULL) {
		sync_id = KOLAB_SYNC_STRATEGY_DEFAULT;
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_debug ("%s: missing sync strategy configuration, assuming default (%u)",
		         __func__, sync_id);
		g_error_free (tmp_err);
		tmp_err = NULL;
	} else {
		sync_id = GPOINTER_TO_UINT (sync_id_ptr);
	}
	g_debug ("%s: UID (%s) Folder (%s) sync strategy id (%u)",
	         __func__, uid, foldername, sync_id);

	folder_context = kolab_mail_summary_get_uint_field (kmh_summary,
	                                                    KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_CONTEXT);

	imap_summary = g_hash_table_lookup (imap_summaries, uid);
	if (imap_summary == NULL) {
		/* UID not found in server folder */
		can_act = TRUE;
		goto cleanup;
	}

	/* if we're here, uid exists in named folder on server */

	folder_type = kolab_mail_summary_get_uint_field (kmh_summary,
	                                                 KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_TYPE);
	folder_summary = kolab_mail_info_db_query_folder_summary (priv->infodb,
	                                                          foldername,
	                                                          &tmp_err);
	if (tmp_err != NULL)
		goto cleanup;
	uidval_changed = kolab_mail_synchronizer_folders_uidvalidity_changed (self,
	                                                                      folder_summary);
	kolab_mail_synchronizer_record_update_info (self,
	                                            opmode,
	                                            *record,
	                                            imap_summary,
	                                            uid,
	                                            foldername,
	                                            folder_type,
	                                            folder_context,
	                                            uidval_changed);

	status = kolab_mail_summary_get_uint_field ((*record)->summary,
	                                            KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS);

	if (! (status & KOLAB_OBJECT_CACHE_STATUS_CHANGED)) {
		/* if we have no server-side changes, we can always act on the object */
		can_act = TRUE;
		goto cleanup;
	}

	if ((ttid == KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_DELETE) ||
	    (status & KOLAB_OBJECT_CACHE_STATUS_DELETED)) {
		/* We pretend we can always delete. In case of a server side
		 * change, the server side object is *not* deleted but re-fetched
		 * instead on next access
		 */
		can_act = TRUE;
		goto cleanup;
	}

	/* if we're here, we're in PUSH/STORE operation */

	switch (sync_id) {
	case KOLAB_SYNC_STRATEGY_CLIENT:
		/* "always client" will simply overwrite */
		g_debug ("%s: UID (%s) Folder (%s) will be overwritten, if exists",
		         __func__, uid, foldername);
		can_act = TRUE;
		break;
	case KOLAB_SYNC_STRATEGY_SERVER:
		/* "take server" will never overwrite if server changed */
		g_debug ("%s: UID (%s) Folder (%s) changed on server, won't overwrite",
		         __func__, uid, foldername);
		can_act = FALSE;
		break;
	case KOLAB_SYNC_STRATEGY_DUPE:
		/* creates a duplicate uid for a server-changed object */
		changed_uids_lst = kolab_mail_info_db_query_changed_uids (priv->infodb,
		                                                          foldername,
		                                                          NULL, /* sexp */
		                                                          FALSE,
		                                                          &tmp_err);
		if (tmp_err != NULL)
			goto cleanup;
		changed_uids_tbl = kolab_util_glib_ghashtable_new_from_str_glist (changed_uids_lst);
		kolab_util_glib_glist_free (changed_uids_lst);
		changed_uids_lst = NULL;
		new_uid = kolab_mail_synchronizer_new_free_uid (self,
		                                                imap_summaries,
		                                                changed_uids_tbl);
		/* set new Kolab UID on handle */
		ok = kolab_mail_handle_set_uid_full (kmailhandle, new_uid, &tmp_err);
		if (! ok)
			goto cleanup;
		kmh_summary_nonconst = kolab_mail_handle_get_summary_nonconst (kmailhandle);
		kolab_mail_summary_set_bool_field (kmh_summary_nonconst,
		                                   KOLAB_MAIL_SUMMARY_BOOL_FIELD_COMPLETE,
		                                   TRUE);
		ok = kolab_mail_handle_convert_eds_to_kconvmail (kmailhandle,
		                                                 &tmp_err);
		if (! ok)
			g_error ("%s: %s", __func__, tmp_err->message);
		/* remaining handle updates */
		kolab_mail_summary_set_char_field (kmh_summary_nonconst,
		                                   KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID,
		                                   NULL);
		/* update our new InfoDb record to the new UIDs */
		kolab_mail_summary_set_char_field ((*record)->summary,
		                                   KOLAB_MAIL_SUMMARY_CHAR_FIELD_KOLAB_UID,
		                                   g_strdup (new_uid));
		kolab_mail_summary_set_char_field ((*record)->summary,
		                                   KOLAB_MAIL_SUMMARY_CHAR_FIELD_IMAP_UID,
		                                   NULL);
		new_status = kolab_mail_summary_get_uint_field ((*record)->summary,
		                                                KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS);
		new_status |= KOLAB_OBJECT_CACHE_STATUS_CHANGED;
		kolab_mail_summary_set_uint_field ((*record)->summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS,
		                                   new_status);
		kolab_mail_info_db_record_set_char_field (*record,
		                                          KOLAB_MAIL_INFO_DB_RECORD_CHAR_FIELD_IMAP_UID_SYNC,
		                                          NULL);
		/* mark the old Kolab UID as changed so it will be reloaded */
		old_record = kolab_mail_info_db_query_record (priv->infodb,
		                                              uid, /* original Kolab UID */
		                                              foldername,
		                                              &tmp_err);
		if (tmp_err != NULL) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_free (tmp_err);
			tmp_err = NULL;
		}
		if (old_record != NULL) {
			KolabObjectCacheStatus old_status = KOLAB_OBJECT_CACHE_STATUS_INVAL;
			KolabObjectCacheLocation old_location = KOLAB_OBJECT_CACHE_LOCATION_INVAL;
			old_status = kolab_mail_summary_get_uint_field (old_record->summary,
			                                                KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS);
			old_status |= KOLAB_OBJECT_CACHE_STATUS_CHANGED;
			kolab_mail_summary_set_uint_field (old_record->summary,
			                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS,
			                                   old_status);
			old_location = kolab_mail_summary_get_uint_field (old_record->summary,
			                                                  KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION);
			old_location &= ~((guint)KOLAB_OBJECT_CACHE_LOCATION_SIDE);
			kolab_mail_summary_set_uint_field (old_record->summary,
			                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION,
			                                   old_location);
			ok = kolab_mail_info_db_update_record (priv->infodb,
			                                       old_record,
			                                       foldername,
			                                       &tmp_err);
			if (! ok) {
				g_warning ("%s: %s", __func__, tmp_err->message);
				g_free (tmp_err);
				tmp_err = NULL;
			}
			ok = kolab_mail_side_cache_delete_by_uid (priv->sidecache,
			                                          uid,
			                                          foldername,
			                                          &tmp_err);
			if (! ok) {
				g_warning ("%s: %s", __func__, tmp_err->message);
				g_free (tmp_err);
				tmp_err = NULL;
			}
			kolab_mail_info_db_record_free (old_record);
		}
		g_debug ("%s: UID (%s) Folder (%s) creating dupe, new UID (%s)",
		         __func__, uid, foldername, new_uid);
		can_act = TRUE;
		break;
	case KOLAB_SYNC_STRATEGY_NEWEST:
		/* keep the "newer" object. this works on timestamps,
		 * which is sort of evil and may yield unexpected results
		 * if client and server clocks are out of sync. YMMV.
		 */
		kmh_srv = kolab_mail_handle_new_from_handle (kmailhandle);
		kolab_mail_handle_set_foldername (kmh_srv, foldername);

		ok = kolab_mail_imap_client_retrieve (priv->client,
		                                      kmh_srv,
		                                      FALSE, /* folder needs to be uptodate already */
		                                      cancellable,
		                                      &tmp_err);
		if (! ok) {
			if (tmp_err->code == KOLAB_BACKEND_ERROR_NOTFOUND) {
				/* uid has _just_now_ been deleted on server,
				 * we can write our object
				 */
				g_error_free (tmp_err);
				tmp_err = NULL;
				can_act = TRUE;
			}
			goto cleanup;
		}
		if (! kolab_mail_handle_has_kconvmail (kmailhandle)) {
			/* oops, should not happen... try again */
			ok = kolab_mail_side_cache_retrieve (priv->sidecache,
			                                     kmailhandle,
			                                     &tmp_err);
			if (! ok)
				/* object not found in SideCache */
				goto cleanup;
		}
		kmh_loc = kolab_mail_handle_new_from_handle_with_kconvmail (kmailhandle);
		kolab_mail_handle_set_foldername (kmh_loc, foldername);

		/* convert to Evo datatype */
		ok = kolab_mail_handle_convert_kconvmail_to_eds (kmh_srv,
		                                                 &tmp_err);
		if (! ok)
			goto cleanup;
		ok = kolab_mail_handle_convert_kconvmail_to_eds (kmh_loc,
		                                                 &tmp_err);
		if (! ok)
			goto cleanup;

		/* datetime comparison between kmailhandle and kmh_server */
		switch (folder_context) {
		case KOLAB_FOLDER_CONTEXT_CONTACT:
			econtact_loc = kolab_mail_handle_get_econtact (kmh_loc);
			econtact_srv = kolab_mail_handle_get_econtact (kmh_srv);
			can_act = kolab_mail_synchronizer_act_on_econtact (econtact_loc,
			                                                   econtact_srv,
			                                                   uid,
			                                                   foldername);
			break;
		case KOLAB_FOLDER_CONTEXT_CALENDAR:
			ecalcomp_loc = kolab_mail_handle_get_ecalcomponent (kmh_loc);
			ecalcomp_srv = kolab_mail_handle_get_ecalcomponent (kmh_srv);
			can_act = kolab_mail_synchronizer_act_on_ecalcomp (ecalcomp_loc,
			                                                   ecalcomp_srv,
			                                                   uid,
			                                                   foldername);
			break;
		default:
			g_assert_not_reached ();
		}

		break;
	default:
		g_assert_not_reached ();
	}

 cleanup:
	if (tmp_err != NULL) {
		if (record_locally_created) {
			kolab_mail_info_db_record_free (*record);
			*record = NULL;
		}
		g_warning ("%s: %s", __func__, tmp_err->message);
		g_propagate_error (err, tmp_err);
		can_act = FALSE;
	}
	if (ecalcomp_loc != NULL)
		g_object_unref (ecalcomp_loc);
	if (ecalcomp_srv != NULL)
		g_object_unref (ecalcomp_srv);
	if (econtact_loc != NULL)
		g_object_unref (econtact_loc);
	if (econtact_srv != NULL)
		g_object_unref (econtact_srv);
	if (kmh_loc != NULL)
		g_object_unref (kmh_loc);
	if (kmh_srv != NULL)
		g_object_unref (kmh_srv);
	if (folder_summary != NULL)
		kolab_folder_summary_free (folder_summary);
	if (changed_uids_tbl != NULL)
		g_hash_table_destroy (changed_uids_tbl);
	if (changed_uids_lst != NULL)
		kolab_util_glib_glist_free (changed_uids_lst);
	if (uid != NULL)
		g_free (uid);
	if (new_uid != NULL)
		g_free (new_uid);
	if (sourcename != NULL)
		g_free (sourcename);
	return can_act;
}

gboolean
kolab_mail_synchronizer_transaction_start (KolabMailSynchronizer *self,
                                           KolabMailAccessOpmodeID opmode,
                                           KolabMailSynchronizerTransactionTypeID ttid,
                                           KolabMailHandle *kmailhandle,
                                           const gchar *foldername,
                                           KolabMailInfoDbRecord *record,
                                           GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert ((opmode > KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED) &&
	          (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE));
	g_assert ((ttid > KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_INVAL) &&
	          (ttid < KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_LAST_TYPE));
	if (kmailhandle != NULL)
		g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_assert (foldername != NULL);
	g_assert (record != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* TODO implement me */

	/* checks needed:
	 * - UID needs sync? -> run KolabMailSynchronizer first?
	 *   - if UID in SideCache only, overwrite?
	 *   - if UID in SideCache and in ImapClient, reject store()
	 *     in favor of a KolabMailSynchronizer run? Imply the run?
	 *   - if UID in ImapClient only, delete() first? Imply it?
	 * - use SideCache as a transaction store first, then try to upload
	 *   (store()) via ImapClient and delete from SideCache on success?
	 * - make sure needed folders exist
	 *   - if kmailhandle->summary has foldername and
	 *     *sourcename != NULL, we're moving folders
	 *   - need to update cache first?
	 * - update InfoDb - at the end only? within the transaction?
	 *
	 */

	/* TODO add status-bits to Summary / InfoDb
	 *      - deleted
	 *      - dirty
	 *      - ...
	 *
	 *      - mark deleted objects (PIM/folder) as "deleted"
	 *        (e.g. if in offline mode and IMAP location bit is set)
	 *      - don't return "deleted" uids/folders in queries
	 *      - remove UIDs/folders from InfoDb only when the physical
	 *        delete operation (ImapClient, SideCache) has actually
	 *        been completed successfully
	 *      - removal of UID from InfoDb marks completed transaction
	 */

	return TRUE;
}

gboolean
kolab_mail_synchronizer_transaction_abort (KolabMailSynchronizer *self,
                                           KolabMailAccessOpmodeID opmode,
                                           KolabMailSynchronizerTransactionTypeID ttid,
                                           KolabMailHandle *kmailhandle,
                                           const gchar *foldername,
                                           KolabMailInfoDbRecord *record,
                                           GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert ((opmode > KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED) &&
	          (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE));
	g_assert ((ttid > KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_INVAL) &&
	          (ttid < KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_LAST_TYPE));
	if (kmailhandle != NULL)
		g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_assert (foldername != NULL);
	g_assert (record != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* TODO implement me */

	return TRUE;
}

gboolean
kolab_mail_synchronizer_transaction_commit (KolabMailSynchronizer *self,
                                            KolabMailAccessOpmodeID opmode,
                                            KolabMailSynchronizerTransactionTypeID ttid,
                                            KolabMailHandle *kmailhandle,
                                            const gchar *foldername,
                                            KolabMailInfoDbRecord *record,
                                            GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	const KolabMailSummary *summary = NULL;
	const gchar *uid = NULL;
	KolabObjectCacheLocation location = KOLAB_OBJECT_CACHE_LOCATION_INVAL;
	KolabObjectCacheStatus status = KOLAB_OBJECT_CACHE_STATUS_DELETED;
	KolabFolderTypeID folder_type = KOLAB_FOLDER_TYPE_INVAL;
	KolabFolderContextID folder_context = KOLAB_FOLDER_CONTEXT_INVAL;
	gboolean ok = TRUE;
	GError *tmp_err = NULL;

	g_assert (KOLAB_IS_MAIL_SYNCHRONIZER (self));
	g_assert ((opmode > KOLAB_MAIL_ACCESS_OPMODE_CONFIGURED) &&
	          (opmode < KOLAB_MAIL_ACCESS_LAST_OPMODE));
	g_assert ((ttid > KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_INVAL) &&
	          (ttid < KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_LAST_TYPE));
	if (kmailhandle != NULL)
		g_assert (KOLAB_IS_MAIL_HANDLE (kmailhandle));
	g_assert (foldername != NULL);
	g_assert (record != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* folder operation transaction */
	if (kmailhandle == NULL) {
		g_warning ("%s: folder operations not yet implemented", __func__);
		g_assert_not_reached ();
	}

	uid = kolab_mail_handle_get_uid (kmailhandle);

	/* updates for store or push operations in online operation */
	if (((ttid == KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_STORE) ||
	     (ttid == KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_PUSH)) &&
	    (opmode == KOLAB_MAIL_ACCESS_OPMODE_ONLINE)) {
		/* mail has been pushed to server successfully and can be
		 * removed from SideCache
		 */
		ok = kolab_mail_side_cache_delete (priv->sidecache,
		                                   kmailhandle,
		                                   &tmp_err);
		if (! ok) {
			gint errcode = tmp_err->code;
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
			if (errcode != KOLAB_BACKEND_ERROR_NOTFOUND)
				goto update_skip;
		}

		kolab_mail_handle_unset_cache_location (kmailhandle,
		                                        KOLAB_OBJECT_CACHE_LOCATION_SIDE);
		kolab_mail_handle_set_cache_location (kmailhandle,
		                                      KOLAB_OBJECT_CACHE_LOCATION_IMAP);
		location = kolab_mail_summary_get_uint_field (record->summary,
		                                              KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION);
		location &= ~((guint)KOLAB_OBJECT_CACHE_LOCATION_SIDE);
		location |= KOLAB_OBJECT_CACHE_LOCATION_IMAP;
		kolab_mail_summary_set_uint_field (record->summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION,
		                                   location);
	update_skip:
		{ /* nop */ }
	}

	/* updates for store operation in offline mode */
	if ((ttid == KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_STORE) &&
	    (opmode == KOLAB_MAIL_ACCESS_OPMODE_OFFLINE)) {
		kolab_mail_handle_set_cache_location (kmailhandle,
		                                      KOLAB_OBJECT_CACHE_LOCATION_SIDE);
		location = kolab_mail_summary_get_uint_field (record->summary,
		                                              KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION);
		location |= (guint)KOLAB_OBJECT_CACHE_LOCATION_SIDE;
		kolab_mail_summary_set_uint_field (record->summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION,
		                                   location);
	}

	/* updates for delete operation in online-mode not needed */

	/* updates for delete operation in offline mode */
	if ((ttid == KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_DELETE) &&
	    (opmode == KOLAB_MAIL_ACCESS_OPMODE_OFFLINE)) {
		status = kolab_mail_summary_get_uint_field (record->summary,
		                                            KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS);
		status |= (guint)KOLAB_OBJECT_CACHE_STATUS_DELETED;
		kolab_mail_summary_set_uint_field (record->summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_STATUS,
		                                   status);
		location = kolab_mail_summary_get_uint_field (record->summary,
		                                              KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION);
		location &= ~((guint)KOLAB_OBJECT_CACHE_LOCATION_SIDE);
		kolab_mail_summary_set_uint_field (record->summary,
		                                   KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION,
		                                   location);
		/* no need to care for the handle here, it will be destroyed */
	}

	summary = kolab_mail_handle_get_summary (kmailhandle);
	folder_type = kolab_mail_summary_get_uint_field (summary,
	                                                 KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_TYPE);
	folder_context = kolab_mail_summary_get_uint_field (summary,
	                                                    KOLAB_MAIL_SUMMARY_UINT_FIELD_FOLDER_CONTEXT);
	(void)kolab_mail_synchronizer_record_update_info (self,
	                                                  opmode,
	                                                  record,
	                                                  summary,
	                                                  uid,
	                                                  foldername,
	                                                  folder_type,
	                                                  folder_context,
	                                                  FALSE);

	location = kolab_mail_summary_get_uint_field (record->summary,
	                                              KOLAB_MAIL_SUMMARY_UINT_FIELD_CACHE_LOCATION);

	if ((location == KOLAB_OBJECT_CACHE_LOCATION_NONE) &&
	    (ttid != KOLAB_MAIL_SYNCHRONIZER_TRANSACTION_TYPE_PUSH)) {
		/* records for pushed objects get removed from InfoDb
		 * when changed_uids are queried from InfoDb, so do not
		 * delete the records here if we're in PUSH operation
		 * (but update the InfoDb from the record instead)
		 */
		g_debug ("%s: UID (%s) removing InfoDb record",
		         __func__, uid);
		ok = kolab_mail_info_db_remove_record (priv->infodb,
		                                       uid,
		                                       foldername,
		                                       &tmp_err);
	} else {
		ok = kolab_mail_info_db_update_record (priv->infodb,
		                                       record,
		                                       foldername,
		                                       &tmp_err);
	}

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

	g_debug ("%s: UID (%s) transaction commit done",
	         __func__, uid);

	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* offline/online bulk synchronization */

gboolean
kolab_mail_synchronizer_info_sync (KolabMailSynchronizer *self,
                                   KolabMailAccessOpmodeID opmode,
                                   const gchar *foldername,
                                   GCancellable *cancellable,
                                   GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	GList *folders_lst = NULL;
	GList *folders_lst_ptr = NULL;
	gboolean ok = TRUE;
	GError *tmp_err = NULL;
	GError *tmp_err_2 = NULL;

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

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* update all folder information */
	ok = kolab_mail_info_db_transaction_start (priv->infodb, &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	ok = kolab_mail_synchronizer_folders_update_infodb (self,
	                                                    opmode,
	                                                    cancellable,
	                                                    &tmp_err);
	if (! ok) {
		ok = kolab_mail_info_db_transaction_abort (priv->infodb, &tmp_err_2);
		if (! ok) {
			g_set_error (err,
			             KOLAB_BACKEND_ERROR,
			             KOLAB_BACKEND_ERROR_INFODB_GENERIC,
			             "%s (%s)",
			             tmp_err_2->message, tmp_err->message);
			g_error_free (tmp_err);
			g_error_free (tmp_err_2);
		} else {
			g_propagate_error (err, tmp_err);
		}
		return FALSE;
	}
	ok = kolab_mail_info_db_transaction_commit (priv->infodb, &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	/* create list of folders to check */
	if (foldername == NULL) {
		folders_lst = kolab_mail_info_db_query_foldernames (priv->infodb,
		                                                    &tmp_err);
		if (tmp_err != NULL) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	} else {
		folders_lst = g_list_prepend (folders_lst,
		                              g_strdup (foldername));
	}

	/* walk all folders, check for (changed) PIM objects */
	ok = kolab_mail_info_db_transaction_start (priv->infodb, &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}
	folders_lst_ptr = folders_lst;
	while (folders_lst_ptr != NULL) {
		gchar *fn = (gchar *)(folders_lst_ptr->data);
		ok = kolab_mail_synchronizer_uids_update_infodb (self,
		                                                 opmode,
		                                                 fn,
		                                                 cancellable,
		                                                 &tmp_err);
		if (! ok)
			break;

		folders_lst_ptr = g_list_next (folders_lst_ptr);
	}
	kolab_util_glib_glist_free (folders_lst);
	if (! ok) {
		ok = kolab_mail_info_db_transaction_abort (priv->infodb, &tmp_err_2);
		if (! ok) {
			g_set_error (err,
			             KOLAB_BACKEND_ERROR,
			             KOLAB_BACKEND_ERROR_INFODB_GENERIC,
			             "%s (%s)",
			             tmp_err_2->message, tmp_err->message);
			g_error_free (tmp_err);
			g_error_free (tmp_err_2);
		} else {
			g_propagate_error (err, tmp_err);
		}
		return FALSE;
	}
	ok = kolab_mail_info_db_transaction_commit (priv->infodb, &tmp_err);
	if (! ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

gboolean
kolab_mail_synchronizer_full_sync (KolabMailSynchronizer *self,
                                   KolabMailAccessOpmodeID opmode,
                                   const gchar *foldername,
                                   GCancellable *cancellable,
                                   GError **err)
{
	KolabMailSynchronizerPrivate *priv = NULL;
	GList *folders_lst = NULL;
	GList *folders_lst_ptr = NULL;
	gboolean ok = FALSE;
	GError *tmp_err = NULL;

	/* kolab_mail_synchronizer_info_sync() must have run
	 * successfully prior to calling this function
	 */

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

	priv = KOLAB_MAIL_SYNCHRONIZER_PRIVATE (self);

	g_assert (priv->is_up == TRUE);

	/* no action to be taken when we're in offline mode */
	if (opmode <=  KOLAB_MAIL_ACCESS_OPMODE_OFFLINE)
		return TRUE;

	/* create list of folders to walk */
	if (foldername == NULL) {
		folders_lst = kolab_mail_info_db_query_foldernames (priv->infodb,
		                                                    &tmp_err);
		if (tmp_err != NULL) {
			g_propagate_error (err, tmp_err);
			return FALSE;
		}
	} else {
		folders_lst = g_list_prepend (folders_lst,
		                              g_strdup (foldername));
	}

	/* walk through folders and push SideCache objects */
	folders_lst_ptr = folders_lst;
	while (folders_lst_ptr != NULL) {
		gchar *fn = (gchar *)(folders_lst_ptr->data);
		ok = kolab_mail_synchronizer_push_sidecache (self,
		                                             fn,
		                                             cancellable,
		                                             &tmp_err);
		if (! ok) {
			g_warning ("%s: %s", __func__, tmp_err->message);
			g_error_free (tmp_err);
			tmp_err = NULL;
		}
		folders_lst_ptr = g_list_next (folders_lst_ptr);
	}
	kolab_util_glib_glist_free (folders_lst);

	return TRUE;
}

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