summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/activity/modules/autosync.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/activity/modules/autosync.jsm')
-rw-r--r--comm/mail/components/activity/modules/autosync.jsm433
1 files changed, 433 insertions, 0 deletions
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);
+ },
+};