email/imap4mtm/imapprotocolcontroller/src/cimapcompoundcopywithinservice.cpp
changeset 0 72b543305e3a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/imap4mtm/imapprotocolcontroller/src/cimapcompoundcopywithinservice.cpp	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,684 @@
+// 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 "cimapcompoundcopywithinservice.h"
+#include "cimapsessionconsts.h"
+#include "cimapsyncmanager.h"
+#include "cimapfolder.h"
+#include "cimaplogger.h"
+#include "imappaniccodes.h"
+
+#include "mobilitytestmtmapi.h"
+
+_LIT8( KMessageDataItem, "+FLAGS" );
+_LIT8( KDeleteValue,	 "\\Deleted" );
+
+CImapCompoundCopyWithinService::CImapCompoundCopyWithinService( CImapSyncManager& aSyncManager, 
+																CMsvServerEntry& aServerEntry, 
+																CImapSettings& aImapSettings, 
+																TBool aIsMove, 
+															  	const TMsvId aDestination )
+ : CImapCompoundBase(aSyncManager, aServerEntry, aImapSettings),
+ 	iIsMove(aIsMove), iDestinationId(aDestination)
+	{
+	
+	}
+
+CImapCompoundCopyWithinService::~CImapCompoundCopyWithinService()
+	{
+	iOutMessageFlagInfo.Close();
+	iMessageUids.Reset();
+	delete iUidSeq;
+	delete iSourceSel;
+	}
+	
+CImapCompoundCopyWithinService* CImapCompoundCopyWithinService::NewL( CImapSyncManager& aSyncManager, 
+																	  CMsvServerEntry& aServerEntry, 
+																	  CImapSettings& aImapSettings,
+																	  TBool aIsMove, 
+															  		  const CMsvEntrySelection& aSourceSel,
+															  		  const TMsvId aDestination )
+	{
+	CImapCompoundCopyWithinService* self = new (ELeave) CImapCompoundCopyWithinService( aSyncManager, 
+																						aServerEntry, 
+																						aImapSettings,
+																						aIsMove, 
+																						aDestination );
+	CleanupStack::PushL(self);
+	self->ConstructL(aSourceSel);
+	CleanupStack::Pop(self);
+	return self;
+	
+	}
+	
+void CImapCompoundCopyWithinService::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
+	TInt err = CheckSelectionL(aSourceSel, iSourceSel, ETrue, ETrue, EFalse, ETrue);
+	if (err==KErrNone)
+		{
+		// reset the stats
+		iMsgsCopied = 0;
+		iMsgsDone = 0;
+		iMsgsToDo = aSourceSel.Count();
+		iTotalSize = CalculateDownloadSizeL(*iSourceSel);
+		}
+
+	// Add to the active scheduler
+	CActiveScheduler::Add(this);
+	}
+
+void CImapCompoundCopyWithinService::StartOperation(TRequestStatus& aStatus, CImapSession& aSession)
+	{
+	iSession = &aSession;
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyWithinService::StartOperation()");
+	iNextStep = ECheckDestinationMailbox;
+	Queue(aStatus);
+	CompleteSelf();
+	}
+
+/**
+Handles the compound operation state machine
+
+@return ETrue if compound operation is completed, 
+		EFalse otherwise.
+*/	
+TBool CImapCompoundCopyWithinService::DoRunLoopL( )
+	{
+	SetCurrentStep();
+	switch (iCurrentStep)
+		{
+		case ECheckDestinationMailbox:		// synchronous
+			{
+			// Obtain pointer to CImapFolder object for the destination folder.
+			iDestinationFolder = iSyncManager.GetFolderL(iDestinationId);
+			if (iDestinationFolder==NULL)
+				{
+				// note the error code for the progress report and complete
+				iProgressErrorCode = KErrNotFound;
+				iNextStep = EFinished;
+				}
+			else
+				{
+				iNextStep = ESetSourceMailbox;
+				}
+			// go to the next step
+			return EFalse;
+			}
+		
+		case ESetSourceMailbox:				// synchronous
+			{
+			// State machine returns to this state after performing a copy/move
+			// on messages in the selection in a single mailbox. The progress is
+			// incremented here for messages that have been copied. If all messages
+			// have been moved/copied the state machine progresses to the
+			// synchronisation of the destination folder.
+			IncrementProgress(iMsgsCopied);
+			
+			// starting a new copy operation...
+			iMsgsCopied = 0;
+			
+			if (iSourceSel->Count() > 0)
+				{
+				if (iStopForMigrate)
+					{
+					__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyWithinService::Stopped for migrate");
+					iCurrentStep = ESuspendedForMigrate;
+					iNextStep = ESetSourceMailbox;
+					Complete(KErrNone);
+					return ETrue;
+					}
+					
+				iCurrentMsgId = (*iSourceSel)[0];
+				SetSourceFolderL(iCurrentMsgId);
+				if (iSourceFolderId != NULL && iSourceFolder != NULL)
+					{
+					iNextStep = (iIsMove) ? ESelectSourceMailboxRW : ESelectSourceMailboxRO;
+					}
+				else
+					{
+					// no change in state if folder not acceptable,
+					// try again with the next message in the selection.
+					iNextStep = ESetSourceMailbox;
+					IncrementProgress(1);
+					}
+				}
+			else
+				{
+				// All messages copied or moved... 
+				// Sync the destination folder
+				iNextStep = ESelectDestinationMailboxRO;
+				}
+			// go to the next step
+			return EFalse;
+			}
+			
+		case ESelectSourceMailboxRW:			// asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService1);	// SELECT source mailbox
+			iSourceFolder->SelectL(iStatus, *iSession);
+			iNextStep = ECopyMessage;
+			SetActive();
+			break;
+			}
+
+		case ESelectSourceMailboxRO:
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService1);	// EXAMINE source mailbox
+			iSourceFolder->ExamineL(iStatus, *iSession);
+			iNextStep = ECopyMessage;
+			SetActive();
+			break;
+			}
+			
+		case ECopyMessage:						// asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService2);	// COPY message(s)
+			// issue the COPY command - copies all messages in the selection
+			// that have the same parent folder as the first in the selection.
+			CopyMessagesL();
+			if (iIsMove)
+				{
+				iProgressState = TImap4GenericProgress::EMoving;
+				iNextStep = EDeleteMessage;
+				}
+			else
+				{
+				// on completion, return to SetSourceMailbox to check for
+				// more messages to copy (from other source folders)
+				iProgressState = TImap4GenericProgress::ECopying;
+				iNextStep = ESetSourceMailbox;
+				}
+			SetActive();
+			break;
+			}
+			
+		case EDeleteMessage: // asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService3);	// 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.
+			MarkMessageForDeletesL();
+
+			if (iImapSettings.UseExpunge())
+				{
+				iNextStep=EExpunge;
+				}
+			else
+				{
+				iNextStep=ECloseFolder;
+				}
+
+			SetActive();
+			break;
+			}
+
+		case EExpunge: // asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService4);	// EXPUNGE source folder (move only)
+			iSession->ExpungeL(iStatus);
+			iNextStep = EDeleteLocalMessage;
+			SetActive();
+			break;
+			}
+
+		case ECloseFolder:
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService7);	// CLOSE FOLDER (move only)
+			//Permanently removes all messages that have the deleted flag set
+			//from the currently selected mailbox
+			iSession->CloseL(iStatus);
+			iNextStep=ESelectFolderAfterClose;
+			SetActive();
+			break;
+			}
+
+		case ESelectFolderAfterClose:
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService8);	// SELECT FOLDER (move only)
+			//Selecting a mailbox so that messages in the mailbox can be accessed. 
+			iSourceFolder->SelectL(iStatus, *iSession);
+			iNextStep=EDeleteLocalMessage;
+			SetActive();
+			break;
+			}
+
+		case EDeleteLocalMessage: // synchonous
+			{
+			// Delete the messages locally
+			DeleteMovedMessagesL();
+			// immediately proceed to the next step
+			iNextStep = ESetSourceMailbox;			
+			return EFalse;
+			}
+			
+		case ESelectDestinationMailboxRO:
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService5);	// SELECT destination folder
+			iDestinationFolder->ExamineL(iStatus, *iSession);
+			iNextStep = ENewSyncFolder;
+			SetActive();
+			break;
+			}
+			
+		case ENewSyncFolder:
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapCopyWithinService6);	// sync'ing destination folder
+			// if the current folder hasn't actually changed then don't
+			// bother with the Sync
+			iNextStep = EFinished;
+			if (iDestinationFolder->Changed(*iSession))
+				{
+				iSyncProgressState=TImap4SyncProgress::ESyncOther;
+				iDestinationFolder->SynchroniseL(iStatus, *iSession, ETrue, 0);
+				SetActive();
+				}
+			break;
+			}
+			
+		case EFinished:
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyWithinService::Completing OK");
+			iProgressState = TImap4GenericProgress::EIdle;
+			iSyncProgressState = TImap4SyncProgress::EIdle;
+			
+			Complete(KErrNone);
+			return ETrue;
+			}
+			
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECopyWithinServiceCompoundUnexpectedState));
+			// unexpected state - complete the request
+			iProgressState = TImap4GenericProgress::EIdle;
+			return ETrue;
+			}
+		} // end switch (iCurrentStep)
+	return 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 CImapCompoundCopyWithinService::DoCancel()
+	{
+	switch (iCurrentStep)
+		{
+		case ESelectSourceMailboxRW:
+			{
+			iSession->Cancel();	
+			iNextStep = ESelectSourceMailboxRW;	// just resume
+			break;
+			}
+		case ESelectSourceMailboxRO:
+			{
+			iSession->Cancel();
+			iNextStep = ESelectSourceMailboxRO; // just resume
+			break;
+			}
+		case ECopyMessage:
+			{
+			iSession->Cancel();
+			iMsgsCopied = 0;					// reset copied count
+			iNextStep = ECopyMessage;			// reSELECT source
+			break;
+			}
+		case EDeleteMessage:
+			{
+			iSession->Cancel();
+			iNextStep = EDeleteMessage;			// reSELECT source
+			break;
+			}	
+		case EExpunge:
+			{
+			iSession->Cancel();
+			iNextStep = EExpunge;				// reSELECT source
+			break;
+			}
+		case ECloseFolder:
+			{
+			iSession->Cancel();
+			iNextStep = ECloseFolder;
+			break;
+			}
+		case ESelectFolderAfterClose:
+			{
+			iSession->Cancel();
+			iNextStep = ESelectFolderAfterClose;
+			break;
+			}
+		case ESelectDestinationMailboxRO:		
+			{
+			iSession->Cancel();
+			iNextStep = ESelectDestinationMailboxRO; // just restart
+			break;
+			}
+		case ENewSyncFolder:					
+			{
+			iDestinationFolder->Cancel();
+			iNextStep = ESelectDestinationMailboxRO; // just restart
+			break;
+			}
+		case ECheckDestinationMailbox:
+		case ESetSourceMailbox:
+		case EDeleteLocalMessage:
+		case EFinished:
+			// self-completed or no outstanding request.
+		case ESuspendedForMigrate:
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECopyWithinServiceCompoundCancelUnexpectedState));
+			iNextStep = ESetSourceMailbox;
+			// starting a new copy operation...
+			iMsgsCopied = 0;
+			break;
+			}
+		} // end switch (iCurrentStep)
+
+	if (!iCancelForMigrate)
+		{
+		// genuine cancel - update progress
+		iProgressErrorCode = KErrCancel;
+		}
+	CMsgActive::DoCancel();
+	}
+
+void CImapCompoundCopyWithinService::Progress(TImap4CompoundProgress& aCompoundProgress)
+	{
+	if (iIsMove)
+		{
+		aCompoundProgress.iGenericProgress.iOperation = TImap4GenericProgress::EMoveWithinService;
+		}
+	else
+		{
+		aCompoundProgress.iGenericProgress.iOperation = TImap4GenericProgress::ECopyWithinService;
+		}
+	
+	// Progress through the received selection
+	aCompoundProgress.iGenericProgress.iTotalSize = iTotalSize;
+	aCompoundProgress.iGenericProgress.iMsgsToDo  = iMsgsToDo;
+	aCompoundProgress.iGenericProgress.iMsgsDone  = iMsgsDone;
+	
+	aCompoundProgress.iSyncProgress.iState=iSyncProgressState;
+	
+	//if currently syncing then get the latest progress
+	if(iSyncProgressState!=TImap4SyncProgress::EIdle)
+		{
+		iDestinationFolder->Progress(aCompoundProgress.iSyncProgress);
+		}
+	
+		
+	// Put error into progress buffer
+	if( aCompoundProgress.iGenericProgress.iErrorCode == KErrNone )
+		{
+		aCompoundProgress.iGenericProgress.iErrorCode = iProgressErrorCode;
+		}
+	}
+
+/**
+Builds an array of messages within the same source folder that can be copied
+with a single command to the remote server.
+*/
+void CImapCompoundCopyWithinService::CopyMessagesL( )
+	{
+	iMessageUids.Reset();
+	// build an array of the UIDs to copy,
+	TInt  i = 0;
+	TInt  count = iSourceSel->Count();
+
+	TBool sameParent = ETrue;
+	while(i<count && sameParent)
+		{
+		SetEntryL((*iSourceSel)[i]);
+		TMsvEmailEntry entry = iServerEntry.Entry();
+		if (entry.Parent()==iSourceFolderId)
+			{
+			TUint id = entry.UID();
+			iMessageUids.AppendL(id);
+			++iMsgsCopied;
+			}
+		else
+			{
+			sameParent=EFalse;
+			}
+		++i;
+		}
+		
+	// store the UID sequence array - we will need to reuse it to
+	// STORE the delete flag if we are performing a move.
+	delete iUidSeq;
+	iUidSeq = CImapSession::CreateSequenceSetLC(iMessageUids);
+	CleanupStack::Pop(iUidSeq);
+
+	// issue the store command
+	iSession->CopyL(iStatus, *iUidSeq, iDestinationFolder->FullFolderPathL());
+	}
+
+/*
+Marks the messages that have been copied as deleted locally.
+
+Takes the uids in iMessageUids and marks messages for delete locally,
+and issues STORE command to mark it for delete remotely.
+*/
+void CImapCompoundCopyWithinService::MarkMessageForDeletesL()
+	{
+	for (TInt i=0 ; i<iMsgsCopied ; ++i)
+		{
+		// Move to the entry in question
+		SetEntryL((*iSourceSel)[i]);
+		
+		// Set deleted flag on this entry
+		TMsvEmailEntry entry = iServerEntry.Entry();
+		entry.SetDeletedIMAP4Flag(ETrue);
+		User::LeaveIfError(iServerEntry.ChangeEntry(entry));
+		}
+
+	iSession->StoreL( iStatus, 
+					  *iUidSeq, 
+					  KMessageDataItem, 
+					  KDeleteValue, 
+					  ETrue, 
+					  iOutMessageFlagInfo );
+	}
+
+/**
+Deletes the local copy of messages that have been expunged on the remote server.
+*/
+void CImapCompoundCopyWithinService::DeleteMovedMessagesL()
+	{
+	// move to parent
+	SetEntryL(iSourceFolderId);
+	
+	TInt messagesToDelete = iMsgsCopied - iMsgsDone ;
+	for (TInt i=0 ; i<messagesToDelete ; ++i)
+		{
+		TMsvId msgId = (*iSourceSel)[i];
+		TInt err (iServerEntry.DeleteEntry(msgId));
+		
+		// Do not leave when entry is in use 
+		if(err == KErrInUse)
+			{
+			// Dont leave if err = KErrInUse
+			}
+		else
+			{
+			User::LeaveIfError(err);
+			}
+		}
+	}
+
+
+/**
+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 CImapCompoundCopyWithinService::SetSourceFolderL(const TMsvId aMessage)
+	{
+	TMsvId parentFolder = FindFolderL(aMessage);
+	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);
+		}
+	return ETrue;
+	}
+
+/**
+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 CImapCompoundCopyWithinService::ProcessNegativeServerResponse()
+	{
+	TInt err = iStatus.Int();
+	switch (iCurrentStep)
+		{
+		case ESelectSourceMailboxRW:
+		case ESelectSourceMailboxRO:
+		case ESelectDestinationMailboxRO:
+		case ESelectFolderAfterClose:
+			{
+			if (err == KErrImapNo)
+				{
+				err = KErrNotFound;
+				}
+			} // fall through
+		case ECopyMessage:
+		case EDeleteMessage:
+		case EExpunge:
+		case ECloseFolder:
+		case ENewSyncFolder:
+			{
+			// save the error with the associated message
+			TRAP_IGNORE(MessageErrorL(iCurrentMsgId, err));
+			// Skip to the next message, or finish
+			iNextStep = EFinished;
+			break;
+			}
+		case ESuspendedForMigrate:
+		case ESetSourceMailbox:
+		case EFinished:
+		default:
+			{
+			// positive error code not expected,
+			// self-completed states or no outstanding request.
+			TImapServerPanic::ImapPanic(TImapServerPanic::ECopyWithinServiceCompoundUnexpectedState);
+			break;
+			}
+		} // end switch (iCurrentStep)
+	iProgressErrorCode = err;
+	return KErrNone;
+	}
+
+
+/**
+Increments the progress count and removes the copied/moved messages
+from the source selection.
+
+@param aNum - number of message copied/moved/otherwise managed.
+*/
+void CImapCompoundCopyWithinService::IncrementProgress(TInt aNum)
+	{
+	if (aNum>0)
+		{
+		iMsgsDone+=aNum;
+		iSourceSel->Delete(0,aNum);
+		}
+	}
+
+
+/**
+Called to resume the compound operation following a bearer migration.
+*/
+void CImapCompoundCopyWithinService::ResumeOperationL(TRequestStatus& aStatus, CImapSession& aSession)
+	{
+	iSession = &aSession;
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundCopyWithinService::Resuming");
+	__ASSERT_DEBUG(iCurrentStep==ESuspendedForMigrate, TImapServerPanic::ImapPanic(TImapServerPanic::ECopyWithinServiceCompoundUnexpectedState));
+	iStopForMigrate = EFalse;
+
+	switch (iNextStep)
+		{
+		case ESelectSourceMailboxRW:
+		case ESelectSourceMailboxRO:
+		case ESelectDestinationMailboxRO:		
+		case ENewSyncFolder:
+		case ESelectFolderAfterClose:
+			{
+			CompleteSelf();
+			break;
+			}
+		case ECopyMessage:
+		case EDeleteMessage:
+		case EExpunge:
+		case ECloseFolder:
+			{
+			iSourceFolder->SelectL(iStatus, *iSession);
+			iCurrentStep = ESelectSourceMailboxRW;
+			SetActive();
+			break;
+			}
+		case ECheckDestinationMailbox:
+		case ESetSourceMailbox:
+		case EDeleteLocalMessage:
+		case EFinished:
+			// self-completed or no outstanding request.
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ECopyWithinServiceCompoundCancelUnexpectedState));
+			// abandon the compound operation
+			iNextStep=EFinished;
+			CompleteSelf();
+			break;
+			}
+		} // end switch (iNextStep)
+	Queue(aStatus);
+	}
+