/*
* Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
* Contact: http://www.qt-project.org/legal
* 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
 @internalComponent
 @released
*/

#include <s32mem.h>
#include <cntitem.h>
#include <cntdb.h>
#include <cntfilt.h>

#include "rcntmodel.h"
#include "persistencelayer.h" // for mlplcollection.
#include "ccntnotificationmonitor.h"
#include "ccontactprivate.h" // for mprogresseventhander.
#include "ccntpackager.h"
#include <cntviewstore.h>
#include <cntfield.h>
#include "cntstd.h"
#include <cntdef.h>
#include <cntfldst.h>
#include "cntimagerescaler.h"
#include "cntdbconsts_internal.h"


/** Contacts server version number. */ 
const TInt KCntServerMajorVersionNumber=1;
const TInt KCntServerMinorVersionNumber=1;
const TInt KCntServerBuildVersionNumber=1;

/** Number of attempts to try restart the server after premature termination. */
const TInt KMaxTimesToRestartServer = 3;

/** Maximum number of asynchronous IPC calls. */
const TInt KAsyncMessageSlots=6;


/**
Unlock the last locked contact item on the server.  Used by OpenLX().  If any
method which locks a contact item leaves thenccnt this method is called as the
cleanup stack unwinds, removing the lock for the last locked contact item.

@capability None
*/
void CleanupUnlockRecord(TAny *aSession)
	{
	static_cast<RCntModel*>(aSession)->UnlockLastLockedContact();
	}


/**
Unlock the last locked contact item on the server.  Used by OpenLX().

@capability None
*/
void RCntModel::UnlockLastLockedContact()
	{
	CloseContact(KNullContactId);
	}


/**
Push a Contact item unlock cleanup item on the cleanup stack.
*/
void RCntModel::PushUnlockL() const
	{
	CleanupStack::PushL( TCleanupItem(CleanupUnlockRecord, const_cast<RCntModel *>(this)));
	}


/**
RCntModel constructor.

Member variables must be initialised (zero'd) in the constructor since
RCntModel does not derive from CBase.
*/
RCntModel::RCntModel()
	:
	iDbNotifyMonitor(NULL), 
	iPackager(NULL),
	iConnectionId(0),
	iNoOfSvrStartAttempts(0),
    iRescaler(NULL),
    iServerRunning(EFalse)
	{
	}


/**
Get the Contacts server version number.

@return Contacts server version number.
*/
TVersion RCntModel::Version() const
	{
	return(TVersion(KCntServerMajorVersionNumber,KCntServerMinorVersionNumber,KCntServerBuildVersionNumber));
	}


/** Name of the executable for the Contacts server. */
_LIT(KCntServerExe,"CNTSRV.EXE");
/** Name used to connect a session to the Contacts server. */
_LIT(KCntServerName,"CNTSRV");


/**
Open a Contacts server session.
*/
void RCntModel::ConnectL()
	{
    iServerRunning = EFalse;
	// Assume the server is already running and attempt to create a session
	// with a maximum of KAsyncMessageSlots message slots.
	TInt err = CreateSession(KCntServerName,Version(),KAsyncMessageSlots);
	
	if(err == KErrNotFound) // Server not running?
		{
		// Use the RProcess API to start the server.
		RProcess server;
		User::LeaveIfError(server.Create(KCntServerExe,KNullDesC));
		
		//Enforce server to be at system default priority EPriorityForeground
		//Contact server used to set as EPriorityHigh and this caused client could
		//not get control or responsive until sorting by the idle sorter in remote 
		//view was done.
		server.SetPriority(EPriorityForeground);
		
		// Synchronise with the server.
		TRequestStatus reqStatus;
		server.Rendezvous(reqStatus);
		server.Resume();
		
		// Server will call the reciprocal static synchronisation call.
		User::WaitForRequest(reqStatus);
		server.Close();
		User::LeaveIfError(reqStatus.Int());
		
		// Create the server session.
		User::LeaveIfError(CreateSession(KCntServerName,Version(),KAsyncMessageSlots));
		}
	else
		{
		User::LeaveIfError(err);
		}
	
	// Connection successful
	iServerRunning = ETrue;

	// Create object packer/unpacker if it doesn't already exist.
	if (iPackager == NULL)	
		{
		iPackager = CCntPackager::NewL();
		}
	
	// Each session (client) will be given a unique ID known as the Connection
	// ID.  This ID forms part of the database event notification message.  This
	// ID is created during the connection to the server.
	ConnectionId();
    
    // Create a image rescaler
	if (iRescaler == NULL)
	    {
	    iRescaler = CImageRescaler::NewL();
	    }
	}

	
/**
Close session.
*/
void RCntModel::Close()
	{
	delete iPackager;
	iPackager = NULL;
	delete iDbNotifyMonitor;
	iDbNotifyMonitor = NULL;
	delete iRescaler;
	iRescaler = NULL;
	RHandleBase::Close();
	}


/**
Open a named contact database.

Opens the default contacts database if the default argument is used.  The given
descriptor must not contain more than KCntMaxFilePath characters.

@param aCntFile Filename (in the form drive:database).  Defaults to
KCntDefaultDrive.

@return KErrNone if success, KErrArgument if the given descriptor contains more than the 
        maximum length of 190 characters, otherwise one of the System error codes.

@capability ReadUserData
*/
TInt RCntModel::OpenDatabase(const TDesC& aCntFile) const
    {
    if (!iServerRunning )
        {
        return KErrServerTerminated;
        }
    TInt err = SetFileName(aCntFile);
    if (err == KErrNone)
        {
        TIpcArgs args;
        args.Set(0,&aCntFile);
        err = SendReceive(ECntOpenDataBase, args);
        }

    return err;
    }


/**
Open a named contact database asynchronously.

Opens the default contacts database if the default argument is used.  The given
descriptor must not contain more than KCntMaxFilePath characters.

@param aStatus Asynchronous request object.  Request is completed when database
has been opened.
@param aCntFile Contacts database filename (in the form drive:database).
Defaults to KCntDefaultDrive.

@capability ReadUserData
*/
void RCntModel::OpenDatabaseAsyncL(TRequestStatus& aStatus, const TDesC& aCntFile)
    {
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    User::LeaveIfError(SetFileName(aCntFile));

    TIpcArgs args;
    args.Set(0,&aCntFile);
    SendReceive(ECntOpenDataBase, args, aStatus);
    }


/**
Cancel last asynchronous database open request.

@capability None
*/
void RCntModel::CancelAsyncOpen() 
	{
    if (!iServerRunning)
        {
        return;
        }
	SendReceive(ECntCancelAsyncOpenDatabase);		
	}


/**
Handle a premature termination of the contact server process by re-connecting
the session and re-opening the database.

@capability ReadUserData
*/
void RCntModel::HandlePrematureServerTerminationL()
	{
	if (iNoOfSvrStartAttempts > KMaxTimesToRestartServer)
		{
		User::Leave(KErrServerTerminated);
		}
	ConnectL();
	User::LeaveIfError(OpenDatabase(iFileName));
	++iNoOfSvrStartAttempts;
	}

	
/**
Set the database filename. Used to re-open the database if the server is
terminated prematurely.

@param aCntFile Contacts database filename (in the form drive:database).
@return KErrNone if success, KErrArgument if the given descriptor contains more than the 
        maximum length of 190 characters.         
*/
TInt RCntModel::SetFileName(const TDesC& aCntFile) const
    {
    if(aCntFile.Length() > KCntMaxFilePath)
        {
        return KErrArgument;
        }

    iFileName = aCntFile;
    return KErrNone;
    }


/**
Close currently open database.

@capability None
*/
void RCntModel::CloseDatabase() const
	{
    if (!iServerRunning)
        {
        return;
        }
	(void)SendReceive(ECntCloseDataBase);
	}


/**
Get the machine ID. 

Note: This function can leave.

@return Machine ID.

@leave KErrNone The send operation is successful
@leave KErrServerTerminated The server no longer present
@leave KErrServerBusy There are no message slots available
@leave KErrNoMemory There is insufficient memory available


@capability None
*/
TInt64 RCntModel::MachineId() const
	{
    if (!iServerRunning)
        {
        return KErrServerTerminated;
        }
	TIpcArgs args;
	TPckgBuf<TInt64> machineID;
	args.Set(0, &machineID);
	User::LeaveIfError(SendReceive(ECntMachineID, args)); // this can leave
	return machineID();	
	}


