messagingfw/msgsrvnstore/server/src/MSVARRAY.CPP
changeset 0 8e480a14352b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/messagingfw/msgsrvnstore/server/src/MSVARRAY.CPP	Mon Jan 18 20:36:02 2010 +0200
@@ -0,0 +1,664 @@
+// Copyright (c) 1998-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 "MSVARRAY.H"
+#include "MSVUIDS.H"
+#include "MSVPANIC.H"
+#include "cmsvdescriptionarray.h"
+
+const TInt KEntryArrayGranularity=32;
+const TInt KSortingMtmListGranularity=8;
+
+
+//**********************************
+// TKeyArrayFixPtr
+//**********************************
+
+EXPORT_C TAny* TKeyArrayFixPtr::At(TInt anIndex) const
+//
+// Key class for sorting on dereferenced pointers
+//
+	{
+	if (anIndex==KIndexPtr)
+		return((TUint8 *)((*((const TUint8 **)iPtr))+iKeyOffset)); 			
+	return((TAny *)((*((const TUint8 **)(iBase->Ptr(anIndex*iRecordLength).Ptr())))+iKeyOffset));
+	}
+
+
+//**********************************
+// CMsvEntryArray
+//**********************************
+
+EXPORT_C CMsvEntryArray* CMsvEntryArray::NewL(const CArrayFix<TUid>& aMtmList)
+	{
+	CMsvEntryArray* self = new(ELeave) CMsvEntryArray(aMtmList);
+	return self;
+	}
+
+EXPORT_C CMsvEntryArray* CMsvEntryArray::NewLC(const CArrayFix<TUid>& aMtmList)
+	{
+	CMsvEntryArray* self = new(ELeave) CMsvEntryArray(aMtmList);
+	CleanupStack::PushL(self);
+	return self;
+	}
+
+CMsvEntryArray::CMsvEntryArray(const CArrayFix<TUid>& aMtmList)
+: CArrayPtrFlat<const TMsvEntry>(KEntryArrayGranularity), iOrigMtmList(aMtmList)
+	{}
+
+
+EXPORT_C CMsvEntryArray::~CMsvEntryArray()
+	{
+	delete iActualMtmList;
+	}
+
+
+// Subject based sorting is a special case because text such as Re: , Fwd: etc is removed
+// before the sort. This means email threads are now appear in sequence.
+// The original TMsvEntry SUBJECT string is preserved as the sort takes place on a temporary
+// array of CDescription.
+// Second part of the method is a bubble sort on DATE which deals with duplicates.
+// This means that email threads will be in DATE order, oldest date first. 
+void CMsvEntryArray::SubjectBasedSortL(TBool aReverse, const TDesC& aSubjectSkipString)
+	{
+	if(Count() < 2) // One or less no sort required
+		return;
+
+	// Create a temporary array of CDescription's and set the offset for the sort to iDescription
+	CMsvDescriptionArray* descriptionArray = new (ELeave) CMsvDescriptionArray();
+	CleanupStack::PushL(descriptionArray);
+	TKeyArrayFixPtr key(_FOFF(TMsvDescription,iDescription),ECmpCollated);
+	TInt count = Count(); // Use this count for both loops
+	// Loop through the class instance array and construct a CDescription array ready
+	// for the sort.
+	for(TInt i =0;i<count;++i)
+		{
+		TMsvDescription* modified = new (ELeave) TMsvDescription();
+		CleanupStack::PushL(modified);
+		modified->iEntry = const_cast<TMsvEntry*>(At(i)); // Pointer to the real TMsvEntry
+		modified->iDescription.Set(At(i)->iDescription);// Copy of its TPtrC (may be modified)
+		TPtrC ptr(modified->iDescription);
+		TInt offset(0);
+		// Perform the search and skip Re: Fwd: etc if found
+		// offset points to the start of the real subject string.
+		if((offset = FindSubjectStart(ptr,aSubjectSkipString)) != KErrNotFound) 
+			{
+			ptr.Set(ptr.Ptr()+offset,ptr.Length()-offset);
+			modified->iDescription.Set(ptr);
+			}
+		descriptionArray->InsertIsqAllowDuplicatesL(modified,key); // Insert into temporary array
+		CleanupStack::Pop(modified);
+		}
+	// Reverse before we do the duplicate sort
+	if(aReverse)
+		{
+		descriptionArray->ReverseOrder();
+		}
+	// Bubble sort individual email threads if any, on DATE
+	// Achieved in a single loop by resetting current to the beginning of the email thread if
+	// we are still bubble sorting the thread.
+	// Less complicated than creating new (N email threads) x CArrayFix derived classes just
+	// for its sort methods
+	TMsvDescription** current = &descriptionArray->At(0);
+	TMsvDescription** startSort(NULL); // Beginning of an email thread
+	TBool threadSorting(EFalse); // Continue bubble sorting a thread whilst this flag is set
+	// Loop exits when sorting is false and end of list
+	for(;;)
+		{
+		++current;
+		// IF not the end of list AND still in email thread 
+		if(current <= &descriptionArray->At(count-1) && (*current)->iDescription == (*(current-1))->iDescription) // Compare current and previous
+			{
+			if(startSort == NULL) // Duplicate
+				{
+				startSort = current-1; // Keep reference to the start of the email thread.
+				}
+			if((*(current-1))->iEntry->iDate < (*current)->iEntry->iDate) // Check the DATE
+				{
+				threadSorting = ETrue; // Set this flag so we loop through this thread again
+				// Perform the swap
+				TMsvDescription* temp = *(current-1);
+				*(current-1) = *current;
+				*current = temp;
+				}
+			}
+		else if(!threadSorting)
+			{
+			// (end of thread  OR end of list). Either continue to next email thread or finished the list 
+			if(current >= &descriptionArray->At(count-1))
+				{
+				break; // End of list
+				}
+			else
+				{
+				startSort = NULL; // Continue to next email thread, if any.
+				}
+			}
+		else
+			{
+			// End of thread but not yet sorted. 
+			// Reset the list pointer to the start of the email thread.
+			threadSorting = EFalse;
+			current=startSort;
+			}
+		}
+	// Reset the CMsvEntryArray class instance list then repopulate it with the bubble sorted one.
+	// The TMsvEntry's are pointers to the ones in the original list
+	Reset();
+	for(TInt i=0;i<descriptionArray->Count();++i)
+		{
+		AppendL(descriptionArray->At(i)->iEntry);
+		}
+	CleanupStack::PopAndDestroy(descriptionArray);
+	}
+
+// Return an offset of the real subject. Skips Re: Fwd: etc.
+// The string to skip is in the class instance iSubjectSkipString.
+// Returns a valid offset or KErrNotFound
+TInt CMsvEntryArray::FindSubjectStart(const TDesC& aSubject, const TDesC& aSubjectSkipString) const
+	{
+	// Set up temporary's for more readable code
+	// current moves along the descriptor
+	// offset is the index into the descriptor of the real subject. (Can be KErrNotFound)
+	TPtrC ptr(aSubject);
+	TInt offset(0);
+	TInt current(0);
+	TInt skipLength = aSubjectSkipString.Length();
+	do
+		{
+		current = ptr.FindF(aSubjectSkipString);
+		if(current != KErrNotFound)
+			{
+			// Found a match so increment past the skip string instance. (Could be more)
+			offset+= current + skipLength;
+			ptr.Set(aSubject.Ptr()+offset,aSubject.Length()-offset);
+			}
+		else if(offset == 0)
+			{
+			// None found
+			offset = KErrNotFound;
+			}
+		}while(current != KErrNotFound);
+	return offset;
+	}
+
+CMsvDescriptionArray::CMsvDescriptionArray() : CArrayPtrFlat<TMsvDescription>(8)
+	{
+	}
+
+CMsvDescriptionArray::~CMsvDescriptionArray()
+	{
+	ResetAndDestroy();
+	}
+
+// Reverse on CDescription array
+void CMsvDescriptionArray::ReverseOrder()
+	{
+	TInt c=Count()-1;
+	if (c>0)
+		{
+		TMsvDescription** low=&At(0);
+		TMsvDescription** high=low+c;
+		while (low<high)
+			{
+			TMsvDescription* t=*low;
+			*low++=*high;
+			*high--=t;
+			}
+		}
+	}
+
+
+
+EXPORT_C void CMsvEntryArray::SortL(TMsvSelectionOrdering aOrdering)
+//
+// sorts this array
+//
+	{
+	// If we're going to reverse the array first sort by id so sorting twice will give the same result
+	switch(aOrdering.Sorting())
+		{
+		case EMsvSortByDateReverse:
+		case EMsvSortByIdReverse:
+		case EMsvSortBySizeReverse:
+		case EMsvSortByDescriptionReverse:
+		case EMsvSortByDetailsReverse:
+			{
+			TKeyArrayFixPtr key = MessageSortKey(EMsvSortById);
+			User::LeaveIfError(Sort(key));
+			break;
+			}
+		default: // Not required - break
+			break;
+		}
+	if (Count() && (aOrdering.Sorting()!=EMsvSortByNone || aOrdering.GroupingOn()))
+		GroupL(EGroupByStandardFolders, aOrdering, (aOrdering.Sorting()!=EMsvSortByNone));
+	}
+
+void CMsvEntryArray::GroupL(TGroupCriterion aGroupCriterion,TMsvSelectionOrdering aOrdering,TBool aDoSort)
+//
+// This function works recursively, grouping and sorting the entry selection.  The 
+// 'sort' happens at the same time as the 1st 'group', then separate grouped arrays 
+// are grouped indidvidually and then merged together at the end. The order in which 
+// the grouping occurs is determined by the CMsvEntryArray::TGroupCriterion enum
+//	
+	{
+	TMsvSorting sortType=aOrdering.Sorting();
+	TKeyArrayFixPtr key=MessageSortKey(sortType);
+	
+	if (aGroupCriterion==EStopGrouping)
+		{  // if you haven't sorted yet
+		if (aDoSort)
+			{
+			// Subject based sorting requires a special algorithm. Only message entries are treated as other entries normally
+			// do not have a prefix like e.g. "re: " or "fwd: "
+			if(At(0)->iType == KUidMsvMessageEntry && (sortType == 	EMsvSortByDescription || sortType == EMsvSortByDescriptionReverse))
+				{
+				SubjectBasedSortL(sortType == EMsvSortByDescriptionReverse,aOrdering.SubjectSkipString());							
+				}
+			else
+				{
+				CMsvEntryArray* temp=CMsvEntryArray::NewLC(iOrigMtmList);
+				TInt count=Count();
+				if (count)
+					temp->InsertL(0,&(*(this))[0],count);
+				Reset();
+				const TMsvEntry** entry = &temp->At(0);
+				while (count--)
+					InsertIsqAllowDuplicatesL(*entry++, key); // Sorted
+				ReverseOrder(sortType);
+				CleanupStack::PopAndDestroy(); // temp
+				}
+			if (At(0)->iType == KUidMsvMessageEntry && (sortType == EMsvSortByDetails || sortType == EMsvSortByDetailsReverse))
+				{
+				DetailBasedSortL(); // Sort blocks of messages with matching details into newest first
+				}
+			}
+		else
+			{
+			// The aDoSort flag is not set, but we still need to do a subject
+			// based sort if this array contains only message entries, and we are
+			// sorting by description. Alternatively, we need to do a date based sort
+			// if this array contains only message entries and we are sorting by detail.
+			// In order to ensure the array contains only message entries, we
+			// check that we have previously grouped the entries by type which would
+			// have put all the message entries together in their own array.
+			if (Count() > 0 && At(0)->iType == KUidMsvMessageEntry && OkToGroup(EGroupByType, aOrdering))
+				{
+				if (sortType == EMsvSortByDescription || sortType == EMsvSortByDescriptionReverse)
+					{
+					SubjectBasedSortL(sortType == EMsvSortByDescriptionReverse,aOrdering.SubjectSkipString());
+					}
+				else if (sortType == EMsvSortByDetails || sortType == EMsvSortByDetailsReverse)
+					{
+					DetailBasedSortL(); // Sort blocks of messages with matching details into newest first
+					}
+				}
+			}
+		return;
+		}
+
+	if (OkToGroup(aGroupCriterion, aOrdering))
+		{
+		//
+		// Copy contents into temp and then put new grouped contents into 'this'
+		TInt count=Count();
+		if (count==0)
+			return; // nothing to do here
+		const TInt numberOfArrays=NumberOfArraysToSplitIntoL(aGroupCriterion);
+		if (numberOfArrays<1)  // cannot group on this so move on to next grouping
+			{
+			GroupL(TGroupCriterion(aGroupCriterion+1), aOrdering, aDoSort);
+			return;
+			}
+		CMsvEntryArray* temp;
+		if (iActualMtmList)
+			temp = CMsvEntryArray::NewLC(*iActualMtmList);
+		else
+			temp = CMsvEntryArray::NewLC(iOrigMtmList);
+		temp->InsertL(0,&(*(this))[0],count);
+		Reset();
+
+		//
+		// create the separate arrays for each group
+		CArrayFixFlat<CMsvEntryArray*>* arrays=new(ELeave) CArrayFixFlat<CMsvEntryArray*>(numberOfArrays);
+		CleanupStack::PushL(arrays);
+		for (TInt ii=0; ii<numberOfArrays; ii++)
+			{
+			if (iActualMtmList)
+				arrays->AppendL(CMsvEntryArray::NewLC(*iActualMtmList));
+			else
+				arrays->AppendL(CMsvEntryArray::NewLC(iOrigMtmList));
+			}
+
+		//
+		// split the selection into the correct group, 
+		// sorting aswell if needed and not doing standard folders
+		const TMsvEntry** entry = &temp->At(0);
+		if (!aDoSort || aGroupCriterion==EGroupByStandardFolders)
+			{
+			while (count--)
+				{
+				arrays->At(ArrayId(*entry,aGroupCriterion))->AppendL(*entry);
+				entry++;
+				}
+			}
+		else if (aGroupCriterion==EGroupByType)
+			{
+			TKeyArrayFixPtr folderKey = TKeyArrayFixPtr(_FOFF(TMsvEntry,iDetails),ECmpCollated);
+			while (count--)
+				{
+				if ((*entry)->iType==KUidMsvFolderEntry)
+					arrays->At(ArrayId(*entry, aGroupCriterion))->InsertIsqAllowDuplicatesL(*entry, folderKey);
+				else
+					arrays->At(ArrayId(*entry, aGroupCriterion))->InsertIsqAllowDuplicatesL(*entry, key);
+				entry++;
+				}
+			for (TInt jj=0; jj<numberOfArrays; jj++)
+				{
+				if (arrays->At(jj)->Count() && arrays->At(jj)->At(0)->iType!=KUidMsvFolderEntry)
+					arrays->At(jj)->ReverseOrder(sortType);
+				}
+			aDoSort=EFalse; 
+			}
+		else
+			{
+			while (count--)
+				{
+				arrays->At(ArrayId(*entry, aGroupCriterion))->InsertIsqAllowDuplicatesL(*entry, key); // Sorted
+				entry++;
+				}
+			for (TInt jj=0; jj<numberOfArrays; jj++)
+				arrays->At(jj)->ReverseOrder(sortType);
+			aDoSort=EFalse; 
+			}
+
+		
+		
+		
+		
+		//
+		// group further - but check that standard entries and grouped folders are not grouped anymore
+		if (aGroupCriterion==EGroupByStandardFolders)
+			{
+			__ASSERT_DEBUG(numberOfArrays==2, PanicServer(EMsvToManyGroups));
+			arrays->At(0)->GroupL(TGroupCriterion(aGroupCriterion+1), aOrdering, aDoSort);
+			}
+		else if (aGroupCriterion==EGroupByType)
+			{
+			for (TInt jj=0; jj<numberOfArrays; jj++)
+				if (arrays->At(jj)->Count() && arrays->At(jj)->At(0)->iType!=KUidMsvFolderEntry)
+					arrays->At(jj)->GroupL(TGroupCriterion(aGroupCriterion+1), aOrdering, aDoSort);
+			}
+		else 
+			{
+			for (TInt jj=0; jj<numberOfArrays; jj++)
+				arrays->At(jj)->GroupL(TGroupCriterion(aGroupCriterion+1), aOrdering, aDoSort);
+			}
+		
+		//
+		// merge the separate arrays into 'this'
+		for (TInt kk=0; kk<numberOfArrays; kk++)
+			{
+			count=arrays->At(kk)->Count();
+			if (count)
+				InsertL(0,&(*(arrays->At(kk)))[0],count);
+			}
+		CleanupStack::PopAndDestroy(numberOfArrays+2); // arrays contents + temp + arrays
+		}
+	else // move on to the next grouping
+		GroupL(TGroupCriterion(aGroupCriterion+1), aOrdering, aDoSort);
+	}
+
+
+TInt CMsvEntryArray::NumberOfArraysToSplitIntoL(TGroupCriterion aGroupCriterion)
+//
+// Returns the number of arrays the selection will be split into with this grouping method
+//
+	{
+	switch (aGroupCriterion)
+		{
+		case EGroupByType:
+			return 5;
+		case EGroupByStandardFolders:
+			return 2;
+		case EGroupByPriority:
+			return 3;
+		case EGroupByMtm:
+			{
+			// copy the orig mtm list
+			if (iActualMtmList==NULL)
+				iActualMtmList = new(ELeave) CArrayFixFlat<TUid>(KSortingMtmListGranularity);
+			else
+				iActualMtmList->Reset();
+
+			if (iOrigMtmList.Count())
+				iActualMtmList->InsertL(0, &iOrigMtmList.At(0), iOrigMtmList.Count());
+
+			// need to add any unknown MTM to the list 
+			TKeyArrayFix key = TKeyArrayFix(0, ECmpTUint);
+			for (TInt ii=0; ii<Count(); ii++)
+				{
+				TUid uid = At(ii)->iMtm;
+				TInt temp;
+				if (iActualMtmList->Find(uid, key, temp))
+					iActualMtmList->AppendL(uid);
+				}	
+			return iActualMtmList->Count();
+			}
+		default: // case EStopGrouping:
+			return 0;
+		}
+	}
+
+
+TBool CMsvEntryArray::OkToGroup(TGroupCriterion aGroupCriterion,TMsvSelectionOrdering aOrdering) const
+//
+// Returns true if the current grouping method is selected
+// 
+	{
+	switch (aGroupCriterion)
+		{
+		case EGroupByType:
+			return aOrdering.GroupByType();
+		case EGroupByStandardFolders:
+			return aOrdering.GroupStandardFolders();
+		case EGroupByPriority:
+			return aOrdering.GroupByPriority();
+		case EGroupByMtm:
+			return aOrdering.GroupByMtm();
+		default: //case EStopGrouping:
+			return EFalse;
+		}
+//	return EFalse;
+	}
+
+TInt CMsvEntryArray::ArrayId(const TMsvEntry* aEntry, TGroupCriterion aGroupCriterion) const
+//
+// Returns the id of the array to plave the entry into
+//
+	{
+	switch (aGroupCriterion)
+		{
+		case EGroupByType:
+			{
+			switch (aEntry->iType.iUid)
+				{
+				case KUidMsvAttachmentEntryValue:
+					return 1;
+				case KUidMsvMessageEntryValue:
+					return 2;
+				case KUidMsvFolderEntryValue:
+					return 3;
+				case KUidMsvServiceEntryValue:
+					return 4;
+				default:
+					return 0; // unknown types are grouped after attachments
+				}
+			}
+		case EGroupByPriority:
+			{
+			switch (aEntry->Priority())
+				{
+				case EMsvLowPriority:
+					return 0;
+				case EMsvMediumPriority:
+					return 1;
+				case EMsvHighPriority:
+					return 2;
+				default:
+					PanicServer(EMsvUnknownPriority);
+				}
+			}
+		case EGroupByMtm:
+			{
+			__ASSERT_DEBUG(iActualMtmList, PanicServer(EMsvMtmListNotDefined));
+			TInt index;
+			TKeyArrayFix key(0, ECmpTUint);
+			TInt error = iActualMtmList->Find(aEntry->iMtm, key, index);
+			__ASSERT_ALWAYS(error==KErrNone, PanicServer(EMsvUnknownMtm));
+			return iActualMtmList->Count() - index - 1;
+			}
+		case EGroupByStandardFolders:
+			return aEntry->StandardFolder() ? 1 : 0;
+		default:
+			return -1;
+		}
+	}
+
+
+TKeyArrayFixPtr CMsvEntryArray::MessageSortKey(TMsvSorting aSortType) const
+//
+// Return appropriate key for desired sort
+//
+	{
+	switch(aSortType)
+		{
+		case EMsvSortByDate:
+		case EMsvSortByDateReverse:
+			{
+			return TKeyArrayFixPtr(_FOFF(TMsvEntry,iDate),ECmpTInt64);
+			}
+		case EMsvSortBySize:
+		case EMsvSortBySizeReverse:
+			{
+			return TKeyArrayFixPtr(_FOFF(TMsvEntry,iSize),ECmpTUint);
+			}
+		case EMsvSortByDescription:
+		case EMsvSortByDescriptionReverse:
+			{
+			return TKeyArrayFixPtr(_FOFF(TMsvEntry,iDescription),ECmpCollated);
+			}
+		case EMsvSortByDetails:
+		case EMsvSortByDetailsReverse:
+			{
+			return TKeyArrayFixPtr(_FOFF(TMsvEntry,iDetails),ECmpCollated);
+			}
+		case EMsvSortById:
+		case EMsvSortByIdReverse:
+			{
+			return TKeyArrayFixPtr(_FOFF(TMsvEntry,iId),ECmpTInt32);
+			}
+		// case EMsvSortByNone:
+		default:
+			return TKeyArrayFixPtr(0,ECmpNormal);			
+		}
+	}
+
+void CMsvEntryArray::ReverseOrder(TMsvSorting aSortType)
+//
+// Reverse based in SortBy value
+//
+	{
+	switch(aSortType)
+		{
+		case EMsvSortByDateReverse:
+		case EMsvSortByIdReverse:
+		case EMsvSortBySizeReverse:
+		case EMsvSortByDescriptionReverse:
+		case EMsvSortByDetailsReverse:
+			ReverseOrder();
+			break;
+		default: // Not required - break
+			return;
+		}
+	}
+
+void CMsvEntryArray::ReverseOrder()
+//
+// Atomic selection reverse function
+//
+	{
+	TInt c=Count()-1;
+	if (c>0)
+		{
+		const TMsvEntry** low=&At(0);
+		const TMsvEntry** high=low+c;
+		while (low<high)
+			{
+			const TMsvEntry* t=*low;
+			*low++=*high;
+			*high--=t;
+			}
+		}
+	}
+
+void CMsvEntryArray::DetailBasedSortL()
+	{
+	// Find blocks of entries with matching details
+	TInt blockStart = 0;
+
+	while (blockStart < Count())
+		{
+		const TMsvEntry* firstEntry = At(blockStart);
+		TInt nextBlock = blockStart + 1;
+
+		// Find the start of the next block		
+		while (nextBlock < Count() && At(nextBlock)->iDetails == firstEntry->iDetails)
+			{
+			nextBlock++;
+			}
+		const TInt blockSize = nextBlock - blockStart;
+
+		// Sort the block by date, newest first
+		if (blockSize > 1)
+			{
+			TKeyArrayFixPtr key(_FOFF(TMsvEntry,iDate),ECmpTInt64);
+			CMsvEntryArray* temp = CMsvEntryArray::NewLC(iOrigMtmList);
+			const TMsvEntry** entry = &At(blockStart);
+
+			// Copy block entries in sequence to temporary array
+			for (TInt tempIndex = 0; tempIndex < blockSize; tempIndex++, entry++)
+				{
+				temp->InsertIsqAllowDuplicatesL(*entry, key);
+				}
+			// Reverse to give newest first
+			temp->ReverseOrder();
+
+			// Replace block with sorted entries
+			entry = &At(blockStart);
+			for (TInt tempIndex = 0; tempIndex < blockSize; tempIndex++, entry++)
+				{
+				*entry = temp->At(tempIndex);
+				}
+
+			CleanupStack::PopAndDestroy(temp);
+			}
+
+		// Move on to next block of entries
+		blockStart = nextBlock;
+		}
+	}
+