diff -r 238255e8b033 -r 84d9eb65b26f email/pop3andsmtpmtm/imapservermtm/src/IMPSMTM.CPP --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/email/pop3andsmtpmtm/imapservermtm/src/IMPSMTM.CPP Mon May 03 12:29:07 2010 +0300 @@ -0,0 +1,2246 @@ +// 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 "impspan.h" + +#include "impsmtm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imapsess.h" +#include "fldsync.h" +#include "imapsync.h" +#include "imapcomp.h" +#include "imapoffl.h" +#include "impsutil.h" + +#ifdef _DEBUG +#define LOG_COMMANDS(a) iPrimarySession->iSession->LogText a +#define DBG(a) a +#define PRINTING +#else +#define LOG_COMMANDS(a) +#define DBG(a) +#undef PRINTING +#endif + +// Type of MTM request: used only in this file +enum + { + EMtmCopyToLocal=1, + EMtmMoveToLocal, + EMtmCopyFromLocal, + EMtmMoveFromLocal, + EMtmCopyWithinService, + EMtmMoveWithinService, + EMtmPopulate + }; + +// Code for the active wrapper +CActiveWrapper* CActiveWrapper::NewL(TInt aID) + { + CActiveWrapper* self=new(ELeave)CActiveWrapper(aID); + CleanupStack::PushL(self); + self->ConstructL(); + CActiveScheduler::Add( self ); + CleanupStack::Pop(); + return self; + } + +CActiveWrapper::CActiveWrapper(TInt aID) : CActive(0), iID(aID) + { + } + +void CActiveWrapper::ConstructL() + { + // The object is a session + iSession=CImImap4Session::NewL(iID, *this); + + // Get a compound operation + iCompound=CImImap4Compound::NewL(iSession); + + // If we're the primary session, we get a fullsync object too + if (iID==1) + iFullSync=CImImap4Synchronise::NewL(iSession); + } + +void CActiveWrapper::StartL(MActiveWrapperObserver* aManager) + { + DBG((iSession->LogText(_L8("CActiveWrapper::StartL(ID=%d)"),iID))); + + // The request has already been issued with our iStatus, so here all we + // do is note down the completion information and do a SetActive() + iParentPtr=aManager; + // Activate the observer as well, in case the request is cancelled. + iParentPtr->Activate(); + SetActive(); + } + +// Pointer to ActiveWrapper's iStatus, so direct commands to session (see above) +// use the correct iStatus. +TRequestStatus* CActiveWrapper::SessionStatus() + { + // Return the pointer + return(&iStatus); + } + +// Set entry pointers for classes the wrapper owns +void CActiveWrapper::SetEntry(CMsvServerEntry* aEntry) + { + iSession->SetEntry(aEntry); + iCompound->SetEntry(aEntry); + if (iFullSync) + iFullSync->SetEntry(aEntry); + } + +void CActiveWrapper::DoCancel() + { + DBG((iSession->LogText(_L8("CActiveWrapper::DoCancel(ID=%d)"),iID))); + + // Cancel anything we might have outstanding + if (iFullSync) + iFullSync->Cancel(); + iCompound->Cancel(); + if( !iSession->IsCancelling()) + { + // Can only cancel the session if we're not waiting for a cancelled fetch + // to complete. + iSession->Cancel(); + } + + // Tell parent we've completed: DON'T complete it ourselves. + iParentPtr->RequestCompleted(iID,KErrCancel); + } + +#ifdef PRINTING +void CActiveWrapper::DoComplete(TInt aStatus) +#else +void CActiveWrapper::DoComplete(TInt /*aStatus*/) +#endif + { + DBG((iSession->LogText(_L8("CActiveWrapper::DoComplete(%d, id=%d)"),aStatus,iID))); + } + +void CActiveWrapper::RunL() + { + // IMPORTANT: The contents of this CActiveWrapper::RunL() MUST NOT ever User::Leave() + DBG((iSession->LogText(_L8("ActiveWrapper RunL(ID=%d, result=%d)"),iID,iStatus.Int()))); + + TInt error=iStatus.Int(); + Cancel(); // Obvious to do it here + iParentPtr->RequestCompleted(iID,error); + } + +CActiveWrapper::~CActiveWrapper() + { + Cancel(); + delete iSession; + delete iCompound; + delete iFullSync; + } + +void CActiveWrapper::NonCompletedFailure() + { + DBG((iSession->LogText(_L8("ActiveWrapper::NonCompletedFailure (%d)"), iID))); + + // A failure has occured on the session, but there is no outstanding asynchronous + // request on it. This can happen if for instance we get a disconnect while doing + // a cancel and idle operation. Pass the failure on to the parent. + + iParentPtr->NonCompletedFailureOnSession(iID); + } + +// Return of current imap session +CImImap4Session* CActiveWrapper::GetImap4Session() + { + return iSession; + } + +// ----------------------------------------------------------------------- + +// The actual MTM +EXPORT_C CImap4ServerMtm* CImap4ServerMtm::NewL(CRegisteredMtmDll& aRegisteredMtmDll,CMsvServerEntry* aEntry) + { + CImap4ServerMtm* self=new CImap4ServerMtm(aRegisteredMtmDll,aEntry); + if (self==NULL) + { + aRegisteredMtmDll.ReleaseLibrary(); + User::Leave(KErrNoMemory); + } + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(); + return self; + } + +void CImap4ServerMtm::ConstructL() + { + // Make an Imap4 session: make it under the activewrapper, so we can have + // more than one at a time + + // The primary session is the one that does the full sync + iPrimarySession=CActiveWrapper::NewL(1); + + // The secondary session is used if we get fetch requests while the + // primary session is busy + iSecondarySession=CActiveWrapper::NewL(2); + + // create an offline op controller + iOffLineControl=CImap4OffLineControl::NewL(iServerEntry, iPrimarySession->iSession); + + // create the utils object + iUtils = CImap4Utils::NewL(iServerEntry); + + // tell the FullSync session about them + iPrimarySession->iFullSync->SetOffLineControl(iOffLineControl); + iPrimarySession->iFullSync->SetUtils(iUtils); + + // Get an entry selection + iSelection=new (ELeave) CMsvEntrySelection; + + // We're disconnected at first + iLastSessionState=TImap4GenericProgress::EDisconnected; + + // We need to see invisible entries + TMsvSelectionOrdering invisible; + invisible=iServerEntry->Sort(); + invisible.SetShowInvisibleEntries(ETrue); + iServerEntry->SetSort(invisible); + + // Tell sessions and foldersync about it + iPrimarySession->SetEntry(iServerEntry); + iSecondarySession->SetEntry(iServerEntry); + } + +// MTM destructor: clean up - this involves removing the sessions. +CImap4ServerMtm::~CImap4ServerMtm() + { + // We may still be active + Cancel(); + + // Ensure service is marked as offline (if the serviceid was initialised, anyway) + if (iServiceId && iServiceSettings) + { + // Make stuff invisible as necessary + TRAP_IGNORE(MarkOnOrOfflineL(EFalse)); + + // Get rid of settings + delete iServiceSettings; + } + + // Clean up + delete iSelection; + delete iOneSelection; + delete iPrimarySession; + delete iSecondarySession; + delete iOffLineControl; + delete iUtils; + } + +// Do setentry, leave if there is an error +void CImap4ServerMtm::SetEntryL(const TMsvId aId) + { + User::LeaveIfError(iServerEntry->SetEntry(aId)); + } + +// Change entry, leave if error +void CImap4ServerMtm::ChangeEntryL(const TMsvEntry& aEntry) + { + User::LeaveIfError(iServerEntry->ChangeEntry(aEntry)); + } + +// Get children, leave if error +void CImap4ServerMtm::GetChildrenL(CMsvEntrySelection& aSelection) + { + User::LeaveIfError(iServerEntry->GetChildren(aSelection)); + } + +// remove an id, leave if error, moves to the parent first +void CImap4ServerMtm::DeleteEntryL(TMsvId aId) + { + SetEntryL(aId); + SetEntryL(iServerEntry->Entry().Parent()); + User::LeaveIfError(iServerEntry->DeleteEntry(aId)); + } + +// Hierarchically make all folders visible/invisible: we go down until we can't +// find any more folders, so this won't affect messages with subfolders. +// +// We don't use the bulk functions as we don't want to affect messages in the +// folder, and we have to scan the entries to check for recursion anyway. +void CImap4ServerMtm::ChangeVisibilityL(TMsvId aParent, TBool aInvisible) + { + ChangeVisibilityL(aParent, aInvisible, ETrue, KUidMsvFolderEntry); + } + +void CImap4ServerMtm::ChangeVisibilityL(TMsvId aParent, TBool aInvisible, TBool aRecurse, TUid aType) + { + DBG((iPrimarySession->iSession->LogText(_L8("ChangeVisibilityL(%x, %d)"),aParent,aInvisible))); + + // Get children at this level + CMsvEntrySelection* selection=new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(selection); + + CMsvEntrySelection* folders=new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(folders); + + SetEntryL(aParent); + GetChildrenL(*selection); + + if (selection->Count()) + { + DBG((iPrimarySession->iSession->LogText(_L8(" Found %d children"),selection->Count()))); + + for(TInt child=0;childCount();child++) + { + // Move to this child + SetEntryL((*selection)[child]); + TMsvEntry message=iServerEntry->Entry(); + + DBG((iPrimarySession->iSession->LogText(_L8(" type %x visible %d"),message.iType,message.Visible()))); + + // Is this the type we want to change? + if (message.iType==aType) + { + // Add to selection to do bulk change on, if necessary + if ((message.Visible() && aInvisible) || + (!message.Visible() && !aInvisible)) + { + DBG((iPrimarySession->iSession->LogText(_L8(" Adding %x to change list"),message.Id()))); + + folders->AppendL(message.Id()); + } + } + + // Recurse downwards + if (aRecurse && message.iType==KUidMsvFolderEntry) + ChangeVisibilityL(message.Id(),aInvisible,aRecurse,aType); + } + + // Change its visibility off all children if necessary + if (folders->Count()) + { + // Do the change to the invisible flag (actual constant for the + // flag we want is private :( ) + SetEntryL(aParent); + User::LeaveIfError(iServerEntry->ChangeAttributes(*folders, + aInvisible?0:KMsvVisibilityAttribute, + aInvisible?KMsvVisibilityAttribute:0)); + } + } + + // Release the service entry + SetEntryL(KMsvNullIndexEntryId); + + // Get rid of selection + CleanupStack::PopAndDestroy(2); + } +void CImap4ServerMtm::ClearNewFlagL(TMsvId aParent) + { + // Get children at this level + CMsvEntrySelection* selection=new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(selection); + CMsvEntrySelection* msgs=new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(msgs); + SetEntryL(aParent); + GetChildrenL(*selection); + // for each child + TInt count=selection->Count(); + for(TInt child=0;childEntry(); + if (entry.New()&& entry.iType==KUidMsvMessageEntry) + msgs->AppendL(entry.Id()); + // if this is a folder then recurse + if (entry.iType==KUidMsvFolderEntry) + ClearNewFlagL((*selection)[child]); + } + if (msgs->Count())// change attribute only when it has finished looking at all the children + { + SetEntryL(aParent); + User::LeaveIfError(iServerEntry->ChangeAttributes(*msgs,0,KMsvNewAttribute)); + iPrimarySession->iSession->LogText(_L8("number of new msgs = (%d)"),msgs->Count()); + } + CleanupStack::PopAndDestroy(2); // selection,msgs + } + + + +// Mark service as on or offline +void CImap4ServerMtm::MarkOnOrOfflineL(const TBool aOnline) + { + // Mark service entry as on/offline + SetEntryL(iServiceId); + TMsvEntry entry=iServerEntry->Entry(); + entry.SetConnected(aOnline); + ChangeEntryL(entry); + + // Release the service entry + SetEntryL(KMsvNullIndexEntryId); + + // Going offline? + if (!aOnline && iServiceSettings->DisconnectedUserMode()) + { + // We're an expert user going offline: don't touch anything + return; + } + + // Mark all immediate children of the service as invisible + if (!aOnline) + ChangeVisibilityL(iServiceId,!aOnline); + } + +// Do the misc bits we need to when we've gone offline +void CImap4ServerMtm::GoneOffline() + { + DBG((iPrimarySession->iSession->LogText(_L8("GoneOffline called")))); + + // Cancel sessions: parking might be dangerous otherwise + iPrimarySession->Cancel(); + iSecondarySession->Cancel(); + + // Park entries (in case a move entry has been left somewhere dangerous by a + // leave) + iPrimarySession->iSession->Park(); + iSecondarySession->iSession->Park(); + + // Only do this is we have a valid service - we may not, due to being called + // both by the state machine and the progress entry + if (iServiceId) + { + // Offline, make folders invisible if we're not a disconnected mode user + TRAP_IGNORE(MarkOnOrOfflineL(EFalse)); + + // Clear serviceid, it's not valid anymore + iServiceId=0; + } + + // Settings can be dumped now + delete iServiceSettings; + iServiceSettings=NULL; + + // We can now be deleted + iCanBeDeletedNow=ETrue; + } + +// Check selection: all entries should be of appropriate +// type. Messages in this context means complete messages, not RFC888 +// parts of another message. parts means attachment or text +// bodies. Make a local copy of valid entries + +TInt CImap4ServerMtm::CheckSelectionL(const CMsvEntrySelection& aSelection, + CMsvEntrySelection* aLocalCopy, + const TBool aMessages, + const TBool aParts, + const TBool aFolders, + const TBool aIsInService) + { + // Reset copy selection + aLocalCopy->Reset(); + + // Check all entries are messages + for(TInt a=0;aSetEntry(aSelection[a])==KErrNone) + { + TUid type = iServerEntry->Entry().iType; + if ((aMessages && type==KUidMsvMessageEntry) || + (aParts && (type==KUidMsvEmailTextEntry || type==KUidMsvAttachmentEntry || type==KUidMsvMessageEntry)) || + (aFolders && type==KUidMsvFolderEntry)) + { + TBool inEnclosingMessage=EFalse; + + // Do we need to check if it's in the local service or + // if it is a complete message + if (aIsInService || (!aParts && type==KUidMsvMessageEntry)) + { + // Work up the tree until we get to the service or the root + do + { + SetEntryL(iServerEntry->Entry().Parent()); + if (iServerEntry->Entry().iType==KUidMsvMessageEntry) + inEnclosingMessage=ETrue; + } + while(iServerEntry->Entry().iType!=KUidMsvServiceEntry && + iServerEntry->Entry().Id()!=KMsvRootIndexEntryId); + + // Are we at the service that this MTM referrs to? + // SJM: if offline iServiceId==0 so allow all + if (!aIsInService || iServiceId==0 || iServerEntry->Entry().Id()==iServiceId) + { + // it's OK if it is not a message type (in + // which case it has already been checked and + // passed) or it is not within an enclosing message + if (type!=KUidMsvMessageEntry || !inEnclosingMessage || aParts) + addIt = ETrue; + } + } + else + { + // Add to local copy + addIt = ETrue; + } + } + } + + if (addIt) + aLocalCopy->AppendL(aSelection[a]); +#ifdef _DEBUG + // UI shouldn't really be giving us bogus items so panic + else + gPanic(EInvalidMsvTypeToCommand); +#endif + } + + // Anything to do? + if (!aLocalCopy->Count()) + { + // Nothing valid to work with + User::RequestComplete(iRequest,KErrNotSupported); + return(KErrNotSupported); + } + + // All OK, the selection isn't empty + return(KErrNone); + } + +// aId has been unsubscribed. If it has no visible child folders then +// make it invisible and check its parent with the same test +void CImap4ServerMtm::PropagateInvisibleFlagL(TMsvId aId) + { + DBG((iPrimarySession->iSession->LogText(_L8("PropagateInvisibleFlagL: 0x%x"), aId))); + + // finish if we've reached the top + if (aId == KMsvRootIndexEntryId) + return; + + SetEntryL(aId); + + // finish if we've reached a service + if (iServerEntry->Entry().iType == KUidMsvServiceEntry) + return; + + // return if we've found a subscribed folder since we can't make + // it invisible + if (((TMsvEmailEntry)iServerEntry->Entry()).LocalSubscription()) + return; + + // check the children of this unsubscribed folder + CMsvEntrySelection* selection=new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(selection); + + GetChildrenL(*selection); + + TBool visible=EFalse; + for (TInt i=0; i < selection->Count(); i++) + { + SetEntryL((*selection)[i]); + + // look for a visible folder + TMsvEmailEntry entry = (TMsvEmailEntry)iServerEntry->Entry(); + if (entry.iType == KUidMsvFolderEntry && entry.Visible()) + { + visible=ETrue; + break; + } + } + + CleanupStack::PopAndDestroy(); // selection + + // if no child folders were visible then make this folder + // invisible and continue up + if (!visible) + { + SetEntryL(aId); + + // make this invisible + TMsvEntry entry = iServerEntry->Entry(); + entry.SetVisible(EFalse); + ChangeEntryL(entry); + + // go up + PropagateInvisibleFlagL(entry.Parent()); + } + } + +// Simply change local subscription flag on a folder immediately: This is used +// as opposed to ChangeL() as this operation can occur offline. + +// SJM: If SubscribeStrategy is UpdateRemote or UpdateBoth then doing +// this should trigger a RemoteSubscription? + +TInt CImap4ServerMtm::SetLocalSubscription(const TMsvId aFolder, TBool aSubscribed) + { + TInt err; + + // Move to the entry + if ((err=iServerEntry->SetEntry(aFolder))!=KErrNone) + return(err); + + // Check it's a folder + if (iServerEntry->Entry().iType!=KUidMsvFolderEntry) + return(KErrNotSupported); + + DBG((iPrimarySession->iSession->LogText(_L8("SetLocalSubscription: 0x%x %d"), aFolder, aSubscribed))); + + // Twiddle flag + TMsvEmailEntry entry=iServerEntry->Entry(); + entry.SetLocalSubscription(aSubscribed); + return(iServerEntry->ChangeEntry(entry)); + } + + +// Perform a general move/copy function: this tidies up a lot of the Copy/Move +// MTM specifics into one place +void CImap4ServerMtm::MtmCommandL(const TInt aType,const CMsvEntrySelection& aSelection, + TMsvId aDestination, TRequestStatus& aStatus) + { + iRequest=&aStatus; + // reset error code + iProgressErrorCode=KErrNone; + + // Is source remote? Ensure we check it's from our service + TBool checksource=ETrue; + if (aType==EMtmCopyFromLocal || aType==EMtmMoveFromLocal) + checksource=EFalse; + + // we can only handle parts of messages on Populate + TBool handleParts=EFalse; + if (aType==EMtmPopulate||aType==EMtmCopyToLocal) + handleParts=ETrue; + + // Check selection contains messages, or maybe parts + if (CheckSelectionL(aSelection,iSelection,ETrue,handleParts,EFalse,checksource)) + return; + + // Save destination + iDestination=aDestination; + + // Are we online? + if (!iPrimarySession->iSession->Connected()) + { + iProgressMsgsToDo=iSelection->Count(); + iProgressMsgsDone=0; + + // set state for dorunl + switch(aType) + { + case EMtmCopyToLocal: + iState=iSavedState=EMtmStateOffLineCopyToLocal; + iRequestedOperation=TImap4GenericProgress::EOffLineCopyToLocal; + break; + + case EMtmCopyFromLocal: + iState=iSavedState=EMtmStateOffLineCopyFromLocal; + iRequestedOperation=TImap4GenericProgress::EOffLineCopyFromLocal; + break; + + case EMtmCopyWithinService: + iState=iSavedState=EMtmStateOffLineCopyWithinService; + iRequestedOperation=TImap4GenericProgress::EOffLineCopyWithinService; + break; + + case EMtmMoveToLocal: + iState=iSavedState=EMtmStateOffLineMoveToLocal; + iRequestedOperation=TImap4GenericProgress::EOffLineMoveToLocal; + break; + + case EMtmMoveFromLocal: + iState=iSavedState=EMtmStateOffLineMoveFromLocal; + iRequestedOperation=TImap4GenericProgress::EOffLineMoveFromLocal; + break; + + case EMtmMoveWithinService: + iState=iSavedState=EMtmStateOffLineMoveWithinService; + iRequestedOperation=TImap4GenericProgress::EOffLineMoveWithinService; + break; + + case EMtmPopulate: + { + iState=iSavedState=EMtmStateOffLinePopulate; + iRequestedOperation=TImap4GenericProgress::EOffLinePopulate; + break; + } + default: + break; + } + TRequestStatus* pS=(&iStatus); + User::RequestComplete(pS,KErrNone); + SetActive(); + } + else + { + TRequestStatus* status; + + // Default to primary session + iCurrentSession=iPrimarySession; + status=iCurrentSession->SessionStatus(); + + // Initialise messages to do, etc + iProgressMsgsToDo=iSelection->Count(); + iProgressMsgsDone=0; + + switch(aType) + { + case EMtmPopulate: + case EMtmCopyToLocal: + // We're online: is the primary session busy? + if( iBackgroundSyncInProgress || iCurrentSession->iSession->IsCancelling() || + iPrimarySession->iSession->Busy()) + { + DBG((iPrimarySession->iSession->LogText(_L8("Primary session busy, using secondary")))); + + // We'll have to use the secondary session + iCurrentSession=iSecondarySession; + + // Is the secondary session connected? + if (!iCurrentSession->iSession->Connected()) + { + DBG((iPrimarySession->iSession->LogText(_L8("Connecting secondary")))); + + // We need to connect the secondary session before + // continuing with the fetch. Do it. The source message + // selection has been checked and stored in iSelection, + // so it should be safe until the connect goes through + // for later issue. + iState=EMtmStateSecondaryConnect; + iSavedState=aType == EMtmPopulate ? EMtmStatePopulate : EMtmStateCopyToLocal; + + // Issue the connect + iCurrentSession->StartL(this); + status=iCurrentSession->SessionStatus(); + // Providing the reference of primary session + iCurrentSession->iSession->SetPrimarySession(iPrimarySession); + iCurrentSession->iSession->ConnectL(*status,iServiceId); + break; + } + else if( iCurrentSession->iSession->IsCancelling() ) + { + // Opps! Cannot do the populate currently - server busy. + DBG((iPrimarySession->iSession->LogText(_L8("Secondary already cancelling")))); + + User::RequestComplete(iRequest,KErrServerBusy); + return; + } + + DBG((iPrimarySession->iSession->LogText(_L8("Secondary already connected, reusing")))); + } + + // Calculate the total size of the messages to be downloaded in this selection. + iTotalSize = iCurrentSession->iSession->CalculateDownloadSizeL(*iSelection); + + // Select relevant folder + iCurrentSession->StartL(this); + status=iCurrentSession->SessionStatus(); + iUtils->SetUpLogMessageL((*iSelection)[0]); + if (aType == EMtmPopulate) + { + iState = EMtmStatePopulate; + // compound need to know the number of msgs to suspend imap idle calls if > 1 + if(iProgressMsgsToDo > 0) + { + iCurrentSession->iCompound->SetMessageCount(iProgressMsgsToDo); + } + DBG((iPrimarySession->iSession->LogText(_L8("CImapMTM::MTMCommandL(): Calling iCurrentSession->iCompound->Populate()")))); + iCurrentSession->iCompound->PopulateL(*status,(*iSelection)[0],iPartialMailInfo); + } + else + { + iState = EMtmStateCopyToLocal; + iCurrentSession->iCompound->CopyToLocalL(*status,(*iSelection)[0],iDestination); + } + break; + + case EMtmMoveToLocal: + // We're online, are we busy? (it's a write op, and so we must use the + // primary session) + if( iBackgroundSyncInProgress || iCurrentSession->iSession->IsCancelling() ) + { + // Can't do it, we're busy + User::RequestComplete(iRequest,KErrServerBusy); + return; + } + + // Calculate the total size of the messages to be downloaded in this selection. + iTotalSize = iCurrentSession->iSession->CalculateDownloadSizeL(*iSelection); + + // Select relevant folder + iCurrentSession->StartL(this); + iState=EMtmStateMoveToLocal; + iUtils->SetUpLogMessageL((*iSelection)[0]); + iCurrentSession->iCompound->MoveToLocalL(*status,(*iSelection)[0],iDestination); + break; + + case EMtmCopyFromLocal: + case EMtmMoveFromLocal: + + // As this is a write operation, is must be done on the primary session: + // is it free? + if( iBackgroundSyncInProgress || iCurrentSession->iSession->IsCancelling() ) + { + // Can't do it + User::RequestComplete(iRequest,KErrServerBusy); + return; + } + + // Calculate the total size of the messages to be downloaded in this selection. + iTotalSize = iCurrentSession->iSession->CalculateDownloadSizeL(*iSelection); + + iState=(aType==EMtmCopyFromLocal)?EMtmStateCopyFromLocal:EMtmStateMoveFromLocal; + + + // Start the first operation + iCurrentSession->StartL(this); + if (aType==EMtmCopyFromLocal) + iCurrentSession->iCompound->CopyFromLocalL(*status,(*iSelection)[0],iDestination); + else + iCurrentSession->iCompound->MoveFromLocalL(*status,(*iSelection)[0],iDestination); + break; + + case EMtmCopyWithinService: + case EMtmMoveWithinService: + // As this is a write operation, is must be done on the primary session: + // is it free? + if( iBackgroundSyncInProgress || iCurrentSession->iSession->IsCancelling() ) + { + // Can't do it + User::RequestComplete(iRequest,KErrServerBusy); + return; + } + + // Set the state, as we may be dealing with multiple entries + iState=(aType==EMtmCopyWithinService)?EMtmStateCopyWithinService: + EMtmStateMoveWithinService; + + // Do the copy with the compound + iCurrentSession->StartL(this); + if (aType==EMtmCopyWithinService) + iCurrentSession->iCompound->CopyWithinServiceL(*status,*iSelection,iDestination); + else + iCurrentSession->iCompound->MoveWithinServiceL(*status,*iSelection,iDestination); + break; + } + } + } + + +// CopyToLocal fetches message parts into the mirror: aDestination is currently +// not used. This is the only command which can cause the second session to +// connect, as it is for fetching *only* (not 'modifiable' commands which may +// cause the current synchronisation in the primary session to hiccup). This +// also rules out MoveToLocal, as it involves a delete on the remote server. +void CImap4ServerMtm::CopyToLocalL(const CMsvEntrySelection& aSelection,TMsvId aDestination, TRequestStatus& aStatus) + { + aStatus=KRequestPending; + + LOG_COMMANDS((_L8("MTMCOMMAND CopyToLocal(%d items to %x)"),aSelection.Count(),aDestination)); + + iRequestedOperation=TImap4GenericProgress::ECopyToLocal; + iGetMailOptions=EGetImap4EmailBodyTextAndAttachments; + MtmCommandL(EMtmCopyToLocal,aSelection,aDestination,aStatus); + } + +// Move To Local moves a message from the remote server to a local folder: +// this is performed as a fetch, then a delete on the remote server. +void CImap4ServerMtm::MoveToLocalL(const CMsvEntrySelection& aSelection,TMsvId aDestination, TRequestStatus& aStatus) + { + aStatus=KRequestPending; + + LOG_COMMANDS((_L8("MTMCOMMAND MoveToLocal(%d items to %x)"),aSelection.Count(),aDestination)); + + // We can't do a 'movetolocal' with the destination being the same as the + // source, as then the mirror will be out of sync. We must be moving to somewhere + // outside this service. Check this. + if (!iOffLineControl->IdIsLocalL(aDestination)) + { + TRequestStatus* status=&aStatus; + User::RequestComplete(status,KErrNotSupported); + } + else + { + iRequestedOperation=TImap4GenericProgress::EMoveToLocal; + iGetMailOptions=EGetImap4EmailBodyTextAndAttachments; + MtmCommandL(EMtmMoveToLocal,aSelection,aDestination,aStatus); + } + } + +// CopyFromLocal appends entire messages in the mirror to the server. +void CImap4ServerMtm::CopyFromLocalL(const CMsvEntrySelection& aSelection,TMsvId aDestination, TRequestStatus& aStatus) + { + aStatus=KRequestPending; + + LOG_COMMANDS((_L8("MTMCOMMAND CopyFromLocal(%d items to %x)"),aSelection.Count(),aDestination)); + + iRequestedOperation=TImap4GenericProgress::ECopyFromLocal; + MtmCommandL(EMtmCopyFromLocal,aSelection,aDestination,aStatus); + } + +// Does a CopyFromLocal (ie, IMAP APPEND of the message), then deletes the local message +// if it was sucessful. +void CImap4ServerMtm::MoveFromLocalL(const CMsvEntrySelection& aSelection,TMsvId aDestination, TRequestStatus& aStatus) + { + aStatus=KRequestPending; + + LOG_COMMANDS((_L8("MTMCOMMAND MoveFromLocal(%d items to %x)"),aSelection.Count(),aDestination)); + + iRequestedOperation=TImap4GenericProgress::EMoveFromLocal; + MtmCommandL(EMtmMoveFromLocal,aSelection,aDestination,aStatus); + } + +// CopyWithinService copies entire messages to other folders on the server, using +// the IMAP COPY command. +void CImap4ServerMtm::CopyWithinServiceL(const CMsvEntrySelection& aSelection,TMsvId aDestination, TRequestStatus& aStatus) + { + aStatus=KRequestPending; + + LOG_COMMANDS((_L8("MTMCOMMAND CopyWithinService(%d items to %x)"),aSelection.Count(),aDestination)); + + iRequestedOperation=TImap4GenericProgress::ECopyWithinService; + MtmCommandL(EMtmCopyWithinService,aSelection,aDestination,aStatus); + } + +// MoveWithinService copies entire messages to other folders on the server, using +// the IMAP COPY command, then deletes the original if the copy was sucessful, +// so performing a move. +void CImap4ServerMtm::MoveWithinServiceL(const CMsvEntrySelection& aSelection,TMsvId aDestination, TRequestStatus& aStatus) + { + aStatus=KRequestPending; + + LOG_COMMANDS((_L8("MTMCOMMAND MoveWithinService(%d items to %x)"),aSelection.Count(),aDestination)); + + iRequestedOperation=TImap4GenericProgress::EMoveWithinService; + MtmCommandL(EMtmMoveWithinService,aSelection,aDestination,aStatus); + } + +void CImap4ServerMtm::UndeleteAllL(const CMsvEntrySelection& aSelection, TRequestStatus& aStatus) + { + aStatus = KRequestPending; + LOG_COMMANDS((_L8("MTMCOMMAND UndeleteAll(%d items)"),aSelection.Count())); + if(IsActive()) + { + TRequestStatus* request = &aStatus; + User::RequestComplete(request,KErrImapServerBusy); + return; + } + + iRequest=&aStatus; + iProgressErrorCode=KErrNone; + + // Check selection contains only messages in the service + if (CheckSelectionL(aSelection,iSelection,ETrue,EFalse,EFalse,ETrue)) + return; + + iRequestedOperation=TImap4GenericProgress::EOffLineUndelete; + + iProgressMsgsToDo=iSelection->Count(); + iProgressMsgsDone=0; + iState=iSavedState=EMtmStateOffLineUndelete; + + TRequestStatus* pS=(&iStatus); + User::RequestComplete(pS,KErrNone); + SetActive(); + } + + +void CImap4ServerMtm::DeleteAllL(const CMsvEntrySelection& aSelection, TRequestStatus& aStatus) + { + aStatus=KRequestPending; + + LOG_COMMANDS((_L8("MTMCOMMAND DeleteAll(%d items)"),aSelection.Count())); + + iRequest=&aStatus; + // reset error code + iProgressErrorCode=KErrNone; + + if (PruneMessages(aSelection)) + // Was this call to DeleteAllL an instruction to delete local parts of a message ? + // If so then we don't need to continue. + return; + + // Are we online? + if (!iPrimarySession->iSession->Connected()) + { + // SJM: Surely we need something like... + // Check selection contains only messages - not folders + if (CheckSelectionL(aSelection,iSelection,ETrue,EFalse,EFalse,EFalse)) + return; + + iRequestedOperation=TImap4GenericProgress::EOffLineDelete; + + iProgressMsgsToDo=iSelection->Count(); + iProgressMsgsDone=0; + iState=iSavedState=EMtmStateOffLineDelete; + + TRequestStatus* pS=(&iStatus); + User::RequestComplete(pS,KErrNone); + SetActive(); + } + else + { + iRequestedOperation=TImap4GenericProgress::EDelete; + + // We need to do this in the primary session: is it busy? + if (iBackgroundSyncInProgress || iPrimarySession->iSession->IsCancelling() || + iPrimarySession->iSession->Busy()) + { + // It's busy, we can't do it + User::RequestComplete(iRequest,KErrServerBusy); + return; + } + + // Message or folder to delete? + DBG((iPrimarySession->iSession->LogText(_L8("First is %x"),aSelection[0]))); + + SetEntryL(aSelection[0]); + if (iServerEntry->Entry().iType==KUidMsvMessageEntry) + { + DBG((iCurrentSession->iSession->LogText(_L8("Delete message")))); + + // Check selection contains only messages + if (CheckSelectionL(aSelection,iSelection,ETrue,EFalse,EFalse,ETrue)) + return; + + iProgressMsgsToDo=iSelection->Count(); + iProgressMsgsDone=0; + // Select parent folder of message + iCurrentSession=iPrimarySession; + iState=iSavedState=EMtmStateDelete; + TRequestStatus* status=iCurrentSession->SessionStatus(); + iCurrentSession->iCompound->DeleteL(*status,*iSelection); + iCurrentSession->StartL(this); + } + else if (iServerEntry->Entry().iType==KUidMsvFolderEntry) + { + DBG((iCurrentSession->iSession->LogText(_L8("Delete folder")))); + + // Check selection contains only folders + if (CheckSelectionL(aSelection,iSelection,EFalse,EFalse,ETrue,ETrue)) + return; + + // Delete folder + iCurrentSession=iPrimarySession; + iState=iSavedState=EMtmStateDeleteFolder; + TRequestStatus* status=iCurrentSession->SessionStatus(); + iCurrentSession->iCompound->DeleteFolderL(*status,(*iSelection)[0]); + iCurrentSession->StartL(this); + } + else + gPanic(EDeleteOfUnknownType); + } + } + +// Create: make a folder or mailbox. Can only happen online +void CImap4ServerMtm::CreateL(TMsvEntry aNewEntry, TRequestStatus& aStatus) + { + LOG_COMMANDS((_L8("MTMCOMMAND Create(parent %x)"),aNewEntry.Parent())); + + aStatus=KRequestPending; + iRequest=&aStatus; + // reset error code + iProgressErrorCode=KErrNone; + + // Creating a folder? + if (aNewEntry.iType!=KUidMsvFolderEntry) + { + // No - illegal op + User::RequestComplete(iRequest,KErrNotSupported); + return; + } + + // Are we online and not busy? + if( iBackgroundSyncInProgress || iPrimarySession->iSession->IsCancelling() ) + { + User::RequestComplete(iRequest,KErrServerBusy); + return; + } + + // Folder or mailbox? + TBool isfolder=ETrue; + if (((TMsvEmailEntry)aNewEntry).Mailbox()) + isfolder=EFalse; + + // Use primary session: issue create folder command + iState=EMtmStateCreateFolder; + iCurrentSession=iPrimarySession; + TRequestStatus* status=iCurrentSession->SessionStatus(); + iCurrentSession->iCompound->CreateL(*status,aNewEntry.Parent(),aNewEntry.iDetails,isfolder); + iCurrentSession->StartL(this); + } + +void CImap4ServerMtm::ChangeL(TMsvEntry aNewEntry, TRequestStatus& aStatus) + { + LOG_COMMANDS((_L8("MTMCOMMAND Change(%x)"),aNewEntry.Id())); + + User::LeaveIfError(iServerEntry->SetEntry( aNewEntry.Id() )); + User::LeaveIfError(iServerEntry->ChangeEntry( aNewEntry )); + + iRequest=&aStatus; + // reset error code + iProgressErrorCode=KErrNone; + + User::RequestComplete(iRequest, KErrNone); + } + +// SJM: Old ChangeL entry. used to be able to update flags as well as +// rename folder. Change to only rename folder as its not obvious how +// you'd pass the flags across +TBool CImap4ServerMtm::RenameFolderL(TMsvId aId, const TImap4RenameFolder& aRename) + { + LOG_COMMANDS((_L8("MTMCOMMAND RenameFolder(%x) to %S"),aId,&aRename.iNewName)); + + // Are we online and not busy? + if( iBackgroundSyncInProgress || iPrimarySession->iSession->IsCancelling() ) + { + User::RequestComplete(iRequest,KErrServerBusy); + return EFalse; + } + + // Issue a rename command on this object + iState=EMtmStateRenameFolder; + iCurrentSession=iPrimarySession; + TRequestStatus* status=iCurrentSession->SessionStatus(); + iCurrentSession->iCompound->RenameL(*status, aId, aRename.iNewName); + return ETrue; + } + +// id can be either the service or anything below it. If it is not the +// service then we move up the tree until we find the service. +void CImap4ServerMtm::LoadSettingsL(TMsvId aId) + { + // Old settings shouldn't be hanging about, but... + if (iServiceSettings) + { + delete iServiceSettings; + iServiceSettings=NULL; + } + + // Get somewhere to store service settings + iServiceSettings=new (ELeave) CImImap4Settings; + + // find the service + SetEntryL(aId); + while (iServerEntry->Entry().iType != KUidMsvServiceEntry) + SetEntryL(iServerEntry->Entry().Parent()); + + // Get settings for this service + CEmailAccounts* account = CEmailAccounts::NewLC(); + TImapAccount id; + id.iImapAccountId = iServerEntry->Entry().MtmData2(); // iMtmData2 of the service entry contains TImapAccountId + id.iImapAccountName = iServerEntry->Entry().iDetails; + id.iImapService = iServerEntry->Entry().iServiceId; + id.iSmtpService = iServerEntry->Entry().iRelatedId; + + account->LoadImapSettingsL(id, *iServiceSettings); + CleanupStack::PopAndDestroy(account); + } + +void CImap4ServerMtm::StartCommandL(CMsvEntrySelection& aSelection, TInt aCommand, const TDesC8& aParameter, TRequestStatus& aStatus) + { + LOG_COMMANDS((_L8("MTMCOMMAND StartCommand(%x)"),aCommand)); + + iTotalSize = 0; + aStatus=KRequestPending; + iRequest=&aStatus; + // reset error code + iProgressErrorCode=KErrNone; + + if(iPrimarySession->iSession->IsActive()) + LOG_COMMANDS((_L8("CImap4ServerMTM::StartCommand(): iPrimarySession->iSession is ACTIVE"))); + else + LOG_COMMANDS((_L8("CImap4ServerMTM::StartCommand(): iPrimarySession->iSession is NOT ACTIVE"))); + + + // Who are we sending this request to? Get their session + iCurrentSession=iPrimarySession; + TRequestStatus* status=iCurrentSession->SessionStatus(); + + // Certain commands require that we are online: check that we're online (the command may have + // been queued when we were online, but we've lost the connection now) + if (aCommand==KIMAP4MTMSynchronise || aCommand==KIMAP4MTMFullSync || + aCommand==KIMAP4MTMInboxNewSync || aCommand==KIMAP4MTMFolderFullSync || + aCommand==KIMAP4MTMRenameFolder || aCommand==KIMAP4MTMDisconnect || + aCommand==KIMAP4MTMSyncTree || aCommand==KIMAP4MTMSelect) + { + if (!iPrimarySession->iSession->Connected()) + { + // Can't hack this! + User::RequestComplete(iRequest,KErrDisconnected); + return; + } + } + + // Save command, as when it completes we would like to know what happened... + iState=EMtmStateMiscCommand; + TBool notComplete=EFalse; + switch(iLastCommand=aCommand) + { + case KIMAP4MTMIsConnected: + // Are we connected? + User::RequestComplete(iRequest, + iPrimarySession->iSession->Connected()?KErrNone:KErrDisconnected); + break; + + case KIMAP4MTMBusy: + // Are we busy? + if (iBackgroundSyncInProgress || + iSecondarySession->iSession->Busy()) + User::RequestComplete(iRequest,KErrServerBusy); + else + User::RequestComplete(iRequest,KErrNone); + break; + + case KIMAP4MTMConnect: + case KIMAP4MTMConnectAndSynchronise: + if (iPrimarySession->iSession->Busy() || + iPrimarySession->iSession->Connected()) + { + User::RequestComplete(iRequest,KErrServerBusy); + } + else + { + iServiceId=aSelection[0]; + + LoadSettingsL(iServiceId); + + // We want to hang around for this one + iCanBeDeletedNow=EFalse; + + // The selection passed through will be any messages selected for download. + // Inform the session about these as they may potentially be deleted during sync. + iCurrentSession->iSession->SetSynchronisationSelectionL(aSelection); + + // Queue a connect + iRequestedOperation=TImap4GenericProgress::EConnect; + iCurrentSession->iSession->ConnectL(*status,iServiceId); + + notComplete = ETrue; + } + break; + + case KIMAP4MTMStartBatch: + // We're about to receive multiple commands from the client: ensure we + // stay loaded by faking the CommandExpected() result to be ETrue + iBatchInProgress=ETrue; + User::RequestComplete(iRequest,KErrNone); + break; + + case KIMAP4MTMEndBatch: + // Stop faking the CommandExpected(), the client has finished with their + // batch of commands. + iBatchInProgress=EFalse; + User::RequestComplete(iRequest,KErrNone); + break; + + case KIMAP4MTMCancelBackgroundSynchronise: + // Anything to cancel? + if (iBackgroundSyncInProgress) + { + // Cancel it + iPrimarySession->Cancel(); + iBackgroundSyncInProgress=EFalse; + User::RequestComplete(iRequest,KErrCancel); + } + else + User::RequestComplete(iRequest,KErrNone); + break; + + case KIMAP4MTMSelect: + // Queue a selection command + iRequestedOperation=TImap4GenericProgress::ESelect; + iCurrentSession->iCompound->SelectL(*status,aSelection[0]); + notComplete = ETrue; + break; + + case KIMAP4MTMSynchronise: + // Queue a synchronise operation + iRequestedOperation=TImap4GenericProgress::ESync; + if (iClearNewFlagOnNextSync) + { + ClearNewFlagL(iServiceId); + iClearNewFlagOnNextSync = EFalse; + } + + iCurrentSession->iCompound->SynchroniseL(*status); + notComplete = ETrue; + break; + + case KIMAP4MTMSyncTree: + // Queue a Synchronise Tree operation + if (iBackgroundSyncInProgress) + { + User::RequestComplete(iRequest,KErrServerBusy); + return; + } + + iRequestedOperation=TImap4GenericProgress::ESync; + iCurrentSession->iFullSync->SynchroniseTreeL(*status,iServiceId,ETrue); + notComplete = ETrue; + break; + + case KIMAP4MTMLocalSubscribe: + // Change local subscription flag on a folder + User::RequestComplete(iRequest,SetLocalSubscription(aSelection[0],ETrue)); + break; + + case KIMAP4MTMLocalUnsubscribe: + { + // Change local subscription flag on a folder + TMsvId folder = aSelection[0]; + TInt err=SetLocalSubscription(folder,EFalse); + if(err==KErrNone) + { + // if we don't have any service settings then load + // them + TBool loadedSettings = EFalse; + if (iServiceSettings==NULL) + { + LoadSettingsL(folder); + loadedSettings = ETrue; + } + + // if synchronisation setting is not remote only then + // update the invisibility flags + if (iServiceSettings->Synchronise() != EUseRemote) + { + PropagateInvisibleFlagL(folder); + ChangeVisibilityL(folder,ETrue,EFalse,KUidMsvMessageEntry); + } + + // if we loaded settings especially then free them + // again + if (loadedSettings) + { + delete iServiceSettings; + iServiceSettings=NULL; + } + } + User::RequestComplete(iRequest,err); + break; + } + + case KIMAP4MTMFullSync: + // Do a full synchronise if we're not doing one already + if (iBackgroundSyncInProgress) + { + // We're busy, go away + User::RequestComplete(iRequest,KErrServerBusy); + } + else + { + iRequestedOperation=TImap4GenericProgress::ESync; + if (iClearNewFlagOnNextSync) + { + ClearNewFlagL(iServiceId); + iClearNewFlagOnNextSync = EFalse; + } + + // SJM: Note new folders should always be invisible as + // they are not subscribed + iCurrentSession->iFullSync->SynchroniseL(*status,iServiceId, ETrue, + !iServiceSettings->DeleteEmailsWhenDisconnecting()); + iState=EMtmStateForegroundSync; + notComplete = ETrue; + } + break; + + case KIMAP4MTMPopulate: + // this function is just a copy to mirror. + { + iRequestedOperation=TImap4GenericProgress::EPopulate; + if (aParameter.Length() > 0) + { + TImImap4GetPartialMailInfo imap4GetPartialMailInfo; + TPckgC paramPartialPack(imap4GetPartialMailInfo); + paramPartialPack.Set(aParameter); + iPartialMailInfo = paramPartialPack(); + } + + MtmCommandL(EMtmPopulate,aSelection,KMsvNullIndexEntryId,aStatus); + break; + } + + case KIMAP4MTMInboxNewSync: + if (iBackgroundSyncInProgress) + { + // We're busy, go away + User::RequestComplete(iRequest,KErrServerBusy); + } + else + { + // First of all, find the inbox + CMsvEntrySelection *findinbox=new (ELeave) CMsvEntrySelection; + CleanupStack::PushL(findinbox); + SetEntryL(iServiceId); + GetChildrenL(*findinbox); + TMsvId inbox=0; + for(TInt a=0;aCount();a++) + { + SetEntryL((*findinbox)[a]); + if (iServerEntry->Entry().iDetails.CompareF(KIMAP_INBOX)==0) + { + inbox=(*findinbox)[a]; + break; + } + } + + // Clean up + CleanupStack::PopAndDestroy(); + + // Found it? + if (inbox) + { + // Start a new-only sync of the inbox + iRequestedOperation=TImap4GenericProgress::ESync; + iCurrentSession->iCompound->NewOnlySyncL(*status,inbox); + notComplete = ETrue; + } + else + { + // Couldn't find it! + User::RequestComplete(iRequest,KErrNotFound); + } + } + break; + + case KIMAP4MTMFolderFullSync: + // Start a full sync of the folder + iRequestedOperation=TImap4GenericProgress::ESync; + iCurrentSession->iCompound->FullSyncL(*status,aSelection[0]); + notComplete = ETrue; + break; + + case KIMAP4MTMWaitForBackground: + // Wait for background operation to complete: is one running? + if (!iBackgroundSyncInProgress) + { + // No, just return + User::RequestComplete(iRequest,KErrNone); + } + else + { + // Otherwise, wait for completion + iState=EMtmStateWaitingForBackgroundToFinish; + // Activate instead of SetActive() + Activate(); + } + break; + + case KIMAP4MTMDisconnect: + // if we want to delete emails on disconnect and we've + // finished the background sync + if ( iServiceSettings->DeleteEmailsWhenDisconnecting() && + !(iBackgroundSyncInProgress || iPrimarySession->iSession->IsCancelling()) ) + { + // Disconnecting + iRequestedOperation=TImap4GenericProgress::EDelete; + + iPrimarySession->iFullSync->SynchroniseDeletesL(*status, iServiceId); + iPrimarySession->StartL(this); + } + else + { + // Disconnecting + iRequestedOperation=TImap4GenericProgress::EDisconnect; + + // Cancel both sessions + iPrimarySession->Cancel(); + + // No sync in progress now... + if (iBackgroundSyncInProgress) + iBackgroundSyncInProgress=EFalse; + + // Disconnect both sessions + status=iPrimarySession->SessionStatus(); + iPrimarySession->iSession->DisconnectL(*status); + iPrimarySession->StartL(this); + } + + // kill off the secondary session whatever + iSecondarySession->Cancel(); + status=iSecondarySession->SessionStatus(); + iSecondarySession->iSession->DisconnectL(*status); + iSecondarySession->StartL(this); + break; + + case KIMAP4MTMRenameFolder: + { + TImap4RenameFolder cmd; + TPckgC package(cmd); + package.Set(aParameter); + if (RenameFolderL(aSelection[0], package())) + notComplete = ETrue; + break; + } + + case KIMAP4MTMUndeleteAll: + UndeleteAllL(aSelection, aStatus); + break; + + case KIMAP4MTMCancelOffLineOperations: + iOffLineControl->CancelOffLineOperationsL(aSelection); + User::RequestComplete(iRequest,KErrNone); + break; + + default: + User::RequestComplete(iRequest,KErrNotSupported); + break; + } + + if (notComplete) + { + // Stuff to do! + iCurrentSession->StartL(this); + } + } + +// Are we finished yet? +TBool CImap4ServerMtm::CommandExpected() + { + // ...basically, when we're disconnected we can be deleted + return (!iCanBeDeletedNow || iBatchInProgress); + } + +// Report progress information back to client +const TDesC8& CImap4ServerMtm::Progress() + { + TImap4CompoundProgress progress; + + // get generic status from the current session, this writes a + // complete GenericProgressState out apart from iOperation. note + // that if we do a populate whilst background syncing (for + // example) then this will give details of the populate. If we are + // only syncing then this will give details about the sync + // operation + CActiveWrapper *session = NULL; + + if(iState==EMtmStateSecondarySessionIdle) + { + session = iPrimarySession; + } + else + { + session = iCurrentSession ? iCurrentSession : iPrimarySession; + } + + progress.iGenericProgress = session->iCompound->Progress(); + + // read the sync status whatever state we are in to ensure that + // the structure is not left uninitialised + progress.iSyncProgress=iPrimarySession->iFullSync->Progress(); + + // Have we gone from non-disconnected to disconnected all of a sudden? If we have, this + // means that the lower layers have detected a disconnection. + // ignore this transition if the last thing we did is ask for a disconnection. + if (iLastSessionState!=TImap4GenericProgress::EDisconnected && + progress.iGenericProgress.iState==TImap4GenericProgress::EDisconnected && + iRequestedOperation!=TImap4GenericProgress::EDisconnect) + { + // Kick ourselves so we know we're disconnected now + LOG_COMMANDS((_L8("CImap4ServerMtm::Progress: Session %d suddenly disconnected"), iCurrentSession->iID)); + + if(iCurrentSession->iID != 2) + { + GoneOffline(); + } + } + + // Save this operation to check next time + iLastSessionState=progress.iGenericProgress.iState; + + // Put error into progress buffer + if( progress.iGenericProgress.iErrorCode == KErrNone ) + progress.iGenericProgress.iErrorCode=iProgressErrorCode; + + // If we're copying or moving, *we* (the mtm) keep track of the + // messages done: this is because these operations involve many + // more operations at MTM level (ie, syncs, etc) which also fiddle + // with the msgsdone total, as they would during a full sync. + switch (iRequestedOperation) + { + case TImap4GenericProgress::EMoveWithinService: + case TImap4GenericProgress::ECopyWithinService: + break; + case TImap4GenericProgress::EMoveToLocal: + case TImap4GenericProgress::ECopyToLocal: + case TImap4GenericProgress::EMoveFromLocal: + case TImap4GenericProgress::ECopyFromLocal: + case TImap4GenericProgress::EPopulate: + case TImap4GenericProgress::EDelete: + case TImap4GenericProgress::EOffLineDelete: + case TImap4GenericProgress::EOffLineUndelete: + case TImap4GenericProgress::EOffLineCopyToLocal: + case TImap4GenericProgress::EOffLineMoveToLocal: + case TImap4GenericProgress::EOffLineCopyFromLocal: + case TImap4GenericProgress::EOffLineMoveFromLocal: + case TImap4GenericProgress::EOffLineCopyWithinService: + case TImap4GenericProgress::EOffLineMoveWithinService: + case TImap4GenericProgress::EOffLinePopulate: + progress.iGenericProgress.iMsgsToDo=iProgressMsgsToDo; + progress.iGenericProgress.iMsgsDone=iProgressMsgsDone; + break; + default: + break; + } + +#ifdef PRINTING + // Log the error we're returning + if (iProgressErrorCode!=KErrNone) + { + iPrimarySession->iSession->LogText(_L8("Progress errorcode=%d, laststate=%d currentstate=%d"), + iProgressErrorCode,iLastSessionState,progress.iGenericProgress.iState); + } +#endif + + // put in the operation we've been asked to perform + progress.iGenericProgress.iOperation=iRequestedOperation; + + // Copy the Sync iTotalSize flag into the Generic Total Size field, if the progress val is 0; + progress.iGenericProgress.iTotalSize = iTotalSize; + + // construct the progress buffer + iProgressBuf=TImap4ProgressBuf(progress); + return iProgressBuf; + } + +void CImap4ServerMtm::DoCancel() + { + DBG((iPrimarySession->iSession->LogText(_L8("CImap4ServerMtm::DoCancel() when in state %d"),iState))); + + // What are we doing? + switch(iState) + { + case EMtmStateCopyFromLocal: + case EMtmStateMoveFromLocal: + case EMtmStateCopyToLocal: + case EMtmStateMoveToLocal: + case EMtmStateCopyWithinService: + case EMtmStateMoveWithinService: + case EMtmStatePopulate: + // These all use the compound objects: cancel this + iPrimarySession->Cancel(); + iSecondarySession->Cancel(); + break; + + case EMtmStateMiscCommand: + // What was the actual misc command? + switch(iLastCommand) + { + case KIMAP4MTMConnect: + case KIMAP4MTMConnectAndSynchronise: + // Kill primary only + iPrimarySession->Cancel(); + break; + + default: + // Cancel both sessions... + iPrimarySession->Cancel(); + iSecondarySession->Cancel(); + } + break; + + case EMtmStateSecondaryConnect: + // Cancel secondary session + iSecondarySession->Cancel(); + break; + + case EMtmStateOffLineCopyToLocal: + case EMtmStateOffLineMoveToLocal: + iServerEntry->Cancel(); + iOffLineControl->Cancel(); + break; + case EMtmStateSecondarySessionIdle: + return; + + case EMtmStateDelete: + case EMtmStateDeleteFolder: + default: + // Cancel everything in sight + iPrimarySession->Cancel(); + iSecondarySession->Cancel(); + break; + } + + DBG((iPrimarySession->iSession->LogText(_L8("CImap4ServerMtm::DoCancel() finished")))); + + // Park entries + iPrimarySession->iSession->Park(); + iSecondarySession->iSession->Park(); + + if (iRequest) + User::RequestComplete(iRequest,KErrCancel); + } + +void CImap4ServerMtm::DoRunL() + { +#ifdef PRINTING + CImImap4Session* logsession=(iId==2)?iSecondarySession->iSession:iPrimarySession->iSession; + logsession->LogText(_L8("CImap4ServerMtm::DoRunL(id=%d, status=%d, state=%d)"), + iId,iStatus.Int(),iState); +#endif + + // Status for kicking off next command + TRequestStatus* status=iCurrentSession->SessionStatus(); + + // What were we doing? + switch(iState) + { + case EMtmStateMiscCommand: + // A misc command has completed: do we need to do anything next? + switch(iLastCommand) + { + case KIMAP4MTMConnect: + case KIMAP4MTMConnectAndSynchronise: + { + // Problems? + if (iCode!=KErrNone) + { + // Mark ourselves as offline, and unloadable + GoneOffline(); + + // Tell world about the error, via progress and + // Completion code + iProgressErrorCode=iCode; + + // If doing a connect and sync, the caller is completed after the connect + // phase and the sync continues in the background. If we get an error during that + // sync we need to ensure we don't try to complete the caller again by checking + // that iRequest is not null. + if (iRequest != NULL) + { + User::RequestComplete(iRequest,iCode); + } + return; + } + + // Connect was successful: update connected bit in the service entry + // Ignore errors, it's not the end of the world + TRAP_IGNORE(MarkOnOrOfflineL(ETrue)); + + // Mark that we need to clear new flag before next sync + iClearNewFlagOnNextSync = ETrue; + + // Do we need to kick off sync? + if (iLastCommand==KIMAP4MTMConnectAndSynchronise) + { + DBG((logsession->LogText(_L8("Kicking off background synchronise")))); + + // Start full sync + if (iClearNewFlagOnNextSync) + { + ClearNewFlagL(iServiceId); + iClearNewFlagOnNextSync = EFalse; + } + // SJM: New folders should always be invisible as they are not subscribed + iCurrentSession->iFullSync->SynchroniseL(*status,iServiceId,ETrue, + !iServiceSettings->DeleteEmailsWhenDisconnecting(), ETrue); + iCurrentSession->StartL(this); + + // Carry on as normal, completing request back to client. + // The synchronise will continue in the background. + iBackgroundSyncInProgress=ETrue; + } + + break; + } + + case KIMAP4MTMDisconnect: + // Disconnecting but marked as busy - that means we were + // carrying out the pending deletes + if (iRequestedOperation == TImap4GenericProgress::EDelete) + { + iRequestedOperation = TImap4GenericProgress::EDisconnect; + + // Disconnect primary sessions + status=iPrimarySession->SessionStatus(); + iPrimarySession->iSession->DisconnectL(*status); + iPrimarySession->StartL(this); + return; + } + + // Disconnecting session 1? (primary session). If so, then clear the + // connected bit in the service entry. + if (iId==1 && iServiceId && iServiceSettings) + { + // Do "we've gone offline" stuff + GoneOffline(); + } + break; + } + break; + + case EMtmStateCopyToLocal: + case EMtmStateMoveToLocal: + case EMtmStatePopulate: + // if no message sent then trigger null event to get to next + // state + SetActive(); + if (!iUtils->SendLogMessageL(iCode,iStatus)) + { + TRequestStatus* status = &iStatus; + User::RequestComplete(status, KErrNone); + } + + // send log message + iSavedState = iState; + iState = EMtmStateLogging; + return; + + case EMtmStateLogging: + // done logging, restore old state + iState = iSavedState; + // and deliberately fall through to next case + + case EMtmStateCopyFromLocal: + case EMtmStateMoveFromLocal: + case EMtmStateCopyWithinService: + case EMtmStateMoveWithinService: + // Note any error in the appropriate message + MessageErrorL((*iSelection)[0],iCode); + + // Note any error in the appropriate message + MessageErrorL((*iSelection)[0],iCode); + + // Remove completed item from selection + if (iState==EMtmStateCopyWithinService || iState==EMtmStateMoveWithinService) + { + TInt count=iSelection->Count(); + iSelection->Delete(0,count); + iProgressMsgsDone+=count; + } + else + { + iSelection->Delete(0,1); + iProgressMsgsDone++; + } + + // One more message done + + // Operation done. Do next one in selection + if (iSelection->Count()) + { + // Do the copy with the compound + status=iCurrentSession->SessionStatus(); + switch(iState) + { + case EMtmStateCopyFromLocal: + iCurrentSession->iCompound->CopyFromLocalL(*status,(*iSelection)[0],iDestination); + break; + + case EMtmStateMoveFromLocal: + iCurrentSession->iCompound->MoveFromLocalL(*status,(*iSelection)[0],iDestination); + break; + + case EMtmStateCopyToLocal: + iUtils->SetUpLogMessageL((*iSelection)[0]); + iCurrentSession->iCompound->CopyToLocalL(*status,(*iSelection)[0],iDestination); + break; + + case EMtmStateMoveToLocal: + iUtils->SetUpLogMessageL((*iSelection)[0]); + iCurrentSession->iCompound->MoveToLocalL(*status,(*iSelection)[0],iDestination); + break; + + case EMtmStateCopyWithinService: + // Will copy all messages in one go. + break; + + case EMtmStateMoveWithinService: + // Will copy all messages in one go. + break; + + case EMtmStatePopulate: + iUtils->SetUpLogMessageL((*iSelection)[0]); + iCurrentSession->iCompound->PopulateL(*status,(*iSelection)[0],iPartialMailInfo); + break; + + default: // Keep gcc quiet + break; + } + + iCurrentSession->StartL(this); + return; + } + else if(iCurrentSession == iSecondarySession) + { + iSecondarySession->Cancel(); + status = iSecondarySession->SessionStatus(); + iSecondarySession->iSession->DisconnectL(*status); + iSecondarySession->StartL(this); + iState = EMtmStateSecondarySessionIdle; + iSavedState = EMtmStateSecondarySessionIdle; + } + + break; + + case EMtmStateDelete: + { + // Problems? + if (iCode!=KErrNone) + { + // Store the error on this one + MessageErrorL((*iSelection)[0],iCode); + + // Continue through selection + } + + // Delete completed. + TInt count=iSelection->Count(); + iSelection->Delete(0,count); + iProgressMsgsDone+=count; + + DBG((iCurrentSession->iSession->LogText(_L8("iMsgsDone now %d"),iProgressMsgsDone))); + break; + } + case EMtmStateDeleteFolder: + // Delete completed: do the next one + iSelection->Delete(0,1); + + // One more message done + iProgressMsgsDone++; + + DBG((iCurrentSession->iSession->LogText(_L8("iMsgsDone now %d"),iProgressMsgsDone))); + + // Anything left to do? + if (!iSelection->Count()) + break; + + iCurrentSession->iCompound->DeleteFolderL(*status,(*iSelection)[0]); + iCurrentSession->StartL(this); + return; + + case EMtmStateOffLineDelete: + case EMtmStateOffLineUndelete: + case EMtmStateOffLineCopyToLocal: + case EMtmStateOffLineMoveToLocal: + case EMtmStateOffLineCopyFromLocal: + case EMtmStateOffLineMoveFromLocal: + case EMtmStateOffLineCopyWithinService: + case EMtmStateOffLineMoveWithinService: + case EMtmStateOffLinePopulate: + { + if(iProgressMsgsDone == iProgressMsgsToDo) + break; + + if(iOneSelection) + delete iOneSelection; + iOneSelection=new (ELeave) CMsvEntrySelection; + iOneSelection->AppendL((*iSelection)[iProgressMsgsDone]); + + CImap4OffLineControl::TImap4OpType opType=CImap4OffLineControl::EImap4OpDelete; //have to initialise to something! + switch(iState) + { + case EMtmStateOffLineDelete: + opType=CImap4OffLineControl::EImap4OpDelete; + break; + case EMtmStateOffLineUndelete: + opType=CImap4OffLineControl::EImap4OpUndelete; + break; + case EMtmStateOffLineCopyToLocal: + opType=CImap4OffLineControl::EImap4OpCopyToLocal; + break; + case EMtmStateOffLineMoveToLocal: + opType=CImap4OffLineControl::EImap4OpMoveToLocal; + break; + case EMtmStateOffLineCopyFromLocal: + opType=CImap4OffLineControl::EImap4OpCopyFromLocal; + break; + case EMtmStateOffLineMoveFromLocal: + opType=CImap4OffLineControl::EImap4OpMoveFromLocal; + break; + case EMtmStateOffLineCopyWithinService: + opType=CImap4OffLineControl::EImap4OpCopyWithinService; + break; + case EMtmStateOffLineMoveWithinService: + opType=CImap4OffLineControl::EImap4OpMoveWithinService; + break; + case EMtmStateOffLinePopulate: + opType=CImap4OffLineControl::EImap4OpPopulate; + break; + default: + break; + } + + if(iState == EMtmStateOffLineDelete || iState == EMtmStateOffLineUndelete) + { + iOffLineControl->StoreOfflineCommandL(opType, *iOneSelection, KMsvNullIndexEntryId, iStatus); + } + else if(iState == EMtmStateOffLinePopulate) + { + TPckgBuf package(iGetMailOptions); + iOffLineControl->StoreOfflineCommandL(opType, *iOneSelection, iDestination, package, iStatus); + } + else + { + iOffLineControl->StoreOfflineCommandL(opType, *iOneSelection, iDestination, iStatus); + } + + iProgressMsgsDone++; + SetActive(); + return; + } + case EMtmStateSecondaryConnect: + // We've now connected the secondary session. Issue the + // saved command and continue; + switch(iState=iSavedState) + { + case EMtmStateCopyToLocal: + // We've just connected, so first thing to do is to do the + // folder selection. No need to check the selection, as + // we know it contains at least 1 item from the originally + // issued command. + iUtils->SetUpLogMessageL((*iSelection)[0]); + iCurrentSession->iCompound->CopyToLocalL(*status,(*iSelection)[0],iDestination); + iCurrentSession->StartL(this); + return; + + case EMtmStatePopulate: + iUtils->SetUpLogMessageL((*iSelection)[0]); + iCurrentSession->iCompound->PopulateL(*status,(*iSelection)[0],iPartialMailInfo); + iCurrentSession->StartL(this); + return; + + default: + break; + } + break; + + case EMtmStateSyncCompleted: // FALL THROUGH + default: + break; + } + + // Async request completed (from wrapper): also return it in the progress + DBG((logsession->LogText(_L8("ID %d completed with code %d"),iId,iCode))); + + iProgressErrorCode=iCode; + + // Park entry + iServerEntry->SetEntry(KMsvNullIndexEntryId); + + // Only complete if we have an iRequest: we may not (ie, 2 sessions + // disconnecting at once) + if (iRequest) + User::RequestComplete(iRequest,KErrNone); + } + +void CImap4ServerMtm::DoComplete(TInt aStatus) + { +#ifdef PRINTING + iPrimarySession->iSession->LogText(_L8("CImap4ServerMtm::DoComplete(%d)"),aStatus); +#endif + + // Park entry + iServerEntry->SetEntry(KMsvNullIndexEntryId); + if (iRequest) + User::RequestComplete(iRequest,aStatus); + } + +// Save error code in a message +void CImap4ServerMtm::MessageErrorL(const TMsvId aMessageId, const TInt aError) + { + // Save error code: if we can't access this entry, then it's probably something to do + // with the error we're trying to report: ignore it silently + // SJM: this used to be != KerrNone - surely shome mistake + if (iServerEntry->SetEntry(aMessageId)==KErrNone) + { + TMsvEntry entry=iServerEntry->Entry(); + + // Save unnecessary writes... + if (entry.iError!=aError) + { + entry.iError=aError; + ChangeEntryL(entry); + } + } + } + +// Mixin - a child has completed +void CImap4ServerMtm::RequestCompleted(TInt aId, TInt aCode) + { +#ifdef PRINTING + CImImap4Session* logsession=(aId==2)?iSecondarySession->iSession:iPrimarySession->iSession; + logsession->LogText(_L8("CImap4ServerMtm::RequestCompleted(id=%d, result=%d, state=%d)"),aId,aCode,iState); +#endif + // Is this the background sync completeing? + if (aId==1 && iBackgroundSyncInProgress) + { + // Not in progress any more, boyo + iBackgroundSyncInProgress=EFalse; + + // Is anyone in the forground waiting for this to happen? + if (iState==EMtmStateWaitingForBackgroundToFinish) + { + // Yes: we need to complete ourselves by falling through + // Reset state first + iState=EMtmStateIdle; + + // Any errors from the background sync aren't interesting to us + aCode=KErrNone; + } + // Makes the secondary session request, when primary completes background sync + else if (iState == EMtmStateSecondaryConnect || iState == EMtmStateCopyToLocal || iState == EMtmStatePopulate) + { + iProgressErrorCode=aCode; + return; + } + else if( aCode == KErrNone ) + { + // Complete with the state EMtmStateSyncCompleted. + // When this is caught in the DoRunL, it does nothing. + iState = EMtmStateSyncCompleted; + + // NOTE - only do this if there are no errors - if there is an error + // then DoRunL must handle appropriately in the previous state. + } + } + + // Are we doing a disconnect? If so, we will get completions from both sessions, and we're only + // interested in the primary one, really + if (iLastCommand==KIMAP4MTMDisconnect && aId!=1) + { + // Silently ignore + return; + } + + // Is the code >0? If so, truncate it to 0 as it's IMPS-specific info + if (aCode>KErrNone) + aCode=KErrNone; + + // Save stuff + iCode=aCode; + iId=aId; + + // Set the current session to the one which has just completed so + // that when DoRunL() is called it knows which to deal with + if (aId==1) + iCurrentSession=iPrimarySession; + else + iCurrentSession=iSecondarySession; + + // Complete *ourselves* + TRequestStatus *a=&iStatus; + User::RequestComplete(a,aCode); + + // The activation may need to be done here. + // e.g. the primary session already completed and then the secondary completes. + // e.g. EMtmCopyToLocal is started whilst we are online and a background + // sync is in progress. One completes and then the other. + if(!IsActive()) + { + SetActive(); + } + } + +// This function allows a CActiveWrapper to set the CImap4ServerMtm active +// when it starts a request, as opposed to just when one finishes. This means +// that Cancel()s are handled properly. +void CImap4ServerMtm::Activate() + { + // Object may already be active as there is a primary and secondary session. + if(!IsActive()) + { + iStatus = KRequestPending; + SetActive(); + } + } + +#ifdef PRINTING +void CImap4ServerMtm::NonCompletedFailureOnSession(TInt aId) +#else +void CImap4ServerMtm::NonCompletedFailureOnSession(TInt /*aId*/) +#endif + { +#ifdef PRINTING + CImImap4Session* logsession=(aId==2)?iSecondarySession->iSession:iPrimarySession->iSession; + logsession->LogText(_L8("CImap4ServerMtm::NonCompletedFailureOnSession (id=%d)"), aId); +#endif + + // A failure has occured on the session, but there is no outstanding asynchronous + // request on it. This can happen if for instance we get a disconnect while doing + // a cancel and idle operation. We need to go offline immediately. + GoneOffline(); + } + +CImap4ServerMtm::CImap4ServerMtm(CRegisteredMtmDll& aRegisteredMtmDll, CMsvServerEntry* aEntry): + CBaseServerMtm(aRegisteredMtmDll,aEntry) + { + __DECLARE_NAME(_S("CImap4ServerMtm")); + + // We can be deleted: at the start, we're not connected + iCanBeDeletedNow=ETrue; + + // Add us to the scheduler + CActiveScheduler::Add(this); + } +TBool CImap4ServerMtm::PruneMessages(const CMsvEntrySelection& aSelection) + { + TInt index = aSelection.Count(); + + // See if the parent of the first entry is a message. + // If it is then we need to prune the entries, ie. delete them locally. + if (index == 0) + return EFalse; + + TInt err = iServerEntry->SetEntry(aSelection[0]); + + if (err == KErrNone) + { + err = iServerEntry->SetEntry(iServerEntry->Entry().Parent()); + if (KUidMsvMessageEntry != iServerEntry->Entry().iType) + // The parent of the given entry was not a message, so we don't prune it. + return EFalse; + } + + while ((index--) && (err==KErrNone)) + { + // Go to the required entry + err = iServerEntry->SetEntry(aSelection[index]); + + if (KErrNone == err) + { + // Go to the parent entry to see if it is a message entry + iServerEntry->SetEntry(iServerEntry->Entry().Parent()); + TMsvEmailEntry entry = iServerEntry->Entry(); + + // assert that (KUidMsvMessageEntry == entry.iType) + + // Clear the complete flag because we are about to delete the child parts. + entry.SetComplete(EFalse); + entry.SetBodyTextComplete(EFalse); + err = iServerEntry->ChangeEntry(entry); + + if (KErrNone == err) + { + // Delete the body of the message. + iServerEntry->DeleteEntry(aSelection[index]); + } + } + } + + User::RequestComplete(iRequest, err); + + return ETrue; + }