diff options
Diffstat (limited to 'mailnews/base/content/junkCommands.js')
-rw-r--r-- | mailnews/base/content/junkCommands.js | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/mailnews/base/content/junkCommands.js b/mailnews/base/content/junkCommands.js new file mode 100644 index 000000000..6332d193f --- /dev/null +++ b/mailnews/base/content/junkCommands.js @@ -0,0 +1,456 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + /* functions use for junk processing commands + * + * TODO: These functions make the false assumption that a view only contains + * a single folder. This is not true for XF saved searches. + * + * globals prerequisites used: + * + * window.MsgStatusFeedback + * + * One of: + * GetSelectedIndices(view) (in suite) + * gFolderDisplay (in mail) + * + * messenger + * gMessengerBundle + * gDBView + * either gMsgFolderSelected or gFolderDisplay + * MsgJunkMailInfo(aCheckFirstUse) + * SetNextMessageAfterDelete() + * pref + * msgWindow + */ + +Components.utils.import("resource:///modules/mailServices.js"); +Components.utils.import("resource:///modules/MailUtils.js"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +/* + * determineActionsForJunkMsgs + * + * Determines the actions that should be carried out on the messages + * that are being marked as junk + * + * @param aFolder + * the folder with messages being marked as junk + * + * @return an object with two properties: 'markRead' (boolean) indicating + * whether the messages should be marked as read, and 'junkTargetFolder' + * (nsIMsgFolder) specifying where the messages should be moved, or + * null if they should not be moved. + */ +function determineActionsForJunkMsgs(aFolder) +{ + var actions = { markRead: false, junkTargetFolder: null }; + var spamSettings = aFolder.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 + + actions.markRead = spamSettings.markAsReadOnSpam; + actions.junkTargetFolder = null; + + // move only when the corresponding setting is activated + // and the currently viewed folder is not the junk folder. + if (spamSettings.moveOnSpam && + !(aFolder.flags & Components.interfaces.nsMsgFolderFlags.Junk)) + { + var spamFolderURI = spamSettings.spamFolderURI; + if (!spamFolderURI) + { + // XXX TODO + // we should use nsIPromptService to inform the user of the problem, + // e.g. when the junk folder was accidentally deleted. + dump('determineActionsForJunkMsgs: no spam folder found, not moving.'); + } + else + actions.junkTargetFolder = MailUtils.getFolderForURI(spamFolderURI); + } + + return actions; +} + +/** + * performActionsOnJunkMsgs + * + * Performs required operations on a list of newly-classified junk messages + * + * @param aFolder + * the folder with messages being marked as junk + * + * @param aJunkMsgHdrs + * nsIArray containing headers (nsIMsgDBHdr) of new junk messages + * + * @param aGoodMsgHdrs + * nsIArray containing headers (nsIMsgDBHdr) of new good messages + */ + function performActionsOnJunkMsgs(aFolder, aJunkMsgHdrs, aGoodMsgHdrs) +{ + if (aFolder instanceof Components.interfaces.nsIMsgImapMailFolder) // need to update IMAP custom flags + { + if (aJunkMsgHdrs.length) + { + var junkMsgKeys = new Array(); + for (var i = 0; i < aJunkMsgHdrs.length; i++) + junkMsgKeys[i] = aJunkMsgHdrs.queryElementAt(i, Components.interfaces.nsIMsgDBHdr).messageKey; + aFolder.storeCustomKeywords(null, "Junk", "NonJunk", junkMsgKeys, junkMsgKeys.length); + } + + if (aGoodMsgHdrs.length) + { + var goodMsgKeys = new Array(); + for (var i = 0; i < aGoodMsgHdrs.length; i++) + goodMsgKeys[i] = aGoodMsgHdrs.queryElementAt(i, Components.interfaces.nsIMsgDBHdr).messageKey; + aFolder.storeCustomKeywords(null, "NonJunk", "Junk", goodMsgKeys, goodMsgKeys.length); + } + } + + if (aJunkMsgHdrs.length) + { + var actionParams = determineActionsForJunkMsgs(aFolder); + if (actionParams.markRead) + aFolder.markMessagesRead(aJunkMsgHdrs, true); + + if (actionParams.junkTargetFolder) + MailServices.copy + .CopyMessages(aFolder, aJunkMsgHdrs, actionParams.junkTargetFolder, + true /* isMove */, null, msgWindow, true /* allow undo */); + } +} + +/** + * MessageClassifier + * + * Helper object storing the list of pending messages to process, + * and implementing junk processing callback + * + * @param aFolder + * the folder with messages to be analyzed for junk + * @param aTotalMessages + * Number of messages to process, used for progress report only + */ + +function MessageClassifier(aFolder, aTotalMessages) +{ + this.mFolder = aFolder; + this.mJunkMsgHdrs = Components.classes["@mozilla.org/array;1"] + .createInstance(Components.interfaces.nsIMutableArray); + this.mGoodMsgHdrs = Components.classes["@mozilla.org/array;1"] + .createInstance(Components.interfaces.nsIMutableArray); + this.mMessages = new Object(); + this.mMessageQueue = new Array(); + this.mTotalMessages = aTotalMessages; + this.mProcessedMessages = 0; + this.firstMessage = true; + this.lastStatusTime = Date.now(); +} + +MessageClassifier.prototype = +{ + /** + * analyzeMessage + * + * Starts the message classification process for a message. If the message + * sender's address is whitelisted, the message is skipped. + * + * @param aMsgHdr + * The header (nsIMsgDBHdr) of the message to classify. + * @param aSpamSettings + * nsISpamSettings object with information about whitelists + */ + analyzeMessage: function(aMsgHdr, aSpamSettings) + { + var junkscoreorigin = aMsgHdr.getStringProperty("junkscoreorigin"); + if (junkscoreorigin == "user") // don't override user-set junk status + return; + + // check whitelisting + if (aSpamSettings.checkWhiteList(aMsgHdr)) + { + // message is ham from whitelist + var db = aMsgHdr.folder.msgDatabase; + db.setStringProperty(aMsgHdr.messageKey, "junkscore", + Components.interfaces.nsIJunkMailPlugin.IS_HAM_SCORE); + db.setStringProperty(aMsgHdr.messageKey, "junkscoreorigin", "whitelist"); + this.mGoodMsgHdrs.appendElement(aMsgHdr, false); + return; + } + + var messageURI = aMsgHdr.folder.generateMessageURI(aMsgHdr.messageKey) + "?fetchCompleteMessage=true"; + this.mMessages[messageURI] = aMsgHdr; + if (this.firstMessage) + { + this.firstMessage = false; + MailServices.junk.classifyMessage(messageURI, msgWindow, this); + } + else + this.mMessageQueue.push(messageURI); + }, + + /* + * nsIJunkMailClassificationListener implementation + * onMessageClassified + * + * Callback function from nsIJunkMailPlugin with classification results + * + * @param aClassifiedMsgURI + * URI of classified message + * @param aClassification + * Junk classification (0: UNCLASSIFIED, 1: GOOD, 2: JUNK) + * @param aJunkPercent + * 0 - 100 indicator of junk likelihood, with 100 meaning probably junk + */ + onMessageClassified: function(aClassifiedMsgURI, aClassification, aJunkPercent) + { + if (!aClassifiedMsgURI) + return; // ignore end of batch + var nsIJunkMailPlugin = Components.interfaces.nsIJunkMailPlugin; + var score = (aClassification == nsIJunkMailPlugin.JUNK) ? + nsIJunkMailPlugin.IS_SPAM_SCORE : nsIJunkMailPlugin.IS_HAM_SCORE; + const statusDisplayInterval = 1000; // milliseconds between status updates + + // set these props via the db (instead of the message header + // directly) so that the nsMsgDBView knows to update the UI + // + var msgHdr = this.mMessages[aClassifiedMsgURI]; + var db = msgHdr.folder.msgDatabase; + db.setStringProperty(msgHdr.messageKey, "junkscore", score); + db.setStringProperty(msgHdr.messageKey, "junkscoreorigin", "plugin"); + db.setStringProperty(msgHdr.messageKey, "junkpercent", aJunkPercent); + + if (aClassification == nsIJunkMailPlugin.JUNK) + this.mJunkMsgHdrs.appendElement(msgHdr, false); + else if (aClassification == nsIJunkMailPlugin.GOOD) + this.mGoodMsgHdrs.appendElement(msgHdr, false); + + var nextMsgURI = this.mMessageQueue.shift(); + if (nextMsgURI) + { + ++this.mProcessedMessages; + if (Date.now() > this.lastStatusTime + statusDisplayInterval) + { + this.lastStatusTime = Date.now(); + var percentDone = 0; + if (this.mTotalMessages) + percentDone = Math.round(this.mProcessedMessages * 100 / this.mTotalMessages); + var percentStr = percentDone + "%"; + window.MsgStatusFeedback.showStatusString( + document.getElementById("bundle_messenger") + .getFormattedString("junkAnalysisPercentComplete", + [percentStr])); + } + + MailServices.junk.classifyMessage(nextMsgURI, msgWindow, this); + } + else + { + window.MsgStatusFeedback.showStatusString( + document.getElementById("bundle_messenger") + .getString("processingJunkMessages")); + performActionsOnJunkMsgs(this.mFolder, this.mJunkMsgHdrs, this.mGoodMsgHdrs); + window.MsgStatusFeedback.showStatusString(""); + } + } +} + +/* + * filterFolderForJunk + * + * Filter all messages in the current folder for junk + */ +function filterFolderForJunk() { processFolderForJunk(true); } + +/* + * analyzeMessagesForJunk + * + * Filter selected messages in the current folder for junk + */ +function analyzeMessagesForJunk() { processFolderForJunk(false); } + +/* + * processFolderForJunk + * + * Filter messages in the current folder for junk + * + * @param aAll: true to filter all messages, else filter selection + */ +function processFolderForJunk(aAll) +{ + MsgJunkMailInfo(true); + + if (aAll) + { + // need to expand all threads, so we analyze everything + gDBView.doCommand(nsMsgViewCommandType.expandAll); + var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView); + var count = treeView.rowCount; + if (!count) + return; + } + else + { + // suite uses GetSelectedIndices, mail uses gFolderDisplay.selectedMessages + var indices = typeof GetSelectedIndices != "undefined" ? + GetSelectedIndices(gDBView) : + gFolderDisplay.selectedIndices; + if (!indices || !indices.length) + return; + } + var totalMessages = aAll ? count : indices.length; + + // retrieve server and its spam settings via the header of an arbitrary message + for (var i = 0; i < totalMessages; i++) + { + var index = aAll ? i : indices[i]; + try + { + var tmpMsgURI = gDBView.getURIForViewIndex(index); + break; + } + catch (e) + { + // dummy headers will fail, so look for another + continue; + } + } + if (!tmpMsgURI) + return; + + var tmpMsgHdr = messenger.messageServiceFromURI(tmpMsgURI).messageURIToMsgHdr(tmpMsgURI); + var spamSettings = tmpMsgHdr.folder.server.spamSettings; + + // create a classifier instance to classify messages in the folder. + var msgClassifier = new MessageClassifier(tmpMsgHdr.folder, totalMessages); + + for ( i = 0; i < totalMessages; i++) + { + var index = aAll ? i : indices[i]; + try + { + var msgURI = gDBView.getURIForViewIndex(index); + var msgHdr = messenger.messageServiceFromURI(msgURI).messageURIToMsgHdr(msgURI); + msgClassifier.analyzeMessage(msgHdr, spamSettings); + } + catch (ex) + { + // blow off errors here - dummy headers will fail + var msgURI = null; + } + } + if (msgClassifier.firstMessage) // the async plugin was not used, maybe all whitelisted? + performActionsOnJunkMsgs(msgClassifier.mFolder, + msgClassifier.mJunkMsgHdrs, + msgClassifier.mGoodMsgHdrs); +} + +function JunkSelectedMessages(setAsJunk) +{ + MsgJunkMailInfo(true); + + // When the user explicitly marks a message as junk, we can mark it as read, + // too. This is independent of the "markAsReadOnSpam" pref, which applies + // only to automatically-classified messages. + // Note that this behaviour should match the one in the back end for marking + // as junk via clicking the 'junk' column. + + if (setAsJunk && Services.prefs.getBoolPref("mailnews.ui.junk.manualMarkAsJunkMarksRead")) + MarkSelectedMessagesRead(true); + + gDBView.doCommand(setAsJunk ? nsMsgViewCommandType.junk + : nsMsgViewCommandType.unjunk); +} + +/** + * Delete junk messages in the current folder. This provides the guarantee that + * the method will be synchronous if no messages are deleted. + * + * @returns The number of messages deleted. + */ +function deleteJunkInFolder() +{ + MsgJunkMailInfo(true); + + // use direct folder commands if possible so we don't mess with the selection + let selectedFolder = gFolderDisplay.displayedFolder; + if ( !(selectedFolder.flags & Components.interfaces.nsMsgFolderFlags.Virtual) ) + { + var junkMsgHdrs = Components.classes["@mozilla.org/array;1"] + .createInstance(Components.interfaces.nsIMutableArray); + var enumerator = gDBView.msgFolder.messages; + while (enumerator.hasMoreElements()) + { + var msgHdr = enumerator.getNext().QueryInterface(Components.interfaces.nsIMsgDBHdr); + var junkScore = msgHdr.getStringProperty("junkscore"); + if (junkScore == Components.interfaces.nsIJunkMailPlugin.IS_SPAM_SCORE) + junkMsgHdrs.appendElement(msgHdr, false); + } + + if (junkMsgHdrs.length) + gDBView.msgFolder.deleteMessages(junkMsgHdrs, msgWindow, false, false, null, true); + return junkMsgHdrs.length; + } + + // Folder is virtual, let the view do the work (but we lose selection) + + // need to expand all threads, so we find everything + gDBView.doCommand(nsMsgViewCommandType.expandAll); + + var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView); + var count = treeView.rowCount; + if (!count) + return 0; + + var treeSelection = treeView.selection; + + var clearedSelection = false; + + // select the junk messages + var messageUri; + let numMessagesDeleted = 0; + for (var i = 0; i < count; ++i) + { + try { + messageUri = gDBView.getURIForViewIndex(i); + } + catch (ex) {continue;} // blow off errors for dummy rows + var msgHdr = messenger.messageServiceFromURI(messageUri).messageURIToMsgHdr(messageUri); + var junkScore = msgHdr.getStringProperty("junkscore"); + var isJunk = (junkScore == Components.interfaces.nsIJunkMailPlugin.IS_SPAM_SCORE); + // if the message is junk, select it. + if (isJunk) + { + // only do this once + if (!clearedSelection) + { + // clear the current selection + // since we will be deleting all selected messages + treeSelection.clearSelection(); + clearedSelection = true; + treeSelection.selectEventsSuppressed = true; + } + treeSelection.rangedSelect(i, i, true /* augment */); + numMessagesDeleted++; + } + } + + // if we didn't clear the selection + // there was no junk, so bail. + if (!clearedSelection) + return 0; + + treeSelection.selectEventsSuppressed = false; + // delete the selected messages + // + // We'll leave no selection after the delete + gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None; + gDBView.doCommand(nsMsgViewCommandType.deleteMsg); + treeSelection.clearSelection(); + ClearMessagePane(); + return numMessagesDeleted; +} + |