/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2007-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
 *
 */

#include <libsyncml/syncml.h>
#include "sml_ds_server.h"

#include <libsyncml/syncml_internals.h>
#include "sml_ds_server_internals.h"
#include <libsyncml/sml_error_internals.h>
#include <libsyncml/sml_manager_internals.h>
#include <libsyncml/sml_session_internals.h>
#include <libsyncml/sml_command_internals.h>
#include <libsyncml/sml_elements_internals.h>

static SmlWriteContext *_write_context_find(SmlDsSession *dsession, const char *uid, SmlChangeType type)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %i)", __func__, dsession, VA_STRING(uid), type);
	
	g_mutex_lock(dsession->pendingMapsLock);
	GList *c = NULL;
	for (c = dsession->pendingMaps; c; c = c->next) {
		SmlWriteContext *ctx = c->data;
		if (!strcmp(uid, ctx->uid) && ctx->type == type) {
			g_mutex_unlock(dsession->pendingMapsLock);
			smlTrace(TRACE_EXIT, "%s: %p", __func__, ctx);
			return ctx;
		}
	}
	g_mutex_unlock(dsession->pendingMapsLock);
	
	smlTrace(TRACE_EXIT_ERROR, "%s: Not found", __func__);
	return NULL;
}

static void _write_context_free(SmlWriteContext *ctx)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, ctx);

	if (ctx->status)
		smlStatusUnref(ctx->status);
	
	if (ctx->uid)
		smlSafeCFree(&(ctx->uid));
		
	if (ctx->newuid)
		smlSafeCFree(&(ctx->newuid));
	
	smlSafeFree((gpointer *)&ctx);

	smlTrace(TRACE_EXIT, "%s", __func__);
}

static void _write_context_dispatch(SmlDsSession *dsession, SmlWriteContext *ctx)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, dsession, ctx);

	if (!ctx->status) {
		smlTrace(TRACE_EXIT, "%s: No status yet", __func__);
		return;
	}
	
	smlTrace(TRACE_INTERNAL, "%s: Dispatching: uid %s, Type %i, newuid %s, result %i", __func__, VA_STRING(ctx->uid), ctx->type, VA_STRING(ctx->newuid), smlStatusGetCode(ctx->status));
	
	if (ctx->type == SML_CHANGE_ADD &&
	    smlStatusGetClass(ctx->status) == SML_ERRORCLASS_SUCCESS &&
	    !dsession->recvMappingCallback &&
	    !ctx->newuid &&
	    dsession->server->servertype == SML_DS_SERVER)
	{
		smlTrace(TRACE_EXIT, "%s: No mapitem yet", __func__);
		return;
	}

	ctx->callback(dsession, ctx->status, ctx->newuid, ctx->userdata);
	
	g_mutex_lock(dsession->pendingMapsLock);
	dsession->pendingMaps = g_list_remove(dsession->pendingMaps, ctx);
	g_mutex_unlock(dsession->pendingMapsLock);

	if (ctx->type == SML_CHANGE_ADD &&
	    smlStatusGetClass(ctx->status) == SML_ERRORCLASS_SUCCESS &&
	    dsession->recvEventCallback &&
	    !dsession->pendingMaps)
	{
		/* FIXME: I'm sure that this event is buggy.
		 * FIXME: What is the semantic of this event?
		 * Now there are no more MapItems ...
		 * do some fancy callback. Some applications will love it ;)
		 */
		dsession->recvEventCallback(dsession, SML_DS_EVENT_COMMITEDCHANGES, dsession->recvEventCallbackUserdata);
		smlTrace(TRACE_INTERNAL, "%s: recvEventCallback commited changes callback", __func__);
	}

	_write_context_free(ctx);
		
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static void _alert_reply(SmlSession *session, SmlStatus *status, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);
	SmlDsSession *dsession = userdata;
	
	if (dsession->sentAlertCallback)
		dsession->sentAlertCallback(session, status, dsession->sentAlertCallbackUserdata);
	
	dsession->sentAlertCallback = NULL;
	dsession->sentAlertCallbackUserdata = NULL;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static void _sync_reply(SmlSession *session, SmlStatus *status, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);
	SmlDsSession *dsession = userdata;
	
	if (dsession->sentSyncCallback)
		dsession->sentSyncCallback(session, status, dsession->sentSyncCallbackUserdata);
	
	dsession->sentSyncCallback = NULL;
	dsession->sentSyncCallbackUserdata = NULL;

	// Now there are no more MapItems ... do some fancy callback. Some applications will love it ;)
	if (dsession->recvEventCallback && !dsession->pendingMaps) {
		dsession->recvEventCallback(dsession, SML_DS_EVENT_COMMITEDCHANGES, dsession->recvEventCallbackUserdata);
		smlTrace(TRACE_INTERNAL, "%s: recvEventCallback commited changes callback", __func__);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

static void _change_reply(SmlSession *session, SmlStatus *status, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, status, userdata);
	smlAssert(session);
	smlAssert(status);
	SmlWriteContext *ctx = userdata;
	SmlDsSession *dsession = ctx->session;
	
	/* Please see smlChangeAssemble in sml_xml_parser.c for
	 * a more detailed description on the checked behaviour.
	 * If the received command is an Add or the remote peer
	 * is an OMA DS client then the remote peer must send a
	 * Source. Otherwise a Target must be sent by the remote
	 * peer. We only check the required behaviour and not
	 * the optional one.
	 */
	if (status->type == SML_COMMAND_TYPE_ADD ||
	    session->sessionType == SML_SESSION_TYPE_CLIENT) {
		/* FIXME: How do we handle remote peers which are clients
		 * FIXME: and only support replace? The assembler will
		 * FIXME: send a replace command with a target instead of
		 * FIXME: an add command with a source.
		 */
		if (!status->sourceRef) {
			smlTrace(TRACE_EXIT_ERROR, "%s: Received add status or change reply from an OMA DS server without sourceRef", __func__);
			return;
		}
	} else {
		if (!status->targetRef) {
			smlTrace(TRACE_EXIT_ERROR, "%s: Received delete or modify status or change reply from an OMA DS client without targetRef", __func__);
			return;
		}
	}
	
	ctx->status = status;
	smlStatusRef(status);
	_write_context_dispatch(dsession, ctx);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlDsServer *smlDsServerNew(const char *type, SmlLocation *location, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%s, %p, %p)", __func__, VA_STRING(type), location, error);
	smlAssert(location);
	CHECK_ERROR_REF
	
	SmlDsServer *server = smlTryMalloc0(sizeof(SmlDsServer), error);
	if (!server)
		goto error;

	server->location = location;
	smlLocationRef(location);

	server->contenttype = g_strdup(type);
	server->servertype = SML_DS_SERVER;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, server);
	return server;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

