email/imap4mtm/imapsyncmanager/src/cimapopsyncfoldertree.cpp
changeset 0 72b543305e3a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/imap4mtm/imapsyncmanager/src/cimapopsyncfoldertree.cpp	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,641 @@
+// Copyright (c) 1999-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 "cimapopsyncfoldertree.h"
+
+#include "cimapsettings.h"
+#include "cimapsession.h"
+#include "cimapfolder.h"
+#include "cimaplogger.h"
+
+_LIT(KImapInbox, "INBOX");
+
+/**
+Factory constructor
+
+@param aEntry Server entry
+@param aSession IMAP session
+@param aSyncMan Sync manager
+@param aImapSettings IMAP settings
+
+@return Instance of class created on heap
+*/
+CImapOpSyncFolderTree* CImapOpSyncFolderTree::NewL(CMsvServerEntry& aEntry, CImapSession& aSession, CImapSyncManager& aSyncMan, CImapSettings& aImapSettings)
+	{
+	CImapOpSyncFolderTree* self=NewLC(aEntry, aSession, aSyncMan, aImapSettings);
+	CleanupStack::Pop();
+
+	return self;
+	}
+
+/**
+Factory constructor
+
+@param aEntry Server entry
+@param aSession IMAP session
+@param aSyncMan Sync manager
+@param aImapSettings IMAP settings
+
+@return Instance of class created on heap
+*/
+CImapOpSyncFolderTree* CImapOpSyncFolderTree::NewLC(CMsvServerEntry& aEntry, CImapSession& aSession, CImapSyncManager& aSyncMan, CImapSettings& aImapSettings)
+	{
+	CImapOpSyncFolderTree* self=new (ELeave) CImapOpSyncFolderTree(aEntry, aSession, aSyncMan, aImapSettings);
+	CleanupStack::PushL(self);
+
+	// Non-trivial constructor
+	self->ConstructL();
+
+	return self;
+	}
+
+/**
+Constructor
+*/
+CImapOpSyncFolderTree::CImapOpSyncFolderTree(CMsvServerEntry& aEntry, CImapSession& aSession, CImapSyncManager& aSyncMan, CImapSettings& aImapSettings)
+: CImapSyncOperation(aEntry, aSession, aSyncMan, aImapSettings, EPriorityLow)
+	{
+	}
+
+/**
+Second phase constructor
+*/
+void CImapOpSyncFolderTree::ConstructL()
+	{
+	// Create some paths that are used in folder name pattern matching
+
+	// Create a path constisting of the inbox followed by the hierarchy
+	// separator
+	iInboxPathWithSeparator = HBufC::NewL(KImapInbox().Length() + iImapSettings.PathSeparator().Length());
+	TPtr inboxPathPtr = iInboxPathWithSeparator->Des();
+	inboxPathPtr.Copy(KImapInbox());
+	inboxPathPtr.Append(iImapSettings.PathSeparator());
+
+	// If the settings folder path is defined, create one path consisting of
+	// the folder path followed by the hierarchy separator, and one path
+	// consisting of the folder path only.
+	if (iImapSettings.FolderPath().Length() > 0)
+		{
+		iFolderPathWithSeparator = HBufC::NewL(iImapSettings.FolderPath().Length() + iImapSettings.PathSeparator().Length());
+		TPtr folderPathPtr = iFolderPathWithSeparator->Des();
+		folderPathPtr.Copy(iImapSettings.FolderPath());
+
+		iFolderPath.Set(folderPathPtr);
+
+		folderPathPtr.Append(iImapSettings.PathSeparator());
+		}
+
+	// We're an active object...
+	CActiveScheduler::Add(this);
+	}
+
+/**
+Destructor
+*/
+CImapOpSyncFolderTree::~CImapOpSyncFolderTree()
+	{
+	Cancel();
+
+	iImapListFolderInfo.ResetAndDestroy();
+	iFolderList.ResetAndDestroy();
+
+	delete iSelection;
+	delete iSearchFolder;
+	delete iInboxPathWithSeparator;
+	delete iFolderPathWithSeparator;
+	}
+
+/**
+Start tree synchronisation
+
+@param aStatus Caller's request status
+*/
+void CImapOpSyncFolderTree::SynchroniseTreeL(TRequestStatus& aStatus)
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::SynchroniseTreeL");
+	StartBuildingLocalFolderListL();
+	Queue(aStatus);
+	}
+
+/**
+Called when async child completes with >=KErrNone
+*/
+void CImapOpSyncFolderTree::DoRunL()
+	{
+	// Finish if any server errors.
+	if (iStatus.Int()!=KErrNone)
+		{
+		Complete(iStatus.Int());
+		return ;
+		}
+
+	switch (iState)
+		{
+		case EStateBuildingLocalFolderList:
+			{
+			ProcessNextFolderInLocalFolderListL();
+			break;
+			}
+
+		case EStateFetchingFolderList:
+			{
+			StartProcessingReceivedFolderListL();
+			break;
+			}
+
+		case EStateProcessingReceivedFolders:
+			{
+			ProcessNextFolderInReceivedFolderListL();
+			break;
+			}
+
+		case EStateIdle:
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedStateSyncFolderTreeDoRunL));
+			break;
+			}
+		}
+	}
+
+/**
+Cancels any outstanding asynchronous service requests.
+*/
+void CImapOpSyncFolderTree::DoCancel()
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::DoCancel");
+
+	switch (iState)
+		{
+		case EStateBuildingLocalFolderList:
+		case EStateProcessingReceivedFolders:
+			{
+			// Self completing states
+			break;
+			}
+
+		case EStateFetchingFolderList:
+			{
+			iSession.Cancel();
+			break;
+			}
+
+		case EStateIdle:
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedStateSyncFolderTreeDoCancel));
+			TRequestStatus* status = &iStatus;
+			User::RequestComplete(status, KErrCancel);
+			break;
+			}
+		}
+
+	CMsgActive::DoCancel();
+	}
+
+/**
+Called when completing a client request
+
+@param aStatus Completion code
+*/
+#ifdef __IMAP_LOGGING
+void CImapOpSyncFolderTree::DoComplete(TInt& aStatus)
+#else //__IMAP_LOGGING
+void CImapOpSyncFolderTree::DoComplete(TInt& /*aStatus*/)
+#endif //__IMAP_LOGGING
+	{
+	__LOG_FORMAT((iSession.LogId(), "CImapOpSyncFolderTree::DoComplete - %d", aStatus));
+
+	iImapListFolderInfo.ResetAndDestroy();
+	iFolderList.ResetAndDestroy();
+
+	delete iSelection;
+	iSelection = NULL;
+
+	delete iSearchFolder;
+	iSearchFolder = NULL;
+
+	delete iInboxPathWithSeparator;
+	iInboxPathWithSeparator = NULL;
+
+	delete iFolderPathWithSeparator;
+	iFolderPathWithSeparator = NULL;
+	
+	iState = EStateIdle;
+	}
+
+/**
+Starts building a list of folders for this service that are in the
+message store
+*/
+void CImapOpSyncFolderTree::StartBuildingLocalFolderListL()
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::StartBuildingLocalFolderListL");
+
+	iState = EStateBuildingLocalFolderList;
+
+	iFolderList.ResetAndDestroy();
+	iCurrentFolder = 0;
+
+	User::LeaveIfError(iServerEntry.SetEntry(iImapSettings.ServiceId()));
+
+	iSelection = new (ELeave) CMsvEntrySelection;
+
+	// Get all the folders directly under the service entry
+	User::LeaveIfError(iServerEntry.GetChildrenWithType(KUidMsvFolderEntry, *iSelection));
+
+	// If the inbox is in the selection, we want to remove it from the selction and
+	// specifically add it to the folder list. This ensures that we use the uppercase
+	// spelling of inbox and helps in the folder matching that is done against the list
+	// of folders we receive from the server.
+	for (TInt selLoop = 0; selLoop < iSelection->Count(); ++selLoop)
+		{
+		User::LeaveIfError(iServerEntry.SetEntry((*iSelection)[selLoop]));
+
+		TMsvEmailEntry entry = iServerEntry.Entry();
+		if (entry.iDetails.CompareF(KImapInbox) == 0)
+			{
+			iSelection->Delete(selLoop);
+
+			CImapFolder* tempFolder = CImapFolder::NewLC(iSyncMan, iServerEntry, entry, iImapSettings, KImapInbox());
+			tempFolder->SetFolderMatched(ETrue);
+			iFolderList.AppendL(tempFolder);
+			CleanupStack::Pop(tempFolder);
+			break;
+			}
+		}
+
+	// If the settings has a defined folder path, then append this at the start
+	// of each new folder path. Where the settings has no folder path defined,
+	// iFolderPath will just be an empty pointer.
+	iCurrentPath.Set(iFolderPath);
+
+	// We can now self complete to keep the state machine running. This will
+	// cause the current selection of folders to be processed
+	TRequestStatus* status = &iStatus;
+	User::RequestComplete(status, KErrNone);
+	SetActive();
+	}
+
+/**
+Process the current selection of folders.
+For each entry in the selection, add a new folder in the folder list. Then
+take the first entry in the folder list and create a selection of all its
+children folders. This process is then repeated until all the folders under
+the service have been found.
+*/
+void CImapOpSyncFolderTree::ProcessNextFolderInLocalFolderListL()
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::ProcessNextFolderInLocalFolderListL");
+
+	HBufC* newPath = NULL;
+	TInt curPathLen = iCurrentPath.Length();
+
+	// Loop through every entry in the selection. Each one is a folder.
+	for (TInt selLoop = 0; selLoop < iSelection->Count(); ++selLoop)
+		{
+		User::LeaveIfError(iServerEntry.SetEntry((*iSelection)[selLoop]));
+
+		TMsvEmailEntry entry = iServerEntry.Entry();
+
+		// Create the full path name for this folder. This will be the path
+		// name passed in (which is the path name of the parent) with the
+		// folder name from the message store entry appended.
+		newPath = HBufC::NewLC(curPathLen + 1 + entry.iDetails.Length());
+		newPath->Des().Copy(iCurrentPath);
+		if (curPathLen > 0)
+			{
+			newPath->Des().Append(iImapSettings.PathSeparator());
+			}
+		newPath->Des().Append(entry.iDetails);
+
+#ifdef __IMAP_LOGGING
+		HBufC8* tempBuf = HBufC8::NewL(newPath->Length());
+		TPtr8 tempBufPtr = tempBuf->Des();
+		tempBufPtr.Copy(*newPath);
+		__LOG_FORMAT((iSession.LogId(), "Adding Local Folder: - %S", &tempBufPtr));
+		delete tempBuf;
+#endif
+
+		// Create new IMAP folder and insert into the list.
+		CImapFolder* tempFolder = CImapFolder::NewLC(iSyncMan, iServerEntry, entry, iImapSettings, *newPath);
+		iFolderList.AppendL(tempFolder);
+		CleanupStack::Pop(tempFolder);
+		CleanupStack::PopAndDestroy(newPath);
+		newPath = NULL;
+		}
+
+	if (iCurrentFolder < iFolderList.Count())
+		{
+		iCurrentPath.Set(iFolderList[iCurrentFolder]->FullFolderPathL());
+		iSelection->Reset();
+
+		User::LeaveIfError(iServerEntry.SetEntry(iFolderList[iCurrentFolder]->MailboxId()));
+		User::LeaveIfError(iServerEntry.GetChildrenWithType(KUidMsvFolderEntry, *iSelection));
+
+		++iCurrentFolder;
+
+		// Self complete to go around again
+		TRequestStatus* status = &iStatus;
+		User::RequestComplete(status, KErrNone);
+		SetActive();
+		}
+	else
+		{
+		// We have created the folder list. Need to sort it into alphabetical
+		// order.
+		TLinearOrder<CImapFolder> sorter(CImapFolder::CompareByFolderName);
+		iFolderList.Sort(sorter);
+
+		delete iSelection;
+		iSelection = NULL;
+
+		// Now get the list of folders from the server
+		SendListRequestL();
+		}
+	}
+
+/**
+Sends the request to the server to get the list of folders
+*/
+void CImapOpSyncFolderTree::SendListRequestL()
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::SendListRequestL");
+
+	iState = EStateFetchingFolderList;
+
+	iSession.ListL(iStatus, KNullDesC, KStarChar(), iImapListFolderInfo);
+	SetActive();
+	}
+
+/**
+Starts processing the received list of folders on the server
+*/
+void CImapOpSyncFolderTree::StartProcessingReceivedFolderListL()
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::StartProcessingReceivedFolderListL");
+
+	iState = EStateProcessingReceivedFolders;
+
+	iCurrentFolder = 0;
+
+	// Create a temporary folder which will be used for subsequent searches
+	User::LeaveIfError(iServerEntry.SetEntry(iImapSettings.ServiceId()));
+	TMsvEmailEntry entry = iServerEntry.Entry();
+	iSearchFolder = CImapFolder::NewL(iSyncMan, iServerEntry, entry, iImapSettings, KNullDesC());
+
+	// Self complete to get the process moving
+	TRequestStatus* status = &iStatus;
+	User::RequestComplete(status, KErrNone);
+	SetActive();
+	}
+
+/**
+Process next folder from the received folder list.
+Check if we know about the folder already or not. If we don't know about
+it, then we need to add a new folder.
+*/
+void CImapOpSyncFolderTree::ProcessNextFolderInReceivedFolderListL()
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::ProcessNextFolderInReceivedFolderListL");
+
+	if (iCurrentFolder < iImapListFolderInfo.Count())
+		{
+		TPtrC currentFolderFullPath = iImapListFolderInfo[iCurrentFolder]->FolderName();
+
+#ifdef __IMAP_LOGGING
+		HBufC8* tempBuf = HBufC8::NewL(currentFolderFullPath.Length());
+		TPtr8 tempBufPtr = tempBuf->Des();
+		tempBufPtr.Copy(currentFolderFullPath);
+		__LOG_FORMAT((iSession.LogId(), "Processing received folder: - %S", &tempBufPtr));
+		delete tempBuf;
+#endif
+
+		if (currentFolderFullPath.Length() > 0)
+			{
+			TBool processFolder = ETrue;
+
+			// If the settings has a defined folder path, don't process this
+			// folder unless the first part of the folder name is the settings
+			// folder path or is the inbox.
+			if (iFolderPathWithSeparator)
+				{
+				// We don't need to do a case insensitive search for the inbox
+				// because the iInboxPathWithSeparator was created using uppercase,
+				// and the received folder list is change by the IMAP session to
+				// ensure that the inbox folder is converted to uppercase.
+				// Note also that this bit of code will result in the actual inbox
+				// folder being removed as it does not have a separator character
+				// on the end of it. This will not cause a problem because the
+				// inbox has already been specifically added to the list of local
+				// folders.
+				if ((iFolderPathWithSeparator->Compare(currentFolderFullPath.Left(iFolderPathWithSeparator->Length())) != 0) &&
+				    (iInboxPathWithSeparator->Compare(currentFolderFullPath.Left(iInboxPathWithSeparator->Length())) != 0))
+					{
+					__LOG_TEXT(iSession.LogId(), "Ignoring non folder path folder");
+					processFolder = EFalse;
+					}
+				}
+
+			if (processFolder)
+				{
+				// Try to find the received folder in the local folder list
+				TLinearOrder<CImapFolder> sorter(CImapFolder::CompareByFolderName);
+				iSearchFolder->SetFullFolderPathL(currentFolderFullPath);
+				TInt pos = iFolderList.FindInOrder(iSearchFolder, sorter);
+
+				if (pos != KErrNotFound)
+					{
+					// We already know about the folder. Just set its flag to indicate
+					// we have matched it.
+					iFolderList[pos]->SetFolderMatched(ETrue);
+					__LOG_TEXT(iSession.LogId(), "Folder already known");
+					}
+				else
+					{
+					AddReceivedFolderL(iImapListFolderInfo[iCurrentFolder], currentFolderFullPath);
+					}
+				}
+			}
+
+		++iCurrentFolder;
+		TRequestStatus* status = &iStatus;
+		User::RequestComplete(status, KErrNone);
+		SetActive();
+		}
+	else
+		{
+		// All the folders have been done. We now need to remove any folders
+		// that we have in the message store, that are no longer on the server.
+		RemoveMissingFolders();
+
+		delete iSearchFolder;
+		iSearchFolder = NULL;
+		
+		delete iInboxPathWithSeparator;
+		iInboxPathWithSeparator = NULL;
+		
+		delete iFolderPathWithSeparator;
+		iFolderPathWithSeparator = NULL;
+		}
+	}
+
+/**
+Add a new folder that is on the server but which we don't have stored locally
+
+@param aCurrentFolderInfo Received folder info of new folder
+@param aCurrentFolderFullPath Full path of new folder
+*/
+void CImapOpSyncFolderTree::AddReceivedFolderL(CImapListFolderInfo* aCurrentFolderInfo, const TDesC& aCurrentFolderFullPath)
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::AddReceivedFolderL");
+
+	CImapFolder* parentFolder = NULL;
+	TBool rootFolder = EFalse;
+	TPtrC parentFolderFullPath = aCurrentFolderFullPath;
+	TPtrC folderName = aCurrentFolderFullPath;
+	TLinearOrder<CImapFolder> sorter(CImapFolder::CompareByFolderName);
+
+	// Try to split the folder path into the folder name and the path of
+	// the parent folder by searching backwards from the end for the
+	// hierarchy separator character
+	TInt sepPos = parentFolderFullPath.LocateReverseF(aCurrentFolderInfo->iHierarchySeperator);
+
+	if (sepPos != KErrNotFound)
+		{
+		// We found the hierarchy separator. Split the path into the folder name
+		// and the parent path
+		parentFolderFullPath.Set(parentFolderFullPath.Left(sepPos));
+		folderName.Set(folderName.Mid(sepPos + 1));
+
+#ifdef __IMAP_LOGGING
+		HBufC8* tempBuf = HBufC8::NewL(parentFolderFullPath.Length());
+		TPtr8 tempBufPtr = tempBuf->Des();
+		tempBufPtr.Copy(parentFolderFullPath);
+		HBufC8* tempBuf2 = HBufC8::NewL(folderName.Length());
+		TPtr8 tempBufPtr2 = tempBuf2->Des();
+		tempBufPtr2.Copy(folderName);
+		__LOG_FORMAT((iSession.LogId(), "Split path into %S and %S", &tempBufPtr, &tempBufPtr2));
+		delete tempBuf;
+		delete tempBuf2;
+#endif
+
+		// Check if this is directly under inbox
+		if (parentFolderFullPath.CompareF(KImapInbox) == 0)
+			{
+			__LOG_TEXT(iSession.LogId(), "Parent is the inbox");
+			parentFolder = iSyncMan.Inbox();
+			}
+		// Check if the parent path is the same as the settings folder path.
+		// If so this is a root folder
+		else if ((iFolderPath.Length() > 0) &&
+		         (parentFolderFullPath.Compare(iFolderPath) == 0))
+			{
+			__LOG_TEXT(iSession.LogId(), "Parent is settings folder path");
+			rootFolder = ETrue;
+			}
+		else
+			{
+			// Search for the parent folder in the folder list.
+			// Note that the received list of folders from the server is in
+			// alphabetical order, so the parent folder will have already
+			// been created so we should be able to find it.
+			iSearchFolder->SetFullFolderPathL(parentFolderFullPath);
+			TLinearOrder<CImapFolder> sorter(CImapFolder::CompareByFolderName);
+			TInt parentPos = iFolderList.FindInOrder(iSearchFolder, sorter);
+
+			if (parentPos != KErrNotFound)
+				{
+				__LOG_FORMAT((iSession.LogId(), "Parent found at position %d", parentPos));
+				parentFolder = iFolderList[parentPos];
+				}
+			}
+		}
+	else
+		{
+		// No hierarchy separator so this must be a root folder
+		folderName.Set(aCurrentFolderFullPath);
+		rootFolder = ETrue;
+		}
+
+	if (rootFolder || parentFolder)
+		{
+		if (rootFolder)
+			{
+			User::LeaveIfError(iServerEntry.SetEntry(iImapSettings.ServiceId()));
+			}
+		else
+			{
+			User::LeaveIfError(iServerEntry.SetEntry(parentFolder->MailboxId()));
+			}
+
+		TMsvEmailEntry entry;
+		entry.iDetails.Set(folderName);
+		entry.iType = KUidMsvFolderEntry;
+		entry.iMtm = KUidMsgTypeIMAP4;
+		entry.iServiceId = iImapSettings.ServiceId();
+		entry.iSize = 0;
+		entry.SetUID(0);
+		entry.SetValidUID(EFalse);
+		entry.SetMailbox(!(aCurrentFolderInfo->QueryFlag(CImapListFolderInfo::ENoselect)));
+		entry.SetLocalSubscription(EFalse);
+		entry.SetComplete(ETrue);
+		entry.SetVisible(EFalse);
+
+		User::LeaveIfError(iServerEntry.CreateEntry(entry));
+
+		CImapFolder* tempFolder = CImapFolder::NewLC(iSyncMan, iServerEntry, entry, iImapSettings, aCurrentFolderInfo->FolderName());
+		tempFolder->SetFolderMatched(ETrue);
+		iFolderList.InsertInOrderL(tempFolder, sorter);
+		CleanupStack::Pop(tempFolder);
+
+		__LOG_TEXT(iSession.LogId(), "Added folder");
+		}
+	}
+
+/**
+Remove any local folders that are no longer on the server
+*/
+void CImapOpSyncFolderTree::RemoveMissingFolders()
+	{
+	__LOG_TEXT(iSession.LogId(), "CImapOpSyncFolderTree::RemoveMissingFolders");
+
+	// Remove in reverse order to ensure that children folders are removed
+	// before their parent
+	for (TInt folderLoop = iFolderList.Count() - 1; folderLoop >= 0; --folderLoop)
+		{
+		if (!iFolderList[folderLoop]->FolderMatched())
+			{
+#ifdef __IMAP_LOGGING
+			HBufC8* tempBuf = HBufC8::NewL(iFolderList[folderLoop]->FullFolderPathL().Length());
+			TPtr8 tempBufPtr = tempBuf->Des();
+			tempBufPtr.Copy(iFolderList[folderLoop]->FullFolderPathL());
+			__LOG_FORMAT((iSession.LogId(), "Removing folder %S", &tempBufPtr));
+			delete tempBuf;
+#endif
+
+			if (iServerEntry.SetEntry(iFolderList[folderLoop]->MailboxId()) == KErrNone)
+				{
+				if (iServerEntry.SetEntry(iServerEntry.Entry().Parent()) == KErrNone)
+					{
+					iServerEntry.DeleteEntry(iFolderList[folderLoop]->MailboxId());
+					}
+				}
+
+			delete iFolderList[folderLoop];
+			iFolderList.Remove(folderLoop);
+			}
+		}
+	}