// Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include "logservsession.h"
#include "LogNotify.h"
#include "LogServServer.h"
#include "logservpanic.h"
#include "LogServView.h"
#include "LogServOperationBase.h"
#include "LogServBackupInterface.h"
#include "LogServOperationFactory.h"
#include "LogServOperationManager.h"
#include "LogServDatabaseChangeInterface.h"
#include "LogServDatabaseChangeDefinition.h"
#include "LogServSqlStrings.h"

// Constants
const TInt KLogViewListGranuality = 5;
const TInt KLogServPendingGlobalChangesArrayGranularity = 3;


/////////////////////////////////////////////////////////////////////////////////////////
// -----> CLogServSession (source)
/////////////////////////////////////////////////////////////////////////////////////////

CLogServSession::CLogServSession(TLogServSessionId aSessionId, 
                                 MLogServSessionLifetimeObserver& aObserver, 
                                 MLogServBackupInterface& aBackupInterface, 
                                 MLogServTaskInterface& aTaskInterface, 
                                 MLogServOperationManager& aOperationManager, 
                                 MLogServDatabaseChangeInterface& aChangeInterface, 
                                 MLogServDatabaseTransactionInterface& aDatabase) :
    iSessionId(aSessionId), 
    iObserver(aObserver), 
    iBackupInterface(aBackupInterface), 
    iTaskInterface(aTaskInterface), 
    iOperationManager(aOperationManager), 
    iChangeInterface(aChangeInterface), 
    iDatabase(aDatabase), 
    iViewList(KLogViewListGranuality), 
    iPendingGlobalChanges(KLogServPendingGlobalChangesArrayGranularity)
	{
	iObserver.SLOHandleEvent(Id(), MLogServSessionLifetimeObserver::ELogServSessionEventCreated); 
	}

CLogServSession::~CLogServSession()
	{
	LOGTEXT3("CLogServSession::~CLogServSession() - client logging off: %S, %d", &iClientThreadName, iSessionId);

	iOperationManager.OMCancel(iSessionId, ETrue);
	
	delete iPackage;
	delete iNotify;

	iChangeInterface.DCIRequestChangeNotificationsCancel(*this);
	//
	iViewList.ResetAndDestroy();
	iViewList.Close();
	iPendingGlobalChanges.Close();
	//
	iObserver.SLOHandleEvent(Id(), MLogServSessionLifetimeObserver::ELogServSessionEventDestroyed); 

	LOGTEXT2("CLogServSession::~CLogServSession() - client dead %d", iSessionId);
	LOGTEXT("");
	}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void CLogServSession::DCOHandleGlobalChangeEventL(const TLogServDatabaseChangeDefinition& aChange)
	{
	if	(iExtendedNotificationRequested)
		User::LeaveIfError(iPendingGlobalChanges.Append(aChange));
	ExtendedNotifyCompleteL(KErrNone);
	}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
void CLogServSession::CreateL()
	{
	iPackage = CLogPackage::NewL();

	// Request change notifications
	iChangeInterface.DCIRequestChangeNotificationsL(*this);
	}

//
//  Handle a client request.
//
//  Leaving is handled by CLogServ::RunError() which reports the error code to the client
//  Note: operation add themselves to the server active queue in their constructor
//  The server active object owns them and deletes them when necessary
//  So the iffy looking code below won't actually leak memory
//
void CLogServSession::ServiceL(const RMessage2& aMessage)
	{
	LOGTEXT3("CLogServSession::ServiceL(%S, %d)", &iClientThreadName, aMessage.Function());

	switch(aMessage.Function())
		{
	/**
	 * Overall operation management
	 */
	case ELogOperationGetResult:
	case ELogOperationCancel:
	case ELogOperationInitiate:
		ServiceOperationFunctionL(aMessage);
		break;

	/**
	 * Notification related
	 */
	case ELogNotify:
		{
		if	(!iNotify)
			{
			iNotify = CLogNotify::NewL(iBackupInterface, iChangeInterface, Server().Priority());

		#ifdef LOGGING_ENABLED
			iNotify->SetClientThreadName(iClientThreadName);
		#endif
			}
		
		if	(!iNotify->IsActive())
			{
			const TTimeIntervalMicroSeconds32 delayTime = reinterpret_cast<TInt>(aMessage.Ptr0());
			
			//Validate time value
			if(delayTime.Int() < 0)
				{
				User::Leave(KErrArgument);
				}
			iNotify->Notify(delayTime, aMessage);
			}
		else
			::PanicClientL(aMessage, ELogAlreadyActive19);
		break;
		}
	case ELogNotifyCancel:
		{
		if	(iNotify)
			iNotify->Cancel();
		aMessage.Complete(KErrCancel);
		break;
		}
	case ELogNotifyExtended:
		{
		if	(iExtendedNotificationMessage == RMessage2())
			{
			iExtendedNotificationMessage = aMessage;
			iExtendedNotificationRequested = ETrue;
			}
		else
			::PanicClientL(aMessage, ELogExtendedMessageAlreadyActive);
		break;
		}
	case ELogNotifyExtendedCancel:
		{
        if	(iExtendedNotificationMessage != RMessage2())
			ExtendedNotifyCompleteL(KErrCancel);
		//
		iExtendedNotificationRequested = EFalse;
		iPendingGlobalChanges.Reset();
		iPendingGlobalChanges.GranularCompress();
		//
		aMessage.Complete(KErrCancel);
		break;
		}

	/**
	 * View functionality
	 */
	case ELogViewCreate:
	case ELogViewDelete:
	case ELogViewCount:
	case ELogViewOperationInitiate:
	case ELogViewChangeNotificationsRequest:
	case ELogViewChangeNotificationsCancel:
	case ELogViewFetchChanges:
	case ELogViewNotifyLockStatusChange:
	case ELogViewNotifyLockStatusChangeCancel:
		ServiceViewFunctionL(aMessage);
		break;

	/** 
	 * Misc. (debug) functionality
	 */
#ifdef _DEBUG
	case ELogMakeTransient:
		{
		Server().MakeTransient((TBool)aMessage.Ptr0());
		aMessage.Complete(KErrNone);
		break;
		}
	case ELogIsServerReady:
		{
		const TBool serverReady = (iBackupInterface.BIErrorValueForCurrentState() == KErrNone);
		aMessage.Complete(serverReady);
		break;
		}
	case ELogSetHeapFail:
		{
		User::__DbgSetAllocFail(RHeap::EUser, RHeap::TAllocFail(aMessage.Int0()),aMessage.Int1());
		aMessage.Complete(KErrNone);
		break;
		}
#endif// _DEBUG
	/**
	 * Unknown operations
	 */
	default:
		::PanicClientL(aMessage, ELogIllegalFunction);
		break;
		}
	}

