email/imap4mtm/imapprotocolcontroller/src/cimapcompoundrename.cpp
changeset 0 72b543305e3a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/imap4mtm/imapprotocolcontroller/src/cimapcompoundrename.cpp	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,461 @@
+// 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 "cimapcompoundrename.h"
+#include "cimapsession.h"
+#include "cimapsyncmanager.h"
+#include "cimapfolder.h"
+#include "cimaplogger.h"
+#include "imappaniccodes.h"
+
+#include "mobilitytestmtmapi.h"
+
+_LIT16(KFullPathFormat, "%S%S%S");
+
+CImapCompoundRename::CImapCompoundRename(CImapSyncManager& aSyncManager,
+						 				 CMsvServerEntry& aServerEntry,
+						 				 CImapSettings& aImapSettings,
+						 				 const TMsvId aTarget)
+ : CImapCompoundBase(aSyncManager, aServerEntry, aImapSettings),
+   iFolderId(aTarget)
+	{
+	}
+
+CImapCompoundRename::~CImapCompoundRename()
+	{
+	delete iNewLeafName;
+	delete iMsgselection;
+	}
+	
+CImapCompoundRename* CImapCompoundRename::NewL(CImapSyncManager& aSyncManager,
+						 				  	   CMsvServerEntry& aServerEntry,
+						 				  	   CImapSettings& aImapSettings,
+						 				  	   const TMsvId aTarget, 
+						 				  	   const TDesC& aNewName)
+	{
+	CImapCompoundRename* self = new (ELeave) CImapCompoundRename(aSyncManager,
+																 aServerEntry,
+																 aImapSettings,
+																 aTarget);
+	CleanupStack::PushL(self);
+	self->ConstructL(aNewName);
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+/**
+Secondary construction
+*/	
+void CImapCompoundRename::ConstructL(const TDesC& aNewName)
+	{
+	// Store the new folder name
+	iNewLeafName = HBufC16::NewL(aNewName.Length());
+	TPtr16 ptr(iNewLeafName->Des());
+	ptr.Copy(aNewName);
+	
+	CActiveScheduler::Add(this);
+	}
+
+/**
+Starts the folder rename operation.
+
+@param aStatus  client object's TRequestStatus, to be notified on completion
+@param aTarget  the local id of the folder to be renamed
+@param aNewName the new name for the specified folder
+*/
+void CImapCompoundRename::StartOperation(TRequestStatus& aStatus, CImapSession& aSession)
+	{
+	iSession = &aSession;
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundRename::StartOperation()");
+	iNextStep = ERename;
+	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 CImapCompoundRename::DoRunLoopL()
+	{
+	SetCurrentStep();
+	switch (iCurrentStep)
+		{
+
+		case ERename:	// asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapRename1); // RENAME issued
+			// Issue the rename command to the remote server
+			// IssueRenameL() calls SetActive()
+			IssueRenameL();
+			break;
+			}
+		
+		case EProcessRenameResponse: // asynchronous if renaming inbox, else synchronous
+			{
+			SetEntryL(iFolderId);
+			TMsvEmailEntry entry = iServerEntry.Entry();
+			TBool remotelySubscribed = entry.Subscribed();
+			iNextStep = remotelySubscribed?ESyncSubscriptions:EFinished;
+
+			// Rename the folder locally. 
+		    if (iFolderId == iSyncManager.Inbox()->MailboxId())
+		    	{
+		    	RenameInboxL(); // calls SetActive()
+		    	}
+		    else
+		    	{
+				// Sync Manager updates affected CImapFolder objects.
+				iSyncManager.RenameLocalL(iFolderId, iNewLeafName->Des());
+		    	}
+		    break;
+			}
+		
+		case ESyncSubscriptions: // asynchronous
+			{
+			MOBILITY_TEST_MTM_STATE(iImapSettings.ServiceId(), KMobilityTestMtmStateImapRename2); // SUBSCRIBE issued
+			// The folder was previously remotely subscribed.
+			// IMAP RFC does not specify that a folder must retain its subscribed
+			// status following a rename, therefore we issue an explicit subscribe
+			// command to ensure remote subscription status is maintained.
+			CImapFolder* folder = iSyncManager.GetFolderL(iFolderId);
+			iSession->SubscribeL(iStatus, folder->FullFolderPathL());
+			SetActive();
+			iNextStep = EFinished;
+			break;
+			}
+			
+		case EFinished:  // finished
+			{
+			__LOG_TEXT(iSession->LogId(), "CImapCompoundRename::Completing OK");
+			iProgressState = TImap4GenericProgress::EIdle;
+			Complete(KErrNone);
+			return ETrue;
+			}
+			
+		default :
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ERenameCompoundUnexpectedState));
+			// unexpected state - complete the request
+			iProgressState = TImap4GenericProgress::EIdle;
+			return ETrue;
+			}
+			
+		} // end of 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 CImapCompoundRename::DoCancel()
+	{
+	switch (iCurrentStep)
+		{
+		case ERename:
+			{
+			// outstanding request on the session
+			iSession->Cancel();
+			// Re-issue the rename after migration. There is a race condition 
+			// - it is possible that the rename occurred on the server but the
+			// response was not received. This is not handled. The next folder
+			// sync will sort it out.
+			iNextStep=ERename;
+			break;
+			}
+		case ESyncSubscriptions:
+			{
+			iSession->Cancel();
+			iNextStep=ESyncSubscriptions;
+			break;
+			}
+		case EProcessRenameResponse:
+			{
+			// moving contents of inbox
+			iServerEntry.Cancel();
+			iNextStep=EProcessRenameResponse;
+			break;
+			}
+		case EFinished:
+		case ESuspendedForMigrate:
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ERenameCompoundCancelUnexpectedState));
+			iNextStep = ERename;
+			break;
+			}
+		} // end switch (iCurrentStep)
+
+	if (!iCancelForMigrate)
+		{
+		// genuine cancel - update progress
+		iProgressErrorCode = KErrCancel;
+		}
+	CMsgActive::DoCancel();
+	}
+
+
+/**
+Builds up the full path name for the renamed folder and issues the
+RENAME IMAP command via the imap session.
+
+@leave system wide error codes
+*/	
+void CImapCompoundRename::IssueRenameL()
+	{
+	CImapFolder* folder = iSyncManager.GetFolderL(iFolderId);
+	if(folder == NULL)
+		{
+		// Sync manager hasn't found the folder under the imap service.
+		// exit with progress error code Not Found
+		iProgressErrorCode = KErrNotFound;
+		iNextStep = EFinished;
+		CompleteSelf();
+		return;
+		}
+
+	// build the new full path name for the folder
+	HBufC16* newPath = folder->MakePathL(EFalse);
+	CleanupStack::PushL(newPath);
+
+	// build up the full path
+	HBufC16* newFullPath = NULL;
+	if (newPath->Length() > 0)
+		{
+		// folder is a subdirectory
+		TInt buffLength = newPath->Length() + iNewLeafName->Length() + 1; // +1 for heirarchy separator
+		newFullPath = HBufC16::NewL(buffLength);
+		newFullPath->Des().Format(
+				KFullPathFormat,
+				newPath,
+			  	&(iImapSettings.PathSeparator()),
+			   	iNewLeafName);
+		CleanupStack::PopAndDestroy(newPath);
+		CleanupStack::PushL(newFullPath);
+		}
+	else
+		{
+		// folder is at root level
+		CleanupStack::PopAndDestroy(newPath);
+		newFullPath = iNewLeafName->AllocLC();
+		}
+	
+	// Get the full path name for the folder's current name.
+	TDesC& oldPath = folder->FullFolderPathL();
+	
+	// issue the command to the remote server.
+	iSession->RenameL(iStatus, oldPath, newFullPath->Des());
+	SetActive();
+	CleanupStack::PopAndDestroy(newFullPath);
+	iProgressState = TImap4GenericProgress::EBusy;
+	iNextStep = EProcessRenameResponse;
+	}
+
+/*
+Special handling of rename is required for the INBOX as this has special
+behaviour as specified in RFC 3501:
+Renaming INBOX is permitted, and has special behavior.  It moves all
+messages in INBOX to a new mailbox with the given name, leaving INBOX empty.
+
+*/
+void CImapCompoundRename::RenameInboxL()
+	{
+	// create a new folder with the new name, a copy of the inbox		
+	SetEntryL(iFolderId);
+
+	// Selection of messages
+	delete iMsgselection;
+	iMsgselection = NULL;
+	CMsvEntrySelection* iMsgselection = new (ELeave) CMsvEntrySelection;
+
+	// Children of original inbox
+	CMsvEntrySelection* children=new (ELeave) CMsvEntrySelection;
+	CleanupStack::PushL(children);
+
+	User::LeaveIfError(iServerEntry.GetChildren(*children));
+
+	// create the new mailbox
+	TMsvEmailEntry entry = iServerEntry.Entry();
+	SetEntryL(entry.Parent());
+	entry.iDetails.Set(*iNewLeafName);
+	iServerEntry.CreateEntry(entry);	
+	iNewFolderId = entry.Id();
+
+	// create a reduced children list - just the messages in Inbox
+	TInt count = children->Count();
+	for (TInt child = 0; child < count; ++child)
+		{
+		SetEntryL((*children)[child]);
+		TMsvEntry entry = iServerEntry.Entry();
+		if (entry.iType==KUidMsvMessageEntry)
+			{
+			iMsgselection->AppendL(entry.Id());
+			}
+		}
+	CleanupStack::PopAndDestroy(children);
+
+	// reset the server entry context 
+	SetEntryL(iFolderId);	
+	if (iMsgselection->Count()>0)
+		{
+		// move children messages to the newly created folder
+		iServerEntry.MoveEntriesL(*iMsgselection, iNewFolderId, iStatus);
+		SetActive();
+		}
+	else
+		{
+		// inbox was empty - nothing to move
+		CompleteSelf();
+		}
+	}
+
+
+/**
+Populates the compound progress object with progress information for the
+rename compound operation.
+
+@param aCompoundProgress the compound progress object to be populated.
+*/	
+void CImapCompoundRename::Progress(TImap4CompoundProgress& aCompoundProgress)
+	{
+	// rename does not set iOperation, it just sets iState to EBusy 
+	// when doing the renaming
+	aCompoundProgress.iGenericProgress.iState = iProgressState;
+	
+	// Put error into progress buffer
+	if( aCompoundProgress.iGenericProgress.iErrorCode == KErrNone )
+		{
+		aCompoundProgress.iGenericProgress.iErrorCode = iProgressErrorCode;
+		}
+	}
+
+/**
+Handles server error responses according to current step
+
+@return TInt error code for completion (if error fatal)
+*/
+TInt CImapCompoundRename::ProcessNegativeServerResponse()
+	{
+	switch(iCurrentStep)
+		{
+	case ERename:
+		{
+		iProgressErrorCode = KErrNotSupported;
+		iNextStep = EFinished;
+		break;
+		}
+	case ESyncSubscriptions:
+		{
+		// subscribe failing is not serious - silently ignore it
+		iNextStep = EFinished;
+		break;
+		}
+	case EFinished:
+	case ESuspendedForMigrate:
+	default:
+		{
+		// positive error code not expected,
+		// self-completed states or no outstanding request.
+		TImapServerPanic::ImapPanic(TImapServerPanic::ERenameCompoundUnexpectedState);
+		break;
+		}
+		}
+	return KErrNone;
+	}
+	
+
+/**
+Resumes the operation following a migration.
+*/
+void CImapCompoundRename::ResumeOperationL(TRequestStatus& aStatus, CImapSession& aSession)
+	{
+	iSession = &aSession;
+	__LOG_TEXT(iSession->LogId(), "CImapCompoundRename::Resuming");
+	__ASSERT_DEBUG(iCurrentStep==ESuspendedForMigrate, TImapServerPanic::ImapPanic(TImapServerPanic::ERenameCompoundUnexpectedState));
+	iStopForMigrate = EFalse;
+
+	switch (iNextStep)
+		{
+		case ERename:
+		case ESyncSubscriptions:
+			{
+			// restart the state machine.
+			CompleteSelf();
+			break;
+			}
+		case EProcessRenameResponse:
+			{
+			// resume move of messages to renamed inbox..
+			// 1st, renew selection of children of original inbox
+			SetEntryL(iFolderId);
+			CMsvEntrySelection* children=new (ELeave) CMsvEntrySelection;
+			CleanupStack::PushL(children);
+			User::LeaveIfError(iServerEntry.GetChildren(*children));
+
+			// 2nd, create a reduced list - just the messages entries
+			delete iMsgselection;
+			iMsgselection = NULL;
+			CMsvEntrySelection* iMsgselection = new (ELeave) CMsvEntrySelection;
+			TInt count = children->Count();
+			for (TInt child = 0; child < count; ++child)
+				{
+				SetEntryL((*children)[child]);
+				TMsvEntry entry = iServerEntry.Entry();
+				if (entry.iType==KUidMsvMessageEntry)
+					{
+					iMsgselection->AppendL(entry.Id());
+					}
+				}
+			CleanupStack::PopAndDestroy(children);
+
+			// reset the server entry context 
+			SetEntryL(iFolderId);	
+			if (iMsgselection->Count()>0)
+				{
+				// move children messages to the newly created folder
+				iServerEntry.MoveEntriesL(*iMsgselection, iNewFolderId, iStatus);
+				SetActive();
+				}
+			else
+				{
+				// inbox was empty - nothing left to move
+				CompleteSelf();
+				}
+			break;
+			}
+		case EFinished:
+		default:
+			{
+			__ASSERT_DEBUG(EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::ERenameCompoundUnexpectedState));
+			// abandon the compound operation
+			iNextStep=EFinished;
+			CompleteSelf();
+			break;
+			}
+		} // end switch (iNextStep)
+	Queue(aStatus);
+	}