/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2008  Felix Moeller <felix@derklecks.de> (man page)
 * Copyright (C) 2008-2009  Michael Bell <michael.bell@opensync.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

/** @page syncml-ds-tool
 *
 * @section SYNOPSIS
 * syncml-ds-tool 'OPTIONS' ...
 * 
 * @brief A SyncML Data Synchronization tool for Unix.
 *        The syncml-ds-tool supports OMA DS server and client mode.
 *        Additionally OBEX and HTTP transports are supported in
 *        client and server mode.
 *
 * @section OPTIONS
 *
 * @subsection OPTIONS_DS_CONFIG Datastore configuration
 *
 * @param --sync "<type>" "<path>" "<directory>"
 *        normal two-way sync
 *
 * @param --slow-sync "<type>" "<path>" "<directory>"
 *        slow two-way sync
 *
 * @arg @b type is the content-type of a datastore
 *              text/x-vcard for contacts         
 *              text/x-vcalendar for calendar     
 *              text/plain for notes              
 *              text/x-vMessage for SMS
 *
 * @arg @b path is the used (virtual) source URL/path
 *              It is the local name of the database.
 *              You can choose something there.
 *
 * @arg @b directory is the local path to the synchronized directory
 *              The directory is optional and an absolute path.        
 *              This directory is a persistent storage.                
 *
 * @subsection OPTION_HTTP_CLIENT_CONFIG HTTP client configuration
 *
 * @param --http-client "<url>"
 *
 * @arg @b url must be an http URL like http://localhost:8080
 *
 * @subsection OPTION_HTTP_CLIENT_SERVER_CONFIG HTTP server configuration
 *
 * @param --http-server "<port>"
 *
 * @arg @b port must be a port for the http server.
 *
 * @subsection OPTION_OBEX_CLIENT_CONFIG OBEX client configuration
 *
 * @param -s "<device>"
 *        Connect to the serial device.
 *
 * @param -u
 *        List all available USB interfaces.
 *
 * @param -u "<id>"
 *        Connect to the given usb interface number. You may discover them with -u.
 *
 * @param -b "<addr>" "<channel>"
 *        Connect to the given bluetooth device.
 *
 * @param --ip "<addr>" "<port>"
 *        Connect to this TCP/IP address.
 *
 * @param --irda
 *        Connect using IrDA.
 *
 * @param --irda-service "<service>"
 *        Use the given IrDA service (default: OBEX).
 *
 * @subsection OPTION_OBEX_SERVER_CONFIG OBEX server configuration
 *
 * @param --port "<port>"
 *        Listen to this TCP/IP port.
 *
 * @subsection OPTION_GENERAL General SyncML options
 *
 * @param --identifier "<ident>"
 *        set the local identity of SyncML (source).
 *
 * @param --target "<ident>"
 *        set the remote identity of SyncML (target).
 *
 * @param --username "<username>"
 *        set the username for authentication.
 *
 * @param --password "<password>"
 *        set the password for authentication.
 *
 * @param --maxMsgSize "<limit>"
 *        set the maximum message size (default: 0)
 *
 * @param --maxObjSize* "<limit>"
 *        set the maximum object size (default: 0)
 *
 * @param --useStringTable
 *        Use wbxml string tables (default: NEVER EVER)
 *
 * @param --disableNumberOfChanges
 *        the most new phones support it (default: enabled)
 *
 * @param --useNumberAnchor
 *        Use numbers as anchors.
 *
 * @param --wbxml
 *        Use wbxml (WAP Binary XML) instead of plain xml.
 *
 * @param --read-only
 *        No write actions will be performed.
 *
 * @param --remoteWinsConflicts
 *        If there is a conflict then the remote change wins.
 *        The default is local wins always.
 *        This option is only usable in OMA DS server mode.
 *
 * @param --dumpinfo
 *        Print info about the phone.
 *
 * @param --version
 *        prints the version of the tool.
 * 
 * @param --version "<version>"
 *        sets the SyncML version.
 *
 * @arg @b ident Some devices require a special identity string.
 *               Nokias for example often requires "PC Suite".
 *               Please use --identifier "PC Suite" in this case.
 * @arg @b version can be "1.0", "1.1" or "1.2".
 *                 The default version is "1.1".
 *
 * @subsection OPTION_FAKE_DEVICE Device faking options
 *
 * Some SyncML servers try to enforce access policies via device filtering.
 * These options can be used to work around such filters.
 * 
 * @param --fake-manufacturer "<Man>"
 *        set the manufacturer of the faked device.
 *
 * @param --fake-model "<Mod>"
 *        set the model of the faked device.
 *
 * @param --fake-software-version "<SwV>"
 *        set the software version of the faked device.
 *
 * @section EXAMPLES
 *
 * @subsection EXAMPLE_BLUETOOTH Get the contacts from your phone via Bluetooth
 *
@verbatim
$ syncml-ds-tool -b <mac> <channel> --slow-sync text/x-vcard contacts --wbxml --identifier "PC Suite"
@endverbatim
 * @subsection EXAMPLE_USB Get the notes from a USB connected phone
 *
@verbatim
$ syncml-ds-tool -u <interface> --slow-sync text/plain notes --wbxml --identifier "PC Suite"
@endverbatim
 *
 * @section BUGS
 *
 * @par
 * There is a bugtracker running at http://libsyncml.opensync.org/.
 * If you have a problem please look there to see
 * if it is already reported and add your information
 * to the relavant ticket if possible.
 *
 * @par
 * When opening a new ticket please provide as many information as possible.
 * For faster processing of your bug it helps to attach the trace files.
 * You may find a description how to create them at
 * http://opensync.org/wiki/tracing.
 * For crashes the output of gdb could help. 
 *
 * @section AUTHOR
 *
 * Written by Felix Moeller, <felix@derklecks.de>
 *
 * @section RESOURCES
 *
 * Website: http://libsyncml.opensync.org
 *
 * @section COPYING
 *
 * Copyright (C) 2008 OpenSync Team. Free use of this software is
 * granted under the terms of the GNU Lesser General Public License (LGPL).
 *
 */

#include <libsyncml/syncml.h>
#include <glib.h>
#include <glib/gstdio.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "syncml-ds-tool_internals.h"
#include "config.h"

#ifdef ENABLE_OBEX
/* necessary for list_interfaces */
#include <openobex/obex.h>
#endif

#define STATUS_FILENAME "SYNCML-DS-TOOL-LAST-SYNC"

/* ************************************ */
/* *********** CONFIG ***************** */
/* ************************************ */

#define TOOL_ERROR g_quark_from_static_string("syncml-ds-tool")

typedef struct SmlDsToolLocationType {
	SmlDataSyncDataStore        *object;
	SmlDataSyncDataStoreSession *session;
	/* char *source; */
	gboolean slow;
	/* char *contentType; */
	char *directory;
	GKeyFile *index;
} SmlDsToolLocationType;

GList *datastores = NULL;

char *identifier = NULL;
char *target = NULL;
char *username = NULL;
char *password = NULL;

SmlSessionType sessionType = SML_SESSION_TYPE_SERVER;
gboolean useWbxml = FALSE;
char *syncmlVersion = NULL;

char *maxMsgSize = NULL;
char *maxObjSize = NULL;
GMutex *runMutex = NULL;

gboolean dumpinfo = FALSE;
gboolean useNumberOfChanges = TRUE;
gboolean useStringTable = FALSE;

SmlDevInf *remoteDevinf = NULL;

gboolean localWinsConflicts = TRUE;
time_t checkpoint;

gboolean readOnly = FALSE;

/* ************************************ */
/* *********** USAGE ****************** */
/* ************************************ */

