diff -r 000000000000 -r 72b543305e3a email/imap4mtm/imapprotocolcontroller/src/cimapidlecontroller.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/email/imap4mtm/imapprotocolcontroller/src/cimapidlecontroller.cpp Thu Dec 17 08:44:11 2009 +0200 @@ -0,0 +1,647 @@ +// 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 "cimapidlecontroller.h" +#include "cimapopfetchbody.h" +#include "cimapsessionconsts.h" +#include "cimapsession.h" +#include "cimapsyncmanager.h" +#include "cimapfolder.h" +#include "cimaplogger.h" +#include "cimapcompoundcopytolocal.h" +#include "cimapmailstore.h" +#include + + +CImapIdleController::CImapIdleController(MImapIdleControllerObserver& aObserver, CImapSession*& aSession, CImapSyncManager& aSyncManager, CMsvServerEntry& aServerEntry, CImapSettings& aImapSettings, CImapMailStore& aImapMailStore) + : CActive(EPriorityStandard), iObserver(aObserver), iSession(aSession), iSyncManager(aSyncManager), iServerEntry(aServerEntry), iImapSettings(aImapSettings), iImapMailStore(aImapMailStore) + { + } + +CImapIdleController::~CImapIdleController() + { + // This is should be the only place where CImapIdleController should Cancel() on itself + // instead of calling InternalCancelAndSetState() + Cancel(); + delete iReissueTimer; + delete iCopyToLocal; + } + +CImapIdleController* CImapIdleController::NewL(MImapIdleControllerObserver& aObserver, CImapSession*& aSession, CImapSyncManager& aSyncManager, CMsvServerEntry& aServerEntry, CImapSettings& aImapSettings, CImapMailStore& aImapMailStore) + { + CImapIdleController* self = new (ELeave) CImapIdleController(aObserver, aSession, aSyncManager, aServerEntry, aImapSettings, aImapMailStore); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + +/** +Secondary construction +*/ +void CImapIdleController::ConstructL() + { + iReissueTimer = CImapObservableTimer::NewL(*this); + + // Add to the active scheduler + CActiveScheduler::Add(this); + } + +/** +Requests idle in the inbox, using the imap session object +passed at construction. +Completes synchronously. The IDLE state is entered +asynchronously in the background. +*/ +void CImapIdleController::StartIdle() + { + __ASSERT_DEBUG(iSession!=NULL, TImapServerPanic::ImapPanic(TImapServerPanic::EIdleSessionIsNull)); + __LOG_TEXT(iSession->LogId(), "CImapIdleController::StartIdle()"); + __ASSERT_DEBUG(iState==EFinished, TImapServerPanic::ImapPanic(TImapServerPanic::EIdleControllerUnexpectedStateAtStart)); + __ASSERT_DEBUG(!IsActive(), TImapServerPanic::ImapPanic(TImapServerPanic::EIdleControllerIsActiveAtStart)); + + // Check whether we are going to use IDLE or issue a "dummy read" + TBool idleSupported=iSession->ImapIdleSupported(); + TBool idleEnabled=iImapSettings.ImapIdle(); + iUsingIdle = idleSupported && idleEnabled; + + iState = EStartingIdle; + iStoppingIdle = EFalse; + + // Complete self to enter state machine + TRequestStatus* status = &iStatus; + User::RequestComplete(status, KErrNone); + SetActive(); + } + + +/** +Asynchronously stops the outstanding idle request. + +@param aStatus - Signals completion of the request +*/ +void CImapIdleController::StopIdle(TRequestStatus& aStatus) + { + __LOG_FORMAT((iSession->LogId(), "CImapIdleController::StopIdle() State = %d", iState)); + + iStoppingIdle = ETrue; + Queue(aStatus); + switch (iState) + { + case EFinished: + { + // not in IDLE state, complete immediately + Complete(KErrNone); + break; + } + + case EIdling: + { + // currently idling - cancel the outstanding request + // then issue the DONE command to the remote server. + InternalCancelAndSetState(EWaitingForDone); + iSession->DoneIdle(iStatus); + SetActive(); + break; + } + + case EWaitingForServerEvent: + { + // "Dummy Read" previously issued. This needs to be + // cancelled and flushed before continuing. + InternalCancelAndSetState(EWaitingForFlush); + iSession->FlushCancelledCommand(iStatus); + SetActive(); + break; + } + + case ESyncFetching: + { + // bring the download-rules controlled fetch to a + // premature halt... The message currently being fetched + // will not be resumed on next rules-based sync + __LOG_TEXT(iSession->LogId(), "CImapIdleController: Stopping a rules-based fetch to allow requested operation"); + InternalCancelAndSetState(EWaitingForFlush); + iSession->FlushCancelledCommand(iStatus); + SetActive(); + break; + } + + case EStartingIdle: + case ESelectingInbox: + case EWaitingForContinuation: + case EWaitingForFlush: + case EWaitingForDone: + case ESynchronising: + default: + { + // no action required, stop request will be completed + // when the appropriate state reached. + break; + } + } // end switch (iState) + } + + +/** +Called when an asynchronous service request completes. +*/ +void CImapIdleController::RunL() + { + // RunL() should never be called while doing an internal cancel + __ASSERT_DEBUG(iInternalCancel==EFalse, TImapServerPanic::ImapPanic(TImapServerPanic::EIdleControllerRunLCalledDuringInternalCancel)); + + TInt status = iStatus.Int(); + if (status!=KErrNone) + { + // Something has gone wrong. + // Tell the Protocol Controller about it. + if (iReport==NULL) + { + // error must have occurred while in IDLE. Inform the idle + // observer. Otherwise errors are reported via Complete(). + iObserver.OnIdleError(status); + + // We need to exit immediately because reporting the error to the + // protocol controller will mean that it deletes us. + return; + } + } + else + { + TRAPD(error, DoRunL()); + __ASSERT_DEBUG(error==KErrNone || !IsActive(), TImapServerPanic::ImapPanic(TImapServerPanic::EIdleControllerLeaveInDoRunL)); + if (IsActive()) + { + // requeued + return; + } + status=error; + } + Complete(status); + } + +void CImapIdleController::DoRunL() + { + if (!iStoppingIdle) + { + RunContinueL(); + } + else + { + // Stop request has been received + RunStopL(); + } + } + +/** +Continue the idle state machine +*/ +void CImapIdleController::RunContinueL() + { + __LOG_FORMAT((iSession->LogId(), "CImapIdleController::RunContinueL() State = %d", iState)); + switch (iState) + { + case EStartingIdle: + { + // state machine entry point - select the inbox. + iSyncManager.Inbox()->SelectL(iStatus, *iSession); + iState = ESelectingInbox; + break; + } + + case ESelectingInbox: + { + // inbox selected + if (SyncRequired()) + { + // inbox has changed since last sync, kick off sync + iSyncManager.Inbox()->SynchroniseL(iStatus, *iSession, EFalse, ETrue); + iState = ESynchronising; + } + // resume any fetch operation if there is anything to do... + else if (!DoBodyDownloadL()) + { + // otherwise, issue the idle command + GoIdleL(); + } + break; + } + + case EWaitingForContinuation: + { + // idle continuation response received + if (SyncRequired()) + { + // if the folder wants to sync, call DONE first then sync + iSession->DoneIdle(iStatus); + iState = EWaitingForDone; + } + else + { + EnterIdlingState(); + } + break; + } + + case EIdling: + { + // idling event has occurred + if (SyncRequired()) + { + // if the folder wants to sync, call DONE first then sync + iSession->DoneIdle(iStatus); + iState = EWaitingForDone; + iReissueTimer->Cancel(); + } + else + { + // otherwise, re-issue WaitForIdleEvent + iSession->WaitForIdleEvent(iStatus); + iState = EIdling; + } + break; + } + + case EWaitingForServerEvent: + { + // an unsolicited event has occurred + if (SyncRequired()) + { + // idling event has occurred + iSyncManager.Inbox()->SynchroniseL(iStatus, *iSession, EFalse, ETrue); + iState = ESynchronising; + iReissueTimer->Cancel(); + } + else + { + // otherwise, return to dummy read + GoIdleL(); + } + break; + } + + case EWaitingForFlush: + case EWaitingForDone: + { + // waiting for done/flush but not stopping: either an event has + // occurred in IDLE or the reissue timer must have expired. + if (SyncRequired()) + { + // idling event has occurred + iSyncManager.Inbox()->SynchroniseL(iStatus, *iSession, EFalse, ETrue); + iState = ESynchronising; + } + else + { + // No change - re-issue idle. + // This will occur if the idle timer has expired. + GoIdleL(); + } + break; + } + + case ESynchronising: + { + // 1st phase (header) synchronise has completed, do content fetch if configured. + if (!DoBodyDownloadL()) + { + // content fetch not configured or nothing to fetch + // Sync any changes, or go idle. + if (SyncRequired()) + { + // an event seems to have happened during the sync - sync again + iSyncManager.Inbox()->SynchroniseL(iStatus, *iSession, EFalse, ETrue); + iState = ESynchronising; + } + else + { + // otherwise, re-issue the idle command + GoIdleL(); + } + } + break; + } + + case ESyncFetching: + { + // 2nd phase synchronise has completed, + delete iCopyToLocal; + iCopyToLocal = NULL; + if (SyncRequired()) + { + // an event seems to have happened during the sync - sync again + iSyncManager.Inbox()->SynchroniseL(iStatus, *iSession, EFalse, ETrue); + iState = ESynchronising; + } + else + { + // otherwise, re-issue the idle command + GoIdleL(); + } + break; + } + + case EFinished: + default: + { + // these are unexpected states. + return; + } + } // end switch (iState) + SetActive(); + } + + +/** +Starts second-phase download of body content according to provisioned +download rules, if enabled, rules defined and any messages to fetch. +Otherwise, causes the CImapIdleController object to self-complete. + +@return ETrue if body content download has been started. + SetActive() must be called following an ETrue return +*/ +TBool CImapIdleController::DoBodyDownloadL() + { + TBool fetchStarted = EFalse; + // are download rules being used? otherwise, self complete. + if (iImapSettings.UseSyncDownloadRules()) + { + // are rules defined for the inbox? otherwise, self complete + TImImap4GetPartialMailInfo mailInfo; + if (KErrNotFound != iImapSettings.GetSyncDownloadRuleL(CImapSyncDownloadRules::EInboxRulesType, mailInfo)) + { + // build a selection of messages to fetch + CMsvEntrySelection* selection = new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(selection); + + // anything to fetch? otherwise, self complete + TInt msgsToFetch = iSyncManager.Inbox()->GetFetchMessageChildrenL(*selection); + if (msgsToFetch>0) + { + __LOG_FORMAT((iSession->LogId(), "CImapIdleController: Starting rules-based Fetch of %d messages", msgsToFetch)); + delete iCopyToLocal; + iCopyToLocal = NULL; + // Create the compound operation object + iCopyToLocal = CImapCompoundCopyToLocal::NewL(iSyncManager, + iServerEntry, + iImapSettings, + iImapMailStore, + EFalse, + *selection, + KMsvNullIndexEntryId, + mailInfo); + iCopyToLocal->StartOperation(iStatus, *iSession); + iState = ESyncFetching; + fetchStarted = ETrue; + } + CleanupStack::PopAndDestroy(selection); + } + } + return fetchStarted; + } + +/** +If IDLE is supported and configured for use, then the IDLE command is +issued to the server. Otherwise the session is configured to perform +a read of any information that arrives at the server. +SetActive() must be called after calling this method. +*/ +void CImapIdleController::GoIdleL() + { + if (iUsingIdle) + { + __LOG_TEXT(iSession->LogId(), "CImapIdleController: Using IDLE"); + // issue the idle command + iSession->EnterIdleL(iStatus); + iState = EWaitingForContinuation; + } + else + { + // Otherwise, request notification of unsolicited server events + // This is referred to as a "dummy read". + __LOG_TEXT(iSession->LogId(), "CImapIdleController: Using Dummy Read"); + iSession->WaitForServerEventL(iStatus); + iState = EWaitingForServerEvent; + } + } + + +/** +Stop the idle state machine +*/ +void CImapIdleController::RunStopL() + { + __LOG_FORMAT((iSession->LogId(), "CImapIdleController::RunStopL() State = %d", iState)); + switch (iState) + { + case EWaitingForContinuation: + { + // StopIdle() was called after the IDLE was issued, but before + // the continuation response had been received. + // Need to cancel the IDLE state and issue the DONE command; + InternalCancelAndSetState(EWaitingForDone); + iSession->DoneIdle(iStatus); + SetActive(); + break; + } + + case EWaitingForServerEvent: + { + // Cancel the "dummy read" request and flush any data in the pipes + InternalCancelAndSetState(EWaitingForFlush); + iSession->FlushCancelledCommand(iStatus); + SetActive(); + break; + } + + case EFinished: // unexpected + case EStartingIdle: // almost possible + case ESelectingInbox: // possible (StopIdle() called in ESelectingInbox) + case EWaitingForFlush: // expected (StopIdle() called in EWaitingForServerEvent) + case EWaitingForDone: // expected (StopIdle() called in EIdling) + case EIdling: // unexpected (state exited in StopIdle()) + case ESynchronising: // possible (StopIdle() called in ESynchronising) + case ESyncFetching: // possible StopIdle() while doing bg fetch + default: + { + // Nothing more needs to be done in these states - the session is + // now ready to be used. + __LOG_TEXT(iSession->LogId(), "CImapIdleController::Idle stopped OK"); + iState=EFinished; + Complete(KErrNone); + break; + } + } // end switch (iState) + } + + +/** +Called by Cancel() to cancel asynchronous service requests +*/ +void CImapIdleController::DoCancel() + { + iReissueTimer->Cancel(); + + switch (iState) + { + case EIdling: + case ESelectingInbox: + case EWaitingForContinuation: + case EWaitingForServerEvent: + case EWaitingForFlush: + case EWaitingForDone: + { + // outstanding request is on the imap session + iSession->Cancel(); + break; + } + case ESynchronising: + { + // outstanding request is on the folder + iSyncManager.Inbox()->Cancel(); + break; + } + case ESyncFetching: + { + if (iCopyToLocal) + { + iCopyToLocal->CancelEnableResume(); + delete iCopyToLocal; + iCopyToLocal = NULL; + break; + } + } + case EStartingIdle: + case EFinished: + default: + { + // self-completed or no outstanding request. + break; + } + } // end switch (iState) + + if (!iInternalCancel) + { + // Notify any waiting active object that the asynchronous operation they were waiting on has been cancelled. + // Note that we only do this if Cancel() was called externally. + // + // Internal Cancel's() that are used for switching from one internal operation to another should not + // call Complete, as the external overall operation has not actually completed. + Complete(KErrCancel); + __LOG_TEXT(KDefaultLog, "CImapIdleController: Cancelled EXTERNALLY while active"); + + // No async ops in progress so reset the state + // Note that InternalCancelAndSetState() will set the state for EInternalCancel. + iState = EFinished; + } + } + +void CImapIdleController::Queue(TRequestStatus& aStatus) + { + __ASSERT_DEBUG(iReport==NULL, TImapServerPanic::ImapPanic(TImapServerPanic::EIdleControllerAlreadyInUse)); + + aStatus=KRequestPending; + iReport=&aStatus; + } + +void CImapIdleController::Complete(TInt aErr) + { + if (iReport) + { + // only complete once + DoComplete(aErr); + User::RequestComplete(iReport, aErr); + } + } + +#ifdef __IMAP_LOGGING +void CImapIdleController::DoComplete(TInt& aErr) +#else +void CImapIdleController::DoComplete(TInt& /*aErr*/) +#endif //__IMAP_LOGGING + { + __LOG_FORMAT((iSession->LogId(), "CImapIdleController::DoComplete(aErr = %d) State = %d", aErr, iState)); + } + +void CImapIdleController::OnTimerL(const CImapObservableTimer& /*aSourceTimer*/) + { + // The Idle reissue timer has expired. + // If we are currently idling, we need to restart the idle. + __LOG_FORMAT((iSession->LogId(), "CImapIdleController: Reissue idle timer expired. State = %d", iState)); + if (iState == EIdling) + { + InternalCancelAndSetState(EWaitingForDone); + iSession->DoneIdle(iStatus); + SetActive(); + } + else if (iState == EWaitingForServerEvent) + { + InternalCancelAndSetState(EWaitingForFlush); + iSession->FlushCancelledCommand(iStatus); + SetActive(); + } + } + +void CImapIdleController::EnterIdlingState() + { + __LOG_FORMAT((iSession->LogId(), "CImapIdleController: Entering idle state. Reissue timeout = %d seconds", iImapSettings.ImapIdleTimeout())); + + iReissueTimer->Cancel(); + iReissueTimer->After(iImapSettings.ImapIdleTimeout() * 1000000); + + iSession->WaitForIdleEvent(iStatus); + iState = EIdling; + } + +/** +When internally switching from one async operation to another, it is necessary to perform a Cancel(). +But when we do this, we don't want to inform the waiting iReport, as the overall operation that it +requested has not itself been cancelled. +So this method temporarily sets the state to EInternalCancel, then calls Cancel() - whose DoCancel() +knows not to call Complete for this state. Finally, the state is switched to the state for the next +internal operation. +Note that this all happens synchronously, so there is no chance of RunL() being called while the state +is set EInternalCancel +@param aNextState the state associated with the next operation. +*/ +void CImapIdleController::InternalCancelAndSetState(TIdleControllerState aNextState) + { + // These states should not cause iSesson->Cancel() to be called within DoCancel() + __ASSERT_DEBUG(iState != ESynchronising && iState != EStartingIdle && iState != EFinished, TImapServerPanic::ImapPanic(TImapServerPanic::EIdleControllerInternalCancelOnBadState)); + + // Ensure that imminent call to DoCancel() dose not cause iReport to be Completed + iInternalCancel = ETrue; + Cancel(); + iInternalCancel = EFalse; + // Now set the real state + iState=aNextState; + } + +/** +Returns true if IDLE is enabled and a change has been detected in the contents of the inbox. +*/ +TBool CImapIdleController::SyncRequired() + { + if (!iImapSettings.ImapIdle()) + { + return EFalse; + } + if (!iSyncManager.Inbox()->Changed(*iSession)) + { + return EFalse; + } + return ETrue; + }