summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/search/content/SpotlightIntegration.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/search/content/SpotlightIntegration.js')
-rw-r--r--comm/mail/components/search/content/SpotlightIntegration.js240
1 files changed, 240 insertions, 0 deletions
diff --git a/comm/mail/components/search/content/SpotlightIntegration.js b/comm/mail/components/search/content/SpotlightIntegration.js
new file mode 100644
index 0000000000..0757800ee6
--- /dev/null
+++ b/comm/mail/components/search/content/SpotlightIntegration.js
@@ -0,0 +1,240 @@
+/* -*- 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/. */
+
+// SearchIntegration.jsm
+/* globals SearchIntegration, SearchSupport, Services */
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var MSG_DB_LARGE_COMMIT = 1;
+var gFileHeader =
+ '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.\ncom/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>';
+
+// eslint-disable-next-line no-global-assign
+SearchIntegration = {
+ __proto__: SearchSupport,
+
+ // The property of the header and (sometimes) folders that's used to check
+ // if a message is indexed
+ _hdrIndexedProperty: "spotlight_reindex_time",
+
+ // The file extension that is used for support files of this component
+ _fileExt: ".mozeml",
+
+ // The Spotlight pref base
+ _prefBase: "mail.spotlight.",
+
+ // The user's profile dir, which we'll cache and use a lot for path clean-up
+ get _profileDir() {
+ delete this._profileDir;
+ return (this._profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile));
+ },
+
+ get _metadataDir() {
+ delete this._metadataDir;
+ let metadataDir = Services.dirsvc.get("Home", Ci.nsIFile);
+ metadataDir.append("Library");
+ metadataDir.append("Caches");
+ metadataDir.append("Metadata");
+ metadataDir.append("Thunderbird");
+ return (this._metadataDir = metadataDir);
+ },
+
+ // Spotlight won't index files in the profile dir, but will use ~/Library/Caches/Metadata
+ _getSearchPathForFolder(aFolder) {
+ // Swap the metadata dir for the profile dir prefix in the folder's path
+ let folderPath = aFolder.filePath.path;
+ let fixedPath = folderPath.replace(
+ this._profileDir.path,
+ this._metadataDir.path
+ );
+ let searchPath = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ searchPath.initWithPath(fixedPath);
+ return searchPath;
+ },
+
+ // Replace ~/Library/Caches/Metadata with the profile directory, then convert
+ _getFolderForSearchPath(aPath) {
+ let folderPath = aPath.path.replace(
+ this._metadataDir.path,
+ this._profileDir.path
+ );
+ let folderFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ folderFile.initWithPath(folderPath);
+ return MailUtils.getFolderForFileInProfile(folderFile);
+ },
+
+ _pathNeedsReindexing(aPath) {
+ // We used to set permissions incorrectly (see bug 670566).
+ const PERM_DIRECTORY = parseInt("0755", 8);
+ if (aPath.permissions != PERM_DIRECTORY) {
+ aPath.permissions = PERM_DIRECTORY;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * These two functions won't do anything, as Spotlight integration is handled
+ * using Info.plist files
+ */
+ register() {
+ return true;
+ },
+
+ deregister() {
+ return true;
+ },
+
+ _init() {
+ this._initLogging();
+
+ let enabled = this._prefBranch.getBoolPref("enable", false);
+ if (enabled) {
+ this._log.info("Initializing Spotlight integration");
+ }
+ this._initSupport(enabled);
+ },
+
+ // The stream listener to read messages
+ _streamListener: {
+ __proto__: SearchSupport._streamListenerBase,
+
+ // Buffer to store the message
+ _message: null,
+
+ // Encodes reserved XML characters
+ _xmlEscapeString(s) {
+ return s.replace(/[<>&]/g, function (s) {
+ switch (s) {
+ case "<":
+ return "&lt;";
+ case ">":
+ return "&gt;";
+ case "&":
+ return "&amp;";
+ default:
+ throw new Error("Unexpected match");
+ }
+ });
+ },
+
+ onStartRequest(request) {
+ try {
+ let outputFileStream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ outputFileStream.init(this._outputFile, -1, -1, 0);
+ this._outputStream = Cc[
+ "@mozilla.org/intl/converter-output-stream;1"
+ ].createInstance(Ci.nsIConverterOutputStream);
+ this._outputStream.init(outputFileStream, "UTF-8");
+
+ this._outputStream.writeString(gFileHeader);
+ this._outputStream.writeString(
+ "<key>kMDItemLastUsedDate</key><string>"
+ );
+ // need to write the date as a string
+ let curTimeStr = new Date().toLocaleString();
+ this._outputStream.writeString(curTimeStr);
+
+ // need to write the subject in utf8 as the title
+ this._outputStream.writeString(
+ "</string>\n<key>kMDItemTitle</key>\n<string>"
+ );
+
+ let escapedSubject = this._xmlEscapeString(
+ this._msgHdr.mime2DecodedSubject
+ );
+ this._outputStream.writeString(escapedSubject);
+
+ this._outputStream.writeString(
+ "</string>\n<key>kMDItemDisplayName</key>\n<string>"
+ );
+ this._outputStream.writeString(escapedSubject);
+
+ this._outputStream.writeString(
+ "</string>\n<key>kMDItemTextContent</key>\n<string>"
+ );
+ this._outputStream.writeString(
+ this._xmlEscapeString(this._msgHdr.mime2DecodedAuthor)
+ );
+ this._outputStream.writeString(
+ this._xmlEscapeString(this._msgHdr.mime2DecodedRecipients)
+ );
+
+ this._outputStream.writeString(escapedSubject);
+ this._outputStream.writeString(" ");
+ } catch (ex) {
+ this._onDoneStreaming(false);
+ }
+ },
+
+ onStopRequest(request, status) {
+ try {
+ // we want to write out the from, to, cc, and subject headers into the
+ // Text Content value, so they'll be indexed.
+ let stringStream = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ stringStream.setData(this._message, this._message.length);
+ let folder = this._msgHdr.folder;
+ let text = folder.getMsgTextFromStream(
+ stringStream,
+ this._msgHdr.charset,
+ 20000,
+ 20000,
+ false,
+ true,
+ {}
+ );
+ text = this._xmlEscapeString(text);
+ SearchIntegration._log.debug(
+ "escaped text = *****************\n" + text
+ );
+ this._outputStream.writeString(text);
+ // close out the content, dict, and plist
+ this._outputStream.writeString("</string>\n</dict>\n</plist>\n");
+
+ this._msgHdr.setUint32Property(
+ SearchIntegration._hdrIndexedProperty,
+ this._reindexTime
+ );
+ folder.msgDatabase.commit(MSG_DB_LARGE_COMMIT);
+
+ this._message = "";
+ } catch (ex) {
+ SearchIntegration._log.error(ex);
+ this._onDoneStreaming(false);
+ return;
+ }
+ this._onDoneStreaming(true);
+ },
+
+ onDataAvailable(request, inputStream, offset, count) {
+ try {
+ let inStream = Cc[
+ "@mozilla.org/scriptableinputstream;1"
+ ].createInstance(Ci.nsIScriptableInputStream);
+ inStream.init(inputStream);
+
+ // It is necessary to read in data from the input stream
+ let inData = inStream.read(count);
+
+ // ignore stuff after the first 20K or so
+ if (this._message && this._message.length > 20000) {
+ return;
+ }
+
+ this._message += inData;
+ } catch (ex) {
+ SearchIntegration._log.error(ex);
+ this._onDoneStreaming(false);
+ }
+ },
+ },
+};
+
+SearchIntegration._init();