nsresult nsImapMailFolder::CopyMessagesOffline()

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;
}