pimprotocols/pbap/server/pbapcontactdbviews.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 02 Feb 2010 10:12:17 +0200
changeset 0 e686773b3f54
permissions -rw-r--r--
Revision: 201003 Kit: 201005

// 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:
//

#include "pbapcontactdbviews.h"
#include <cntfldst.h>
#include <cntitem.h>
#include <cntfield.h>
#include "pbapserver.h"
#include "btaccesshostlog.h"

_LIT(KNameFieldSeparator, ";");

	

/*static*/ CPbapContactDbViews* CPbapContactDbViews::NewL(CContactDatabase& aDatabase)
	{
	LOG_STATIC_FUNC
	CPbapContactDbViews* self=new(ELeave) CPbapContactDbViews(aDatabase);
	return self;
	}


CPbapContactDbViews::CPbapContactDbViews(CContactDatabase& aDatabase)
	: iDatabase(aDatabase)
	{
	LOG_FUNC
	}


CPbapContactDbViews::~CPbapContactDbViews()
	{
	LOG_FUNC
	CloseAllViews();
	}	


/**
Close all currently open views and abort current asynchronous search and sort request. 
This function should be called when the PBAP session ends (gracefully or otherwise)
to save memory while there is no PBAP session open
*/
void CPbapContactDbViews::CloseAllViews()
	{
	LOG_FUNC
	
	CancelAndCleanup();
	if (iNameView)
		{
		iNameView->Close(*this);
		iNameView=NULL;
		}
	if (iPhoneticView)
		{
		iPhoneticView->Close(*this);
		iPhoneticView=NULL;
		}
	iNameViewReady = EFalse;
	iPhoneticViewReady = EFalse;	
	}
	
/**
Get contact name string with fields separated by a semi-colon. The fields are read from the name
view when it is ready, otherwise they are read from the database
*/
HBufC* CPbapContactDbViews::GetContactNameFromIdL(TContactItemId aContactId) const
	{
	LOG_FUNC

	HBufC* name=NULL;
	
	// check to see if the view of the underlying database is ready, i.e. HandleContactViewEvent
	// has been called back after we created iNameView
	if (iNameViewReady)
		{
		// find the contact id in the name view
		TInt index=iNameView->FindL(aContactId);
		User::LeaveIfError(index); //leave with KErrNotFound if contact id not found

		// read concatenated name from name view
		name=iNameView->AllFieldsLC(index, KNameFieldSeparator);
		}
	else
		{
		// create buffer large enough to contain name fields with separators
		// NB We can't use the 'separator' functionality of 
		// TContactTextDefItem because the separator is elided when there is 
		// no text in the field following, which isn't what we want.
		name=HBufC::NewLC(5*KCntMaxTextFieldLength+4);

		CContactItem* item = iDatabase.ReadMinimalContactLC(aContactId);
		const CContactItemFieldSet& fieldSet = item->CardFields();

		AppendField(name, KUidContactFieldFamilyName, fieldSet);
		AppendField(name, KUidContactFieldGivenName, fieldSet);
		AppendField(name, KUidContactFieldAdditionalName, fieldSet);
		AppendField(name, KUidContactFieldPrefixName, fieldSet);
		AppendField(name, KUidContactFieldSuffixName, fieldSet, ETrue); // ETrue- last field: don't add ;

		CleanupStack::PopAndDestroy(item);
		}
	CleanupStack::Pop(name); //ownership passed
	return name;
	}

// Append the field aUid from field set aFieldSet to aBuf. 
// Also append KNameFieldSeparator to that, unless aLast.
void CPbapContactDbViews::AppendField(HBufC* aBuf, TUid aUid, const CContactItemFieldSet& aFieldSet, TBool aLast) const
	{
	LOG_FUNC
	TPtr ptr(aBuf->Des());
	const TInt index = aFieldSet.Find(aUid);
	if ( index != KErrNotFound )
		{
		ptr.Append(aFieldSet[index].TextStorage()->Text());
		}
	if ( !aLast )
		{
		ptr.Append(KNameFieldSeparator);
		}
	}

