diff -r 000000000000 -r 72b543305e3a email/imap4mtm/imapsyncmanager/src/cimapopsyncfoldertree.cpp --- /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 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 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 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 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); + } + } + }