SmlDsServer *smlDsClientNew(const char *type, SmlLocation *location, SmlLocation *target, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%s, %p, %p, %p)", __func__, VA_STRING(type), location, target, error);
	smlAssert(location);
	smlAssert(target);
	CHECK_ERROR_REF
	
	SmlDsServer *server = smlTryMalloc0(sizeof(SmlDsServer), error);
	if (!server)
		goto error;

	server->location = location;
	smlLocationRef(location);
	
	server->target = target;
	smlLocationRef(target);

	server->contenttype = g_strdup(type);
	server->servertype = SML_DS_CLIENT;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, server);
	return server;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

SmlDsServerType smlDsServerGetServerType(SmlDsServer *server)
{
	smlAssert(server);
	return server->servertype;
}

void smlDsServerFree(SmlDsServer *server)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, server);
	smlAssert(server);
	
	if (server->location)
		smlLocationUnref(server->location);
		
	if (server->target)
		smlLocationUnref(server->target);
	
	if (server->contenttype)
		smlSafeCFree(&(server->contenttype));
	
	smlSafeFree((gpointer *)&server);
	
	smlTrace(TRACE_EXIT, "%s", __func__);	
}

/** @brief Registers a callback that will get called once a client connects
 * 
 * This function will get called once a client connects to our ds server (which means
 * that it sent a alert to our server). You can then use the smlDsServerRequestAlert() function
 * to get the alert
 * 
 * @param server The DS server
 * @param callback The callback that will receive the alert
 * @param userdata The userdata that will be passed to the alert
 */
void smlDsServerSetConnectCallback(SmlDsServer *server, SmlDsSessionConnectCb callback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, server, callback, userdata);
	smlAssert(server);
	
	server->connectCallback = callback;
	server->connectCallbackUserdata = userdata;
	
	smlTrace(TRACE_EXIT, "%s", __func__);	
}

void smlDsServerSetSanCallback(SmlDsServer *server, SmlDsServerSanCb callback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, server, callback, userdata);
	smlAssert(server);
	smlAssert(server->servertype == SML_DS_CLIENT);
	
	server->sanCallback = callback;
	server->sanCallbackUserdata = userdata;
	
	smlTrace(TRACE_EXIT, "%s", __func__);	
}

void smlDsServerSetSanSessionCallback(SmlDsServer *server, SmlDsServerSanSessionCb callback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, server, callback, userdata);
	smlAssert(server);
	smlAssert(server->servertype == SML_DS_CLIENT);
	
	server->sanSessionCallback = callback;
	server->sanSessionCallbackUserdata = userdata;
	
	smlTrace(TRACE_EXIT, "%s", __func__);	
}

const char *smlDsServerGetLocation(SmlDsServer *server)
{
	smlAssert(server);
	if (server->location)
		return server->location->locURI;
	return NULL;
}

const char *smlDsServerGetContentType(SmlDsServer *server)
{
	smlAssert(server);
	return server->contenttype;
}

SmlBool smlDsServerAddSan(SmlDsServer *server, SmlNotification *san, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, server, san, error);
	smlAssert(server);
	smlAssert(san);
	CHECK_ERROR_REF
	
	if (!smlNotificationNewAlert(san, SML_ALERT_TWO_WAY_BY_SERVER, server->contenttype, smlLocationGetURI(server->location), error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

SmlDsSession *smlDsServerRecvAlert(SmlDsServer *server, SmlSession *session, SmlCommand *cmd)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, server, session, cmd);
	SmlError *error = NULL;
	
	SmlDsSession *dsession = smlDsSessionNew(server, session, &error);
	if (!dsession)
		goto error;
	
	smlDsSessionRecvAlert(session, cmd, dsession);
	
	if (server->connectCallback)
		server->connectCallback(dsession, server->connectCallbackUserdata);
	
	smlDsSessionUnref(dsession);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return dsession;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
	smlErrorDeref(&error);
	return NULL;
}

