email/imap4mtm/imapprotocolcontroller/src/cimapcompoundcopytolocal.cpp
changeset 0 72b543305e3a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/imap4mtm/imapprotocolcontroller/src/cimapcompoundcopytolocal.cpp	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,804 @@
+// 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 "cimapcompoundcopytolocal.h"
+#include "cimapopfetchbody.h"
+#include "cimapsessionconsts.h"
+#include "cimapsession.h"
+#include "cimapsyncmanager.h"
+#include "cimapfolder.h"
+#include "cimaplogger.h"
+#include "imappaniccodes.h"
+#include "cimapfolderinfo.h"
+#include <imapset.h>
+
+#include "mobilitytestmtmapi.h"
+
+_LIT8(KMessageDataItem, "+FLAGS");
+_LIT8(KDeleteValue,	 "\\Deleted");
+
+CImapCompoundCopyToLocal::CImapCompoundCopyToLocal(CImapSyncManager& aSyncManager, CMsvServerEntry& aServerEntry, CImapSettings& aImapSettings, CImapMailStore& aImapMailStore, TBool aIsMove, const TMsvId aDestination)
+ : CImapCompoundBase(aSyncManager, aServerEntry, aImapSettings), 
+   iMailStore(aImapMailStore), iIsMove(aIsMove), iDestinationFolderId(aDestination)
+	{
+	iPopulateCommand = EFalse;
+	UpdatePartialMailInfoToDefaults(aDestination);
+	}
+
+CImapCompoundCopyToLocal::CImapCompoundCopyToLocal(CImapSyncManager& aSyncManager, CMsvServerEntry& aServerEntry, CImapSettings& aImapSettings, CImapMailStore& aImapMailStore, TBool aIsMove, const TMsvId aDestination, const TImImap4GetPartialMailInfo& aGetPartialMailInfo)
+ : CImapCompoundBase(aSyncManager, aServerEntry, aImapSettings), 
+   iMailStore(aImapMailStore), iIsMove(aIsMove), iDestinationFolderId(aDestination), iGetPartialMailInfo(aGetPartialMailInfo)
+	{
+	iPopulateCommand = ETrue;
+	}
+
+CImapCompoundCopyToLocal::~CImapCompoundCopyToLocal()
+	{
+	iOutMessageFlagInfo.Close();
+	delete iSourceSel;
+	delete iBodyFetcher;
+	delete iMoveEntry;
+	}
+	
+CImapCompoundCopyToLocal* CImapCompoundCopyToLocal::NewL(CImapSyncManager& aSyncManager,
+														 CMsvServerEntry& aServerEntry, 
+														 CImapSettings& aImapSettings, 
+														 CImapMailStore& aImapMailStore, 
+														 TBool aIsMove, 
+														 const CMsvEntrySelection& aSourceSel,
+														 const TMsvId aDestination)
+	{
+	CImapCompoundCopyToLocal* self = new (ELeave) CImapCompoundCopyToLocal(aSyncManager, aServerEntry, aImapSettings, aImapMailStore, aIsMove, aDestination);
+	CleanupStack::PushL(self);
+	self->ConstructL(aSourceSel);
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+CImapCompoundCopyToLocal* CImapCompoundCopyToLocal::NewL(CImapSyncManager& aSyncManager, CMsvServerEntry& aServerEntry, CImapSettings& aImapSettings, CImapMailStore& aImapMailStore, TBool aIsMove, const CMsvEntrySelection& aSourceSel, const TMsvId aDestination, const TImImap4GetPartialMailInfo& aGetPartialMailInfo)
+	{
+	CImapCompoundCopyToLocal* self = new (ELeave) CImapCompoundCopyToLocal(aSyncManager, aServerEntry, aImapSettings, aImapMailStore, aIsMove, aDestination, aGetPartialMailInfo);
+	CleanupStack::PushL(self);
+	self->ConstructL(aSourceSel);
+	CleanupStack::Pop(self);
+	return self;
+	}
+	
+/**
+Secondary construction
+*/	
+void CImapCompoundCopyToLocal::ConstructL(const CMsvEntrySelection& aSourceSel)
+	{
+	// Local copy of the selection of messages to copy
+	iSourceSel=new (ELeave) CMsvEntrySelection;
+	
+	// Check the source selection for acceptable message types/parts
+	// Messages         True
+	// Handle Parts     True
+	// Handle Folders   False
+	// Check source     True
+	// Makes a local copy of the source selection in iSourceSel
+	User::LeaveIfError(CheckSelectionL(aSourceSel, iSourceSel, ETrue, ETrue, EFalse, ETrue));
+	
+	// Create an Imap Body Fetcher object
+	iBodyFetcher = CImapOpFetchBody::NewL(iSession, iSyncManager, iServerEntry, iImapSettings, iMailStore);
+	// initialise progress stats
+	iMessageSelection = iSelectionStillToCopy = iSourceSel->Count();
+	iTotalSize = CalculateDownloadSizeL(*iSourceSel);
+	
+	// Add to the active scheduler
+	CActiveScheduler::Add(this);
+	}
+
+void CImapCompoundCopyToLocal::StartOperation(TRequestStatus& aStatus, CImapSession& aSession)
+	{
+	iSession = &aSession;
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::StartOperation()");
+	iNextStep = ESetSourceMailbox;
+	Queue(aStatus);
+	CompleteSelf();
+	}
+
+/**
+Handles the compound operation state machine
+
+Sequence of steps:
+	For each message:
+		Select source mailbox
+		Fetch the message to the local mirror
+	IF copy/move to local service
+		Copy message to local service
+		IF move to local service
+			STORE /deleted flag on remote message
+			issue EXPUNGE command
+			delete local copy of the message
+	
+	
+@return ETrue if compound operation is completed, 
+		EFalse otherwise.
+*/	
+TBool CImapCompoundCopyToLocal::DoRunLoopL()
+	{
+	SetCurrentStep();
+	switch (iCurrentStep)
+		{
+		case ESetSourceMailbox: // synchronous
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(ESetSourceMailbox)");
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyToLocal1); // SELECT source folder
+
+			if (iSelectionStillToCopy > 0)
+				{
+				if (iStopForMigrate)
+					{
+					__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::Stopped for migrate");
+					iCurrentStep = ESuspendedForMigrate;
+					iNextStep = ESetSourceMailbox;
+					Complete(KErrNone);
+					return ETrue;
+					}
+				
+				// decrement iSelectionStillToCopy - it becomes the index in the list 
+				// of the current message to operate on.
+				--iSelectionStillToCopy;
+					
+				iCurrentMsgId = (*iSourceSel)[iSelectionStillToCopy];
+				if (SetSourceFolderL(iCurrentMsgId))
+					{
+					// no change in state if folder not acceptable,
+					// try again with the next message in the selection.
+					if (iSourceFolderId != NULL && iSourceFolder != NULL)
+						{
+						// When iIsMove is EFalse, we still need to have a writable mailbox 
+						// so that the server can update the \Seen message flag duriung FETCH BODY
+						
+						iNextStep=ESelectSourceMailboxRW;
+						}
+					}
+				else
+					{
+					iNextStep=EFetchMessage;
+					}
+				}
+			else
+				{
+				// All messages copied.. 
+				iNextStep=EFinished;
+				}
+			// immediately proceed to the next step
+			return EFalse;
+			}
+			
+		case ESelectSourceMailboxRW: // asynchronous
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(ESelectSourceMailboxRW)");
+			iSourceFolder->SelectL(iStatus, *iSession);
+			iNextStep=EFetchMessage;
+			SetActive();
+			break;
+			}
+
+		case ESelectSourceMailboxRO: // asynchronous
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(ESelectSourceMailboxRO)");
+			iSourceFolder->ExamineL(iStatus, *iSession);
+			iNextStep=EFetchMessage;
+			SetActive();
+			break;
+			}
+			
+		case EFetchMessage:	// asynchronous
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(EFetchMessage)");
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyToLocal2); // FETCHing the message
+			SetEntryL(iCurrentMsgId);
+			TMsvEmailEntry entry = static_cast<TMsvEmailEntry> (iServerEntry.Entry()); 
+
+			TBool doFetch(ETrue);
+
+			// Don't fetch the message if it is marked for delete
+			if (entry.DisconnectedOperation() == EDisconnectedDeleteOperation)
+				{
+				doFetch = EFalse;
+				iNextStep = ESetSourceMailbox;
+				return EFalse;
+				}
+			else
+				{
+				// Don't attempt to fetch the message if it has either been fully or 
+				// partially downloaded, unless this is a populate command.
+				if (entry.Complete() && entry.PartialDownloaded() && !iPopulateCommand)
+					{
+					doFetch = EFalse;
+					}
+				}
+
+			if (doFetch)
+				{
+				// Fetch the message...
+				iBodyFetcher->FetchBodyL(iStatus, iCurrentMsgId, iGetPartialMailInfo);
+				SetActive();
+				}
+
+			// Set next state
+			if (iDestinationFolderId == KMsvNullIndexEntryId)
+				{
+				// Populating/Copying to local mirror. When fetched, proceed to next
+				// message in the selection
+				iNextStep=ESetSourceMailbox;
+				}
+			else
+				{
+				iNextStep=(iIsMove)?EInboxDuplicateMove:EInboxDuplicateCopy;
+				}
+			break;
+			}
+		
+		case EInboxDuplicateCopy: // asynchronous
+		case EInboxDuplicateMove: // asynchronous
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(EInboxDuplicate)");
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyToLocal3); // local async copy
+			SetEntryL(iCurrentMsgId);
+			iNextStep=ELocalCopyComplete;
+
+			// CopyMessage() kicks off an asynchronous copy operation if it succeeds
+			TInt ret = CopyMessage(iCurrentMsgId, iDestinationFolderId, iIsMove);
+
+			if (ret != KErrNone) // Asynchronous copy operation failed to start
+				{
+				// Store the error in the message entry
+				TRAPD(err, MessageErrorL(iCurrentMsgId, ret));
+				if (err != KErrNone)
+					{
+					iProgressErrorCode = err;
+					}
+
+				// Move straight on to next step
+				iNextStep = ESetSourceMailbox;
+
+				return EFalse;
+				}
+			break;
+			}
+
+		case ELocalCopyComplete: // synchronous
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(ELocalCopyComplete)");
+			// The copy to local folder has completed.
+			CopyCompleteL();
+			// Delete the source message from the IMAP Service if it is a move operation, 
+			// otherwise proceed to next message in the selection.
+			iNextStep=(iIsMove)?EDeleteMessage:ESetSourceMailbox;
+			// straight on to next step...
+			return EFalse;
+			}
+
+		case EDeleteMessage: // asynchronous
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(EDeleteMessage)");
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyToLocal4); // STORE /deleted flag (move only)
+			// Deleting a message is a two-stage operation:
+			// 1. STORE the /deleted flag on the remote server.
+			// 2.a) EXPUNGE the messages 
+			//	or
+			// 2.b) CLOSE the folder and then SELECT the folder
+			// 2a) or 2b) used is dependent on the KImap4EmailExpungeFlag in CImImap4Settings.
+			// we haven't changed the TMsvId of moved messages,
+			// therefore can just use the original TmsvId to identify the UID.
+			MarkMessageForDeleteL(iCurrentMsgId);
+
+			if (iImapSettings.UseExpunge())
+				{
+				iNextStep=EExpunge;
+				}
+			else
+				{
+				iNextStep=ECloseFolder;
+				}
+			SetActive();
+			break;
+			}
+
+		case EExpunge: // asynchronous
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(EExpunge)");
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyToLocal5); // EXPUNGE
+			// Issue expunge to delete the message now.
+			iSession->ExpungeL(iStatus);
+			// This is the end of a move operation - move to EFetchMessage state
+			// to check for more messages to move.
+			iNextStep=ESetSourceMailbox;
+			SetActive();
+			break;
+			}
+
+		case ECloseFolder:
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(ECloseFolder)");
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyToLocal6); // CLOSE FOLDER
+			//Permanently removes all messages that have the deleted flag set
+			//from the currently selected mailbox
+			iSession->CloseL(iStatus);
+			iNextStep=ESelectFolderAfterClose;
+			SetActive();
+			break;
+			}
+
+		case ESelectFolderAfterClose:
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(ESelectFolderAfterClose)");
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyToLocal7); // SELECT FOLDER
+			//Selecting a mailbox so that messages in the mailbox can be accessed. 
+			iSourceFolder->SelectL(iStatus, *iSession);
+			iNextStep=ESetSourceMailbox;
+			SetActive();
+			break;
+			}
+
+		case EFinished:
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::DoRunLoopL(EFinished OK)");
+			iProgressState = TImap4GenericProgress::EIdle;
+			Complete(KErrNone);
+			return ETrue;		
+			}
+
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECopyToLocalCompoundUnexpectedState));
+			// unexpected state - complete the request
+			iProgressState = TImap4GenericProgress::EIdle;
+			return ETrue;
+			}
+		} // end of switch (iCurrentStep)
+	return EFalse;
+	}
+
+/**
+Special form of cancel, which enables message currently being fetched to be resumed.
+*/
+void CImapCompoundCopyToLocal::CancelEnableResume()
+	{
+	iResume = ETrue;
+	Cancel();
+	iResume = EFalse;
+	}
+
+/**
+May be called in case of a genuine cancel or a cancel for migrate.
+Following a genuine cancel, the compound operation will be deleted.
+Following a cancel for migrate, the compound operation will be resumed,
+so the iNextState is updated here to ensure the operation is
+correctly restarted.
+
+In either case, CMsgActive::DoCancel() is called to complete the
+user request with KErrCancel.
+
+Note that if the default CMsgActive::DoComplete() is overridden,
+then that must also be modified to handle either case described above.
+*/
+void CImapCompoundCopyToLocal::DoCancel()
+	{
+	__LOG_FORMAT((iSession->LogId(), "CImapCompoundCopyToLocal::DoCancel(iCurrentStep=%d)", iCurrentStep));	
+	switch (iCurrentStep)
+		{
+		case ESelectSourceMailboxRW:
+			{
+			iSession->Cancel();
+			iNextStep=ESelectSourceMailboxRW;	// (already set up - just need to resume here)
+			break;
+			}
+		case ESelectSourceMailboxRO:
+			{
+			iSession->Cancel();
+			iNextStep=ESelectSourceMailboxRO;	// (already set up - just need to resume here)
+			break;
+			}
+		case EFetchMessage:
+			{
+			if (iResume)
+				{
+				iBodyFetcher->CancelEnableResume();
+				}
+			else
+				{
+				iBodyFetcher->Cancel();
+				}
+			iNextStep=EFetchMessage;			// need to re-SELECT folder before resuming fetch
+			break;
+			}
+		case EInboxDuplicateCopy:
+		case EInboxDuplicateMove:
+			{
+			// outstanding request is on CMsvServerEntry
+			iMoveEntry->Cancel();
+			// cancel the copy, restart on re-connect
+			iNextStep=iCurrentStep;
+			break;
+			}
+		case EDeleteMessage:
+			{
+			iSession->Cancel();
+			iNextStep=EDeleteMessage;         	// need to re-SELECT folder before deleting
+			break;
+			}
+		case EExpunge:
+			{
+			iSession->Cancel();
+			iNextStep=EExpunge;         		// need to re-SELECT folder before deleting
+			break;
+			}
+		case ECloseFolder:
+			{
+			iSession->Cancel();
+			iNextStep=ECloseFolder;
+			break;
+			}
+		case ESelectFolderAfterClose:
+			{
+			iSession->Cancel();
+			iNextStep=ESelectFolderAfterClose;
+			break;
+			}
+		case EDeleteLocalMessage:
+		case ESetSourceMailbox:
+		case ELocalCopyComplete:
+		case EFinished:
+			{
+			// self-completed or no outstanding request.
+			break;
+			}
+		case ESuspendedForMigrate:
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECopyToLocalCompoundCancelUnexpectedState));
+			// attempt to resume from the next message in the array.
+			iNextStep=ESetSourceMailbox;
+			break;
+			}
+		} // end of switch (iCurrentStep)
+
+	if (!iCancelForMigrate)
+		{
+		// genuine cancel - update progress
+		iProgressErrorCode = KErrCancel;
+		}
+	CMsgActive::DoCancel();
+	}
+	
+void CImapCompoundCopyToLocal::Progress(TImap4CompoundProgress& aCompoundProgress)
+	{
+	if (iPopulateCommand)
+		{
+		aCompoundProgress.iGenericProgress.iOperation = TImap4GenericProgress::EPopulate;
+		}
+	else
+		{
+			
+		if (iIsMove)
+			{
+			aCompoundProgress.iGenericProgress.iOperation = TImap4GenericProgress::EMoveToLocal;
+			}
+		else
+			{
+			aCompoundProgress.iGenericProgress.iOperation = TImap4GenericProgress::ECopyToLocal;
+			}
+		}
+	// Progress through the received selection
+	aCompoundProgress.iGenericProgress.iTotalSize = iTotalSize;
+	aCompoundProgress.iGenericProgress.iMsgsToDo  = iMessageSelection;
+	aCompoundProgress.iGenericProgress.iMsgsDone  = iMessageSelection - iSelectionStillToCopy;
+	// current message progress
+	iBodyFetcher->Progress(aCompoundProgress.iGenericProgress);
+	
+	// Put error into progress buffer
+	if( aCompoundProgress.iGenericProgress.iErrorCode == KErrNone )
+		{
+		aCompoundProgress.iGenericProgress.iErrorCode = iProgressErrorCode;
+		}
+	}
+
+/**
+Find the parent folder
+@return ETrue if the source folder has changed since the last message operated on
+always true on the first call after creation.
+@post iSourceFolderId - The TMsvId of the source folder for the message. May be NULL
+@post iSourceFolder - CImapFolder pointer for the parent folder. May be NULL.
+*/
+TBool CImapCompoundCopyToLocal::SetSourceFolderL(const TMsvId aMessage)
+	{
+	TMsvId folderId = KMsvNullIndexEntryId;
+	TMsvId parentFolder = FindFolderL(aMessage);
+	CImapFolderInfo* selectedFolder = iSession->SelectedFolderInfo();
+	if(selectedFolder != NULL)
+		{
+		folderId = selectedFolder->MsvId() ;
+		}
+	if (parentFolder==iSourceFolderId)
+		{
+		// Same folder as last message copied.
+		return EFalse;
+		}
+	// otherwise, update to the new folder.	
+	iSourceFolderId = parentFolder;
+	if (iSourceFolderId!=NULL)
+		{
+		iSourceFolder = iSyncManager.GetFolderL(iSourceFolderId);
+		}
+		//Check the same INBOX folder is selected before
+	if (parentFolder== folderId)
+		{
+		return EFalse;
+		}
+	return ETrue;
+	}
+	
+/**
+Updates iGetPartialMailInfo to default values - (fetch whole message and attachments
+without limits). This is used for copy/move to local operations (populate operations
+uses a user-specified partial mail info object.
+
+@param aDestination - destination folder.
+@internalTechnology
+*/
+void CImapCompoundCopyToLocal::UpdatePartialMailInfoToDefaults(TMsvId aDestination)
+	{	
+	iGetPartialMailInfo.iTotalSizeLimit= KMaxTInt;
+	iGetPartialMailInfo.iBodyTextSizeLimit = KMaxTInt;
+	iGetPartialMailInfo.iAttachmentSizeLimit = KMaxTInt;
+	iGetPartialMailInfo.iGetMailBodyParts = EGetImap4EmailBodyTextAndAttachments;		 
+	iGetPartialMailInfo.iPartialMailOptions=ENoSizeLimits;
+	iGetPartialMailInfo.iDestinationFolder=aDestination;
+	}
+	
+/**
+Copy or move a message to the specified local service folder.
+@param aSource				The message to copy or move
+@param aDestinationFolder   The id of the destination folder
+@param aRemoveOriginal      ETrue if this is a move, EFalse for a copy.
+*/
+TInt CImapCompoundCopyToLocal::CopyMessage(const TMsvId aSource, const TMsvId aDestinationFolder, const TBool aRemoveOriginal)
+	{
+	TRAPD(err,CopyMessageL(aSource, aDestinationFolder, aRemoveOriginal));
+	if (err!=KErrNone)
+		{
+		// park moveentry if it fails to get going
+		if (iMoveEntry)
+			{
+			iMoveEntry->SetEntry(NULL);
+			}
+		
+		__LOG_FORMAT((iSession->LogId(), " CopyMessage() failed, err=", err));
+		}
+
+	return err;
+	}
+
+void CImapCompoundCopyToLocal::CopyMessageL(const TMsvId aSource, const TMsvId aDestinationFolder, const TBool aRemoveOriginal)
+	{
+	__LOG_FORMAT((iSession->LogId(), "CImapCompoundCopyToLocal::CopyMessageL(%x (in %x) to %x)", aSource, iSourceFolderId, aDestinationFolder));	
+
+	// Get a moveentry if we don't already have one
+	if (!iMoveEntry)
+		{
+		// Get a MoveEntry: we need to ask for one as a child of this entry, so
+		// move it to the root and ask for a child in the local service, which should
+		// always be there.
+		SetEntryL(KMsvRootIndexEntryId);
+
+		// Get child
+		iMoveEntry=iServerEntry.NewEntryL(KMsvLocalServiceIndexEntryId);
+		}
+
+	// Do the move, using the iMoveEntry CMsvServerEntry object
+	SetEntryL(aSource);
+	User::LeaveIfError(iMoveEntry->SetEntry(iServerEntry.Entry().Parent()));
+	
+	// Set context to null because the move / copy tries to lock each entry,
+	// and it will fail if we have it locked already
+	SetEntryL(KMsvNullIndexEntryId);
+
+	aRemoveOriginal?
+		iMoveEntry->MoveEntryL(aSource,aDestinationFolder,iStatus):
+		iMoveEntry->CopyEntryL(aSource,aDestinationFolder,iStatus);
+	if (!IsActive()) SetActive();
+	}
+
+/**
+Called when the copy operation is complete.
+Clears the new flag on the message entry just copied/moved.
+*/
+void CImapCompoundCopyToLocal::CopyCompleteL()
+	{
+	// Park the move entry again
+	User::LeaveIfError(iMoveEntry->SetEntry(NULL));
+
+	SetEntryL(iCurrentMsgId);
+	TMsvEntry entry=iServerEntry.Entry();
+	entry.SetNew(EFalse);
+	iServerEntry.ChangeEntry(entry);
+	}
+
+/**
+Marks message specified by aTarget for delete locally,
+and issues STORE command to mark it for delete remotely.
+
+Issues an asynchronous request to the CImapSession. SetActive()
+must be called after calling this function.
+
+@param aTarget The message to be marked for delete
+*/
+void CImapCompoundCopyToLocal::MarkMessageForDeleteL(const TMsvId& aTarget)
+	{
+	// Move to the entry in question
+	SetEntryL(aTarget);
+
+	// Set deleted flag on this entry
+	TMsvEmailEntry entry=iServerEntry.Entry();
+	entry.SetDeletedIMAP4Flag(ETrue);
+	User::LeaveIfError(iServerEntry.ChangeEntry(entry));
+
+	RArray<TUint> uidArray;
+	CleanupClosePushL(uidArray);
+	
+	uidArray.AppendL(entry.UID());
+	HBufC8* uids = CImapSession::CreateSequenceSetLC(uidArray);
+	
+	iSession->StoreL(iStatus, *uids, KMessageDataItem, KDeleteValue, ETrue, iOutMessageFlagInfo);
+	CleanupStack::PopAndDestroy(2, &uidArray);	// uids, uidArray
+	}
+
+/**
+Handles NO/BAD responses according to current step.
+Negative server responses are not fatal - the error is saved in
+the message currently being operated on and the state machine pushed
+on to process the next message in the requested selection.
+
+@return KErrNone if the error has been handled
+		Completion error code otherwise.
+*/
+TInt CImapCompoundCopyToLocal::ProcessNegativeServerResponse()
+	{
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::ProcessNegativeServerResponse");
+	TInt err = iStatus.Int();
+	switch (iCurrentStep)
+		{
+		case ESelectSourceMailboxRW:
+		case ESelectSourceMailboxRO:
+		case ESelectFolderAfterClose:
+			{
+			if (err == KErrImapNo)
+				{
+				err = KErrNotFound;
+				}
+			} // fall through
+
+		case EDeleteMessage:
+		case EExpunge:
+		case ECloseFolder:
+		case EFetchMessage:
+			{
+			// save the error with the associated message
+			TRAP_IGNORE(MessageErrorL(iCurrentMsgId, err));
+			// Skip to the next message or finish
+			iNextStep = (iSelectionStillToCopy>0)?ESetSourceMailbox:EFinished;
+			break;
+			}
+			
+		case EInboxDuplicateCopy:
+		case EInboxDuplicateMove:
+		case EDeleteLocalMessage:
+		case ESetSourceMailbox:
+		case ELocalCopyComplete:
+		case EFinished:
+		case ESuspendedForMigrate:
+		default:
+			{
+			// positive error code not expected,
+			// self-completed states or no outstanding request.
+			TImapServerPanic::ImapPanic(TImapServerPanic::ECopyToLocalCompoundUnexpectedState);
+			break;
+			}
+		} // end of switch (iCurrentStep)
+	iProgressErrorCode = err;
+	return KErrNone;
+	}
+	
+
+/**
+Called to resume the compound operation following a bearer migration.
+*/
+void CImapCompoundCopyToLocal::ResumeOperationL(TRequestStatus& aStatus, CImapSession& aSession)
+	{
+	iSession = &aSession;
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyToLocal::Resuming");
+	__ASSERT_DEBUG(iCurrentStep==ESuspendedForMigrate, TImapServerPanic::ImapPanic(TImapServerPanic::ECopyToLocalCompoundUnexpectedState));
+	iStopForMigrate = EFalse;
+
+	// Switch on next step - some "next steps" require a SELEECT first...
+	switch (iNextStep)
+		{
+		case ESetSourceMailbox: 	  // clean start on the next message in the array
+		case ESelectSourceMailboxRW:  // current message already selected... continue...
+		case ESelectSourceMailboxRO:
+		case ESelectFolderAfterClose:
+			{
+			// just return to the main state machine
+			CompleteSelf();
+			break;
+			}
+		case EFetchMessage:
+		case EDeleteMessage:
+		case EExpunge:
+		case ECloseFolder:
+		case EInboxDuplicateCopy: 
+		case EInboxDuplicateMove: // need to be selected to delete the message if this is a move
+			{
+			// select the source folder before returning to main state machine
+			iSourceFolder->SelectL(iStatus, *iSession);
+			iCurrentStep = ESelectSourceMailboxRW;
+			SetActive();
+			break;
+			}
+		case ELocalCopyComplete:
+		case EDeleteLocalMessage:
+		case EFinished:
+			// not expected
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECopyToLocalCompoundCancelUnexpectedState));
+			// abandon the compound operation
+			iNextStep=EFinished;
+			CompleteSelf();
+			break;
+			}
+		} // end switch (iNextStep)
+	Queue(aStatus);
+	}
+
+/**
+Removes matching messages from the array of messages to be fetched.
+This is called when a populate or copy to local operation is requested while
+a background sync operation is in progress. It is used to remove
+messages from the array of messages to be auto-fetched that are going to
+be fetched due to the populate request.
+
+Note that this does not impact messages that have already been auto-fetched, or
+messages that are in the process of being fetched as this function is called.
+
+@param aSelection - the selection of messages to be removed from the list
+*/
+void CImapCompoundCopyToLocal::RemoveFromSelection(CMsvEntrySelection& aDeleteSel)
+	{
+	// Check each entry in the delete selection...
+	TInt source;
+	for (TInt del=0;del<aDeleteSel.Count();++del)
+		{
+		source = iSourceSel->Find(aDeleteSel[del]);
+		if (source!=KErrNotFound)
+			{
+			if (source < iSelectionStillToCopy)
+				{
+				// message has not yet been auto-fetched, remove it from the list.
+				// note that iSelection starts at iSourceSel.Count() and counts down...
+				iSourceSel->Delete(source);
+				// update the fetch index.
+				--iSelectionStillToCopy;
+				}
+			// otherwise message has already been auto-fetched. No action necessary.
+			}
+		}
+	}