/**
Finds contacts matching search parameters and returns the ids sorted in the requested order
*/		
void CPbapContactDbViews::GetContactIdsMatchingCriteriaL(SymbianPBAP::TOrder aOrder, SymbianPBAP::TSearchAttribute aSearchAttribute,
						const TDesC& aSearchValue, CContactIdArray& aResults, MPbapContactDbViewsCallback& aCallback)
	{
	LOG_FUNC
	
	// the only supported sort orders are alphabetical and phonetical
	if (aOrder != SymbianPBAP::EAlphabetical && aOrder != SymbianPBAP::EPhonetical)
		{
		__ASSERT_DEBUG(EFalse, Panic(EPbapServerPanicInvalidViewSortOrder));
		// leave if sort order not supported
		User::Leave(KErrNotSupported);
		}
		
	// store callback and results array
	iCallback = &aCallback;
	iResults = &aResults;

	// abort previous request and cleanup
	CancelAndCleanup();
	
	// store sort and search parameters
	iOrder = aOrder;
	iSearchAttribute = aSearchAttribute;
	iSearchValue = aSearchValue.AllocL();
	
	if (!iNameView)
		{
		// first request so create name view now
		CreateNameViewL();
		}
		
	if (!iPhoneticView && IsPhoneticViewRequired())
		{
		// phonetic search or sort required so create phonetic name view
		CreatePhoneticViewL();
		}
	
	iOpState = EPendingSearch;
	if (iNameViewReady && (iPhoneticViewReady || !IsPhoneticViewRequired()))
 		{
		// views are ready (or not required depending on sort order) so start search and sort now
		TCallBack callback(SearchAndSortCallback,this);
		iAsyncCallback = new(ELeave) CAsyncCallBack(callback, CActive::EPriorityStandard);
		iAsyncCallback->CallBack();
		}
	}


void CPbapContactDbViews::CancelSearchAndSortRequest()
	{
	LOG_FUNC
	CancelAndCleanup();
	iCallback=NULL;
	}
	
	
void CPbapContactDbViews::CreateNameViewL()
	{
	LOG_FUNC
	// check name view does not already exist
	if(iNameView)
		{
		__ASSERT_DEBUG(EFalse, Panic(EPbapServerPanicNameViewExists));
		User::Leave(KErrAlreadyExists);
		}

	RContactViewSortOrder viewSortOrder;
   	CleanupClosePushL(viewSortOrder);
   	viewSortOrder.AppendL(KUidContactFieldFamilyName);
   	viewSortOrder.AppendL(KUidContactFieldGivenName);
   	viewSortOrder.AppendL(KUidContactFieldAdditionalName);
   	viewSortOrder.AppendL(KUidContactFieldPrefixName);
   	viewSortOrder.AppendL(KUidContactFieldSuffixName);

	const TContactViewPreferences prefs = static_cast<TContactViewPreferences>(EContactsOnly | EUnSortedAtEnd);
    iNameView = CContactLocalView::NewL(*this, iDatabase, viewSortOrder, prefs);
    CleanupStack::PopAndDestroy();  // viewSortOrder
	}


void CPbapContactDbViews::CreatePhoneticViewL()
	{
	LOG_FUNC
	// check phonetic view does not already exist
	if(iPhoneticView)
		{
		__ASSERT_DEBUG(EFalse, Panic(EPbapServerPanicPhoneticViewExists));
		User::Leave(KErrAlreadyExists);
		}

	RContactViewSortOrder viewSortOrder;
   	CleanupClosePushL(viewSortOrder);
   	viewSortOrder.AppendL(KUidContactFieldFamilyNamePronunciation);
   	viewSortOrder.AppendL(KUidContactFieldGivenNamePronunciation);

	const TContactViewPreferences prefs = static_cast<TContactViewPreferences>(EContactsOnly | EUnSortedAtEnd);
    iPhoneticView = CContactLocalView::NewL(*this, iDatabase, viewSortOrder, prefs);
    CleanupStack::PopAndDestroy();  // viewSortOrder
	}
	
	