SmlDsSession *smlDsServerSendAlert(SmlDsServer *server, SmlSession *session, SmlAlertType type, const char *last, const char *next, SmlStatusReplyCb callback, void *userdata, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %s, %s, %p)", __func__, server, session, type, VA_STRING(last), VA_STRING(next), error);
	smlAssert(server);
	smlAssert(session);
	CHECK_ERROR_REF
	
	SmlDsSession *dsession = smlDsSessionNew(server, session, error);
	if (!dsession)
		goto error;
	
	if (server->manager) {
		if (!smlManagerObjectRegister(server->manager, SML_COMMAND_TYPE_SYNC, session, server->location, NULL, NULL, smlDsSessionRecvSync, smlDsSessionRecvChange, dsession, error))
			goto error_free_dsession;
		
		if (!smlManagerObjectRegister(server->manager, SML_COMMAND_TYPE_MAP, session, server->location, NULL, NULL, smlDsSessionRecvMap, NULL, dsession, error))
			goto error_free_dsession;
		
		if (!smlManagerObjectRegister(server->manager, SML_COMMAND_TYPE_ALERT, session, server->location, NULL, NULL, smlDsSessionRecvAlert, NULL, dsession, error))
			goto error_free_dsession;
	}
	
	if (!smlDsSessionSendAlert(dsession, type, last, next, callback, userdata, error))
		goto error_free_dsession;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return dsession;

error_free_dsession:
	smlSafeFree((gpointer *)&dsession);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

SmlDsSession *smlDsSessionNew(SmlDsServer *server, SmlSession *session, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, server, session, error);
	smlAssert(server);
	CHECK_ERROR_REF
	
	SmlDsSession *dsession = smlTryMalloc0(sizeof(SmlDsSession), error);
	if (!dsession)
		goto error;

	dsession->server = server;
	dsession->session = session;
	dsession->write_lock = g_mutex_new();
	dsession->lock = g_mutex_new();
	dsession->syncReply = SML_ERROR_UNKNOWN;
	dsession->refCount = 1;
	
	if (server->servertype == SML_DS_CLIENT) {
		dsession->target = server->target;
		smlLocationRef(dsession->target);
	}
	
	dsession->location = server->location;
	smlLocationRef(dsession->location);

	dsession->pendingMapsLock = g_mutex_new();
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, dsession);
	return dsession;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

SmlDsSession *smlDsSessionRef(SmlDsSession *dsession)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, dsession);
	smlAssert(dsession);
	
	g_atomic_int_inc(&(dsession->refCount));
	
	smlTrace(TRACE_EXIT, "%s: New refcount: %i", __func__, dsession->refCount);
	return dsession;
}