static void usage (char *name, gboolean isError)
{
	fprintf(stderr, "Usage: %s\n\n", name);

	fprintf(stderr, "\tDatastore configuration:\n");
	fprintf(stderr, "\t========================\n\n");
	fprintf(stderr, "\t--sync <type> <path> <directory>\tnormal two-way sync\n");
	fprintf(stderr, "\t--slow-sync <type> <path> <directory>\tslow two-way sync\n\n");
	fprintf(stderr, "\t<type>\tis the content-type of a datastore\n");
	fprintf(stderr, "\t\ttext/x-vcard for contacts\n");
	fprintf(stderr, "\t\ttext/x-vcalendar for calendar\n");
	fprintf(stderr, "\t\ttext/plain for notes\n");
	fprintf(stderr, "\t\ttext/x-vMessage for SMS\n");
	fprintf(stderr, "\t<path>\tis the used (virtual) source URL/path\n");
	fprintf(stderr, "\t\tIt is the local name of the database.\n");
	fprintf(stderr, "\t\tYou can choose something there.\n");
	fprintf(stderr, "\t<directory>\tis the local path to the synchronized directory\n");
	fprintf(stderr, "\t\tThe directory is optional and an absolute path.\n");
	fprintf(stderr, "\t\tThis directory is a persistent storage.\n\n");

	fprintf(stderr, "\tHTTP client configuration:\n");
	fprintf(stderr, "\t==========================\n\n");
	fprintf(stderr, "\t--http-client <url>\n\n");
	fprintf(stderr, "\t--ssl-ca-certs <file>\n\n");
	fprintf(stderr, "\t<url>\tmust be an http URL like http://localhost:8080\n\n");

	fprintf(stderr, "\tHTTP server configuration:\n");
	fprintf(stderr, "\t==========================\n\n");
	fprintf(stderr, "\t--http-server <port>\n");
	fprintf(stderr, "\t--ssl-key <file>\n");
	fprintf(stderr, "\t--ssl-cert <file>\n\n");
	fprintf(stderr, "\t<port>\tmust be a port for the http server.\n\n");

#ifdef ENABLE_OBEX
	fprintf(stderr, "\tOBEX client configuration:\n");
	fprintf(stderr, "\t==========================\n\n");
	fprintf(stderr, "\t-s <device>\tConnect to the serial device.\n");
	fprintf(stderr, "\t-u\t\tList all available USB interfaces.\n");
	fprintf(stderr, "\t-u <id>\t\tConnect to the given usb interface number.\n");
#ifdef ENABLE_BLUETOOTH
	fprintf(stderr, "\t-b <addr> <channel>\tConnect to the given bluetooth device.\n");
#endif
	fprintf(stderr, "\t--ip <addr> <port>\tConnect to this TCP/IP address.\n");
	fprintf(stderr, "\t--irda\t\tConnect using IrDA.\n");
	fprintf(stderr, "\t--irda-service <service>\tUse the given IrDA service (default: OBEX).\n\n");

	fprintf(stderr, "\tOBEX server configuration:\n");
	fprintf(stderr, "\t==========================\n\n");
	fprintf(stderr, "\t--port <port>\tListen to this TCP/IP port.\n\n");
#endif

	fprintf(stderr, "\tGeneral SyncML options:\n");
	fprintf(stderr, "\t=======================\n\n");
	fprintf(stderr, "\t--identifier <ident>\tsets the local identity of the SyncML (source).\n");
	fprintf(stderr, "\t--target <ident>\tsets the remote identity of SyncML (target).\n");
	fprintf(stderr, "\t--username <username>\tsets the username for authentication.\n");
	fprintf(stderr, "\t--password <password>\tsets the password for authentication.\n");
	fprintf(stderr, "\t--maxMsgSize <limit>\tsets the maximum message size (default: %s)\n", maxMsgSize);
	fprintf(stderr, "\t--maxObjSize <limit>\tsets the maximum object size (default: %s)\n", maxObjSize);
	fprintf(stderr, "\t--useStringTable\tUse wbxml string tables (default: NEVER EVER)\n");
	fprintf(stderr, "\t--disableNumberOfChanges\tthe most new phones support it (default: enabled)\n");
	fprintf(stderr, "\t--useNumberAnchor\t\tUse numbers as anchors.\n");
	fprintf(stderr, "\t--useLocaltime\t\tUse localtime instead of UTC.\n");
	fprintf(stderr, "\t--wbxml\t\t\tUse wbxml (WAP Binary XML) instead of plain xml.\n");
	fprintf(stderr, "\t--read-only\t\tno write actions will be performed.\n");
	fprintf(stderr, "\t--remoteWinsConflicts\tIf there is a conflict then the remote change wins.\n");
	fprintf(stderr, "\t\t\t\tThe default is local wins always.\n");
	fprintf(stderr, "\t\t\t\tThis option is only usable in OMA DS server mode.\n");
	fprintf(stderr, "\t--dumpinfo\t\tPrint info about the phone.\n");
	fprintf(stderr, "\t--version\t\tprints the version of the tool.\n");
	fprintf(stderr, "\t--version <version>\tsets the SyncML version.\n\n");
	fprintf(stderr, "\t<ident>\tSome devices require a special identity string.\n");
	fprintf(stderr, "\t\tNokias for example often requires \"PC Suite\".\n");
	fprintf(stderr, "\t\tPlease use --identifier \"PC Suite\" in this case.\n");
	fprintf(stderr, "\t<version>\tcan be \"1.0\", \"1.1\" or \"1.2\".\n");
	fprintf(stderr, "\t\tThe default version is \"1.1\".\n\n");

	fprintf(stderr, "\tDevice faking configuration:\n");
	fprintf(stderr, "\t============================\n\n");
	fprintf(stderr, "\t--fake-manufacturer <Man>\tset the manufacturer of the faked device.\n");
	fprintf(stderr, "\t--fake-model <Mod>\t\tset the model of the faked device.\n");
	fprintf(stderr, "\t--fake-software-version <SwV>\tset the software version of the faked device.\n");

	fprintf(stderr, "\n");
	if (isError)
		exit (1);
	else
		exit(0);
}

/* ************************************ */
/* ******** CLI OPTIONS *************** */
/* ************************************ */

#ifdef ENABLE_OBEX

/* directly copied from syncml-obex-client */

static void discover_cb(obex_t *handle, obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp)
{
        (void) handle;
        (void) object;
        (void) mode;
        (void) event;
        (void) obex_cmd;
        (void) obex_rsp;
}

void list_interfaces()
{
	obex_t *handle;
	obex_interface_t* obex_intf;
	int i, interfaces_number = 0;
	
	if(!(handle = OBEX_Init(OBEX_TRANS_USB, discover_cb, 0))) {
		printf("OBEX_Init failed\n");
		return;
	}

#ifndef WIN32	
	if (geteuid() != 0)
		fprintf(stderr, "Superuser privileges are required to access complete USB information.\n");
#endif

	interfaces_number = OBEX_FindInterfaces(handle, &obex_intf);
	printf("Found %d USB OBEX interfaces\n", interfaces_number);
	
	for (i = 0; i < interfaces_number; i++)
		printf("Interface %d:\n\tManufacturer: %s\n\tProduct: %s\n\tInterface description: %s\n", i,
			obex_intf[i].usb.manufacturer,
			obex_intf[i].usb.product,
			obex_intf[i].usb.control_interface);
	
	printf("Use '-u interface_number' to connect\n");
	OBEX_Cleanup(handle);
}

#endif