/**
If aError is KErrBadDescriptor, then panic the client, else - default error handling.
KErrBadDescriptor error may be thrown from "message write" operations, if the client supplied a bad
descriptor to the server.
*/
void CLogServSession::ServiceError(const RMessage2& aMessage,TInt aError)
	{
	if(aError == KErrBadDescriptor)
		{
		//The __LOGPANIC_CLIENT() macro cannot be used here because it calls a leaving function. A leaving call
		//from a leaving call will terminate the server.
		aMessage.Panic(KLogServ, ELogBadDescriptor);
		}
	CSession2::ServiceError(aMessage, aError);
	}
	
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

CLogServServer& CLogServSession::Server() const
	{
	return *static_cast<CLogServServer*>(const_cast<CServer2*>(CSession2::Server()));
	}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////


CLogServViewBase& CLogServSession::ViewByIdL(TUint32 aViewId)
	{
	const TInt index = ViewPositionById(aViewId);
	User::LeaveIfError(index);
	return *iViewList[index];
	}

TInt CLogServSession::ViewPositionById(TUint32 aViewId) const
	{
	const TInt count = iViewList.Count();
	for(TInt i=0; i<count; i++)
		{
		const CLogServViewBase& view = *iViewList[i];
		if	(view.ViewId() == aViewId)
			return i;
		}
	return KErrNotFound;
	}


void CLogServSession::ReadClientServerDataL(TLogClientServerData& aClientServerData,
		const RMessage2 &aMessage, TInt aMinOperation, TInt aMaxOperation)
	{

	TInt length = aMessage.GetDesLengthL(0);
	if(length != sizeof(TLogClientServerData))
		{
		User::Leave(KErrBadDescriptor);
		}
	
	TPckg<TLogClientServerData> pData(aClientServerData);
	aMessage.ReadL(0, pData);
	
	//Validate Operation Type
	if((aClientServerData.iOperationType < aMinOperation) ||
			(aClientServerData.iOperationType > aMaxOperation))
		{
		User::Leave(KErrArgument);
		}
	}