void smlDsSessionUnref(SmlDsSession *dsession)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, dsession);
	smlAssert(dsession);
	
	if (g_atomic_int_dec_and_test(&(dsession->refCount))) {
		smlTrace(TRACE_INTERNAL, "%s: Refcount == 0!", __func__);
		
		if (dsession->target)
			smlLocationUnref(dsession->target);
			
		if (dsession->location)
			smlLocationUnref(dsession->location);
		
		if (dsession->alertCommand)
			smlCommandUnref(dsession->alertCommand);
		
		while (dsession->recvSync) {
			SmlCommand *cmd = dsession->recvSync->data;
			smlCommandUnref(cmd);
			dsession->recvSync = g_list_delete_link(dsession->recvSync, dsession->recvSync);
		}
		
		while (dsession->recvChanges) {
			SmlCommand *cmd = dsession->recvChanges->data;
			smlCommandUnref(cmd);
			dsession->recvChanges = g_list_delete_link(dsession->recvChanges, dsession->recvChanges);
		}
		
		if (dsession->syncCommand)
			smlCommandUnref(dsession->syncCommand);
		
		if (!g_mutex_trylock(dsession->pendingMapsLock)) {
			smlTrace(TRACE_ERROR,
				"%s: somebody still uses this object", __func__);
			g_mutex_lock(dsession->pendingMapsLock);
		}
		while (dsession->pendingMaps) {
			SmlWriteContext *ctx = dsession->pendingMaps->data;
			_write_context_free(ctx);
			dsession->pendingMaps = g_list_delete_link(dsession->pendingMaps, dsession->pendingMaps);
		}
		g_mutex_unlock(dsession->pendingMapsLock);
		g_mutex_free(dsession->pendingMapsLock);
		
		while (dsession->mapItems) {
			SmlMapItem *item = dsession->mapItems->data;
			smlMapItemUnref(item);
			dsession->mapItems = g_list_delete_link(dsession->mapItems, dsession->mapItems);
		}
		
		if (dsession->lock)
			g_mutex_free(dsession->lock);
		if (dsession->write_lock)
			g_mutex_free(dsession->write_lock);
		
		smlSafeFree((gpointer *)&dsession);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlDsSessionDispatch(SmlDsSession *dsession)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, dsession);
	SmlError *error = NULL;
	SmlStatus *reply = NULL;

	/* A dispatch function should never block on a
	 * locked resource. This is like a busy wait
	 * because the thread is not available for this
	 * time which can block the whole system if two
	 * dispatchers use the same thread.
	 */
	if (!g_mutex_trylock(dsession->lock))
	{
		smlTrace(TRACE_EXIT, "%s - DsSession is already locked", __func__);
		return;
	}
	smlTrace(TRACE_INTERNAL, "%s - locked DsSession successfully", __func__);

	if (dsession->alertCommand && dsession->recvAlertCallback) {
		smlTrace(TRACE_INTERNAL, "%s: Dispatching alert", __func__);
		
		SmlErrorType type = SML_NO_ERROR;
		if (!dsession->recvAlertCallback(dsession, dsession->alertCommand->private.alert.type, dsession->alertCommand->private.alert.anchor->last, dsession->alertCommand->private.alert.anchor->next, dsession->recvAlertCallbackUserdata))
			type = SML_ERROR_REQUIRE_REFRESH;
		dsession->recvAlertCallback = NULL;

		reply = smlCommandNewReply(dsession->alertCommand, type, &error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(dsession->session, reply, &error))
			goto error;
		
		
		smlStatusUnref(reply);
		
		smlCommandUnref(dsession->alertCommand);
		dsession->alertCommand = NULL;

		/* unlock the final event at the manager
		 * 
		 * If this is called from a test case then the datastore
		 * is sometimes not registered at a SmlManager.
		 */
		if (dsession->server->manager && dsession->finalLock)
		{
			dsession->finalLock = FALSE;
			smlManagerSessionFinalLockUnref(dsession->server->manager, dsession->session);
		}

	} else if (dsession->recvSync && dsession->recvSyncCallback) {
		smlTrace(TRACE_INTERNAL, "%s: Dispatching sync", __func__);
		
		dsession->recvSyncCallback(dsession, ((SmlCommand *)(dsession->recvSync->data))->private.sync.numChanged, dsession->recvSyncCallbackUserdata);
		dsession->recvSyncCallback = NULL;
		
		while (dsession->recvSync) {
			SmlCommand *cmd = dsession->recvSync->data;
			
			smlTrace(TRACE_INTERNAL, "%s: answering sync command with cmdRef %i and msgRef %i", __func__, cmd->cmdID, cmd->msgID);
			reply = smlCommandNewReply(cmd, SML_NO_ERROR, &error);
			if (!reply)
				goto error;
			
			if (!smlSessionSendReply(dsession->session, reply, &error))
				goto error;
				
			smlStatusUnref(reply);
			
			smlCommandUnref(cmd);
			dsession->recvSync = g_list_delete_link(dsession->recvSync, dsession->recvSync);
		}
		
		dsession->syncReply = SML_NO_ERROR;
		
		if (!dsession->recvChanges) {
			/* There are no changes. */

			/* ... do some fancy callback. Some applications will love it ;) */
			if (dsession->recvEventCallback) {
				dsession->recvEventCallback(dsession, SML_DS_EVENT_GOTCHANGES, dsession->recvEventCallbackUserdata);
				smlTrace(TRACE_INTERNAL, "%s: recvEventCallback no changes in recvSync callback", __func__);
			}
			/* unlock the final event at the manager
			 * 
			 * If this is called from a test case then the datastore
			 * is sometimes not registered at a SmlManager.
			 */
			if (dsession->server->manager && dsession->finalLock)
			{
				dsession->finalLock = FALSE;
				smlManagerSessionFinalLockUnref(dsession->server->manager, dsession->session);
			}
			smlTrace(TRACE_INTERNAL, "%s: final handling done", __func__);
		}
	} else if (dsession->recvChanges && dsession->changesCallback) {
		smlTrace(TRACE_INTERNAL, "%s: Dispatching changes", __func__);
		
		while (dsession->recvChanges) {

			SmlCommand *cmd = dsession->recvChanges->data;
			
			if (cmd) {
				if (!cmd->private.change.items ||
				    !g_list_length(cmd->private.change.items))
				{
					smlErrorSet(&error, SML_ERROR_GENERIC, "No items found in command.");
					goto error;
				}

				/* All items handled at once because libsyncml
				 * only supports one status code for all items.
				 * So one failed item means that the complete
				 * command is aborted.
				 */

				guint i;
				for (i=0; i < g_list_length(cmd->private.change.items); i++)
				{
					SmlItem *item = g_list_nth_data(cmd->private.change.items, i);
				        if (!item)
					{
						smlErrorSet(&error, SML_ERROR_GENERIC,
							"Item %i of the command's item list is NULL.", i);
						goto error;
					}
					/* We'd rather use the target of the change since it already the mapped
					 * uid (if we are a client for example). If it is not given we use the
					 * source uri. This has then to be translated by the sync engine of course */
					if ((!item->source && !item->target)) {
						smlErrorSet(&error, SML_ERROR_GENERIC,
							"Cannot determine UID because source an target are missing on item %i.", i);
						goto error;
					}

					char *data = NULL;
					unsigned int size = 0;
					if (!smlItemStealData(item, &data, &size, &error))
						goto error;

					if (!dsession->changesCallback(dsession, cmd->private.change.type, item->target ? item->target->locURI : item->source->locURI, data, size, item->contenttype, dsession->changesCallbackUserdata, &error))
						goto error;
				}

				if (cmd->private.change.type == SML_CHANGE_ADD)
					reply = smlCommandNewReply(cmd, SML_ITEM_ADDED, &error);
				else
					reply = smlCommandNewReply(cmd, SML_NO_ERROR, &error);
				
				if (!reply)
					goto error;
				
				if (!smlSessionSendReply(dsession->session, reply, &error))
					goto error;
					
				smlStatusUnref(reply);
			
				smlCommandUnref(cmd);
			}

			dsession->recvChanges = g_list_delete_link(dsession->recvChanges, dsession->recvChanges);
		}

		// Now there are no more changes... do some fancy callback. Some applications will love it ;)
		if (dsession->recvEventCallback) {
			dsession->recvEventCallback(dsession, SML_DS_EVENT_GOTCHANGES, dsession->recvEventCallbackUserdata);
			smlTrace(TRACE_INTERNAL, "%s: recvEventCallback all changes sent in recvChanges callback", __func__);
		}
		/* unlock the final event at the manager
		 * 
		 * If this is called from a test case then the datastore
		 * is sometimes not registered at a SmlManager.
		 */
		if (dsession->server->manager)
		{
			dsession->finalLock = FALSE;
			smlManagerSessionFinalLockUnref(dsession->server->manager, dsession->session);
		}
		else
			smlTrace(TRACE_INTERNAL, "%s - no manager so should be a test", __func__);
	} else {
		
		smlTrace(TRACE_INTERNAL, "%s: recvChanges: %p changesCallback: %p", __func__, dsession->recvChanges, dsession->changesCallback); 
	}

	smlTrace(TRACE_EXIT, "%s()", __func__);
	g_mutex_unlock(dsession->lock);
	
	return;