TBool CPbapContactDbViews::IsPhoneticViewRequired() const
	{
	LOG_FUNC
	return (iOrder==SymbianPBAP::EPhonetical || iSearchAttribute==SymbianPBAP::ESound);
	}


/*static*/ TInt CPbapContactDbViews::SearchAndSortCallback(TAny* aAny)
	{
	LOG_STATIC_FUNC
	CPbapContactDbViews* self = static_cast<CPbapContactDbViews*>(aAny);
	self->DoSearchAndSortCallback();
	return KErrNone;	
	}

void CPbapContactDbViews::DoSearchAndSortCallback()
	{
	LOG_FUNC
	if (iOpState == EPendingSearch)
		{
		iOpState = ESearching;
		TRAPD(error, DoSearchAndSortL());
		if (error != KErrNone)
			{
			LOG1(_L("Error %d from DoSearchAndSortL"), error);
			// NotifySearchAndSortComplete will also cancel and cleanup search
			NotifySearchAndSortComplete(error);
			}
		}	
	}


void CPbapContactDbViews::DoSearchAndSortL()
	{
	LOG_FUNC
	if (!iSearchValue->Length())
		{
		// no search value so just sort
		DoSortL();
		}
	else
		{
		if (iSearchAttribute==SymbianPBAP::ENumber)
			{
			FindInPhoneFieldsL();
			}
		else if (iSearchAttribute==SymbianPBAP::EName)
			{
			FindInViewL(*iNameView);
			}
		else if (iSearchAttribute==SymbianPBAP::ESound)
			{
			FindInViewL(*iPhoneticView);
			}
		}
	}
	
	
void CPbapContactDbViews::FindInViewL(CContactViewBase& aView)
	{
	LOG_FUNC
	// check search values in correct state
	if(iSearchValueArray || iFindView)
		{
		__ASSERT_DEBUG(EFalse, Panic(EPbapServerPanicInvalidViewSearchState));
		User::Leave(KErrAlreadyExists);
		}

	iSearchValueArray = new(ELeave) CPtrCArray(1);
	iSearchValueArray->AppendL(*iSearchValue);

	// start asynchronous search of view
	iFindView = CContactFindView::NewL(iDatabase, aView, *this, iSearchValueArray, CContactViewBase::EPrefixSearch);
	}


void CPbapContactDbViews::FindInPhoneFieldsL()
	{
	LOG_FUNC
	// check search values in correct state
	if(iFindFieldDef || iIdleFinder)
		{
		__ASSERT_DEBUG(EFalse, Panic(EPbapServerPanicInvalidFieldSearchState));
		User::Leave(KErrAlreadyExists);
		}

	iFindFieldDef = new(ELeave) CContactItemFieldDef;
	iFindFieldDef->AppendL(KUidContactFieldPhoneNumber);

	// start an asynchronous search of phone fields in database
	iIdleFinder = iDatabase.FindAsyncL(*iSearchValue, iFindFieldDef, this);
	}


void CPbapContactDbViews::IdleFindCallback()
	{
	LOG_FUNC
	TInt idleFindError = iIdleFinder->Error();
	if(idleFindError == KErrNone)
		{
		// this callback will be called for every 16 items searched to allow users to update their search
		// status but we are only concerned when the entire search has completed
		if(iIdleFinder->IsComplete())
			{
			// find completed successfully
			iOpState = ESorting;
			
			TRAPD(error, HandleIdleFindCompleteL());
			if (error != KErrNone)
				{
				LOG1(_L("Error %d from HandleIdleFindCompleteL"), error);
				// NotifySearchAndSortComplete will also cancel and cleanup search
				NotifySearchAndSortComplete(error);
				}	
			}
		}
	else
		{
		LOG1(_L("Idle finder error %d"), idleFindError);
		// error occurred during find notify the observer (and abort find)	
		NotifySearchAndSortComplete(idleFindError);
		}
	}