/**
Set the machine ID (debug only).

@param aMachineUniqueId New machine ID.

@capability None
*/
void RCntModel::OverrideMachineUniqueId(TInt64 aMachineUniqueId)
	{
    if (!iServerRunning)
        {
        return;
        }
	TIpcArgs args;
	TPckgBuf<TInt64> machineID(aMachineUniqueId);
	args.Set(0, &machineID);
	(void)SendReceive(ECntOverrideMachineID, args);	
	}


/**
Replace a named contact database with an empty one, replacing any database with
the same name.  Replaces the default contacts database if the default argument
is used.  The given descriptor must not contain more than KCntMaxFilePath
characters.

@param aCntFile Contacts database filename (in the form drive:database).
Defaults to KCntDefaultDrive.

@return KErrNone if success, KErrArgument if the given descriptor contains more than the 
        maximum length of 190 characters, KErrInUse if the database is currently open, 
        otherwise one of the System error codes.

@capability WriteUserData
*/
TInt RCntModel::ReplaceDatabase(const TDesC& aCntFile) const
    {
    if (!iServerRunning)
        {
        return KErrServerTerminated;
        }
    TInt err = SetFileName(aCntFile);
    if (err == KErrNone)
        {
        TIpcArgs args;
        args.Set(0,&aCntFile);

        err = SendReceive(ECntReplaceDatabase, args);
        }

    return err;
    }


/**
Returns an array of contact item IDs for all the contact items which may contain
the specified telephone number in a telephone, fax or SMS type field.

@see CContactDatabase::MatchPhoneNumberL() for more details on the match.

@param aNumber Phone number string
@param aMatchLengthFromRight Number of digits from the right of the phone number
to use.  Up to 15 digits can be specified and it is recommended that at least 7
match digits are specified.

@return CContactIdArray of candidate matches.

@capability ReadUserData
*/
CContactIdArray* RCntModel::MatchPhoneNumberL(const TDesC& aNumber, const TInt aMatchLengthFromRight)
	{
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0, &iPackager->GetReceivingBufferL());
	args.Set(1, MLplCollection::EMatchPhoneNos);
	args.Set(2, &aNumber);
	args.Set(3, aMatchLengthFromRight);
	
	TInt newBuffSize = 0;
	User::LeaveIfError(newBuffSize = SendReceive(ECntGetCollection, args));
	
	if (newBuffSize > 0)
		{
		// If the buffer is not large enough resize the packager's internal
		// buffer and make the call again.
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntGetCollection, args));		
		}
		
	CContactIdArray* idArray = iPackager->UnpackCntIdArrayLC();	
	CleanupStack::Pop(idArray);

	return idArray;
	}
	
	

/**
Create an empty named contact database.

Creates the default contacts database if the default argument is used.  The
given descriptor must not contain more than KCntMaxFilePath characters.

@param aCntFile Contacts database filename (in the form drive:database).
Defaults to KCntDefaultDrive.

@return KErrNone if success, KErrArgument if the given descriptor contains more than the 
        maximum length of 190 characters, KErrAlreadyExists if the database already 
        exists otherwise one of the System error codes.

@capability WriteUserData
*/
TInt RCntModel::CreateDatabase(const TDesC& aCntFile) const
    {
    if (!iServerRunning)
        {
        return KErrServerTerminated;
        }
    
    TInt err = SetFileName(aCntFile);
    if (err == KErrNone)
        {
        TIpcArgs args;
        args.Set(0,&aCntFile);
        err = SendReceive(ECntCreateDatabase, args);
        }
    return err;
    }


/**
Create the system template.

@return KErrNone if success otherwise one of the System error codes.
	
@capability WriteUserData
*/
TInt RCntModel::ReCreateTemplate() const
	{
    if (!iServerRunning)
        {
        return KErrServerTerminated;
        }
	return SendReceive(ECntReCreateTemplate);
	}


/**
Get the database file UID.

@return Pointer to file UID.

@capability None
*/
TPtrC RCntModel::FileUidL() const
	{
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0, &iFileUid);
	User::LeaveIfError (SendReceive(ECntFileUniqueId, args));
    return iFileUid;
	}


/**
Determine if the database is ready.  Ready in this context means that the
database is open and readable/writable (i.e. the state machine is in
CStateWritable).

@capability None

@return ETrue if the database is ready, EFalse if the database is not ready.
*/
TBool RCntModel::DatabaseReadyL() const
    {
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    TBool retVal = EFalse;

    // Although the ECntGetDatabaseReady msg. is completed with TBool values,
    // during message processing, it is possible that ServiceError() occurs,
    // causing the message to be completed with negative error codes.
    // LeaveIfError() would protect against such cases.
    User::LeaveIfError(retVal = SendReceive(ECntGetDatabaseReady));

    return retVal;
    }


/**
Tests whether a contact item's hint bit field matches a filter.

@param aBitWiseFilter The filter to compare the item against.  This is a
combination of TContactViewFilter values.
@param aContactId The ID of the item in the database.

@return ETrue if the item is of the correct type for inclusion in the database 
and its hint bit field matches the specified filter, EFalse if either of these
conditions are not met.

@capability None
*/
TBool RCntModel::ContactMatchesHintFieldL(TInt aBitWiseFilter, TContactItemId aContactId)
    {
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
    TIpcArgs args;
    args.Set(0, aBitWiseFilter);
    args.Set(1, aContactId);
    TBool retVal = EFalse;

    // Although the ECntMatchesHintField msg. is completed with TBool values,
    // during message processing, it is possible that ServiceError() occurs,
    // causing the message to be completed with negative error codes.
    // LeaveIfError() would protect against such cases.
    User::LeaveIfError (retVal = SendReceive(ECntMatchesHintField, args));

    return retVal;
    }


/**
Delete a named contact database.

Deletes the default contacts database if the default argument is used.  The
given descriptor must not contain more than KCntMaxFilePath characters.

@param aCntFile Contacts database filename (in the form drive:database).
Defaults to KCntDefaultDrive.

@return KErrNone if success, KErrArgument if the given descriptor contains more 
        than the maximum length of 190 characters, KErrInUse if the database is 
        in use, KErrNotFound if the database does not exist otherwise one of the 
        System error codes.

@capability WriteUserData
*/
TInt RCntModel::DeleteDatabase(const TDesC& aCntFile) const
	{
    if (!iServerRunning)
        {
        return KErrServerTerminated;
        }
    
	if(aCntFile.Length() > KCntMaxFilePath)
		{
		return KErrArgument;
		}
	TIpcArgs args;
	args.Set(0,&aCntFile);
	return SendReceive(ECntDeleteDatabase,args);
	}


/**
Get the name of the default contact database file.

The given descriptor must have room for minimum of KCntMaxFilePath characters.

@param aCntFile Contains the default contact database name on return.

@return KErrNone if success, KErrArgument if the given descriptor contains more 
        than the maximum length of 190 characters, otherwise one of the System 
        error codes.

@capability None
*/
TInt RCntModel::DefaultDatabase(TDes& aCntFile) const
	{
    if (!iServerRunning)
        {
        return KErrServerTerminated;
        }
    
	TIpcArgs args;
	args.Set(0,&aCntFile);
	return SendReceive(ECntGetDefaultDatabaseName,args);
	}


/**
Retrieve the current contact database drive.

@param aDriveUnit Contains the current contact database drive unit on return.

@return KErrNone if success otherwise one of the System error codes.

@capability None
*/
TInt RCntModel::DatabaseDrive(TDriveUnit& aDriveUnit) const
	{
    if (!iServerRunning)
        {
        return KErrServerTerminated;
        }
    
	TInt ret = SendReceive(ECntDatabaseDrive);
	if (ret >= KErrNone)
		{
		aDriveUnit = ret;	
		}
	return ret;
	}


/**
Sets the contact database drive and optionally moves the default contact
database from its current location to the new drive.  This function guarantees
an all or nothing operation.  If the database is not successfully moved, the
drive setting is not updated to reflect the change.

@param aDriveUnit The drive to which to move the contact database.
@param aCopy ETrue moves the existing file to the specified drive.  Deletion of
the source file will fail if it is in use.  EFalse does not move the file.  The
default argument value is ETrue.

@return KErrNone if success, KErrInUse if destination file for copy is open,
KErrNotFound found if source contact database (default) cannot be found,
KErrAlreadyExists if the destination contact database file exists.

@capability WriteUserData
*/
void RCntModel::SetDatabaseDriveL(TDriveUnit aDriveUnit, TBool aCopy)
	{
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0,aDriveUnit);
	args.Set(1,aCopy);
	User::LeaveIfError(SendReceive(ECntSetDatabaseDrive,args));
	}