static gboolean     smlDsToolRecvDevInfCallback       (SmlDataSyncSession *self, SmlDevInf *devinf, void *userdata, GError **error);
static SmlAlertType smlDsToolRecvAlertTypeCallback    (SmlDataSyncDataStoreSession *self, SmlAlertType type, void *userdata, GError **error);
static gboolean     smlDsToolRecvChangeCallback       (SmlDataSyncDataStoreSession *self, SmlDataSyncChangeItem *item, void *userdata, GError **error);
static gboolean     smlDsToolRecvChangeStatusCallback (SmlDataSyncDataStoreSession *self, SmlDataSyncChangeItem *item, SmlErrorType code, void *userdata, GError **error);
static gboolean     smlDsToolRecvMappingCallback      (SmlDataSyncDataStoreSession *self, SmlMapItem *item, void *userdata, GError **error);
static gchar*       smlDsToolGetAnchorCallback        (SmlDataSyncDataStoreSession *self, gboolean remote, void *userdata, GError **error);
static gboolean     smlDsToolSetAnchorCallback        (SmlDataSyncDataStoreSession *self, gboolean remote, const gchar *anchor, void *userdata, GError **error);

SmlTransportType getTransportType(int argc, char *argv[])
{
	smlTrace(TRACE_ENTRY, "%s", __func__);

	SmlTransportType tspType = SML_TRANSPORT_OBEX_CLIENT;
	int i = 0;
	for (i = 1; i < argc; i++) {
		char *arg = argv[i];
		if (!strcmp (arg, "--http-client")) {
			tspType = SML_TRANSPORT_HTTP_CLIENT;
		} else if (!strcmp (arg, "--http-server")) {
			tspType = SML_TRANSPORT_HTTP_SERVER;
		} else if (!strcmp (arg, "--port")) {
			tspType = SML_TRANSPORT_OBEX_SERVER;
		}
	}
	smlTrace(TRACE_EXIT, "%s - %d", __func__, tspType);
	return tspType;
}

SmlSessionType getSessionType(int argc, char *argv[])
{
	smlTrace(TRACE_ENTRY, "%s", __func__);

	SmlSessionType type = SML_SESSION_TYPE_SERVER;
	int i = 0;
	for (i = 1; i < argc; i++) {
		char *arg = argv[i];
		if (!strcmp (arg, "--http-client")) {
			type = SML_SESSION_TYPE_CLIENT;
		} else if (!strcmp (arg, "--http-server")) {
			type = SML_SESSION_TYPE_SERVER;
		} else if (!strcmp (arg, "--port")) {
			/* this is not a typo */
			type = SML_SESSION_TYPE_CLIENT;
		}
	}
	smlTrace(TRACE_EXIT, "%s - %d", __func__, type);
	return type;
}

gboolean scanArguments(
		SmlDataSync *dsObject,
		int argc, char *argv[],
		GError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	if (argc == 1)
		usage (argv[0], TRUE);
	maxMsgSize = g_strdup("65535");
	maxObjSize = g_strdup("3000000");

	int i = 0;
	for (i = 1; i < argc; i++) {
		char *arg = argv[i];
		if (!strcmp (arg, "--sync") || !strcmp(arg, "--slow-sync")) {

			/* prepare datastore */

			SmlDsToolLocationType *datastore = NULL;
			datastore = (SmlDsToolLocationType *) smlTryMalloc0(sizeof(SmlDsToolLocationType), error);
			if (!datastore)
				goto error;
			datastores = g_list_append(datastores, datastore);

			datastore->object = sml_data_sync_data_store_new();
			sml_data_sync_data_store_register_get_alert_type_callback(datastore->object, smlDsToolRecvAlertTypeCallback, datastore);
			sml_data_sync_data_store_register_change_callback(datastore->object, smlDsToolRecvChangeCallback, datastore);
			sml_data_sync_data_store_register_change_status_callback(datastore->object, smlDsToolRecvChangeStatusCallback, datastore);
			sml_data_sync_data_store_register_mapping_callback(datastore->object, smlDsToolRecvMappingCallback, datastore);

			/* sync type */

			if (!strcmp(arg, "--slow-sync"))
				datastore->slow = TRUE;
			else
				datastore->slow = FALSE;
				
			/* load content type */

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);

			sml_data_sync_data_store_set_content_type(datastore->object, argv[i]);
			if (!strstr(argv[i], "/"))
				fprintf(stderr,
					"WARNING: Specified database type \"%s\" doesn't look like a valid MIME type!\n"
					"WARNING: (Mixed up database path/location with database type?)\n",
					argv[i]);

			/* load location */

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			
			sml_data_sync_data_store_set_local_uri(datastore->object, argv[i]);

			/* load directory if available */

			if (argv[i+1] && argv[i+1][0] != '-') {
				i++;
				datastore->directory = argv[i];
				/* check the directory */
				if (g_mkdir_with_parents(datastore->directory, 0700) != 0)
				{
					g_set_error(error, TOOL_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
						"There is a problem with the directory %s (%d).",
						datastore->directory, errno);
					goto error;
				}
				/* load the index if available */
				gchar *filename = g_strdup_printf("%s/%s.INDEX", datastore->directory, STATUS_FILENAME);
				gsize length = 0;
				gchar *data = NULL;
				if (g_file_test(filename, G_FILE_TEST_EXISTS) &&
				    !g_file_get_contents(filename, &data, &length, error))
				{
					g_set_error(error, TOOL_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
						"The index file %s cannot be loaded.",
						filename);
					g_free(filename);
					goto error;
				}
				g_free(filename);
				datastore->index = g_key_file_new();
				if (data && length &&
				    !g_key_file_load_from_data(datastore->index, data, length, 0, error)) {
					g_free(data);
					goto error;
				}
				g_free(data);
				/* anchors are only managed if they can be cached */
				sml_data_sync_data_store_register_get_anchor_callback(datastore->object, smlDsToolGetAnchorCallback, datastore);
				sml_data_sync_data_store_register_set_anchor_callback(datastore->object, smlDsToolSetAnchorCallback, datastore);
			} else {
				datastore->directory = NULL;
			}

			/* register datastore */
			if (!sml_data_sync_add_data_store(
					dsObject,
					datastore->object,
					error))
				goto error;

		} else if (!strcmp (arg, "-u")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_USB,
					error))
				goto error;
			i++;
			if (!argv[i]) {
#ifdef ENABLE_OBEX
				list_interfaces();
				smlTrace(TRACE_EXIT, "%s - OBEX list returned", __func__);
				return FALSE;
#else
				printf("OBEX not available in this build\n");
				smlTrace(TRACE_EXIT_ERROR, "%s - OBEX list requested but not available", __func__);
				return FALSE;
#endif
			}
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_PORT,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "-b")) {
#ifdef ENABLE_BLUETOOTH
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_BLUETOOTH,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_BLUETOOTH_ADDRESS, 
					argv[i], error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_BLUETOOTH_CHANNEL, 
					argv[i], error))
				goto error;
#else
			printf("Bluetooth is not available in this build\n");
			smlTrace(TRACE_EXIT, "%s - Bluetooth requested but not available", __func__);
			return FALSE;
#endif
		} else if (!strcmp (arg, "--irda")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_IRDA,
					error))
				goto error;
		} else if (!strcmp (arg, "--irda-service")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_IRDA,
					error))
				goto error;
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_IRDA_SERVICE, 
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "-s")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_SERIAL,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_URL, 
					argv[i], error))
				goto error;

		} else if (!strcmp (arg, "--channel")) {
#ifdef ENABLE_BLUETOOTH
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_BLUETOOTH,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_BLUETOOTH_CHANNEL, 
					argv[i], error))
				goto error;
#else
			printf("Bluetooth is not available in this build\n");
			smlTrace(TRACE_EXIT, "%s - Bluetooth requested but not available", __func__);
			return FALSE;
