diff options
Diffstat (limited to 'comm/mail/components/activity/modules/autosync.jsm')
-rw-r--r-- | comm/mail/components/activity/modules/autosync.jsm | 433 |
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); + }, +}; |