/** 
Gets the size of the database file in bytes.

@return The size of the contact database in bytes.

@capability None
*/
TInt RCntModel::FileSize() const
	{
    if (!iServerRunning )
        {
        return KErrServerTerminated;
        }
	return SendReceive(ECntFilesSize);	
	}

/**
Determine if the named contact database file exists.

If the aCntFile argument is the default, determines if default contact database
file exists.

@param aCntFile Contacts database filename (in the form drive:database).
@leave KErrArgument if the given descriptor contains more than the maximum length 
       of 190 characters, otherwise one of the System error codes.
@return ETrue if exists, EFalse does not exist. 
@capability None
*/
TBool RCntModel::DatabaseExistsL(const TDesC& aCntFile) const
    {
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
    if(aCntFile.Length() > KCntMaxFilePath)
        {
        User::Leave(KErrArgument);
        }

    TIpcArgs args;
    args.Set(0,&aCntFile);
    TBool ret = EFalse;

    // Although ECntDatabaseExists is normally completed with TBool values,
    // ServiceError() can occur which leads to a negative system error codes
    // being returned.  User::LeaveIfError will allow that to be properly handled.
    User::LeaveIfError(ret = SendReceive(ECntDatabaseExists,args));

    return ret;
    }


/**
List the contact database files for any given drive unit.  Lists all databases
on all drives if aDriveUnit is set to NULL.

@param aDriveUnit The drive unit to search.  Searches all drives if default
argument is used.

@return CDesCArray containing the database list.  Always returns list even if
empty.

@leave KErrNoMemory if not enough free memory, KErrNotReady if drive does not,
otherwise one of the System error codes.

@capability ReadUserData
*/
CDesCArray* RCntModel::ListDatabasesL(TDriveUnit* aDriveUnit) const
	{
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	// Convert the drive unit to an integer for IPC.  The TDriveUnit constructor
	// does not allow values outside 0 - 25.
	TInt driveNumber;
	(aDriveUnit == NULL) ? (driveNumber = ECntAllDrives) : (driveNumber = *aDriveUnit);

	// Provide a CBufFlat for receiving the list of databases from the server.
	// Format is a (proprietary) serialized CDesCArray.
	const TInt KInitialBufferSize = 200;
	CBufFlat* listBuffer = CBufFlat::NewL(KInitialBufferSize);
	CleanupStack::PushL(listBuffer);
	listBuffer->ExpandL(0,KInitialBufferSize);

	// IPC argument list is:
	// 0 (Return param)	- Address of our receive buffer
	// 1 (Param)		- The drive number 0 - 25 or ECntAllDrives
	// 2 (Return param)	- The size of buffer required for the transfer
	TIpcArgs args;
	TPtr8 ptr(listBuffer->Ptr(0));
	args.Set(0,&ptr);
	args.Set(1,driveNumber);
	TPckgBuf<TInt> size;
	args.Set(2,&size);
	User::LeaveIfError(SendReceive(ECntListDatabases,args));

	// Check if our buffer was large enough by reading returned size argument.
	// The server will not have written any data if our povided buffer was not
	// big enough.
	if(size() > KInitialBufferSize)
		{
		// Not big enough but now we know how big the buffer needs to be.
		// Just allocate buffer again, reset the IPC args and call again.
		CleanupStack::PopAndDestroy(listBuffer);
		listBuffer = CBufFlat::NewL(size());
		CleanupStack::PushL(listBuffer);
		listBuffer->ExpandL(0,size());
		ptr.Set(listBuffer->Ptr(0));
		args.Set(0,&ptr);
		args.Set(1,driveNumber);
		args.Set(2,&size);
		User::LeaveIfError(SendReceive(ECntListDatabases,args));
		}

	// Wrap the buffer in a read stream class to internalize.
	RBufReadStream readStream;
	readStream.Open(*listBuffer);
	CleanupClosePushL(readStream);

	// Number of array elements.
	const TInt count = readStream.ReadUint32L();
	// Use count if it is not zero.
	CDesCArray* list = new(ELeave) CDesCArrayFlat(count ? count : 1);
	CleanupStack::PushL(list);
	for (TInt i=0; i<count; ++i)
		{
		TFileName path;
		TInt length=readStream.ReadInt8L();
		readStream.ReadL(path,length);
		// Add to the list.
		list->AppendL(path);
		}

	CleanupStack::Pop(list);
	CleanupStack::PopAndDestroy(&readStream);
	CleanupStack::PopAndDestroy(listBuffer);
	
	return list;
	}


/**
Add a new contact to the database.

@param aContact The contact to be added to the database.

@capability WriteUserData
*/
TContactItemId RCntModel::CreateContactL(CContactItem& aContact) const
	{
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
    // Process the image field
    if (iRescaler)
        {
        iRescaler->ProcessImageFieldL(aContact);
        }
    
	// Pack the contact into the first IPC argument.
	TIpcArgs args;
	TPtr8 ptr(iPackager->PackL(aContact));
	args.Set(0,&ptr);

	TInt cntId = 0;
	User::LeaveIfError(cntId = SendReceive(ECntItemCreate, args));

	return cntId;
	}


/**
Delete a contact from the database.

@param aCntId The contact to be deleted.
@param aCntEventType The event type to pass on to the observers.
@param aDecAccessCount If ETrue access count should be decremented prior to the
deletion.

@capability WriteUserData
*/
void RCntModel::DeleteContactL(TContactItemId aCntId, TCntSendEventAction aCntEventType, TBool aDecAccessCount) const
	{
    if (!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args(aCntId, aCntEventType, aDecAccessCount);
	User::LeaveIfError(SendReceive(ECntItemDelete, args));
	}

/**
Delete a contact from the database.

@param aCntId The contact to be deleted.
@param aCntEventType The event type to pass on to the observers.
@param aDecAccessCount If ETrue access count should be decremented prior to the
deletion.

@return KErrNone if the function completed successfully, otherwise one of the 
standard error codes.

@capability WriteUserData
*/
TInt RCntModel::DeleteContact(TContactItemId aCntId, TCntSendEventAction aCntEventType, TBool aDecAccessCount) const
	{
    if (!iServerRunning)
        {
        return KErrServerTerminated;
        }
	TIpcArgs args(aCntId, aCntEventType, aDecAccessCount);
	return SendReceive(ECntItemDelete, args);
	}

/** 
Open the database tables.

@capability ReadUserData
*/
void RCntModel::OpenTablesL()
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	User::LeaveIfError(SendReceive(ECntReOpenDbTables));
	}


/**
Close the database tables.

@capability None
*/
void RCntModel::CloseTablesL()
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	User::LeaveIfError(SendReceive(ECntCloseDbTables));
	}


/**
Update and unlock an existing contact in the database.

@param aContact The contact to be updated and unlocked
@param aSendChangedEvent If ETrue the changed event should be sent after the
update/unlock.

@capability WriteUserData
*/
void RCntModel::CommitContactL(const CContactItem& aContact, TBool aSendChangedEvent) const
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    //check what contacts were added/removed to a group
    if (aContact.Type() == KUidContactGroup)
        {
        const CContactGroup& group = static_cast<const CContactGroup&>(aContact);
        if (group.iInitialContactIds != NULL)
            {
            group.iAddedContactIds = CContactIdArray::NewL();
            group.iRemovedContactIds = CContactIdArray::NewL();
            for (int i = 0; i < group.iItems->Count(); i++)
                {
                if (group.iInitialContactIds->Find(group.iItems->operator[](i)) == KErrNotFound)
                    {
                    group.iAddedContactIds->AddL(group.iItems->operator[](i));
                    }
                }
            for (int j = 0; j < group.iInitialContactIds->Count(); j++)
                {
                if (group.iItems->Find(group.iInitialContactIds->operator[](j)) == KErrNotFound)
                    {
                    group.iRemovedContactIds->AddL(group.iInitialContactIds->operator[](j));
                    }
                }           
            }
        }
    
    // Process the image field
    if (iRescaler)
        {
        iRescaler->ProcessImageFieldL(const_cast<CContactItem&>(aContact));
        }
    
	// Pack the contact into the first IPC argument.
	TIpcArgs args; 
	TPtr8 ptr(iPackager->PackL((const_cast<CContactItem&>(aContact))));
	args.Set(0,&ptr);
	
	args.Set(1,aSendChangedEvent);

	User::LeaveIfError(SendReceive(ECntItemCommit, args));
	
    //clear list of contacts added/removed to a group
    if (aContact.Type() == KUidContactGroup)
        {
        const CContactGroup& group = static_cast<const CContactGroup&>(aContact);
        if (group.iAddedContactIds != NULL)
            {
            delete group.iAddedContactIds;
            group.iAddedContactIds = NULL;
            }
        if (group.iRemovedContactIds != NULL)
            {
            delete group.iRemovedContactIds;
            group.iRemovedContactIds = NULL;
            }
        if (group.iInitialContactIds != NULL)
            {
            delete group.iInitialContactIds;
            group.iInitialContactIds = NULL;
            }
        }	
	}