#endif
		} else if (!strcmp (arg, "--ip")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_NET,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_URL, 
					argv[i], error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_PORT, 
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--port")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_NET,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_PORT, 
					argv[i], error))
				goto error;
 		} else if (!strcmp (arg, "--identifier")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_IDENTIFIER, 
					argv[i], error))
				goto error;
			identifier = g_strdup(argv[i]);
 		} else if (!strcmp (arg, "--target")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_TARGET, 
					argv[i], error))
				goto error;
			target = g_strdup(argv[i]);
		} else if (!strcmp (arg, "--username")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_AUTH_USERNAME, 
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--password")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_AUTH_PASSWORD, 
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--maxMsgSize")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			maxMsgSize = argv[i];
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_MAX_MSG_SIZE,
					maxMsgSize, error))
				goto error;
		} else if (!strcmp (arg, "--maxObjSize")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			maxObjSize = argv[i];
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_MAX_OBJ_SIZE,
					maxObjSize, error))
				goto error;
		} else if (!strcmp (arg, "--useStringTable")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_STRING_TABLE,
					"1", error))
				goto error;
		} else if (!strcmp (arg, "--disableNumberOfChanges")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_NUMBER_OF_CHANGES,
					"0", error))
				goto error;
		} else if (!strcmp (arg, "--useNumberAnchor")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_TIMESTAMP_ANCHOR,
					"0", error))
				goto error;
		} else if (!strcmp (arg, "--useLocaltime")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_LOCALTIME,
					"1", error))
				goto error;
		} else if (!strcmp (arg, "--version")) {
			i++;
			if (!argv[i]) {
				fprintf(stdout, "Version: %s ($Revision: 1360 $)\n", VERSION);
				smlTrace(TRACE_EXIT, "%s - Only the version was requested.", __func__);
				return FALSE;
			}

			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_VERSION,
					argv[i], error))
				goto error;
			syncmlVersion = g_strdup(argv[i]);
		} else if (!strcmp (arg, "--help")) {
			usage (argv[0], FALSE);
		} else if (!strcmp (arg, "--wbxml")) {
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_WBXML,
					"1", error))
				goto error;
			useWbxml = TRUE;
		} else if (!strcmp (arg, "--dumpinfo")) {
			dumpinfo = TRUE;
			sml_data_sync_register_handle_remote_dev_inf_callback (dsObject, smlDsToolRecvDevInfCallback, NULL);
		} else if (!strcmp (arg, "--remoteWinsConflicts")) {
			localWinsConflicts = FALSE;
			if (sessionType == SML_SESSION_TYPE_CLIENT)
				usage(argv[0], TRUE);
		} else if (!strcmp(arg, "--read-only")) {
			readOnly = TRUE;
		} else if (!strcmp (arg, "--http-client")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_URL,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--ssl-ca-certs")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_SSL_CA_FILE,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--http-server")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_PORT,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--ssl-key")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_SSL_KEY,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--ssl-cert")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_TRANSPORT_CONFIG_SSL_SERVER_CERT,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--fake-manufacturer")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_DEVICE,
					"1", error))
				goto error;
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_MANUFACTURER,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--fake-model")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_DEVICE,
					"1", error))
				goto error;
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_MODEL,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--fake-software-version")) {
			i++;
			if (!argv[i])
				usage (argv[0], TRUE);
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_DEVICE,
					"1", error))
				goto error;
			if (!sml_data_sync_set_option(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_SOFTWARE_VERSION,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--")) {
			break;
		} else {
			g_warning("Unknown parameter: %s", arg);
			usage (argv[0], TRUE);
		}
	}

	if (g_list_length(datastores) == 0) {
		if (syncmlVersion == NULL || strcmp(syncmlVersion, "1.2")) {
			g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "You have to configure at least one database");
			goto error;
		} else {
			printf("All datastores will be requested because no datastore is configured.\n");
		}
	}

	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR,
		"%s - Failed to start the client: %s\n",
		(*error)->message);
	return FALSE;
}

/* ************************************ */
/* *********** CALLBACKS ************** */
/* ************************************ */

static void writeSyncStatus(gboolean success)
{
	smlTrace(TRACE_ENTRY, "%s (%s)", __func__, success ? "TRUE" : "FALSE");
	GError *error = NULL;
	SmlDsToolLocationType *datastore = NULL;
	GList *o = datastores;
	for(;o;o = o->next) {
		datastore = o->data;
		if (datastore->directory) {
			char *absolute = g_strdup_printf("%s/%s", datastore->directory, STATUS_FILENAME);
			if (success) {
				if (!g_file_set_contents(absolute, "Okay.", 5, &error))
					goto error;

				/* migrate all entries from committed_index to last_index */
				char **keys = NULL;
				if (g_key_file_has_group(datastore->index, "committed_index"))
					keys = g_key_file_get_keys(datastore->index, "committed_index", NULL, NULL);
				int pos = 0;
				while (keys != NULL && keys[pos] != NULL)
				{
					char *digest = g_key_file_get_string(datastore->index, "committed_index", keys[pos], &error);
					if (!digest && error)
						goto error;
					/* NOTICE: glib changed the return type of g_key_file_remove_key */
					g_key_file_remove_key(datastore->index, "committed_index", keys[pos], &error);
					if (error)
						goto error;
					g_key_file_set_string(datastore->index, "last_index", keys[pos], digest);
					smlSafeCFree(&digest);
					pos++;
				}
				g_strfreev(keys);

				/* dump index */
				gchar *filename = g_strdup_printf("%s/%s.INDEX", datastore->directory, STATUS_FILENAME);
				gsize length = 0;
				gchar *data = g_key_file_to_data(datastore->index, &length, &error);
				if (!data && error)
					goto error;
				if (!g_file_set_contents(filename, data, length, &error))
					goto error;
				g_free(filename);
			} else {
				g_unlink(STATUS_FILENAME);
			}
			smlSafeCFree(&absolute);
		}
	}
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", error->message);
	g_error("%s", error->message);
}

