--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/email/imap4mtm/imapofflinecontrol/src/cimapofflinecontrol.cpp Thu Dec 17 08:44:11 2009 +0200
@@ -0,0 +1,1242 @@
+// 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 <msventry.h>
+#include <miutset.h>
+#include <imapset.h>
+#include "cimapofflinecontrol.h"
+#include "cimaplogger.h"
+
+#ifdef __IMAP_LOGGING
+
+LOCAL_D TPtrC8 OffLineOpTypeString(const CImOffLineOperation& aOp)
+ {
+ switch (aOp.OpType())
+ {
+ case CImOffLineOperation::EOffLineOpNone:
+ return _L8("None");
+
+ case CImOffLineOperation::EOffLineOpCopyToLocal:
+ return _L8("CopyToLocal");
+ case CImOffLineOperation::EOffLineOpCopyFromLocal:
+ return _L8("CopyFromLocal");
+ case CImOffLineOperation::EOffLineOpCopyWithinService:
+ return _L8("CopyWithinService");
+
+ case CImOffLineOperation::EOffLineOpMoveToLocal:
+ return _L8("MoveToLocal");
+ case CImOffLineOperation::EOffLineOpMoveFromLocal:
+ return _L8("MoveFromLocal");
+ case CImOffLineOperation::EOffLineOpMoveWithinService:
+ return _L8("MoveWithinService");
+
+ case CImOffLineOperation::EOffLineOpDelete:
+ return _L8("Delete");
+
+ case CImOffLineOperation::EOffLineOpChange:
+ return _L8("Change");
+ case CImOffLineOperation::EOffLineOpCreate:
+ return _L8("Create");
+
+ case CImOffLineOperation::EOffLineOpMtmSpecific:
+ switch (aOp.MtmFunctionId())
+ {
+ case EFnOffLineOpMoveDelete:
+ return _L8("MoveDelete");
+ case EFnOffLineOpPopulate:
+ return _L8("Populate");
+ default:
+ return _L8("UnknownMtmSpecific");
+ }
+ default:
+ break;
+ }
+ return _L8("Unknown");
+ }
+
+LOCAL_D TPtrC8 Imap4OpTypeString(CImapOfflineControl::TImap4OpType aOpType)
+ {
+ switch (aOpType)
+ {
+ case CImapOfflineControl::EImap4OpCopyToLocal:
+ return _L8("CopyToLocal");
+ case CImapOfflineControl::EImap4OpCopyFromLocal:
+ return _L8("CopyFromLocal");
+ case CImapOfflineControl::EImap4OpCopyWithinService:
+ return _L8("CopyWithinService");
+ case CImapOfflineControl::EImap4OpMoveToLocal:
+ return _L8("MoveToLocal");
+ case CImapOfflineControl::EImap4OpMoveFromLocal:
+ return _L8("MoveFromLocal");
+ case CImapOfflineControl::EImap4OpMoveWithinService:
+ return _L8("MoveWithinService");
+ case CImapOfflineControl::EImap4OpDelete:
+ return _L8("Delete");
+ case CImapOfflineControl::EImap4OpMoveTypeDelete:
+ return _L8("MoveDelete");
+ case CImapOfflineControl::EImap4OpPopulate:
+ return _L8("Populate");
+ default:
+ break;
+ }
+ return _L8("Unknown");
+ }
+#endif
+
+
+EXPORT_C CImapOfflineControl* CImapOfflineControl::NewL(CMsvServerEntry& aEntry)
+ {
+ CImapOfflineControl* self = new (ELeave) CImapOfflineControl(aEntry);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+ }
+
+CImapOfflineControl::CImapOfflineControl(CMsvServerEntry& aEntry)
+ : CMsgActive(EPriorityStandard), iEntry(aEntry)
+ {
+ CActiveScheduler::Add(this);
+ }
+
+void CImapOfflineControl::ConstructL()
+ {
+ iCopyDirect = new (ELeave) CMsvEntrySelection;
+ iMoveDirect = new (ELeave) CMsvEntrySelection;
+ iMoveToLocalDirect = new (ELeave) CMsvEntrySelection;
+ }
+
+CImapOfflineControl::~CImapOfflineControl()
+ {
+ delete iCopyDirect;
+ delete iMoveDirect;
+ delete iMoveToLocalDirect;
+ }
+
+/**
+ public routines
+
+ Store an offline copy/move/delete command: we need to determine which
+ folder the offline command should be stored in dependent on the
+ source of the command.
+
+ CopyToLocal can contain whole messages or parts (but not embedded
+ messages). It can also be a copy to NULL, in which case it means
+ just populate the mirror
+
+ Any item can contain whole messages, but not folders, and can
+ contain shadow ids
+*/
+
+EXPORT_C void CImapOfflineControl::StoreOfflineCommandL(TImap4OpType aOperation,
+ const CMsvEntrySelection& aSelection,
+ TMsvId aDestination,
+ TRequestStatus& aStatus)
+ {
+ TBuf8<128> params = _L8("");
+ StoreOfflineCommandL( aOperation, aSelection, aDestination, params, aStatus );
+ }
+
+EXPORT_C void CImapOfflineControl::StoreOfflineCommandL(TImap4OpType aOperation,
+ const CMsvEntrySelection& aSelection,
+ TMsvId aDestination,
+ const TDesC8& aParams,
+ TRequestStatus& aStatus)
+ {
+#ifdef __IMAP_LOGGING
+ TPtrC8 p = Imap4OpTypeString(aOperation);
+ __LOG_FORMAT((KDefaultLog,"StoreOfflineCommand: op %S %d entries to %x param bytes %d", &p, aSelection.Count(), aDestination, aParams.Length()));
+#endif
+
+ Queue(aStatus);
+
+ iDestination = aDestination;
+
+ // work our which service we are dealing with
+ iServiceId = ServiceOfL( aOperation == EImap4OpCopyFromLocal ||
+ aOperation == EImap4OpMoveFromLocal ?
+ aDestination : aSelection[0] );
+
+ // clear list of Direct operations to do after storing
+ // commands
+ iCopyDirect->Reset();
+ iMoveDirect->Reset();
+ iMoveToLocalDirect->Reset();
+
+ //update the progress info
+ iProgressMsgsToDo=aSelection.Count();
+
+ for (TInt i = 0; i < aSelection.Count(); i++)
+ {
+ CImOffLineOperation* op = new(ELeave)CImOffLineOperation();
+ CleanupStack::PushL(op);
+
+ // See if the message is in fact a shadow
+ TMsvId origId = aSelection[i];
+ SetEntryL(origId);
+
+ TMsvId shadowId = KMsvNullIndexEntryId;
+ TMsvId shadowParentId = KMsvNullIndexEntryId;
+ TMsvEmailEntry entry = iEntry.Entry();
+ if (entry.iRelatedId)
+ {
+ shadowId = origId;
+ shadowParentId = entry.Parent();
+ origId = entry.iRelatedId;
+
+ // it is possible that the original has been deleted by
+ // now (if it were local). If so then skip this operation
+ TInt err = iEntry.SetEntry(origId);
+ if (err != KErrNone)
+ origId = KMsvNullIndexEntryId;
+ else
+ entry = iEntry.Entry();
+ }
+
+ if (origId != KMsvNullIndexEntryId)
+ {
+ // entry contains original (not shadow) message details
+
+ // it is an undo type operation if we are copying or moving a
+ // shadow back to its original folder and the original is
+ // invisible or deleted
+ TBool undeleteOp = shadowId != KMsvNullIndexEntryId &&
+ entry.Parent() == iDestination &&
+ (!entry.Visible() || entry.DisconnectedOperation() == EDisconnectedDeleteOperation);
+
+ // Make operation & save it
+ switch(aOperation)
+ {
+ case EImap4OpCopyToLocal:
+ iRequestedOperation=TImap4GenericProgress::EOffLineCopyToLocal;
+ if (undeleteOp)
+ {
+ UndeleteOperationL(origId, shadowParentId, ETrue);
+ }
+ else if (IdIsLocalL(origId) || entry.Complete())
+ {
+ // either direct local copy or copy from mirror of completely populated message
+ // either way, add new entry to array
+ iCopyDirect->AppendL(origId);
+ }
+ else
+ {
+ op->SetCopyToLocal(origId,iDestination);
+ SaveOperationL(*op);
+ }
+ break;
+
+ case EImap4OpCopyFromLocal:
+ iRequestedOperation=TImap4GenericProgress::EOffLineCopyFromLocal;
+ case EImap4OpCopyWithinService:
+ iRequestedOperation=TImap4GenericProgress::EOffLineCopyWithinService;
+ if (undeleteOp)
+ {
+ UndeleteOperationL(origId, shadowParentId, ETrue);
+ }
+ else if (IdIsLocalL(origId))
+ {
+ op->SetCopyFromLocal(origId,iDestination);
+ SaveOperationL(*op);
+ }
+ else
+ {
+ op->SetCopyWithinService(origId,iDestination);
+ SaveOperationL(*op);
+ }
+ break;
+
+ case EImap4OpMoveToLocal:
+ iRequestedOperation=TImap4GenericProgress::EOffLineMoveToLocal;
+ if (undeleteOp)
+ {
+ UndeleteOperationL(origId, shadowParentId, EFalse);
+ DeleteEntryL(shadowId);
+ }
+ else if (IdIsLocalL(origId))
+ {
+ CImOffLineOperation* origOp = new(ELeave)CImOffLineOperation();
+ CleanupStack::PushL(origOp);
+
+ if (FindOffLineOpByIdL( origId, shadowParentId, *origOp, ETrue /* delete op */) == 0)
+ {
+ User::Leave(KErrNotSupported);
+ }
+
+ if ( OffLineOpIsCopy(*origOp) )
+ {
+ // add new local to local copy op
+ iCopyDirect->AppendL(origId);
+ }
+ else
+ {
+ // direct local move
+ iMoveDirect->AppendL(origId);
+ }
+
+ DeleteEntryL(shadowId);
+ CleanupStack::PopAndDestroy(origOp);
+ }
+ else if (entry.Complete())
+ {
+ // Not local, but completely populated
+ iMoveToLocalDirect->AppendL(origId);
+ }
+ else
+ {
+ op->SetMoveToLocal(origId,iDestination);
+ SaveOperationL(*op);
+ }
+ break;
+
+ case EImap4OpMoveFromLocal:
+ iRequestedOperation=TImap4GenericProgress::EOffLineMoveFromLocal;
+ case EImap4OpMoveWithinService:
+ iRequestedOperation=TImap4GenericProgress::EOffLineMoveWithinService;
+ if (undeleteOp)
+ {
+ UndeleteOperationL(origId, shadowParentId, EFalse);
+
+ // this one can fail depending on what kind of
+ // undelete operation it was
+ CImOffLineOperation* origOp = new(ELeave)CImOffLineOperation();
+ CleanupStack::PushL(origOp);
+
+ FindOffLineOpByIdL( origId, shadowParentId, *origOp, ETrue /* delete op */);
+
+ DeleteEntryL(shadowId);
+ CleanupStack::PopAndDestroy(origOp);
+ }
+ else if (shadowId)
+ {
+ CImOffLineOperation* origOp = new(ELeave)CImOffLineOperation();
+ CleanupStack::PushL(origOp);
+
+ if (FindOffLineOpByIdL( origId, shadowParentId, *origOp, ETrue /* delete op */) == 0)
+ {
+ User::Leave(KErrNotSupported);
+ }
+
+ // Clean disconnected flags
+ SetEntryL(origId);
+ TMsvEmailEntry entry = iEntry.Entry();
+ if (entry.DisconnectedOperation() != EDisconnectedMultipleOperation)
+ {
+ entry.SetDisconnectedOperation(ENoDisconnectedOperations);
+ ChangeEntryL(entry);
+ }
+
+ // if shadow was the result of a copy then change
+ // original copy to point to new destination
+
+ // if shadow was result of a move then change move to
+ // point to new destination
+ if ( OffLineOpIsCopy(*origOp) )
+ {
+ if (IdIsLocalL(origId))
+ op->SetCopyFromLocal(origId,iDestination);
+ else
+ op->SetCopyWithinService(origId,iDestination);
+ }
+ else
+ {
+ if (IdIsLocalL(origId))
+ op->SetMoveFromLocal(origId,iDestination);
+ else
+ op->SetMoveWithinService(origId,iDestination);
+ }
+
+ SaveOperationL(*op);
+
+ DeleteEntryL(shadowId);
+ CleanupStack::PopAndDestroy(origOp);
+ }
+ else
+ {
+ if (IdIsLocalL(origId))
+ op->SetMoveFromLocal(origId,iDestination);
+ else
+ op->SetMoveWithinService(origId,iDestination);
+ SaveOperationL(*op);
+ }
+ break;
+
+ case EImap4OpDelete:
+ iRequestedOperation=TImap4GenericProgress::EOffLineDelete;
+ // we treat shadows and real items the same for deletion
+ // currently
+ op->SetDelete( shadowId ? shadowId : origId );
+ SaveOperationL(*op);
+ break;
+
+ case EImap4OpUndelete:
+ iRequestedOperation=TImap4GenericProgress::EOffLineUndelete;
+ if (shadowId)
+ {
+ UndeleteOperationL(shadowId, shadowParentId, EFalse);
+ }
+ else
+ {
+ // if the entry is not a shadow then we need to
+ // replace the disconnected op flags with the original
+ // flags before it was deleted.
+ CImOffLineOperation* origOp = new(ELeave)CImOffLineOperation();
+ CleanupStack::PushL(origOp);
+
+ // this searches the list before the delete is
+ // removed. However since deletes are stored at
+ // the end of the list then if there are any other
+ // operations it will return the other, and a
+ // count of 2 or greater.
+ TInt count = FindOffLineOpByIdL(origId, KMsvNullIndexEntryId, *origOp, EFalse);
+
+ TImDisconnectedOperationType disconnectedType = ENoDisconnectedOperations;
+ if (count == 2)
+ disconnectedType = OffLineOpToDisconnectedOp( *origOp );
+ else if (count > 2)
+ disconnectedType = EDisconnectedMultipleOperation;
+
+ UndeleteOperationL(origId, KMsvNullIndexEntryId, EFalse, disconnectedType);
+
+ CleanupStack::PopAndDestroy(origOp);
+ }
+
+ ++iProgressMsgsDone; // this is normally done in SaveOperationL() but the undelete op does not use that method.
+ break;
+
+ case EImap4OpPopulate:
+ iRequestedOperation=TImap4GenericProgress::EOffLinePopulate;
+ /* easy one, just populate the original */
+ op->SetMtmSpecificCommandL(origId, iDestination, EFnOffLineOpPopulate, aParams);
+ SaveOperationL(*op);
+ break;
+
+ case EImap4OpMoveTypeDelete:
+ __ASSERT_DEBUG(0, User::Invariant());
+ break;
+ }
+ }
+
+ CleanupStack::PopAndDestroy(op);
+
+ } // end of for loop
+
+ // if there are entries left over then they are ones we added to
+ // be done immediately
+ if (!DoLocalOpL())
+ {
+ // Request has been queued, complete immediately
+ Complete(KErrNone);
+ }
+ }
+
+// Cancel offline operations queued in the folders/service mentioned
+// in the selection
+
+EXPORT_C void CImapOfflineControl::CancelOffLineOperationsL(const CMsvEntrySelection& aSelection)
+ {
+ __LOG_FORMAT((KDefaultLog, "CancelOfflineOperations: %d entries", aSelection.Count()));
+
+ for (TInt i = 0; i < aSelection.Count(); i++)
+ {
+ TMsvId id = aSelection[i];
+
+ SetEntryL(id);
+ TMsvEmailEntry entry = iEntry.Entry();
+ if (entry.iType == KUidMsvFolderEntry)
+ {
+ CImOffLineOperationArray* array = OffLineOpArrayL(id);
+ CleanupStack::PushL(array);
+
+ if (array->CountOperations())
+ {
+ // remove the queued ops
+ while (array->CountOperations())
+ {
+ CImOffLineOperation* thisOp = new(ELeave)CImOffLineOperation();
+ CleanupStack::PushL(thisOp);
+
+ thisOp->CopyL(array->Operation(0));
+
+ UndoOfflineOpL(*thisOp, ETrue);
+
+ array->Delete(0);
+ CleanupStack::PopAndDestroy(thisOp);
+ }
+
+ // write back empty array to store
+ SetOffLineOpArrayL(id, *array);
+ }
+
+ CleanupStack::PopAndDestroy(); // array
+ }
+ }
+ }
+
+
+TImDisconnectedOperationType CImapOfflineControl::OffLineOpToDisconnectedOp(const CImOffLineOperation& aOp)
+ {
+ TImDisconnectedOperationType type;
+ switch (aOp.OpType())
+ {
+ case CImOffLineOperation::EOffLineOpMoveToLocal:
+ type = EDisconnectedMoveToOperation;
+ break;
+ case CImOffLineOperation::EOffLineOpMoveFromLocal:
+ type = EDisconnectedMoveFromOperation;
+ break;
+ case CImOffLineOperation::EOffLineOpMoveWithinService:
+ type = EDisconnectedMoveWithinServiceOperation;
+ break;
+
+ case CImOffLineOperation::EOffLineOpCopyToLocal:
+ type = EDisconnectedCopyToOperation;
+ break;
+ case CImOffLineOperation::EOffLineOpCopyFromLocal:
+ type = EDisconnectedCopyFromOperation;
+ break;
+ case CImOffLineOperation::EOffLineOpCopyWithinService:
+ type = EDisconnectedCopyWithinServiceOperation;
+ break;
+
+ case CImOffLineOperation::EOffLineOpDelete:
+ type = EDisconnectedDeleteOperation;
+ break;
+
+ case CImOffLineOperation::EOffLineOpMtmSpecific:
+ type = EDisconnectedSpecialOperation;
+ break;
+ default:
+ type = EDisconnectedUnknownOperation;
+ break;
+ }
+ return type;
+ }
+
+// This returns TRUE is it is a strict copy operation. Populate can be
+// considered False by the callers of this function.
+
+TBool CImapOfflineControl::OffLineOpIsCopy(const CImOffLineOperation& aOp)
+ {
+ switch (aOp.OpType())
+ {
+ case CImOffLineOperation::EOffLineOpCopyToLocal:
+ case CImOffLineOperation::EOffLineOpCopyFromLocal:
+ case CImOffLineOperation::EOffLineOpCopyWithinService:
+ return ETrue;
+ default:
+ break;
+ }
+ return EFalse;
+ }
+
+TInt CImapOfflineControl::PosVal(const CImOffLineOperation& aOp)
+ {
+ switch (aOp.OpType())
+ {
+ case CImOffLineOperation::EOffLineOpMtmSpecific: // populate
+ switch (aOp.MtmFunctionId())
+ {
+ case EFnOffLineOpMoveDelete:
+ return 5;
+ case EFnOffLineOpPopulate:
+ return 0;
+ }
+ break;
+
+ case CImOffLineOperation::EOffLineOpCopyToLocal:
+ case CImOffLineOperation::EOffLineOpCopyWithinService:
+ return 1;
+ case CImOffLineOperation::EOffLineOpCopyFromLocal:
+ return 2;
+
+ case CImOffLineOperation::EOffLineOpMoveToLocal:
+ case CImOffLineOperation::EOffLineOpMoveWithinService:
+ return 3;
+
+ case CImOffLineOperation::EOffLineOpMoveFromLocal:
+ return 4;
+
+ case CImOffLineOperation::EOffLineOpDelete:
+ return 6;
+ default:
+ break;
+ }
+ return 6;
+ }
+
+
+// Do setentry, leave if there is an error
+void CImapOfflineControl::SetEntryL(TMsvId aId)
+ {
+ User::LeaveIfError(iEntry.SetEntry(aId));
+ }
+
+// Change entry, leave if error
+void CImapOfflineControl::ChangeEntryL(TMsvEntry& aEntry)
+ {
+ User::LeaveIfError(iEntry.ChangeEntry(aEntry));
+ }
+
+// remove an id, leave if error, moves to the parent first
+void CImapOfflineControl::DeleteEntryL(TMsvId aId)
+ {
+ SetEntryL(aId);
+ SetEntryL(iEntry.Entry().Parent());
+ User::LeaveIfError(iEntry.DeleteEntry(aId));
+ }
+
+// Find the folder that encloses this message or message part. Note
+// that this must be a real folder, not a folder component of a
+// message, and that it may not be in our service.
+TMsvId CImapOfflineControl::FolderOfL(TMsvId aId)
+ {
+ SetEntryL( MessageOfL(aId) );
+ return iEntry.Entry().Parent();
+ }
+
+// If the message is not in our service then return the destination
+// folder. Otherwise return its own parent folder.
+TMsvId CImapOfflineControl::FindOffLineSaveFolderL(TMsvId aId, TMsvId aDestId)
+ {
+ TMsvId folder = FolderOfL(aId);
+ if (ServiceOfL(folder) == iServiceId)
+ return folder;
+ return aDestId;
+ }
+
+// Find the top level message that holds this message part. Can be
+// itself if it is a real message itself. This is located by finding
+// the message that is highest up the tree.
+TMsvId CImapOfflineControl::MessageOfL(TMsvId aId)
+ {
+ TMsvId current=aId;
+ TMsvId msg=aId;
+ while(current!=KMsvRootIndexEntryIdValue)
+ {
+ // Visit this entry
+ SetEntryL(current);
+
+ TMsvEmailEntry entry = iEntry.Entry();
+
+ // if service then searched far enough
+ if (entry.iType==KUidMsvServiceEntry)
+ break;
+
+ // if message type then store it
+ if (entry.iType==KUidMsvMessageEntry)
+ msg = entry.Id();
+
+ // Go upwards
+ current=entry.Parent();
+ }
+
+ return msg;
+ }
+
+// return the id of the service containing this id
+TMsvId CImapOfflineControl::ServiceOfL(TMsvId aId)
+ {
+ TMsvId current=aId;
+ while(current!=KMsvRootIndexEntryIdValue)
+ {
+ // Visit this entry
+ SetEntryL(current);
+
+ TMsvEmailEntry entry = iEntry.Entry();
+
+ // if service then searched far enough
+ if (entry.iType==KUidMsvServiceEntry)
+ break;
+
+ // Go upwards
+ current=entry.Parent();
+ }
+
+ return current;
+ }
+
+// is this id in the local service?
+EXPORT_C TMsvId CImapOfflineControl::IdIsLocalL(TMsvId aId)
+ {
+ return ServiceOfL(aId) == KMsvLocalServiceIndexEntryIdValue;
+ }
+
+
+// simple functions to get and set the offline array on an id. More
+// efficient open and modify versions are possible and used elsewhere
+
+EXPORT_C CImOffLineOperationArray* CImapOfflineControl::OffLineOpArrayL(TMsvId aId)
+ {
+ SetEntryL(aId);
+
+ CImOffLineOperationArray* array = CImOffLineOperationArray::NewL();
+ CleanupStack::PushL(array);
+
+ // if no store then return an empty array (easier for higher
+ // layers than a NULL pointer).
+ if (iEntry.HasStoreL())
+ {
+ CMsvStore* store = iEntry.ReadStoreL();
+ CleanupStack::PushL(store);
+
+ CImOffLineArrayStore arraystore(*array);
+ arraystore.RestoreL(*store);
+
+ CleanupStack::PopAndDestroy(); // store
+ }
+
+ // DBG((_L8("OffLineOpArrayL: folder 0x%x count %d"), aId, array->CountOperations()));
+
+ CleanupStack::Pop(); // array
+ return array;
+ }
+
+EXPORT_C void CImapOfflineControl::SetOffLineOpArrayL(TMsvId aId, CImOffLineOperationArray& aArray)
+ {
+ // DBG((_L8("SetOffLineOpArrayL: folder 0x%x count %d"), aId, aArray.CountOperations()));
+
+ SetEntryL( aId );
+
+ CMsvStore* store=iEntry.EditStoreL();
+ CleanupStack::PushL(store);
+
+ CImOffLineArrayStore arraystore(aArray);
+ arraystore.StoreL(*store);
+
+ store->CommitL();
+
+ CleanupStack::PopAndDestroy(); // store
+ }
+
+
+// Save offline operation
+void CImapOfflineControl::SaveOperationL(const CImOffLineOperation& aOperation)
+ {
+ // DBG((_L8("SaveOperation:")));
+
+ // We need an array, to store the current offline operations of this folder
+ CImOffLineOperationArray *array=CImOffLineOperationArray::NewL();
+ CleanupStack::PushL(array);
+ CImOffLineArrayStore arraystore(*array);
+
+ // find where to store the op
+ TMsvId storehere = FindOffLineSaveFolderL(aOperation.MessageId(), aOperation.TargetMessageId());
+ SetEntryL(storehere);
+
+ // open the store
+ CMsvStore *store=iEntry.EditStoreL();
+ CleanupStack::PushL(store);
+
+ arraystore.RestoreL(*store);
+
+ // we add this operation after others of the same type
+ TInt insertBefore = PosVal(aOperation) + 1;
+ TBool done = EFalse;
+
+ for(TInt a=0; a<array->CountOperations(); a++)
+ {
+ if (insertBefore <= PosVal(array->Operation(a)))
+ {
+ array->InsertOperationL(MUTABLE_CAST(CImOffLineOperation&, aOperation), a);
+ done = ETrue;
+ break;
+ }
+ }
+
+ if (!done)
+ array->AppendOperationL(aOperation);
+
+ // write back
+ arraystore.StoreL(*store);
+ store->CommitL();
+
+ // Dispose of store & array
+ CleanupStack::PopAndDestroy(2);
+
+ // make the shadow
+ MakeShadowL(aOperation);
+
+ //update the progrees info
+ ++iProgressMsgsDone;
+ }
+
+// returns ETrue if a matching Op was found
+
+TInt CImapOfflineControl::FindOffLineOpByIdL(TMsvId aId, TMsvId aDestFolder,
+ CImOffLineOperation& aOp, TBool aDelete)
+ {
+ CImOffLineOperationArray *array=CImOffLineOperationArray::NewL();
+ CleanupStack::PushL(array);
+ CImOffLineArrayStore arraystore(*array);
+
+ SetEntryL(FindOffLineSaveFolderL(aId, aDestFolder));
+ CMsvStore *store=aDelete ? iEntry.EditStoreL() : iEntry.ReadStoreL();
+ CleanupStack::PushL(store);
+
+ arraystore.RestoreL(*store);
+
+ // look in the array for an operation on this Id and optionally to
+ // the matching folder
+ TInt found = 0;
+ TInt foundAt = -1;
+ for(TInt a=0; a<array->CountOperations(); a++)
+ {
+ if (array->Operation(a).MessageId() == aId &&
+ (aDestFolder == KMsvNullIndexEntryId ||
+ aDestFolder == array->Operation(a).TargetMessageId()) )
+ {
+ // only write out the first operation found
+ if (found == 0)
+ {
+ foundAt = a;
+ aOp.CopyL( array->Operation(a) );
+ }
+ found++;
+ }
+ }
+
+ // optionally now delete the operation from the array
+ if (aDelete && foundAt != -1)
+ {
+ array->Delete(foundAt);
+
+ arraystore.StoreL(*store);
+ store->CommitL();
+ }
+
+ CleanupStack::PopAndDestroy(2); // store, array
+
+ return found;
+ }
+
+// this means remove the cause of the delete, ie remove delete or
+// change move to copy, unless ConvertToCopy is False in which case
+// delete any move operation rather than convert it.
+
+// there can only be one relevant operation in the array as the UI or
+// MTM should have prevented further operations
+
+// Deleting any shadow entry should be done outside this function
+
+void CImapOfflineControl::UndeleteOperationL(TMsvId aId, TMsvId aDestId, TBool aConvertMoveToCopy,
+ TImDisconnectedOperationType aDisconnected)
+ {
+ // DBG((_L8("UndeleteOperation: Id %x CvtMove %d type %d"),
+ // aId, aConvertMoveToCopy, aDisconnected));
+
+ // We need an array, to store the current offline operations of this folder
+ CImOffLineOperationArray *array=CImOffLineOperationArray::NewL();
+ CleanupStack::PushL(array);
+ CImOffLineArrayStore arraystore(*array);
+
+ SetEntryL(FindOffLineSaveFolderL(aId, aDestId));
+ // DBG((_L8("UndeleteOperation: opending savefolder store %x"), iEntry.Entry().Id() ));
+ CMsvStore *store=iEntry.EditStoreL();
+ CleanupStack::PushL(store);
+
+ arraystore.RestoreL(*store);
+
+ // look in the array for a delete or move operation on this Id
+ CImOffLineOperation* thisOp = new(ELeave)CImOffLineOperation();
+ CleanupStack::PushL(thisOp);
+
+ for(TInt a=0; a<array->CountOperations(); a++)
+ {
+ thisOp->CopyL(array->Operation(a));
+
+ if (thisOp->MessageId() == aId)
+ {
+ TBool finish = ETrue;
+ TBool isDelete = EFalse;
+
+ switch (thisOp->OpType())
+ {
+ // if move then convert it to an equivalent copy
+ case CImOffLineOperation::EOffLineOpMoveToLocal:
+ thisOp->SetCopyToLocal(aId, thisOp->TargetMessageId());
+ break;
+
+ case CImOffLineOperation::EOffLineOpMoveFromLocal:
+ thisOp->SetCopyFromLocal(aId, thisOp->TargetMessageId());
+ break;
+
+ case CImOffLineOperation::EOffLineOpMoveWithinService:
+ thisOp->SetCopyWithinService(aId, thisOp->TargetMessageId());
+ break;
+
+ // if delete then get rid of the pending operation
+ case CImOffLineOperation::EOffLineOpDelete:
+ isDelete = ETrue;
+ break;
+
+ default:
+ finish = EFalse;
+ break;
+ }
+
+ if (finish)
+ {
+ // remove the existing operation
+ array->Delete(a);
+
+ // potentially add a new one
+ if (!isDelete)
+ {
+ // it's become a copy so insert at head of list
+ if (aConvertMoveToCopy)
+ array->InsertOperationL(*thisOp, 0);
+ }
+
+ break;
+ }
+ }
+
+ } // end of for loop
+
+ // DBG((_L8("UndeleteOperation: write store")));
+
+ // write back offline op array
+ arraystore.StoreL(*store);
+ store->CommitL();
+
+ CleanupStack::PopAndDestroy(thisOp);
+ thisOp = NULL;
+ CleanupStack::PopAndDestroy(store);
+ store = NULL;
+ CleanupStack::PopAndDestroy(array);
+ array = NULL;
+
+ // DBG((_L8("UndeleteOperation: ensure visible")));
+
+ // then make the item visible and update its pending operation
+ // type
+ SetEntryL(aId);
+ TMsvEmailEntry entry = iEntry.Entry();
+
+ entry.SetDisconnectedOperation(aDisconnected);
+ entry.SetVisible(ETrue);
+
+ ChangeEntryL(entry);
+
+ // DBG((_L8("UndeleteOperation: done")));
+ }
+
+// Make shadow for offline operation - this shadow indicates what
+// *will* happen at the next sync
+
+// Note if we want to copy the entire structure of the message then
+// there is a ready made function Imap4Session->CopyMessageL() to do
+// this
+void CImapOfflineControl::MakeCopyMoveShadowL(const CImOffLineOperation& aOp)
+ {
+ // get copy of the original message
+ SetEntryL(aOp.MessageId());
+ TMsvEmailEntry origMsg = iEntry.Entry();
+
+ // check this is a real message, we don't make shadows of parts
+ if (origMsg.iType != KUidMsvMessageEntry)
+ return;
+
+ // if this is not a copy to mirror only operation then make shadow
+ if ( aOp.OpType() != CImOffLineOperation::EOffLineOpMtmSpecific )
+ {
+ // copy out the non embedded data
+ HBufC* details = origMsg.iDetails.AllocL();
+ CleanupStack::PushL(details);
+ HBufC* description = origMsg.iDescription.AllocL();
+ CleanupStack::PushL(description);
+
+ // set up the new message, clearing any disconnected op flags
+ // it may have
+ TMsvEmailEntry newMsg = origMsg;
+ newMsg.iRelatedId = aOp.MessageId();
+ newMsg.SetComplete(EFalse);
+ newMsg.SetDisconnectedOperation(ENoDisconnectedOperations);
+ // ensure that this one is visible (may be copied from one
+ // that wasn't)
+ newMsg.SetVisible(ETrue);
+
+ // create shadow entry
+ SetEntryL(aOp.TargetMessageId());
+
+ newMsg.iDetails.Set(details->Des());
+ newMsg.iDescription.Set(description->Des());
+ User::LeaveIfError(iEntry.CreateEntry(newMsg));
+
+ CleanupStack::PopAndDestroy(2); // description, details
+ }
+
+ // set flags on the original message
+ SetEntryL(origMsg.Id());
+
+ if (origMsg.DisconnectedOperation() == ENoDisconnectedOperations)
+ origMsg.SetDisconnectedOperation( OffLineOpToDisconnectedOp(aOp) );
+ else
+ origMsg.SetDisconnectedOperation( EDisconnectedMultipleOperation );
+
+ // make original invisible if this was a move operation
+ if (!OffLineOpIsCopy(aOp))
+ origMsg.SetVisible(EFalse);
+
+ // write back changes
+ ChangeEntryL(origMsg);
+ }
+
+void CImapOfflineControl::MakeShadowL(const CImOffLineOperation& aOp)
+ {
+ // DBG((_L8("MakeShadow: of %x in folder %x"), aOp.MessageId(), aOp.TargetMessageId()));
+
+ switch (aOp.OpType())
+ {
+ case CImOffLineOperation::EOffLineOpMtmSpecific: // populate
+ case CImOffLineOperation::EOffLineOpMoveToLocal:
+ case CImOffLineOperation::EOffLineOpMoveFromLocal:
+ case CImOffLineOperation::EOffLineOpMoveWithinService:
+ case CImOffLineOperation::EOffLineOpCopyToLocal:
+ case CImOffLineOperation::EOffLineOpCopyFromLocal:
+ case CImOffLineOperation::EOffLineOpCopyWithinService:
+ MakeCopyMoveShadowL(aOp);
+ break;
+
+ case CImOffLineOperation::EOffLineOpDelete:
+ // Set the pending operation to Delete, we don't care if there
+ // were other operations already pending
+ {
+ SetEntryL(aOp.MessageId());
+ TMsvEmailEntry msg = iEntry.Entry();
+ msg.SetDisconnectedOperation(EDisconnectedDeleteOperation);
+ ChangeEntryL(msg);
+ }
+ break;
+
+ case CImOffLineOperation::EOffLineOpNone:
+ case CImOffLineOperation::EOffLineOpChange:
+ case CImOffLineOperation::EOffLineOpCreate:
+ __ASSERT_DEBUG(0, User::Invariant());
+ break;
+ }
+
+ }
+
+// look in the folder for an item whose iRelatedId matches
+TBool CImapOfflineControl::FindShadowIdsL(const CImOffLineOperation& aOp, CMsvEntrySelection& aSelection)
+ {
+ CMsvEntrySelection* selection=new (ELeave) CMsvEntrySelection;
+ CleanupStack::PushL(selection);
+
+ SetEntryL(aOp.TargetMessageId());
+ User::LeaveIfError(iEntry.GetChildren(*selection));
+
+ TBool foundOne = EFalse;
+ for(TInt child=0;child<selection->Count();child++)
+ {
+ TMsvId childId = (*selection)[child];
+ SetEntryL(childId);
+ TMsvEntry message = iEntry.Entry();
+ if (message.iRelatedId == aOp.MessageId())
+ {
+ aSelection.InsertL(0, childId);
+ foundOne = ETrue;
+ }
+ }
+
+ CleanupStack::PopAndDestroy();
+
+ return foundOne;
+ }
+
+EXPORT_C TMsvId CImapOfflineControl::FindShadowIdL(const CImOffLineOperation& aOp)
+ {
+ CMsvEntrySelection* selection=new (ELeave) CMsvEntrySelection;
+ CleanupStack::PushL(selection);
+
+ TMsvId id = KMsvNullIndexEntryId;
+
+ // the target folder might have been deleted - in which case just
+ // return that the id was not found
+ if (iEntry.SetEntry(aOp.TargetMessageId()) == KErrNone)
+ {
+ User::LeaveIfError(iEntry.GetChildren(*selection));
+ for(TInt child=0;child<selection->Count();child++)
+ {
+ TMsvId childId = (*selection)[child];
+ SetEntryL(childId);
+ TMsvEntry message = iEntry.Entry();
+ if (message.iRelatedId == aOp.MessageId())
+ {
+ id = childId;
+ break;
+ }
+ }
+ }
+
+ CleanupStack::PopAndDestroy();
+
+ return id;
+ }
+
+void CImapOfflineControl::UndoOfflineOpL(const CImOffLineOperation& aOp, TBool aClearMultiples)
+ {
+#ifdef __IMAP_LOGGING
+ TPtrC8 p = ::OffLineOpTypeString(aOp);
+ __LOG_FORMAT((KDefaultLog, "UndoOfflineOp: %S Id %x TargetFolder %x",&p, aOp.MessageId(), aOp.TargetMessageId()));
+#endif
+
+ // get the first id related to the source of this message, unless
+ // it has no destination (ie it is a delete op)
+ if (aOp.TargetMessageId())
+ {
+ TMsvId id = FindShadowIdL(aOp);
+ if (id != KMsvNullIndexEntryId)
+ {
+ SetEntryL(aOp.TargetMessageId());
+ iEntry.DeleteEntry(id);
+ }
+ }
+
+ // remove the disconnected op flags from the source entry and make
+ // it visible (does't harm if it was visible anyway), if it has
+ // multiple ops then we leave it as we don't know what to do.
+
+ // entry might not exist if it was a shadow
+ if (iEntry.SetEntry(aOp.MessageId()) == KErrNone)
+ {
+ TMsvEmailEntry entry = iEntry.Entry();
+ if (!entry.Visible() || aClearMultiples ||
+ entry.DisconnectedOperation() != EDisconnectedMultipleOperation)
+ {
+ entry.SetDisconnectedOperation(ENoDisconnectedOperations);
+ entry.SetVisible(ETrue);
+ ChangeEntryL(entry);
+ }
+ }
+ }
+
+void CImapOfflineControl::PrepareLocalOpL(TMsvId aId)
+ {
+ SetEntryL(aId);
+
+ // clear the disconnected op flag
+ TMsvEmailEntry entry = iEntry.Entry();
+ entry.SetDisconnectedOperation(ENoDisconnectedOperations);
+ ChangeEntryL(entry);
+
+ SetEntryL(iEntry.Entry().Parent());
+ }
+
+TBool CImapOfflineControl::DoLocalOpL()
+ {
+
+
+
+ if (iCopyDirect->Count())
+ {
+ TMsvId id = (*iCopyDirect)[0];
+
+ __LOG_FORMAT((KDefaultLog, "CImapOfflineControl::DoLocalOpL Copy id %x to do %d",id, iCopyDirect->Count()));
+
+ PrepareLocalOpL(id);
+
+ iEntry.CopyEntryL(id, iDestination, iStatus);
+ SetActive();
+ return ETrue;
+ }
+
+ if (iMoveDirect->Count())
+ {
+ TMsvId id = (*iMoveDirect)[0];
+
+ __LOG_FORMAT((KDefaultLog, "CImapOfflineControl::DoLocalOpL Move id %x to do %d",id, iMoveDirect->Count()));
+
+ PrepareLocalOpL(id);
+
+ iEntry.MoveEntryL(id, iDestination, iStatus);
+ SetActive();
+ return ETrue;
+ }
+
+ if (iMoveToLocalDirect->Count())
+ {
+ TMsvId id = (*iMoveToLocalDirect)[0];
+
+ __LOG_FORMAT((KDefaultLog, "CImapOfflineControl::DoLocalOpL MoveToLocal id %x to do %d",id, iMoveToLocalDirect->Count()));
+
+ PrepareLocalOpL(id);
+
+ iEntry.CopyEntryL(id, iDestination, iStatus); // I do mean Copy
+ SetActive();
+ return ETrue;
+ }
+
+ return EFalse;
+ }
+
+
+void CImapOfflineControl::DoCancel()
+ {
+ CMsgActive::DoCancel();
+ }
+
+void CImapOfflineControl::DoComplete(TInt& /*aStatus*/)
+ {
+
+ }
+
+void CImapOfflineControl::DoRunL()
+ {
+
+ // DBG((_L8("::DoRunL")));
+ __LOG_FORMAT((KDefaultLog, "CImapOfflineControl::DoRunL"));
+
+ // successfully copied/moved the item
+
+ // Remove completed item from selection
+ if (iCopyDirect->Count())
+ iCopyDirect->Delete(0,1);
+ else if (iMoveDirect->Count())
+ iMoveDirect->Delete(0,1);
+ else
+ {
+ // We managed to do the copy portion of a move to local
+ // Now we need to queue up a delete of the original which
+ // is still in the remote mailbox.
+ CImOffLineOperation* op = new(ELeave)CImOffLineOperation();
+ CleanupStack::PushL(op);
+
+ op->SetDelete((*iMoveToLocalDirect)[0]);
+ iMoveToLocalDirect->Delete(0,1);
+ SaveOperationL(*op);
+
+ CleanupStack::PopAndDestroy(op);
+ }
+
+ // Operation done. Do next one in selection
+ DoLocalOpL();
+
+ //update the progrees info
+ ++iProgressMsgsDone;
+ }
+
+
+EXPORT_C TImap4CompoundProgress CImapOfflineControl::Progress()
+ {
+ iProgress.iGenericProgress.iType=EImap4GenericProgressType;
+ iProgress.iGenericProgress.iOperation=iRequestedOperation;
+ iProgress.iGenericProgress.iMsgsToDo=iProgressMsgsToDo;
+ iProgress.iGenericProgress.iMsgsDone=iProgressMsgsDone;
+
+ return iProgress;
+ }