nsresult nsMsgFilterAfterTheFact::ApplyFilter()

in mailnews/search/src/nsMsgFilterService.cpp [609:986]


nsresult nsMsgFilterAfterTheFact::ApplyFilter() {
  MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
          ("(Post) nsMsgFilterAfterTheFact::ApplyFilter"));
  nsresult rv;
  do {
    // Error management block, break if unable to continue with filter.

    if (!m_curFilter)
      break;  // Maybe not an error, we just need to call RunNextFilter();
    if (!m_curFolder)
      break;  // Maybe not an error, we just need to call AdvanceToNextFolder();

    // 'm_curFolder' can be reset asynchronously by the copy service
    // calling OnStopCopy(). So take a local copy here and use it throughout the
    // function.
    nsCOMPtr<nsIMsgFolder> curFolder = m_curFolder;
    nsCOMPtr<nsIMsgFilter> curFilter = m_curFilter;

    // We're going to log the filter actions before firing them because some
    // actions are async.
    bool loggingEnabled = false;
    if (m_filters) (void)m_filters->GetLoggingEnabled(&loggingEnabled);

    nsTArray<RefPtr<nsIMsgRuleAction>> actionList;
    rv = curFilter->GetSortedActionList(actionList);
    BREAK_IF_FAILURE(rv, "Could not get action list for filter");

    uint32_t numActions = actionList.Length();

    if (m_nextAction == 0) {
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
              ("(Post) Applying %" PRIu32 " filter actions to %" PRIu32
               " matched messages",
               numActions, static_cast<uint32_t>(m_searchHits.Length())));
    } else if (m_nextAction < numActions) {
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
              ("(Post) Applying remaining %" PRIu32
               " filter actions to %" PRIu32 " matched messages",
               numActions - m_nextAction,
               static_cast<uint32_t>(m_searchHits.Length())));
    }

    // We start from m_nextAction to allow us to continue applying actions
    // after the return from an async copy.
    while (m_nextAction < numActions) {
      nsresult finalResult = NS_OK;
      nsCOMPtr<nsIMsgRuleAction> filterAction(actionList[m_nextAction]);
      ++m_nextAction;

      nsMsgRuleActionType actionType;
      rv = filterAction->GetType(&actionType);
      CONTINUE_IF_FAILURE(rv, "Could not get type for filter action");
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
              ("(Post) Running filter action at index %" PRIu32
               ", action type = %i",
               m_nextAction - 1, actionType));

      nsCString actionTargetFolderUri;
      if (actionType == nsMsgFilterAction::MoveToFolder ||
          actionType == nsMsgFilterAction::CopyToFolder) {
        rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
        CONTINUE_IF_FAILURE(rv, "GetTargetFolderUri failed");
        CONTINUE_IF_FALSE(!actionTargetFolderUri.IsEmpty(),
                          "actionTargetFolderUri is empty");
      }

      if (loggingEnabled) {
        for (auto msgHdr : m_searchHitHdrs) {
          (void)curFilter->LogRuleHit(filterAction, msgHdr);
        }
      }

      // all actions that pass "this" as a listener in order to chain filter
      // execution when the action is finished need to return before reaching
      // the bottom of this routine, because we run the next filter at the end
      // of this routine.
      switch (actionType) {
        case nsMsgFilterAction::Delete:
          // we can't pass ourselves in as a copy service listener because the
          // copy service listener won't get called in several situations (e.g.,
          // the delete model is imap delete) and we rely on the listener
          // getting called to continue the filter application. This means we're
          // going to end up firing off the delete, and then subsequently
          // issuing a search for the next filter, which will block until the
          // delete finishes.
          rv = curFolder->DeleteMessages(m_searchHitHdrs, m_msgWindow, false,
                                         false, nullptr, false /*allow Undo*/);
          BREAK_ACTION_IF_FAILURE(rv, "Deleting messages failed");

          // don't allow any more filters on this message
          m_stopFiltering.AppendElements(m_searchHits);
          for (uint32_t i = 0; i < m_searchHits.Length(); i++)
            curFolder->OrProcessingFlags(m_searchHits[i],
                                         nsMsgProcessingFlags::FilterToMove);
          // if we are deleting then we couldn't care less about applying
          // remaining filter actions
          m_nextAction = numActions;
          break;

        case nsMsgFilterAction::MoveToFolder:
          // Even if move fails we will not run additional actions, as they
          // would not have run if move succeeded.
          m_nextAction = numActions;
          // Fall through to the copy case.
          [[fallthrough]];
        case nsMsgFilterAction::CopyToFolder: {
          nsCString uri;
          curFolder->GetURI(uri);

          if (uri.Equals(actionTargetFolderUri)) {
            MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
                    ("(Post) Target folder is the same as source folder, "
                     "skipping"));
            break;
          }

          nsCOMPtr<nsIMsgFolder> destIFolder;
          rv = GetOrCreateFolder(actionTargetFolderUri,
                                 getter_AddRefs(destIFolder));
          BREAK_ACTION_IF_FAILURE(rv, "Could not get action folder");

          bool canFileMessages = true;
          nsCOMPtr<nsIMsgFolder> parentFolder;
          destIFolder->GetParent(getter_AddRefs(parentFolder));
          if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages);
          if (!parentFolder || !canFileMessages) {
            curFilter->SetEnabled(false);
            destIFolder->ThrowAlertMsg("filterDisabled", m_msgWindow);
            // we need to explicitly save the filter file.
            m_filters->SaveToDefaultFile();
            // In the case of applying multiple filters
            // we might want to remove the filter from the list, but
            // that's a bit evil since we really don't know that we own
            // the list. Disabling it doesn't do a lot of good since
            // we still apply disabled filters. Currently, we don't
            // have any clients that apply filters to multiple folders,
            // so this might be the edge case of an edge case.
            m_nextAction = numActions;
            BREAK_ACTION_IF_FALSE(false,
                                  "No parent folder or folder can't file "
                                  "messages, disabling the filter");
          }
          nsCOMPtr<nsIMsgCopyService> copyService =
              do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
          BREAK_ACTION_IF_FAILURE(rv, "Could not get copy service");

          if (actionType == nsMsgFilterAction::MoveToFolder) {
            m_stopFiltering.AppendElements(m_searchHits);
            for (uint32_t i = 0; i < m_searchHits.Length(); i++)
              curFolder->OrProcessingFlags(m_searchHits[i],
                                           nsMsgProcessingFlags::FilterToMove);
          }

          rv = copyService->CopyMessages(
              curFolder, m_searchHitHdrs, destIFolder,
              actionType == nsMsgFilterAction::MoveToFolder, this, m_msgWindow,
              false);
          BREAK_ACTION_IF_FAILURE(rv, "CopyMessages failed");
          MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
                  ("(Post) Action execution continues async"));
          return NS_OK;  // OnStopCopy callback to continue;
        } break;
        case nsMsgFilterAction::MarkRead:
          // crud, no listener support here - we'll probably just need to go on
          // and apply the next filter, and, in the imap case, rely on multiple
          // connection and url queueing to stay out of trouble
          rv = curFolder->MarkMessagesRead(m_searchHitHdrs, true);
          BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
          break;
        case nsMsgFilterAction::MarkUnread:
          rv = curFolder->MarkMessagesRead(m_searchHitHdrs, false);
          BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
          break;
        case nsMsgFilterAction::MarkFlagged:
          rv = curFolder->MarkMessagesFlagged(m_searchHitHdrs, true);
          BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
          break;
        case nsMsgFilterAction::KillThread:
        case nsMsgFilterAction::WatchThread: {
          for (auto msgHdr : m_searchHitHdrs) {
            nsCOMPtr<nsIMsgThread> msgThread;
            nsMsgKey threadKey;
            m_curFolderDB->GetThreadContainingMsgHdr(msgHdr,
                                                     getter_AddRefs(msgThread));
            BREAK_ACTION_IF_FALSE(msgThread, "Could not find msg thread");
            msgThread->GetThreadKey(&threadKey);
            if (actionType == nsMsgFilterAction::KillThread) {
              rv = m_curFolderDB->MarkThreadIgnored(msgThread, threadKey, true,
                                                    nullptr);
              BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
            } else {
              rv = m_curFolderDB->MarkThreadWatched(msgThread, threadKey, true,
                                                    nullptr);
              BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
            }
          }
        } break;
        case nsMsgFilterAction::KillSubthread: {
          for (auto msgHdr : m_searchHitHdrs) {
            nsMsgKey msgKey;
            msgHdr->GetMessageKey(&msgKey);
            rv = m_curFolderDB->MarkKilled(msgKey, true, nullptr);
            BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
          }
        } break;
        case nsMsgFilterAction::ChangePriority: {
          nsMsgPriorityValue filterPriority;
          filterAction->GetPriority(&filterPriority);
          for (auto msgHdr : m_searchHitHdrs) {
            rv = msgHdr->SetPriority(filterPriority);
            BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
          }
        } break;
        case nsMsgFilterAction::AddTag: {
          nsCString keyword;
          filterAction->GetStrValue(keyword);
          rv = curFolder->AddKeywordsToMessages(m_searchHitHdrs, keyword);
          BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
        } break;
        case nsMsgFilterAction::JunkScore: {
          nsAutoCString junkScoreStr;
          int32_t junkScore;
          filterAction->GetJunkScore(&junkScore);
          rv = curFolder->SetJunkScoreForMessages(m_searchHitHdrs, junkScore,
                                                  "filter"_ns, -1);
          BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed");
        } break;
        case nsMsgFilterAction::Forward: {
          nsCOMPtr<nsIMsgIncomingServer> server;
          rv = curFolder->GetServer(getter_AddRefs(server));
          BREAK_ACTION_IF_FAILURE(rv, "Could not get server");
          nsCString forwardTo;
          filterAction->GetStrValue(forwardTo);
          BREAK_ACTION_IF_FALSE(!forwardTo.IsEmpty(), "blank forwardTo URI");
          nsCOMPtr<nsIMsgComposeService> compService =
              do_GetService("@mozilla.org/messengercompose;1", &rv);
          BREAK_ACTION_IF_FAILURE(rv, "Could not get compose service");

          for (auto msgHdr : m_searchHitHdrs) {
            rv = compService->ForwardMessage(
                NS_ConvertASCIItoUTF16(forwardTo), msgHdr, m_msgWindow, server,
                nsIMsgComposeService::kForwardAsDefault);
            BREAK_ACTION_IF_FAILURE(rv, "Forward action failed");
          }
        } break;
        case nsMsgFilterAction::Reply: {
          nsCString replyTemplateUri;
          filterAction->GetStrValue(replyTemplateUri);
          BREAK_ACTION_IF_FALSE(!replyTemplateUri.IsEmpty(),
                                "Empty reply template URI");

          nsCOMPtr<nsIMsgIncomingServer> server;
          rv = curFolder->GetServer(getter_AddRefs(server));
          BREAK_ACTION_IF_FAILURE(rv, "Could not get server");

          nsCOMPtr<nsIMsgComposeService> compService =
              do_GetService("@mozilla.org/messengercompose;1", &rv);
          BREAK_ACTION_IF_FAILURE(rv, "Could not get compose service");
          for (auto msgHdr : m_searchHitHdrs) {
            rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri,
                                                m_msgWindow, server);
            if (NS_FAILED(rv)) {
              if (rv == NS_ERROR_ABORT) {
                (void)curFilter->LogRuleHitFail(
                    filterAction, msgHdr, rv,
                    "filterFailureSendingReplyAborted"_ns);
              } else {
                (void)curFilter->LogRuleHitFail(
                    filterAction, msgHdr, rv,
                    "filterFailureSendingReplyError"_ns);
              }
            }
            BREAK_ACTION_IF_FAILURE(rv, "ReplyWithTemplate failed");
          }
        } break;
        case nsMsgFilterAction::DeleteFromPop3Server: {
          nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
              do_QueryInterface(curFolder, &rv);
          BREAK_ACTION_IF_FAILURE(rv, "Current folder not a local folder");
          BREAK_ACTION_IF_FALSE(localFolder,
                                "Current folder not a local folder");
          // This action ignores the deleteMailLeftOnServer preference
          rv = localFolder->MarkMsgsOnPop3Server(m_searchHitHdrs,
                                                 POP3_FORCE_DEL);
          BREAK_ACTION_IF_FAILURE(rv, "MarkMsgsOnPop3Server failed");

          // Delete the partial headers. They're useless now
          //   that the server copy is being deleted.
          nsTArray<RefPtr<nsIMsgDBHdr>> partialMsgs;
          for (uint32_t i = 0; i < m_searchHits.Length(); ++i) {
            nsIMsgDBHdr* msgHdr = m_searchHitHdrs[i];
            nsMsgKey msgKey = m_searchHits[i];
            uint32_t flags;
            msgHdr->GetFlags(&flags);
            if (flags & nsMsgMessageFlags::Partial) {
              partialMsgs.AppendElement(msgHdr);
              m_stopFiltering.AppendElement(msgKey);
              curFolder->OrProcessingFlags(msgKey,
                                           nsMsgProcessingFlags::FilterToMove);
            }
          }
          if (!partialMsgs.IsEmpty()) {
            rv = curFolder->DeleteMessages(partialMsgs, m_msgWindow, true,
                                           false, nullptr, false);
            BREAK_ACTION_IF_FAILURE(rv, "Delete messages failed");
          }
        } break;
        case nsMsgFilterAction::FetchBodyFromPop3Server: {
          nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
              do_QueryInterface(curFolder, &rv);
          BREAK_ACTION_IF_FAILURE(rv, "current folder not local");
          BREAK_ACTION_IF_FALSE(localFolder, "current folder not local");
          nsTArray<RefPtr<nsIMsgDBHdr>> messages;
          for (nsIMsgDBHdr* msgHdr : m_searchHitHdrs) {
            uint32_t flags = 0;
            msgHdr->GetFlags(&flags);
            if (flags & nsMsgMessageFlags::Partial)
              messages.AppendElement(msgHdr);
          }
          if (messages.Length() > 0) {
            rv = curFolder->DownloadMessagesForOffline(messages, m_msgWindow);
            BREAK_ACTION_IF_FAILURE(rv, "DownloadMessagesForOffline failed");
          }
        } break;

        case nsMsgFilterAction::StopExecution: {
          // don't apply any more filters
          m_stopFiltering.AppendElements(m_searchHits);
          m_nextAction = numActions;
        } break;

        case nsMsgFilterAction::Custom: {
          nsMsgFilterTypeType filterType;
          curFilter->GetFilterType(&filterType);
          nsCOMPtr<nsIMsgFilterCustomAction> customAction;
          rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
          BREAK_ACTION_IF_FAILURE(rv, "Could not get custom action");

          nsAutoCString value;
          rv = filterAction->GetStrValue(value);
          BREAK_ACTION_IF_FAILURE(rv, "Could not get custom action value");
          bool isAsync = false;
          customAction->GetIsAsync(&isAsync);
          rv = customAction->ApplyAction(m_searchHitHdrs, value, this,
                                         filterType, m_msgWindow);
          BREAK_ACTION_IF_FAILURE(rv, "custom action failed to apply");
          if (isAsync) {
            MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
                    ("(Post) Action execution continues async"));
            return NS_OK;  // custom action should call ApplyFilter on callback
          }
        } break;

        default:
          NS_ERROR("unexpected filter action");
          BREAK_ACTION_IF_FAILURE(NS_ERROR_UNEXPECTED,
                                  "Unexpected filter action");
      }
      if (NS_FAILED(finalResult)) {
        mFinalResult = finalResult;
        MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
                ("(Post) Action execution failed with error: %" PRIx32,
                 static_cast<uint32_t>(mFinalResult)));
        if (loggingEnabled && m_searchHitHdrs.Length() > 0) {
          (void)curFilter->LogRuleHitFail(filterAction, m_searchHitHdrs[0],
                                          mFinalResult,
                                          "filterActionFailed"_ns);
        }
      } else {
        MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
                ("(Post) Action execution succeeded"));
      }
    }
  } while (false);  // end error management block
  MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
          ("(Post) Finished executing actions"));
  return RunNextFilter();
}