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