error:
	if (reply)
		smlStatusUnref(reply);
	g_mutex_unlock(dsession->lock);
	smlTrace(TRACE_EXIT_ERROR, "%s: Unable to dispatch: %s", __func__, smlErrorPrint(&error));
	smlErrorDeref(&error);
}

SmlBool smlDsSessionCheck(SmlDsSession *dsession)
{
	if ((dsession->alertCommand && dsession->recvAlertCallback) || \
		(dsession->recvSync && dsession->recvSyncCallback) || \
		(dsession->recvChanges && dsession->changesCallback))
		return TRUE;
	return FALSE;
}

void smlDsSessionRecvAlert(SmlSession *session, SmlCommand *cmd, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, cmd, userdata);
	SmlDsSession *dsession = userdata;
	SmlError *error = NULL;
	smlAssert(dsession->location);
	
	g_mutex_lock(dsession->lock);
	
	if (!cmd->target || !cmd->source) {
		SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_BAD_REQUEST, &error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(session, reply, &error)) {
			smlStatusUnref(reply);
			goto error;
		}
		
		smlStatusUnref(reply);
			
		smlTrace(TRACE_EXIT, "%s: Alert had no target or source", __func__);
		return;
	}
	
	if (!smlLocationCompare(NULL, dsession->location, NULL, cmd->target)) {
		SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_NOT_FOUND, &error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(session, reply, &error)) {
			smlStatusUnref(reply);
			goto error;
		}
		
		smlStatusUnref(reply);
		
		smlTrace(TRACE_EXIT, "%s: Alert does not match our location", __func__);
		return;
	}
	
	smlCommandRef(cmd);
	
	if (!dsession->target) {
		dsession->target = cmd->source;
		smlLocationRef(cmd->source);
	}
	
	dsession->alertCommand = cmd;

	/* Lock the final event at the manager.
	 * 
	 * If this is called from a test case then the datastore
	 * is sometimes not registered at a SmlManager.
	 */
	if (dsession->server->manager)
	{
		smlManagerSessionFinalLockRef(dsession->server->manager, session);
		dsession->finalLock = TRUE;
	}
	else
		smlTrace(TRACE_INTERNAL, "%s - no manager so should be a test", __func__);
	
	g_mutex_unlock(dsession->lock);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;

error:
	g_mutex_unlock(dsession->lock);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
	smlErrorDeref(&error);
	return;
}

void smlDsSessionRecvSync(SmlSession *session, SmlCommand *cmd, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, cmd, userdata);
	SmlDsSession *dsession = userdata;
	SmlError *error = NULL;

	/* Create a write lock on the complete DsSession object to avoid race
	 * conditions. If the write lock is already present then this is the
	 * end of the sync block and the write lock must only be removed.
	 */
	if (g_mutex_trylock(dsession->write_lock))
	{
		/* write lock succeeded so lock the complete DsSession */
		g_mutex_lock(dsession->lock);
		smlTrace(TRACE_INTERNAL, "%s - write lock enabled", __func__);
		/* Lock the final event at the manager.
		 * 
		 * If this is called from a test case then the datastore
		 * is sometimes not registered at a SmlManager.
		 */
		if (dsession->server->manager)
		{
			smlManagerSessionFinalLockRef(dsession->server->manager, session);
			dsession->finalLock = TRUE;
			dsession->emptySync = TRUE;
		}
		else
			smlTrace(TRACE_INTERNAL, "%s - no manager so should be a test", __func__);
	} else {
		/* detected an old write lock so this is the closing sync call */
		if (dsession->finalLock && dsession->emptySync)
		{
			smlTrace(TRACE_INTERNAL, "%s - empty sync of moreData ?", __func__);
			dsession->finalLock = FALSE;
			smlManagerSessionFinalLockUnref(dsession->server->manager, session);
		}
		g_mutex_unlock(dsession->lock);
		g_mutex_unlock(dsession->write_lock);
		smlTrace(TRACE_EXIT, "%s - removed write lock", __func__);
		return;
	}

	if (dsession->syncReply == SML_ERROR_UNKNOWN) {
		smlTrace(TRACE_INTERNAL, "%s: Storing sync command with cmdRef %i and msgRef %i", __func__, cmd->cmdID, cmd->msgID);
		smlCommandRef(cmd);
		dsession->recvSync = g_list_append(dsession->recvSync, cmd);
	} else {
		smlTrace(TRACE_INTERNAL, "%s: Using stored sync reply on cmd with cmdRef %i and msgRef %i", __func__, cmd->cmdID, cmd->msgID);
		SmlStatus *reply = smlCommandNewReply(cmd, dsession->syncReply, &error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(dsession->session, reply, &error))
			goto error;
		
		smlStatusUnref(reply);
	}

	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
	