/**
Unpacks a contact item contained within the Packager's internal buffer and
creates a contact item object.  Within the server, the contact item has been
written to the Packager's internal buffer by the RMessage2.

If the buffer size was returned then the buffer was not large enough to hold a
serialised contact item.  The buffer must be expanded to the size returned by
the server (ie aBufferSize) and the read operation performed again. 

@see CCntPackager::SetBufferFromMessageL()
	
@param aBufferSize The new buffer size.
@param aArgs The argument buffer where the serialised contact item is written by
the server.

@return CContactItem unpacked from Packager's internal buffer.

@capability ReadUserData
*/
CContactItem* RCntModel::UnPackContactLC(TInt aBufferSize, TIpcArgs& aArgs) const
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	if(aBufferSize > 0) // Packager's internal buffer is not large enough.
		{
		// Set new extended receiving buffer.
		aArgs.Set(1, &iPackager->GetReceivingBufferL(aBufferSize));
		// Perform read operation again.
		User::LeaveIfError(SendReceive(ECntItemRead, aArgs));
		}
	return iPackager->UnpackCntItemLC();
	}


/**
Read an existing contact in the database.

@param aCntItemVDef The view definition to use.
@param aCntId Contact ID to read.

@return CContactItem object with contact ID aCntId.
*/
CContactItem* RCntModel::ReadContactL(const CContactItemViewDef* aCntItemVDef, TContactItemId aCntId) const
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args;

	if (aCntItemVDef) 
		{
		iPackager->PackL(*(const_cast<CContactItemViewDef*>(aCntItemVDef)));
		// Indicates that a user ContactItemViewDef is present in first IPC
		// argument.
		args.Set(3,TRUE);
		}
	else
		{
		iPackager->Clear();
		// Indicates that no user ContactItemViewDef is present in first IPC
		// argument.  The default view definition will be used instead.
		args.Set(3,FALSE);
		}

	args.Set(0, &iPackager->GetTransmittingBuffer());
	args.Set(1, &iPackager->GetReceivingBufferL());
	args.Set(2, aCntId);

	TInt newBuffSize = 0;
	User::LeaveIfError(newBuffSize = SendReceive(ECntItemRead, args));
	CContactItem* cntItem = UnPackContactLC(newBuffSize, args);
	CleanupStack::Pop(cntItem);

	return cntItem;
	}
	
	
/**
Open and lock an existing contact in the database.  The contact item lock is
pushed onto the cleanup stack.

@param aCntItemVDef The view definition to use.
@param aCntId Contact ID to read.

@return Opened/locked CContactItem object.
*/
CContactItem* RCntModel::OpenContactLX(const CContactItemViewDef* aCntItemVDef, TContactItemId aCntId) const
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args;
	if (aCntItemVDef) 
		{
		iPackager->PackL(*(const_cast<CContactItemViewDef*>(aCntItemVDef)));
		// Indicates that a user ContactItemViewDef is present in first IPC
		// argument.
		args.Set(3,TRUE);
		}
	else
		{
		iPackager->Clear();
		// Indicates that no user ContactItemViewDef is present in first IPC
		// argument.  The default view definition will be used instead.
		args.Set(3,FALSE);
		}

	args.Set(0, &iPackager->GetTransmittingBuffer());
	args.Set(1, &iPackager->GetReceivingBufferL());
	args.Set(2, aCntId);

	TInt newBuffSize = 0;
	User::LeaveIfError(newBuffSize = SendReceive(ECntItemOpen, args));

	// Push the contact item lock onto the cleanup stack.  If UnPackContactLC()
	// (or any other RCntModel method leaves after this point) then the last
	// contact to be locked will be unlocked (aka closed).  The method returns
	// with the lock remaining on the cleanup stack.
	PushUnlockL(); 

	CContactItem* cntItem = UnPackContactLC(newBuffSize, args);
	
    //save a list of contacts belonging to a group
    if (cntItem->Type() == KUidContactGroup)
        {
        CContactGroup* group = static_cast<CContactGroup*>(cntItem);
        delete group->iInitialContactIds;
        group->iInitialContactIds = NULL;
        group->iInitialContactIds = CContactIdArray::NewL(group->ItemsContained());
        }	
	
	CleanupStack::Pop(cntItem);
	
	return cntItem;		
	}


/**
Close/unLock an existing contact in the database.

@param aCntId The contact ID of the contact to close.

@return ETrue if the contact was opened before and closed after the call,
EFalse if there was no need to close the contact.
*/
TBool RCntModel::CloseContact(TContactItemId aCntId)
	{
    if(!iServerRunning)
        {
        return EFalse;
        }
	TInt res = SendReceive(ECntItemClose, TIpcArgs(aCntId));
	if (res == KErrNone)
		{
		return ETrue;
		}
	return EFalse;
	}


/**
Start a database transaction.
*/
TInt RCntModel::BeginDbTransaction() const
	{
    if(!iServerRunning)
        {
        return KErrServerTerminated;
        }
	return SendReceive(EBeginDbTransaction);
	}

	
/**
Commit a database transaction.
*/
TInt RCntModel::CommitDbTransaction() const
	{
    if(!iServerRunning)
        {
        return KErrServerTerminated;
        }
	return SendReceive(EEndDbTransaction);
	}

/**
Asychrounous commit of a database transaction.

@param aStatus Request status variable that will contain the completion value
*/
void RCntModel::CommitDbTransaction(TRequestStatus*& aStatus) const
    {
    if(!iServerRunning)
        {
        return;
        }
    SendReceive(EEndDbTransaction, *aStatus);
    }
	
/**
Rollback a database transaction.
*/
TInt RCntModel::RollbackDbTransaction() const
	{
    if(!iServerRunning)
        {
        return KErrServerTerminated;
        }
	return SendReceive(ERollbackDbTransaction);
	}


/**
Set the operation timeout value.  This value is the length of time that a
deferred request will remain in the request queue in the State Machine before
being completed with an error code.

@param aMicroSeconds The operation timeout in microseconds.  This timeout will
only be applied to requests sent by clients of this database after this point in
time. 
*/
void RCntModel::SetOperationTimeOutL(const TInt aMicroSeconds) const
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args;
	args.Set(0, aMicroSeconds);
	User::LeaveIfError(SendReceive(ECntOpsTimeOut, args));		
	}

	
/**
Change the existing contact view definition on the server.

@param aView The new contact view defintion.
*/
void RCntModel::SetViewDefinitionL(const CContactViewDef& aView)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TPtr8 ptr(iPackager->PackL(aView));
	User::LeaveIfError(SendReceive(ECntChangeViewDef, TIpcArgs(&ptr)));	
	}


/**
Add a database event observer.

@param aObserver Observer callback inteface used to send database event.
*/
void RCntModel::AddObserverL(MContactDbObserver& aObserver)
	{
	// If this is the first observer to be added then required to create the
	// database event notification monitor (lazy initialisation).
	if (!iDbNotifyMonitor)
		{
		iDbNotifyMonitor = CCntDbNotifyMonitor::NewL(*this);
		}
	iDbNotifyMonitor->AddObserverL(aObserver);
	}

	