gboolean
sendAllChanges (SmlDataSyncSession *session,
                GError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	GList *o = datastores;
	/* If readOnly then data MUST NOT be sent. */
	for (;!readOnly && o;o=o->next) {
		SmlDsToolLocationType *datastore = o->data;
		if (!datastore->directory)
			continue;
		printf("Sending all changes of directory %s ...\n", datastore->directory);
		smlTrace(TRACE_INTERNAL, "%s: checking %s", __func__, VA_STRING(sml_data_sync_data_store_get_local_uri(datastore->object)));

		GDir *dir = g_dir_open(datastore->directory, 0, error);
		if (!dir)
			goto error;
		const gchar *filename = g_dir_read_name(dir);

		datastore->session = sml_data_sync_session_get_data_store_session(session, datastore->object, error);
		if (!datastore->session)
			goto error;

		/* handle all required ADD and REPLACE commands
		 *
		 * The INDEX file includes a group called last_index.
		 * last_index includes a hash of every known item.
		 * So every found file will be checked.
		 *     1. If it is new or changed
		 *        then the item will be migrated to the change_index.
		 *     2. If the file is already known
		 *        then the item will be migrated to the committed_index.
		 */
		for (;filename; filename = g_dir_read_name(dir)) {
			smlTrace(TRACE_INTERNAL, "%s: checking %s", __func__, VA_STRING(filename));
			if (!strncmp(filename, STATUS_FILENAME, strlen(STATUS_FILENAME)))
				continue;
			char *absolute = g_strdup_printf("%s/%s", datastore->directory, filename);

			/* load data */
			gsize length;
			char *data = NULL;
			if (!g_file_get_contents(absolute, &data, &length, error)) {
				smlSafeCFree(&absolute);
				goto error;
			}
			smlSafeCFree(&absolute);

			/* calculate and load checksum */
#ifdef HAVE_GLIB_GCHECKSUM_H
			char *digest = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *) data, length);
#else
			char *digest = sml_ds_tool_get_md5 (data, length, error);
			if (!digest)
				goto error;
#endif
			char *original = NULL;
			if (g_key_file_has_group(datastore->index, "last_index"))
				original = g_key_file_get_string(datastore->index, "last_index", filename, NULL);

			/* fix indexes */
			SmlChangeType changeType = SML_CHANGE_UNKNOWN;
			if (original)
			{
				/* potentially changed item */
				if (strcmp(digest, original))
				{
					/* changed item */
					printf("\tItem %s was locally changed.\n", filename);
					if (datastore->slow)
					{
						/* SLOWSYNC and a conflict => create a duplicate */
						printf("\tCONFLICT: SLOWSYNC => Creating a duplicate ...\n");
						changeType = SML_CHANGE_ADD;
						g_key_file_set_string(datastore->index, "change_index", filename, digest);
					} else {
						changeType = SML_CHANGE_REPLACE;
						g_key_file_set_string(datastore->index, "change_index", filename, digest);
						/* NOTICE: glib changed the return type of g_key_file_remove_key */
						g_key_file_remove_key(datastore->index, "last_index", filename, error);
						if (*error)
							goto error;
					}
				} else {
					/* nothing to do */
					g_key_file_set_string(datastore->index, "committed_index", filename, digest);
					/* NOTICE: glib changed the return type of g_key_file_remove_key */
					g_key_file_remove_key(datastore->index, "last_index", filename, error);
					if (*error)
						goto error;
					continue;
				}
			} else {
				/* new item */
				printf("\tItem %s was locally added.\n", filename);
				changeType = SML_CHANGE_ADD;
				g_key_file_set_string(datastore->index, "change_index", filename, digest);
			}

			/* add change */

			smlTrace(TRACE_INTERNAL, "%s: sending %s", __func__, VA_STRING(filename));

			SmlDataSyncChangeItem *item = sml_data_sync_change_item_new();
			sml_data_sync_change_item_set_action(item, changeType);

			SmlLocation *uid = sml_location_new();
			if (changeType != SML_CHANGE_ADD)
			{
				const gchar *map = g_key_file_get_string(datastore->index, "map", filename, error);
				if (!map)
					goto error;
				sml_location_set_uri(uid, map);
			} else {
				sml_location_set_uri(uid, filename);
			}
			if (!sml_data_sync_change_item_set_location(item, uid, error))
				goto error;
			if (!sml_data_sync_change_item_set_data(item, data, length, error))
				goto error;

			if (!sml_data_sync_data_store_session_add_change(datastore->session, item, NULL, error))
				goto error;

			smlSafeCFree(&data);
		}
		g_dir_close(dir);
		dir = NULL;

		/* All items which are still in last_index must be deleted.
		 * Those items will be written to delete_index.
		 */

		gsize count = 0;
                char **keys = NULL;
		if (g_key_file_has_group(datastore->index, "last_index")) {
			keys = g_key_file_get_keys(datastore->index, "last_index", &count, error);
			if (*error)
				goto error;
		}
		gsize i = 0;
		for (; !datastore->slow && i < count; i++)
		{
			printf("\tItem %s was locally deleted.\n", keys[i]);

			/* fix indexes */

			char *digest = g_key_file_get_string(datastore->index, "last_index", keys[i], error);
			if (!digest && *error)
				goto error;
			g_key_file_set_string(datastore->index, "change_index", keys[i], digest);
			/* NOTICE: glib changed the return type of g_key_file_remove_key */
			g_key_file_remove_key(datastore->index, "last_index", keys[i], error);
			if (*error)
				goto error;

			/* add change */

			SmlDataSyncChangeItem *item = sml_data_sync_change_item_new();
			sml_data_sync_change_item_set_action(item, SML_CHANGE_DELETE);

			SmlLocation *uid = sml_location_new();
			const gchar *map = g_key_file_get_string(datastore->index, "map", keys[i], error);
			if (!map)
				goto error;
			sml_location_set_uri(uid, map);
			if (!sml_data_sync_change_item_set_location(item, uid, error))
				goto error;

			if (!sml_data_sync_data_store_session_add_change(datastore->session, item, NULL, error))
				goto error;
		}
	}
	smlTrace(TRACE_EXIT, "%s", __func__);
	return sml_data_sync_session_send_changes(session, error);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

static void
smlDsToolRecvEventCallback (SmlDataSyncSession *session,
                            SmlDataSync *dsObject,
                            SmlDataSyncEventType type,
                            void *userdata,
                            const GError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p, %p)", __func__, session, dsObject, type, userdata, error);
	GError *locerror = NULL;
	
	switch (type) {
		case SML_DATA_SYNC_SESSION_EVENT_ERROR:
			g_message("ERROR: %s\n", error->message);
			writeSyncStatus(FALSE);
			// g_error_free(error);
			smlTrace(TRACE_EXIT_ERROR, "%s - failed.", __func__);
			exit(2);
			break;
		case SML_DATA_SYNC_SESSION_EVENT_CONNECT:
			g_message("Remote device was successfully connected.");
			break;
		case SML_DATA_SYNC_SESSION_EVENT_DISCONNECT:
			g_message("Remote device was successfully disconnected.");
			break;
		case SML_DATA_SYNC_SESSION_EVENT_FINISHED:
			g_message("SyncML session finished successfully.");
			writeSyncStatus(TRUE);
			g_mutex_unlock(runMutex);
			break;
		case SML_DATA_SYNC_SESSION_EVENT_GOT_ALL_ALERTS:
			g_message("All alerts of the remote device were received.");
			if (sessionType == SML_SESSION_TYPE_CLIENT)
			{
				if (!sendAllChanges(session, &locerror))
					goto error;
			}
			break;
		case SML_DATA_SYNC_SESSION_EVENT_GOT_ALL_CHANGES:
			g_message("All changes of the remote device were received.");
			if (sessionType == SML_SESSION_TYPE_SERVER)
			{
				if (!sendAllChanges(session, &locerror))
					goto error;
			}
			/* the map of the client is send automatically */
			break;
		case SML_DATA_SYNC_SESSION_EVENT_GOT_ALL_MAPPINGS:
			if (sessionType == SML_SESSION_TYPE_SERVER)
			{
				g_message("All mappings of the remote device were received.");
			} else {
				g_error("Received a map but I'm a client!");
			}
			break;
		default:
			g_error("Unknown event(%d).\n", type);
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;

error:
	fprintf(stderr, "An error occured while handling events: %s\n", locerror->message);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, locerror->message);
	g_error_free(locerror);
	exit(3);
}

static char * getSafeFilename(const char *filename)
{
	gboolean clean = TRUE;
	size_t i;
	for (i=0; i < strlen(filename); i++)
	{
		if (!g_ascii_isalnum(filename[i]) &&
		    filename[i] != '-' &&
		    filename[i] != '_')
			clean = FALSE;
	}
	if (clean) {
		return g_strdup(filename);
	} else {
		return g_base64_encode((const unsigned char *) filename, strlen(filename));
	}
}