error:
	g_mutex_unlock(dsession->lock);
	g_mutex_unlock(dsession->write_lock);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
	smlErrorDeref(&error);
}

void smlDsSessionRecvChange(SmlSession *session, SmlCommand *cmd, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, cmd, userdata);
	SmlDsSession *dsession = userdata;

	/* It is not necessary to lock the DsSesion object
	 * because RecvSync already enabled a global write_lock.
	 */
	dsession->recvChanges = g_list_append(dsession->recvChanges, cmd);
	smlCommandRef(cmd);

	/* This is important for fast and consistent final signalling. */
	dsession->emptySync = FALSE;

	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlDsSessionRecvMap(SmlSession *session, SmlCommand *cmd, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, cmd, userdata);
	SmlDsSession *dsession = userdata;
	SmlError *error = NULL;
	
	g_mutex_lock(dsession->lock);
	
	SmlStatus *reply = smlCommandNewReply(cmd, SML_NO_ERROR, &error);
	if (!reply)
		goto error;
	
	if (!smlSessionSendReply(session, reply, &error))
		goto error;
	
	smlStatusUnref(reply);
	
	GList *m = NULL;
	for (m = cmd->private.map.items; m; m = m->next) {
		SmlMapItem *item = m->data;

		if (dsession->recvMappingCallback) {
			dsession->recvMappingCallback(
					dsession,
					item->target,
					item->source,
					dsession->recvMappingCallbackUserdata);
		} else {
			SmlWriteContext *ctx = _write_context_find(dsession, item->target->locURI, SML_CHANGE_ADD);
			if (ctx) {
				ctx->newuid = g_strdup(item->source->locURI);
				_write_context_dispatch(dsession, ctx);
			} else {
				smlTrace(TRACE_ERROR, "%s: Unknown map ... %s => %s",
					__func__,
					VA_STRING(item->target->locURI),
					VA_STRING(item->source->locURI));
			}
		}
	}
	
	g_mutex_unlock(dsession->lock);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
	
error:
	g_mutex_unlock(dsession->lock);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
	smlErrorDeref(&error);
}

/** @brief Gets a already received alert
 * 
 * This function will get a already received alert or register a callback that will be
 * called once the alert is received. If the alert already was waiting the callback is 
 * called immediatly.
 * 
 * @param server The DS server
 * @param callback The callback that will receive the alert
 * @param userdata The userdata that will be passed to the alert
 * @param error A pointer to a error struct
 * @returns TRUE if the call was successful, FALSE otherwise
 */
void smlDsSessionGetAlert(SmlDsSession *dsession, SmlDsSessionAlertCb callback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, dsession, callback, userdata);
	smlAssert(dsession);
	smlAssert(callback);
	
	dsession->recvAlertCallback = callback;
	dsession->recvAlertCallbackUserdata = userdata;
	
	smlDsSessionDispatch(dsession);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/** @brief Sends the alert to the remote side
 * 
 * This function will get a already received alert or register a callback that will be
 * called once the alert is received. If the alert already was waiting the callback is 
 * called immediatly.
 * 
 * @param server The DS server
 * @param callback The callback that will receive the alert
 * @param userdata The userdata that will be passed to the alert
 * @param error A pointer to a error struct
 * @returns TRUE if the call was successful, FALSE otherwise
 */
SmlBool smlDsSessionSendAlert(SmlDsSession *dsession, SmlAlertType type, const char *last, const char *next, SmlStatusReplyCb callback, void *userdata, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %s, %s, %p)", __func__, dsession, type, VA_STRING(last), VA_STRING(next), error);
	smlAssert(dsession);
	CHECK_ERROR_REF
	
	SmlCommand *alert = smlCommandNewAlert(type, dsession->target, dsession->location, next, last, NULL, error);
	if (!alert)
		goto error;
	
	dsession->sentAlertCallback = callback;
	dsession->sentAlertCallbackUserdata = userdata;
	
	if (!smlSessionSendCommand(dsession->session, alert, NULL, _alert_reply, dsession, error))
		goto error;
	
	smlCommandUnref(alert);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