/**
Remove a database event observer.

@param aObserver Observer callback inteface to be removed.
*/
void RCntModel::RemoveObserver(const MContactDbObserver& aObserver)
	{
	if (iDbNotifyMonitor)
		{
		iDbNotifyMonitor->RemoveObserver(aObserver);
		// If this is the last observer to be removed then dispose of the
		// database event notification monitor (no longer required).
		if (iDbNotifyMonitor->NumberOfListeners() == 0)
			{
			delete iDbNotifyMonitor;
			iDbNotifyMonitor = NULL;
			}
		}
	}

/**
Add a database event observer.

@param aObserver Observer callback inteface used to send database event.
*/
void RCntModel::AddObserverV2L(MContactDbObserverV2& aObserver)
    {
    // If this is the first observer to be added then required to create the
    // database event notification monitor (lazy initialisation).
    if (!iDbNotifyMonitor)
        {
        iDbNotifyMonitor = CCntDbNotifyMonitor::NewL(*this);
        }
    iDbNotifyMonitor->AddObserverV2L(aObserver);
    }

    
/**
Remove a database event observer.

@param aObserver Observer callback inteface to be removed.
*/
void RCntModel::RemoveObserverV2(const MContactDbObserverV2& aObserver)
    {
    if (iDbNotifyMonitor)
        {
        iDbNotifyMonitor->RemoveObserverV2(aObserver);
        // If this is the last observer to be removed then dispose of the
        // database event notification monitor (no longer required).
        if (iDbNotifyMonitor->NumberOfListeners() == 0)
            {
            delete iDbNotifyMonitor;
            iDbNotifyMonitor = NULL;
            }
        }
    }

/**
Request a database event from the server.

@param aStatus Completed when database event is available.
@param aEvent When aStatus is completed contains the database event.
*/
void RCntModel::StartNotificationTransfer(TRequestStatus& aStatus, TDes8& aEvent, TDes8& aIdArray)
	{	
    if(!iServerRunning)
        {
        return;
        }
	SendReceive(ECntRequestEvent, TIpcArgs(&aEvent, &aIdArray), aStatus);	
	}


/**
End (cancel) request for database event from server.
*/
void RCntModel::EndNotificationTransfer()
	{	
    if(!iServerRunning)
        {
        return;
        }
	SendReceive(ECntCancelEventRequest);	
	}
	
/** 
Sets the ID of the current item and persists it in the database.  The current 
item is provided for use by clients who want to identify one contact item in the
database as the currently selected item.

@param aContactId The ID of the current item. 

@capability WriteUserData
*/
void RCntModel::SetCurrentItem(TContactItemId aContactId) const
	{
    if(!iServerRunning)
        {
        return;
        }
	TIpcArgs args(aContactId);
	(void)SendReceive(ECntSetCurrentItem, args);	
	}

	
/** 
Gets the ID of the current item, as set by SetCurrentItem().  The current item 
ID is initialised to KNullContactId when the database is opened.

@return The ID of the current item. 

@capability None
*/
TContactItemId RCntModel::CurrentItem() const
	{		
    if(!iServerRunning)
        {
        return KNullContactId;
        }
    TContactItemId id = SendReceive(ECntGetCurrentItem);
    if (id < 0)
        {
        return KNullContactId;
        }
    else
        {
        return id;
        }
	}


/** 
When there are multiple contact databases on a device this method can be used to
enquire which database is the current one.

The current database functions are provided as part of current item
functionality.  In order to pass a current item from one contacts model client
to another, the receiving client needs to be using the same database.

The current database is a path and filename, set using SetCurrentDatabase(),
which is persisted by the contacts server.

@deprecated

@param aDatabase The path and filename of the current database.  KNullDesC 
if no current database has been set.

@return KErrNone if success otherwise one of the System error codes. 

@capability None
*/
TInt RCntModel::GetCurrentDatabase(TDes& aDatabase) const
	{
    if(!iServerRunning)
        {
        return KErrServerTerminated;
        }
	TIpcArgs args(&aDatabase);
	return SendReceive(ECntGetCurrentDb, args);
	}


/** 
When there are multiple contact databases on a device this method can be used to
set a database as the current one.

Note: this function simply updates a file name which is stored by the Contacts
server and its use is optional.  It is provided as part of current item
functionality. 

In order to pass a current item from one contacts model client to another, 
the receiving client needs to be using the same database.

@deprecated

@param aDatabase The path and filename of the current database.

@return KErrNone if success otherwise one of the System error codes. 

@capability WriteUserData
*/
TInt RCntModel::SetCurrentDatabase(const TDesC& aDatabase) const
	{
    if(!iServerRunning)
        {
        return KErrServerTerminated;
        }
	TIpcArgs args(&aDatabase);
	return SendReceive(ECntSetCurrentDb, args);
	}


/** 
Returns the ID of the contact item whose telephone number field is mapped to 
the speed dial position specified.

This function is provided so that information may be displayed about a contact
item whose telephone number is being dialled using speed dialling.

The function also retrieves the telephone number stored in the field.

@param aSpeedDialPosition The speed dial position.  This is an integer in the 
range 1 to 9 inclusive.  If outside this range, the function leaves with
KErrArgument.
@param aPhoneNumber On return contains the telephone number which is mapped to
the speed dial position specified.  Returns KNullDesC if the speed dial position
requested has not been set.

@return The ID of the contact item to which the speed dial is mapped. 

@capability ReadUserData
*/
TContactItemId RCntModel::GetSpeedDialFieldL(TInt aSpeedDialPosition, TDes& aPhoneNumber)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	__ASSERT_ALWAYS(aSpeedDialPosition >= KCntMinSpeedDialIndex && aSpeedDialPosition <= KCntMaxSpeedDialIndex , User::Leave(KErrArgument));	
	TPckgBuf<TContactItemId> contact(KNullContactId);
	TIpcArgs args(aSpeedDialPosition,&aPhoneNumber);
	return (TContactItemId)SendReceive(ECntGetSpeedDialContactIdAndPhoneNumber, args);
	}

	
/**
Sets a field containing a telephone number as a speed dial field.  The field 
is identified by aFieldIndex within the contact item aItem.  It is assigned a 
speed dial position between 1 and 9 inclusive.

The field's speed dial and user added attributes are set and the appropriate 
UID (KUidSpeedDialXxx) is added to the field's content type.  The changes are 
committed to the database.

Notes:

- If an item's telephone number field has already been assigned to position 
aSpeedDialPosition, that item is updated so that the speed dial attribute 
is removed from its field and the speed dial field type UID is removed from 
the field's content type, before the new speed dial field is set.

- The speed dial attribute can be tested for using the
CContactItemField::IsSpeedDial() method.

- The contact item passed to this function (aItem) must have been obtained using 
one of the variants of CContactDatabase::OpenContactL().  This is because it 
is modified and committed to the database by this function - no further 
commits are necessary.

@param aItem The contact item containing the field to set as a speed dial 
field.
@param aFieldIndex Index of a field in aItem's field set to set as a speed dial
field.
@param aSpeedDialPosition The speed dial position.  This is an integer in the 
range 1 to 9 inclusive.  If outside this range, the function leaves with
KErrArgument. 

@leave KErrDiskFull The disk does not have enough free space to perform the
operation.

@capability ReadUserData
@capability WriteUserData
*/
void RCntModel::SetFieldAsSpeedDialL(TContactItemId aContactId, TInt aFieldIndex, TInt aSpeedDialPosition)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	__ASSERT_ALWAYS(aSpeedDialPosition >= KCntMinSpeedDialIndex && aSpeedDialPosition <= KCntMaxSpeedDialIndex , User::Leave(KErrArgument));
	TIpcArgs args(aSpeedDialPosition, aContactId, aFieldIndex);
	User::LeaveIfError(SendReceive(ECntSetSpeedDialIdForPosition, args));
	}


/** 
Removes the mapping between a contact item field and a speed dial position.

Removes the KUidSpeedDialXxx UID from the field's content type, removes the 
field's speed dial attribute and commits the changes to the item.

@param aContactId The ID of the contact item containing the speed dial field.
@param aSpeedDialPosition The speed dial position.  This is an integer in the 
range 1 to 9 inclusive.  If outside this range, the function leaves with
KErrArgument. 

@leave KErrDiskFull The disk does not have enough free space to perform the operation.

@capability ReadUserData
@capability WriteUserData
*/
void RCntModel::RemoveSpeedDialFieldL(TContactItemId aContactId, TInt aSpeedDialPosition)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	__ASSERT_ALWAYS(aSpeedDialPosition >= KCntMinSpeedDialIndex && aSpeedDialPosition <= KCntMaxSpeedDialIndex , User::Leave(KErrArgument));
	// Tell the server that this speed dial slot is now free.  Third argument
	// (i.e. field index) is not used.
	TIpcArgs args(aSpeedDialPosition, aContactId, -1);
	User::LeaveIfError(SendReceive(ECntSetSpeedDialIdForPosition, args));		
	}