void CLogServSession::ServiceViewFunctionL(const RMessage2& aMessage)
	{
	switch(aMessage.Function())
		{
	case ELogViewCreate:
		{
		iViewList.ReserveL(iViewList.Count() + 1);
		// Type is first parameter, view id is second
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int0());
		const TLogViewType type = static_cast<TLogViewType>(aMessage.Int1());
		
		//Validate type value
		if((type < ELogViewTypeEvent)||(type > ELogViewTypeDuplicate))
			{
			User::Leave(KErrArgument);
			}
		
		// Ask the factory to create it
		CLogServViewBase* view = LogServFactory::NewViewL(type, id, iDatabase, iBackupInterface, *iPackage, aMessage);
		TInt err = iViewList.Append(view);
        __ASSERT_ALWAYS(err == KErrNone, Panic(ELogArrayReserved));
		aMessage.Complete(err);
		break;
		}
	case ELogViewDelete:
		{
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int0());
		TInt indexAndError = ViewPositionById(id);
		if	(indexAndError >= 0)
			{
			delete iViewList[indexAndError];
			iViewList.Remove(indexAndError);
			indexAndError = KErrNone;
			}
		aMessage.Complete(indexAndError);
		break;
		}
	case ELogViewCount:
		{
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int0());
		CLogServViewBase& view = ViewByIdL(id);
		const TInt count = view.Count();
		aMessage.Complete(count);
		break;
		}
	case ELogViewOperationInitiate:
		{
		// We don't allow any operations during a backup
		iBackupInterface.BIValidateStateForDatabaseOperationL();
		//
		TLogClientServerData clientServerData;
		ReadClientServerDataL(clientServerData,aMessage, 
				ELogOperationViewSetup, ELogOperationViewWindowFetch);

		//
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int1());
		CLogServViewBase& view = ViewByIdL(id);

		// Create operation. Operations are owned by the operation manager (they are added to a queue)
		// when the objects are created, so this does not leak any memory.
		CLogServOperationBase* operation = LogServFactory::NewViewOperationL(clientServerData, iTaskInterface, iOperationManager, aMessage, *iPackage, iSessionId, view);
		(void) operation;
		break;
		}
	case ELogViewChangeNotificationsRequest:
		{
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int0());
		CLogServViewBase& view = ViewByIdL(id);
		view.RequestChangeNotifications(aMessage);
		break;
		}
	case ELogViewChangeNotificationsCancel:
		{
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int0());
		CLogServViewBase& view = ViewByIdL(id);
		view.RequestChangeNotificationsCancel();
		aMessage.Complete(KErrNone);
		break;
		}
	case ELogViewFetchChanges:
		{
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int0());
		CLogServViewBase& view = ViewByIdL(id);
		view.RequestChangesL(aMessage);
		aMessage.Complete(KErrNone);
		break;
		}
	case ELogViewNotifyLockStatusChange:
		{
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int0());
		CLogServViewBase& view = ViewByIdL(id);
		view.RequestLockStatusChanges(aMessage);
		break;
		}
	case ELogViewNotifyLockStatusChangeCancel:
		{
		const TLogViewId id = static_cast<TLogViewId>(aMessage.Int0());
		CLogServViewBase& view = ViewByIdL(id);
		view.RequestLockStatusChangesCancel();
		aMessage.Complete(KErrNone);
		break;
		}
		}
	}

void CLogServSession::ServiceOperationFunctionL(const RMessage2& aMessage)
	{
	// We don't allow any operations during a backup
	iBackupInterface.BIValidateStateForDatabaseOperationL();
	//
	TLogClientServerData clientServerData;
	ReadClientServerDataL(clientServerData,aMessage, ELogOperationEventAdd,ELogOperationMaintain);
	//
	const TInt function = aMessage.Function();
	//
	const TLogOperationId& operationId = clientServerData.iOperationId;
	#ifdef LOGGING_ENABLED
	const TLogOperationType& operationType = clientServerData.iOperationType;
	#endif
	LOGTEXT4("CLogServSession::ServiceOperationFunctionL() - Operation function for client %S: Id: %d, Type: %d", &iClientThreadName, operationId, operationType);
	//
	switch(function)
		{
	case ELogOperationGetResult:
		LOGTEXT("CLogServSession::ServiceOperationFunctionL() - getting result");
		iOperationManager.OMGetResultL(operationId, iSessionId, aMessage);
		aMessage.Complete(KErrNone);
		break;
	case ELogOperationCancel:
		LOGTEXT("CLogServSession::ServiceOperationFunctionL() - cancelling");
		iOperationManager.OMCancel(operationId, iSessionId, ETrue);
		aMessage.Complete(KErrCancel);
		break;
	case ELogOperationInitiate:
		LOGTEXT("CLogServSession::ServiceOperationFunctionL() - initiating");
		// Create operation. Operations are owned by the operation manager (they are added to a queue)
		// when the objects are created, so this does not leak any memory.
		CLogServOperationBase* operation = LogServFactory::NewOperationL(clientServerData, iTaskInterface, iOperationManager, aMessage, *iPackage, iSessionId);
		(void) operation;
		break;
		}
	LOGTEXT("CLogServSession::ServiceOperationFunctionL() - end");
	}

void CLogServSession::ExtendedNotifyCompleteL(TInt aCompletionCode)
	{
	const TInt count = iPendingGlobalChanges.Count();
    if	(iExtendedNotificationMessage != RMessage2() && (count || aCompletionCode < KErrNone))
		{
		if	(aCompletionCode >= KErrNone && count)
			{
			const TLogServDatabaseChangeDefinition& change = iPendingGlobalChanges[0];
			//
			const TPckgBuf<TInt> pContext(change.iChangeType.iUid);
			const TPckg<TInt> pParam1(change.iChangeParam1);
			const TPckg<TInt> pParam2(change.iChangeParam2);
			const TPckg<TInt> pParam3(change.iChangeParam3);
			//
			iExtendedNotificationMessage.WriteL(0, pContext);
			iExtendedNotificationMessage.WriteL(1, pParam1);
			iExtendedNotificationMessage.WriteL(2, pParam2);
			iExtendedNotificationMessage.WriteL(3, pParam3);
			//
			iPendingGlobalChanges.Remove(0);
			}
		//
		iExtendedNotificationMessage.Complete(aCompletionCode);
		}
	}
