--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/phonebookengines/contactsmodel/cntsrv/src/CCntStateMachine.cpp Wed Sep 01 12:29:52 2010 +0100
@@ -0,0 +1,2764 @@
+// Copyright (c) 2006-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:
+//
+
+/**
+ @file
+ @internalTechnology
+ @released
+*/
+
+#include "CCntStateMachine.h"
+#include "CCntRequest.h"
+#include "CCntDbManager.h"
+#include "CCntBackupRestoreAgent.h"
+#include "persistencelayer.h"
+#include "CntSpeedDials.h"
+#include <cntfldst.h> // for ccontacttextfield
+#include <cntfield.h> // for ccontacttextfield
+#include "CNTSTD.H" // for Panic codes
+
+// Event related headers
+#include <cntdbobs.h> // for ccontactdbobserver
+#include "CCntServer.h" // for KCntNullConnectionId.
+#include "CCntEventQueue.h" // for KMaxNumberOfEventsInEventQueue, KCntEventGranularity
+#include "CCntLogger.h"
+
+// Require SQL header for leave code checking
+#include <sqldb.h>
+
+/**
+CState - Base state destructor, default implementataion
+*/
+CState::~CState()
+ {}
+
+/**
+ CState constructor.
+ The main purpose of the CState class is to define the state transition table.
+ The CState class implements the most common state behaviour for each request object,
+ in the overridden AcceptRequestL method.
+ Note there is no NewL method on the CState class. It is not meant to be instantiated.
+
+ @param aStateMachine The statemachine object that is used for state transitions
+ @param aPersistenceLayer The persistence layer that allows provides database access
+
+*/
+CState::CState(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+:iStateMachine(aStateMachine), iPersistenceLayer(aPersistenceLayer)
+ {}
+
+/**
+ TransactionStartLC is called from many derived states. The session that started the
+ transaction is remembered in order to only allow that session perform database operations
+ during the transaction.
+ Transaction rollback is pushed onto the clean up stack in case of a leave should occur before the
+ transaction is committed. If the transaction can not be committed in full, then none of
+ the transaction should be committed.
+
+ @param aSessionId The unique ID of session that is moving the state machine into a transaction state.
+*/
+void CState::TransactionStartLC(TUint aSessionId)
+ {
+ iCurrentTransactionSessionId = aSessionId;
+ iPersistenceLayer.FactoryL().GetCollectorL().Reset();
+ iPersistenceLayer.TransactionManager().StartTransactionL();
+ CleanupStack::PushL(TCleanupItem(CState::CleanupTransactionRollback, this));
+ }
+
+/**
+ Rollback a transaction after an leave has occured.
+ None of the transaction is committed to the database
+
+ @param aState The state in which the leave occured
+*/
+void CState::CleanupTransactionRollback(TAny* aState)
+ {
+ TRAP_IGNORE(static_cast<CState*>(aState)->RollbackTransAndRecoverL(EFalse));
+ }
+
+/**
+ Commit the current transaction.
+*/
+void CState::TransactionCommitLP()
+ {
+ iPersistenceLayer.TransactionManager().CommitCurrentTransactionL(iCurrentTransactionSessionId);
+ iCurrentTransactionSessionId = 0;
+ CleanupStack::Pop(); // CleanupTransactionRollback
+ }
+
+/**
+ Clean up the transaction after a leave occurs.
+*/
+void CState::RollbackTransAndRecoverL(const TBool aNotification)
+ {
+ // Some operation has left before a commit could be called.
+ iPersistenceLayer.TransactionManager().RollbackCurrentTransactionL(iCurrentTransactionSessionId);
+ iCurrentTransactionSessionId = 0;
+
+ iPersistenceLayer.ContactsFileL().CloseTablesL(!aNotification);
+ iPersistenceLayer.ContactsFileL().OpenTablesL(!aNotification);
+ iStateMachine.SetCurrentStateL(iStateMachine.StateWritable());
+ }
+
+/* Note: The following methods implement default
+ AcceptRequestL behaviour for all states derived
+ from Parent Class CState
+*/
+
+/*
+ Default behaviour: The file has already been opened
+ so complete KErrNone.
+
+ @param aRequest Open database request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqAsyncOpen* aRequest)
+ {
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Update contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqUpdateCnt* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Commit contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqCommitCnt* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+
+/**
+ Default behaviour is to allow read access on the database
+ Open is the same as read but with locking - does not change the database
+
+ @param aRequest Open contact item request object
+ @return EProcessed if the request is completed or one of the values returned by
+ CState::DeferRequest if the database is currently locked.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqOpenCnt* aRequest)
+ {
+ if (iStateMachine.TransactionLockL().LockLX(aRequest->SessionId(), aRequest->CntItemId()) == KErrInUse)
+ {
+ // The contact item has been locked by another session
+ aRequest->SetTimeOutError(KErrInUse);
+ return DeferRequest(aRequest);
+ }
+
+ CContactItem* cntItem = iPersistenceLayer.PersistenceBroker().ReadLC(aRequest->CntItemId(), aRequest->ItemViewDef(), EPlAllInfo, aRequest->SessionId(), ETrue);
+ aRequest->CompleteL(*cntItem);
+ CleanupStack::PopAndDestroy(cntItem);
+ CleanupStack::Pop(); // we do not destroy it since that would trigger the leave mechanism and unlock the record
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is to allow read access on the database.
+ Read Contact does not change the database
+
+ @param aRequest Read contact item request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqReadCnt* aRequest)
+ {
+ CContactItem* cntItem = iPersistenceLayer.PersistenceBroker().ReadLC(aRequest->CntItemId(), aRequest->ItemViewDef(), EPlAllInfo, aRequest->SessionId());
+ aRequest->CompleteL(*cntItem);
+ CleanupStack::PopAndDestroy(cntItem);
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Delete contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqDeleteCnt* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to close the contact item by removing the locking
+ The lock was added during from an Open Contact Request
+ If the contact id is supplied, explicity unlock that contact item, otherwise
+ unlock the last contact item locked by the session
+
+ @param aRequest Close contact item request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqCloseCnt* aRequest)
+ {
+ if (aRequest->CntItemId() > KNullContactId)
+ {
+ aRequest->Complete(iStateMachine.TransactionLockL().UnLockL(aRequest->SessionId(), aRequest->CntItemId()));
+ }
+ else
+ {
+ iStateMachine.TransactionLockL().UnlockLastLockedContactL(aRequest->SessionId());
+ aRequest->Complete(KErrNone);
+ }
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is to always allow a session to unlock any contact
+ items that remain locked for that session before the session is closed
+
+ @param aRequest Unlock all contact items request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqInternalSessionUnlock* aRequest)
+ {
+ iStateMachine.TransactionLockL().UnLockAllL(aRequest->SessionId());
+ aRequest->Complete(KErrNone);
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param Create contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqCreateCnt* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ The default behaviour for Cancelling an Asyncronous Open database command
+ is to accept the file has been opened.
+
+ @param aRequest Cancel database open request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqCancelAsyncOpen* aRequest)
+ {
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Close contact database tables request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqCloseTables* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest ReOpen contact database tables request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqReOpen* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Begin transaction request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqDbBeginTrans* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Commit transaction request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqDbCommitTrans* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Rollback transaction request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+ */
+TAccept CState::AcceptRequestL(CReqDbRollbackTrans* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Notification of database backup/restore request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqBackupRestoreBegin* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to complete the request
+
+ @param aRequest Notification that a backup/restore has finished request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqBackupRestoreEnd* aRequest)
+ {
+ // In most cases no backup/restore will be in progress so by default
+ // complete this request.
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is set an internal low disk flag to true
+
+ @param aRequest Notification of low disk space request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqDiskSpaceLow* aRequest)
+ {
+ iStateMachine.SetLowDisk(ETrue);
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is to set an internal low disk flag to false
+
+ @param aRequest Notification disk space is normal request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqDiskSpaceNormal* aRequest)
+ {
+ iStateMachine.SetLowDisk(EFalse);
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is set an internal async activity flag to true
+
+ @param aRequest Notification of async activity request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqAsyncActivity* aRequest)
+ {
+ iStateMachine.SetAsyncActivity(ETrue);
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is to set an internal async activity flag to false
+
+ @param aRequest Notification of no async activity request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CState::AcceptRequestL(CReqNoAsyncActivity* aRequest)
+ {
+ iStateMachine.SetAsyncActivity(EFalse);
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Speed dial request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqSetSpeedDial* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Default behaviour is to defer the request
+
+ @param aRequest Own card request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::AcceptRequestL(CReqSetOwnCard* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+
+/**
+ Don't do anything with the event here. Simply propagate the event to the dbmanager
+ Any state that wishes to handle the event implements it's own overwritten
+ HandleDatabaseEventL method. The only state
+ that actually implements this is the Transaction State.
+
+ @param aEvent Database event generated in the Persistence Layer
+*/
+void CState::HandleDatabaseEventL(TContactDbObserverEvent aEvent)
+ {
+ iStateMachine.DbManager().HandleDatabaseEventL(aEvent);
+ }
+
+/**
+ Sets a timeout error code on the request. This error code is retrieved from the
+ derived state class via a call to TimeOutErrorCode()
+ if the derived state class does not implement a overridden TimeOutErrorCode(),
+ the CState::TimeOutErrorCode() is used.
+
+ Calls CState::DeferRequest to determine if the request should be deferred or not
+ depending on the request's timer status.
+
+ @param aRequest The request on which the timeout error is set.
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CState::DeferWithTimeOutError(CCntRequest* aRequest)
+ {
+ // use the time out error code specified with the current state
+ aRequest->SetTimeOutError(TimeOutErrorCode());
+
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Determines if an un-processed request should be completed with timeout error or if it
+ should be re-tried again later, depending on the current status of the request's timer.
+
+ @param aRequest The request failed to be processed by the current state
+ @return EDeferred - if the request's timer has not expired, the request can be processed again at the
+ next opportunity.
+ EProcessed - if the request's timer has expired and is completed with timeout
+ error.
+*/
+TAccept CState::DeferRequest(CCntRequest* aRequest)
+ {
+ // request still cannot be processed, check if the timer on the request
+ // has expired yet
+ if (aRequest->TimeOut() <= 0)
+ {
+ // timer expired, request should be completed with timeout error
+ aRequest->Complete(aRequest->TimeOutError());
+ return EProcessed;
+ }
+
+ // timer still valid, as request cannot be processed now, signal to re-try again later
+ return EDeferred;
+ }
+
+/**
+ Returns the default timeout - KErrNotReady
+*/
+TInt CState::TimeOutErrorCode()
+ {
+ return KErrNotReady;
+ }
+
+
+/**
+ CStateClosed Class NewL factory constructor
+ @see CState constructor
+*/
+CStateClosed* CStateClosed::NewL(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+ {
+ CStateClosed* stateClosed = new (ELeave) CStateClosed(aStateMachine, aPersistenceLayer);
+ return stateClosed;
+ }
+
+/**
+ CStateClosed Class constructor
+ @see CState constructor
+*/
+CStateClosed::CStateClosed(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+:CState(aStateMachine, aPersistenceLayer)
+ {}
+
+/**
+ CStateClosed Class destructor
+ @see CState destructor
+*/
+CStateClosed::~CStateClosed() {}
+
+
+/**
+ Process an open request in the closed state.
+ Note on the State design pattern:
+ Unlike the common state design pattern, most of the work is done in
+ the opening state and not in the closed state before the state transition.
+ The reason for this is re-use of code.
+
+ @param aRequest Open database request object
+ @return TAccept EProcessed if finished processing request
+ EDeferred if the request was not processed
+*/
+TAccept CStateClosed::AcceptRequestL(CReqAsyncOpen* aRequest)
+ {
+ iStateMachine.SetCurrentStateL(iStateMachine.StateOpening());
+ return iStateMachine.CurrentState().AcceptRequestL(aRequest);
+ }
+
+/**
+ We can only process re-open tables requests in the CStateTablesClosed
+ which means that this request must be preceeded by both a CReqAsyncOpen &
+ a CReqCloseTables. The correct behaviour is to defer the request with
+ the default CStateClosed timeout error.
+
+ @param aRequest ReOpen database tables request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+
+TAccept CStateClosed::AcceptRequestL(CReqReOpen* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+
+/**
+ Overridden read-only operations from base class: can't read from the database
+ while in the Closed state so defer these requests.
+
+ @param aRequest Read contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqReadCnt* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer Update requests with the default error for CStateClosed.
+ Note: This default error code is taken from the original
+ contacts model.
+
+ @param aRequest Update contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqUpdateCnt* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer Commit requests
+ The default error for CStateClosed has been taken from the original
+ contacts model
+
+ @param aRequest Commit contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqCommitCnt* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer Delete requests
+ The default error for CStateClosed has been taken from the original
+ contacts model
+
+ @param aRequest Delete contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqDeleteCnt* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer Create requests
+ The default error for CStateClosed has been taken from the original
+ contacts model
+
+ @param aRequest Create contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqCreateCnt* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer Open requests
+ The default error for CStateClosed has been taken from the original
+ contacts model
+ The default behaviour of the parent class CStateis to process an open request, the Closed State
+ defers this request
+
+ @param aRequest Commit contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqOpenCnt* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer the Close tables request
+ The tables are closed but so is the file.
+ To close the tables the file must be open
+
+ @param aRequest Close database tables request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqCloseTables* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer begin transaction requests
+ The default behaviour of the parent class CState is to process a begin transaction request,
+ the Closed State defers this request
+
+ @param aRequest Begin transaction request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqDbBeginTrans* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer commit transaction requests
+ The default behaviour of the parent class CState is to process a commit transaction request,
+ the Closed State defers this request
+
+ @param aRequest Commit transaction request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqDbCommitTrans* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer rollback transaction requests
+ The default behaviour of the parent class CState is to process a rollback transaction request,
+ the Closed State defers this request
+
+ @param aRequest Rollback transaction request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqDbRollbackTrans* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer set speed dial requests
+ The default behaviour of the parent class CState is to process a set speed dial request,
+ the Closed State defers this request
+
+ @param aRequest Set speed dial request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqSetSpeedDial* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Defer set own card requests
+ The default behaviour of the parent class CState is to process a set own card request,
+ the Closed State defers this request
+
+ @param aRequest Set own card request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateClosed::AcceptRequestL(CReqSetOwnCard* aRequest)
+ {
+ return DeferWithTimeOutError(aRequest);
+ }
+
+
+/**
+ Backup/restore begin notification requests
+
+ @param aRequest backup/restore begin notification request object
+ @return TAccept EProcessed
+*/
+TAccept CStateClosed::AcceptRequestL(CReqBackupRestoreBegin* aRequest)
+ {
+ // Backup/restore can take place from this state without doing anything so
+ // simply complete request.
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+
+/**
+ CStateTablesClosed Class NewL factory constructor
+ @see CState constructor
+*/
+CStateTablesClosed* CStateTablesClosed::NewL(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+ {
+ CStateTablesClosed* stateTablesClosed = new (ELeave) CStateTablesClosed(aStateMachine, aPersistenceLayer);
+ return stateTablesClosed;
+ }
+
+/**
+ CStateTablesClosed Class destructor
+ @see CState Destructor
+*/
+CStateTablesClosed::~CStateTablesClosed()
+ {
+ }
+
+/**
+ CStateTablesClosed Class constructor
+ @see CState constructor
+*/
+CStateTablesClosed::CStateTablesClosed(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+ :CStateClosed(aStateMachine, aPersistenceLayer)
+ {
+ }
+
+
+/**
+ Accept re-open tables requests
+ The tables are closed. A re-open request will result in re-opening the tables.
+
+ @param aRequest re-open tables request object
+ @return TAccept EProcessed - the request was processed
+*/
+TAccept CStateTablesClosed::AcceptRequestL(CReqReOpen* aRequest)
+ {
+ iPersistenceLayer.ContactsFileL().OpenTablesL(ETrue);
+ // We can only ever come into this state from the writable state
+ // as the database must have been opened
+ iStateMachine.SetCurrentStateL(iStateMachine.StateWritable());
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Accept Open database requests
+ The tables are closed. An async open request will result in re-opening the
+ tables.
+
+ @param aRequest async open request object
+ @return TAccept EProcessed if finished processing request
+*/
+TAccept CStateTablesClosed::AcceptRequestL(CReqAsyncOpen* aRequest)
+ {
+ iPersistenceLayer.ContactsFileL().OpenTablesL(ETrue);
+ // We can only ever come into this state from the writable state
+ // as the database must have been opened
+ iStateMachine.SetCurrentStateL(iStateMachine.StateWritable());
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ CStateOpening Class NewL factory constructor
+ @see CState constructor
+*/
+CStateOpening* CStateOpening::NewL(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+ {
+ CStateOpening* stateOpening = new (ELeave) CStateOpening(aStateMachine, aPersistenceLayer);
+ CleanupStack::PushL(stateOpening);
+ stateOpening->ConstructL();
+ CleanupStack::Pop(stateOpening);
+ return stateOpening;
+ }
+
+/**
+ CStateOpening ConstructL, gets and holds an instance of the persisitnce layer contact file interface
+ and creates and ActiveLoop object which facilitates long running operations.
+ @see CState constructor
+*/
+void CStateOpening::ConstructL()
+ {
+ iCntFile = &(iPersistenceLayer.ContactsFileL());
+ iActive = CActiveLoop::NewL();
+ }
+/**
+ CStateOpening constructor
+ @see CState constructor
+*/
+CStateOpening::CStateOpening(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+:CState(aStateMachine, aPersistenceLayer)
+ {
+ }
+
+/**
+ CStateOpening Class destructor
+ @see CState destructor
+*/
+CStateOpening::~CStateOpening()
+ {
+ delete iActive;
+ delete iFileName;
+ iOpenReqsStore.ResetAndDestroy();
+ }
+
+/**
+ Accept Open database requests
+ Opens the database file and tables.
+
+ @param aRequest Open database request object
+ @return EOwnershipPassed
+ @see CStateOpening::OpenDatabaseFileL
+*/
+TAccept CStateOpening::AcceptRequestL(CReqAsyncOpen* aRequest)
+ {
+ SetFileNameL(aRequest->FileName());
+ return OpenDatabaseFileL(aRequest);
+ }
+
+/**
+ Private member function that starts the database opening process.
+ This method may be called from several AcceptRequestL methods.
+
+ @param aRequest Open database request object
+ @return EOwnershipPassed to indicate ownership of the open database request has
+ been transferred
+*/
+TAccept CStateOpening::OpenDatabaseFileL(CCntRequest* aRequest, TBool aNotify)
+ {
+ // Add this request to the store where it will be completed on completion of
+ // the open operaion
+ //
+ // Note that after the request is successfully transferred to the Request
+ // Store, no leave can occur until the caller is notified that ownership
+ // of the request has been transferred.
+ iOpenReqsStore.AppendL(aRequest);
+
+ iNotify = aNotify;
+
+ // If we are not already processing an open request, start opening the file.
+ if (!iActive->IsActive())
+ {
+ InitialStep();
+ }
+
+ return EOwnershipPassed;
+ }
+
+/**
+ Accept the Reopen tables request as the tables are being opened anyway
+
+ @param aRequest Open database request object
+ @return EOwnershipPassed
+ @see CStateOpening::OpenDatabaseFileL
+*/
+TAccept CStateOpening::AcceptRequestL(CReqReOpen* aRequest)
+ {
+ // Open the database and notify (ETrue) all session of the recovery
+ return OpenDatabaseFileL(aRequest, ETrue);
+ }
+
+/**
+ Explicit cancel of an asyncronous Open Database request
+
+ @param aRequest Open database request object
+ @return TAccept EProcessed
+*/
+TAccept CStateOpening::AcceptRequestL(CReqCancelAsyncOpen* aRequest)
+ {
+ TInt max = iOpenReqsStore.Count();
+ for (TInt ii = 0; ii < max; ++ii)
+ {
+ if (aRequest->SessionId() == iOpenReqsStore[ii]->SessionId())
+ {
+ // If we only have one concurrent open request, cancel the open
+ // operation
+ if (max == 1)
+ {
+ iCntFile->Close();
+ iActive->Cancel();
+ iStateMachine.SetCurrentStateL(iStateMachine.StateClosed());
+ }
+
+ iOpenReqsStore[ii]->Complete(KErrCancel);
+ aRequest->Complete();
+ delete iOpenReqsStore[ii];
+ iOpenReqsStore.Remove(ii);
+ return EProcessed;
+ }
+ }
+ aRequest->Complete(KErrNotFound);
+ return EProcessed;
+ }
+
+/**
+ Start Opening the database using the CActiveLoop class. Opening a file
+ is a long running operation and is done in steps allowing other active objects
+ processor time between each opening step.
+*/
+void CStateOpening::InitialStep()
+ {
+ //iActive->Register(*this, ETrue);
+ iActive->Register( *this );
+ }
+
+/**
+Perform the next step in the opening operation
+@see InitialStep for more details
+*/
+TBool CStateOpening::DoStepL()
+ {
+ if (!iFileName)
+ {
+ User::Leave(KErrGeneral);
+ }
+
+#ifdef __VERBOSE_DEBUG__
+ RDebug::Print(_L("[CNTMODEL] ------------- Open Database Step -------------"));
+#endif
+
+#if defined(__PROFILE_DEBUG__)
+ RDebug::Print(_L("[CNTMODEL] MTD:CStateOpening::DoStepL"));
+#endif
+
+ iCntFile->OpenL(*iFileName);
+ // always go to this code if using SQLite
+ #ifdef __VERBOSE_DEBUG__
+ RDebug::Print(_L("[CNTMODEL] ------------- Database Opened -------------"));
+ #endif
+ // The file is now open.
+ DoCompletion(KErrNone);
+ iStateMachine.SetCurrentStateL(iStateMachine.StateWritable());
+ // Check if Backup/Restore Agent indicates backup in progress (i.e. we
+ // are opening database after backup starts).
+ if (iStateMachine.DbManager().BackupRestoreAgent().BackupInProgress())
+ {
+ TContactDbObserverEvent event;
+ event.iType = EContactDbObserverEventBackupBeginning;
+ event.iContactId = 0;
+ event.iConnectionId = 0;
+ // The HandleBackupRestoreEventL() method of the CCntDbManager that
+ // owns this state machine is called to send the appropriate request
+ // to the state machine and to notify any observers. The request
+ // causes a transition from CStateWritable to CStateBackupRestore.
+ // We need to send this event here since the event has only been
+ // heard by CCntDbManager instances (and any of its observers) that
+ // existed at the time when the backup first started.
+ iStateMachine.DbManager().HandleBackupRestoreEventL(event);
+ }
+ return EFalse;
+ }
+
+/**
+ Complete all Opening requests. More than one client (or internal
+ request) may open a database file simultaneously.
+ DoCompletion completes all outstanding Open requests.
+
+ @param aError The error code used to complete the request.
+ */
+void CStateOpening::DoCompletion(TInt aError)
+ {
+ TInt max = iOpenReqsStore.Count();
+ for (TInt ii = 0; ii < max; ++ii)
+ {
+ iOpenReqsStore[ii]->Complete(aError);
+ }
+ iOpenReqsStore.ResetAndDestroy();
+ }
+
+/**
+ Completes all open requests with an error code
+
+ @param aError The error code used to complete the request.
+*/
+void CStateOpening::DoError(TInt aError)
+ {
+
+ DEBUG_PRINT2(__VERBOSE_DEBUG__,_L("[CNTMODEL] ------------- Database Open Error %d -------------"), aError);
+
+ DoCompletion(aError);
+ TRAP_IGNORE(iStateMachine.SetCurrentStateL(iStateMachine.StateClosed() ) );
+ }
+
+/**
+ Sets the name of the file that is being opened. The name is held by
+ the opeing state and re-used when database needs to be closed and
+ re-opened (for example during recovery)
+
+ @param aFileName The name of the file that is being opened
+*/
+void CStateOpening::SetFileNameL(const TDes& aFileName)
+ {
+ HBufC* tmpFileName = HBufC::NewL(aFileName.Length());
+ *tmpFileName = aFileName;
+ delete iFileName;
+ iFileName = tmpFileName;
+ }
+
+/**
+ Once a Backup or Restore has completed we can re-open the database.
+
+ @param aRequest Notification of Backup/Restore end request object
+ @return EOwnershipPassed
+ @see CStateOpening::OpenDatabaseFileL
+*/
+TAccept CStateOpening::AcceptRequestL(CReqBackupRestoreEnd* aRequest)
+ {
+ return OpenDatabaseFileL(aRequest);
+ }
+
+/**
+ Overridden read-only operations from base class: can't read from the database
+ while in the Opening state so defer these requests.
+
+ @param aRequest Read contact item request object
+ @return EDeferred if the request is not processed, EProcessed if the request is
+ completed with timeout error.
+ @see CState::DeferRequest
+*/
+TAccept CStateOpening::AcceptRequestL(CReqReadCnt* aRequest)
+ {
+ return DeferRequest(aRequest); // Uses the requests default timeout error
+ }
+
+// @see CStateOpening::AcceptRequestL(CReqReadCnt* )
+TAccept CStateOpening::AcceptRequestL(CReqOpenCnt* aRequest)
+ {
+ return DeferRequest(aRequest);
+ }
+
+
+
+// CStateWritable Class implementation //
+
+/**
+ CStateWritable Class NewL factory constructor
+ @see CState constructor
+*/
+CStateWritable* CStateWritable::NewL(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+ {
+ CStateWritable* stateWritable = new (ELeave) CStateWritable(aStateMachine, aPersistenceLayer);
+ return stateWritable;
+ }
+
+/**
+ CStateWritable Class constructor
+ @see CState constructor
+*/
+CStateWritable::CStateWritable(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+:CState(aStateMachine, aPersistenceLayer)
+ {
+ }
+
+/**
+ CStateWritable Class destructor
+ @see CState destructor
+*/
+CStateWritable::~CStateWritable()
+ {
+ }
+
+
+// Derived AcceptRequestL methods - Vistor Pattern
+
+/**
+ Update a contact item in the database
+
+ @param aRequest Update contact item request object
+ @return TAccept EProcessed if finished processing request or one of the
+ values returned by DeferWithTimeOutError
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateWritable::AcceptRequestL(CReqUpdateCnt* aRequest)
+ {
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+ // Check if the contact has been locked
+ if (iStateMachine.TransactionLockL().IsLocked(aRequest->Item().Id()))
+ {
+ // If the request can not be procesed after the timeout period, it should
+ // complete with KErrInUse to assure binary compatibility with the original model
+ return DeferWithTimeOutError(aRequest);
+ }
+
+ TRAPD(updateErr,
+ {
+ TransactionStartLC(aRequest->SessionId());
+
+ iPersistenceLayer.PersistenceBroker().SetConnectionId(aRequest->SessionId());
+ iPersistenceLayer.PersistenceBroker().UpdateL(aRequest->Item(), aRequest->SessionId());
+
+ TransactionCommitLP();
+ });
+ if (updateErr == KSqlErrGeneral)
+ {
+ // Write operation failed, probably due to view read activity
+ return DeferWithTimeOutError(aRequest);
+ }
+ else if (updateErr != KErrNone)
+ {
+ // Unknown error, propagate the leave to the client
+ User::Leave(updateErr);
+ }
+
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Commit an opened contact item to the database
+ Unlock the contact item so other session can modify it.
+ Starts and commits a local transaction inorder to handle leaves.
+
+ @param aRequest Commit contact item request object
+ @return TAccept EProcessed if finished processing request or one of the
+ values returned by DeferWithTimeOutError
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateWritable::AcceptRequestL(CReqCommitCnt* aRequest)
+ {
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ // Check if the contact has been locked by this session
+ if (iStateMachine.TransactionLockL().IsLocked(aRequest->SessionId(), aRequest->Item().Id()))
+ {
+ // If the request can not be procesed after the timeout period, it should
+ // complete with KErrInUse to assure binary compatibility with the original model
+ return DeferWithTimeOutError(aRequest);
+ }
+
+ TRAPD(commitErr,
+ {
+ TransactionStartLC(aRequest->SessionId());
+ iPersistenceLayer.PersistenceBroker().SetConnectionId(aRequest->SessionId());
+ iPersistenceLayer.PersistenceBroker().UpdateL(aRequest->Item(), aRequest->SessionId());
+ User::LeaveIfError(iStateMachine.TransactionLockL().UnLockL(aRequest->SessionId(), aRequest->Item().Id()));
+ TransactionCommitLP();
+ });
+ if (commitErr == KSqlErrGeneral)
+ {
+ // Can't commit contact, probably due to idle sorter activity
+ return DeferWithTimeOutError(aRequest);
+ }
+ else
+ {
+ User::LeaveIfError(commitErr);
+ }
+
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+
+/**
+ Delete a contact item from the database.
+ Starts and commits a local transaction inorder to handle leaves.
+ Checks if the contact item is locked by another session.
+
+ @param aRequest Delete contact item request object
+ @return TAccept EProcessed if finished processing request or one of the
+ values returned by DeferWithTimeOutError
+ @see CState::DeferWithTimeOutError
+*/
+TAccept CStateWritable::AcceptRequestL(CReqDeleteCnt* aRequest)
+ {
+ // Check if the contact has been locked
+ if (iStateMachine.TransactionLockL().IsLocked(aRequest->CntItemId()))
+ {
+ // If the request can not be procesed after the timeout period, it should
+ // complete with KErrInUse - the contact is locked
+ return DeferWithTimeOutError(aRequest);
+ }
+
+ TRAPD(deleteErr,
+ {
+ TransactionStartLC(aRequest->SessionId());
+
+ iPersistenceLayer.PersistenceBroker().SetConnectionId(aRequest->SessionId());
+ CContactItem* cntItem = iPersistenceLayer.PersistenceBroker().DeleteLC(aRequest->CntItemId(), aRequest->SessionId(), ESendEvent);
+ CleanupStack::PopAndDestroy(cntItem);
+
+ TransactionCommitLP();
+ });
+ if (deleteErr == KSqlErrGeneral)
+ {
+ // Write operation failed, probably due to view read activity
+ return DeferWithTimeOutError(aRequest);
+ }
+ else if (deleteErr != KErrNone)
+ {
+ // Unknown error, propagate the leave to the client
+ User::Leave(deleteErr);
+ }
+
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Create a new contact item
+
+ @param aRequest Create contact item request object
+ @return TAccept EProcessed
+*/
+TAccept CStateWritable::AcceptRequestL(CReqCreateCnt* aRequest)
+ {
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ TContactItemId cntID(KNullContactId);
+ TRAPD(createErr,
+ {
+ TransactionStartLC(aRequest->SessionId());
+
+ iPersistenceLayer.PersistenceBroker().SetConnectionId(aRequest->SessionId());
+ cntID = iPersistenceLayer.PersistenceBroker().CreateL(aRequest->Item(), aRequest->SessionId());
+
+ TransactionCommitLP();
+ });
+ if (createErr == KSqlErrGeneral)
+ {
+ // Write operation failed, probably due to view read activity
+ return DeferWithTimeOutError(aRequest);
+ }
+ else if (createErr != KErrNone)
+ {
+ // Unknown error, propagate the leave to the client
+ User::Leave(createErr);
+ }
+
+ aRequest->Complete(cntID);
+ return EProcessed;
+ }
+
+/**
+ Change the current state of the model to CStateTablesClosed.
+ The database is unavailable.
+
+ @param aRequest Close database tables request object
+ @return TAccept EProcessed if finished processing request
+*/
+TAccept CStateWritable::AcceptRequestL(CReqCloseTables* aRequest)
+ {
+ iPersistenceLayer.ContactsFileL().CloseTablesL(ETrue);
+ iStateMachine.SetCurrentStateL(iStateMachine.StateTablesClosed());
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+
+/**
+ Do nothing for Cancel - it's too late as the database is already open.
+
+ @param aRequest Cancel open database request object
+ @return TAccept EProcessed if finished processing request
+*/
+TAccept CStateWritable::AcceptRequestL(CReqCancelAsyncOpen* aRequest)
+ {
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+
+/**
+ Start a database transaction by moving to a transaction state
+
+ @param aRequest Begin transaction request object
+ @return TAccept EProcessed if finished processing request
+ EDeferred if the request was not processed
+*/
+TAccept CStateWritable::AcceptRequestL(CReqDbBeginTrans* aRequest)
+ {
+ // In the current implementation there are no operations allowed under the
+ // low disk condition that will reduce the size of the database: this is in
+ // line with Contacts Model 1 behaviour. Later, when we allow operations
+ // that reduce the size of the database, this check should be removed and
+ // allow the transition to CStateTransaction.
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ iStateMachine.SetCurrentStateL(iStateMachine.StateTransaction());
+ return iStateMachine.CurrentState().AcceptRequestL(aRequest);
+ }
+
+/**
+ Start a database backup/restore by moving to CStateBackupRestore state. If any
+ contact items are locked or there is any asynchronous activity using the
+ database then we cannot close the file (and the backup/restore client will not
+ be allowed to backup/restore).
+
+ @param aRequest Notification of backup/restore begin request object
+ @return TAccept EProcessed if finished processing request
+*/
+TAccept CStateWritable::AcceptRequestL(CReqBackupRestoreBegin* aRequest)
+ {
+ if (!iStateMachine.TransactionLockL().AnyLocked() &&
+ !iStateMachine.AsyncActivity())
+ {
+ // First reset collection, since it construct views based on table
+ // Reset will fail if called after closing tables
+ iPersistenceLayer.FactoryL().GetCollectorL().Reset();
+ // Close the file to allow the backup/restore to take place.
+ iPersistenceLayer.ContactsFileL().Close();
+ }
+ iStateMachine.SetCurrentStateL(iStateMachine.StateBackupRestore());
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+
+/**
+ Reset the speed dials
+
+ @param aRequest Reset speed dials request object
+ @return TAccept EProcessed if finished processing request
+ EDeferred if the request was not processed
+*/
+TAccept CStateWritable::AcceptRequestL(CReqSetSpeedDial* aRequest)
+ {
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ TContactItemId contactId = aRequest->TheContactId();
+ if(contactId == 0)
+ {
+ User::Leave(KErrArgument);
+ }
+
+ // Obtain the contact ID currently associated with the speed dial index.
+ // The phone number is not being used at the moment
+ TSpeedDialPhoneNumber phoneNumberFromSpeedDialTable;
+ TContactItemId OldContactId = aRequest->SpeedDialTable().SpeedDialContactItem(aRequest->SpeedDialIndex(), phoneNumberFromSpeedDialTable);
+
+ // We should not be able to remove a speed dial from an open item, even if
+ // it has been opened by the same session: use the IsLocked() method which
+ // ignores session ID.
+ TBool isLocked = iStateMachine.TransactionLockL().IsLocked(OldContactId);
+
+ // This code resets an entry from the speed dial table, as required when calling either
+ // CContactDatabase::ResetServerSpeedDialsL() (contactId is KNullContactId) or
+ // CContactDatabase::RemoveSpeedDialFieldL() (contactId must be equal to the OldContactId)
+ // If the field index is -1, it indicates that the speed dial entry corresponding
+ // to the speed dial index passed in the request should be reset.
+ TBool doResetOldContactItem = ETrue;
+ TBool doResetSpeedDialEntry = aRequest->TheFieldIndex() == -1;
+
+ if (doResetSpeedDialEntry)
+ {
+ if (contactId == KNullContactId || contactId == OldContactId)
+ {
+ if (isLocked)
+ {
+ User::Leave(KErrInUse);
+ }
+
+ aRequest->IniFileManager().SetSpeedDialIdForPositionL(aRequest->SpeedDialIndex(), KNullContactId, KNullDesC(), aRequest->SessionId(), EFalse);
+ }
+ else
+ {
+ doResetOldContactItem = EFalse;
+ }
+ }
+
+ //Everything, i.e. removal of the old speed dial reference and
+ //the setting of the new takes place during the same transaction
+ //i.e. start transaction here
+ TransactionStartLC(aRequest->SessionId());
+ {
+ // Check if there is already a contact associated with this speed dial
+ // index.
+ if (OldContactId != KErrNotFound && doResetOldContactItem)
+ {
+ // Fetch the item from the ID, remember to pop it
+ CContactItem* cntItem = iPersistenceLayer.PersistenceBroker().ReadLC(OldContactId, aRequest->ItemViewDef(), EPlAllInfo, aRequest->SessionId());
+ // Remove speed dial attributes from the contact item field.
+ TUid fieldTypeUid = CCntServerSpeedDialManager::SpeedDialFieldUidFromSpeedDialPosition(aRequest->SpeedDialIndex());
+ TInt fieldIdFound = cntItem->CardFields().Find(fieldTypeUid);
+ if (fieldIdFound != KErrNotFound)
+ {
+ cntItem->CardFields()[fieldIdFound].RemoveFieldType(fieldTypeUid);
+ cntItem->CardFields()[fieldIdFound].SetSpeedDial(EFalse);
+ }
+ // Update changes to the contact item in the database.
+ iPersistenceLayer.PersistenceBroker().SetConnectionId(aRequest->SessionId());
+ iPersistenceLayer.PersistenceBroker().UpdateL(*cntItem, aRequest->SessionId(), ETrue);
+ CleanupStack::PopAndDestroy(cntItem);
+ }
+
+ if (!doResetSpeedDialEntry)
+ {
+ // Fetch the contact item containing the phone number to be used as
+ // a speed dial.
+ CContactItem* cntItem = iPersistenceLayer.PersistenceBroker().ReadLC(contactId, aRequest->ItemViewDef(), EPlAllInfo, aRequest->SessionId());
+ if (cntItem->CardFields().Count() < 1)
+ {
+ User::Leave(KErrUnknown);
+ }
+ // Get the field containing the number to be associated with the
+ // speed dial.
+ CContactItemField& speeddialField = cntItem->CardFields()[aRequest->TheFieldIndex()];
+ // Add speed dial attributes to the contact item field.
+ TUid fieldTypeUid = CCntServerSpeedDialManager::SpeedDialFieldUidFromSpeedDialPosition(aRequest->SpeedDialIndex());
+ if (!speeddialField.ContentType().ContainsFieldType(fieldTypeUid))
+ {
+ speeddialField.AddFieldTypeL(fieldTypeUid);
+ }
+ speeddialField.SetUserAddedField(ETrue);
+ speeddialField.SetSpeedDial(ETrue);
+ // Get the phone number from the field.
+ if (speeddialField.StorageType() != KStorageTypeText)
+ {
+ User::Leave(KErrArgument);
+ }
+ // Truncate it if its length is > KSpeedDialPhoneLength
+ TInt numLen = Min(speeddialField.TextStorage()->Text().Length(), KSpeedDialPhoneLength);
+ TPtrC phoneNumber(speeddialField.TextStorage()->Text().Mid(0, numLen));
+ // Update changes to the contact item in the database.
+ iPersistenceLayer.PersistenceBroker().SetConnectionId(aRequest->SessionId());
+ // Update the speed dial table.
+ aRequest->IniFileManager().SetSpeedDialIdForPositionL(aRequest->SpeedDialIndex(), contactId, phoneNumber, aRequest->SessionId(), EFalse);
+ iPersistenceLayer.PersistenceBroker().UpdateL(*cntItem, aRequest->SessionId(), ETrue);
+ // Unlock the item.
+ User::LeaveIfError(iStateMachine.TransactionLockL().UnLockL(aRequest->SessionId(), contactId));
+ CleanupStack::PopAndDestroy(cntItem);
+ }
+ }
+ TransactionCommitLP();
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+
+/**
+ Set own card data
+
+ @param aRequest Set own card request object
+ @return TAccept EProcessed if finished processing request
+ EDeferred if the request was not processed
+*/
+TAccept CStateWritable::AcceptRequestL(CReqSetOwnCard* aRequest)
+ {
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ TUid aContactType = aRequest->Item().Type();
+
+ // this should leave with kerrnotsupported if the type doesn't match!!!!
+ if (aContactType==KUidContactGroup || aContactType==KUidContactTemplate || aContactType==KUidContactCardTemplate)
+ {
+ User::Leave(KErrNotSupported);
+ }
+ // if the requested item is already set as own card then just return
+ if (aRequest->Item().Id()==iPersistenceLayer.ContactProperties().OwnCardIdL())
+ {
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+ // if the requested ID is system template or KNUllContactID then leave
+ if (aRequest->Item().Id()==KGoldenTemplateId || aRequest->Item().Id()==KNullContactId)
+ {
+ User::Leave(KErrNotFound);
+ }
+ //Everything, i.e. removal of the old own card reference and
+ //the setting of the new takes place during the same transaction
+ //i.e. start transaction here
+ TransactionStartLC(aRequest->SessionId());
+ {//reset the old own card to become an ordinary contacts card
+ if (iPersistenceLayer.ContactProperties().OwnCardIdL() != KNullContactId)
+ {
+ // Fetch the old current item from the ID, remember to pop it
+ CContactItem* oldOwnCard = iPersistenceLayer.PersistenceBroker().ReadLC(iPersistenceLayer.ContactProperties().OwnCardIdL(), aRequest->ItemViewDef(), EPlAllInfo, aRequest->SessionId());
+ iPersistenceLayer.PersistenceBroker().SetConnectionId(aRequest->SessionId());
+ //change the type to KUidContactCard for the old ownCard
+ iPersistenceLayer.PersistenceBroker().ChangeTypeL(oldOwnCard->Id(), KUidContactCard);
+ CleanupStack::PopAndDestroy(oldOwnCard);
+ }
+ iPersistenceLayer.PersistenceBroker().SetConnectionId(aRequest->SessionId());
+ //Now set the new own card
+ iPersistenceLayer.PersistenceBroker().ChangeTypeL(aRequest->Item().Id(), KUidContactOwnCard);
+ }
+ TransactionCommitLP();
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ The error code which deferred requests use after a timeout from the
+ writable state this maintians consistency with the original contacts model
+
+ @return The most common the timeout error code -KErrInUse- used in the writable state
+*/
+TInt CStateWritable::TimeOutErrorCode()
+ {
+ return KErrInUse;
+ }
+
+
+
+// CStateTransaction Implementation//
+/**
+ CStateTransaction Class NewL factory constructor
+ Create a transaction state
+ @see CState constructor
+*/
+CStateTransaction* CStateTransaction::NewL(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+ {
+ CStateTransaction* stateTransaction = new (ELeave) CStateTransaction(aStateMachine, aPersistenceLayer);
+ CleanupStack::PushL(stateTransaction);
+ stateTransaction->ConstructL();
+ CleanupStack::Pop(stateTransaction);
+ return stateTransaction;
+ }
+
+/**
+ Instantiate the CTransctionTimer object. The transaction state contains a timer
+ because a client may put the server into a transation state and for some unknown
+ reason my never complete the transaction. Such a senario would result in the server
+ becoming unusable (ie No other client can modify the database until the transaction
+ is completed or rolled back). If the CStateTransaction class does not hear from the
+ client for a given time, the transaction times out and is rolled back. This is the
+ responsibility of the CTransactionTimer.
+*/
+void CStateTransaction::ConstructL()
+ {
+ // Pass the transaction state that will timeout.
+ iTimeOut = CTransactionTimer::NewL(*this);
+ }
+
+/**
+ CStateTransaction Class constructor
+ @see CState constructor
+*/
+CStateTransaction::CStateTransaction(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+: CState(aStateMachine, aPersistenceLayer), iEventQ(KCntEventGranularity)
+ {
+ }
+
+/**
+ CStateTransaction Class constructor
+ @see CState constructor
+*/
+CStateTransaction::~CStateTransaction()
+ {
+ delete iTimeOut;
+ iEventQ.Close();
+ }
+
+/**
+ Cancel the transaction - will result in a database rollback
+ implemented in the base CState class
+ This overwritten CancelTransaction is only ever called when the transaction
+ times out. The base class CState::CancelTransactionL() is called when any
+ state class or persistence layer method leaves
+*/
+void CStateTransaction::CancelTransactionL()
+ {
+ CState::RollbackTransAndRecoverL(EFalse);
+ iSessionId = 0; // Allow another session enter a transaction state.
+ }
+
+/**
+ Get the CStateTransaction default timeout error code.
+
+ @return TInt ErrLocked
+*/
+TInt CStateTransaction::TimeOutErrorCode()
+ {
+ return KErrLocked;
+ }
+
+
+/**
+ Begin a database transaction, start the transaction timeout which rolls-back
+ the transaction if the session dies. Reset the transactions event queue - this
+ event-queue queues all events until the transaction has been committed.
+
+ @param aRequest Begin a database transaction
+ @return TAccept EProcessed if finished processing request, or one of the values returned
+ by CState::DeferRequest
+ @see CState::DeferRequest
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqDbBeginTrans* aRequest)
+ {
+
+ #if defined(__PROFILE_DEBUG__)
+ RDebug::Print(_L("[CNTMODEL] MTD: CStateTransaction::AcceptRequestL"));
+ #endif
+
+ // Only one session can ever be in a transaction state
+ if (iSessionId == 0)
+ {
+ iSessionId = aRequest->SessionId();
+ iPersistenceLayer.FactoryL().GetCollectorL().Reset();
+ iPersistenceLayer.TransactionManager().StartTransactionL();
+ iTimeOut->Start();
+ aRequest->Complete();
+ // Reset the event queue - although it is also reset when a transaction
+ // is committed or explicitly rolled back, it will now be reset if a rollback occured
+ // because of a leave. Resetting the queue after commit/explicit rollback free's memory.
+ iEventQ.Reset();
+ return EProcessed;
+ }
+ // This session has already started a transaction
+ if (iSessionId == aRequest->SessionId())
+ {
+ aRequest->Complete();
+ return EProcessed;
+ }
+ // Another session has started a transaction
+ return DeferRequest(aRequest);
+ }
+
+
+/**
+ Commit the current transaction if started by the same session.
+ Stop the transaction timer.
+ Propogate all events to the observer.
+ Compress the database.
+
+ @param aRequest Commit a database transaction
+ @return TAccept EProcessed if the transaction has been committed
+ @leave KErrLocked if a different session had started the transaction
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqDbCommitTrans* aRequest)
+ {
+ if (iSessionId == aRequest->SessionId())
+ {
+ TRAPD(commitErr, iPersistenceLayer.TransactionManager().CommitCurrentTransactionL(aRequest->SessionId()));
+ if (commitErr == KSqlErrGeneral)
+ {
+ // Operation has probably been blocked due to read lock by view idle sorter.
+ return DeferWithTimeOutError(aRequest);
+ }
+ else
+ {
+ User::LeaveIfError(commitErr);
+ }
+
+ iTimeOut->Stop(); // Transaction completed - shouldn't timeout
+
+ // The database had been updated. All session should now be notified
+ // of events.
+ PropagateDatabaseEventsL();
+
+ iStateMachine.SetCurrentStateL(iStateMachine.StateWritable());
+
+ iSessionId = 0;
+ // Only complete the request after the last leaving method. If
+ // a leave occurs after the request (message) has been completed, then the method
+ // CCntSession::ServiceError will try to complete the message a second time
+ // causing a panic.
+ aRequest->Complete();
+ }
+ else
+ {
+ StrayRequestL(aRequest); // Only the current session should be able to
+ } // send a commit transaction request
+ return EProcessed;
+ }
+
+
+
+/**
+ Rollback the current transaction if started by the same session
+ Reset the event queue as no operation has been committed to the database.
+ Don't compress the database as it hasn't changed.
+ Notify the observer that a rollback has occured.
+
+ @param aRequest Rollback a database transaction
+ @return TAccept EProcessed if the transaction has been rolled back
+ @leave KErrLocked if a different session had started the transaction
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqDbRollbackTrans* aRequest)
+ {
+ if (iSessionId == aRequest->SessionId())
+ {
+ iEventQ.Reset(); // Empty the event queue - no operations have been committed
+ // so sessions should never be notified of the event
+
+ iCurrentTransactionSessionId = aRequest->SessionId();
+ CState::RollbackTransAndRecoverL(ETrue);
+ iSessionId = 0;
+
+ // Transaction completed - shouldn't timeout
+ iTimeOut->Stop();
+
+ // Rollback event needs to be propagated.
+ TContactDbObserverEvent event;
+ event.iType = EContactDbObserverEventRollback;
+ event.iContactId = KNullContactId;
+ event.iConnectionId = iSessionId;
+ iStateMachine.DbManager().HandleDatabaseEventL(event);
+
+ aRequest->Complete();
+ }
+ else
+ {
+ StrayRequestL(aRequest); // Only the current session should be able
+ } // to rollback a transaction
+ return EProcessed;
+ }
+
+
+/**
+ A session tried to commit or rollback a transaction that was started
+ by another session. Destroy the request and leave.
+
+ @param aRequest A pointer to a request object scoped to it parent class.
+ @leave KErrLocked A different session had started the transaction
+*/
+void CStateTransaction::StrayRequestL(CCntRequest* /* aRequest */)
+ {
+ User::Leave(KErrLocked);
+ }
+
+
+/**
+ Create a contact item from a the session that started the transaction
+
+ @param aRequest The request that will contain the created contact item
+ @return TAccept EProcessed if finished processing request
+ EDeferred if the request was not processed
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqCreateCnt* aRequest)
+ {
+ #if defined(__PROFILE_DEBUG__)
+ RDebug::Print(_L("[CNTMODEL] MTD: CStateTransaction::AcceptRequestL"));
+ #endif
+
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ if (iSessionId == aRequest->SessionId())
+ {
+ TContactItemId itemId(KNullContactId);
+ TRAPD(createErr, itemId = iPersistenceLayer.PersistenceBroker().CreateL(aRequest->Item(), aRequest->SessionId()));
+ if (createErr == KSqlErrGeneral)
+ {
+ // Can't create contact item, probably due to view idle sorter activity
+ return DeferWithTimeOutError(aRequest);
+ }
+ else
+ {
+ User::LeaveIfError(createErr);
+ }
+
+ aRequest->Complete(itemId);
+ iTimeOut->Reset(); // restart the timeout as the client session is still alive
+ return EProcessed;
+ }
+
+ // The session that is trying to perform this operation has not started the transaction
+ return DeferWithTimeOutError(aRequest);
+ }
+
+/**
+ Read a contact item - always allow read operation from any session
+ ie the session does not need to have started the transaction to read the contact item
+
+ @param aRequest The request that will contain the contact item read from the database
+ @return TAccept EProcessed if finished processing request
+ EDeferred if the request was not processed
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqReadCnt* aRequest)
+ {
+ iTimeOut->Reset(); // Reset the timeout, the client is still alive
+ return CState::AcceptRequestL(aRequest);
+ }
+
+/**
+ Update a contact item from a the session that started the transaction
+
+ @param aRequest The request that contain the contact item that is to be updated in the database
+ @return TAccept EProcessed if finished processing request, or one of the values returned
+ by CState::DeferRequest
+ @see CState::DeferRequest
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqUpdateCnt* aRequest)
+ {
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ // Check if the contact has been locked by any session - including this session
+ // This is for reasons of compatibility with the original model only
+ if (iStateMachine.TransactionLockL().IsLocked(aRequest->Item().Id()) == EFalse)
+ {
+ if (iSessionId == aRequest->SessionId())
+ {
+ TRAPD(updateErr, iPersistenceLayer.PersistenceBroker().UpdateL(aRequest->Item(), aRequest->SessionId()));
+ if (updateErr == KSqlErrGeneral)
+ {
+ // Can't update item, probably due to idle sorter activity
+ return DeferWithTimeOutError(aRequest);
+ }
+ else
+ {
+ User::LeaveIfError(updateErr);
+ }
+
+ aRequest->Complete();
+ iTimeOut->Reset();
+
+ return EProcessed;
+ }
+ else
+ {
+ // The session that is trying to perform this operation has not started the transaction
+ return DeferWithTimeOutError(aRequest);
+ }
+ }
+ else
+ {
+ // If the request can not be procesed after the timeout period, it should
+ // complete with KErrInUse as the contact is locked
+ aRequest->SetTimeOutError(KErrInUse);
+ }
+ return DeferRequest(aRequest);
+ }
+
+/**
+ Delete a contact item if the delete request is from the same session that started the transaction
+
+ @param aRequest The request that contain the contact item ID that is to be deleted from the database
+ @return TAccept EProcessed if finished processing request, or one of the values returned
+ by CState::DeferRequest
+ @see CState::DeferRequest
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqDeleteCnt* aRequest)
+ {
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ // Check if the contact has been locked by any session - including this session
+ if (iStateMachine.TransactionLockL().IsLocked(aRequest->CntItemId()) == EFalse)
+ {
+ if (iSessionId == aRequest->SessionId())
+ {
+ CContactItem* item = NULL;
+ TRAPD(deleteErr, item = iPersistenceLayer.PersistenceBroker().DeleteLC(aRequest->CntItemId(), aRequest->SessionId(), aRequest->NotificationEventAction());CleanupStack::PopAndDestroy(item));
+ if (deleteErr == KSqlErrGeneral)
+ {
+ // Delete failed, probably due to idle sorter activity
+ return DeferWithTimeOutError(aRequest);
+ }
+ else
+ {
+ User::LeaveIfError(deleteErr);
+ }
+
+ aRequest->Complete();
+ iTimeOut->Reset();
+
+ return EProcessed;
+ }
+ else
+ {
+ // The session that is trying to perform this operation has not started the transaction
+ return DeferWithTimeOutError(aRequest);
+ }
+ }
+ else
+ {
+ // If the request can not be procesed after the timeout period, it should
+ // complete with KErrInUse as the contact is locked
+ aRequest->SetTimeOutError(KErrInUse);
+ }
+
+ return DeferRequest(aRequest);
+ }
+
+
+/**
+ Commit (write & unlock) a contact item that has been locked to the database
+ The contact item is unlocked.
+
+ @param aRequest The request that contain the contact item that is to be written to the database
+ @return TAccept EProcessed if finished processing request
+ EDeferred if the request was not processed
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqCommitCnt* aRequest)
+ {
+ if (iStateMachine.LowDisk())
+ {
+ aRequest->Complete(KErrDiskFull);
+ return EProcessed;
+ }
+
+ if (iSessionId == aRequest->SessionId())
+ {
+ User::LeaveIfError(iStateMachine.TransactionLockL().UnLockL(aRequest->SessionId(), aRequest->Item().Id()));
+ TRAPD(updateErr, iPersistenceLayer.PersistenceBroker().UpdateL(aRequest->Item(), aRequest->SessionId()));
+ if (updateErr == KSqlErrGeneral)
+ {
+ // Can't update contact, probably due to idle sorter activity
+ return DeferWithTimeOutError(aRequest);
+ }
+ else
+ {
+ User::LeaveIfError(updateErr);
+ }
+
+ aRequest->Complete();
+ iTimeOut->Reset();
+
+ return EProcessed;
+ }
+ else
+ {
+ // The session that is trying to perform this operation has not started the transaction
+ return DeferWithTimeOutError(aRequest);
+ }
+ }
+
+/**
+ Open (read and lock) the contact item, returning the opened contact item.
+ The contact item is also locked
+
+ @param aRequest The request that will contain the contact item that is to be opened (read and locked)
+ in the database
+ @return TAccept EProcessed if finished processing request
+ EDeferred if the request was not processed
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqOpenCnt* aRequest)
+ {
+ if (iSessionId == aRequest->SessionId())
+ {
+ // As a valid operation has been performed by the session, it is still
+ // alive so the timeout for the transaction state must be reset.
+ iTimeOut->Reset();
+ return CState::AcceptRequestL(aRequest);
+ }
+ else
+ {
+ // The session that is trying to perform this operation has not started the transaction
+ return DeferWithTimeOutError(aRequest);
+ }
+ }
+
+/**
+ Close (unlock) the locked contact without commiting the contact item to the database
+ only if the session also started the transaction
+
+ @param aRequest The request that contain the contact item that is to be closed (unlocked but not updated)
+ @return One of the values returned by DeferWithTimeOutError, or CState::AcceptRequest(CReqCloseCnt*)
+ @see CState::DeferWithTimeOutError
+ @see CState::AcceptRequest(CReqCloseCnt*)
+*/
+TAccept CStateTransaction::AcceptRequestL(CReqCloseCnt* aRequest)
+ {
+ if (iSessionId == aRequest->SessionId())
+ {
+ iTimeOut->Reset();
+ return CState::AcceptRequestL(aRequest);
+ }
+ else
+ {
+ // The session that is trying to perform this operation has not started the transaction
+ return DeferWithTimeOutError(aRequest);
+ }
+ }
+
+/**
+ Hanlde a database event while in the transaction state.
+ The CStateTransaction holds all events, and does not propagate them to
+ the DbManager, until an explicit commit request is called and the database
+ is written to. All events are not valid until a commit is called as a
+ transaction rollback would invalidate the events.
+
+ @param aEvent The database event that is being propogated.
+*/
+void CStateTransaction::HandleDatabaseEventL(TContactDbObserverEvent aEvent)
+ {
+
+ DEBUG_PRINT1(__VERBOSE_DEBUG__,_L("[CNTMODEL] Database Event in Transaction"));
+
+ // Do not add a rollback event to the queue. This event will be propagated as
+ // part of the transaction rollback.
+ if (aEvent.iType == EContactDbObserverEventRollback)
+ {
+ return;
+ }
+ if (iEventQ.Count() <= KMaxNumberOfEventsInEventQueue)
+ {
+
+ DEBUG_PRINT1(__VERBOSE_DEBUG__,_L("[CNTMODEL] Database Event Added To Q in Transaction"));
+
+ iEventQ.AppendL(aEvent);
+ }
+ // else - do nothing as a EContactDbObserverEventUnknownChanges will be propagated
+ // to all observers
+ }
+
+
+/**
+ Propagate events back to the CCntDbManager after a commit transaction request
+*/
+void CStateTransaction::PropagateDatabaseEventsL()
+ {
+ if (iEventQ.Count() >= KMaxNumberOfEventsInEventQueue)
+ {
+
+ DEBUG_PRINT1(__VERBOSE_DEBUG__,_L("[CNTMODEL] Max Database Events reached in Transaction - Unknown changes event"));
+
+ // Tell the observer to re-sync with the database as there has been too many
+ // operations for which it needs to recieve notification
+ TContactDbObserverEvent unknownChangeEvent;
+ unknownChangeEvent.iType = EContactDbObserverEventUnknownChanges;
+ unknownChangeEvent.iContactId = KNullContactId;
+ unknownChangeEvent.iConnectionId = KCntNullConnectionId;
+
+ iStateMachine.DbManager().HandleDatabaseEventL(unknownChangeEvent);
+ }
+ else
+ {
+
+ DEBUG_PRINT1(__VERBOSE_DEBUG__,_L("[CNTMODEL] Database Events being propagated in Transaction"));
+
+ TInt max = iEventQ.Count();
+ TInt ii = 0;
+ // Propagate the events from the beginning of the EventQ
+ while(ii != max)
+ {
+ iStateMachine.DbManager().HandleDatabaseEventL(iEventQ[ii]);
+ ++ii;
+ }
+ }
+ iEventQ.Reset(); // Empty the queue
+ }
+
+
+
+// CTransactionTimer Implementation //
+
+/**
+ CTransactionTimer Class NewL factory constructor
+ Create a transaction timer
+
+ The transaction timer is used to allow a transaction to timeout should
+ the session which put the state machine into a transaction state die
+ unexpectedly. It is derived from CTimer and times out after sixty seconds.
+*/
+CTransactionTimer* CTransactionTimer::NewL(CStateTransaction& aTransState)
+ {
+ CTransactionTimer* self = new (ELeave) CTransactionTimer(aTransState);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+ }
+
+/**
+ CTransactionTimer Class destructor
+*/
+CTransactionTimer::~CTransactionTimer()
+ {
+ CTimer::Cancel();
+ }
+
+/**
+ CTransactionTimer constructor
+*/
+CTransactionTimer::CTransactionTimer(CStateTransaction& aTransState)
+ : CTimer(CActive::EPriorityIdle), iTransState(aTransState)
+ {
+ }
+
+void CTransactionTimer::ConstructL()
+ {
+ CTimer::ConstructL();
+ CActiveScheduler::Add(this);
+ }
+
+
+/**
+ The Transaction was neither commited nor rolled back
+ The client may have died - clean up the state machine by rolling back
+*/
+void CTransactionTimer::RunL()
+ {
+ iTransState.CancelTransactionL();
+ CTimer::Cancel();
+ }
+
+/**
+ Start the timer. This is done when a the state machine moves
+ into the transaction state.
+*/
+void CTransactionTimer::Start()
+ { // wait for 60 seconds
+ CTimer::Cancel();
+ CTimer::After(KSixtySeconds);
+ }
+
+/**
+ Stop the timer. This is done when a the state machine moves
+ out of the transaction state.
+*/
+void CTransactionTimer::Stop()
+ {
+ CTimer::Cancel();
+ }
+
+/**
+ When a valid operation is performed within the transaction state
+ the timer is reset to sixty seconds, if another transaction operation is not
+ performed within sixty seconds, the transaction should timeout.
+*/
+void CTransactionTimer::Reset()
+ {
+ CTimer::Cancel();
+ CTimer::After(KSixtySeconds);
+ }
+
+// CTransactionLock Class Implementation //
+/**
+ CTransactionLock Class NewL factory constructor
+ The CTransactionLock class locks contacts allowing only the locking session to
+ modify the contact item in the database.
+*/
+CTransactionLock* CTransactionLock::NewL(CCntStateMachine& aStateMachine)
+ {
+ CTransactionLock* self = new (ELeave) CTransactionLock(aStateMachine);
+ return self;
+ }
+
+// TLockData constructor
+CTransactionLock::TLockData::TLockData(TContactItemId aCntId, const TUint aSessionId):iCntItemId(aCntId), iSessionId(aSessionId)
+ {}
+
+// --------- Locking Methods -----------
+/**
+ Locks a contact item by adding its ID to an array of locked contact items IDs.
+ After a session locks the contact item, no other session can modify the locked contact
+ item.
+ Adds the lock to the clean up stack, indicated by the 'X' in the method name
+
+ @param aCntId The ID of contact item to be locked
+ @param aSessionId The Session which is locking the contact Item
+ aContact The contact item to add to the database.
+ @return KErrNone if locking was successful.
+ KErrInUse if the contact item was locked by another session
+*/
+
+TInt CTransactionLock::LockLX(const TUint aSessionId, const TContactItemId aCntId)
+ {
+
+ DEBUG_PRINT2(__VERBOSE_DEBUG__,_L("[CNTMODEL] * Lock item %d"), aCntId);
+
+ if (IsLocked(aCntId))
+ {
+ return KErrInUse; // A session can only lock a cnt item once.
+ }
+
+ TLockData lockData(aCntId, aSessionId);
+
+ iLockedIds.InsertInSignedKeyOrderL(lockData);
+
+ CleanupStack::PushL(TCleanupItem(CTransactionLock::CleanupUnlockRecord, this));
+
+ return KErrNone;
+ }
+
+
+/**
+ Unlocks the last locked contact item after a leave
+
+ @param aTransLock The CTransactionLock object from which the last locked contact item
+ ID must be removed (unlocked).
+*/
+void CTransactionLock::CleanupUnlockRecord(TAny* aTransLock)
+ {
+ TRAP_IGNORE(static_cast<CTransactionLock*>(aTransLock)->UnlockLastLockedContactL() );
+ }
+
+/**
+ UnLocks a contact item by removing its ID to an array of locked contact items IDs.
+
+ @param aCntId The ID of contact item to be unlocked
+ @param aSessionId The Session which is unlocking the contact Item
+ @return KErrNone if locking was successful.
+ KErrAccessDenied if the contact item was not successfuly locked
+*/
+TInt CTransactionLock::UnLockL(const TUint aSessionId, const TContactItemId aCntId)
+ {
+
+ DEBUG_PRINT2(__VERBOSE_DEBUG__,_L("[CNTMODEL] * UnLock item %d"), aCntId);
+
+ TLockData lockData(aCntId, aSessionId);
+ TInt index = iLockedIds.FindInSignedKeyOrder(lockData);
+ if (index < 0)
+ return KErrAccessDenied;
+
+ if (index > iLockedIds.Count())
+ {
+ return KErrAccessDenied;
+ }
+
+ if (iLockedIds[index].iSessionId == aSessionId)
+ {
+ iLockedIds.Remove(index);
+ ProcessLockedContactsL(); // Process any requests in the Store
+ }
+ return KErrNone;
+ }
+
+/**
+ Process any requests in the Store - Another session
+ may have been trying to perform an operation on a
+ locked contact item. This method is called after a contact item
+ has been unlocked to allow an operation on the same contact item
+ to be performed by another session.
+*/
+void CTransactionLock::ProcessLockedContactsL()
+ {
+ if(iStateMachine.ReqStoreL().IsEmpty() == EFalse)
+ {
+ iStateMachine.ReqStoreL().ActivateRequestsL();
+ }
+ }
+
+/**
+ Unlocks all the locked contacts for a given sessionid, the request
+ that calls this method, CReqInternalSessionUnlock, originates in the session destructor
+
+ @param aSessionId The session that is being closed.
+*/
+void CTransactionLock::UnLockAllL(const TUint aSessionId)
+ {
+ TInt ii = iLockedIds.Count();
+ while(ii)
+ {
+ --ii;
+ if (iLockedIds[ii].iSessionId == aSessionId)
+ {
+ iLockedIds.Remove(ii);
+ }
+ }
+ ProcessLockedContactsL(); // Process any requests in the Store
+ }
+
+/**
+ Unlock the last locked contact after an leave has occured
+
+ @param aSessionId The ID of the session that performed the operation in which
+ the leave occured.
+*/
+void CTransactionLock::UnlockLastLockedContactL(TUint aSessionId)
+ {
+ if (aSessionId == nsState::KNoSessionId)
+ {
+ // Remove the last Locked Contact regardless of session
+ iLockedIds.Remove(iLockedIds.Count() - 1);
+ ProcessLockedContactsL(); // Process any requests in the Store
+ return;
+ }
+
+ TInt ii = iLockedIds.Count();
+ while(ii)
+ {
+ --ii;
+ if (iLockedIds[ii].iSessionId == aSessionId)
+ {
+ iLockedIds.Remove(ii);
+ ProcessLockedContactsL(); // Process any requests in the Store
+ return; // Finished
+ }
+ }
+ }
+
+/**
+Checks if a contact item is locked by another session.
+
+@param aCntId The ID of contact item to be checked
+@param aSessionId The Session which is checking for a lock
+
+@return True if the contact has been locked.
+ False if the contact has not been locked by another session.
+*/
+TBool CTransactionLock::IsLocked(const TUint aSessionId, const TContactItemId aCntId) const
+ {
+ TInt ii = iLockedIds.Count();
+
+ while(ii)
+ {
+ --ii;
+ if (iLockedIds[ii].iCntItemId == aCntId)
+ {
+ if (iLockedIds[ii].iSessionId != aSessionId)
+ {
+ return ETrue; // locked by another session
+ }
+ return EFalse; // has not been locked by another session
+ }
+ }
+ return EFalse; // has not been locked by any session
+ }
+
+/**
+ Checks if a contact item is locked by this or another session (any session).
+ Original behaviour of the contacts model was not to allow a session to lock a contact twice.
+
+ @param aCntId The ID of contact item to be checked
+ @return True if the contact has been locked.
+ False if the contact has not been locked.
+*/
+TBool CTransactionLock::IsLocked(const TContactItemId aCntId)const
+ {
+ TInt ii = iLockedIds.Count();
+
+ while(ii)
+ {
+ --ii;
+ if (iLockedIds[ii].iCntItemId == aCntId)
+ {
+ return ETrue; // locked
+ }
+ }
+ return EFalse;
+ }
+
+/**
+ Determines if any contacts items are locked by this or another session (any
+ session).
+ @return True if there is a locked contact.
+ False if there is no locked contact.
+*/
+TBool CTransactionLock::AnyLocked() const
+ {
+ return iLockedIds.Count() != 0;
+ }
+
+/** CStateBackupRestore
+ While in this state neither read nor write operations are allowed. The
+ CStateClosed parent class AcceptRequestL() handles all read and write
+ requests.
+
+ Methods completes the request with KErrLocked via a call to TimeOutErrorCode()
+ from the parent CStateClosed class.
+*/
+CStateBackupRestore* CStateBackupRestore::NewL(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+ {
+ CStateBackupRestore* stateBUR = new (ELeave) CStateBackupRestore(aStateMachine, aPersistenceLayer);
+ return stateBUR;
+ }
+
+
+CStateBackupRestore::~CStateBackupRestore()
+ {
+ }
+
+
+CStateBackupRestore::CStateBackupRestore(CCntStateMachine& aStateMachine, CPersistenceLayer& aPersistenceLayer)
+:CStateClosed(aStateMachine, aPersistenceLayer)
+ {
+ }
+
+
+// Default AsyncOpen requests behaviour to the base implementation
+TAccept CStateBackupRestore::AcceptRequestL(CReqAsyncOpen* aRequest)
+ {
+ return CState::AcceptRequestL(aRequest);
+ }
+
+
+TAccept CStateBackupRestore::AcceptRequestL(CReqBackupRestoreBegin* aRequest)
+ {
+ // A backup/restore is already in progress so don't defer this request -
+ // simply complete it.
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+
+TAccept CStateBackupRestore::AcceptRequestL(CReqBackupRestoreEnd* aRequest)
+ {
+ // Once Backup/Restore completes we can re-open the database. Re-accept
+ // request in CStateOpening.
+ iStateMachine.SetCurrentStateL(iStateMachine.StateOpening());
+ return iStateMachine.CurrentState().AcceptRequestL(aRequest);
+ }
+
+
+/**
+ If there was asynchronous activity then it has now finished so safe to close
+ database.
+
+ @param aRequest Notification of end of async activity request object
+ @return TAccept EProcessed - finished processing request
+*/
+TAccept CStateBackupRestore::AcceptRequestL(CReqNoAsyncActivity* aRequest)
+ {
+ if (iStateMachine.AsyncActivity())
+ {
+ iStateMachine.SetAsyncActivity(EFalse);
+ // Close the file to allow the backup/restore to take place.
+ iPersistenceLayer.ContactsFileL().Close();
+ }
+ aRequest->Complete();
+ return EProcessed;
+ }
+
+/**
+ Returns the default error code - KErrLocked - for the backup restore state
+*/
+TInt CStateBackupRestore::TimeOutErrorCode()
+ {
+ return KErrLocked;
+ }
+
+
+// ======================================
+// CCntStateMachine Class implementation
+// The main purpose of the CState object
+// is to define the state transition table
+
+CCntStateMachine::~CCntStateMachine()
+ {
+ iStateArray.ResetAndDestroy();
+ delete iReqStore;
+ delete iTransLock;
+ }
+
+CCntStateMachine* CCntStateMachine::NewL(CPersistenceLayer& aPersistenceLayer, CCntDbManager& aDbManager)
+ {
+ CCntStateMachine* stateMachine = new (ELeave) CCntStateMachine(aDbManager);
+ CleanupStack::PushL(stateMachine);
+ stateMachine->ConstructL(aPersistenceLayer);
+ CleanupStack::Pop(stateMachine);
+ return stateMachine;
+ }
+
+/**
+ Create and add all states to the state machine array
+
+ @param aPersistenceLayer. The persistence layer that wraps up access to the database.
+*/
+void CCntStateMachine::ConstructL(CPersistenceLayer& aPersistenceLayer)
+ {
+ iState = CStateClosed::NewL(*this, aPersistenceLayer);
+ CleanupStack::PushL(iState);
+ // The order in which states are appended must be in sync with
+ // the declaration order of the state enum.
+ iStateArray.AppendL(iState);
+ CleanupStack::Pop(iState);
+
+ iStateArray.AppendL(CStateTablesClosed::NewL(*this, aPersistenceLayer));
+ iStateArray.AppendL(CStateWritable ::NewL(*this, aPersistenceLayer));
+ iStateArray.AppendL(CStateOpening ::NewL(*this, aPersistenceLayer));
+ iStateArray.AppendL(CStateTransaction ::NewL(*this, aPersistenceLayer));
+ iStateArray.AppendL(CStateBackupRestore::NewL(*this, aPersistenceLayer));
+ }
+
+/**
+ Get the current active state
+
+ @return The current active state
+*/
+CState& CCntStateMachine::CurrentState()
+ {
+ return *iState;
+ }
+
+/**
+ Get the transaction lock
+
+ @return The Transaction Lock object
+*/
+CTransactionLock& CCntStateMachine::TransactionLockL()
+ {
+ if (!iTransLock)
+ {
+ iTransLock = CTransactionLock::NewL(*this);
+ }
+
+ return *iTransLock;
+ }
+
+/**
+ Get the Database Manager
+
+ @return the Contact Database Manager object
+*/
+CCntDbManager& CCntStateMachine::DbManager()
+ {
+ return iDbManager;
+ }
+
+/**
+ StateMachine constructor
+
+ @param aDbManager The Database Manager.
+*/
+CCntStateMachine::CCntStateMachine(CCntDbManager& aDbManager)
+ :
+ iDbManager(aDbManager),
+ iLowDisk(EFalse),
+ iAsyncActivity(EFalse)
+ {
+ // Nothing to do.
+ }
+
+/**
+ Used for debugging the transition between state.
+ Define __STATE_MACHINE_DEBUG__ in the project definition file (mmp).
+
+ @param aState The state which is becoming active
+ @return A descriptor containing the active state name.
+*/
+#ifdef __STATE_MACHINE_DEBUG__
+const TDesC& CCntStateMachine::StateName(CState& aState)
+ {
+ _LIT(KStateClosedName, "EStateClosed");
+ _LIT(KStateTablesClosed, "EStateTablesClosed");
+ _LIT(KStateWritableName, "EStateWritable");
+ _LIT(KStateOpeningName, "EStateOpening");
+ _LIT(KStateTransactionName, "EStateTransaction");
+ _LIT(KStateBackupRestoreName, "EStateBackupRestore");
+ _LIT(KStateUnknownName, "Unknown State");
+
+ if (&aState == &StateClosed())
+ {
+ return KStateClosedName();
+ }
+
+ if (&aState == &StateTablesClosed())
+ {
+ return KStateTablesClosed();
+ }
+
+ if (&aState == &StateWritable())
+ {
+ return KStateWritableName();
+ }
+
+ if (&aState == &StateOpening())
+ {
+ return KStateOpeningName();
+ }
+
+ if (&aState == &StateTransaction())
+ {
+ return KStateTransactionName();
+ }
+
+ if (&aState == &StateBackupRestore())
+ {
+ return KStateBackupRestoreName();
+ }
+
+ return KStateUnknownName();
+ }
+#endif
+
+/**
+ Set the active state in the state machine
+
+ @param aState The state which is becoming active
+*/
+void CCntStateMachine::SetCurrentStateL(CState& aState)
+ {
+
+#ifdef __STATE_MACHINE_DEBUG__
+ RDebug::Print(_L("[CNTMODEL] STA: %S --> %S\r\n"), &CCntStateMachine::StateName(*iState), &CCntStateMachine::StateName(aState));
+#endif
+
+ iState = &aState;
+ // Process any requests in the Store on each state change
+ // The state may have changed to one where queued requests
+ // can now be processed.
+ if(ReqStoreL().IsEmpty() == EFalse)
+ {
+ iReqStore->ActivateRequestsL();
+ }
+ }
+
+/**
+ This is the interface to the state machine (used by the session class).
+ The state machine is asked to process a request by the session. It does this
+ by passing the current active state to the request object (VisitStateL).
+ The request then calls AcceptRequest on the current active state object. The
+ state and the request are completely decoupled.
+
+ Caller of ProcessRequestL should assume transfer request ownership unless
+ ProcessRequestL leaves. In the event ProcessRequestL leaves, the original owner
+ of the request is responsible for the cleanup of the request.
+
+ Note that if the request is to be transferred to another object, ensure once the
+ transfer has taken place, no leave should occur, as this will trigger both the new
+ owner and the original owner to cleanup the request.
+
+ @param aRequest A request that is being processed.
+*/
+void CCntStateMachine::ProcessRequestL(CCntRequest* aRequest)
+ {
+ // Obtained ownership of the request object. It is the responsibility
+ // of this function to ensure the request is properly disposed after being
+ // processed unless leave occurs.
+ TAccept result = aRequest->VisitStateL(*iState);
+
+ switch(result)
+ {
+ case EDeferred:
+ // The visited state cannot process the request at the moment. Activate the
+ // request's timer and transfer ownership of the request to the request store.
+ // Hopefully, a state change happens before timeout occurs, when the new state
+ // will attempt to process all deferred request. If timeout occurs before
+ // a change of state, the request will be completed with its' timeout error code.
+ // This timeout error code can be set thru the CCntRequest API SetTimeOutError.
+ aRequest->ActivateTimeOutL(ReqStoreL());
+ ReqStoreL().AppendL(aRequest); // ownership transferred
+ break;
+
+ case EProcessed:
+ // The request has been processed by the visited state - nothing more
+ // to do, except to destroy the request now
+ delete aRequest;
+ break;
+
+ case EOwnershipPassed:
+ // the request has been hi-jacked by the visited state, the new owner will
+ // assume responsibility of cleanup, no need to do anything here.
+ default:
+ break;
+ }
+ }
+
+
+/**
+ Allow the active state to process the event. The only state that actually
+ processes events is the Transaction State. All other states (via the base state CState)
+ simply propagate the event back to the CCntDbManager, from where it is propogated to
+ each CCntSession contained within the CCntDbManager.
+
+ @param aEvent The database event that is being propagated.
+*/
+void CCntStateMachine::HandleDatabaseEventL(TContactDbObserverEvent aEvent)
+ {
+ #if defined(__PROFILE_DEBUG__)
+ RDebug::Print(_L("[CNTMODEL] MTD: CCntStateMachine::HandleDatabaseEventL"));
+ #endif
+
+ iState->HandleDatabaseEventL(aEvent);
+ }
+
+/**
+ Create the state machines request store. When a request cannot be processed in
+ the active state, it is deferred until the active state changes or a contact item is unlocked.
+ That is the request in this ReqStore is processed by the state machine - again - after either
+ a state change or unlock operation.
+ The store takes a reference to the CCntStateMachine in order to ProcessRequestL
+
+ @return The Request Store
+*/
+CRequestStore& CCntStateMachine::ReqStoreL()
+ {
+ if (!iReqStore)
+ {
+ iReqStore = CRequestStore::NewL(*this);
+ }
+ return *iReqStore;
+ }