void CPbapContactDbViews::HandleIdleFindCompleteL()
	{
	LOG_FUNC
	CContactIdArray* contactIds=iIdleFinder->TakeContactIds();
	LOG1(_L("Idle finder returned %d contacts"), contactIds->Count());
	CleanupStack::PushL(contactIds);
	DoSortSearchResultsL(*contactIds);
	CleanupStack::PopAndDestroy(contactIds);	
	}


void CPbapContactDbViews::HandleFindInViewCompleteL()
	{
	LOG_FUNC
	CContactIdArray* contactIds = GetContactIdsInViewL(*iFindView);
	LOG1(_L("Find view contains %d contacts"), contactIds->Count());
	CleanupStack::PushL(contactIds);
	DoSortSearchResultsL(*contactIds);
	CleanupStack::PopAndDestroy(contactIds);
	}


void CPbapContactDbViews::DoSortL()
	{
	LOG_FUNC
	// get all the ids from the view
	CContactIdArray* contactIds=NULL;
	switch (iOrder)
		{
		case SymbianPBAP::EAlphabetical:
			contactIds = GetContactIdsInViewL(*iNameView);
			break;
		case SymbianPBAP::EPhonetical:
			contactIds = GetContactIdsInViewL(*iPhoneticView);
			break;
		default:
			Panic(EPbapServerPanicInvalidViewSortOrder);
		}

	if (contactIds)
		{
		CleanupStack::PushL(contactIds);
		CopyToResultsArrayL(*contactIds);
		CleanupStack::PopAndDestroy(contactIds);
		}

	// the search and sort request is complete notify the observer
	NotifySearchAndSortComplete(KErrNone);
	}


CContactIdArray* CPbapContactDbViews::GetContactIdsInViewL(CContactViewBase &aView)
	{
	LOG_FUNC
	CContactIdArray* contactIds = CContactIdArray::NewLC();
	CArrayFixFlat<TInt>* indexArray = new(ELeave) CArrayFixFlat<TInt>(32);	
	CleanupStack::PushL(indexArray);
	TInt count=aView.CountL();
	for (TInt index=0; index < count; ++index)
		{
		indexArray->AppendL(index);
		}

	aView.GetContactIdsL(*indexArray, *contactIds);
	CleanupStack::PopAndDestroy(indexArray);
	CleanupStack::Pop(contactIds);
	return contactIds; //ownership passed
	}


void CPbapContactDbViews::DoSortSearchResultsL(CContactIdArray& aContactIds)
	{
	LOG_FUNC

	if (iOrder == SymbianPBAP::EAlphabetical)
		{
		if (iSearchAttribute != SymbianPBAP::EName)
			{
			// the find view was sorted on the phonetic name so we need to resort the
			// ids in the find view to match the name view ordering
			ResortIdsInViewOrderL(aContactIds, *iNameView);
			}
		}
	else if (iOrder == SymbianPBAP::EPhonetical)
		{	
		if (iSearchAttribute != SymbianPBAP::ESound)
			{
			// the find view was sorted on the name so we need to resort the
			// ids in the find view to match the phonetic name view ordering
			ResortIdsInViewOrderL(aContactIds, *iPhoneticView);
			}
		}

	CopyToResultsArrayL(aContactIds);

	// the search and sort is complete notify the observer
	NotifySearchAndSortComplete(KErrNone);
	}

