summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/activity/modules
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/activity/modules')
-rw-r--r--comm/mail/components/activity/modules/activityModules.jsm33
-rw-r--r--comm/mail/components/activity/modules/alertHook.jsm101
-rw-r--r--comm/mail/components/activity/modules/autosync.jsm433
-rw-r--r--comm/mail/components/activity/modules/glodaIndexer.jsm251
-rw-r--r--comm/mail/components/activity/modules/moveCopy.jsm396
-rw-r--r--comm/mail/components/activity/modules/pop3Download.jsm154
-rw-r--r--comm/mail/components/activity/modules/sendLater.jsm298
7 files changed, 1666 insertions, 0 deletions
diff --git a/comm/mail/components/activity/modules/activityModules.jsm b/comm/mail/components/activity/modules/activityModules.jsm
new file mode 100644
index 0000000000..945f7473c2
--- /dev/null
+++ b/comm/mail/components/activity/modules/activityModules.jsm
@@ -0,0 +1,33 @@
+/* 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/. */
+
+// This module is designed to be a central place to initialise activity related
+// modules.
+
+const EXPORTED_SYMBOLS = [];
+
+const { sendLaterModule } = ChromeUtils.import(
+ "resource:///modules/activity/sendLater.jsm"
+);
+sendLaterModule.init();
+const { moveCopyModule } = ChromeUtils.import(
+ "resource:///modules/activity/moveCopy.jsm"
+);
+moveCopyModule.init();
+const { glodaIndexerActivity } = ChromeUtils.import(
+ "resource:///modules/activity/glodaIndexer.jsm"
+);
+glodaIndexerActivity.init();
+const { autosyncModule } = ChromeUtils.import(
+ "resource:///modules/activity/autosync.jsm"
+);
+autosyncModule.init();
+const { alertHook } = ChromeUtils.import(
+ "resource:///modules/activity/alertHook.jsm"
+);
+alertHook.init();
+const { pop3DownloadModule } = ChromeUtils.import(
+ "resource:///modules/activity/pop3Download.jsm"
+);
+pop3DownloadModule.init();
diff --git a/comm/mail/components/activity/modules/alertHook.jsm b/comm/mail/components/activity/modules/alertHook.jsm
new file mode 100644
index 0000000000..b3083aef0a
--- /dev/null
+++ b/comm/mail/components/activity/modules/alertHook.jsm
@@ -0,0 +1,101 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["alertHook"];
+
+var nsActWarning = Components.Constructor(
+ "@mozilla.org/activity-warning;1",
+ "nsIActivityWarning",
+ "init"
+);
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// This module provides a link between the send later service and the activity
+// manager.
+var alertHook = {
+ get activityMgr() {
+ delete this.activityMgr;
+ return (this.activityMgr = Cc["@mozilla.org/activity-manager;1"].getService(
+ Ci.nsIActivityManager
+ ));
+ },
+
+ get alertService() {
+ delete this.alertService;
+ return (this.alertService = Cc["@mozilla.org/alerts-service;1"].getService(
+ Ci.nsIAlertsService
+ ));
+ },
+
+ get brandShortName() {
+ delete this.brandShortName;
+ return (this.brandShortName = Services.strings
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandShortName"));
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgUserFeedbackListener"]),
+
+ onAlert(aMessage, aUrl) {
+ // Create a new warning.
+ let warning = new nsActWarning(aMessage, this.activityMgr, "");
+
+ if (aUrl && aUrl.server && aUrl.server.prettyName) {
+ warning.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_BYCONTEXT;
+ warning.contextType = "incomingServer";
+ warning.contextDisplayText = aUrl.server.prettyName;
+ warning.contextObj = aUrl.server;
+ } else {
+ warning.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_STANDALONE;
+ }
+
+ this.activityMgr.addActivity(warning);
+
+ // If we have a message window in the url, then show a warning prompt,
+ // just like the modal code used to. Otherwise, don't.
+ try {
+ if (!aUrl || !aUrl.msgWindow) {
+ return true;
+ }
+ } catch (ex) {
+ // nsIMsgMailNewsUrl.msgWindow will throw on a null pointer, so that's
+ // what we're handling here.
+ if (
+ ex instanceof Ci.nsIException &&
+ ex.result == Cr.NS_ERROR_INVALID_POINTER
+ ) {
+ return true;
+ }
+ throw ex;
+ }
+
+ try {
+ let alert = Cc["@mozilla.org/alert-notification;1"].createInstance(
+ Ci.nsIAlertNotification
+ );
+ alert.init(
+ "", // name
+ "chrome://branding/content/icon48.png",
+ this.brandShortName,
+ aMessage
+ );
+ this.alertService.showAlert(alert);
+ } catch (ex) {
+ // XXX On Linux, if libnotify isn't supported, showAlert
+ // can throw an error, so fall-back to the old method of modal dialogs.
+ return false;
+ }
+
+ return true;
+ },
+
+ init() {
+ // We shouldn't need to remove the listener as we're not being held by
+ // anyone except by the send later instance.
+ MailServices.mailSession.addUserFeedbackListener(this);
+ },
+};
diff --git a/comm/mail/components/activity/modules/autosync.jsm b/comm/mail/components/activity/modules/autosync.jsm
new file mode 100644
index 0000000000..c2483c4b53
--- /dev/null
+++ b/comm/mail/components/activity/modules/autosync.jsm
@@ -0,0 +1,433 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["autosyncModule"];
+
+var nsActProcess = Components.Constructor(
+ "@mozilla.org/activity-process;1",
+ "nsIActivityProcess",
+ "init"
+);
+var nsActEvent = Components.Constructor(
+ "@mozilla.org/activity-event;1",
+ "nsIActivityEvent",
+ "init"
+);
+
+/**
+ * This code aims to mediate between the auto-sync code and the activity mgr.
+ *
+ * Not every auto-sync activity is directly mapped to a process or event.
+ * To prevent a possible event overflow, Auto-Sync monitor generates one
+ * sync'd event per account when after all its _pending_ folders are sync'd,
+ * rather than generating one event per folder sync.
+ */
+
+var autosyncModule = {
+ _inQFolderList: [],
+ _running: false,
+ _syncInfoPerFolder: new Map(),
+ _syncInfoPerServer: new Map(),
+ _lastMessage: new Map(),
+
+ get log() {
+ delete this.log;
+ return (this.log = console.createInstance({
+ prefix: "mail.activity",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.activity.loglevel",
+ }));
+ },
+
+ get activityMgr() {
+ delete this.activityMgr;
+ return (this.activityMgr = Cc["@mozilla.org/activity-manager;1"].getService(
+ Ci.nsIActivityManager
+ ));
+ },
+
+ get autoSyncManager() {
+ delete this.autoSyncManager;
+ return (this.autoSyncManager = Cc[
+ "@mozilla.org/imap/autosyncmgr;1"
+ ].getService(Ci.nsIAutoSyncManager));
+ },
+
+ get bundle() {
+ delete this.bundle;
+ return (this.bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/activity.properties"
+ ));
+ },
+
+ getString(stringName) {
+ try {
+ return this.bundle.GetStringFromName(stringName);
+ } catch (e) {
+ this.log.error("error trying to get a string called: " + stringName);
+ throw e;
+ }
+ },
+
+ createSyncMailProcess(folder) {
+ try {
+ // create an activity process for this folder
+ let msg = this.bundle.formatStringFromName("autosyncProcessDisplayText", [
+ folder.prettyName,
+ ]);
+ let process = new nsActProcess(msg, this.autoSyncManager);
+ // we want to use default auto-sync icon
+ process.iconClass = "syncMail";
+ process.addSubject(folder);
+ // group processes under folder's imap account
+ process.contextType = "account";
+ process.contextDisplayText = this.bundle.formatStringFromName(
+ "autosyncContextDisplayText",
+ [folder.server.prettyName]
+ );
+
+ process.contextObj = folder.server;
+
+ return process;
+ } catch (e) {
+ this.log.error("createSyncMailProcess: " + e);
+ throw e;
+ }
+ },
+
+ createSyncMailEvent(syncItem) {
+ try {
+ // extract the relevant parts
+ let process = syncItem.activity;
+ let folder = syncItem.syncFolder;
+
+ // create an activity event
+
+ let msg = this.bundle.formatStringFromName("autosyncEventDisplayText", [
+ folder.server.prettyName,
+ ]);
+
+ let statusMsg;
+ let numOfMessages = this._syncInfoPerServer.get(
+ folder.server
+ ).totalDownloads;
+ if (numOfMessages) {
+ statusMsg = this.bundle.formatStringFromName(
+ "autosyncEventStatusText",
+ [numOfMessages]
+ );
+ } else {
+ statusMsg = this.getString("autosyncEventStatusTextNoMsgs");
+ }
+
+ let event = new nsActEvent(
+ msg,
+ this.autoSyncManager,
+ statusMsg,
+ this._syncInfoPerServer.get(folder.server).startTime,
+ Date.now()
+ ); // completion time
+
+ // since auto-sync events do not have undo option by nature,
+ // setting these values are informational only.
+ event.contextType = process.contextType;
+ event.contextDisplayText = this.bundle.formatStringFromName(
+ "autosyncContextDisplayText",
+ [folder.server.prettyName]
+ );
+ event.contextObj = process.contextObj;
+ event.iconClass = "syncMail";
+
+ // transfer all subjects.
+ // same as above, not mandatory
+ let subjects = process.getSubjects();
+ for (let subject of subjects) {
+ event.addSubject(subject);
+ }
+
+ return event;
+ } catch (e) {
+ this.log.error("createSyncMailEvent: " + e);
+ throw e;
+ }
+ },
+
+ onStateChanged(running) {
+ try {
+ this._running = running;
+ this.log.info(
+ "OnStatusChanged: " + (running ? "running" : "sleeping") + "\n"
+ );
+ } catch (e) {
+ this.log.error("onStateChanged: " + e);
+ throw e;
+ }
+ },
+
+ onFolderAddedIntoQ(queue, folder) {
+ try {
+ if (
+ folder instanceof Ci.nsIMsgFolder &&
+ queue == Ci.nsIAutoSyncMgrListener.PriorityQueue
+ ) {
+ this._inQFolderList.push(folder);
+ this.log.info(
+ "Auto_Sync OnFolderAddedIntoQ [" +
+ this._inQFolderList.length +
+ "] " +
+ folder.prettyName +
+ " of " +
+ folder.server.prettyName
+ );
+ // create an activity process for this folder
+ let process = this.createSyncMailProcess(folder);
+
+ // create a sync object to keep track of the process of this folder
+ let imapFolder = folder.QueryInterface(Ci.nsIMsgImapMailFolder);
+ let syncItem = {
+ syncFolder: folder,
+ activity: process,
+ percentComplete: 0,
+ totalDownloaded: 0,
+ pendingMsgCount: imapFolder.autoSyncStateObj.pendingMessageCount,
+ };
+
+ // if this is the first folder of this server in the queue, then set the sync start time
+ // for activity event
+ if (!this._syncInfoPerServer.has(folder.server)) {
+ this._syncInfoPerServer.set(folder.server, {
+ startTime: Date.now(),
+ totalDownloads: 0,
+ });
+ }
+
+ // associate the sync object with the folder in question
+ // use folder.URI as key
+ this._syncInfoPerFolder.set(folder.URI, syncItem);
+ }
+ } catch (e) {
+ this.log.error("onFolderAddedIntoQ: " + e);
+ throw e;
+ }
+ },
+ onFolderRemovedFromQ(queue, folder) {
+ try {
+ if (
+ folder instanceof Ci.nsIMsgFolder &&
+ queue == Ci.nsIAutoSyncMgrListener.PriorityQueue
+ ) {
+ let i = this._inQFolderList.indexOf(folder);
+ if (i > -1) {
+ this._inQFolderList.splice(i, 1);
+ }
+
+ this.log.info(
+ "OnFolderRemovedFromQ [" +
+ this._inQFolderList.length +
+ "] " +
+ folder.prettyName +
+ " of " +
+ folder.server.prettyName +
+ "\n"
+ );
+
+ let syncItem = this._syncInfoPerFolder.get(folder.URI);
+ let process = syncItem.activity;
+ let canceled = false;
+ if (process instanceof Ci.nsIActivityProcess) {
+ canceled = process.state == Ci.nsIActivityProcess.STATE_CANCELED;
+ process.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+
+ try {
+ this.activityMgr.removeActivity(process.id);
+ } catch (e) {
+ // It is OK to end up here; If the folder is queued and the
+ // message get manually downloaded by the user, we might get
+ // a folder removed notification even before a download
+ // started for this folder. This behavior stems from the fact
+ // that we add activities into the activity manager in
+ // onDownloadStarted notification rather than onFolderAddedIntoQ.
+ // This is an expected side effect.
+ // Log a warning, but do not throw an error.
+ this.log.warn("onFolderRemovedFromQ: " + e);
+ }
+
+ // remove the folder/syncItem association from the table
+ this._syncInfoPerFolder.delete(folder.URI);
+ }
+
+ // if this is the last folder of this server in the queue
+ // create a sync event and clean the sync start time
+ let found = false;
+ for (let value of this._syncInfoPerFolder.values()) {
+ if (value.syncFolder.server == folder.server) {
+ found = true;
+ break;
+ }
+ }
+ this.log.info(
+ "Auto_Sync OnFolderRemovedFromQ Last folder of the server: " + !found
+ );
+ if (!found) {
+ // create an sync event for the completed process if it's not canceled
+ if (!canceled) {
+ let key = folder.server.prettyName;
+ if (
+ this._lastMessage.has(key) &&
+ this.activityMgr.containsActivity(this._lastMessage.get(key))
+ ) {
+ this.activityMgr.removeActivity(this._lastMessage.get(key));
+ }
+ this._lastMessage.set(
+ key,
+ this.activityMgr.addActivity(this.createSyncMailEvent(syncItem))
+ );
+ }
+ this._syncInfoPerServer.delete(folder.server);
+ }
+ }
+ } catch (e) {
+ this.log.error("onFolderRemovedFromQ: " + e);
+ throw e;
+ }
+ },
+ onDownloadStarted(folder, numOfMessages, totalPending) {
+ try {
+ if (folder instanceof Ci.nsIMsgFolder) {
+ this.log.info(
+ "OnDownloadStarted (" +
+ numOfMessages +
+ "/" +
+ totalPending +
+ "): " +
+ folder.prettyName +
+ " of " +
+ folder.server.prettyName +
+ "\n"
+ );
+
+ let syncItem = this._syncInfoPerFolder.get(folder.URI);
+ let process = syncItem.activity;
+
+ // Update the totalPending number. if new messages have been discovered in the folder
+ // after we added the folder into the q, totalPending might be greater than what we have
+ // initially set
+ if (totalPending > syncItem.pendingMsgCount) {
+ syncItem.pendingMsgCount = totalPending;
+ }
+
+ if (process instanceof Ci.nsIActivityProcess) {
+ // if the process has not beed added to activity manager already, add now
+ if (!this.activityMgr.containsActivity(process.id)) {
+ this.log.info(
+ "Auto_Sync OnDownloadStarted: No process, adding a new process"
+ );
+ this.activityMgr.addActivity(process);
+ }
+
+ syncItem.totalDownloaded += numOfMessages;
+
+ process.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
+ let percent =
+ (syncItem.totalDownloaded / syncItem.pendingMsgCount) * 100;
+ if (percent > syncItem.percentComplete) {
+ syncItem.percentComplete = percent;
+ }
+
+ let msg = this.bundle.formatStringFromName(
+ "autosyncProcessProgress2",
+ [
+ syncItem.totalDownloaded,
+ syncItem.pendingMsgCount,
+ folder.prettyName,
+ folder.server.prettyName,
+ ]
+ );
+
+ process.setProgress(
+ msg,
+ syncItem.totalDownloaded,
+ syncItem.pendingMsgCount
+ );
+
+ let serverInfo = this._syncInfoPerServer.get(
+ syncItem.syncFolder.server
+ );
+ serverInfo.totalDownloads += numOfMessages;
+ this._syncInfoPerServer.set(syncItem.syncFolder.server, serverInfo);
+ }
+ }
+ } catch (e) {
+ this.log.error("onDownloadStarted: " + e);
+ throw e;
+ }
+ },
+
+ onDownloadCompleted(folder) {
+ try {
+ if (folder instanceof Ci.nsIMsgFolder) {
+ this.log.info(
+ "OnDownloadCompleted: " +
+ folder.prettyName +
+ " of " +
+ folder.server.prettyName
+ );
+
+ let process = this._syncInfoPerFolder.get(folder.URI).activity;
+ if (process instanceof Ci.nsIActivityProcess && !this._running) {
+ this.log.info(
+ "OnDownloadCompleted: Auto-Sync Manager is paused, pausing the process"
+ );
+ process.state = Ci.nsIActivityProcess.STATE_PAUSED;
+ }
+ }
+ } catch (e) {
+ this.log.error("onDownloadCompleted: " + e);
+ throw e;
+ }
+ },
+
+ onDownloadError(folder) {
+ if (folder instanceof Ci.nsIMsgFolder) {
+ this.log.error(
+ "OnDownloadError: " +
+ folder.prettyName +
+ " of " +
+ folder.server.prettyName +
+ "\n"
+ );
+ }
+ },
+
+ onDiscoveryQProcessed(folder, numOfHdrsProcessed, leftToProcess) {
+ this.log.info(
+ "onDiscoveryQProcessed: Processed " +
+ numOfHdrsProcessed +
+ "/" +
+ (leftToProcess + numOfHdrsProcessed) +
+ " of " +
+ folder.prettyName +
+ "\n"
+ );
+ },
+
+ onAutoSyncInitiated(folder) {
+ this.log.info(
+ "onAutoSyncInitiated: " +
+ folder.prettyName +
+ " of " +
+ folder.server.prettyName +
+ " has been updated.\n"
+ );
+ },
+
+ init() {
+ // XXX when do we need to remove ourselves?
+ this.log.info("initing");
+ Cc["@mozilla.org/imap/autosyncmgr;1"]
+ .getService(Ci.nsIAutoSyncManager)
+ .addListener(this);
+ },
+};
diff --git a/comm/mail/components/activity/modules/glodaIndexer.jsm b/comm/mail/components/activity/modules/glodaIndexer.jsm
new file mode 100644
index 0000000000..5307d5cefa
--- /dev/null
+++ b/comm/mail/components/activity/modules/glodaIndexer.jsm
@@ -0,0 +1,251 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["glodaIndexerActivity"];
+
+var nsActProcess = Components.Constructor(
+ "@mozilla.org/activity-process;1",
+ "nsIActivityProcess",
+ "init"
+);
+var nsActEvent = Components.Constructor(
+ "@mozilla.org/activity-event;1",
+ "nsIActivityEvent",
+ "init"
+);
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PluralForm: "resource://gre/modules/PluralForm.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ Gloda: "resource:///modules/gloda/GlodaPublic.jsm",
+ GlodaConstants: "resource:///modules/gloda/GlodaConstants.jsm",
+ GlodaIndexer: "resource:///modules/gloda/GlodaIndexer.jsm",
+});
+
+/**
+ * Gloda message indexer feedback.
+ */
+var glodaIndexerActivity = {
+ get log() {
+ delete this.log;
+ return (this.log = console.createInstance({
+ prefix: "mail.activity",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.activity.loglevel",
+ }));
+ },
+
+ get activityMgr() {
+ delete this.activityMgr;
+ return (this.activityMgr = Cc["@mozilla.org/activity-manager;1"].getService(
+ Ci.nsIActivityManager
+ ));
+ },
+
+ get bundle() {
+ delete this.bundle;
+ return (this.bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/activity.properties"
+ ));
+ },
+
+ getString(stringName) {
+ try {
+ return this.bundle.GetStringFromName(stringName);
+ } catch (e) {
+ this.log.error("error trying to get a string called: " + stringName);
+ throw e;
+ }
+ },
+
+ init() {
+ // Register a listener with the Gloda indexer that receives notifications
+ // about Gloda indexing status. We wrap the listener in this function so we
+ // can set |this| to the GlodaIndexerActivity object inside the listener.
+ function listenerWrapper(...aArgs) {
+ glodaIndexerActivity.listener(...aArgs);
+ }
+ lazy.GlodaIndexer.addListener(listenerWrapper);
+ },
+
+ /**
+ * Information about the current job. An object with these properties:
+ *
+ * folder {String}
+ * the name of the folder being processed by the job
+ * jobNumber {Number}
+ * the index of the job in the list of jobs
+ * process {nsIActivityProcess}
+ * the activity process corresponding to the current job
+ * startTime {Date}
+ * the time at which we were first notified about the job
+ * totalItemNum {Number}
+ * the total number of messages being indexed in the job
+ * jobType {String}
+ * The IndexinbJob jobType (ex: "folder", "folderCompact")
+ */
+ currentJob: null,
+
+ listener(aStatus, aFolder, aJobNumber, aItemNumber, aTotalItemNum, aJobType) {
+ this.log.debug("Gloda Indexer Folder/Status: " + aFolder + "/" + aStatus);
+ this.log.debug("Gloda Indexer Job: " + aJobNumber);
+ this.log.debug("Gloda Indexer Item: " + aItemNumber + "/" + aTotalItemNum);
+
+ if (aStatus == lazy.GlodaConstants.kIndexerIdle) {
+ if (this.currentJob) {
+ this.onJobCompleted();
+ }
+ } else {
+ // If the job numbers have changed, the indexer has finished the job
+ // we were previously tracking, so convert the corresponding process
+ // into an event and start a new process to track the new job.
+ if (this.currentJob && aJobNumber != this.currentJob.jobNumber) {
+ this.onJobCompleted();
+ }
+
+ // If we aren't tracking a job, either this is the first time we've been
+ // called or the last job we were tracking was completed. Either way,
+ // start tracking the new job.
+ if (!this.currentJob) {
+ this.onJobBegun(aFolder, aJobNumber, aTotalItemNum, aJobType);
+ }
+
+ // If there is only one item, don't bother creating a progress item.
+ if (aTotalItemNum != 1) {
+ this.onJobProgress(aFolder, aItemNumber, aTotalItemNum);
+ }
+ }
+ },
+
+ onJobBegun(aFolder, aJobNumber, aTotalItemNum, aJobType) {
+ let displayText = aFolder
+ ? this.getString("indexingFolder").replace("#1", aFolder)
+ : this.getString("indexing");
+ let process = new nsActProcess(displayText, lazy.Gloda);
+
+ process.iconClass = "indexMail";
+ process.contextType = "account";
+ process.contextObj = aFolder;
+ process.addSubject(aFolder);
+
+ this.currentJob = {
+ folder: aFolder,
+ jobNumber: aJobNumber,
+ process,
+ startTime: new Date(),
+ totalItemNum: aTotalItemNum,
+ jobType: aJobType,
+ };
+
+ this.activityMgr.addActivity(process);
+ },
+
+ onJobProgress(aFolder, aItemNumber, aTotalItemNum) {
+ this.currentJob.process.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
+ // The total number of items being processed in the job can change, as can
+ // the folder being processed, since we sometimes get notified about a job
+ // before it has determined these things, so we update them here.
+ this.currentJob.folder = aFolder;
+ this.currentJob.totalItemNum = aTotalItemNum;
+
+ let statusText;
+ if (aTotalItemNum == null) {
+ statusText = aFolder
+ ? this.getString("indexingFolderStatusVague").replace("#1", aFolder)
+ : this.getString("indexingStatusVague");
+ } else {
+ let percentComplete =
+ aTotalItemNum == 0
+ ? 100
+ : parseInt((aItemNumber / aTotalItemNum) * 100);
+ // Note: we must replace the folder name placeholder last; otherwise,
+ // if the name happens to contain another one of the placeholders, we'll
+ // hork the name when replacing it.
+ statusText = this.getString(
+ aFolder ? "indexingFolderStatusExact" : "indexingStatusExact"
+ );
+ statusText = lazy.PluralForm.get(aTotalItemNum, statusText)
+ .replace("#1", aItemNumber + 1)
+ .replace("#2", aTotalItemNum)
+ .replace("#3", percentComplete)
+ .replace("#4", aFolder);
+ }
+
+ this.currentJob.process.setProgress(statusText, aItemNumber, aTotalItemNum);
+ },
+
+ onJobCompleted() {
+ this.currentJob.process.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+
+ this.activityMgr.removeActivity(this.currentJob.process.id);
+
+ // this.currentJob.totalItemNum might still be null at this point
+ // if we were first notified about the job before the indexer determined
+ // the number of messages to index and then it didn't find any to index.
+ let totalItemNum = this.currentJob.totalItemNum || 0;
+
+ // We only create activity events when specific folders get indexed,
+ // since event-driven indexing jobs are too numerous. We also only create
+ // them when we ended up indexing something in the folder, since otherwise
+ // we'd spam the activity manager with too many "indexed 0 messages" items
+ // that aren't useful enough to justify their presence in the manager.
+ // TODO: Aggregate event-driven indexing jobs into batches significant
+ // enough for us to create activity events for them.
+ if (
+ this.currentJob.jobType == "folder" &&
+ this.currentJob.folder &&
+ totalItemNum > 0
+ ) {
+ // Note: we must replace the folder name placeholder last; otherwise,
+ // if the name happens to contain another one of the placeholders, we'll
+ // hork the name when replacing it.
+ let displayText = lazy.PluralForm.get(
+ totalItemNum,
+ this.getString("indexedFolder")
+ )
+ .replace("#1", totalItemNum)
+ .replace("#2", this.currentJob.folder);
+
+ let endTime = new Date();
+ let secondsElapsed = parseInt(
+ (endTime - this.currentJob.startTime) / 1000
+ );
+
+ let statusText = lazy.PluralForm.get(
+ secondsElapsed,
+ this.getString("indexedFolderStatus")
+ ).replace("#1", secondsElapsed);
+
+ let event = new nsActEvent(
+ displayText,
+ lazy.Gloda,
+ statusText,
+ this.currentJob.startTime,
+ endTime
+ );
+ event.contextType = this.currentJob.contextType;
+ event.contextObj = this.currentJob.contextObj;
+ event.iconClass = "indexMail";
+
+ // Transfer subjects.
+ let subjects = this.currentJob.process.getSubjects();
+ for (let subject of subjects) {
+ event.addSubject(subject);
+ }
+
+ this.activityMgr.addActivity(event);
+ }
+
+ this.currentJob = null;
+ },
+};
diff --git a/comm/mail/components/activity/modules/moveCopy.jsm b/comm/mail/components/activity/modules/moveCopy.jsm
new file mode 100644
index 0000000000..de3e51d85b
--- /dev/null
+++ b/comm/mail/components/activity/modules/moveCopy.jsm
@@ -0,0 +1,396 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["moveCopyModule"];
+
+var nsActEvent = Components.Constructor(
+ "@mozilla.org/activity-event;1",
+ "nsIActivityEvent",
+ "init"
+);
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+// This module provides a link between the move/copy code and the activity
+// manager.
+var moveCopyModule = {
+ lastMessage: {},
+ lastFolder: {},
+
+ get log() {
+ delete this.log;
+ return (this.log = console.createInstance({
+ prefix: "mail.activity",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.activity.loglevel",
+ }));
+ },
+
+ get activityMgr() {
+ delete this.activityMgr;
+ return (this.activityMgr = Cc["@mozilla.org/activity-manager;1"].getService(
+ Ci.nsIActivityManager
+ ));
+ },
+
+ get bundle() {
+ delete this.bundle;
+ return (this.bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/activity.properties"
+ ));
+ },
+
+ getString(stringName) {
+ try {
+ return this.bundle.GetStringFromName(stringName);
+ } catch (e) {
+ this.log.error("error trying to get a string called: " + stringName);
+ throw e;
+ }
+ },
+
+ msgAdded(aMsg) {},
+
+ msgsDeleted(aMsgList) {
+ this.log.info("in msgsDeleted");
+
+ if (aMsgList.length <= 0) {
+ return;
+ }
+
+ let displayCount = aMsgList.length;
+ // get the folder of the deleted messages
+ let folder = aMsgList[0].folder;
+
+ let activities = this.activityMgr.getActivities();
+ if (
+ activities.length > 0 &&
+ activities[activities.length - 1].id == this.lastMessage.id &&
+ this.lastMessage.type == "deleteMail" &&
+ this.lastMessage.folder == folder.prettyName
+ ) {
+ displayCount += this.lastMessage.count;
+ this.activityMgr.removeActivity(this.lastMessage.id);
+ }
+
+ this.lastMessage = {};
+ let displayText = PluralForm.get(
+ displayCount,
+ this.getString("deletedMessages2")
+ );
+ displayText = displayText.replace("#1", displayCount);
+ this.lastMessage.count = displayCount;
+ displayText = displayText.replace("#2", folder.prettyName);
+ this.lastMessage.folder = folder.prettyName;
+
+ let statusText = folder.server.prettyName;
+
+ // create an activity event
+ let event = new nsActEvent(
+ displayText,
+ folder,
+ statusText,
+ Date.now(), // start time
+ Date.now()
+ ); // completion time
+
+ event.iconClass = "deleteMail";
+ this.lastMessage.type = event.iconClass;
+
+ for (let msgHdr of aMsgList) {
+ event.addSubject(msgHdr.messageId);
+ }
+
+ this.lastMessage.id = this.activityMgr.addActivity(event);
+ },
+
+ msgsMoveCopyCompleted(aMove, aSrcMsgList, aDestFolder) {
+ try {
+ this.log.info("in msgsMoveCopyCompleted");
+
+ let count = aSrcMsgList.length;
+ if (count <= 0) {
+ return;
+ }
+
+ // get the folder of the moved/copied messages
+ let folder = aSrcMsgList[0].folder;
+ this.log.info("got folder");
+
+ let displayCount = count;
+
+ let activities = this.activityMgr.getActivities();
+ if (
+ activities.length > 0 &&
+ activities[activities.length - 1].id == this.lastMessage.id &&
+ this.lastMessage.type == (aMove ? "moveMail" : "copyMail") &&
+ this.lastMessage.sourceFolder == folder.prettyName &&
+ this.lastMessage.destFolder == aDestFolder.prettyName
+ ) {
+ displayCount += this.lastMessage.count;
+ this.activityMgr.removeActivity(this.lastMessage.id);
+ }
+
+ let statusText = "";
+ if (folder.server != aDestFolder.server) {
+ statusText = this.getString("fromServerToServer");
+ statusText = statusText.replace("#1", folder.server.prettyName);
+ statusText = statusText.replace("#2", aDestFolder.server.prettyName);
+ } else {
+ statusText = folder.server.prettyName;
+ }
+
+ this.lastMessage = {};
+ let displayText;
+ if (aMove) {
+ displayText = PluralForm.get(
+ displayCount,
+ this.getString("movedMessages")
+ );
+ } else {
+ displayText = PluralForm.get(
+ displayCount,
+ this.getString("copiedMessages")
+ );
+ }
+
+ displayText = displayText.replace("#1", displayCount);
+ this.lastMessage.count = displayCount;
+ displayText = displayText.replace("#2", folder.prettyName);
+ this.lastMessage.sourceFolder = folder.prettyName;
+ displayText = displayText.replace("#3", aDestFolder.prettyName);
+ this.lastMessage.destFolder = aDestFolder.prettyName;
+
+ // create an activity event
+ let event = new nsActEvent(
+ displayText,
+ folder,
+ statusText,
+ Date.now(), // start time
+ Date.now()
+ ); // completion time
+ event.iconClass = aMove ? "moveMail" : "copyMail";
+ this.lastMessage.type = event.iconClass;
+
+ for (let msgHdr of aSrcMsgList) {
+ event.addSubject(msgHdr.messageId);
+ }
+ this.lastMessage.id = this.activityMgr.addActivity(event);
+ } catch (e) {
+ this.log.error("Exception: " + e);
+ }
+ },
+
+ folderAdded(aFolder) {},
+
+ folderDeleted(aFolder) {
+ // When a new account is created we get this notification with an empty named
+ // folder that can't return its server. Ignore it.
+ // TODO: find out what it is.
+ let server = aFolder.server;
+ // If the account has been removed, we're going to ignore this notification.
+ if (
+ !MailServices.accounts.findServer(
+ server.username,
+ server.hostName,
+ server.type
+ )
+ ) {
+ return;
+ }
+
+ let displayText;
+ let statusText = server.prettyName;
+
+ // Display a different message depending on whether we emptied the trash
+ // or actually deleted a folder
+ if (aFolder.isSpecialFolder(Ci.nsMsgFolderFlags.Trash, false)) {
+ displayText = this.getString("emptiedTrash");
+ } else {
+ displayText = this.getString("deletedFolder").replace(
+ "#1",
+ aFolder.prettyName
+ );
+ }
+
+ // create an activity event
+ let event = new nsActEvent(
+ displayText,
+ server,
+ statusText,
+ Date.now(), // start time
+ Date.now()
+ ); // completion time
+
+ event.addSubject(aFolder);
+ event.iconClass = "deleteMail";
+
+ // When we rename, we get a delete event as well as a rename, so store
+ // the last folder we deleted
+ this.lastFolder = {};
+ this.lastFolder.URI = aFolder.URI;
+ this.lastFolder.event = this.activityMgr.addActivity(event);
+ },
+
+ folderMoveCopyCompleted(aMove, aSrcFolder, aDestFolder) {
+ this.log.info("in folderMoveCopyCompleted, aMove = " + aMove);
+
+ let displayText;
+ if (aMove) {
+ displayText = this.getString("movedFolder");
+ } else {
+ displayText = this.getString("copiedFolder");
+ }
+
+ displayText = displayText.replace("#1", aSrcFolder.prettyName);
+ displayText = displayText.replace("#2", aDestFolder.prettyName);
+
+ let statusText = "";
+ if (aSrcFolder.server != aDestFolder.server) {
+ statusText = this.getString("fromServerToServer");
+ statusText = statusText.replace("#1", aSrcFolder.server.prettyName);
+ statusText = statusText.replace("#2", aDestFolder.server.prettyName);
+ } else {
+ statusText = aSrcFolder.server.prettyName;
+ }
+ // create an activity event
+ let event = new nsActEvent(
+ displayText,
+ aSrcFolder.server,
+ statusText,
+ Date.now(), // start time
+ Date.now()
+ ); // completion time
+
+ event.addSubject(aSrcFolder);
+ event.addSubject(aDestFolder);
+ event.iconClass = aMove ? "moveMail" : "copyMail";
+
+ this.activityMgr.addActivity(event);
+ },
+
+ folderRenamed(aOrigFolder, aNewFolder) {
+ this.log.info(
+ "in folderRenamed, aOrigFolder = " +
+ aOrigFolder.prettyName +
+ ", aNewFolder = " +
+ aNewFolder.prettyName
+ );
+
+ let displayText;
+ let statusText = aNewFolder.server.prettyName;
+
+ // Display a different message depending on whether we moved the folder
+ // to the trash or actually renamed the folder.
+ if (aNewFolder.isSpecialFolder(Ci.nsMsgFolderFlags.Trash, true)) {
+ displayText = this.getString("movedFolderToTrash");
+ displayText = displayText.replace("#1", aOrigFolder.prettyName);
+ } else {
+ displayText = this.getString("renamedFolder");
+ displayText = displayText.replace("#1", aOrigFolder.prettyName);
+ displayText = displayText.replace("#2", aNewFolder.prettyName);
+ }
+
+ // When renaming a folder, a delete event is always fired first
+ if (this.lastFolder.URI == aOrigFolder.URI) {
+ this.activityMgr.removeActivity(this.lastFolder.event);
+ }
+
+ // create an activity event
+ let event = new nsActEvent(
+ displayText,
+ aOrigFolder.server,
+ statusText,
+ Date.now(), // start time
+ Date.now()
+ ); // completion time
+
+ event.addSubject(aOrigFolder);
+ event.addSubject(aNewFolder);
+
+ this.activityMgr.addActivity(event);
+ },
+
+ msgUnincorporatedMoved(srcFolder, msgHdr) {
+ try {
+ this.log.info("in msgUnincorporatedMoved");
+
+ // get the folder of the moved/copied messages
+ let destFolder = msgHdr.folder;
+ this.log.info("got folder");
+
+ let displayCount = 1;
+
+ let activities = this.activityMgr.getActivities();
+ if (
+ activities.length > 0 &&
+ activities[activities.length - 1].id == this.lastMessage.id &&
+ this.lastMessage.type == "moveMail" &&
+ this.lastMessage.sourceFolder == srcFolder.prettyName &&
+ this.lastMessage.destFolder == destFolder.prettyName
+ ) {
+ displayCount += this.lastMessage.count;
+ this.activityMgr.removeActivity(this.lastMessage.id);
+ }
+
+ let statusText = "";
+ if (srcFolder.server != destFolder.server) {
+ statusText = this.getString("fromServerToServer");
+ statusText = statusText.replace("#1", srcFolder.server.prettyName);
+ statusText = statusText.replace("#2", destFolder.server.prettyName);
+ } else {
+ statusText = srcFolder.server.prettyName;
+ }
+
+ this.lastMessage = {};
+ let displayText;
+ displayText = PluralForm.get(
+ displayCount,
+ this.getString("movedMessages")
+ );
+
+ displayText = displayText.replace("#1", displayCount);
+ this.lastMessage.count = displayCount;
+ displayText = displayText.replace("#2", srcFolder.prettyName);
+ this.lastMessage.sourceFolder = srcFolder.prettyName;
+ displayText = displayText.replace("#3", destFolder.prettyName);
+ this.lastMessage.destFolder = destFolder.prettyName;
+
+ // create an activity event
+ let event = new nsActEvent(
+ displayText,
+ srcFolder,
+ statusText,
+ Date.now(), // start time
+ Date.now()
+ ); // completion time
+
+ event.iconClass = "moveMail";
+ this.lastMessage.type = event.iconClass;
+ event.addSubject(msgHdr.messageId);
+ this.lastMessage.id = this.activityMgr.addActivity(event);
+ } catch (e) {
+ this.log.error("Exception: " + e);
+ }
+ },
+
+ init() {
+ // XXX when do we need to remove ourselves?
+ MailServices.mfn.addListener(
+ this,
+ MailServices.mfn.msgsDeleted |
+ MailServices.mfn.msgsMoveCopyCompleted |
+ MailServices.mfn.folderDeleted |
+ MailServices.mfn.folderMoveCopyCompleted |
+ MailServices.mfn.folderRenamed |
+ MailServices.mfn.msgUnincorporatedMoved
+ );
+ },
+};
diff --git a/comm/mail/components/activity/modules/pop3Download.jsm b/comm/mail/components/activity/modules/pop3Download.jsm
new file mode 100644
index 0000000000..f203b33212
--- /dev/null
+++ b/comm/mail/components/activity/modules/pop3Download.jsm
@@ -0,0 +1,154 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["pop3DownloadModule"];
+
+var nsActEvent = Components.Constructor(
+ "@mozilla.org/activity-event;1",
+ "nsIActivityEvent",
+ "init"
+);
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+// This module provides a link between the pop3 service code and the activity
+// manager.
+var pop3DownloadModule = {
+ // hash table of most recent download items per folder
+ _mostRecentActivityForFolder: new Map(),
+ // hash table of prev download items per folder, so we can
+ // coalesce consecutive no new message events.
+ _prevActivityForFolder: new Map(),
+
+ get log() {
+ delete this.log;
+ return (this.log = console.createInstance({
+ prefix: "mail.activity",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.activity.loglevel",
+ }));
+ },
+
+ get activityMgr() {
+ delete this.activityMgr;
+ return (this.activityMgr = Cc["@mozilla.org/activity-manager;1"].getService(
+ Ci.nsIActivityManager
+ ));
+ },
+
+ get bundle() {
+ delete this.bundle;
+ return (this.bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/activity.properties"
+ ));
+ },
+
+ getString(stringName) {
+ try {
+ return this.bundle.GetStringFromName(stringName);
+ } catch (e) {
+ this.log.error("error trying to get a string called: " + stringName);
+ throw e;
+ }
+ },
+
+ onDownloadStarted(aFolder) {
+ this.log.info("in onDownloadStarted");
+
+ let displayText = this.bundle.formatStringFromName(
+ "pop3EventStartDisplayText2",
+ [
+ aFolder.server.prettyName, // account name
+ aFolder.prettyName,
+ ]
+ ); // folder name
+ // remember the prev activity for this folder, if any.
+ this._prevActivityForFolder.set(
+ aFolder.URI,
+ this._mostRecentActivityForFolder.get(aFolder.URI)
+ );
+ let statusText = aFolder.server.prettyName;
+
+ // create an activity event
+ let event = new nsActEvent(
+ displayText,
+ aFolder,
+ statusText,
+ Date.now(), // start time
+ Date.now()
+ ); // completion time
+
+ event.iconClass = "syncMail";
+
+ let downloadItem = {};
+ downloadItem.eventID = this.activityMgr.addActivity(event);
+ this._mostRecentActivityForFolder.set(aFolder.URI, downloadItem);
+ },
+
+ onDownloadProgress(aFolder, aNumMsgsDownloaded, aTotalMsgs) {
+ this.log.info("in onDownloadProgress");
+ },
+
+ onDownloadCompleted(aFolder, aNumMsgsDownloaded) {
+ this.log.info("in onDownloadCompleted");
+
+ // Remove activity if there was any.
+ // It can happen that download never started (e.g. couldn't connect to server),
+ // with onDownloadStarted, but we still get a onDownloadCompleted event
+ // when the connection is given up.
+ let recentActivity = this._mostRecentActivityForFolder.get(aFolder.URI);
+ if (recentActivity) {
+ this.activityMgr.removeActivity(recentActivity.eventID);
+ }
+
+ let displayText;
+ if (aNumMsgsDownloaded > 0) {
+ displayText = PluralForm.get(
+ aNumMsgsDownloaded,
+ this.getString("pop3EventStatusText")
+ );
+ displayText = displayText.replace("#1", aNumMsgsDownloaded);
+ } else {
+ displayText = this.getString("pop3EventStatusTextNoMsgs");
+ }
+
+ let statusText = aFolder.server.prettyName;
+
+ // create an activity event
+ let event = new nsActEvent(
+ displayText,
+ aFolder,
+ statusText,
+ Date.now(), // start time
+ Date.now()
+ ); // completion time
+
+ event.iconClass = "syncMail";
+
+ let downloadItem = { numMsgsDownloaded: aNumMsgsDownloaded };
+ this._mostRecentActivityForFolder.set(aFolder.URI, downloadItem);
+ downloadItem.eventID = this.activityMgr.addActivity(event);
+ if (!aNumMsgsDownloaded) {
+ // If we didn't download any messages this time, and the prev event
+ // for this folder also didn't download any messages, remove the
+ // prev event from the activity manager.
+ let prevItem = this._prevActivityForFolder.get(aFolder.URI);
+ if (prevItem != undefined && !prevItem.numMsgsDownloaded) {
+ if (this.activityMgr.containsActivity(prevItem.eventID)) {
+ this.activityMgr.removeActivity(prevItem.eventID);
+ }
+ }
+ }
+ },
+ init() {
+ // XXX when do we need to remove ourselves?
+ MailServices.pop3.addListener(this);
+ },
+};
diff --git a/comm/mail/components/activity/modules/sendLater.jsm b/comm/mail/components/activity/modules/sendLater.jsm
new file mode 100644
index 0000000000..37027d96f1
--- /dev/null
+++ b/comm/mail/components/activity/modules/sendLater.jsm
@@ -0,0 +1,298 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["sendLaterModule"];
+
+var nsActProcess = Components.Constructor(
+ "@mozilla.org/activity-process;1",
+ "nsIActivityProcess",
+ "init"
+);
+var nsActEvent = Components.Constructor(
+ "@mozilla.org/activity-event;1",
+ "nsIActivityEvent",
+ "init"
+);
+var nsActWarning = Components.Constructor(
+ "@mozilla.org/activity-warning;1",
+ "nsIActivityWarning",
+ "init"
+);
+
+/**
+ * This really, really, sucks. Due to mailnews widespread use of
+ * nsIMsgStatusFeedback we're bound to the UI to get any sensible feedback of
+ * mail sending operations. The current send later code can't hook into the
+ * progress listener easily to get the state of messages being sent, so we'll
+ * just have to do it here.
+ */
+var sendMsgProgressListener = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIMsgStatusFeedback",
+ "nsISupportsWeakReference",
+ ]),
+
+ showStatusString(aStatusText) {
+ sendLaterModule.onMsgStatus(aStatusText);
+ },
+
+ startMeteors() {},
+
+ stopMeteors() {},
+
+ showProgress(aPercentage) {
+ sendLaterModule.onMessageSendProgress(0, 0, aPercentage, 0);
+ },
+};
+
+// This module provides a link between the send later service and the activity
+// manager.
+var sendLaterModule = {
+ _sendProcess: null,
+ _copyProcess: null,
+ _identity: null,
+ _subject: null,
+
+ get log() {
+ delete this.log;
+ return (this.log = console.createInstance({
+ prefix: "mail.activity",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.activity.loglevel",
+ }));
+ },
+
+ get activityMgr() {
+ delete this.activityMgr;
+ return (this.activityMgr = Cc["@mozilla.org/activity-manager;1"].getService(
+ Ci.nsIActivityManager
+ ));
+ },
+
+ get bundle() {
+ delete this.bundle;
+ return (this.bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/activity.properties"
+ ));
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgSendLaterListener"]),
+
+ _displayTextForHeader(aLocaleStringBase, aSubject) {
+ return aSubject
+ ? this.bundle.formatStringFromName(aLocaleStringBase + "WithSubject", [
+ aSubject,
+ ])
+ : this.bundle.GetStringFromName(aLocaleStringBase);
+ },
+
+ _newProcess(aLocaleStringBase, aAddSubject) {
+ let process = new nsActProcess(
+ this._displayTextForHeader(
+ aLocaleStringBase,
+ aAddSubject ? this._subject : ""
+ ),
+ this.activityMgr
+ );
+
+ process.iconClass = "sendMail";
+ process.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_BYCONTEXT;
+ process.contextObj = this;
+ process.contextType = "SendLater";
+ process.contextDisplayText =
+ this.bundle.GetStringFromName("sendingMessages");
+
+ return process;
+ },
+
+ // Use this to group an activity by the identity if we have one.
+ _applyIdentityGrouping(aActivity) {
+ if (this._identity) {
+ aActivity.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_BYCONTEXT;
+ aActivity.contextType = this._identity.key;
+ aActivity.contextObj = this._identity;
+ let contextDisplayText = this._identity.identityName;
+ if (!contextDisplayText) {
+ contextDisplayText = this._identity.email;
+ }
+
+ aActivity.contextDisplayText = contextDisplayText;
+ } else {
+ aActivity.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_STANDALONE;
+ }
+ },
+
+ // Replaces the process with an event that reflects a completed process.
+ _replaceProcessWithEvent(aProcess) {
+ this.activityMgr.removeActivity(aProcess.id);
+
+ let event = new nsActEvent(
+ this._displayTextForHeader("sentMessage", this._subject),
+ this.activityMgr,
+ "",
+ aProcess.startTime,
+ new Date()
+ );
+
+ event.iconClass = "sendMail";
+ this._applyIdentityGrouping(event);
+
+ this.activityMgr.addActivity(event);
+ },
+
+ // Replaces the process with a warning that reflects the failed process.
+ _replaceProcessWithWarning(
+ aProcess,
+ aCopyOrSend,
+ aStatus,
+ aMsg,
+ aMessageHeader
+ ) {
+ this.activityMgr.removeActivity(aProcess.id);
+
+ let warning = new nsActWarning(
+ this._displayTextForHeader("failedTo" + aCopyOrSend, this._subject),
+ this.activityMgr,
+ ""
+ );
+
+ warning.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_STANDALONE;
+ this._applyIdentityGrouping(warning);
+
+ this.activityMgr.addActivity(warning);
+ },
+
+ onStartSending(aTotalMessageCount) {
+ if (!aTotalMessageCount) {
+ this.log.error("onStartSending called with zero messages\n");
+ }
+ },
+
+ onMessageStartSending(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageHeader,
+ aIdentity
+ ) {
+ // We want to use the identity and subject later, so store them for now.
+ this._identity = aIdentity;
+ if (aMessageHeader) {
+ this._subject = aMessageHeader.mime2DecodedSubject;
+ }
+
+ // Create the process to display the send activity.
+ let process = this._newProcess("sendingMessage", true);
+ this._sendProcess = process;
+ this.activityMgr.addActivity(process);
+
+ // Now the one for the copy process.
+ process = this._newProcess("copyMessage", false);
+ this._copyProcess = process;
+ this.activityMgr.addActivity(process);
+ },
+
+ onMessageSendProgress(
+ aCurrentMessage,
+ aTotalMessageCount,
+ aMessageSendPercent,
+ aMessageCopyPercent
+ ) {
+ if (aMessageSendPercent < 100) {
+ // Ensure we are in progress...
+ if (this._sendProcess.state != Ci.nsIActivityProcess.STATE_INPROGRESS) {
+ this._sendProcess.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
+ }
+
+ // ... and update the progress.
+ this._sendProcess.setProgress(
+ this._sendProcess.lastStatusText,
+ aMessageSendPercent,
+ 100
+ );
+ } else if (aMessageSendPercent == 100) {
+ if (aMessageCopyPercent == 0) {
+ // Set send state to completed
+ if (this._sendProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED) {
+ this._sendProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+ }
+ this._replaceProcessWithEvent(this._sendProcess);
+
+ // Set copy state to in progress.
+ if (this._copyProcess.state != Ci.nsIActivityProcess.STATE_INPROGRESS) {
+ this._copyProcess.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
+ }
+
+ // We don't know the progress of the copy, so just set to 0, and we'll
+ // display an undetermined progress meter.
+ this._copyProcess.setProgress(this._copyProcess.lastStatusText, 0, 0);
+ } else if (aMessageCopyPercent >= 100) {
+ // We need to set this to completed otherwise activity manager
+ // complains.
+ if (this._copyProcess) {
+ this._copyProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+ this.activityMgr.removeActivity(this._copyProcess.id);
+ this._copyProcess = null;
+ }
+
+ this._sendProcess = null;
+ }
+ }
+ },
+
+ onMessageSendError(aCurrentMessage, aMessageHeader, aStatus, aMsg) {
+ if (
+ this._sendProcess &&
+ this._sendProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED
+ ) {
+ this._sendProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+ this._replaceProcessWithWarning(
+ this._sendProcess,
+ "SendMessage",
+ aStatus,
+ aMsg,
+ aMessageHeader
+ );
+ this._sendProcess = null;
+
+ if (
+ this._copyProcess &&
+ this._copyProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED
+ ) {
+ this._copyProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+ this.activityMgr.removeActivity(this._copyProcess.id);
+ this._copyProcess = null;
+ }
+ }
+ },
+
+ onMsgStatus(aStatusText) {
+ this._sendProcess.setProgress(
+ aStatusText,
+ this._sendProcess.workUnitComplete,
+ this._sendProcess.totalWorkUnits
+ );
+ },
+
+ onStopSending(aStatus, aMsg, aTotalTried, aSuccessful) {},
+
+ init() {
+ // We should need to remove the listener as we're not being held by anyone
+ // except by the send later instance.
+ let sendLaterService = Cc[
+ "@mozilla.org/messengercompose/sendlater;1"
+ ].getService(Ci.nsIMsgSendLater);
+
+ sendLaterService.addListener(this);
+
+ // Also add the nsIMsgStatusFeedback object.
+ let statusFeedback = Cc[
+ "@mozilla.org/messenger/statusfeedback;1"
+ ].createInstance(Ci.nsIMsgStatusFeedback);
+
+ statusFeedback.setWrappedStatusFeedback(sendMsgProgressListener);
+
+ sendLaterService.statusFeedback = statusFeedback;
+ },
+};