email/pop3andsmtpmtm/clientmtms/src/CONSYNC.CPP
changeset 0 72b543305e3a
child 76 60a8a215b0ec
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/email/pop3andsmtpmtm/clientmtms/src/CONSYNC.CPP	Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,633 @@
+// Copyright (c) 1998-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 <miutset.h>
+#include <mtclreg.h>
+#include <msvids.h>
+#include <miutset.h>
+#include <commdb.h>
+#include <mtclbase.h>
+#include "IMAPSET.H"
+#include "IMAPCMDS.H"
+#include "CONSYNC.H"
+#include "MIUT_ERR.H"
+#include "ImapConnectionObserver.H"	//	MMsvImapConnectionObserver
+#include <cemailaccounts.h>
+
+#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS 
+#include "msvconsts.h"
+#include "miut_errconsts.h"
+#endif
+
+const TInt KImapIdleProgressRate	=300000000;	// 5 minutes
+const TInt KImapDefaultProgressRate	=0xF0000;	// approx 1sex
+
+//	static
+CImapConnectAndSyncOp* CImapConnectAndSyncOp::NewL(	CMsvSession& aSession, const CMsvEntrySelection& aSelection, 
+													CBaseMtm& aBaseMtm, TInt aPriority, 
+													TRequestStatus& aStatus, 
+													TImapConnectionCompletionState aCompletionState, 
+													MMsvImapConnectionObserver* aConnectionObserver)
+//
+//
+//
+	{
+	CImapConnectAndSyncOp* self=new(ELeave) CImapConnectAndSyncOp(aSession, aSelection, aBaseMtm, aPriority, aStatus, aCompletionState, aConnectionObserver);
+	CleanupStack::PushL(self);
+	self->ConstructL(aSelection);
+	CleanupStack::Pop(); //	self
+	return self;
+	}
+
+CImapConnectAndSyncOp::CImapConnectAndSyncOp(CMsvSession& aSession, const CMsvEntrySelection& aSelection, 
+											 CBaseMtm& aBaseMtm, TInt aPriority, 
+											 TRequestStatus& aStatus, 
+											 TImapConnectionCompletionState aCompletionState,
+											 MMsvImapConnectionObserver* aConnectionObserver)
+:	CMsvOperation(aSession, aPriority, aStatus), 
+	iBaseMtm(aBaseMtm),
+	iConnectionObserver(aConnectionObserver),
+	iCompletionState(aCompletionState)
+//
+//
+//
+	{
+	iService=aSelection.At(0);
+	iMtm=KUidMsgTypeIMAP4;
+	}
+
+void CImapConnectAndSyncOp::ConstructL(const CMsvEntrySelection& aSelection)
+//
+//
+//
+	{
+	iServiceEntry = CMsvEntry::NewL(iMsvSession, iService, TMsvSelectionOrdering());
+	iServiceEntry->SetEntryL(iService);
+	iServiceEntry->AddObserverL(*this);
+	CActiveScheduler::Add(this);
+
+	iProgressTimer	= CProgressTimer::NewL(*this);
+	iRefreshTimer	= CRefreshTimer::NewL(*this);
+
+	// Need to restore the imap settings to get the refresh rate. This rate will
+	// persist for the life time of this connect and sync op.
+	CImImap4Settings* settings = new (ELeave) CImImap4Settings();
+	CleanupStack::PushL(settings);
+
+	CEmailAccounts* account = CEmailAccounts::NewLC();
+  	TImapAccount id;
+	account->GetImapAccountL(iServiceEntry->Entry().Id(), id);
+	account->LoadImapSettingsL(id, *settings);
+	
+	iRefreshRate = settings->SyncRate();
+	CleanupStack::PopAndDestroy(2, settings); // account, settings
+	
+	// Start the connection operation
+	iSelection = aSelection.CopyL();
+	TBuf8<4> buf;
+	iOperation = iBaseMtm.InvokeAsyncFunctionL(KIMAP4MTMConnectAndSynchronise, *iSelection, buf, iStatus);
+	SetActive();
+	iState = EConnecting;
+	iObserverRequestStatus=KRequestPending;
+	iProgress().iSyncProgress.iState=TImap4SyncProgress::EConnecting;
+	}
+
+CImapConnectAndSyncOp::~CImapConnectAndSyncOp()
+//
+//
+//
+	{
+	if (!IsActive() && iState!=ENotStarted && iState!=ECompleted)
+		Completed(KErrCancel);
+	Cancel();	
+	delete iServiceEntry;
+	delete iOperation;
+	delete iSelection;
+	delete iRefreshTimer;//.Close();
+	delete iProgressTimer;
+	};
+
+
+//
+//
+//
+void CImapConnectAndSyncOp::DoRefreshInboxL()
+	{
+	//	If this operation is not already doing something,
+	//	make it check the local inbox for new mail.
+	//
+	if( !iOperation && iState==EWaiting )
+		{
+		// check that we can do a forced sync
+		if( iMsvSession.ServiceActive(iService) )
+			{
+			// The server MTM is active and therefore the forced sync cannot be 
+			// done - set off the refresh timer again.
+			ResetRefreshTimer();
+			}
+		else
+			{
+			// start a forced sync of inbox
+			TBuf8<4> buf;
+			TRAPD(err,iOperation=iBaseMtm.InvokeAsyncFunctionL(KIMAP4MTMInboxNewSync, *iSelection, buf, iStatus));
+			if( err )
+				{
+				//	Couldn't start operation, but we are active and pending -
+				//	take the connection down.
+				Cancel();
+				return;
+				}
+			iState = EForcedSyncing;
+			}
+		}
+	}
+
+
+const TDesC8& CImapConnectAndSyncOp::ProgressL()
+//
+//
+//
+	{
+	//	Deal with states where we don't need to do anything particularly special
+	switch(iState)
+		{
+	case ENotStarted:
+	case ECompleted:
+	case EDisconnectingOnTimeout:
+		return iProgress;
+	default:	//	Keep GCC happy
+		break;
+		};
+
+	TInt serviceErr=GetServiceProgress();
+	if(serviceErr || (iProgress().iGenericProgress.iState==TImap4GenericProgress::EDisconnected && 
+		iProgress().iGenericProgress.iOperation!=TImap4GenericProgress::EDisconnect))
+		{
+		// Check to see if the error is due to this operation.
+		if( iState == EWaiting && iMsvSession.ServiceActive(iService) )
+			{
+			// Ok there's an operation in the service which is not for this
+			// service (as in Waiting state) - therefore error was for them
+			// and not this operation.
+			iProgress().iGenericProgress.iErrorCode = KErrNone;			
+			}
+					
+		// Need to'cancel' ourselves here - this will NOT complete the observer
+		// do that later.	
+		iForcedCancel = ETrue;
+		Cancel();
+		iStatus = serviceErr;
+		Completed(serviceErr);	
+		}
+
+	TInt syncProgressState=iProgress().iSyncProgress.iState;
+
+	switch(iState)
+		{
+	case EConnecting:
+	case EForcedSyncing:
+		// active - return the operation's progress
+		return iOperation->ProgressL();
+
+		// to provide sufficient information to our observer (if we have
+		// one), the first sync is broken into three stages.
+	
+	case EFirstSyncingUpdatingInbox:
+		//	syncProgressState ought to be ESyncInbox at this point
+		if(syncProgressState==TImap4SyncProgress::ESyncInbox)
+			{
+			// as expected - just reset progress timer and return
+			ResetProgressTimer();
+			break;
+			}
+
+		//	We're not syncing the inbox...perhaps we're updating the folder list
+		iState=EFirstSyncingUpdatingFolderList;
+		UpdateObserver();	//	We've changed state - let our observer know.
+		//	Fall through...
+
+	case EFirstSyncingUpdatingFolderList:
+		//	progressState ought to be EFolderTreeSync at this point
+		if(syncProgressState==TImap4SyncProgress::ESyncFolderTree)
+			{
+			//	as expected - just reset progress timer and return
+			ResetProgressTimer();
+			break;
+			}
+
+		//	We're not updating the folder list...perhaps we're updating the folders
+		iState=EFirstSyncingUpdatingFolders;
+		UpdateObserver();	//	We've changed state - let our observer know.
+		//	Fall through...
+
+	case EFirstSyncingUpdatingFolders:
+		ResetProgressTimer();
+		if(syncProgressState==TImap4SyncProgress::EIdle)
+			{
+			//	Transition between end of background sync and 
+			//	periodic inbox checking state
+			if(iCompletionState==EAfterFullSync)
+				{
+				iState=ECompletingSelf;	
+				TRequestStatus* status=&iStatus;
+				iStatus=KRequestPending;
+				User::RequestComplete(status,KErrNone);
+
+				// NOTE - the active object is already active and so no need
+				// to call SetActive() again.
+				__ASSERT_DEBUG( IsActive(), User::Invariant() );
+				}
+			else
+				{
+				ResetRefreshTimer();				
+				iState=EWaiting;
+				UpdateObserver();	// notify observer as state has changed
+				}
+			};
+		break;				
+	case EWaiting:
+		{
+		// check for idle timeout
+		if(!iTimeout || iProgress().iGenericProgress.iState!=TImap4GenericProgress::EIdle)
+			{
+			//	Either...
+			//	!iTimeout, in which case the timeout needs to be set for the first time
+			//	or.. Server is doing something, so reset the timer
+			if( iIdleTimeout.Int()<=0 )
+				{
+				// As idle timeout is less zero or -ve, don't timeout at all!
+				iTimeout=EFalse;  
+				}
+			else
+				{
+				iTimeout=ETrue;
+				iTimeoutAt.UniversalTime();
+				iTimeoutAt=iTimeoutAt+iIdleTimeout;
+				}
+			}
+		TTime currentTime;
+		currentTime.UniversalTime();
+		if(currentTime>iTimeoutAt && iTimeout)
+			{
+			//	Timed out - try to perform a disconnection.
+			//
+			TBuf8<1> buf;
+			TRAPD(err,iOperation=iBaseMtm.InvokeAsyncFunctionL(KIMAP4MTMDisconnect, *iSelection, buf, iStatus));
+			if(err)
+				Cancel();
+			else
+				{
+				iState=EDisconnectingOnTimeout;
+				// NB no need to SetActive - this operation is ALREADY active.
+				UpdateObserver();
+				}
+			}
+		else
+			{
+			if(iTimeout != EFalse)
+				{
+				// reset the progress timer
+				iProgressTimer->Cancel();
+				iProgressTimer->After(KImapDefaultProgressRate);
+				}			
+			else
+				{				
+				iProgressTimer->Cancel();
+				iProgressTimer->After(KImapIdleProgressRate);				
+				}
+			}
+		};
+		break;
+	default:
+		break;
+		}
+	
+	iSyncProgress()=iProgress().iSyncProgress;
+	return iSyncProgress;
+	}
+
+void CImapConnectAndSyncOp::ResetProgressTimer()
+	{
+	// reset the progress timer
+	iProgressTimer->Cancel();
+	iProgressTimer->After(KImapDefaultProgressRate);
+	}	
+
+void CImapConnectAndSyncOp::UpdateObserver() const
+//
+//
+//
+	{
+	if(iConnectionObserver)
+		{
+		//	We have an observer - signal them regarding our current state
+		TImapConnectionEvent event;
+		event=EConnectingToServer;	//	Assume connecting
+
+		//	Translate our new internal state into an event for the observer
+		//
+		switch(iState)
+			{
+		case ENotStarted:
+		case EConnecting:
+			//	iState is already EConnectingToServer
+			break;
+
+		case EDisconnectingOnTimeout:
+			event=EDisconnecting;
+			break;
+
+		case ECompleted:
+			event=EConnectionCompleted;
+			break;
+
+		case EForcedSyncing:
+		case EFirstSyncingUpdatingInbox:
+			event=ESynchronisingInbox;
+			break;
+
+		case EFirstSyncingUpdatingFolderList:
+			event=ESynchronisingFolderList;
+			break;
+
+		case EFirstSyncingUpdatingFolders:
+			event=ESynchronisingFolders;
+			break;
+
+		case EWaiting:
+			event=ESynchronisationComplete;
+			break;
+
+		default:
+			//	Should never get here
+			gPanic(ESmtcMTMOperationNULL);	//DS EImcmBadStateInConnectionOp
+			break;
+			}
+
+		iConnectionObserver->HandleImapConnectionEvent(event);
+		}
+	}
+
+TInt CImapConnectAndSyncOp::GetServiceProgress()
+	{
+	TInt error = iMsvSession.ServiceProgress(iService, iProgress);
+	if(error == KErrNone)
+		error=iProgress().iGenericProgress.iErrorCode;
+	if(error == KErrNone)
+		error=iProgress().iSyncProgress.iErrorCode;
+
+	if (error!= KErrNone)
+		{
+		switch (iState)
+			{
+			case EWaiting:
+			case ECompleted:
+			case ENotStarted:
+				// CImapConnectAndSyncOp not really doing anything, so this error
+				// comes from some other IMPS command and so should not be handled
+				// by this operation.
+
+				// Possible KErrIMAPNO response from server - warning, not error.
+				if (error==KErrNotSupported)
+					error=KErrNone;
+				break;
+			default:
+				// Genuine error in CImapConnectAndSyncOp as it is currently doing
+				// something useful...
+				break;
+			}
+		}
+
+	return error;
+	}
+
+
+void CImapConnectAndSyncOp::Completed(TInt aError)
+	{
+	if(iState==ECompleted)
+		return;
+
+	TRequestStatus* observer=&iObserverRequestStatus;
+	User::RequestComplete(observer, aError);
+	iState=ECompleted;
+	UpdateObserver();
+	}
+
+
+void CImapConnectAndSyncOp::DoCancel()
+	{
+	switch (iState)
+		{
+	case EConnecting:
+	case EForcedSyncing:
+		__ASSERT_DEBUG(iOperation, gPanic(ESmtcMTMOperationNULL));	//DS EImcmNullOperation
+		iOperation->Cancel();
+		// Stop the service, because it's possible that iOperation has completed and so we have
+		// no handle on the server MTM behaviour which may be performing background sync
+		iMsvSession.StopService(iService);
+		break;
+	case EFirstSyncingUpdatingInbox:
+	case EFirstSyncingUpdatingFolderList:
+	case EFirstSyncingUpdatingFolders:
+	case EWaiting:
+		{
+		if( !iForcedCancel )
+			iMsvSession.StopService(iService);
+		
+		//	Complete ourselves (no one else will)
+		TRequestStatus* myStatus=&iStatus;
+		iStatus=KRequestPending;
+		User::RequestComplete(myStatus,KErrCancel);
+		};
+		break;
+	default:
+		break;
+		}
+	if (!iForcedCancel)
+		Completed(KErrCancel);
+	
+	iProgressTimer->Cancel();
+	iRefreshTimer->Cancel();
+	}
+
+
+void CImapConnectAndSyncOp::RunL()
+//
+//
+//
+	{
+	if (iOperation)
+		{
+		iProgress.Copy(iOperation->ProgressL());
+		delete iOperation;
+		iOperation=NULL;
+
+		TInt error = iStatus.Int();
+		if (error==KErrNone)
+			error=iProgress().iSyncProgress.iErrorCode;
+		if (error==KErrNone)
+			error=iProgress().iGenericProgress.iErrorCode;
+		if (error!=KErrNone)
+			{				
+			iProgress().iSyncProgress.iErrorCode=error;
+			User::Leave(error);
+			}
+		}
+
+	switch (iState)
+		{
+	case EConnecting:
+		{
+		if(iCompletionState==EAfterConnect)
+			Completed(KErrNone);
+		else
+			{
+			iMtm = KUidMsgTypeIMAP4;
+			iState = EFirstSyncingUpdatingInbox;	// IMAP server may or may not still be synchronising - assume that it is.
+			UpdateObserver();			
+			iStatus = KRequestPending;	// so we still look pending to the OpWatcher
+			ResetProgressTimer();		// Kick off the progress timer...
+			SetActive();				// At next change of state, MUST NOT SetActive again. 
+			}
+	
+		// The socket timeout value was stored in iMtmData1 from Imps.
+		iBaseMtm.Entry().SetEntryL(iService);
+		TMsvEntry entry = iBaseMtm.Entry().Entry();
+		TInt32 socketTimeout = entry.MtmData1();
+
+		// Adjust the timeout to make sure that we disconnect the IMAP layer
+		// *before* the socket underneath us times out.
+		const TUint KTimeOutPercentage = 80; // Arbitrarily choose 80% - should be enough
+		iIdleTimeout = (socketTimeout * KTimeOutPercentage)/100;
+		break;
+		}
+	case EForcedSyncing:
+		// move back to waiting
+		iState = EWaiting;
+		UpdateObserver();
+		iStatus = KRequestPending;
+		SetActive();				// At next change of state, MUST NOT SetActive again. 
+		ResetRefreshTimer();
+		ResetProgressTimer();
+		break;
+	case EDisconnectingOnTimeout:
+		iProgress().iSyncProgress.iErrorCode=KErrTimedOut;
+		Completed(KErrNone);
+		break;
+	case EFirstSyncingUpdatingInbox:
+	case EFirstSyncingUpdatingFolderList:
+	case EFirstSyncingUpdatingFolders:
+	case EWaiting:
+		iProgress().iSyncProgress.iErrorCode=KErrCancel;
+		Completed(KErrCancel);
+		break;
+	case ECompletingSelf:
+		Completed(KErrNone);
+		break;
+	default: // i.e. ECompleted:
+		__ASSERT_DEBUG(EFalse, gPanic(ESmtcMTMOperationNULL)); //DS EImcmBadStateInConnectionOp
+		break;
+		}
+	}
+
+TInt CImapConnectAndSyncOp::RunError(TInt aError)
+	{
+	Completed(aError);
+	return KErrNone;
+	}
+
+void CImapConnectAndSyncOp::HandleEntryEventL(TMsvEntryEvent aEvent, TAny* /*aArg1*/, TAny* /*aArg2*/, TAny* /*aArg3*/)
+	{
+	if(aEvent == MMsvEntryObserver::EMsvEntryChanged)
+		{
+		ProgressL();
+		}
+	}
+
+void CImapConnectAndSyncOp::ResetRefreshTimer()
+	{
+	// Only start the timer if the rate is greater than zero. A value of zero
+	// (or less) indicates that the inbox should not be refreshed.
+	if( iRefreshRate.Int() > 0 )
+		{
+		TTime refreshTime;
+		refreshTime.UniversalTime();
+		refreshTime += iRefreshRate;
+
+		iRefreshTimer->Cancel();
+		iRefreshTimer->AtUTC(refreshTime);
+		}
+	}
+
+/*
+ *	CProgressTimer
+ */
+
+CProgressTimer::CProgressTimer(CImapConnectAndSyncOp& aOperation)
+: CTimer(EPriorityLow), iOperation(aOperation)
+	{}
+
+void CProgressTimer::RunL()
+	{
+	// we ignore error, as this is just used ensure that progress is called 
+	iOperation.ProgressL();
+	}
+
+TInt CProgressTimer::RunError(TInt /*aError*/)
+	{
+	// Do nothing...
+	return KErrNone;
+	}
+
+CProgressTimer* CProgressTimer::NewL(CImapConnectAndSyncOp& aOperation)
+	{
+	CProgressTimer* self = new(ELeave) CProgressTimer(aOperation);
+	CleanupStack::PushL(self);
+	self->ConstructL(); // CTimer
+	CActiveScheduler::Add(self);
+	CleanupStack::Pop();
+	return self;
+	}
+
+/*
+ *	CRefreshTimer
+ */
+
+CRefreshTimer::CRefreshTimer(CImapConnectAndSyncOp& aOperation)
+: CTimer(EPriorityLow), iOperation(aOperation)
+	{}
+
+void CRefreshTimer::RunL()
+	{
+	// we ignore error, as this is just used ensure that progress is called
+	iOperation.DoRefreshInboxL();
+	}
+
+TInt CRefreshTimer::RunError(TInt /*aError*/)
+	{
+	// Do nothing...
+	return KErrNone;
+	}
+
+CRefreshTimer* CRefreshTimer::NewL(CImapConnectAndSyncOp& aOperation)
+	{
+	CRefreshTimer* self = new(ELeave) CRefreshTimer(aOperation);
+	CleanupStack::PushL(self);
+	self->ConstructL(); // CTimer
+	CActiveScheduler::Add(self);
+	CleanupStack::Pop();
+	return self;
+	}
+