/**
Returns the ID of the template that should be used with CContactICCEntry items.
If aPhonebookUid is set, the ID of the template belonging to the phonebook with 
TUid aPhonebookUid is returned.

@param aPhonebookUid The phonebook ID with default KNullUid.

@return A template ID.
*/
TContactItemId RCntModel::ICCTemplateIdL(TUid aPhonebookUid)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args(aPhonebookUid.iUid);
	return SendReceive(ECntICCTemplateId, args);
	}


/**
Returns the ID of the contacts model group which represents the ADN phonebook.

@return ADN phonebook group ID.
*/ 
TContactItemId RCntModel::PhonebookGroupIdL()
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	return SendReceive(ECntPhonebookGroupId);
	}


/** 
Sets an existing contact item to be the database's current own card.

@param aContact The contact item to set as the database's current own card. 
It must already exist in the database.  It cannot be a group or a template.

@leave KErrNotFound aContact does not exist in the database. 
@leave KErrDiskFull The disk does not have enough free space to perform the
operation.

@capability None
*/
void RCntModel::SetOwnCardL(const CContactItem& aContact)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args; 
	TPtr8 ptr(iPackager->PackL(aContact));
	args.Set(0,&ptr);
	User::LeaveIfError(SendReceive(ECntSetOwnCard, args));	
	}


/**
Returns the ID of the database's current own card. 

Having obtained this ID, the client may then open the own card in the same 
way as an ordinary contact card (using ReadContactL() or OpenContactL()).

@return The ID of the database's current own card.  KNullContactId if the own 
card has been deleted or has not yet been set.
*/
TContactItemId RCntModel::OwnCard() const
	{
    if(!iServerRunning)
        {
        return KNullContactId;
        }
	return static_cast<TContactItemId>(SendReceive(ECntGetOwnCard));
	}

	
/**
Returns the connection ID of the current client session.

@return Connection ID of the current client session.
*/
TInt RCntModel::ConnectionId() const
	{
    if(!iServerRunning)
        {
        return KErrServerTerminated;
        }
	// If asking for the connection ID the first time, then we ask the server
	// about the session id (lazy initialisation).
	if (!iConnectionId)
		{
		iConnectionId = SendReceive(ECntConnectionId); 	
		}
	return iConnectionId;		
	}


/** 
Debug only.

@capability None
*/
void RCntModel::SetHeapFailure(RHeap::TAllocFail aType, TInt aRate)
	{	
    if(!iServerRunning)
        {
        return;
        }
	TIpcArgs args(aType,aRate);
	SendReceive(ECntSetHeapFailure,args);
	}

	
/** 
Debug only.

@return The heap size usage of the server in debug mode, 0 in release mode.

@capability None
*/
TInt RCntModel::ResourceCount()
	{
    if(!iServerRunning)
        {
        return KErrServerTerminated;
        }
	return(SendReceive(ECntResourceCount));
	}	

	
/**
Returns the ID of the database's preferred template, as set by the method
SetPrefTemplateL().  The preferred template is for clients who may have 
multiple templates but want to identify one as preferred.

@return The ID of the database's current preferred template.  KNullContactId if
not set.
*/
TContactItemId RCntModel::PrefTemplateId() const
	{
    if(!iServerRunning)
        {
        return KNullContactId;
        }
	return(SendReceive(ECntGetPrefTemplateId));	
	}


/** 
Sets the database's preferred template.

The preferred template's ID persists when the database is opened and closed. 
If the preferred template is subsequently deleted, the preferred template 
ID is set to KNullContactId.

@param aContact The contact card template to set as the database's preferred 
template.

@leave KErrNotSupported The item is not a template (i.e. is not of type
KUidContactCardTemplate).
@leave KErrDiskFull The disk does not have enough free space to perform the
operation.

@capability WriteUserData
*/
void RCntModel::SetPrefTemplateL(const TContactItemId aContactId)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args(aContactId);
	User::LeaveIfError(SendReceive(ECntSetPrefTemplateId, args));
	}

	
/**
Get a list of template IDs from the server.

@return Array of template IDs.
*/
CContactIdArray* RCntModel::FetchTemplateListIdsL()
{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0, &iPackager->GetReceivingBufferL());

	TInt newBuffSize;
	User::LeaveIfError(newBuffSize = SendReceive(ECntFetchTemplateIds, args));
	if (newBuffSize > 0)
		{
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntFetchTemplateIds, args));		
		}

	CContactIdArray* idArray = iPackager->UnpackCntIdArrayLC();	
	CleanupStack::Pop(idArray);

	return idArray;	
}


/**
Get a list of group IDs from the server.

@return Array of group IDs.
*/
CContactIdArray* RCntModel::FetchGroupListIdsL()
{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0, &iPackager->GetReceivingBufferL());

	TInt newBuffSize;
	User::LeaveIfError(newBuffSize = SendReceive(ECntFetchGroupIdLists, args));
	if (newBuffSize > 0)
		{
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntFetchGroupIdLists, args));		
		}

	CContactIdArray* idArray = iPackager->UnpackCntIdArrayLC();	
	CleanupStack::Pop(idArray);

	return idArray;	
}

/**
Get a collection of contact IDs using the specified collection parameters.

@param aCollectionType Type of collection.
@param aTime Used if collection type is "changed since".
@param aGuid Used if collection type is "find GUID".

@return Contact ID array of those contacts which match the specified collection
parameters.
*/
CContactIdArray* RCntModel::CollectionL(TInt aCollectionType, TTime aTime,const TDesC& aGuid)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	CContactIdArray* idArray = NULL;
	switch(aCollectionType)
	{
	case MLplCollection::EChangedSince :		
		{
		idArray = DoGetCollectionChangedSinceL(aTime);
		}
		break;
	case MLplCollection::EDeleted :
	case MLplCollection::EUnfiled :
		{
		idArray = DoGetCollectionL(aCollectionType);
		}
		break;
	case MLplCollection::EFindGuid :
		{
		idArray = DoGetCollectionGuidL(aGuid);
		}
		break;
	default:
		{
		User::Leave(KErrNotFound);
		}
		break;
	}	
	return idArray;
	}


/**
Get a collection of contact IDs which have changed since the given time.

@param aTime Changed since time.

@return Contact ID array of those contacts which have changed since the given
time.
*/
CContactIdArray* RCntModel::DoGetCollectionChangedSinceL(TTime aTime)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0, &iPackager->GetReceivingBufferL());
	args.Set(1,MLplCollection::EChangedSince);
	TPckgBuf<TTime> pckg(aTime);
	args.Set(2,&pckg);

	TInt newBuffSize;
	User::LeaveIfError(newBuffSize = SendReceive(ECntGetCollection, args));
	if (newBuffSize > 0)
		{
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntGetCollection, args));		
		}

	CContactIdArray* idArray = iPackager->UnpackCntIdArrayLC();	
	CleanupStack::Pop(idArray);

	return idArray;
	}

	
/**
Get a collection of contact IDs for the given collection type.

@param aCollectionType Collection type.

@return Contact ID array of those contacts which meet the given collection type
criteria.
*/
CContactIdArray* RCntModel::DoGetCollectionL(TInt aCollectionType)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0, &iPackager->GetReceivingBufferL());
	args.Set(1,aCollectionType);

	TInt newBuffSize;
	User::LeaveIfError(newBuffSize = SendReceive(ECntGetCollection, args));
	if (newBuffSize > 0)
		{
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntGetCollection, args));		
		}

	CContactIdArray* idArray = iPackager->UnpackCntIdArrayLC();	
	CleanupStack::Pop(idArray);

	return idArray;
	}