void smlDsSessionGetMapping(
		SmlDsSession *dsession,
		SmlDsSessionMapCb mapCallback,
		void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	dsession->recvMappingCallback = mapCallback;
	dsession->recvMappingCallbackUserdata = userdata;
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/** @brief Gets a already received sync command
 * 
 * This function will get a already received sync command or register a callback that will be
 * called once the sync is received. If the sync already was waiting the callback is 
 * called immediatly. The read callback is called with every subcommand of the sync.
 * 
 * @param server The DS server
 * @param chgCallback The callback that will receive all the changes (subcommands of sync)
 * @param syncCallback The callback that will receive the sync command
 * @param userdata The userdata that will be passed to the sync and change callbacks
 * @param error A pointer to a error struct
 * @returns TRUE if the call was successful, FALSE otherwise
 */
void smlDsSessionGetChanges(SmlDsSession *dsession, SmlDsSessionChangesCb chgCallback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, dsession, chgCallback, userdata);
	smlAssert(dsession);
	smlAssert(chgCallback);
	
	dsession->changesCallback = chgCallback;
	dsession->changesCallbackUserdata = userdata;
	
	smlDsSessionDispatch(dsession);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlDsSessionGetSync(SmlDsSession *dsession, SmlDsSessionSyncCb syncCallback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, dsession, syncCallback, userdata);
	smlAssert(dsession);
	smlAssert(syncCallback);
	
	dsession->recvSyncCallback = syncCallback;
	dsession->recvSyncCallbackUserdata = userdata;
	
	smlDsSessionDispatch(dsession);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlDsSessionGetEvent(SmlDsSession *dsession, SmlDsSessionEventCb eventCallback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, dsession, eventCallback, userdata);
	smlAssert(dsession);
	smlAssert(eventCallback);
	
	dsession->recvEventCallback = eventCallback;
	dsession->recvEventCallbackUserdata = userdata;
	
	smlDsSessionDispatch(dsession);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}


/** @brief Start the sync command to send to the other side
 * 
 * This function will start the sync command with which the changes will be sent to the other side.
 * After this command you can start to queue the changes. After you are done queueing changes, you
 * have to end the sync command with smlDsServerCloseSync().
 * 
 * @param server The DS server
 * @param callback The callback that will the answer to the sync command
 * @param userdata The userdata that will be passed to the alert
 * @param error A pointer to a error struct
 * @returns TRUE if the call was successful, FALSE otherwise
 */
