Dactyloidae/mailnews/base/content/junkCommands.js

456 lines
15 KiB
JavaScript

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