/**
Get a collection of contact IDs which have the given GUID.  Since GUIDs are
unique (i.e. no two contacts can have the same GUID) there will only ever be
one contact ID in this collection.

@param aGuid Contact GUID.

@return Contact ID array containing the contact with the given GUID.
*/
CContactIdArray* RCntModel::DoGetCollectionGuidL(const TDesC& aGuid)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0, &iPackager->GetReceivingBufferL());
	args.Set(1,MLplCollection::EFindGuid);
	args.Set(2,&aGuid);

	TInt newBuffSize;
	User::LeaveIfError(newBuffSize = SendReceive(ECntGetCollection, args));
	if (newBuffSize > 0)
		{
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntGetCollection, args));		
		}

	CContactIdArray* idArray = iPackager->UnpackCntIdArrayLC();	
	CleanupStack::Pop(idArray);

	return idArray;
	}


/**
Set the sort preferences in the server.

@param aSortOrder Sort order preferences.
*/
void RCntModel::SetSortPreferenceL(const CArrayFix<CContactDatabase::TSortPref>& aSortOrder)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TPtr8 ptr(iPackager->PackL(aSortOrder));
	TIpcArgs args;
	args.Set(0,&ptr);
	User::LeaveIfError(SendReceive(ECntSetSortPrefs,args));	
	}


/**
Get the sort preferences from the server.

@return Sort order preferences.
*/
CArrayFix<CContactDatabase::TSortPref>* RCntModel::GetSortPreferenceL() const
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0, &iPackager->GetReceivingBufferL());

	TInt newBuffSize;
	User::LeaveIfError(newBuffSize = SendReceive(ECntGetSortPrefs, args));
	if (newBuffSize > 0)
		{
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntGetSortPrefs, args));		
		}

	CArrayFix<CContactDatabase::TSortPref>* prefs = iPackager->UnpackCArrayFixLC();	
	CleanupStack::Pop(prefs);

	return prefs;
	}


/**
Get the number of contacts in the database.

@return Number of contacts in the database if successful, otherwise it leaves.
*/
TInt RCntModel::ContactCountL() const
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    TInt numContacts = SendReceive( ECntDbContactCount );
    User::LeaveIfError( numContacts );
    return numContacts;
	}

/**
Seek to the given contact ID.

@param aContactId Contact ID to seek to.
@param aId On return the contact ID that the seek found.  This will be the
nearest contact ID if the given aContactID cannot be found.
@param aContactType On return the contact type of the contact that the seek
found.
@param aDeleted On return ETrue if the contact that the seek found is marked as
deleted, EFalse otherwise.

@return ETrue if the seek was successful, EFalse otherwise.
*/
TBool RCntModel::SeekContactL(TContactItemId aContactId,TContactItemId& aId,TUid& aContactType, TBool& aDeleted)
    {
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
    TPckgBuf<TInt> id;
    TPckgBuf<TUid> type;
    TPckgBuf<TBool> deleted;
    TIpcArgs args(aContactId,&id,&type,&deleted);
    TInt ret = SendReceive(ECntSeekContactInCollection,args);

    // Although ECntSeekContact/ECntSeekContactInCollection is normally completed
    // with TBool values, ServiceError() can occur which leads to a negative system
    // error codes being returned.  User::LeaveIfError will allow that to be properly
    // handled.
    User::LeaveIfError(ret);

    if(ret)
        {
        aContactType = type();
        aId = id();
        aDeleted = deleted();
        return ETrue;
        }
    else
        {
        return EFalse;
        }
    }


void RCntModel::TextFieldL(TInt aCntItemId,TFieldType aFieldType, TDes& aText)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TPckgBuf<TFieldType> fieldType(aFieldType);
	TIpcArgs args(aCntItemId,&fieldType,&aText);
	User::LeaveIfError(SendReceive(ECntTextField,args));
	}


void RCntModel::ReadContactTextDefL(TContactItemId aContactId, TDes &aResult,const CContactTextDef& aTextDef)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	iPackager->PackL( const_cast<CContactTextDef&>(aTextDef));
	TIpcArgs args(&iPackager->GetTransmittingBuffer(), &aResult,aContactId);
	User::LeaveIfError(SendReceive(ECntReadContactTextDef, args));
	}


CContactIdArray* RCntModel::FindL(const TDesC& aText,const CContactItemFieldDef* aFieldDef)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;

	// Parameter can be NULL so send down an empty descriptor to the server.
	// Server can detect this and pass NULL parameter to the Persistence Layer
	// FindL() method.
	if(aFieldDef != NULL)
		{
		// Packager's sending buffer in slot 1 (contains packed field
		// definition).
		iPackager->PackL(*aFieldDef);
		args.Set(1,&iPackager->GetTransmittingBuffer());
		}
	else
		{
		// Frigged NULL.
		args.Set(1,&KNullDesC);
		}

	// Packager's receiving buffer in slot 0.
	args.Set(0, &iPackager->GetReceivingBufferL());

	// Text in slot 2.
	args.Set(2,&aText);

	// Ensure receiving buffer is big enough.
	TInt newBuffSize;
	User::LeaveIfError(newBuffSize = SendReceive(ECntFind, args));
	if (newBuffSize > 0)
		{
		// Not big enough so resize and call again.
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntFind, args));
		}

	CContactIdArray* idArray = iPackager->UnpackCntIdArrayLC();	
	CleanupStack::Pop(idArray);

	return idArray;	
	}


void RCntModel::SetDbViewContactType(const TUid aUid)
	{
    if(!iServerRunning)
        {
        return;
        }
	TIpcArgs args(aUid.iUid);
	(void)SendReceive(ECntSetDbViewContactType,args);
	}


TUid RCntModel::GetDbViewContactType() const
	{
    if(!iServerRunning)
        {
        return KNullUid;
        }
	TPckgBuf<TUid> type;
	TIpcArgs args(&type);
	(void)SendReceive(ECntSetDbViewContactType,args);
	return type();	
	}


/**
Initialise the Persistence Layer collection class ready for iterative calls to
the FindAsyncL() method.  This form of initialisation is for an asynchronous
find which uses a text definition and an array of "find words".

@param aWords "Find words" array.
@param aTextDef Text definition to use in find.
*/
void RCntModel::FindAsyncTextDefInitL(const CDesCArray& aWords,CContactTextDef* aTextDef)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	if(aTextDef != NULL)
		{
		// Pack the text definition.
		iPackager->PackL(*aTextDef);
		args.Set(1,&iPackager->GetTransmittingBuffer());
		}
	else
		{
		// No user supplied text definition.
		args.Set(1, &KNullDesC);
		}
	
	// Do the CDesCArray manually as Packager can't do both.
	RBufWriteStream writeStream;
	CBufFlat* buf = CBufFlat::NewL(1 << 8);
	CleanupStack::PushL(buf);

	writeStream.Open(*buf);
	TInt count = aWords.Count();
	writeStream.WriteInt32L(count);	
	TInt length = 0;
	for(TInt i=0; i<count;++i)
		{
		length = aWords[i].Length();
		writeStream.WriteInt32L(length);		
		writeStream.WriteL(aWords[i],length);
		}
	writeStream.CommitL();
	writeStream.Close();

	TPtr8 ptr8(buf->Ptr(0));
	args.Set(0,&ptr8);

	User::LeaveIfError(SendReceive(ECntFindAsyncTextDefInit,args));

	CleanupStack::PopAndDestroy(buf);
	}


/**
Initialise the Persistence Layer collection class ready for iterative calls to
the FindAsyncL() method.  This form of initialisation is for an asynchronous
find which uses text and a field definition.

@param aText Find text.
@param aFieldDef Field definition to use in find.
*/
void RCntModel::FindAsyncInitL(const TDesC& aText,CContactItemFieldDef* aFieldDef)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args;
	args.Set(0,&aText);
	if(aFieldDef != NULL)
		{
		// Pack the field definition.
		iPackager->PackL(*aFieldDef);
		args.Set(1,&iPackager->GetTransmittingBuffer());
		}
	else
		{
		// No user supplied field definition.
		args.Set(1, &KNullDesC);
		}

	User::LeaveIfError(SendReceive(ECntFindAsyncInit,args));
	}