SmlBool smlDsSessionSendSync(SmlDsSession *dsession, unsigned int num_changes, SmlStatusReplyCb callback, void *userdata, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p)", __func__, dsession, num_changes, callback, userdata, error);
	smlAssert(dsession);
	CHECK_ERROR_REF
	
	if (dsession->syncCommand) {
		smlErrorSet(error, SML_ERROR_GENERIC, "There already was a sync command started");
		goto error;
	}
	
	dsession->sentSyncCallback = callback;
	dsession->sentSyncCallbackUserdata = userdata;
	
	dsession->syncCommand = smlCommandNewSync(dsession->target, dsession->location, num_changes, error);
	if (!dsession->syncCommand)
		goto error;
	
	if (!smlSessionStartCommand(dsession->session, dsession->syncCommand, NULL, _sync_reply, dsession, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

//Send a change to the remote side
SmlBool smlDsSessionQueueChange(SmlDsSession *dsession, SmlChangeType type, const char *uid, const char *data, unsigned int size, const char *contenttype, SmlDsSessionWriteCb callback, void *userdata, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %s, %p, %i, %s, %p, %p, %p)", __func__, dsession, type, VA_STRING(uid), data, size, VA_STRING(contenttype), callback, userdata, error);
	smlAssert(dsession);
	CHECK_ERROR_REF
	
	if (!dsession->syncCommand) {
		smlErrorSet(error, SML_ERROR_GENERIC, "You have to start a sync command first");
		goto error;
	}
		
	SmlCommand *cmd = smlCommandNewChange(type, uid, data, size, contenttype, error);
	if (!cmd)
		goto error;
	
	SmlWriteContext *ctx = smlTryMalloc0(sizeof(SmlWriteContext), error);
	if (!ctx)
		goto error_free_cmd;
	
	ctx->callback = callback;
	ctx->userdata = userdata;
	ctx->uid = g_strdup(uid);
	ctx->type = type;
	ctx->session = dsession;
	
	g_mutex_lock(dsession->pendingMapsLock);
	dsession->pendingMaps = g_list_append(dsession->pendingMaps, ctx);
	g_mutex_unlock(dsession->pendingMapsLock);
	
	if (!smlSessionSendCommand(dsession->session, cmd, dsession->syncCommand, _change_reply, ctx, error))
		goto error_free_ctx;
	
	smlCommandUnref(cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error_free_ctx:
	smlSafeCFree(&(ctx->uid));
	smlSafeFree((gpointer *)&ctx);
error_free_cmd:
	smlCommandUnref(cmd);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Closes the sync command
 * 
 * @param server The DS server
 * @param error A pointer to a error struct
 * @returns TRUE if the call was successful, FALSE otherwise
 */
SmlBool smlDsSessionCloseSync(SmlDsSession *dsession, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, dsession, error);
	smlAssert(dsession);
	CHECK_ERROR_REF
	
	if (!dsession->syncCommand) {
		smlErrorSet(error, SML_ERROR_GENERIC, "There already was a sync command started");
		goto error;
	}
	
	if (!smlSessionEndCommand(dsession->session, NULL, error))
		goto error;
	
	smlCommandUnref(dsession->syncCommand);
	dsession->syncCommand = NULL;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

SmlBool smlDsSessionQueueMap(SmlDsSession *dsession, const char *uid, const char *newuid, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %s, %p)", __func__, dsession, VA_STRING(uid), VA_STRING(newuid), error);
	smlAssert(dsession);
	CHECK_ERROR_REF
	
	SmlMapItem *item = smlMapItemNew(uid, newuid, error);
	if (!item)
		goto error;
	
	dsession->mapItems = g_list_append(dsession->mapItems, item);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Closes the map command
 * 
 * This will tell the ds server that you are not going to queue
 * any more map commands.
 * 
 * @param server The DS server
 * @param error A pointer to a error struct
 * @returns TRUE if the call was successful, FALSE otherwise
 */
SmlBool smlDsSessionCloseMap(SmlDsSession *dsession, SmlStatusReplyCb callback, void *userdata, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, dsession, callback, userdata, error);
	smlAssert(dsession);
	CHECK_ERROR_REF
	
	if (!dsession->mapItems) {
		smlTrace(TRACE_EXIT, "%s: No mapitems", __func__);
		return TRUE;
	}
	
	SmlCommand *cmd = smlCommandNewMap(dsession->server->target, dsession->server->location, error);
	if (!cmd)
		goto error;
	
	while (dsession->mapItems) {
		SmlMapItem *item = dsession->mapItems->data;
		if (!smlCommandAddMapItem(cmd, item, error))
			goto error_free_cmd;
		smlMapItemUnref(item);
		
		dsession->mapItems = g_list_remove(dsession->mapItems, item);
	}
	
	if (!smlSessionSendCommand(dsession->session, cmd, NULL, callback, userdata, error))
		goto error_free_cmd;
	
	smlCommandUnref(cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error_free_cmd:
	smlCommandUnref(cmd);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

const char *smlDsSessionGetLocation(SmlDsSession *dsession)
{
	smlAssert(dsession);
	return smlDsServerGetLocation(dsession->server);
}

SmlDsServer *smlDsSessionGetServer(SmlDsSession *dsession)
{
	smlAssert(dsession);
	return dsession->server;
}

const char *smlDsSessionGetContentType(SmlDsSession *dsession)
{
	smlAssert(dsession);
	return smlDsServerGetContentType(dsession->server);
}

static void _recv_manager_alert(SmlSession *session, SmlCommand *cmd, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, cmd, userdata);
	SmlDsServer *server = userdata;
	SmlError *error = NULL;
	
	SmlDsSession *dsession = smlDsServerRecvAlert(server, session, cmd);
	if (!dsession)
		goto error;
	
	if (!smlManagerObjectRegister(server->manager, SML_COMMAND_TYPE_SYNC, session, server->location, NULL, NULL, smlDsSessionRecvSync, smlDsSessionRecvChange, dsession, &error))
		goto error_free_dsession;
	
	if (!smlManagerObjectRegister(server->manager, SML_COMMAND_TYPE_MAP, session, server->location, NULL, NULL, smlDsSessionRecvMap, NULL, dsession, &error))
		goto error_free_dsession;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;

error_free_dsession:
	smlSafeFree((gpointer *)&dsession);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
	if (error)
		smlErrorDeref(&error);
}

static void _recv_manager_san(SmlSession *session, SmlCommand *cmd, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, cmd, userdata);
	SmlDsServer *server = userdata;
	SmlError *error = NULL;

	/* ensure that the session is in client mode */
	session->sessionType = SML_SESSION_TYPE_CLIENT;

	/* fix target */
	smlLocationUnref(server->target);
	server->target = cmd->source;
	smlLocationRef(server->target);

	/* handle the SAN */
	if (server->sanSessionCallback) {
		SmlErrorType type = server->sanSessionCallback(
						server,
						session,
						cmd->private.alert.type,
						server->sanSessionCallbackUserdata);
		
		SmlStatus *reply = smlCommandNewReply(cmd, type, &error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(session, reply, &error))
			goto error;
			
		smlStatusUnref(reply);
	} else if (server->sanCallback) {
		SmlErrorType type = server->sanCallback(server, cmd->private.alert.type, server->sanCallbackUserdata);
		
		SmlStatus *reply = smlCommandNewReply(cmd, type, &error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(session, reply, &error))
			goto error;
			
		smlStatusUnref(reply);
	} else {
		smlTrace(TRACE_INTERNAL, "%s: SAN ignored", __func__);
		
		SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_NOT_IMPLEMENTED, &error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(session, reply, &error))
			goto error;
			
		smlStatusUnref(reply);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&error));
	if (error)
		smlErrorDeref(&error);
}

SmlBool smlDsServerRegister(SmlDsServer *server, SmlManager *manager, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, server, manager, error);
	smlAssert(server);
	smlAssert(manager);
	CHECK_ERROR_REF
	
	if (!smlManagerObjectRegister(manager, SML_COMMAND_TYPE_ALERT, NULL, server->location, NULL, NULL, _recv_manager_alert, NULL, server, error))
		goto error;
	
	if (!smlManagerObjectRegister(manager, SML_COMMAND_TYPE_ALERT, NULL, NULL, NULL,server->contenttype, _recv_manager_san, NULL, server, error))
		goto error;
	
	server->manager = manager;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

SmlLocation *smlDsSessionGetTarget(SmlDsSession *dsession)
{
	smlAssert(dsession);
	return dsession->target;
}

