/* -*- 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 = '\n\n\n'; // 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 "<"; case ">": return ">"; case "&": return "&"; 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( "kMDItemLastUsedDate" ); // 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( "\nkMDItemTitle\n" ); let escapedSubject = this._xmlEscapeString( this._msgHdr.mime2DecodedSubject ); this._outputStream.writeString(escapedSubject); this._outputStream.writeString( "\nkMDItemDisplayName\n" ); this._outputStream.writeString(escapedSubject); this._outputStream.writeString( "\nkMDItemTextContent\n" ); 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("\n\n\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();