FireTray/src/modules/FiretrayMessaging.jsm

430 lines
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
var EXPORTED_SYMBOLS = [ "firetray", "FLDRS_UNINTERESTING" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource:///modules/iteratorUtils.jsm");
Cu.import("resource:///modules/mailServices.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PluralForm.jsm");
Cu.import("resource://firetray/commons.js");
const FLDRS_UNINTERESTING = {
Archive: Ci.nsMsgFolderFlags.Archive,
Drafts: Ci.nsMsgFolderFlags.Drafts,
Junk: Ci.nsMsgFolderFlags.Junk,
Queue: Ci.nsMsgFolderFlags.Queue,
SentMail: Ci.nsMsgFolderFlags.SentMail,
Templates: Ci.nsMsgFolderFlags.Templates,
Trash: Ci.nsMsgFolderFlags.Trash,
Virtual: Ci.nsMsgFolderFlags.Virtual
};
const ACCOUNTS_PREF_BRANCH = "mail.accountmanager.accounts";
let log = firetray.Logging.getLogger("firetray.Messaging");
firetray.Messaging = {
initialized: false,
currentMsgCount: null,
newMsgCount: null,
init: function() {
if (this.initialized) {
log.warn("Messaging already initialized");
return;
}
log.debug("Enabling Messaging");
/* addPrefObserver() called after appStarted because it's hazardous to
clean our excludedAccounts while mail.accountmanager.accounts is being
built dynamically at startup */
let that = this;
MailServices.mailSession.AddFolderListener(that.mailSessionListener,
that.mailSessionListener.notificationFlags);
this.initialized = true;
},
shutdown: function() {
if (!this.initialized) return;
log.debug("Disabling Messaging");
MailServices.mailSession.RemoveFolderListener(this.mailSessionListener);
this.removePrefObserver();
this.initialized = false;
},
addPrefObserver: function() {
// includes IM accounts
Services.prefs.addObserver(ACCOUNTS_PREF_BRANCH, this, false);
log.debug("PrefObserver added");
},
removePrefObserver: function() {
Services.prefs.removeObserver(ACCOUNTS_PREF_BRANCH, this);
},
/* could also use a PrefListener, but let's keep it simple for now */
observe: function(subject, topic, data) {
if (topic === "nsPref:changed" &&
data === ACCOUNTS_PREF_BRANCH) {
log.debug(ACCOUNTS_PREF_BRANCH+"="+subject.QueryInterface(Ci.nsIPrefBranch).getCharPref(ACCOUNTS_PREF_BRANCH));
this.cleanExcludedAccounts();
}
},
/* removes removed accounts from excludedAccounts pref. NOTE: Can't be called
at shutdown because MailServices.accounts no longer available */
cleanExcludedAccounts: function() {
log.info("* cleaning *");
let mailAccounts = firetray.Utils.getObjPref('mail_accounts');
let excludedAccounts = mailAccounts["excludedAccounts"];
// build current list of account server keys
let accounts = MailServices.accounts.accounts;
let accountServerKeys = [];
for (let account in fixIterator(MailServices.accounts, Ci.nsIMsgAccount)) {
accountServerKeys.push(account.incomingServer.key);
}
let newExcludedAccounts = [], cleaningNeeded = 0;
for (let i=0, len=excludedAccounts.length; i<len; ++i) {
let excludedAccount = excludedAccounts[i];
if (accountServerKeys.indexOf(excludedAccount) >= 0)
newExcludedAccounts.push(excludedAccount);
else
cleaningNeeded += 1;
}
if (cleaningNeeded) {
log.debug("cleaning excluded accounts");
let prefObj = {"serverTypes":mailAccounts["serverTypes"], "excludedAccounts":newExcludedAccounts};
firetray.Utils.setObjPref('mail_accounts', prefObj);
}
},
// https://bugzilla.mozilla.org/show_bug.cgi?id=715799 for TB15+
// mozINewMailNotificationService (alternative message counting)
/* http://mxr.mozilla.org/comm-central/source/mailnews/base/public/nsIFolderListener.idl */
mailSessionListener: {
notificationFlags:
// Ci.nsIFolderListener.propertyChanged |
Ci.nsIFolderListener.propertyFlagChanged |
// Ci.nsIFolderListener.event |
Ci.nsIFolderListener.boolPropertyChanged |
Ci.nsIFolderListener.intPropertyChanged,
OnItemPropertyChanged: function(item, property, oldValue, newValue) { // NumNewBiffMessages
log.debug("OnItemPropertyChanged "+property+" for folder "+item.prettyName+" was "+oldValue+" became "+newValue+" NEW MESSAGES="+item.getNumNewMessages(true));
},
OnItemIntPropertyChanged: function(item, property, oldValue, newValue) { // TotalUnreadMessages, BiffState (per server)
log.debug("OnItemIntPropertyChanged "+property+" for folder "+item.prettyName+" was "+oldValue+" became "+newValue+" NEW MESSAGES="+item.getNumNewMessages(true));
this.onMsgCountChange(item, property, oldValue, newValue);
},
OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) { // NewMessages (per folder)
log.debug("OnItemBoolPropertyChanged "+property+" for folder "+item.prettyName+" was "+oldValue+" became "+newValue+" NEW MESSAGES="+item.getNumNewMessages(true));
this.onMsgCountChange(item, property, oldValue, newValue);
},
OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {
log.debug("OnItemPropertyFlagChanged"+property+" for "+item+" was "+oldFlag+" became "+newFlag);
this.onMsgCountChange(item, property, oldFlag, newFlag);
},
OnItemEvent: function(item, event) {
log.debug("OnItemEvent "+event+" for folder "+item.prettyName);
},
onMsgCountChange: function(item, property, oldValue, newValue) {
let excludedFoldersFlags = firetray.Utils.prefService.getIntPref("excluded_folders_flags");
let onlyFavorites = firetray.Utils.prefService.getBoolPref("only_favorite_folders");
let msgCountType = firetray.Utils.prefService.getIntPref("message_count_type");
if (!(item.flags & excludedFoldersFlags)) {
let prop = property.toString();
if (prop === "FolderFlag" && onlyFavorites) {
if ((oldValue ^ newValue) & Ci.nsMsgFolderFlags.Favorite)
firetray.Messaging.updateMsgCountWithCb();
} else if (prop === "TotalUnreadMessages" &&
msgCountType === FIRETRAY_MESSAGE_COUNT_TYPE_UNREAD) {
firetray.Messaging.updateMsgCountWithCb();
} else if (prop === "NewMessages" &&
msgCountType === FIRETRAY_MESSAGE_COUNT_TYPE_NEW) {
if (oldValue === true && newValue === false)
item.setNumNewMessages(0); // https://bugzilla.mozilla.org/show_bug.cgi?id=727460
firetray.Messaging.updateMsgCountWithCb();
}
}
}
},
/**
* @param callback: optional callback to call when msgCount changed.
*/
updateMsgCountWithCb: function(callback) {
log.debug("updateMsgCountWithCb");
if (!this.initialized) return;
if ("undefined" === typeof(callback) || !callback)
callback = function(currentMsgCount, newMsgCount) { // default
firetray.Messaging.updateIcon(newMsgCount);
if (newMsgCount !== currentMsgCount) {
let mailChangeTriggerFile = firetray.Utils.prefService.getCharPref("mail_change_trigger");
if (mailChangeTriggerFile)
firetray.Messaging.runProcess(mailChangeTriggerFile, [newMsgCount.toString()]);
let setUrgency = firetray.Utils.prefService.getBoolPref("mail_urgency_hint");
if (setUrgency && (newMsgCount > currentMsgCount))
for (let winId in firetray.Handler.windows)
firetray.Window.setUrgency(winId, true);
}
};
let msgCountType = firetray.Utils.prefService.getIntPref("message_count_type");
log.debug("msgCountType="+msgCountType);
if (msgCountType === FIRETRAY_MESSAGE_COUNT_TYPE_UNREAD) {
this.countMessages("UnreadMessages");
} else if (msgCountType === FIRETRAY_MESSAGE_COUNT_TYPE_NEW) {
this.countMessages("HasNewMessages");
} else
log.error('unknown message count type');
/* currentMsgCount and newMsgCount may be integers or bool, which do
also support comparaison operations */
callback.call(this, this.currentMsgCount, this.newMsgCount);
this.currentMsgCount = this.newMsgCount;
},
updateIcon: function(msgCount) {
if ("undefined" === typeof(msgCount)) msgCount = this.currentMsgCount;
let localizedTooltip;
let msgCountType = firetray.Utils.prefService.getIntPref("message_count_type");
if (msgCountType === FIRETRAY_MESSAGE_COUNT_TYPE_UNREAD) {
localizedTooltip = PluralForm.get(
msgCount,
firetray.Utils.strings.GetStringFromName("tooltip.unread_messages"))
.replace("#1", msgCount);
log.debug(localizedTooltip);
} else if (msgCountType === FIRETRAY_MESSAGE_COUNT_TYPE_NEW) {
localizedTooltip = firetray.Utils.strings.GetStringFromName("tooltip.new_messages");
} else
log.error('unknown message count type');
if (msgCount == 0) {
firetray.Handler.setIconImageDefault();
firetray.Handler.setIconTooltipDefault();
} else if (msgCount > 0) {
let prefMailNotification = firetray.Utils.prefService.getIntPref('mail_notification_type');
switch (prefMailNotification) {
case FIRETRAY_NOTIFICATION_MESSAGE_COUNT:
let prefIconTextColor = firetray.Utils.prefService.getCharPref("icon_text_color");
firetray.Handler.setIconText(msgCount.toString(), prefIconTextColor);
break;
case FIRETRAY_NOTIFICATION_NEWMAIL_ICON:
firetray.Handler.setIconImageNewMail();
break;
case FIRETRAY_NOTIFICATION_CUSTOM_ICON:
let prefCustomIconPath = firetray.Utils.prefService.getCharPref("custom_mail_icon");
firetray.Handler.setIconImageFromFile(prefCustomIconPath);
break;
default:
log.error("Unknown notification mode: "+prefMailNotification);
}
firetray.Handler.setIconTooltip(localizedTooltip);
} else {
throw "negative message count"; // should never happen
}
},
/**
* computes total unread or new message count.
*/
countMessages: function(countType) {
let mailAccounts = firetray.Utils.getObjPref('mail_accounts');
log.debug("mail accounts from pref: "+JSON.stringify(mailAccounts));
let serverTypes = mailAccounts["serverTypes"];
let excludedAccounts = mailAccounts["excludedAccounts"];
this.newMsgCount = 0;
let accounts = new this.Accounts();
for (let accountServer in accounts) { // nsIMsgIncomingServer
if (accountServer.type === FIRETRAY_ACCOUNT_SERVER_TYPE_IM) {
continue; // IM messages are counted elsewhere
} else if (!serverTypes[accountServer.type]) {
log.warn("'"+accountServer.type+"' server type is not handled");
continue;
}
log.debug("is servertype excluded: "+serverTypes[accountServer.type].excluded+", account exclusion index: "+excludedAccounts.indexOf(accountServer.key));
if ((serverTypes[accountServer.type].excluded) ||
(excludedAccounts.indexOf(accountServer.key) >= 0))
continue;
this.applyToSubfolders(
accountServer.rootFolder,
firetray.Utils.prefService.getBoolPref("folder_count_recursive"),
function(folder){this.msgCountIterate(countType, folder);}
);
}
log.debug("Total "+countType+"="+this.newMsgCount);
},
/**
* @param folder: a nsIMsgFolder
* @param recursive: if we should look into nested folders
* @param fun: a function to apply to all folders
*/
applyToSubfolders: function(folder, recursive, fun) {
if (folder.hasSubFolders) {
let subFolders = folder.subFolders;
while(subFolders.hasMoreElements()) {
let subFolder = subFolders.getNext().QueryInterface(Ci.nsIMsgFolder);
if (recursive && subFolder.hasSubFolders)
firetray.Messaging.applyToSubfoldersRecursive(subFolder, recursive, fun);
else
fun.call(this, subFolder);
}
}
},
applyToSubfoldersRecursive: function(folder, recursive, fun) {
fun.call(this, folder);
this.applyToSubfolders(folder, recursive, fun);
},
/**
* @param type: one of 'UnreadMessages', 'HasNewMessages'
*/
msgCountIterate: function(type, folder) {
let onlyFavorites = firetray.Utils.prefService.getBoolPref("only_favorite_folders");
let excludedFoldersFlags = firetray.Utils.prefService.getIntPref("excluded_folders_flags");
if (!(folder.flags & excludedFoldersFlags)) {
if (!onlyFavorites || folder.flags & Ci.nsMsgFolderFlags.Favorite) {
firetray.Messaging["add"+type](folder);
}
}
log.debug("newMsgCount="+this.newMsgCount);
},
addUnreadMessages: function(folder) {
let folderUnreadMsgCount = folder['getNumUnread'](false);
log.debug("folder: "+folder.prettyName+" folderUnreadMsgCount="+folderUnreadMsgCount);
/* nsMsgDBFolder::GetNumUnread basically returns mNumUnreadMessages +
mNumPendingUnreadMessages, while mNumPendingUnreadMessages may get -1
when updated from the cache. Which means getNumUnread might return -1. */
if (folderUnreadMsgCount > 0)
this.newMsgCount += folderUnreadMsgCount;
},
addHasNewMessages: function(folder) {
let folderNewMsgCount = folder.hasNewMessages;
log.debug("folder: "+folder.prettyName+" hasNewMessages="+folderNewMsgCount);
this.newMsgCount = this.newMsgCount || folderNewMsgCount;
},
runProcess: function(filepath, args) {
log.debug("runProcess="+filepath+" args="+args);
try {
// create a file for the process
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(filepath);
// create the process
var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(file);
process.run(false, args, args.length);
} catch(x) {log.error(x);}
}
};
/**
* Accounts Iterator/Generator for iterating over all account servers.
* NOTE: MailServices.accounts.allServers exludes hidden and IM servers
* @param sortByTypeAndName: boolean
* @return a generator over all nsIMsgIncomingServer including hidden and IM ones
*/
firetray.Messaging.Accounts = function(sortByTypeAndName) {
if (typeof(sortByTypeAndName) == "undefined") {
this.sortByTypeAndName = false;
return;
}
if (typeof(sortByTypeAndName) !== "boolean")
throw new TypeError();
this.sortByTypeAndName = sortByTypeAndName;
};
firetray.Messaging.Accounts.prototype.__iterator__ = function() {
log.debug("sortByTypeAndName="+this.sortByTypeAndName);
/* NOTE: sort() not provided by nsIMsgAccountManager.accounts
(nsISupportsArray or nsIArray if xulrunner >= 20.0). Should be OK to
re-build a JS-Array for few accounts */
let accountServers = [];
for (let accountServer in fixIterator(MailServices.accounts.accounts,
Ci.nsIMsgAccount)) {
accountServers.push(accountServer.incomingServer);
}
let mailAccounts = firetray.Utils.getObjPref('mail_accounts');
let serverTypes = mailAccounts["serverTypes"];
if (this.sortByTypeAndName) {
accountServers.sort(function(a,b) {
if (serverTypes[a.type].order
< serverTypes[b.type].order)
return -1;
if (serverTypes[a.type].order
> serverTypes[b.type].order)
return 1;
if (a.prettyName < b.prettyName)
return -1;
if (a.prettyName > b.prettyName)
return 1;
return 0; // no sorting
});
}
for (let i=0, len=accountServers.length; i<len; ++i) {
log.debug("ACCOUNT: "+accountServers[i].prettyName+" type: "+accountServers[i].type);
yield accountServers[i];
}
};
/**
* return accounts grouped by mail_accounts.
*
* ex: { movemail: {"server1", "server2"}, imap: {"server3"} }
*/
firetray.Messaging.accountsByServerType = function() {
let accountsByServerType = {};
let accounts = new firetray.Messaging.Accounts(false);
for (let accountServer in accounts) {
let accountServerKey = accountServer.key.toString();
let accountServerName = accountServer.prettyName;
let accountServerType = accountServer.type;
if (typeof(accountsByServerType[accountServerType]) == "undefined")
accountsByServerType[accountServerType] = [];
accountsByServerType[accountServerType].push(
{ key: accountServerKey, name: accountServerName });
}
return accountsByServerType;
};