email/imap4mtm/imapprotocolcontroller/src/cimapcompoundrename.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Thu, 17 Dec 2009 08:44:11 +0200
changeset 0 72b543305e3a
permissions -rw-r--r--
Revision: 200949 Kit: 200951

// 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);
	}