/**
aView will be sorted in a specific order and this function re-orders the contact ids in 
aContectIds to be in the same order
*/
void CPbapContactDbViews::ResortIdsInViewOrderL(CContactIdArray& aContactIds, CContactViewBase& aView)
	{
	LOG_FUNC
	// create sorted array of indexes of ids in the view  
	RArray<TInt> indexes;
	CleanupClosePushL(indexes);
	TInt count = aContactIds.Count();
	for (TInt i = 0; i < count; ++i)
		{
		TInt pos = aView.FindL(aContactIds[i]);
		if (pos != KErrNotFound)
			{
			indexes.InsertInOrderL(pos);
			}
		}
		
	// now replace id array contents with ids sorted in same order as view	
	aContactIds.Reset();
	count = indexes.Count();
	for (TInt j = 0; j < count; ++j)
		{
		aContactIds.AddL(aView.AtL(indexes[j]));
		}
	CleanupStack::PopAndDestroy(); //indexes	
	}


void CPbapContactDbViews::CopyToResultsArrayL(const CContactIdArray& aContactIds)
	{
	LOG_FUNC
	const TInt count = aContactIds.Count();
	for (TInt i=0; i < count; ++i)
		{
		iResults->AddL(aContactIds[i]);
		}
	}

/**
this function is part of MContactViewObserver and is called back as a result
of an event occurring on a specific view, in our case iNameView or iPhoneticView
*/
void CPbapContactDbViews::HandleContactViewEvent(const CContactViewBase &aView,
												 const TContactViewEvent &aEvent)
	{
	LOG_FUNC
	TInt error=KErrNone;
	switch(aEvent.iEventType)
		{
		case TContactViewEvent::ESortOrderChanged:
		case TContactViewEvent::EReady:
			{
			if (&aView == iNameView)
				{
				iNameViewReady = ETrue;
				}
			else if (&aView == iPhoneticView)
				{
				iPhoneticViewReady = ETrue;
				}
			else if (&aView == iFindView && iOpState == ESearching)
				{
				iOpState = ESorting;
				TRAP(error, HandleFindInViewCompleteL());
				if (error != KErrNone)
					{
					LOG1(_L("Error %d from HandleFindInViewCompleteL"), error);
					// NotifySearchAndSortComplete will also cancel and cleanup search
					NotifySearchAndSortComplete(error);
					}
				return;	
				}
					
			if (iNameViewReady && (iPhoneticViewReady || !IsPhoneticViewRequired())
				&& iOpState == EPendingSearch)
				{
				// necessary views are ready now so start the find and sort
				iOpState = ESearching;
				TRAP(error, DoSearchAndSortL());
				if (error != KErrNone)
					{
					LOG1(_L("Error %d from DoSearchAndSort"), error);
					// NotifySearchAndSortComplete will also cancel and cleanup search
					NotifySearchAndSortComplete(error);
					}				
				}
			}
			break;
		case TContactViewEvent::EUnavailable:	
		case TContactViewEvent::EServerError:
		case TContactViewEvent::ESortError:	
			{
			if (iOpState != EIdle)
				{
				LOG1(_L("Error %d reported by contact views"), aEvent.iInt);
				// error during find and sort request
				NotifySearchAndSortComplete(aEvent.iInt);
				}
			}
			break;
		default:
			break;
		}
	}
	
	
void CPbapContactDbViews::NotifySearchAndSortComplete(TInt aError)
	{
	LOG_FUNC
	if (iCallback)
		{
		iCallback->HandleSearchAndSortComplete(aError);
		iCallback = NULL;
		}
	CancelAndCleanup();
	}


void CPbapContactDbViews::CancelAndCleanup()
	{
	LOG_FUNC
	iOpState=EIdle;

	if (iFindView)
		{
		iFindView->Close(*this);
		iFindView=NULL;	
		}
	delete iSearchValue;
	iSearchValue=NULL;
	delete iSearchValueArray;
	iSearchValueArray=NULL;
	delete iFindFieldDef;
	iFindFieldDef=NULL;

	if (iIdleFinder)
		{
		iIdleFinder->Cancel();
		delete iIdleFinder;
		iIdleFinder=NULL;
		}	
	if (iAsyncCallback)
		{
		iAsyncCallback->Cancel();
		delete iAsyncCallback;
		iAsyncCallback=NULL;
		}
	}