email/pop3andsmtpmtm/imapservermtm/src/FLDSYNC.CPP
changeset 25 84d9eb65b26f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/pop3andsmtpmtm/imapservermtm/src/FLDSYNC.CPP	Mon May 03 12:29:07 2010 +0300
@@ -0,0 +1,514 @@
+// 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:
+// IMAP4 folder list synchronise
+// This class deals with keeping the local mirror folder tree in sync with the
+// remote server's directory structure.
+// 980914 - Modifications to local tree traverse so that it will not
+// look for children below message level: previously, it was
+// picking up attachment folders in messages!
+// 990304 - Orphaned folder check no longer orphans the inbox: we
+// basically never touch it. The problem with orphaning it
+// comes about with servers which have a namespace (ie
+// folder path) of INBOX, which means we never "see" the
+// actual inbox as we are inside it. We still know it
+// exists, though.
+// 990502 - Was calling CMsgActive::Cancel() as opposed to DoCancel().
+// 
+//
+
+#include <e32base.h>
+#include <e32cons.h>
+#include <mentact.h>
+#include "fldsync.h"
+#include "impspan.h"
+#include "imapsess.h"
+
+#ifdef _DEBUG
+#define LOG_COMMANDS(a) a
+#define DBG(a) a
+#define PRINTING
+#else
+#define LOG_COMMANDS(a)
+#define DBG(a)
+#undef PRINTING
+#endif
+
+// Priority of MsgActive object
+const TInt EFolderSyncPriority=1;
+
+
+// Debugging of folder tree traversal
+#undef DEBUG_FOLDERSYNC
+
+CImImap4FolderSync::CImImap4FolderSync():CMsgActive(EFolderSyncPriority)
+	{
+	__DECLARE_NAME(_S("CImImap4FolderSync"));
+	}
+
+CImImap4FolderSync::~CImImap4FolderSync()
+	{
+	// Global bits
+	delete iFolderContents;
+
+	if (iFolderList)
+		iFolderList->ResetAndDestroy();
+	delete iFolderList;
+
+	// Get rid of bits at each level
+	for(TInt a=0;a<KFolderDepth;a++)
+		delete iFolderIds[a];
+
+	// Delete local folder list
+	delete iLocalFolders;
+	}
+
+CImImap4FolderSync* CImImap4FolderSync::NewLC(CImImap4Session *aSession)
+	{
+	CImImap4FolderSync* self=new (ELeave) CImImap4FolderSync();
+	CleanupStack::PushL(self);
+
+	// Non-trivial constructor
+	self->ConstructL(aSession);
+	return self;
+	}
+
+CImImap4FolderSync* CImImap4FolderSync::NewL(CImImap4Session *aSession)
+	{
+	CImImap4FolderSync* self=NewLC(aSession);
+	CleanupStack::Pop();
+	return self;
+	}
+
+// The non-trivial constructor
+void CImImap4FolderSync::ConstructL(CImImap4Session *aSession)
+	{
+	// Save session
+	iSession=aSession;
+
+	// We're an active object...
+	CActiveScheduler::Add(this);
+
+	// One-off bits
+	iFolderList=new (ELeave) CArrayPtrFlat<CImImap4DirStruct>(8);
+	iFolderContents=new (ELeave) CMsvEntrySelection;
+
+	// ...and per-level bits
+	for(TInt a=0;a<KFolderDepth;a++)
+		iFolderIds[a]=new (ELeave) CArrayFixFlat<TMsvId>(8);
+
+	// Local folder list
+	iLocalFolders=new (ELeave) CArrayFixFlat<TMsvId>(16);
+	}
+
+// Do setentry, leave if there is an error
+void CImImap4FolderSync::SetEntryL(const TMsvId aId)
+	{
+	User::LeaveIfError(iEntry->SetEntry(aId));
+	}
+
+// Change entry, leave if error
+void CImImap4FolderSync::ChangeEntryL(const TMsvEntry& aEntry)
+	{
+	User::LeaveIfError(iEntry->ChangeEntry(aEntry));
+	}
+
+// Change entry in bulk mode (i.e. no index file commit), leave if error
+void CImImap4FolderSync::ChangeEntryBulkL(const TMsvEntry& aEntry)
+	{
+	User::LeaveIfError(iEntry->ChangeEntryBulk(aEntry));
+	}
+// Get children, leave if error
+void CImImap4FolderSync::GetChildrenL(CMsvEntrySelection& aSelection)
+	{
+	User::LeaveIfError(iEntry->GetChildren(aSelection));
+	}
+
+// Set the entry to use to talk to the server
+void CImImap4FolderSync::SetEntry(CMsvServerEntry* aEntry)
+	{
+	// Save it
+	iEntry=aEntry;
+	}
+
+// Build list of local folders
+void CImImap4FolderSync::BuildLocalL(const TMsvId aFolder, const TBool aDoThisOne)
+	{
+	// Select it
+	SetEntryL(aFolder);
+	TMsvEmailEntry entry=iEntry->Entry();
+
+	// If we're a folder, set the flag
+	if (entry.iType==KUidMsvFolderEntry && aDoThisOne)
+		{
+		// Add to list
+		iLocalFolders->AppendL(iEntry->Entry().Id());
+		}
+
+	// If current entry is a message, don't look for children
+	if (entry.iType==KUidMsvMessageEntry)
+		return;
+
+	// Any children?
+	CMsvEntrySelection *children=new (ELeave) CMsvEntrySelection;
+	CleanupStack::PushL(children);
+	GetChildrenL(*children);
+	if (children->Count())
+		{
+		// Do each in turn
+		for(TInt child=0;child<children->Count();child++)
+			BuildLocalL((*children)[child],ETrue);
+		}
+	CleanupStack::PopAndDestroy();
+	}
+
+// Called when async child completes
+void CImImap4FolderSync::DoRunL()
+	{
+	if (ProcessDirListL())
+		{
+		// Done whole list, check to see what has vanished on the
+		// server with regard to what's in the mirror: basically,
+		// see what's still flagged in the tree.
+		OrphanedFolderCheckL();
+
+		// All done!
+		Complete(KErrNone);
+		}
+	else
+		{
+		// Otherwise, another command has been issued
+		SetActive();
+		}
+	}
+
+// Cancel this operation
+void CImImap4FolderSync::DoCancel()
+	{
+	DBG((iSession->LogText(_L8("CImImap4FolderSync::DoCancel()"))));
+
+	// Cancel any outstanding session operation
+	iSession->Cancel();
+
+	// ...and parent
+	CMsgActive::DoCancel();
+	}
+
+// Check for orphaned folders
+void CImImap4FolderSync::OrphanedFolderCheckL()
+	{
+	DBG((iSession->LogText(_L8("CImImap4FolderSync::OrphanedFolderCheckL()"))));
+
+	// Set stats
+	iOrphanedFolders=iLocalFolders->Count();
+
+	// Selection for storing children
+	CMsvEntrySelection *children=new CMsvEntrySelection;
+	CleanupStack::PushL(children);
+
+	// Any folders left in iLocalFolders are orphans
+	for(TInt a=0;a<iLocalFolders->Count();a++)
+		{
+		// Go to this folder
+		if (iEntry->SetEntry((*iLocalFolders)[a])!=KErrNone)
+			{
+			// Probably been removed already (nested?)
+			continue;
+			}
+		TMsvEmailEntry entry=iEntry->Entry();
+
+		// Is it the INBOX? If so, ignore it
+		if (entry.Parent()==iServiceId &&
+			entry.iDetails.CompareF(KIMAP_INBOX)==0)
+			continue;
+
+		// Check all the entries in the folder
+		TInt subfolders=0;
+		children->Reset();
+		GetChildrenL(*children);
+		if (children->Count())
+			{
+			// Check each message
+			for(TInt a=0;a<children->Count();a++)
+				{
+				SetEntryL((*children)[a]);
+				TMsvEmailEntry child=iEntry->Entry();
+				if (child.iType==KUidMsvMessageEntry)
+					{
+					// Orphan this message: it may delete it, if there
+					// are no downloaded bodyparts
+
+					DBG((iSession->LogText(_L8("Orphaning message %x"),(*children)[a])));
+
+					iSession->OrphanMessageL((*children)[a]);
+					}
+				else if (child.iType==KUidMsvFolderEntry)
+					{
+					// There's a subfolder
+					subfolders++;
+					}
+				}
+			}
+
+		// Check number of children again: if all the orphaned children
+		// have been deleted (ie, they had no downloaded body parts)
+		// then we can delete the folder locally, otherwise we just have
+		// to mark it as orphaned.
+		SetEntryL((*iLocalFolders)[a]);
+		GetChildrenL(*children);
+		if ((children->Count() || subfolders>0) && !entry.Orphan())
+			{
+
+			DBG((iSession->LogText(_L8("Orphaning folder %x"),(*iLocalFolders)[a])));
+
+			// Orphan folder
+			TMsvEmailEntry message=iEntry->Entry();
+			message.SetOrphan(ETrue);
+			ChangeEntryL(message);
+			}
+		else
+			{
+
+			DBG((iSession->LogText(_L8("Deleting folder %x"),(*iLocalFolders)[a])));
+
+			// Delete folder
+			SetEntryL(iEntry->Entry().Parent());
+			iEntry->DeleteEntry((*iLocalFolders)[a]);
+			}
+		}
+
+	// Get rid of children list
+	CleanupStack::PopAndDestroy();
+
+	// Remove list
+	iLocalFolders->Reset();
+
+	DBG((iSession->LogText(_L8("CImImap4FolderSync::OrphanedFolderCheckL() done"))));
+	}
+
+// Process returned directory list
+TBool CImImap4FolderSync::ProcessDirListL()
+	{
+	// We've got a list, and the mirror folder ID that the contents belong to:
+	// process it, and mirror the folder structure
+
+	// First, find the existing children of this folder from the message server
+	SetEntryL(iFolderId);
+	GetChildrenL(*iFolderContents);
+	TInt noofchildren=iFolderContents->Count();
+
+	// Number of items in reply from server
+	TInt noofreplies=iFolderList->Count();
+
+#ifdef DEBUG_FOLDERSYNC
+	iSession->LogText(_L("Level %d: Server has %d entries, mirror has %d"),
+					  iFolderLevel,noofreplies,noofchildren);
+#endif
+
+	// Empty the 'to do' array
+	iFolderIds[iFolderLevel]->Reset();
+
+	// ...for each entry in reply
+	for(TInt replyentry=0;replyentry<noofreplies;replyentry++)
+		{
+		// Does this already exist?
+		TInt a;
+		TMsvId thisentry;
+		for(a=0;a<noofchildren;a++)
+			{
+			// See if the details field matches the server folder name.
+			// For the INBOX folder the match should be case insensitive, all other folders
+			// we must perform a case sensitive match
+			SetEntryL(thisentry=(*iFolderContents)[a]);
+			TPtrC entryName = iEntry->Entry().iDetails;
+			TPtrC svrFolderName = (*iFolderList)[replyentry]->Leafname();
+			TBool foldersMatch = EFalse;
+			if(svrFolderName.CompareF(KIMAP_INBOX)==0)
+				{
+				// Server folder is the INBOX
+				foldersMatch = (entryName.CompareF(svrFolderName)==0);
+				}
+			else
+				{
+				// Server folder is not the INBOX
+				foldersMatch = (entryName.Compare(svrFolderName)==0);
+				}
+				
+			if (foldersMatch)
+				{
+				// Update mailbox flag as necessary, but NOT if it's the inbox, which is
+				// *ALWAYS* a mailbox
+				TMsvEmailEntry folder=iEntry->Entry();
+				if ((*iFolderList)[replyentry]->iIsMailbox!=folder.Mailbox())
+					{
+					if (folder.Parent()!=iServiceId ||
+					    folder.iDetails.CompareF(KIMAP_INBOX)!=0)
+						{
+						folder.SetMailbox((*iFolderList)[replyentry]->iIsMailbox);
+						ChangeEntryBulkL(folder);
+						}
+					else
+						{
+						DBG((iSession->LogText(_L8("Skipping Mailbox flag twiddle for folder %x (=%d)"),
+						  folder.Id(),(*iFolderList)[replyentry]->iIsMailbox)));
+						}
+					}
+
+				// Exists: remove from local folders list
+				for(TInt b=0;b<iLocalFolders->Count();b++)
+					{
+					// Found it?
+					if ((*iLocalFolders)[b]==thisentry)
+						{
+						// Remove from list
+						iLocalFolders->Delete(b,1);
+						break;
+						}
+					}
+
+				// Exit loop
+				break;
+				}
+			}
+
+		// Found a match?
+		if (a==noofchildren)
+			{
+			// No, create it
+			TMsvEmailEntry message;
+			
+			// All flags unset (urrgh!)
+			message.SetMtmData1(0);
+			message.SetMtmData2(0);
+			message.SetMtmData3(0);
+
+			message.iType=KUidMsvFolderEntry;
+			message.iMtm=iEntry->Entry().iMtm;
+			message.iServiceId=iServiceId;
+			message.iSize=0;
+			message.iDetails.Set((*iFolderList)[replyentry]->Leafname());
+			message.SetValidUID(EFalse);
+			message.SetComplete(ETrue);
+
+			// Visibility
+			if (iNewFoldersAreInvisible)
+				message.SetVisible(EFalse);
+
+			// Is a mailbox? (basically, is it selectable?).
+			message.SetMailbox((*iFolderList)[replyentry]->iIsMailbox);
+
+#ifdef DEBUG_FOLDERSYNC
+			iSession->LogText(_L("Creating folder '%S'"),&message.iDetails);
+#endif
+			SetEntryL(iFolderId);
+			User::LeaveIfError(iEntry->CreateEntryBulk(message));
+
+			// Increment stats
+			iNewFolders++;
+			
+			// Get the ID, incase we're going into this level
+			thisentry=message.Id();
+			}
+
+		// Does this have children? (and are we at the end of our nesting levels?)
+		if ((*iFolderList)[replyentry]->iIsFolder && iFolderLevel<(KFolderDepth-1))
+			{
+#ifdef DEBUG_FOLDERSYNC
+			iSession->LogText(_L("Adding folder to todolist, level %d"),iFolderLevel);
+#endif
+			// Add it to the list to scan at this level
+			iFolderIds[iFolderLevel]->AppendL(thisentry);
+			}
+		}
+
+#ifdef DEBUG_FOLDERSYNC
+	iSession->LogText(_L("Level %d: %d items in todolist"),iFolderLevel,iFolderIds[iFolderLevel]->Count());
+#endif
+
+	// Anything in our 'to do' queue?
+	while(!iFolderIds[iFolderLevel]->Count())
+		{
+#ifdef DEBUG_FOLDERSYNC
+		iSession->LogText(_L("Nothing to do at level %d"),iFolderLevel);
+#endif
+		// Nothing at this level: are we at the top?
+		if (iFolderLevel==0)
+			{
+#ifdef DEBUG_FOLDERSYNC
+			iSession->LogText(_L("At level 0: completing"));
+#endif
+			// All done!
+			// Commit any outstanding bulk creates
+			SetEntryL(iFolderId);
+			iEntry->CompleteBulk();
+			return(ETrue);
+			}
+
+#ifdef DEBUG_FOLDERSYNC
+		iSession->LogText(_L("Going up a level"));
+#endif
+		// Not at the top: back up a level
+		iFolderLevel--;
+		}
+
+	// Something to process: remove it from todo list
+	iFolderId=(*iFolderIds[iFolderLevel])[0];
+	iFolderIds[iFolderLevel]->Delete(0,1);
+
+	// Nest & issue command
+	iFolderLevel++;
+	iSession->ListL(iStatus,iFolderId,iFolderList);
+
+#ifdef DEBUG_FOLDERSYNC
+	iSession->LogText(_L("Stuff to do at level %d - scanning dir %x"),iFolderLevel,iFolderId);
+#endif
+	return(EFalse);
+	}
+
+// Synchronise mirror tree with remote server
+void CImImap4FolderSync::SynchroniseTreeL(TRequestStatus& aStatus, const TMsvId aService, const TBool aNewFoldersAreInvisible)
+	{
+	LOG_COMMANDS((iSession->LogText(_L8("COMMAND CImImap4FolderSync::SychroniseTree (serviceid=%x)"),aService)));
+
+	Queue(aStatus);
+
+	// Note where this list came from
+	iServiceId=aService;
+	iFolderId=iServiceId;
+	iFolderLevel=0;
+
+	// Save invisibility state
+	iNewFoldersAreInvisible=aNewFoldersAreInvisible;
+
+	// Set flags on all folders, so we can tell which ones aren't matched by the
+	// list returned from the server.
+	iLocalFolders->Reset();
+	BuildLocalL(iServiceId,EFalse);
+
+	// List this tree
+	iSession->ListL(iStatus,iFolderId,iFolderList);
+	SetActive();
+	}
+
+void CImImap4FolderSync::IncProgress(TImap4SyncProgress& aProgress)
+	{
+	aProgress.iNewFolders+=iNewFolders;
+	aProgress.iOrphanedFolders+=iOrphanedFolders;
+	}
+
+void CImImap4FolderSync::ResetStats()
+	{
+	// initialise counts
+	iNewFolders=0;
+	iOrphanedFolders=0;
+	}