email/imap4mtm/imapprotocolcontroller/src/cimapcompounddelete.cpp
changeset 0 72b543305e3a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/imap4mtm/imapprotocolcontroller/src/cimapcompounddelete.cpp	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,491 @@
+// 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 "cimapcompounddelete.h"
+#include "cimapsession.h"
+#include "cimapfolder.h"
+#include "cimapsessionconsts.h"
+#include "cimaplogger.h"
+#include "imappaniccodes.h"
+
+#include "mobilitytestmtmapi.h"
+
+_LIT8(KImapAddFlags, "+FLAGS");
+_LIT8(KImapFlagDeleted, "(\\Deleted)");
+
+CImapCompoundDelete::CImapCompoundDelete(CImapSyncManager& aSyncManager,
+						 				 CMsvServerEntry& aServerEntry,
+						 				 CImapSettings& aImapSettings) :
+	CImapCompoundBase(aSyncManager, aServerEntry, aImapSettings)
+	{
+	
+	}
+
+CImapCompoundDelete::~CImapCompoundDelete()
+	{
+	delete iSourceSel;
+	iMessageUids.Reset();
+	iFlagInfoResponseArray.Reset();
+	}
+	
+CImapCompoundDelete* CImapCompoundDelete::NewL(CImapSyncManager& aSyncManager,
+						 				  	   CMsvServerEntry& aServerEntry,
+						 				  	   CImapSettings& aImapSettings, 
+						 				  	   const CMsvEntrySelection& aSourceSel)
+	{
+	CImapCompoundDelete* self = new (ELeave) CImapCompoundDelete(aSyncManager,
+																 aServerEntry,
+																 aImapSettings );
+	CleanupStack::PushL(self);
+	self->ConstructL(aSourceSel);
+	CleanupStack::Pop(self);
+	return self;
+	}
+	
+void CImapCompoundDelete::ConstructL(const CMsvEntrySelection& aSourceSel)
+	{
+	iSourceSel=new (ELeave) CMsvEntrySelection;
+	
+
+	// Check the source selection contains only messages
+	// Messages         True
+	// Handle Parts     False
+	// Handle Folders   False
+	// Check source     True
+	// Makes a local copy of the source selection in iSourceSel
+	CheckSelectionL(aSourceSel, iSourceSel, ETrue, EFalse, EFalse, ETrue);
+
+	// Initialise the progress counters	
+	iProgressMsgsToDo=iSourceSel->Count();
+	iProgressMsgsDone=0;
+
+	// Add to the active scheduler
+	CActiveScheduler::Add(this);
+	}
+
+void CImapCompoundDelete::StartOperation(TRequestStatus& aStatus, CImapSession& aSession)
+	{
+	iSession = &aSession;
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundDelete::StartOperation()");
+	iNextStep = ESelectSourceMailboxRW;
+	Queue(aStatus);
+	CompleteSelf();
+	}
+
+/**
+Handles the compound operation state machine
+
+@return ETrue if compound operation is completed, 
+		EFalse otherwise (will be called again, unless active)
+*/
+TBool CImapCompoundDelete::DoRunLoopL()
+	{
+	SetCurrentStep();
+	switch (iCurrentStep)
+		{
+		case ESelectSourceMailboxRW: // asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapDelete1); // select source folder
+			// Anything to do?
+			if (iSourceSel->Count()==0)
+				{
+				iNextStep = EFinished;
+				return EFalse;
+				}
+			
+			// suspend operation if stop for migrate has been requested
+			if (iStopForMigrate)
+				{
+				__LOG_TEXT(iSession->LogId(), "CImapCompoundDelete::Stopped for migrate");
+				iCurrentStep = ESuspendedForMigrate;
+				iNextStep = ESelectSourceMailboxRW;
+				Complete(KErrNone);
+				return ETrue;
+				}
+			
+			// Find the parent folder for the first entry in the array
+			iParentFolderId = FindFolderL((*iSourceSel)[0]);
+			iParentFolder = iSyncManager.GetFolderL(iParentFolderId);
+			if (iParentFolder==NULL)
+				{
+				// Try again with the next message
+				// Delete the first entry in the source selection
+				iSourceSel->Delete(0, 1);
+				// increment progress counter
+				++iProgressMsgsDone;
+				return EFalse;
+				}
+			
+			// reset the message UID list
+			iMessageUids.Reset();
+
+			__LOG_FORMAT((iSession->LogId(), "Deleting message %x in folder %x", (*iSourceSel)[0], iParentFolderId));
+
+			iParentFolder->SelectL(iStatus, *iSession);
+			iProgressState = TImap4GenericProgress::ESelecting;
+			iNextStep = EDeleteMessage;
+			SetActive();
+			break;
+			}
+
+		case EDeleteMessage: // asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapDelete2); // issue the STORE /deleted command
+			// build an array of the UIDs to delete,
+			// mark local IMAP \deleted flag.
+			TInt  i = 0;
+			TInt  count = iSourceSel->Count();
+			TBool sameParent = ETrue;
+			while (i<count && sameParent)
+				{
+				SetEntryL((*iSourceSel)[i]);
+				TMsvEmailEntry entry = iServerEntry.Entry();
+				if (entry.Parent()==iParentFolderId)
+					{
+					TUint id = entry.UID();
+					iMessageUids.AppendL(id);
+
+					// Set deleted flag on local entry
+					entry.SetDeletedIMAP4Flag(ETrue);
+					User::LeaveIfError(iServerEntry.ChangeEntry(entry));
+					++iSelectionToDelete;
+					}
+				else
+					{
+					sameParent=EFalse;
+					}
+				++i;
+				}
+
+			HBufC8* uidSeq = CImapSession::CreateSequenceSetLC(iMessageUids);
+
+			// issue the store command
+			iSession->StoreL(iStatus, uidSeq->Des(), KImapAddFlags, KImapFlagDeleted, ETrue, iFlagInfoResponseArray);
+			iProgressState = TImap4GenericProgress::EDeleting;
+			CleanupStack::PopAndDestroy(uidSeq);
+			if (iImapSettings.UseExpunge())
+				{
+				iNextStep=EExpunge;
+				}
+			else
+				{
+				iNextStep=ECloseFolder;
+				}
+			SetActive();
+			break;
+			}
+
+		case EExpunge: // asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapDelete3); // issue the EXPUNGE command
+			// Messages have been marked for delete on the remote server
+			// issue the expunge command to remove them completely
+			iSession->ExpungeL(iStatus);
+			// no change in progress state
+			iNextStep = EDeleteLocalMessage;
+			SetActive();
+			break;
+			}
+
+		case ECloseFolder:
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapDelete4); // 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:
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapDelete5); // SELECT FOLDER
+			//Selecting a mailbox so that messages in the mailbox can be accessed. 
+			iParentFolder->SelectL(iStatus, *iSession);
+			iNextStep=EDeleteLocalMessage;
+			SetActive();
+			break;
+			}
+
+		case EDeleteLocalMessage: // synchronous
+			{
+			DeleteLocalMessagesL();
+			// test for remaining messages to delete performed in state ESelectSourceMailboxRW;
+			iNextStep=ESelectSourceMailboxRW;
+			// no async request made.
+			return EFalse;
+			}
+				
+		case EFinished:	// finished
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundDelete::Completing OK");
+			iProgressState = TImap4GenericProgress::EIdle;
+			Complete(KErrNone);
+			return ETrue;
+			}
+			
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, 
+				TImapServerPanic::ImapPanic(TImapServerPanic::EDeleteCompoundUnexpectedState));
+			// unexpected state - complete the request
+			iProgressState = TImap4GenericProgress::EIdle;
+			return ETrue;
+			}
+		}
+	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 CImapCompoundDelete::DoCancel()
+	{
+	switch (iCurrentStep)
+		{
+		case ESelectSourceMailboxRW:
+			{
+			// There is an outstanding select command issued. Cancel it.
+			// Need to re-start with next step = ESelectSourceMailboxRW
+			iSession->Cancel();
+			iNextStep = ESelectSourceMailboxRW;
+			break;
+			}
+		case EDeleteMessage: 
+			{
+			// in DoRunL an array of messages in this folder has been created
+			// so, it is possible that that requested delete has not actually
+			// been received by the remote server. When the operation re-starts
+			// it will need to re-select the parent folder and then re-issue the
+			// STORE \deleted command.
+			
+			// the entries have already been marked locally.
+			// we still have the iUID array - iMessageUids.
+		
+			// Cancel the session command
+			iSession->Cancel();
+			iSelectionToDelete = 0; // reset the count of messages to delete locally
+			iNextStep = EDeleteMessage; // restart function
+			break;
+			}
+		case EExpunge:
+			{
+			// Messages have been marked for delete on the remote server
+			// and the expunge command issued - cancel it
+
+			// Cancel the session command
+			iSession->Cancel();
+
+			// need to re-issue the expunge command after migrate 
+			// (and re-select of correct folder)
+			iNextStep = EExpunge;
+			break;
+			}
+		case ECloseFolder:
+			{
+			iSession->Cancel();
+			iNextStep = ECloseFolder;
+			break;
+			}
+		case ESelectFolderAfterClose:
+			{
+			iSession->Cancel();
+			iNextStep = ESelectFolderAfterClose;
+			break;
+			}
+		case EDeleteLocalMessage:
+		case EFinished:
+			{
+			// self-completed or no outstanding request.
+			break;
+			}
+		case ESuspendedForMigrate:
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, 
+				TImapServerPanic::ImapPanic(TImapServerPanic::EDeleteCompoundCancelUnexpectedState));
+			break;
+			}
+		} // end switch (iCurrentStep)	
+	
+	if (!iCancelForMigrate)
+		{
+		// genuine cancel - update progress
+		iProgressErrorCode = KErrCancel;
+		}
+	CMsgActive::DoCancel();
+	}
+
+	
+void CImapCompoundDelete::Progress(TImap4CompoundProgress& aCompoundProgress)
+	{
+	aCompoundProgress.iGenericProgress.iOperation = TImap4GenericProgress::EDelete;
+	aCompoundProgress.iGenericProgress.iState = iProgressState;
+	aCompoundProgress.iGenericProgress.iMsgsToDo = iProgressMsgsToDo;
+	aCompoundProgress.iGenericProgress.iMsgsDone = iProgressMsgsDone;
+	
+	// Put error into progress buffer
+	if( aCompoundProgress.iGenericProgress.iErrorCode == KErrNone )
+		{
+		aCompoundProgress.iGenericProgress.iErrorCode = iProgressErrorCode;
+		}
+		
+	}
+
+
+/**
+Deletes messages locally
+Deletes messages in iSourceSel contains only messages
+belonging to a single parent folder.
+*/
+void CImapCompoundDelete::DeleteLocalMessagesL()
+	{
+	// Set the server entry context to the parent.
+	SetEntryL((*iSourceSel)[0]);
+	SetEntryL(iServerEntry.Entry().Parent());
+	
+	// delete local entries that have been expunged remotely
+	TInt count = 0;
+	while(count<iSelectionToDelete)
+		{
+		// Do not leave when entry is in use 
+		TInt err = iServerEntry.DeleteEntry((*iSourceSel)[count]);
+		if(err!=KErrNone)
+			{
+			if(err!=KErrInUse)
+				{
+				User::LeaveIfError(err);
+				}
+			}
+		++count;
+		} // end while()
+		
+	// remove deleted items from the selection 
+	iSourceSel->Delete(0,count);
+	iSelectionToDelete=0;
+
+	// increment progress counter
+	iProgressMsgsDone+=count;
+	}
+
+
+/**
+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 CImapCompoundDelete::ProcessNegativeServerResponse()
+	{
+	TInt err = iStatus.Int();
+	switch (iCurrentStep)
+		{
+		case ESelectSourceMailboxRW:
+		case ESelectFolderAfterClose:
+			{
+			// only knowingly operating on a single message at this point.
+			if (err == KErrImapNo)
+				{
+				err = KErrNotFound;
+				}
+			// save the error with the associated message
+			TRAP_IGNORE(MessageErrorL((*iSourceSel)[0], err));
+			++iProgressMsgsDone;
+			// Skip to the next message, or finish
+			iNextStep = (iProgressMsgsDone<iProgressMsgsToDo)?ESelectSourceMailboxRW:EFinished;
+			break;
+			}
+		case EDeleteMessage:
+		case EExpunge:
+		case ECloseFolder:
+			{
+			// failed to mark one or more messages for delete.
+			// ignore the error - no point in storing it in a 
+			// message that is just about to be deleted.
+			break;
+			}
+		case EDeleteLocalMessage:
+		case EFinished:
+		case ESuspendedForMigrate:
+		default:
+			{
+			// positive error code not expected,
+			// self-completed states or no outstanding request.
+			TImapServerPanic::ImapPanic(TImapServerPanic::EDeleteCompoundUnexpectedState);
+			break;
+			}
+		} // end switch (iCurrentStep)
+	iProgressErrorCode = err;
+	return KErrNone;
+	}
+
+/**
+Resumes the operation following a migration.
+*/
+void CImapCompoundDelete::ResumeOperationL(TRequestStatus& aStatus, CImapSession& aSession)
+	{
+	iSession = &aSession;
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundDelete::Resuming");
+	__ASSERT_DEBUG(iCurrentStep==ESuspendedForMigrate, TImapServerPanic::ImapPanic(TImapServerPanic::EDeleteCompoundUnexpectedState));
+	iStopForMigrate = EFalse;
+	
+	// Switch on next step - some "next steps" require a SELEECT first...
+	switch (iNextStep)
+		{
+		case ESelectSourceMailboxRW:
+		case ESelectFolderAfterClose:
+			{
+			// just return to the main state machine
+			CompleteSelf();
+			break;
+			}
+		case EDeleteMessage: 
+		case EExpunge:
+		case ECloseFolder:
+			{
+			// re-issue the SELECT before kicking off the DELETE or EXPUNGE command:
+			iParentFolder->SelectL(iStatus, *iSession);
+			iCurrentStep = ESelectSourceMailboxRW;
+			SetActive();
+			break;
+			}
+		case EDeleteLocalMessage:
+		case EFinished:
+			// not expected
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EDeleteCompoundUnexpectedState));
+			// abandon the compound operation
+			iNextStep=EFinished;
+			CompleteSelf();
+			break;
+			}
+		} // end switch (iNextStep)
+	Queue(aStatus);
+	}