static gboolean
smlDsToolRecvChangeCallback (SmlDataSyncDataStoreSession *session,
                             SmlDataSyncChangeItem *item,
                             void *userdata,
                             GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, session, item, userdata, error);
	char *absolute_uid = NULL;

	/* find the appropriate datasoure */
	SmlDsToolLocationType *datastore = userdata;
	if (!datastore)
	{
		g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC,
		            "The userdata of the callback %s is missing.", __func__);
		goto error;
	}

	/* handle the item */
	if (datastore->directory) {

		/* prepare UIDs and absolute filenames */

		SmlLocation *uid = sml_data_sync_change_item_get_location(item);
		const char *orgUID = sml_location_get_uri(uid);
		char *safeUID = getSafeFilename(orgUID);
		printf("Writing item %s to directory %s.\n", safeUID, datastore->directory);

		/* map the item if necessary */
		if (sml_data_sync_change_item_get_action(item) != SML_CHANGE_ADD)
		{
			/* sometimes an item is not mappeni
			 * (e.g. client:ADD => server:DELETE because of error 419)
			 */
			const char *map = NULL;
			if (g_key_file_has_group(datastore->index, "reverse_map"))
				map = g_key_file_get_string(datastore->index, "reverse_map", safeUID, error);
			if (!map && *error)
				goto error;
			if (map)
			{
				smlSafeCFree(&safeUID);
				safeUID = g_strdup(map);
			}
			printf("\tMap item to %s.\n", safeUID);
		}

		absolute_uid = g_strdup_printf("%s/%s", datastore->directory, safeUID);
		/* sanity check for uid */
		if (!strncmp(safeUID, STATUS_FILENAME, strlen(STATUS_FILENAME)))
		{
			g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC,
				"The filename %s cannot be used as UID. This is a potential attack.",
				STATUS_FILENAME);
			printf("\tIllegal filename %s detected.\n", STATUS_FILENAME);
			g_warning("Potential attack against status file %s detected.",
				STATUS_FILENAME);
			goto error;
		}
		SmlChangeType action = sml_data_sync_change_item_get_action(item);

		/* load digest information */

		char *index_digest = NULL;
		char *item_digest = NULL;
		char *file_digest = NULL;
		if (g_key_file_has_group(datastore->index, "last_index"))
			index_digest = g_key_file_get_string(datastore->index, "last_index", safeUID, NULL);
		const char *data = sml_data_sync_change_item_get_data(item);
		if (data)
		{
#ifdef HAVE_GLIB_GCHECKSUM_H
			item_digest = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *) data, strlen(data));
#else
			item_digest = sml_ds_tool_get_md5 (data, strlen(data), error);
			if (!item_digest)
				goto error;
#endif
		}
		if (g_file_test(absolute_uid, G_FILE_TEST_EXISTS))
		{
			char *file_data = NULL;
			gsize file_length = 0;
			if (!g_file_get_contents(absolute_uid, &file_data, &file_length, error))
				goto error;
#ifdef HAVE_GLIB_GCHECKSUM_H
			file_digest = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *) file_data, file_length);
#else
			file_digest = sml_ds_tool_get_md5 (file_data, file_length, error);
			if (!file_digest)
				goto error;
#endif
			smlSafeCFree(&file_data);
		}

		/* determine the action */

		gboolean doWrite = FALSE;
		switch(action)
		{
			case SML_CHANGE_ADD:
				if (g_file_test(absolute_uid, G_FILE_TEST_EXISTS))
				{
					if (datastore->slow)
					{
						if (strcmp(file_digest, item_digest) == 0)
						{
							/* nothing to do - items identical */
							g_key_file_set_string(datastore->index, "last_index", safeUID, file_digest);
							printf("\tIdentical items detected.\n");
						} else {
							/* The items do not match.
							 * This is a classical conflict.
							 */
							if (localWinsConflicts)
							{
								printf("\tConflict: Remote add command is ignored.\n");
								printf("\tConflict: Local item wins conflict.\n");
								g_key_file_set_string(datastore->index, "last_index", safeUID, file_digest);
							} else {
								printf("\tConflict: Local item is ignored.\n");
								printf("\tConflict: Remote add command wins conflict.\n");
								doWrite = TRUE;
							}
						}
					} else {
						/* This should be a new remote item.
						 * The entry must be duplicated.
						 */
						doWrite = TRUE;
						while (g_file_test(absolute_uid, G_FILE_TEST_EXISTS))
						{
							char *new = g_strdup_printf("%s%s", safeUID, "_dup");
							smlSafeCFree(&safeUID);
							smlSafeCFree(&absolute_uid);
							safeUID = new;
							absolute_uid = g_strdup_printf("%s/%s", datastore->directory, safeUID);
						}
						SmlMapItem *map = sml_map_item_new();
						if (!sml_map_item_set_remote(map, uid, error))
							goto error;
						SmlLocation *local = sml_location_new();
						sml_location_set_uri(local, safeUID);
						if (!sml_map_item_set_local(map, local, error))
							goto error;
						if (!sml_data_sync_data_store_session_add_mapping(session, map, NULL, error))
							goto error;
						g_object_unref(map);
						g_object_unref(local);
						g_key_file_set_string(datastore->index, "map", safeUID, orgUID);
						g_key_file_set_string(datastore->index, "reverse_map", orgUID, safeUID);
						printf("\tItem %s will be duplicated.\n", safeUID);
					}
				} else {
					if (index_digest && strcmp(index_digest, item_digest) == 0)
					{
						/* the item was locally deleted => ignore add */
						printf("\tThe item was already locally deleted.\n");
						g_key_file_remove_key(datastore->index, "last_index", safeUID, NULL);
					} else if (!index_digest)
					{
						/* normal add */
						printf("\tThe item is new.\n");
						doWrite = TRUE;
						SmlMapItem *map = sml_map_item_new();
						if (!sml_map_item_set_remote(map, uid, error))
							goto error;
						SmlLocation *local = sml_location_new();
						sml_location_set_uri(local, safeUID);
						if (!sml_map_item_set_local(map, local, error))
							goto error;
						if (!sml_data_sync_data_store_session_add_mapping(session, map, NULL, error))
							goto error;
						g_object_unref(map);
						g_object_unref(local);
						g_key_file_set_string(datastore->index, "map", safeUID, orgUID);
						g_key_file_set_string(datastore->index, "reverse_map", orgUID, safeUID);
					} else {
						/* The item was locally different
						 * but it was already deleted.
						 * This is a classical conflict.
						 */
						if (localWinsConflicts)
						{
							printf("\tConflict: Remote add command is ignored.\n");
							printf("\tConflict: Local deletion wins conflict.\n");
							g_key_file_remove_key(datastore->index, "last_index", safeUID, NULL);
						} else {
							printf("\tConflict: Local deletion is ignored.\n");
							printf("\tConflict: Remote add command wins conflict.\n");
							doWrite = TRUE;
							SmlMapItem *map = sml_map_item_new();
							if (!sml_map_item_set_remote(map, uid, error))
								goto error;
							SmlLocation *local = sml_location_new();
							sml_location_set_uri(local, safeUID);
							if (!sml_map_item_set_local(map, local, error))
								goto error;
							if (!sml_data_sync_data_store_session_add_mapping(session, map, NULL, error))
								goto error;
							g_object_unref(map);
							g_object_unref(local);
							g_key_file_set_string(datastore->index, "map", safeUID, orgUID);
							g_key_file_set_string(datastore->index, "reverse_map", orgUID, safeUID);
						}
					}
				}
				break;
			case SML_CHANGE_REPLACE:
				if (g_file_test(absolute_uid, G_FILE_TEST_EXISTS))
				{
					if (strcmp(file_digest, index_digest) == 0)
					{
						/* There is no local change.
						 * The local item is in sync.
						 * So the remote change should be accepted.
						 */
						printf("\tThe local item will be changed.\n");
						doWrite = TRUE;
					} else if (strcmp(file_digest, item_digest) == 0)
					{
						/* nothing to do - items identical */
						printf("\tIdentical items detected.\n");
						g_key_file_set_string(datastore->index, "last_index", safeUID, file_digest);
					} else if (strcmp(index_digest, item_digest) == 0)
					{
						/* nothing to do - the change is already known */
						printf("\tOld change detected.\n");
					} else {
						/* The items do not match and the change is unknown.
						 * This is a classical conflict.
						 */
						if (localWinsConflicts)
						{
							printf("\tConflict: Remote replace command is ignored.\n");
							printf("\tConflict: Local changed item wins conflict.\n");
						} else {
							printf("\tConflict: Local changed item is ignored.\n");
							printf("\tConflict: Remote replace command wins conflict.\n");
							doWrite = TRUE;
						}
					}
				} else {
					/* The item was locally deleted.
					 * If you need to restore it
					 * then you must add it again.
					 * A normal synchronization is no backup.
					 * You can only restore it if you do a slow sync.
					 * So the change will be committed but not executed.
					 */
					printf("\tThe item was already locally deleted.\n");
				}
				break;
			case SML_CHANGE_DELETE:
				if (g_file_test(absolute_uid, G_FILE_TEST_EXISTS))
				{
					/* The item was deleted on the remote device.
					 * If you need to restore it
					 * then you must add it again.
					 * A normal synchronization is no backup.
					 * You can only restore it if you do a slow sync.
					 * So the change will be executed.
					 */
					printf("\tThe item will be deleted.\n");
					doWrite = TRUE;
				} else {
					/* The item was already locally deleted. */
					printf("\tThe item was already locally deleted.\n");
					g_key_file_remove_key(datastore->index, "last_index", safeUID, NULL);
				}
				break;
			default:
				g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC,
					"The change type of %s is not supported.", safeUID);
				printf("\t%s\n", (*error)->message);
				goto error;
		}

		/* write the change */

		if (doWrite) {
			switch(action)
			{
				case SML_CHANGE_ADD:
				case SML_CHANGE_REPLACE:
					data = sml_data_sync_change_item_get_data(item);
					if (!g_file_set_contents(absolute_uid, data, strlen(data), error))
						goto error;
					g_key_file_set_string(datastore->index, "last_index", safeUID, item_digest);
					/* printf("\tDigest: %s\n", item_digest); */
					printf("\tThe item was successfully written.\n");
					break;
				case SML_CHANGE_DELETE:
					if (g_unlink(absolute_uid) != 0)
					{
						g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC,
						            "The file %s cannot be removed.",
						            absolute_uid);
						goto error;
					}
					/* NOTICE: glib changed the return type of g_key_file_remove_key */
					g_key_file_remove_key(datastore->index, "last_index", safeUID, error);
					if (*error)
						goto error;
					printf("\tThe item was successfully deleted.\n");
					break;
				default:
					g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC,
						"The change type of %s is not supported.", safeUID);
					printf("\t%s\n", (*error)->message);
					goto error;
			}
		} else {
			printf("\tThe change was ignored.\n");
		}

		/* cleanup UIDs */
		smlSafeCFree(&safeUID);
		smlSafeCFree(&absolute_uid);
	} else {
		/* print received change */
		printf("-----BEGIN CHANGE-----\n");
		if (sml_data_sync_change_item_get_action(item) == SML_CHANGE_DELETE)
			printf("DELETE %s\n", sml_location_get_uri(sml_data_sync_change_item_get_location(item)));
		else
			printf("%s", sml_data_sync_change_item_get_data(item));
		printf("-----END CHANGE-----\n");
	}

	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	if (absolute_uid)
		smlSafeCFree(&absolute_uid);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

