summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/search/SearchIntegration.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/search/SearchIntegration.jsm')
-rw-r--r--comm/mail/components/search/SearchIntegration.jsm871
1 files changed, 871 insertions, 0 deletions
diff --git a/comm/mail/components/search/SearchIntegration.jsm b/comm/mail/components/search/SearchIntegration.jsm
new file mode 100644
index 0000000000..4bedb50f58
--- /dev/null
+++ b/comm/mail/components/search/SearchIntegration.jsm
@@ -0,0 +1,871 @@
+/* -*- 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/. */
+
+/*
+ * Common, useful functions for desktop search integration components.
+ *
+ * The following symbols have to be defined for each component that includes this:
+ * - gHdrIndexedProperty: the property in the database that indicates whether a message
+ * has been indexed
+ * - gFileExt: the file extension to be used for support files
+ * - gPrefBase: the base for preferences that are stored
+ * - gStreamListener: an nsIStreamListener to read message text
+ */
+
+/* exported SearchSupport */
+
+var EXPORTED_SYMBOLS = ["SearchIntegration"];
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var PERM_DIRECTORY = 0o755;
+var PERM_FILE = 0o644;
+
+var SearchIntegration = null;
+
+var SearchSupport = {
+ /**
+ * URI of last folder indexed. Kept in sync with the pref
+ */
+ __lastFolderIndexedUri: null,
+ set _lastFolderIndexedUri(uri) {
+ this._prefBranch.setStringPref("lastFolderIndexedUri", uri);
+ this.__lastFolderIndexedUri = uri;
+ },
+ get _lastFolderIndexedUri() {
+ // If we don't know about it, get it from the pref branch
+ if (this.__lastFolderIndexedUri === null) {
+ this.__lastFolderIndexedUri = this._prefBranch.getStringPref(
+ "lastFolderIndexedUri",
+ ""
+ );
+ }
+ return this.__lastFolderIndexedUri;
+ },
+
+ /**
+ * Queue of message headers to index, along with reindex times for each header
+ */
+ _msgHdrsToIndex: [],
+
+ /**
+ * Messenger object, used primarily to get message URIs
+ */
+ __messenger: null,
+ get _messenger() {
+ if (!this.__messenger) {
+ this.__messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ }
+ return this.__messenger;
+ },
+
+ // The preferences branch to use
+ __prefBranch: null,
+ get _prefBranch() {
+ if (!this.__prefBranch) {
+ this.__prefBranch = Services.prefs.getBranch(this._prefBase);
+ }
+ return this.__prefBranch;
+ },
+
+ /**
+ * If this is true, we won't show any UI because the OS doesn't have the
+ * support we need
+ */
+ osVersionTooLow: false,
+
+ /**
+ * If this is true, we'll show disabled UI, because while the OS does have
+ * the support we need, not all the OS components we need are running
+ */
+ osComponentsNotRunning: false,
+
+ /**
+ * Whether the preference is enabled. The module might be in a state where
+ * the preference is on but "enabled" is false, so take care of that.
+ */
+ get prefEnabled() {
+ // Don't cache the value
+ return this._prefBranch.getBoolPref("enable");
+ },
+ set prefEnabled(aEnabled) {
+ if (this.prefEnabled != aEnabled) {
+ this._prefBranch.setBoolPref("enable", aEnabled);
+ }
+ },
+
+ /**
+ * Whether the first run has occurred. This will be used to determine if
+ * a dialog box needs to be displayed.
+ */
+ get firstRunDone() {
+ // Don't cache this value either
+ return this._prefBranch.getBoolPref("firstRunDone");
+ },
+ set firstRunDone(aAlwaysTrue) {
+ this._prefBranch.setBoolPref("firstRunDone", true);
+ },
+
+ /**
+ * Last global reindex time, used to check if reindexing is required.
+ * Kept in sync with the pref
+ */
+ _globalReindexTime: null,
+ set globalReindexTime(aTime) {
+ this._globalReindexTime = aTime;
+ // Set the pref as well
+ this._prefBranch.setCharPref("global_reindex_time", "" + aTime);
+ },
+ get globalReindexTime() {
+ if (!this._globalReindexTime) {
+ // Try getting the time from the preferences
+ try {
+ this._globalReindexTime = parseInt(
+ this._prefBranch.getCharPref("global_reindex_time")
+ );
+ } catch (e) {
+ // We don't have it defined, so set it (Unix time, in seconds)
+ this._globalReindexTime = parseInt(Date.now() / 1000);
+ this._prefBranch.setCharPref(
+ "global_reindex_time",
+ "" + this._globalReindexTime
+ );
+ }
+ }
+ return this._globalReindexTime;
+ },
+
+ /**
+ * Amount of time the user is idle before we (re)start an indexing sweep
+ */
+ _idleThresholdSecs: 30,
+
+ /**
+ * Reference to timer object
+ */
+ __timer: null,
+ get _timer() {
+ if (!this.__timer) {
+ this.__timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+ return this.__timer;
+ },
+
+ _cancelTimer() {
+ try {
+ this._timer.cancel();
+ } catch (ex) {}
+ },
+
+ /**
+ * Enabled status.
+ *
+ * When we're enabled, then we get notifications about every message or folder
+ * operation, including "message displayed" operations which we bump up in
+ * priority. We also have a background sweep which we do on idle.
+ *
+ * We aren't fully disabled when we're "disabled", though. We still observe
+ * message and folder moves and deletes, as we don't want to have support
+ * files for non-existent messages.
+ */
+ _enabled: null,
+ set enabled(aEnable) {
+ // Nothing to do if there's no change in state
+ if (this._enabled == aEnable) {
+ return;
+ }
+
+ this._log.info(
+ "Enabled status changing from " + this._enabled + " to " + aEnable
+ );
+
+ this._removeObservers();
+
+ if (aEnable) {
+ // This stuff we always need to do.
+ // This code pre-dates msgsClassified.
+ // Some events intentionally omitted.
+ MailServices.mfn.addListener(
+ this._msgFolderListener,
+ MailServices.mfn.msgAdded |
+ MailServices.mfn.msgsDeleted |
+ MailServices.mfn.msgsMoveCopyCompleted |
+ MailServices.mfn.folderDeleted |
+ MailServices.mfn.folderMoveCopyCompleted |
+ MailServices.mfn.folderRenamed
+ );
+ Services.obs.addObserver(this, "MsgMsgDisplayed");
+ let idleService = Cc["@mozilla.org/widget/useridleservice;1"].getService(
+ Ci.nsIUserIdleService
+ );
+ idleService.addIdleObserver(this, this._idleThresholdSecs);
+ } else {
+ // We want to observe moves, deletes and renames in case we're disabled
+ // If we don't, we'll have no idea the support files exist later
+ MailServices.mfn.addListener(
+ this._msgFolderListener,
+ MailServices.mfn.msgsMoveCopyCompleted |
+ MailServices.mfn.msgsDeleted |
+ // folderAdded intentionally omitted
+ MailServices.mfn.folderDeleted |
+ MailServices.mfn.folderMoveCopyCompleted |
+ MailServices.mfn.folderRenamed
+ );
+ }
+
+ this._enabled = aEnable;
+ },
+ get enabled() {
+ return this._enabled;
+ },
+
+ /**
+ * Remove whatever observers are present. This is done while switching states
+ */
+ _removeObservers() {
+ if (this.enabled === null) {
+ return;
+ }
+
+ MailServices.mfn.removeListener(this._msgFolderListener);
+
+ if (this.enabled) {
+ Services.obs.removeObserver(this, "MsgMsgDisplayed");
+ let idleService = Cc["@mozilla.org/widget/useridleservice;1"].getService(
+ Ci.nsIUserIdleService
+ );
+ idleService.removeIdleObserver(this, this._idleThresholdSecs);
+
+ // in case there's a background sweep going on
+ this._cancelTimer();
+ }
+ // We don't need to do anything extra if we're disabled
+ },
+
+ /**
+ * Init function -- this should be called from the component's init function
+ */
+ _initSupport(enabled) {
+ this._log.info(
+ "Search integration running in " +
+ (enabled ? "active" : "backoff") +
+ " mode"
+ );
+ this.enabled = enabled;
+
+ // Set up a pref observer
+ this._prefBranch.addObserver("enable", this);
+ },
+
+ /**
+ * Current folder being indexed
+ */
+ _currentFolderToIndex: null,
+
+ /**
+ * For the current folder being indexed, an enumerator for all the headers in
+ * the folder
+ */
+ _headerEnumerator: null,
+
+ /*
+ * These functions are to index already existing messages
+ */
+
+ /**
+ * Generator to look for the next folder to index, and return it
+ *
+ * This first looks for folders that have their corresponding search results
+ * folders missing. If it finds such a folder first, it'll yield return that
+ * folder.
+ *
+ * Next, it looks for the next folder after the lastFolderIndexedUri. If it is
+ * in such a folder, it'll yield return that folder, then set the
+ * lastFolderIndexedUrl to the URI of that folder.
+ *
+ * It resets lastFolderIndexedUri to an empty string, then yield returns null
+ * once iteration across all folders is complete.
+ */
+ *_foldersToIndexGenerator() {
+ // Stores whether we're after the last folder indexed or before that --
+ // if the last folder indexed is empty, this needs to be true initially
+ let afterLastFolderIndexed = this._lastFolderIndexedUri.length == 0;
+
+ for (let server of MailServices.accounts.allServers) {
+ this._log.debug(
+ "in find next folder, lastFolderIndexedUri = " +
+ this._lastFolderIndexedUri
+ );
+
+ for (var folder of server.rootFolder.descendants) {
+ let searchPath = this._getSearchPathForFolder(folder);
+ searchPath.leafName = searchPath.leafName + ".mozmsgs";
+ // If after the last folder indexed, definitely index this
+ if (afterLastFolderIndexed) {
+ // Create the folder if it doesn't exist, so that we don't hit the
+ // condition below later
+ if (!searchPath.exists()) {
+ searchPath.create(Ci.nsIFile.DIRECTORY_TYPE, PERM_DIRECTORY);
+ }
+
+ yield folder;
+ // We're back after yielding -- set the last folder indexed
+ this._lastFolderIndexedUri = folder.URI;
+ } else {
+ // If a folder's entire corresponding search results folder is
+ // missing, we need to index it, and force a reindex of all the
+ // messages in it
+ if (!searchPath.exists()) {
+ this._log.debug(
+ "using folder " +
+ folder.URI +
+ " because " +
+ "corresponding search folder does not exist"
+ );
+ // Create the folder, so that next time we're checking we don't hit
+ // this
+ searchPath.create(Ci.nsIFile.DIRECTORY_TYPE, PERM_DIRECTORY);
+ folder.setStringProperty(
+ this._hdrIndexedProperty,
+ "" + Date.now() / 1000
+ );
+ yield folder;
+ } else if (this._pathNeedsReindexing(searchPath)) {
+ // folder may need reindexing for other reasons
+ folder.setStringProperty(
+ this._hdrIndexedProperty,
+ "" + Date.now() / 1000
+ );
+ yield folder;
+ }
+
+ // Even if we yielded above, check if this is the last folder
+ // indexed
+ if (this._lastFolderIndexedUri == folder.URI) {
+ afterLastFolderIndexed = true;
+ }
+ }
+ }
+ }
+ // We're done with one iteration of all the folders; time to reset the
+ // lastFolderIndexedUri
+ this._lastFolderIndexedUri = "";
+ yield null;
+ },
+
+ __foldersToIndex: null,
+ get _foldersToIndex() {
+ if (!this.__foldersToIndex) {
+ this.__foldersToIndex = this._foldersToIndexGenerator();
+ }
+ return this.__foldersToIndex;
+ },
+
+ _findNextHdrToIndex() {
+ try {
+ let reindexTime = this._getLastReindexTime(this._currentFolderToIndex);
+ this._log.debug("Reindex time for this folder is " + reindexTime);
+ if (!this._headerEnumerator) {
+ // we need to create search terms for messages to index
+ let searchSession = Cc[
+ "@mozilla.org/messenger/searchSession;1"
+ ].createInstance(Ci.nsIMsgSearchSession);
+ let searchTerms = [];
+
+ searchSession.addScopeTerm(
+ Ci.nsMsgSearchScope.offlineMail,
+ this._currentFolderToIndex
+ );
+ let nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ let nsMsgSearchOp = Ci.nsMsgSearchOp;
+ // first term: (_hdrIndexProperty < reindexTime)
+ let searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = false; // actually don't care here
+ searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
+ searchTerm.op = nsMsgSearchOp.IsLessThan;
+ let value = searchTerm.value;
+ value.attrib = searchTerm.attrib;
+ searchTerm.hdrProperty = this._hdrIndexedProperty;
+ value.status = reindexTime;
+ searchTerm.value = value;
+ searchTerms.push(searchTerm);
+ this._headerEnumerator =
+ this._currentFolderToIndex.msgDatabase.getFilterEnumerator(
+ searchTerms
+ );
+ }
+
+ // iterate over the folder finding the next message to index
+ for (let msgHdr of this._headerEnumerator) {
+ // Check if the file exists. If it does, then assume indexing to be
+ // complete for this file
+ if (this._getSupportFile(msgHdr).exists()) {
+ this._log.debug(
+ "Message time not set but file exists; setting " +
+ "time to " +
+ reindexTime
+ );
+ msgHdr.setUint32Property(this._hdrIndexedProperty, reindexTime);
+ } else {
+ return [msgHdr, reindexTime];
+ }
+ }
+ } catch (ex) {
+ this._log.debug("Error while finding next header: " + ex);
+ }
+
+ // If we couldn't find any headers to index, null out the enumerator
+ this._headerEnumerator = null;
+ if (!(this._currentFolderToIndex.flags & Ci.nsMsgFolderFlags.Inbox)) {
+ this._currentFolderToIndex.msgDatabase = null;
+ }
+ return null;
+ },
+
+ /**
+ * Get the last reindex time for this folder. This will be whichever's
+ * greater, the global reindex time or the folder reindex time
+ */
+ _getLastReindexTime(aFolder) {
+ let reindexTime = this.globalReindexTime;
+
+ // Check if this folder has a separate string property set
+ let folderReindexTime;
+ try {
+ folderReindexTime = this._currentFolderToIndex.getStringProperty(
+ this._hdrIndexedProperty
+ );
+ } catch (e) {
+ folderReindexTime = "";
+ }
+
+ if (folderReindexTime.length > 0) {
+ let folderReindexTimeInt = parseInt(folderReindexTime);
+ if (folderReindexTimeInt > reindexTime) {
+ reindexTime = folderReindexTimeInt;
+ }
+ }
+ return reindexTime;
+ },
+
+ /**
+ * Whether background indexing has been completed
+ */
+ __backgroundIndexingDone: false,
+
+ /**
+ * The main background sweeping function. It first looks for a folder to
+ * start or continue indexing in, then for a header. If it can't find anything
+ * to index, it resets the last folder indexed URI so that the sweep can
+ * be restarted
+ */
+ _continueSweep() {
+ let msgHdrAndReindexTime = null;
+
+ if (this.__backgroundIndexingDone) {
+ return;
+ }
+
+ // find the current folder we're working on
+ if (!this._currentFolderToIndex) {
+ this._currentFolderToIndex = this._foldersToIndex.next().value;
+ }
+
+ // we'd like to index more than one message on each timer fire,
+ // but since streaming is async, it's hard to know how long
+ // it's going to take to stream any particular message.
+ if (this._currentFolderToIndex) {
+ msgHdrAndReindexTime = this._findNextHdrToIndex();
+ } else {
+ // We've cycled through all the folders. We should take a break
+ // from indexing of existing messages.
+ this.__backgroundIndexingDone = true;
+ }
+
+ if (!msgHdrAndReindexTime) {
+ this._log.debug("reached end of folder");
+ if (this._currentFolderToIndex) {
+ this._currentFolderToIndex = null;
+ }
+ } else {
+ this._queueMessage(msgHdrAndReindexTime[0], msgHdrAndReindexTime[1]);
+ }
+
+ // Restart the timer, and call ourselves
+ this._cancelTimer();
+ this._timer.initWithCallback(
+ this._wrapContinueSweep,
+ this._msgHdrsToIndex.length > 1 ? 5000 : 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ },
+
+ /**
+ * A simple wrapper to make "this" be right for _continueSweep
+ */
+ _wrapContinueSweep() {
+ SearchIntegration._continueSweep();
+ },
+
+ /**
+ * Observer implementation. Consists of
+ * - idle observer; starts running through folders when it receives an "idle"
+ * notification, and cancels any timers when it receives a "back" notification
+ * - msg displayed observer, queues the message if necessary
+ * - pref observer, to see if the preference has been poked
+ */
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "idle") {
+ this._log.debug("Idle detected, continuing sweep");
+ this._continueSweep();
+ } else if (aTopic == "back") {
+ this._log.debug("Non-idle, so suspending sweep");
+ this._cancelTimer();
+ } else if (aTopic == "MsgMsgDisplayed") {
+ this._log.debug("topic = " + aTopic + " uri = " + aData);
+ let msgHdr = this._messenger.msgHdrFromURI(aData);
+ let reindexTime = this._getLastReindexTime(msgHdr.folder);
+ this._log.debug("Reindex time for this folder is " + reindexTime);
+ if (msgHdr.getUint32Property(this._hdrIndexedProperty) < reindexTime) {
+ // Check if the file exists. If it does, then assume indexing to be
+ // complete for this file
+ if (this._getSupportFile(msgHdr).exists()) {
+ this._log.debug(
+ "Message time not set but file exists; setting " +
+ " time to " +
+ reindexTime
+ );
+ msgHdr.setUint32Property(this._hdrIndexedProperty, reindexTime);
+ } else {
+ this._queueMessage(msgHdr, reindexTime);
+ }
+ }
+ } else if (aTopic == "nsPref:changed" && aData == "enable") {
+ let prefEnabled = this.prefEnabled;
+ // Search integration turned on
+ if (prefEnabled && this.register()) {
+ this.enabled = true;
+ } else if (!prefEnabled && this.deregister()) {
+ // Search integration turned off
+ this.enabled = false;
+ } else {
+ // The call to register or deregister has failed.
+ // This is a hack to handle this case
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(
+ function () {
+ SearchIntegration._handleRegisterFailure(!prefEnabled);
+ },
+ 200,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ }
+ }
+ },
+
+ // Handle failure to register or deregister
+ _handleRegisterFailure(enabled) {
+ // Remove ourselves from the observer list, flip the pref,
+ // and add ourselves back
+ this._prefBranch.removeObserver("enable", this);
+ this.prefEnabled = enabled;
+ this._prefBranch.addObserver("enable", this);
+ },
+
+ /**
+ * This object gets notifications for new/moved/copied/deleted messages/folders
+ */
+ _msgFolderListener: {
+ msgAdded(aMsg) {
+ SearchIntegration._log.info("in msgAdded");
+ // The message already being there is an expected case
+ let file = SearchIntegration._getSupportFile(aMsg);
+ if (!file.exists()) {
+ SearchIntegration._queueMessage(
+ aMsg,
+ SearchIntegration._getLastReindexTime(aMsg.folder)
+ );
+ }
+ },
+
+ msgsDeleted(aMsgs) {
+ SearchIntegration._log.info("in msgsDeleted");
+ for (let msgHdr of aMsgs) {
+ let file = SearchIntegration._getSupportFile(msgHdr);
+ if (file.exists()) {
+ file.remove(false);
+ }
+ }
+ },
+
+ msgsMoveCopyCompleted(aMove, aSrcMsgs, aDestFolder) {
+ SearchIntegration._log.info("in msgsMoveCopyCompleted, aMove = " + aMove);
+ // Forget about copies if disabled
+ if (!aMove && !this.enabled) {
+ return;
+ }
+
+ let count = aSrcMsgs.length;
+ for (let i = 0; i < count; i++) {
+ let srcFile = SearchIntegration._getSupportFile(aSrcMsgs[i]);
+ if (srcFile && srcFile.exists()) {
+ let destFile = SearchIntegration._getSearchPathForFolder(aDestFolder);
+ destFile.leafName = destFile.leafName + ".mozmsgs";
+ if (!destFile.exists()) {
+ try {
+ // create the directory, if it doesn't exist
+ destFile.create(Ci.nsIFile.DIRECTORY_TYPE, PERM_DIRECTORY);
+ } catch (ex) {
+ SearchIntegration._log.warn(ex);
+ }
+ }
+ SearchIntegration._log.debug("dst file path = " + destFile.path);
+ SearchIntegration._log.debug("src file path = " + srcFile.path);
+ // We're not going to copy in case we're not in active mode
+ if (destFile.exists()) {
+ if (aMove) {
+ srcFile.moveTo(destFile, "");
+ } else {
+ srcFile.copyTo(destFile, "");
+ }
+ }
+ }
+ }
+ },
+
+ folderDeleted(aFolder) {
+ SearchIntegration._log.info(
+ "in folderDeleted, folder name = " + aFolder.prettyName
+ );
+ let srcFile = SearchIntegration._getSearchPathForFolder(aFolder);
+ srcFile.leafName = srcFile.leafName + ".mozmsgs";
+ if (srcFile.exists()) {
+ srcFile.remove(true);
+ }
+ },
+
+ folderMoveCopyCompleted(aMove, aSrcFolder, aDestFolder) {
+ SearchIntegration._log.info(
+ "in folderMoveCopyCompleted, aMove = " + aMove
+ );
+
+ // Forget about copies if disabled
+ if (!aMove && !this.enabled) {
+ return;
+ }
+
+ let srcFile = SearchIntegration._getSearchPathForFolder(aSrcFolder);
+ let destFile = SearchIntegration._getSearchPathForFolder(aDestFolder);
+ srcFile.leafName = srcFile.leafName + ".mozmsgs";
+ destFile.leafName += ".sbd";
+ SearchIntegration._log.debug("src file path = " + srcFile.path);
+ SearchIntegration._log.debug("dst file path = " + destFile.path);
+ if (srcFile.exists()) {
+ // We're not going to copy if we aren't in active mode
+ if (aMove) {
+ srcFile.moveTo(destFile, "");
+ } else {
+ srcFile.copyTo(destFile, "");
+ }
+ }
+ },
+
+ folderRenamed(aOrigFolder, aNewFolder) {
+ SearchIntegration._log.info(
+ "in folderRenamed, aOrigFolder = " +
+ aOrigFolder.prettyName +
+ ", aNewFolder = " +
+ aNewFolder.prettyName
+ );
+ let srcFile = SearchIntegration._getSearchPathForFolder(aOrigFolder);
+ srcFile.leafName = srcFile.leafName + ".mozmsgs";
+ let destName = aNewFolder.name + ".mozmsgs";
+ SearchIntegration._log.debug("src file path = " + srcFile.path);
+ SearchIntegration._log.debug("dst name = " + destName);
+ if (srcFile.exists()) {
+ srcFile.moveTo(null, destName);
+ }
+ },
+ },
+
+ /*
+ * Support functions to queue/generate files
+ */
+ _queueMessage(msgHdr, reindexTime) {
+ if (this._msgHdrsToIndex.push([msgHdr, reindexTime]) == 1) {
+ this._log.info("generating support file for id = " + msgHdr.messageId);
+ this._streamListener.startStreaming(msgHdr, reindexTime);
+ } else {
+ this._log.info(
+ "queueing support file generation for id = " + msgHdr.messageId
+ );
+ }
+ },
+
+ /**
+ * Handle results from the command line. This method is the inverse of the
+ * _getSupportFile method below.
+ *
+ * @param aFile the file passed in by the command line
+ * @returns the nsIMsgDBHdr corresponding to the file passed in
+ */
+ handleResult(aFile) {
+ // The file path has two components -- the search path, which needs to be
+ // converted into a folder, and the message ID.
+ let searchPath = aFile.parent;
+ // Strip off ".mozmsgs" from the end (8 characters)
+ searchPath.leafName = searchPath.leafName.slice(0, -8);
+
+ let folder = this._getFolderForSearchPath(searchPath);
+
+ // Get rid of the file extension at the end (7 characters), and unescape
+ let messageID = decodeURIComponent(aFile.leafName.slice(0, -7));
+
+ // Look for the message ID in the folder
+ return folder.msgDatabase.getMsgHdrForMessageID(messageID);
+ },
+
+ _getSupportFile(msgHdr) {
+ let folder = msgHdr.folder;
+ if (folder) {
+ let messageId = encodeURIComponent(msgHdr.messageId);
+ this._log.debug("encoded message id = " + messageId);
+ let file = this._getSearchPathForFolder(folder);
+ file.leafName = file.leafName + ".mozmsgs";
+ file.appendRelativePath(messageId + this._fileExt);
+ this._log.debug("getting support file path = " + file.path);
+ return file;
+ }
+ return null;
+ },
+
+ /**
+ * Base to use for stream listeners, extended by the respective
+ * implementations
+ */
+ _streamListenerBase: {
+ // Output file
+ _outputFile: null,
+
+ // Stream to use to write to the output file
+ __outputStream: null,
+ set _outputStream(stream) {
+ if (this.__outputStream) {
+ this.__outputStream.close();
+ }
+ this.__outputStream = stream;
+ },
+ get _outputStream() {
+ return this.__outputStream;
+ },
+
+ // Reference to message header
+ _msgHdr: null,
+
+ // Reindex time for this message header
+ _reindexTime: null,
+
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+
+ // "Finish" function, cleans up behind itself if unsuccessful
+ _onDoneStreaming(successful) {
+ this._outputStream = null;
+ if (!successful && this._msgHdr) {
+ let file = SearchIntegration._getSupportFile(this._msgHdr);
+ if (file && file.exists()) {
+ file.remove(false);
+ }
+ }
+ // should we try to delete the file on disk in case not successful?
+ SearchIntegration._msgHdrsToIndex.shift();
+
+ if (SearchIntegration._msgHdrsToIndex.length > 0) {
+ let [msgHdr, reindexTime] = SearchIntegration._msgHdrsToIndex[0];
+ this.startStreaming(msgHdr, reindexTime);
+ }
+ },
+
+ // "Start" function
+ startStreaming(msgHdr, reindexTime) {
+ try {
+ let folder = msgHdr.folder;
+ if (folder) {
+ let messageId = encodeURIComponent(msgHdr.messageId);
+ SearchIntegration._log.info(
+ "generating support file, id = " + messageId
+ );
+ let file = SearchIntegration._getSearchPathForFolder(folder);
+
+ file.leafName = file.leafName + ".mozmsgs";
+ SearchIntegration._log.debug("file leafname = " + file.leafName);
+ if (!file.exists()) {
+ try {
+ // create the directory, if it doesn't exist
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, PERM_DIRECTORY);
+ } catch (ex) {
+ this._log.error(ex);
+ }
+ }
+
+ file.appendRelativePath(messageId + SearchIntegration._fileExt);
+ SearchIntegration._log.debug("file path = " + file.path);
+ file.create(0, PERM_FILE);
+ let uri = folder.getUriForMsg(msgHdr);
+ let msgService = MailServices.messageServiceFromURI(uri);
+ this._msgHdr = msgHdr;
+ this._outputFile = file;
+ this._reindexTime = reindexTime;
+ try {
+ // XXX For now, try getting the messages from the server. This has
+ // to be improved so that we don't generate any excess network
+ // traffic
+ msgService.streamMessage(uri, this, null, null, false, "", false);
+ } catch (ex) {
+ // This is an expected case, in case we're offline
+ SearchIntegration._log.warn(
+ "StreamMessage unsuccessful for id = " + messageId
+ );
+ this._onDoneStreaming(false);
+ }
+ }
+ } catch (ex) {
+ SearchIntegration._log.error(ex);
+ this._onDoneStreaming(false);
+ }
+ },
+ },
+
+ /**
+ * Logging functionality, shamelessly ripped from gloda
+ * If enabled, warnings and above are logged to the error console, while dump
+ * gets everything
+ */
+ _log: null,
+ _initLogging() {
+ this._log = console.createInstance({
+ prefix: this._prefBase.slice(0, -1),
+ maxLogLevel: "Warn",
+ maxLogLevelPref: `${this._prefBase}loglevel`,
+ });
+ this._log.info("Logging initialized");
+ },
+};
+
+if (AppConstants.platform == "win") {
+ Services.scriptloader.loadSubScript(
+ "chrome://messenger/content/WinSearchIntegration.js"
+ );
+} else if (AppConstants.platform == "macosx") {
+ Services.scriptloader.loadSubScript(
+ "chrome://messenger/content/SpotlightIntegration.js"
+ );
+}