diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mail/components/activity/modules | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mail/components/activity/modules')
-rw-r--r-- | comm/mail/components/activity/modules/activityModules.jsm | 33 | ||||
-rw-r--r-- | comm/mail/components/activity/modules/alertHook.jsm | 101 | ||||
-rw-r--r-- | comm/mail/components/activity/modules/autosync.jsm | 433 | ||||
-rw-r--r-- | comm/mail/components/activity/modules/glodaIndexer.jsm | 251 | ||||
-rw-r--r-- | comm/mail/components/activity/modules/moveCopy.jsm | 396 | ||||
-rw-r--r-- | comm/mail/components/activity/modules/pop3Download.jsm | 154 | ||||
-rw-r--r-- | comm/mail/components/activity/modules/sendLater.jsm | 298 |
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; + }, +}; |