static gboolean
smlDsToolRecvMappingCallback (SmlDataSyncDataStoreSession *session,
                              SmlMapItem *item,
                              void *userdata,
                              GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, session, item, userdata, error);

	/* find the appropriate datasoure */
	SmlDsToolLocationType *datastore = userdata;
	if (!datastore)
	{
		g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC,
		            "The userdata of the callback %s is missing.", __func__);
		goto error;
	}

	/* handle the item */
	if (datastore->index) {
		const gchar *local = sml_map_item_get_local_uri(item);
		const gchar *remote = sml_map_item_get_remote_uri(item);
		g_key_file_set_string(datastore->index, "map", local, remote);
		g_key_file_set_string(datastore->index, "reverse_map", remote, local);
	}

	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

static gboolean
smlDsToolRecvChangeStatusCallback (SmlDataSyncDataStoreSession *session,
                                   SmlDataSyncChangeItem *item,
                                   SmlErrorType code,
                                   void *userdata,
                                   GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %d, %p, %p)", __func__, session, item, code, userdata, error);

	/* If there is an error then the whole operation must be aborted.
	 *   418 => The item is already present. So no problem.
	 *   419 => There was a conflict but the server data wins.
	 *          Usually this means that the item should be removed.
	 */
	if (sml_error_get_class(code) != SML_ERRORCLASS_SUCCESS &&
	    code != SML_ERROR_ALREADY_EXISTS &&
	    code != SML_ERROR_CONFLICT_SERVER_WINS)
	{
		g_set_error(error, TOOL_ERROR, code, smlErrorTypeGetMessage(code));
		goto error;
	}

	/* find the appropriate datasoure */
	SmlDsToolLocationType *datastore = userdata;
	if (!datastore)
	{
		g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC,
		            "The userdata of the callback %s is missing.", __func__);
		goto error;
	}

	SmlLocation *loc = sml_data_sync_change_item_get_location(item);
	const char *key = sml_location_get_uri(loc);
	if (sml_data_sync_change_item_get_action(item) != SML_CHANGE_ADD)
		key = g_key_file_get_string(datastore->index, "reverse_map", key, error);
	const char *digest = g_key_file_get_string(datastore->index, "change_index", key, error);
	if (!digest && *error)
		goto error;

	if (sml_data_sync_change_item_get_action(item) != SML_CHANGE_DELETE &&
	    ! (code == SML_ERROR_CONFLICT_SERVER_WINS &&
	       sml_data_sync_change_item_get_action(item) == SML_CHANGE_ADD
	      )
	   )
	{
		/* No delete and no 419 of an add. */
		g_key_file_set_string(datastore->index, "committed_index", key, digest);
	}

	/* NOTICE: glib changed the return type of g_key_file_remove_key */
	g_key_file_remove_key(datastore->index, "change_index", key, error);
	if (*error)
		goto error;

//	/* 419 => conflict and server wins
//	 *     => item must be locally removed.
//	 */
//	if (code == SML_ERROR_CONFLICT_SERVER_WINS &&
//	    sml_data_sync_change_item_get_action(item) == SML_CHANGE_ADD)
//	{
//		char *safeUID = getSafeFilename(key);
//		char *absoluteUID = g_strdup_printf("%s/%s", datastore->directory, safeUID);
//		g_unlink(absoluteUID);
//		smlSafeCFree(&absoluteUID);
//		smlSafeCFree(&safeUID);
//	}

	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %d => %s", __func__, (*error)->code, (*error)->message);
	return FALSE;
}

static gboolean
smlDsToolRecvDevInfCallback (SmlDataSyncSession *session,
                             SmlDevInf *devinf,
                             void *userdata,
                             GError **error)
{
	smlTrace(TRACE_INTERNAL, "%s(%p, %p, %p, %p)", session, devinf, userdata, error);
	printf("Received device information.\n");
	remoteDevinf = devinf;
	g_object_ref(remoteDevinf);
	return TRUE;
}