/**
Perform an asynchronous find iteration.  Either the FindAsyncTextDefInitL() or
the FindAsyncInitL() method above must have first been called.

@param aOrigIdArray Set of contacts IDs from previous calls to FindAsyncL().
Will be NULL if this is the first iteration.

@return ETrue if further find iterations are required, EFalse otherwise.
*/
TBool RCntModel::FindAsyncL(CContactIdArray*& aOrigIdArray)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TBool moreToGo;
	TPckg<TBool> pckg(moreToGo);
	// Iterations can't fail so expand buffer to 4Kb.  4096 contacts should be
	// enough.  Rather than this brutal approach we could call ContactCountL()
	// and add a contingency in case another client adds some.
	TIpcArgs args(&iPackager->GetReceivingBufferL(4096),&pckg);

	User::LeaveIfError(SendReceive(ECntFindAsync,args));

	CContactIdArray* newIdArray = iPackager->UnpackCntIdArrayLC();	
	if(aOrigIdArray != NULL)
		{
		// Append to the old array if it was not NULL.
		for(TInt i=0;i<newIdArray->Count();++i)
			{
			aOrigIdArray->AddL((*newIdArray)[i]);
			}
		CleanupStack::PopAndDestroy(newIdArray);
		}
	else
		{
		CleanupStack::Pop(newIdArray);
		aOrigIdArray = newIdArray;
		}

	return pckg();
	}


/**
Set the asynchronous activity flag in the server.  Any asynchronous activity
such as asynchronous find or asynchronous sort prevent the database from
being closed and should set this flag.  When all asynchronous activites are
finished this flag should be cleared.

@capability None

@param aAsyncActivity ETrue if at least one asynchronous activity, EFalse if
no asynchronous activities.
*/
void RCntModel::SetAsyncActivityL(TBool aAsyncActivity)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args;
	args.Set(0,aAsyncActivity);
	User::LeaveIfError(SendReceive(ECntSetAsyncActivity,args));
	}


/**
Filter the database using the given filter.

@param aFilter Filter to use.  On return the contact IDs in this filter will be
augmented with those contact IDs which match the given filter.
*/
void RCntModel::FilterDatabaseL(CCntFilter& aFilter)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	CContactIdArray* origIdArray = aFilter.iIds;
	aFilter.iIds = NULL;

	TIpcArgs args;
	iPackager->PackL(aFilter);
	args.Set(1,&iPackager->GetTransmittingBuffer());	
	args.Set(0, &iPackager->GetReceivingBufferL());

	TInt newBuffSize(0);
	User::LeaveIfError(newBuffSize = SendReceive(ECntFilterDatabase, args));
	if (newBuffSize > 0)
		{
		// Not big enough so resize and call again.
		args.Set(0, &iPackager->GetReceivingBufferL(newBuffSize));
		User::LeaveIfError(newBuffSize = SendReceive(ECntFilterDatabase, args));
		}
	
	CContactIdArray* newIdArray = iPackager->UnpackCntIdArrayLC();
	if(origIdArray != NULL)
		{
		for(TInt i=0;i<newIdArray->Count();++i)
			{
			origIdArray->AddL((*newIdArray)[i]);
			}
		aFilter.iIds = origIdArray;
		CleanupStack::PopAndDestroy(newIdArray);
		}
	else
		{
		CleanupStack::Pop(newIdArray);
		aFilter.iIds = newIdArray;
		}
	}

	
TInt RCntModel::OpenViewL(const CContactTextDef& aTextDef, const TInt aViewPrefs)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TPckgBuf<TInt> viewSessionId;
	iPackager->PackL(const_cast<CContactTextDef&>(aTextDef));
	TIpcArgs args(&iPackager->GetTransmittingBuffer(), aViewPrefs, &viewSessionId);
	
	TInt ret = SendReceive(ECntOpenViewSession,args);
	User::LeaveIfError(ret);
	
	return 	viewSessionId();
	}
	
	
void RCntModel::CloseView(TInt aViewId)
	{
    if(!iServerRunning)
        {
        return;
        }
	TIpcArgs args(aViewId);
	SendReceive(ECntCloseViewSession,args);
	}
	
	
void RCntModel::ChangeSortOrderL(TInt aViewId, const CContactTextDef& aTextDef)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	// Serialize the text def using packager overload.
	iPackager->PackL( const_cast<CContactTextDef&>(aTextDef));
	

	TIpcArgs args(aViewId, &iPackager->GetTransmittingBuffer());
	TInt ret = SendReceive(ECntViewChangeSortOrderL, args);
	
	User::LeaveIfError(ret);
	}
	
	
void RCntModel::BeginIterateL(TInt aViewId)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args(aViewId);
	TInt ret = SendReceive(ECntViewBeginIterate,args);
	User::LeaveIfError(ret);
	}
	
	
void RCntModel::EndIterateL(TInt aViewId)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
	TIpcArgs args(aViewId);
	TInt ret = SendReceive(ECntViewEndIterate,args);
	User::LeaveIfError(ret);
	}
	
	
CViewContact* RCntModel::NextItemL(TInt aViewId, const TInt aViewPrefs)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args(aViewId, aViewPrefs, &iPackager->GetReceivingBufferL());
	TInt size = SendReceive(ECntViewNextItemL, args);

	// KErrNotFound is returned if Persistence Layer view item manager 
	// can't reach end of the contacts.
	if(size == KErrNotFound)
		{
		// Not an error just no match.
		return NULL;
		}

	// Leave on genuine errors.
	User::LeaveIfError(size);

	if(size != 0) // Server session requires larger packager buffer.
		{
		// Increase the buffer size and call again.  Just in case some other
		// client has deleted in between calls do the KErrNotFound check.
		TIpcArgs args(aViewId, aViewPrefs, &iPackager->GetReceivingBufferL(size));
		TInt ret = SendReceive(ECntViewNextItemL, args);
		if(ret == KErrNotFound)
			{
			return NULL;
			}
		// Leave on genuine errors
		User::LeaveIfError(ret);
		}

	// Got it so unpack and return.
	CViewContact* viewContact = iPackager->UnpackViewContactLC();

	CleanupStack::Pop(viewContact);

	return viewContact;
	}

	
CViewContact* RCntModel::ItemAtL(TContactItemId aContactId, TInt aViewId)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	TIpcArgs args(aContactId, aViewId, &iPackager->GetReceivingBufferL());
	TInt size = SendReceive(ECntItemAtL, args);

	// KErrNotFound is returned if Persistence Layer view item manager cannot 
	// find the view contact by given aContactId.
	if(size == KErrNotFound)
		{
		// Not an error just no match.
		return NULL;
		}

	// Leave on genuine errors.
	User::LeaveIfError(size);

	if(size != 0) // Server session requires larger packager buffer.
		{
		// Increase the buffer size and call again.  Just in case some other
		// client has deleted in between calls do the KErrNotFound check.
		TIpcArgs args(aContactId, aViewId, &iPackager->GetReceivingBufferL(size));
		TInt ret = SendReceive(ECntItemAtL, args);
		if(ret == KErrNotFound)
			{
			return NULL;
			}
		// Leave on genuine errors
		User::LeaveIfError(ret);
		}

	// Got it so unpack and return.
	CViewContact* viewContact = iPackager->UnpackViewContactLC();

	CleanupStack::Pop(viewContact);

	return viewContact;
	}
	

#if defined(_DEBUG)	
void RCntModel::GetDefinitionsOfExistingViewsL(const TDesC& aDbName, RPointerArray<CContactDefaultViewDefinition>& aViewDefs)
	{
    if(!iServerRunning)
        {
        User::Leave(KErrServerTerminated);
        }
    
	RBuf8 buf;
	CleanupClosePushL(buf);
	buf.CreateL(256);
	TIpcArgs args(&aDbName, &buf);

	TInt newBufSize;
	User::LeaveIfError(newBufSize = SendReceive(ECntGetDefinitionsForExistingView, args));
	if (newBufSize > 0)
		{
		buf.ReAllocL(newBufSize);
		args.Set(1, &buf);
		User::LeaveIfError(newBufSize = SendReceive(ECntGetDefinitionsForExistingView, args));		
		}

	RDesReadStream readStream(buf);
	CleanupClosePushL(readStream);
	TInt32 count;
	readStream >> count;
	CContactDefaultViewDefinition* view;
	for (TInt i = 0; i < count; i++)
		{
		view = CContactDefaultViewDefinition::NewLC(readStream);
		aViewDefs.AppendL(view);
		CleanupStack::Pop(view);
		}
		
	CleanupStack::PopAndDestroy(2); // readStream, buf	
	}
#else
void RCntModel::GetDefinitionsOfExistingViewsL(const TDesC& /*aDbName*/, RPointerArray<CContactDefaultViewDefinition>& /*aViewDefs*/)
	{	
	}
#endif // _DEBUG
