in mailnews/imap/src/nsImapMailFolder.cpp [6571:6891]
nsresult nsImapMailFolder::CopyMessagesOffline(
nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) {
nsresult rv;
nsresult stopit = NS_OK;
nsCOMPtr<nsIMsgDatabase> sourceMailDB;
nsCOMPtr<nsIDBFolderInfo> srcDbFolderInfo;
srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(srcDbFolderInfo),
getter_AddRefs(sourceMailDB));
bool deleteToTrash = false;
bool deleteImmediately = false;
uint32_t srcCount = messages.Length();
nsCOMPtr<nsIImapIncomingServer> imapServer;
rv = GetImapIncomingServer(getter_AddRefs(imapServer));
nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsCopied;
nsTArray<RefPtr<nsIMsgDBHdr>> destMsgHdrs;
if (NS_SUCCEEDED(rv) && imapServer) {
nsMsgImapDeleteModel deleteModel;
imapServer->GetDeleteModel(&deleteModel);
deleteToTrash = (deleteModel == nsMsgImapDeleteModels::MoveToTrash);
deleteImmediately = (deleteModel == nsMsgImapDeleteModels::DeleteNoTrash);
}
// This array is used only when we are actually removing the messages from the
// source database.
nsTArray<nsMsgKey> keysToDelete(
(isMove && (deleteToTrash || deleteImmediately)) ? srcCount : 0);
if (sourceMailDB) {
// save the future ops in the source DB, if this is not a imap->local
// copy/move
nsCOMPtr<nsITransactionManager> txnMgr;
if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
if (txnMgr) txnMgr->BeginBatch(nullptr);
nsCOMPtr<nsIMsgDatabase> destDB;
GetMsgDatabase(getter_AddRefs(destDB));
if (destDB) {
// N.B. We must not return out of the for loop - we need the matching
// end notifications to be sent.
// We don't need to acquire the semaphore since this is synchronous
// on the UI thread but we should check if the offline store is locked.
bool isLocked;
GetLocked(&isLocked);
nsTArray<nsMsgKey> addedKeys;
nsTArray<nsMsgKey> srcKeyArray;
nsCOMArray<nsIMsgDBHdr> addedHdrs;
nsCOMArray<nsIMsgDBHdr> srcMsgs;
nsOfflineImapOperationType moveCopyOpType;
nsOfflineImapOperationType deleteOpType =
nsIMsgOfflineImapOperation::kDeletedMsg;
if (!deleteToTrash)
deleteOpType = nsIMsgOfflineImapOperation::kMsgMarkedDeleted;
nsCString messageIds;
rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
// put fake message in destination db, delete source if move
EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false);
nsCString originalSrcFolderURI;
srcFolder->GetURI(originalSrcFolderURI);
nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
do_QueryInterface(sourceMailDB, &rv);
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t sourceKeyIndex = 0;
NS_SUCCEEDED(stopit) && (sourceKeyIndex < srcCount);
sourceKeyIndex++) {
bool messageReturningHome = false;
RefPtr<nsIMsgDBHdr> message = messages[sourceKeyIndex];
nsMsgKey originalKey;
if (message) {
rv = message->GetMessageKey(&originalKey);
} else {
NS_ERROR("bad msg in src array");
continue;
}
// Set up an offline op for this message in the source DB.
nsCOMPtr<nsIMsgOfflineImapOperation> sourceOp;
rv = opsDb->GetOfflineOpForKey(originalKey, true,
getter_AddRefs(sourceOp));
if (NS_SUCCEEDED(rv) && sourceOp) {
srcFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
nsCOMPtr<nsIMsgDatabase> originalDB;
nsOfflineImapOperationType opType;
sourceOp->GetOperation(&opType);
// if we already have an offline op for this key, then we need to see
// if it was moved into the source folder while offline
if (opType ==
nsIMsgOfflineImapOperation::kMoveResult) // offline move
{
// gracious me, we are moving something we already moved while
// offline! find the original operation and clear it!
nsCOMPtr<nsIMsgOfflineImapOperation> originalOp;
GetClearedOriginalOp(sourceOp, getter_AddRefs(originalOp),
getter_AddRefs(originalDB));
nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDbOriginal =
do_QueryInterface(originalDB, &rv);
if (NS_SUCCEEDED(rv) && originalOp) {
nsCString srcFolderURI;
srcFolder->GetURI(srcFolderURI);
sourceOp->GetSourceFolderURI(originalSrcFolderURI);
sourceOp->GetMessageKey(&originalKey);
if (isMove) opsDb->RemoveOfflineOp(sourceOp);
sourceOp = originalOp;
if (originalSrcFolderURI.Equals(srcFolderURI)) {
messageReturningHome = true;
opsDbOriginal->RemoveOfflineOp(originalOp);
}
}
}
if (!messageReturningHome) {
nsCString folderURI;
GetURI(folderURI);
if (isMove) {
uint32_t msgSize;
uint32_t msgFlags;
imapMessageFlagsType newImapFlags = 0;
message->GetMessageSize(&msgSize);
message->GetFlags(&msgFlags);
sourceOp->SetDestinationFolderURI(folderURI); // offline move
sourceOp->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved);
sourceOp->SetMsgSize(msgSize);
newImapFlags = msgFlags & 0x7;
if (msgFlags & nsMsgMessageFlags::Forwarded)
newImapFlags |= kImapMsgForwardedFlag;
sourceOp->SetNewFlags(newImapFlags);
} else {
sourceOp->AddMessageCopyOperation(folderURI); // offline copy
}
sourceOp->GetOperation(&moveCopyOpType);
srcMsgs.AppendObject(message);
}
} else {
stopit = NS_ERROR_FAILURE;
}
// End of block to set up offline op.
nsCOMPtr<nsIMsgDBHdr> mailHdr;
rv =
sourceMailDB->GetMsgHdrForKey(originalKey, getter_AddRefs(mailHdr));
if (NS_SUCCEEDED(rv) && mailHdr) {
// Copy the DB hdr into the destination folder.
bool successfulCopy = false;
nsMsgKey srcDBhighWaterMark;
srcDbFolderInfo->GetHighWater(&srcDBhighWaterMark);
// Generate a fake key which is very unlikely to clash with any
// UIDs that appear once this operation has been played out on the
// IMAP server (Because IMAP uses server-side UIDs as msgKeys -
// Bug 1806770).
nsMsgKey fakeKey;
destDB->GetNextFakeOfflineMsgKey(&fakeKey);
nsCOMPtr<nsIMsgDBHdr> newMailHdr;
rv = destDB->CopyHdrFromExistingHdr(fakeKey, mailHdr, true,
getter_AddRefs(newMailHdr));
if (!newMailHdr || NS_FAILED(rv)) {
NS_ASSERTION(false, "failed to copy hdr");
stopit = rv;
}
if (NS_SUCCEEDED(stopit)) {
bool hasMsgOffline = false;
destMsgHdrs.AppendElement(newMailHdr);
srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
newMailHdr->SetUint32Property("pseudoHdr", 1);
if (hasMsgOffline && !isLocked) {
uint64_t bytesCopied;
stopit = CopyStoreMessage(mailHdr, newMailHdr, bytesCopied);
if (NS_SUCCEEDED(stopit)) {
uint32_t unused;
newMailHdr->OrFlags(nsMsgMessageFlags::Offline, &unused);
newMailHdr->SetOfflineMessageSize(bytesCopied);
}
} else {
destDB->MarkOffline(fakeKey, false, nullptr);
}
// Create a corresponding offline op in the destination DB.
nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
do_QueryInterface(destDB, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgOfflineImapOperation> destOp;
opsDb->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(destOp));
if (destOp) {
// check if this is a move back to the original mailbox, in which
// case we just delete the offline operation.
if (messageReturningHome) {
opsDb->RemoveOfflineOp(destOp);
} else {
SetFlag(nsMsgFolderFlags::OfflineEvents);
// SetSourceFolderURI() sets the op to kMoveResult.
destOp->SetSourceFolderURI(originalSrcFolderURI);
// Attach the key of the source message (in the srcDB).
destOp->SetSrcMessageKey(originalKey);
addedKeys.AppendElement(fakeKey);
addedHdrs.AppendObject(newMailHdr);
}
} else {
stopit = NS_ERROR_FAILURE;
}
}
successfulCopy = NS_SUCCEEDED(stopit);
nsMsgKey msgKey;
mailHdr->GetMessageKey(&msgKey);
if (isMove && successfulCopy) {
if (deleteToTrash || deleteImmediately)
keysToDelete.AppendElement(msgKey);
else
sourceMailDB->MarkImapDeleted(msgKey, true,
nullptr); // offline delete
}
if (successfulCopy) {
// This is for both moves and copies
msgHdrsCopied.AppendElement(mailHdr);
}
}
} // End message loop.
EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
RefPtr<nsImapOfflineTxn> addHdrMsgTxn = new nsImapOfflineTxn(
this, &addedKeys, nullptr, this, isMove,
nsIMsgOfflineImapOperation::kAddedHeader, addedHdrs);
if (addHdrMsgTxn && txnMgr) txnMgr->DoTransaction(addHdrMsgTxn);
RefPtr<nsImapOfflineTxn> undoMsgTxn =
new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
isMove, moveCopyOpType, srcMsgs);
if (undoMsgTxn) {
if (isMove) {
undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
nsCOMPtr<nsIMsgImapMailFolder> srcIsImap(
do_QueryInterface(srcFolder));
// remember this undo transaction so we can hook up the result
// msg ids in the undo transaction.
if (srcIsImap) {
nsImapMailFolder* srcImapFolder =
static_cast<nsImapMailFolder*>(srcFolder);
srcImapFolder->m_pendingOfflineMoves.AppendElement(undoMsgTxn);
}
} else {
undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
}
// we're adding this undo action before the delete is successful. This
// is evil, but 4.5 did it as well.
if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
}
undoMsgTxn =
new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
isMove, deleteOpType, srcMsgs);
if (undoMsgTxn) {
if (isMove) {
if (mFlags & nsMsgFolderFlags::Trash) {
undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
} else {
undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
}
} else {
undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
}
if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
}
if (isMove) sourceMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
destDB->Commit(nsMsgDBCommitType::kLargeCommit);
SummaryChanged();
srcFolder->SummaryChanged();
}
if (txnMgr) txnMgr->EndBatch(false);
}
if (!msgHdrsCopied.IsEmpty()) {
nsCOMPtr<nsIMsgFolderNotificationService> notifier(
do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
if (notifier) {
notifier->NotifyMsgsMoveCopyCompleted(isMove, msgHdrsCopied, this,
destMsgHdrs);
}
}
// NOTE (Bug 1787963):
// If we're performing a move, by rights we should be deleting the source
// message(s) here. But that would mean they won't be available when we try
// to run the offline move operation once we're back online. So we'll just
// leave things as they are:
// - the message(s) copied into the destination folder
// - the original message(s) left in the source folder
// - the offline move operation all queued up for when we go back online
// When we do go back online, the offline move op will be performed and
// the source message(s) will be deleted. For real.
// Would be nice to have some marker to hide or grey out messages which are
// in this state of impending doom... but it's a pretty obscure corner case
// and we've already got quite enough of those.
//
// BUT... CopyMessagesOffline() is also used when online (ha!), *if* we're
// copying between folders on the same nsIMsgIncomingServer, in order to
// support undo. In that case we _do_ want to go ahead with the delete now.
bool sameServer;
rv = IsOnSameServer(srcFolder, this, &sameServer);
if (NS_SUCCEEDED(rv) && sameServer && isMove &&
(deleteToTrash || deleteImmediately)) {
DeleteStoreMessages(keysToDelete, srcFolder);
srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
false);
sourceMailDB->DeleteMessages(keysToDelete, nullptr);
srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
true);
}
nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
OnCopyCompleted(srcSupport, rv);
if (isMove) {
srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
: kDeleteOrMoveMsgFailed);
}
return rv;
}