static SmlAlertType
smlDsToolRecvAlertTypeCallback (SmlDataSyncDataStoreSession *session,
                                SmlAlertType type,
                                void *userdata,
                                GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %d, %p, %p)", __func__, session, type, userdata, error);
	/* find the appropriate datasoure */
	SmlDsToolLocationType *datastore = userdata;
	if (!datastore) {
		SmlDataSyncDataStore *ds = sml_data_sync_data_store_session_get_data_store(session);
		g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC,
			"Cannot find datastore %s.",
			sml_data_sync_data_store_get_local_uri(ds));
		goto error;
	}
	smlTrace(TRACE_INTERNAL, "%s: datastores scanned", __func__);

	/* synchronize the alert type */
	if (type == SML_ALERT_SLOW_SYNC)
		datastore->slow = TRUE;
	if (datastore->slow)
		type = SML_ALERT_SLOW_SYNC;
	char *status = g_strdup_printf("%s/%s", datastore->directory, STATUS_FILENAME);
	if (!g_file_test(status, G_FILE_TEST_EXISTS))
	{
		/* directory is out-of-sync => enforce slow sync */
		datastore->slow = TRUE;
		type = SML_ALERT_SLOW_SYNC;
	}
	smlSafeCFree(&status);

	if (type == SML_ALERT_SLOW_SYNC)
	{
		g_message("\t%s => SLOW SYNC.", sml_data_sync_data_store_get_local_uri(datastore->object));
	} else {
		g_message("\t%s => NORMAL SYNC.", sml_data_sync_data_store_get_local_uri(datastore->object));
	}

	smlTrace(TRACE_EXIT, "%s - slow == %d", __func__, datastore->slow);
	return type;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return SML_ALERT_UNKNOWN;
}

/* ***** sync anchor management ***** */

static gchar*
smlDsToolGetAnchorCallback (SmlDataSyncDataStoreSession *self,
                            gboolean remote,
                            void *userdata,
                            GError **error)
{
	smlTrace(TRACE_ENTRY, "%s (%p, %d, %p, %p)", __func__, self, remote, userdata, error);
	SmlDsToolLocationType *datastore = userdata;

	gchar *filename = g_strdup_printf("%s/%s.%s", datastore->directory, STATUS_FILENAME, remote ? "REMOTE" : "LOCAL");

	/* does the file exist */
	struct stat fdata;
	if (g_stat(filename, &fdata) != 0) {
		g_free(filename);
		smlTrace(TRACE_EXIT, "%s - There is no such sync anchor.", __func__);
		return NULL;
	}

	gsize length = 0;
	gchar *data = NULL;
	if (!g_file_get_contents(filename, &data, &length, error))
	{
		g_free(filename);
		smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
		return NULL;
	}
	g_free(filename);

	gchar *result = smlTryMalloc0(length+1, error);
	if (!result) {
		g_free(data);
		smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
		return NULL;
	}
	memcpy(result, data, length);
	g_free(data);

	smlTrace(TRACE_EXIT, "%s - %s", __func__, result);
	return result;
}

static gboolean
smlDsToolSetAnchorCallback (SmlDataSyncDataStoreSession *self,
                            gboolean remote,
                            const gchar* anchor,
                            void *userdata,
                            GError **error)
{
	smlTrace(TRACE_ENTRY, "%s (%p, %d, %p, %p)", __func__, self, remote, userdata, error);
	SmlDsToolLocationType *datastore = userdata;

	gchar *filename = g_strdup_printf("%s/%s.%s", datastore->directory, STATUS_FILENAME, remote ? "REMOTE" : "LOCAL");
	gboolean result = g_file_set_contents(filename, anchor, strlen(anchor), error);
	g_free(filename);

	smlTrace(TRACE_EXIT, "%s - %d", __func__, result);
	return result;
}

/* ************************************ */
/* ************** TOOL **************** */
/* ************************************ */

int main(int argc, char *argv[])
{
	GError *error = NULL;
	if (!g_thread_supported ()) g_thread_init (NULL);
	g_type_init();

	/* init DS object */
	
	SmlTransportType tspType = getTransportType(argc, argv);
	sessionType = getSessionType(argc, argv);
	SmlDataSync *dsObject = sml_data_sync_new();
	if (!sml_data_sync_set_transport_type(dsObject, tspType, &error))
		goto error;
	if (!sml_data_sync_set_session_type(dsObject, sessionType, &error))
		goto error;
	if (!scanArguments(dsObject, argc, argv, &error))
		goto error;

	sml_data_sync_register_event_callback(dsObject, smlDsToolRecvEventCallback, NULL);

	if (!sml_data_sync_initialize(dsObject, &error))
		goto error;
	g_message("Starting data sync ...");

	/* run the sync */
	checkpoint = time(NULL);
	if (!sml_data_sync_run(dsObject, &error))
		goto error;

	runMutex = g_mutex_new();
	g_mutex_lock(runMutex);
	g_mutex_lock(runMutex);
	g_mutex_unlock(runMutex);
	g_mutex_free(runMutex);
	runMutex = NULL;

	/* dump some information */
	if (dumpinfo) {
		if (!remoteDevinf) {
			printf("Didn't receive the device information though it was requested.\n");
		} else {
			printf("Send the output below to the libsyncml developers\n");
			printf("\n========================================\n");
			printf("Man: %s\n", sml_dev_inf_get_man(remoteDevinf));
			printf("Mod: %s\n", sml_dev_inf_get_mod(remoteDevinf));
			printf("FirmwareVersion: %s\n", sml_dev_inf_get_fwv(remoteDevinf));
			printf("SoftwareVersion: %s\n", sml_dev_inf_get_swv(remoteDevinf));
			printf("HardwareVersion: %s\n", sml_dev_inf_get_hwv(remoteDevinf));
			printf("\n");
			printf("MaxMsgSize: %s\n", maxMsgSize);
			printf("MaxObjSize: %s\n", maxObjSize);
			printf("Transport used: ");
			switch(tspType) {
				case SML_TRANSPORT_HTTP_CLIENT:
					printf("HTTP client\n");
					break;
				case SML_TRANSPORT_HTTP_SERVER:
					printf("HTTP server\n");
					break;
				case SML_TRANSPORT_OBEX_CLIENT:
					printf("OBEX client\n");
					break;
				default:
					printf("unknown\n");
					break;
			}
			printf("OMA DS mode used: ");
			switch(sessionType) {
				case SML_SESSION_TYPE_CLIENT:
					printf("client\n");
					break;
				case SML_SESSION_TYPE_SERVER:
					printf("server\n");
					break;
				default:
					printf("unknown\n");
					break;
			}
			printf("Identifier (Source): %s \n", identifier);
			printf("Target: %s\n", target);
			printf("\nDatastores:\n");
			unsigned int i = 0;
			for (i = 0; i < sml_dev_inf_num_data_stores(remoteDevinf); i++) {
				SmlDevInfDataStore *datastore = sml_dev_inf_get_nth_data_store(remoteDevinf, i);
				printf("\tLocations: %s\n", sml_dev_inf_data_store_get_source_ref(datastore));
			}
			printf("Wbxml: %s\n", useWbxml ? "Yes" : "No");
			printf("SyncML Version: %s\n", syncmlVersion);
			printf("SupportsUTC: %s\n", sml_dev_inf_get_support_utc(remoteDevinf) ? "Yes" : "No");
			printf("SupportsNumberOfChanges: %s\n", sml_dev_inf_get_support_number_of_changes(remoteDevinf) ? "Yes" : "No");
			printf("SupportsLargeObjects: %s\n", sml_dev_inf_get_support_large_objs(remoteDevinf) ? "Yes" : "No");
			g_object_unref(remoteDevinf);
			printf("\nlibsyncml Version: %s ($Revision: 1360 $)\n", VERSION);
		}
	}
	/* close the object */
	g_object_unref(dsObject);
        g_message("syncml-ds-tool succeeded.");

	return 0;
error:
	if (error != NULL) {
		fprintf(stderr, "ERROR: %s\n", error->message);
		g_error_free(error);
	}
        g_message("syncml-ds-tool failed.");
	return 1;
}
