diff --git a/mozilla/mailnews/base/public/nsISpamSettings.idl b/mozilla/mailnews/base/public/nsISpamSettings.idl index d1b8513e820..8dd574c0f0f 100644 --- a/mozilla/mailnews/base/public/nsISpamSettings.idl +++ b/mozilla/mailnews/base/public/nsISpamSettings.idl @@ -55,6 +55,7 @@ interface nsISpamSettings: nsISupports { attribute long level; attribute boolean moveOnSpam; + attribute boolean markAsReadOnSpam; /** * Most consumers will just use spamFolderURI rather than accessing any of diff --git a/mozilla/mailnews/base/resources/content/junkMail.js b/mozilla/mailnews/base/resources/content/junkMail.js index 9d59205598c..74fdb1b0c41 100644 --- a/mozilla/mailnews/base/resources/content/junkMail.js +++ b/mozilla/mailnews/base/resources/content/junkMail.js @@ -115,6 +115,9 @@ function setupForAccountFromFolder(aURI) document.getElementById("moveOnSpam").checked = obj.settings.moveOnSpam; document.getElementById("moveTargetMode").selectedItem = document.getElementById("moveTargetMode" + obj.settings.moveTargetMode); + // set up the 'mark as read' checkbox + document.getElementById("markAsReadOnSpam").checked = obj.settings.markAsReadOnSpam; + // the default account should be the current account // unless you can't create a folder on that server // or search on that account (for purge) @@ -177,6 +180,7 @@ function onAccept() function storeSettings(aSettings, aLoggingEnabled) { aSettings.level = document.getElementById("level").checked ? 100 : 0; + aSettings.markAsReadOnSpam = document.getElementById("markAsReadOnSpam").checked; aSettings.moveOnSpam = document.getElementById("moveOnSpam").checked; aSettings.moveTargetMode = document.getElementById("moveTargetMode").value; aSettings.actionTargetAccount = document.getElementById("actionTargetAccount").getAttribute("uri"); @@ -206,6 +210,7 @@ function conditionallyEnableUI(id) document.getElementById("useWhiteList").disabled = true; document.getElementById("whiteListAbURI").disabled = true; document.getElementById("moveOnSpam").disabled = true; + document.getElementById("markAsReadOnSpam").disabled = true; document.getElementById("moveTargetMode").disabled = true; document.getElementById("actionTargetAccount").disabled = true; @@ -222,6 +227,7 @@ function conditionallyEnableUI(id) document.getElementById("useWhiteList").disabled = false; document.getElementById("moveOnSpam").disabled = false; + document.getElementById("markAsReadOnSpam").disabled = false; document.getElementById("manualMark").disabled = false; var enabled; diff --git a/mozilla/mailnews/base/resources/content/junkMail.xul b/mozilla/mailnews/base/resources/content/junkMail.xul index fa7a5e0ee56..d9a9f3b7163 100644 --- a/mozilla/mailnews/base/resources/content/junkMail.xul +++ b/mozilla/mailnews/base/resources/content/junkMail.xul @@ -150,6 +150,8 @@ + + diff --git a/mozilla/mailnews/base/resources/content/mail3PaneWindowCommands.js b/mozilla/mailnews/base/resources/content/mail3PaneWindowCommands.js index 85d7c4a404a..41ed95044b4 100644 --- a/mozilla/mailnews/base/resources/content/mail3PaneWindowCommands.js +++ b/mozilla/mailnews/base/resources/content/mail3PaneWindowCommands.js @@ -611,7 +611,7 @@ var DefaultController = MsgApplyFilters(null); return; case "cmd_runJunkControls": - analyzeFolderForJunk(); + filterFolderForJunk(); return; case "cmd_deleteJunk": deleteJunkInFolder(); diff --git a/mozilla/mailnews/base/resources/content/mailCommands.js b/mozilla/mailnews/base/resources/content/mailCommands.js index ecd54ef7104..e77aed40d6d 100644 --- a/mozilla/mailnews/base/resources/content/mailCommands.js +++ b/mozilla/mailnews/base/resources/content/mailCommands.js @@ -480,68 +480,71 @@ const nsIJunkMailPlugin = Components.interfaces.nsIJunkMailPlugin; const nsIMsgDBHdr = Components.interfaces.nsIMsgDBHdr; var gJunkmailComponent; -var gJunkKeys = []; -var gJunkTargetFolder; -function saveJunkMsgForAction(aServer, aMsgURI, aClassification) +function determineActionsForJunkMsgs(aView, aIndices, aActionParams) { - // we only care when the message gets marked as junk - if (aClassification == nsIJunkMailPlugin.GOOD) + + // we use some arbitrary message to determine the + // message server + var msgURI = aView.getURIForViewIndex(aIndices[0]); + var msgHdr = messenger.messageServiceFromURI(msgURI).messageURIToMsgHdr(msgURI); + var server = msgHdr.folder.server; + + var spamSettings = server.spamSettings; + + // note we will do moves/marking as read even if the spam + // feature is disabled, since the user has asked to use it + // despite the disabling + + // note also that we will only act on messages which + // _the_current_run_ of the classifier has classified as + // junk, rather than on all junk messages in the folder + + aActionParams.markRead = spamSettings.markAsReadOnSpam; + aActionParams.junkTargetFolder = null; + + if (!spamSettings.moveOnSpam) return; - var spamSettings = aServer.spamSettings - - // if the spam feature is disabled, - // or if the move functionality is turned off, bail out. - // the user could still run the JMC manually, - // but let's not move in that scenario - if (!spamSettings.level || !spamSettings.moveOnSpam) - return; - - var msgHdr = messenger.messageServiceFromURI(aMsgURI).messageURIToMsgHdr(aMsgURI); - // don't move if we are already in the junk folder if (msgHdr.folder.flags & MSG_FOLDER_FLAG_JUNK) return; var spamFolderURI = spamSettings.spamFolderURI; if (!spamFolderURI) - return; - - var spamFolder = GetMsgFolderFromUri(spamFolderURI); - - if (spamFolder) { - gJunkKeys[gJunkKeys.length] = msgHdr.messageKey; - gJunkTargetFolder = spamFolder; + dump('no spam folder!'); + return; } + + aActionParams.junkTargetFolder = GetMsgFolderFromUri(spamFolderURI); } -function performActionOnJunkMsgs() +function performActionsOnJunkMsgs(aIndices) { - if (!gJunkKeys.length) - { - gJunkTargetFolder = []; - return; - } - - var indices = new Array(gJunkKeys.length); - for (var i=0;i + diff --git a/mozilla/mailnews/base/src/nsMsgDBView.cpp b/mozilla/mailnews/base/src/nsMsgDBView.cpp index b5807437341..aa66afc4288 100644 --- a/mozilla/mailnews/base/src/nsMsgDBView.cpp +++ b/mozilla/mailnews/base/src/nsMsgDBView.cpp @@ -141,8 +141,9 @@ nsMsgDBView::nsMsgDBView() mIsNews = PR_FALSE; mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; m_deletingRows = PR_FALSE; - mOutstandingJunkBatches = 0; - + mJunkIndices = nsnull; + mNumJunkIndices = 0; + /* mCommandsNeedDisablingBecauseOffline - A boolean that tell us if we needed to disable commands because we're offline w/o a downloaded msg select */ mCommandsNeedDisablingBecauseOffline = PR_FALSE; @@ -2229,185 +2230,206 @@ nsresult nsMsgDBView::ApplyCommandToIndices(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices, PRInt32 numIndices) { - nsresult rv = NS_OK; - nsMsgKeyArray imapUids; - - // if numIndices == 0, return quietly, just in case - if (numIndices == 0) - return NS_OK; - NS_ASSERTION(numIndices >= 0, "nsMsgDBView::ApplyCommandToIndices(): " "numIndices is negative!"); - nsCOMPtr imapFolder = do_QueryInterface(m_folder); - PRBool thisIsImapFolder = (imapFolder != nsnull); + if (numIndices == 0) + return NS_OK; // return quietly, just in case if (command == nsMsgViewCommandType::deleteMsg) - rv = DeleteMessages(mMsgWindow, indices, numIndices, PR_FALSE); - else if (command == nsMsgViewCommandType::deleteNoTrash) - rv = DeleteMessages(mMsgWindow, indices, numIndices, PR_TRUE); - else + return DeleteMessages(mMsgWindow, indices, numIndices, PR_FALSE); + if (command == nsMsgViewCommandType::deleteNoTrash) + return DeleteMessages(mMsgWindow, indices, numIndices, PR_TRUE); + + nsMsgKeyArray imapUids; + nsresult rv = NS_OK; + nsCOMPtr imapFolder = do_QueryInterface(m_folder); + PRBool thisIsImapFolder = (imapFolder != nsnull); + nsCOMPtr junkPlugin; + + // if this is a junk command, start a batch. + // + if ( command == nsMsgViewCommandType::junk + || command == nsMsgViewCommandType::unjunk ) { - nsCOMPtr junkPlugin; + // get the folder from the first item; we assume that + // all messages in the view are from the same folder (no + // more junk status column in the 'search messages' dialog + // like in earlier versions...) + // + nsCOMPtr folder; + rv = GetFolderForViewIndex(indices[0], getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); - // if this is a junk command, start a batch. The batch will be ended - // in the last callback. - // - if ( command == nsMsgViewCommandType::junk - || command == nsMsgViewCommandType::unjunk ) + nsCOMPtr server; + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + if (command == nsMsgViewCommandType::junk) { - - // get the folder from the first item (if it's the search view, - // only one item can be touched at a time; if a regular folder view, - // all items will have the same folder). - // - nsCOMPtr folder; - rv = GetFolderForViewIndex(indices[0], getter_AddRefs(folder)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr server; - rv = folder->GetServer(getter_AddRefs(server)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr filterPlugin; - rv = server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin)); - NS_ENSURE_SUCCESS(rv, rv); - - junkPlugin = do_QueryInterface(filterPlugin, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = junkPlugin->StartBatch(); - NS_ENSURE_SUCCESS(rv, rv); - - mOutstandingJunkBatches++; + // append this batch of junk message indices to the + // array of junk message indices to be acted upon + // once OnMessageClassified() is run for the last message + // + // note: although message classification is done + // asynchronously, it is not done in a different thread, + // so the manipulations of mJunkIndices here and in + // OnMessageClassified() cannot interrupt each other + // + mNumJunkIndices += numIndices; + mJunkIndices = (nsMsgViewIndex *)nsMemory::Realloc(mJunkIndices, mNumJunkIndices * sizeof(nsMsgViewIndex)); + memcpy(mJunkIndices + (mNumJunkIndices - numIndices), indices, numIndices * sizeof(nsMsgViewIndex)); + + // save the last URI, so that OnMessageClassified() + // will know when the classification it runs after + // is the last one; if the classification of previously-marked + // messages has not been completed, we replace here the + // previous 'last URI' with a new 'last URI', + // causing the batches to be coalesced + // + rv = GetURIForViewIndex(indices[numIndices-1], getter_Copies(mLastJunkURIInBatch)); + NS_ENSURE_SUCCESS(rv, rv); } - - m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, PR_FALSE, PR_TRUE /*dbBatching*/); + + nsCOMPtr filterPlugin; + rv = server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin)); + NS_ENSURE_SUCCESS(rv, rv); - for (int32 i = 0; i < numIndices; i++) + junkPlugin = do_QueryInterface(filterPlugin, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = junkPlugin->StartBatch(); + NS_ENSURE_SUCCESS(rv, rv); + } + + m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, PR_FALSE, PR_TRUE /*dbBatching*/); + + for (int32 i = 0; i < numIndices; i++) + { + if (thisIsImapFolder && command != nsMsgViewCommandType::markThreadRead) + imapUids.Add(GetAt(indices[i])); + + switch (command) { - if (thisIsImapFolder && command != nsMsgViewCommandType::markThreadRead) - imapUids.Add(GetAt(indices[i])); - - switch (command) - { - case nsMsgViewCommandType::markMessagesRead: - rv = SetReadByIndex(indices[i], PR_TRUE); - break; - case nsMsgViewCommandType::markMessagesUnread: - rv = SetReadByIndex(indices[i], PR_FALSE); - break; - case nsMsgViewCommandType::toggleMessageRead: - rv = ToggleReadByIndex(indices[i]); - break; - case nsMsgViewCommandType::flagMessages: - rv = SetFlaggedByIndex(indices[i], PR_TRUE); - break; - case nsMsgViewCommandType::unflagMessages: - rv = SetFlaggedByIndex(indices[i], PR_FALSE); - break; - case nsMsgViewCommandType::markThreadRead: - rv = SetThreadOfMsgReadByIndex(indices[i], imapUids, PR_TRUE); - break; - case nsMsgViewCommandType::label0: - case nsMsgViewCommandType::label1: - case nsMsgViewCommandType::label2: - case nsMsgViewCommandType::label3: - case nsMsgViewCommandType::label4: - case nsMsgViewCommandType::label5: - rv = SetLabelByIndex(indices[i], (command - nsMsgViewCommandType::label0)); - break; - case nsMsgViewCommandType::junk: - rv = SetJunkScoreByIndex(junkPlugin.get(), indices[i], - nsIJunkMailPlugin::JUNK, (i == numIndices-1)); - break; - case nsMsgViewCommandType::unjunk: - rv = SetJunkScoreByIndex(junkPlugin.get(), indices[i], - nsIJunkMailPlugin::GOOD, (i == numIndices-1)); - break; - case nsMsgViewCommandType::undeleteMsg: - break; // this is completely handled in the imap code below. - default: - NS_ASSERTION(PR_FALSE, "unhandled command"); - break; - } - } - m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, PR_TRUE, PR_TRUE /*dbBatching*/); - - if (thisIsImapFolder) - { - imapMessageFlagsType flags = kNoImapMsgFlag; - PRBool commandIsLabelSet = PR_FALSE; - PRBool addFlags = PR_FALSE; - PRBool isRead = PR_FALSE; - - switch (command) - { - case nsMsgViewCommandType::markThreadRead: - case nsMsgViewCommandType::markMessagesRead: - flags |= kImapMsgSeenFlag; - addFlags = PR_TRUE; - break; - case nsMsgViewCommandType::markMessagesUnread: - flags |= kImapMsgSeenFlag; - addFlags = PR_FALSE; - break; - case nsMsgViewCommandType::toggleMessageRead: - { - flags |= kImapMsgSeenFlag; - m_db->IsRead(GetAt(indices[0]), &isRead); - if (isRead) - addFlags = PR_TRUE; - else - addFlags = PR_FALSE; - } - break; - case nsMsgViewCommandType::flagMessages: - flags |= kImapMsgFlaggedFlag; - addFlags = PR_TRUE; - break; - case nsMsgViewCommandType::unflagMessages: - flags |= kImapMsgFlaggedFlag; - addFlags = PR_FALSE; - break; - case nsMsgViewCommandType::label0: - case nsMsgViewCommandType::label1: - case nsMsgViewCommandType::label2: - case nsMsgViewCommandType::label3: - case nsMsgViewCommandType::label4: - case nsMsgViewCommandType::label5: - flags |= ((command - nsMsgViewCommandType::label0) << 9); - addFlags = (command != nsMsgViewCommandType::label0); - commandIsLabelSet = PR_TRUE; - break; - case nsMsgViewCommandType::undeleteMsg: - flags = kImapMsgDeletedFlag; - addFlags = PR_FALSE; - break; - case nsMsgViewCommandType::junk: - return imapFolder->StoreCustomKeywords(mMsgWindow, - "Junk", - "NonJunk", - imapUids.GetArray(), imapUids.GetSize(), - nsnull); - case nsMsgViewCommandType::unjunk: - return imapFolder->StoreCustomKeywords(mMsgWindow, - "NonJunk", - "Junk", - imapUids.GetArray(), imapUids.GetSize(), - nsnull); - - default: - break; - } - - if (flags != kNoImapMsgFlag || commandIsLabelSet) // can't get here without thisIsImapThreadPane == TRUE - imapFolder->StoreImapFlags(flags, addFlags, imapUids.GetArray(), imapUids.GetSize()); - + case nsMsgViewCommandType::markMessagesRead: + rv = SetReadByIndex(indices[i], PR_TRUE); + break; + case nsMsgViewCommandType::markMessagesUnread: + rv = SetReadByIndex(indices[i], PR_FALSE); + break; + case nsMsgViewCommandType::toggleMessageRead: + rv = ToggleReadByIndex(indices[i]); + break; + case nsMsgViewCommandType::flagMessages: + rv = SetFlaggedByIndex(indices[i], PR_TRUE); + break; + case nsMsgViewCommandType::unflagMessages: + rv = SetFlaggedByIndex(indices[i], PR_FALSE); + break; + case nsMsgViewCommandType::markThreadRead: + rv = SetThreadOfMsgReadByIndex(indices[i], imapUids, PR_TRUE); + break; + case nsMsgViewCommandType::label0: + case nsMsgViewCommandType::label1: + case nsMsgViewCommandType::label2: + case nsMsgViewCommandType::label3: + case nsMsgViewCommandType::label4: + case nsMsgViewCommandType::label5: + rv = SetLabelByIndex(indices[i], (command - nsMsgViewCommandType::label0)); + break; + case nsMsgViewCommandType::junk: + rv = SetAsJunkByIndex(junkPlugin.get(), indices[i], + nsIJunkMailPlugin::JUNK); + break; + case nsMsgViewCommandType::unjunk: + rv = SetAsJunkByIndex(junkPlugin.get(), indices[i], + nsIJunkMailPlugin::GOOD); + break; + case nsMsgViewCommandType::undeleteMsg: + break; // this is completely handled in the imap code below. + default: + NS_ASSERTION(PR_FALSE, "unhandled command"); + break; } } + + m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, PR_TRUE, PR_TRUE /*dbBatching*/); + + if (thisIsImapFolder) + { + imapMessageFlagsType flags = kNoImapMsgFlag; + PRBool commandIsLabelSet = PR_FALSE; + PRBool addFlags = PR_FALSE; + PRBool isRead = PR_FALSE; + + switch (command) + { + case nsMsgViewCommandType::markThreadRead: + case nsMsgViewCommandType::markMessagesRead: + flags |= kImapMsgSeenFlag; + addFlags = PR_TRUE; + break; + case nsMsgViewCommandType::markMessagesUnread: + flags |= kImapMsgSeenFlag; + addFlags = PR_FALSE; + break; + case nsMsgViewCommandType::toggleMessageRead: + { + flags |= kImapMsgSeenFlag; + m_db->IsRead(GetAt(indices[0]), &isRead); + if (isRead) + addFlags = PR_TRUE; + else + addFlags = PR_FALSE; + } + break; + case nsMsgViewCommandType::flagMessages: + flags |= kImapMsgFlaggedFlag; + addFlags = PR_TRUE; + break; + case nsMsgViewCommandType::unflagMessages: + flags |= kImapMsgFlaggedFlag; + addFlags = PR_FALSE; + break; + case nsMsgViewCommandType::label0: + case nsMsgViewCommandType::label1: + case nsMsgViewCommandType::label2: + case nsMsgViewCommandType::label3: + case nsMsgViewCommandType::label4: + case nsMsgViewCommandType::label5: + flags |= ((command - nsMsgViewCommandType::label0) << 9); + addFlags = (command != nsMsgViewCommandType::label0); + commandIsLabelSet = PR_TRUE; + break; + case nsMsgViewCommandType::undeleteMsg: + flags = kImapMsgDeletedFlag; + addFlags = PR_FALSE; + break; + case nsMsgViewCommandType::junk: + return imapFolder->StoreCustomKeywords(mMsgWindow, + "Junk", + "NonJunk", + imapUids.GetArray(), imapUids.GetSize(), + nsnull); + case nsMsgViewCommandType::unjunk: + return imapFolder->StoreCustomKeywords(mMsgWindow, + "NonJunk", + "Junk", + imapUids.GetArray(), imapUids.GetSize(), + nsnull); + + default: + break; + } + + if (flags != kNoImapMsgFlag || commandIsLabelSet) // can't get here without thisIsImapThreadPane == TRUE + imapFolder->StoreImapFlags(flags, addFlags, imapUids.GetArray(), imapUids.GetSize()); + + } + return rv; } + // view modifications methods by index // This method just removes the specified line from the view. It does @@ -2621,12 +2643,10 @@ nsresult nsMsgDBView::SetStringPropertyByIndex(nsMsgViewIndex index, const char return rv; } -nsresult nsMsgDBView::SetJunkScoreByIndex(nsIJunkMailPlugin *aJunkPlugin, +nsresult nsMsgDBView::SetAsJunkByIndex(nsIJunkMailPlugin *aJunkPlugin, nsMsgViewIndex aIndex, - nsMsgJunkStatus aNewClassification, - PRBool aIsLastInBatch) + nsMsgJunkStatus aNewClassification) { - // get the message header (need this to get string properties) // nsCOMPtr msgHdr; @@ -2667,13 +2687,6 @@ nsresult nsMsgDBView::SetJunkScoreByIndex(nsIJunkMailPlugin *aJunkPlugin, rv = GetURIForViewIndex(aIndex, getter_Copies(uri)); NS_ENSURE_SUCCESS(rv, rv); - if ( aIsLastInBatch ) { - // if there's already a batch in progress, just replace the URI, - // thus causing the batches to be coalesced - // - mLastJunkUriInBatch = uri; - } - // tell the plugin about this change, so that it can (potentially) // adjust its database appropriately // @@ -2709,106 +2722,185 @@ NS_IMETHODIMP nsMsgDBView::OnMessageClassified(const char *aMsgURI, nsMsgJunkStatus aClassification) { - // we can't just use m_folder - // as this might be from a cross folder search - // see bug #180477 - nsCOMPtr folder; - nsresult rv = GetFolderFromMsgURI(aMsgURI, getter_AddRefs(folder)); - NS_ENSURE_SUCCESS(rv,rv); + // Note: we know all messages in a batch have the same + // classification, since unlike OnMessageClassified + // methods in other classes (such as nsLocalMailFolder + // and nsImapMailFolder), this class, nsMsgDBView, currently + // only triggers message classifications due to a command to + // mark some of the messages in the view as junk, or as not + // junk - so the classification is dictated to the filter, + // not suggested by it. + // + // for this reason the only thing we (may) have to do is + // perform the action on all of the junk messages + // + + // this check is necessary because it is theoretically + // possible for a message to be flagged as non-junk, + // and before the classifier can finish with it, for it + // to be again classified as junk, and thus to have + // mLastJunkURIInBatch set with its URI - and it should + // not be considered the last junk messages during + // the first call of this function + if (aClassification == nsIJunkMailPlugin::GOOD) + return NS_OK; + + NS_ASSERTION(mJunkIndices != nsnull, "the classification of a manually-marked junk message has been classified as junk, yet there seem to be no such outstanding messages"); - nsCOMPtr server; - rv = folder->GetServer(getter_AddRefs(server)); - NS_ENSURE_SUCCESS(rv, rv); + if ( mLastJunkURIInBatch.Equals(aMsgURI) ) + { + nsCOMPtr folder; + nsresult rv = GetFolderForViewIndex(mJunkIndices[0], getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); - // save off the msg hdr, if we need to - rv = SaveJunkMsgForAction(server, aMsgURI, aClassification); - NS_ENSURE_SUCCESS(rv,rv); + // it seems EndBatch must be called here, rather + // than after the last message has been dispatched for + // classification. One would think when the UI tells the + // classifier "I am done with sending you this batch of + // messages" the classifer should say "ok, now as soon + // as I finish classification I should flush my data file" + // ... but that's not the way it works. If that were + // to change you could move the EndBatch() call to + // ApplyCommandToIndices and avoid the redundant + // lines of code which get the pointer to the junk plugin + // object. + + nsCOMPtr server; + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); - // is this the last url in the batch? - if (mLastJunkUriInBatch.Equals(aMsgURI)) - { - // get the filter, and QI to the interface we want nsCOMPtr filterPlugin; rv = server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin)); NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr junkPlugin = do_QueryInterface(filterPlugin, &rv); + + nsCOMPtr junkPlugin; + junkPlugin = do_QueryInterface(filterPlugin, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = junkPlugin->EndBatch(); NS_ENSURE_SUCCESS(rv, rv); - // close out existing coalesced junk batches - for ( ; mOutstandingJunkBatches > 0 ; --mOutstandingJunkBatches ) - { - // tell the plugin that all outstanding batches from us - // have finished. - rv = junkPlugin->EndBatch(); - NS_ENSURE_SUCCESS(rv, rv); + if ( mNumJunkIndices > 0 ) + { + PerformActionsOnJunkMsgs(); + nsMemory::Free(mJunkIndices); + mJunkIndices = nsnull; + mNumJunkIndices = 0; + mLastJunkURIInBatch.Truncate(); } - rv = PerformActionOnJunkMsgs(); - NS_ENSURE_SUCCESS(rv,rv); } return NS_OK; } nsresult -nsMsgDBView::PerformActionOnJunkMsgs() +nsMsgDBView::PerformActionsOnJunkMsgs() { - PRUint32 numIndices = mJunkKeys.GetSize(); - // nothing to do, bail out - if (!numIndices) - { - mJunkTargetFolder = nsnull; // just to be safe - return NS_OK; - } + PRBool movingJunkMessages,markingJunkMessagesRead; + nsCOMPtr junkTargetFolder; - nsMsgViewIndex *indices = (nsMsgViewIndex *)nsMemory::Alloc(numIndices * sizeof(nsMsgViewIndex)); - if (!indices) - return NS_ERROR_OUT_OF_MEMORY; - - for (PRUint32 i=0;iUpdateNextMessageAfterDelete(); - NS_ENSURE_SUCCESS(rv,rv); + // question: is it possible for the junk mail move/mark as read + // options to change after we've handled some of the batches but + // before we've handled the last one? if so, we can decide when + // handling each batch whether to save its indices or forget + // them, and then perform the known action when handling the + // last batch; however if the options can change between batches + // we may have to remember in separate arrays the indices to + // mark as read and the indices to move + // + // for now, we assume the options do not change between batches + // - if (numIndices > 1) - NS_QuickSort(indices, numIndices, sizeof(nsMsgViewIndex), CompareViewIndices, nsnull); - NoteStartChange(nsMsgViewNotificationCode::none, 0, 0); - if (mJunkTargetFolder) - rv = ApplyCommandToIndicesWithFolder(nsMsgViewCommandType::moveMessages, indices, numIndices, mJunkTargetFolder); - else - rv = ApplyCommandToIndices(nsMsgViewCommandType::deleteMsg, indices, numIndices); - NoteEndChange(nsMsgViewNotificationCode::none, 0, 0); + nsresult rv = DetermineActionsForJunkMsgs(&movingJunkMessages, &markingJunkMessagesRead, getter_AddRefs(junkTargetFolder)); + NS_ENSURE_SUCCESS(rv,rv); + + // nothing to do, bail out + if (!(movingJunkMessages || markingJunkMessagesRead)) + return NS_OK; - mJunkKeys.RemoveAll(); - mJunkTargetFolder = nsnull; - nsMemory::Free(indices); + NS_ASSERTION( (mNumJunkIndices > 0), "no indices of marked-as-junk messages to act on"); - NS_ASSERTION(NS_SUCCEEDED(rv), "move or delete failed"); + if (mNumJunkIndices > 1) + NS_QuickSort(mJunkIndices, mNumJunkIndices, sizeof(nsMsgViewIndex), CompareViewIndices, nsnull); + + if (markingJunkMessagesRead) + { + // notes on marking junk as read: + // 1. there are 2 occasions on which junk messages are marked as + // read: here (after a manual marking) as well as after automatic + // marking by the bayesian filter (see code for local mail folders + // and for imap mail folders); it is perhaps worth considering + // making these two separate options... but for now they are + // controlled by a single preference + // 2. even though move/delete on manual mark may be + // turned off, we might still need to mark as read + + NoteStartChange(nsMsgViewNotificationCode::none, 0, 0); + rv = ApplyCommandToIndices(nsMsgViewCommandType::markMessagesRead, mJunkIndices, mNumJunkIndices); + NoteEndChange(nsMsgViewNotificationCode::none, 0, 0); + NS_ASSERTION(NS_SUCCEEDED(rv), "marking marked-as-junk messages as read failed"); + } + if (movingJunkMessages) + { + // tell the FE to call SetNextMessageAfterDelete() because a delete is coming + rv = mCommandUpdater->UpdateNextMessageAfterDelete(); + NS_ENSURE_SUCCESS(rv,rv); + + NoteStartChange(nsMsgViewNotificationCode::none, 0, 0); + if (junkTargetFolder) + rv = ApplyCommandToIndicesWithFolder(nsMsgViewCommandType::moveMessages, mJunkIndices, mNumJunkIndices, junkTargetFolder); + else + rv = ApplyCommandToIndices(nsMsgViewCommandType::deleteMsg, mJunkIndices, mNumJunkIndices); + NoteEndChange(nsMsgViewNotificationCode::none, 0, 0); + + NS_ASSERTION(NS_SUCCEEDED(rv), "move or deletion of marked-as-junk messages failed"); + } return rv; } nsresult -nsMsgDBView::SaveJunkMsgForAction(nsIMsgIncomingServer *aServer, const char *aMsgURI, nsMsgJunkStatus aClassification) +nsMsgDBView::DetermineActionsForJunkMsgs(PRBool* movingJunkMessages, PRBool* markingJunkMessagesRead, nsIMsgFolder** junkTargetFolder) { - // we only care when the message gets marked as junk - if (aClassification == nsIJunkMailPlugin::GOOD) - return NS_OK; + // there are two possible actions which may be performed + // on messages marked as spam: marking as read and moving + // somewhere... + + *movingJunkMessages = false; + *markingJunkMessagesRead = false; + + // ... the 'somewhere', junkTargetFolder, can be a folder, + // but if it remains null we'll delete the messages + + *junkTargetFolder = nsnull; + + nsCOMPtr folder; + nsresult rv = GetFolderForViewIndex(mJunkIndices[0], getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr server; + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr spamSettings; - nsresult rv = aServer->GetSpamSettings(getter_AddRefs(spamSettings)); + rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); NS_ENSURE_SUCCESS(rv, rv); - // if the spam feature is disabled, do nothing - // the user could still manually mark spam if the feature is disabled - // but let's not move or delete in that scenario + // if the spam system is completely disabled we won't do anything + // question: is this a valid choice? PRInt32 spamLevel; (void)spamSettings->GetLevel(&spamLevel); if (!spamLevel) return NS_OK; - // if the manual mark functionality is turned off, bail out. + // now let's determine whether we'll be taking the first action, + // marking as read + + (void)spamSettings->GetMarkAsReadOnSpam(markingJunkMessagesRead); + + + // now let's determine whether we'll be taking the second action, + // the move / deletion (and also determine which of these two) + PRBool manualMark; (void)spamSettings->GetManualMark(&manualMark); if (!manualMark) @@ -2816,28 +2908,14 @@ nsMsgDBView::SaveJunkMsgForAction(nsIMsgIncomingServer *aServer, const char *aMs PRInt32 manualMarkMode; (void)spamSettings->GetManualMarkMode(&manualMarkMode); + NS_ASSERTION(manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE + || manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE, + "bad manual mark mode"); - nsCOMPtr msgMessageService; - rv = GetMessageServiceFromURI(aMsgURI, getter_AddRefs(msgMessageService)); - NS_ENSURE_SUCCESS(rv,rv); - - nsCOMPtr msgHdr; - rv = msgMessageService->MessageURIToMsgHdr(aMsgURI, getter_AddRefs(msgHdr)); - NS_ENSURE_SUCCESS(rv,rv); - - nsCOMPtr srcFolder; - rv = msgHdr->GetFolder(getter_AddRefs(srcFolder)); - NS_ENSURE_SUCCESS(rv,rv); - - nsMsgKey msgKey; - rv = msgHdr->GetMessageKey(&msgKey); - NS_ENSURE_SUCCESS(rv,rv); - - // we can execute the move or delete + // the folder must allow us to execute the move (or the deletion) PRUint32 folderFlags; - srcFolder->GetFlags(&folderFlags); + folder->GetFlags(&folderFlags); - NS_ASSERTION(manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE || manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE, "bad mode"); if (manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE) { PRBool moveOnSpam; @@ -2856,48 +2934,25 @@ nsMsgDBView::SaveJunkMsgForAction(nsIMsgIncomingServer *aServer, const char *aMs rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI)); NS_ENSURE_SUCCESS(rv,rv); - NS_ASSERTION(!spamFolderURI.IsEmpty(), "spam folder is empty, can't move"); + NS_ASSERTION(!spamFolderURI.IsEmpty(), "spam folder URI is empty, can't move"); if (!spamFolderURI.IsEmpty()) { - nsCOMPtr destFolder; - rv = GetExistingFolder(spamFolderURI.get(), getter_AddRefs(destFolder)); - if (NS_SUCCEEDED(rv) && destFolder) - { -#ifdef DEBUG - // double check the assumptions - if (mJunkKeys.GetSize()) - { - NS_ASSERTION(mJunkTargetFolder, "should have a junk folder at this point"); - NS_ASSERTION(mJunkTargetFolder.get() == destFolder.get(), "junk folder doesn't match"); - } - else - NS_ASSERTION(mJunkTargetFolder == nsnull, "junk folder should be null, no keys yet"); -#endif - // save off msg key and folder - mJunkKeys.Add(msgKey); - if (!mJunkTargetFolder) - mJunkTargetFolder = destFolder; - } + //nsCOMPtr destFolder; + rv = GetExistingFolder(spamFolderURI.get(), junkTargetFolder); + NS_ENSURE_SUCCESS(rv,rv); + + *movingJunkMessages = true; } + return NS_OK; } - else // manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE) - { - // if this is in the trash, don't delete? - if (folderFlags & MSG_FOLDER_FLAG_TRASH) - return NS_OK; - - // we can't delete, bail out - PRBool canDelete; - (void)srcFolder->GetCanDeleteMessages(&canDelete); - if (!canDelete) - return NS_OK; - - // save off msg key - mJunkKeys.Add(msgKey); - NS_ASSERTION(mJunkTargetFolder == nsnull, "should be null"); - mJunkTargetFolder = nsnull; // should already be null - } - return NS_OK; + + // at this point manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE) + + // if this is in the trash, let's not delete + if (folderFlags & MSG_FOLDER_FLAG_TRASH) + return NS_OK; + + return folder->GetCanDeleteMessages(movingJunkMessages); } // reversing threads involves reversing the threads but leaving the diff --git a/mozilla/mailnews/base/src/nsMsgDBView.h b/mozilla/mailnews/base/src/nsMsgDBView.h index b6f8f7055bf..a9b6c221610 100644 --- a/mozilla/mailnews/base/src/nsMsgDBView.h +++ b/mozilla/mailnews/base/src/nsMsgDBView.h @@ -250,10 +250,9 @@ protected: virtual nsresult CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool isMove, nsIMsgFolder *destFolder); virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool deleteStorage); nsresult SetStringPropertyByIndex(nsMsgViewIndex index, const char *aProperty, const char *aValue); - nsresult SetJunkScoreByIndex(nsIJunkMailPlugin *aJunkPlugin, + nsresult SetAsJunkByIndex(nsIJunkMailPlugin *aJunkPlugin, nsMsgViewIndex aIndex, - nsMsgJunkStatus aNewClassification, - PRBool aIsLastInBatch); + nsMsgJunkStatus aNewClassification); nsresult ToggleReadByIndex(nsMsgViewIndex index); nsresult SetReadByIndex(nsMsgViewIndex index, PRBool read); nsresult SetThreadOfMsgReadByIndex(nsMsgViewIndex index, nsMsgKeyArray &keysMarkedRead, PRBool read); @@ -360,12 +359,21 @@ protected: // used to cache the atoms created for each color to be displayed static nsIAtom* mLabelPrefColorAtoms[PREF_LABELS_MAX]; - // used to know to finish out the junk mail classification batch when the - // last classification callback happens - nsCString mLastJunkUriInBatch; - PRUint8 mOutstandingJunkBatches; + // comparing against this value, the classifier + // callback function, OnMessageClassified(), can know + // when the classified message is the last one in the + // batch/series of batches (in which case the + // appropriate action is taken for all the messages) + nsXPIDLCString mLastJunkURIInBatch; + // these are the indices of the messages in the current + // batch/series of batches of messages manually marked + // as junk + nsMsgViewIndex *mJunkIndices; + PRUint32 mNumJunkIndices; + nsUInt32Array mIndicesToNoteChange; + protected: static nsresult InitDisplayFormats(); @@ -375,10 +383,8 @@ private: static nsDateFormatSelector m_dateFormatToday; PRBool ServerSupportsFilterAfterTheFact(); - nsMsgKeyArray mJunkKeys; - nsCOMPtr mJunkTargetFolder; - nsresult PerformActionOnJunkMsgs(); - nsresult SaveJunkMsgForAction(nsIMsgIncomingServer *aServer, const char *aMsgURI, nsMsgJunkStatus aClassification); + nsresult PerformActionsOnJunkMsgs(); + nsresult DetermineActionsForJunkMsgs(PRBool* movingJunkMessages, PRBool* markingJunkMessagesRead, nsIMsgFolder** junkTargetFolder); }; diff --git a/mozilla/mailnews/base/src/nsSpamSettings.cpp b/mozilla/mailnews/base/src/nsSpamSettings.cpp index e9cc109e8e4..e7c8e2265aa 100644 --- a/mozilla/mailnews/base/src/nsSpamSettings.cpp +++ b/mozilla/mailnews/base/src/nsSpamSettings.cpp @@ -58,6 +58,7 @@ nsSpamSettings::nsSpamSettings() { mLevel = 0; mMoveOnSpam = PR_FALSE; + mMarkAsReadOnSpam = PR_FALSE; mMoveTargetMode = nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT; mPurge = PR_FALSE; mPurgeInterval = 14; // 14 days @@ -123,6 +124,7 @@ NS_IMETHODIMP nsSpamSettings::SetManualMarkMode(PRInt32 aManualMarkMode) NS_IMPL_GETSET(nsSpamSettings, LoggingEnabled, PRBool, mLoggingEnabled) NS_IMPL_GETSET(nsSpamSettings, MoveOnSpam, PRBool, mMoveOnSpam) +NS_IMPL_GETSET(nsSpamSettings, MarkAsReadOnSpam, PRBool, mMarkAsReadOnSpam) NS_IMPL_GETSET(nsSpamSettings, Purge, PRBool, mPurge) NS_IMPL_GETSET(nsSpamSettings, UseWhiteList, PRBool, mUseWhiteList) NS_IMPL_GETSET(nsSpamSettings, ManualMark, PRBool, mManualMark) @@ -349,6 +351,7 @@ NS_IMETHODIMP nsSpamSettings::Clone(nsISpamSettings *aSpamSettings) NS_ENSURE_SUCCESS(rv,rv); (void)aSpamSettings->GetMoveOnSpam(&mMoveOnSpam); + (void)aSpamSettings->GetMarkAsReadOnSpam(&mMarkAsReadOnSpam); (void)aSpamSettings->GetManualMark(&mManualMark); (void)aSpamSettings->GetManualMarkMode(&mManualMarkMode); (void)aSpamSettings->GetPurge(&mPurge); diff --git a/mozilla/mailnews/base/src/nsSpamSettings.h b/mozilla/mailnews/base/src/nsSpamSettings.h index 618d809873f..70ca97a3341 100644 --- a/mozilla/mailnews/base/src/nsSpamSettings.h +++ b/mozilla/mailnews/base/src/nsSpamSettings.h @@ -72,6 +72,7 @@ private: PRBool mPurge; PRBool mUseWhiteList; PRBool mMoveOnSpam; + PRBool mMarkAsReadOnSpam; nsCString mActionTargetAccount; nsCString mActionTargetFolder; diff --git a/mozilla/mailnews/base/util/nsMsgIncomingServer.cpp b/mozilla/mailnews/base/util/nsMsgIncomingServer.cpp index 82d7f589a5d..1b4451ce7aa 100644 --- a/mozilla/mailnews/base/util/nsMsgIncomingServer.cpp +++ b/mozilla/mailnews/base/util/nsMsgIncomingServer.cpp @@ -2101,6 +2101,10 @@ nsMsgIncomingServer::SetSpamSettings(nsISpamSettings *aSpamSettings) (void)mSpamSettings->GetMoveOnSpam(&moveOnSpam); (void)SetBoolValue("moveOnSpam", moveOnSpam); + PRBool markAsReadOnSpam; + (void)mSpamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam); + (void)SetBoolValue("markAsReadOnSpam", markAsReadOnSpam); + PRInt32 moveTargetMode; (void)mSpamSettings->GetMoveTargetMode(&moveTargetMode); (void)SetIntValue("moveTargetMode", moveTargetMode); @@ -2213,6 +2217,12 @@ nsMsgIncomingServer::GetSpamSettings(nsISpamSettings **aSpamSettings) rv = mSpamSettings->SetMoveOnSpam(moveOnSpam); NS_ENSURE_SUCCESS(rv,rv); + PRBool markAsReadOnSpam; + rv = GetBoolValue("markAsReadOnSpam", &markAsReadOnSpam); + NS_ENSURE_SUCCESS(rv,rv); + rv = mSpamSettings->SetMarkAsReadOnSpam(markAsReadOnSpam); + NS_ENSURE_SUCCESS(rv,rv); + PRInt32 moveTargetMode; rv = GetIntValue("moveTargetMode", &moveTargetMode); NS_ENSURE_SUCCESS(rv,rv); diff --git a/mozilla/mailnews/imap/src/nsImapMailFolder.cpp b/mozilla/mailnews/imap/src/nsImapMailFolder.cpp index c8376c9e1be..25f49f0eef6 100644 --- a/mozilla/mailnews/imap/src/nsImapMailFolder.cpp +++ b/mozilla/mailnews/imap/src/nsImapMailFolder.cpp @@ -7512,6 +7512,15 @@ nsImapMailFolder::OnMessageClassified(const char *aMsgURI, nsMsgJunkStatus aClas rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); NS_ENSURE_SUCCESS(rv, rv); + PRBool markAsReadOnSpam; + (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam); + if (markAsReadOnSpam) + { + if (!m_junkMessagesToMarkAsRead) + NS_NewISupportsArray(getter_AddRefs(m_junkMessagesToMarkAsRead)); + m_junkMessagesToMarkAsRead->AppendElement(msgHdr); + } + PRBool willMoveMessage = PR_FALSE; // don't do the move when we are opening up @@ -7562,6 +7571,17 @@ nsImapMailFolder::OnMessageClassified(const char *aMsgURI, nsMsgJunkStatus aClas } if (--m_numFilterClassifyRequests == 0) { + if (m_junkMessagesToMarkAsRead) + { + PRUint32 count; + m_junkMessagesToMarkAsRead->Count(&count); + if (count > 0) + { + rv = MarkMessagesRead(m_junkMessagesToMarkAsRead, true); + NS_ENSURE_SUCCESS(rv,rv); + m_junkMessagesToMarkAsRead->SizeTo(0); + } + } PlaybackCoalescedOperations(); // If we are performing biff for this folder, tell the server object if (m_performingBiff) diff --git a/mozilla/mailnews/imap/src/nsImapMailFolder.h b/mozilla/mailnews/imap/src/nsImapMailFolder.h index 75ccd61f475..91c3da538ff 100644 --- a/mozilla/mailnews/imap/src/nsImapMailFolder.h +++ b/mozilla/mailnews/imap/src/nsImapMailFolder.h @@ -447,6 +447,7 @@ protected: PRInt32 m_numFilterClassifyRequests; PRBool m_msgMovedByFilter; nsImapMoveCoalescer *m_moveCoalescer; // strictly owned by the nsImapMailFolder + nsCOMPtr m_junkMessagesToMarkAsRead; nsMsgKey m_curMsgUid; PRUint32 m_uidValidity; PRInt32 m_numStatusRecentMessages; // used to store counts from Status command diff --git a/mozilla/mailnews/local/src/nsLocalMailFolder.cpp b/mozilla/mailnews/local/src/nsLocalMailFolder.cpp index c5ea725492d..93dfc2d274b 100644 --- a/mozilla/mailnews/local/src/nsLocalMailFolder.cpp +++ b/mozilla/mailnews/local/src/nsLocalMailFolder.cpp @@ -3429,6 +3429,15 @@ nsMsgLocalMailFolder::OnMessageClassified(const char *aMsgURI, nsMsgJunkStatus a if (aClassification == nsIJunkMailPlugin::JUNK) { + PRBool markAsReadOnSpam; + (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam); + if (markAsReadOnSpam) + { + rv = mDatabase->MarkRead(msgKey, true, this); + if (!NS_SUCCEEDED(rv)) + NS_WARNING("failed marking spam message as read"); + } + PRBool willMoveMessage = PR_FALSE; // don't do the move when we are opening up diff --git a/mozilla/mailnews/mailnews.js b/mozilla/mailnews/mailnews.js index 9b7eb0ef1c2..e349e6587ed 100644 --- a/mozilla/mailnews/mailnews.js +++ b/mozilla/mailnews/mailnews.js @@ -378,6 +378,7 @@ pref("mail.server.default.use_idle", true); // for spam pref("mail.server.default.spamLevel",100); // 0 off, 100 on. not doing bool since we might have real levels one day. pref("mail.server.default.moveOnSpam",false); +pref("mail.server.default.markAsReadOnSpam",false); pref("mail.server.default.moveTargetMode",0); // 0 == "Junk" on server, 1 == specific folder pref("mail.server.default.spamActionTargetAccount",""); pref("mail.server.default.spamActionTargetFolder","");