diff --git a/mozilla/mail/components/Makefile.in b/mozilla/mail/components/Makefile.in index 9b5154a1e4c..a8e546334d1 100644 --- a/mozilla/mail/components/Makefile.in +++ b/mozilla/mail/components/Makefile.in @@ -52,6 +52,11 @@ ifdef MOZ_SAFE_BROWSING DIRS += phishing endif +# Mac and Windows have search integration components +ifneq (,$(filter windows cocoa mac, $(MOZ_WIDGET_TOOLKIT))) +DIRS += search +endif + DIRS += build EXTRA_PP_COMPONENTS = nsMailDefaultHandler.js diff --git a/mozilla/mail/components/search/Makefile.in b/mozilla/mail/components/search/Makefile.in index c5bb2fd9a2c..ab827d8a34b 100644 --- a/mozilla/mail/components/search/Makefile.in +++ b/mozilla/mail/components/search/Makefile.in @@ -54,4 +54,12 @@ EXTRA_PP_COMPONENTS = \ $(NULL) endif +# If on Windows, build Windows Search integration +ifneq (,$(filter windows, $(MOZ_WIDGET_TOOLKIT))) +# Windows Search component +EXTRA_PP_COMPONENTS = \ + nsWinSearchIntegration.js \ + $(NULL) +endif + include $(topsrcdir)/config/rules.mk diff --git a/mozilla/mail/components/search/content/searchCommon.js b/mozilla/mail/components/search/content/searchCommon.js new file mode 100644 index 00000000000..a6b080d0408 --- /dev/null +++ b/mozilla/mail/components/search/content/searchCommon.js @@ -0,0 +1,413 @@ +#if 0 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is spotlight integration code. + * + * The Initial Developer of the Original Code is + * David Bienvenu + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Siddharth Agarwal + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * 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 + */ +#endif + +const Cc = Components.classes; +const Ci = Components.interfaces; + +var gCurrentFolderToIndex; +var gLastFolderIndexedUri = ""; // this is stored in a pref +var gHeaderEnumerator; +var gMsgHdrsToIndex; +var gMessenger; +var gAlarm; +var gBackgroundIndexingDone; +var gPrefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).getBranch(null); + +/* + * Init function -- this should be called from the component's init function + */ +function InitSupportIntegration() +{ + gMessenger = Cc["@mozilla.org/messenger;1"].createInstance().QueryInterface(Ci.nsIMessenger); + + var notificationService = Cc["@mozilla.org/messenger/msgnotificationservice;1"] + .getService(Ci.nsIMsgFolderNotificationService); + notificationService.addListener(gFolderListener); + var ObserverService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + ObserverService.addObserver(MsgMsgDisplayedObserver, "MsgMsgDisplayed", false); + gMsgHdrsToIndex = new Array(); + + restartTimer(60); +} + +/* + * These functions are to index already existing messages + */ +function FindNextFolderToIndex() +{ + accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager); + var servers = accountManager.allServers; + var foundFolder = false; + var useNextFolder = false; + + for (var i = 0; i < servers.Count() && !foundFolder; i++) + { + var server = servers.QueryElementAt(i, Ci.nsIMsgIncomingServer); + var rootFolder = server.rootFolder; + var allFolders = Cc["@mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray); + rootFolder.ListDescendents(allFolders); + var numFolders = allFolders.Count(); + SIDump("in find next folder, gLastFolderIndexedUri = " + gLastFolderIndexedUri + "\n"); + for (var folderIndex = 0; folderIndex < numFolders && !foundFolder; folderIndex++) + { + var folder = allFolders.GetElementAt(folderIndex).QueryInterface(Ci.nsIMsgFolder); + // if no folder was indexed (or the pref's not set), just use the first folder + if (!gLastFolderIndexedUri.length || useNextFolder) + { + gCurrentFolderToIndex = folder; + foundFolder = true; + } + else + { + if (gLastFolderIndexedUri == folder.URI) + useNextFolder = true; + } + } + } + if (!foundFolder) + gCurrentFolderToIndex = null; +} + +function FindNextHdrToIndex() +{ + try + { + if (!gHeaderEnumerator) + gHeaderEnumerator = gCurrentFolderToIndex.getMessages(null); + + // iterate over the folder finding the next message to index + while (gHeaderEnumerator.hasMoreElements()) + { + var msgHdr = gHeaderEnumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr); + if (!msgHdr.getUint32Property(gHdrIndexedProperty)) + return msgHdr; + } + } + catch(ex) {} + gHeaderEnumerator = null; + return null; +} + +function onTimer() +{ + var msgHdrToIndex = null; + + if (gBackgroundIndexingDone) + return; + + // find the current folder we're working on + if (!gCurrentFolderToIndex) + FindNextFolderToIndex(); + + // 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 (gCurrentFolderToIndex) + { + var msgHdrToIndex = FindNextHdrToIndex(); + } + else + { + // we've cycled through all the folders, we should take a break + // from indexing of existing messages + gBackgroundIndexingDone = true; + gPrefBranch.setCharPref(gPrefBase + ".lastFolderIndexedUri", ""); + } + if (!msgHdrToIndex) + { + SIDump("reached end of folder\n"); + if (gCurrentFolderToIndex) + { + gLastFolderIndexedUri = gCurrentFolderToIndex.URI; + gPrefBranch.setCharPref(gPrefBase + ".lastFolderIndexedUri", gLastFolderIndexedUri); + gCurrentFolderToIndex = null; + } + } + else + { + QueueMessageToGetIndexed(msgHdrToIndex); + } + restartTimer(gMsgHdrsToIndex.length > 1 ? 5 : 1); +} + +function restartTimer(seconds) +{ + if (gAlarm) + gAlarm.cancel(); + var jslib = Cc["@mozilla.org/url-classifier/jslib;1"] + .getService().wrappedJSObject; + + gAlarm = new jslib.G_Alarm(onTimer, seconds*1000); +} + +/* + * This object gets notifications for messages that are read, giving them a + * higher priority + */ +var MsgMsgDisplayedObserver = +{ + // Components.interfaces.nsIObserver + observe: function(aHeaderSink, aTopic, aData) + { + // if the user is reading messages, we're not idle, so restart timer. + restartTimer(60); + SIDump("topic = " + aTopic + " uri = " + aData + "\n"); + var msgHdr = gMessenger.msgHdrFromURI(aData); + var indexed = msgHdr.getUint32Property(gHdrIndexedProperty); + if (!indexed) + { + var file = GetSupportFileForMsgHdr(msgHdr); + if (!file.exists()) + QueueMessageToGetIndexed(msgHdr); + } + } +}; + +/* + * This object gets notifications for new/moved/copied/deleted messages/folders + */ +var gFolderListener = { + msgAdded: function(aMsg) + { + SIDump("in msgAdded\n"); + restartTimer(30); + // The message already being there is an expected case + var file = GetSupportFileForMsgHdr(aMsg); + if (!file.exists()) + QueueMessageToGetIndexed(aMsg); + }, + + msgsDeleted: function(aMsgs) + { + SIDump("in msgsDeleted\n"); + // mail getting deleted, we're not idle, so restart timer. + restartTimer(60); + var count = aMsgs.length; + for (var i = 0; i < count; i++) + { + var file = GetSupportFileForMsgHdr(aMsgs.queryElementAt(i, Ci.nsIMsgDBHdr)); + if (file.exists()) + file.remove(false); + } + }, + + msgsMoveCopyCompleted: function(aMove, aSrcMsgs, aDestFolder) + { + SIDump("in msgsMoveCopyCompleted\n"); + var count = aSrcMsgs.length; + for (var i = 0; i < count; i++) + { + var msg = aSrcMsgs.queryElementAt(i, Ci.nsIMsgDBHdr); + var srcFile = GetSupportFileForMsgHdr(msg); + if (srcFile && srcFile.exists()) + { + var destFile = aDestFolder.filePath; + destFile.leafName = destFile.leafName + ".mozmsgs"; + if (!destFile.exists()) + { + try + { + // create the directory, if it doesn't exist + destFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0644); + } + catch(ex) {SIDump(ex);} + } + SIDump ("dst file path = " + destFile.path + "\n"); + SIDump ("src file path = " + srcFile.path + "\n"); + if (destFile.exists()) + if (aMove) + srcFile.moveTo(destFile, ""); + else + srcFile.copyTo(destFile, ""); + } + } + restartTimer(30); + SIDump("moveCopyCompleted move = " + aMove + "\n"); + }, + + folderDeleted: function(aFolder) + { + SIDump("in folderDeleted, folder name = " + aFolder.prettiestName + "\n"); + var srcFile = aFolder.filePath; + srcFile.leafName = srcFile.leafName + ".mozmsgs"; + srcFile.remove(true); + }, + + folderMoveCopyCompleted: function(aMove, aSrcFolder, aDestFolder) + { + SIDump("in folderMoveCopyCompleted, aMove = " + aMove + "\n"); + var srcFile = aSrcFolder.filePath; + var destFile = aDestFolder.filePath; + srcFile.leafName = srcFile.leafName + ".mozmsgs"; + destFile.leafName += ".sbd"; + SIDump("src file path = " + srcFile.path + "\n"); + SIDump("dst file path = " + destFile.path + "\n"); + if (srcFile.exists()) + { + if (aMove) + srcFile.moveTo(destFile, ""); + else + srcFile.copyTo(destFile, ""); + } + }, + + folderRenamed: function(aOrigFolder, aNewFolder) + { + SIDump("in folderRenamed, aOrigFolder = "+aOrigFolder.prettiestName+", aNewFolder = "+aNewFolder.prettiestName+"\n"); + var srcFile = aOrigFolder.filePath; + srcFile.leafName = srcFile.leafName + ".mozmsgs"; + var destName = aNewFolder.name + ".mozmsgs"; + SIDump("src file path = " + srcFile.path + "\n"); + SIDump("dst name = " + destName + "\n"); + if (srcFile.exists()) + srcFile.moveTo(null, destName); + }, + + itemEvent: function(aItem, aEvent, aData) + { + SIDump("in itemEvent, aItem = "+aItem+", aEvent = "+aEvent+", aData = "+aData+"\n"); + } +}; + +/* + * Support functions to queue/generate files + */ +function QueueMessageToGetIndexed(msgHdr) +{ + if (gMsgHdrsToIndex.push(msgHdr) == 1) + { + SIDump("generating support file\n"); + GenerateSupportFile(msgHdr); + } + else + SIDump("queueing support file generation\n"); +} + +function GetSupportFileForMsgHdr(msgHdr) +{ + var folder = msgHdr.folder; + if (folder) + { + var messageId = msgHdr.messageId; + messageId = encodeURIComponent(messageId); + SIDump("encoded message id = " + messageId + "\n"); + // this should work on the trunk, but not in 2.0 +// messageId = netUtils.escapeString(messageId, 3 /* netUtils.ESCAPE_URL_PATH */); + if (folder) + { + var file = folder.filePath; + file.leafName = file.leafName + ".mozmsgs"; + file.appendRelativePath(messageId + gFileExt); + SIDump("getting support file path = " + file.path + "\n"); + return file; + } + } + return null; +} + +const MSG_FLAG_HAS_RE = 0x0010; + +function GenerateSupportFile(msgHdr) +{ + try + { + var folder = msgHdr.folder; + if (folder) + { + var messageId = msgHdr.messageId; + // for the trunk, this should work + // var netUtils = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsINetUtil); + // messageId = netUtils.escapeString(messageId, netUtils.ESCAPE_URL_PATH); + messageId = encodeURIComponent(messageId); + // We don't require the subject for this, keeping it if necessary later. + // gStreamListener.subject = ((msgHdr.flags & MSG_FLAG_HAS_RE) ? "Re: " : "") + msgHdr.mime2DecodedSubject; + SIDump("generate support file, message id = " + messageId + "\n"); + var file = folder.filePath; + + file.leafName = file.leafName + ".mozmsgs"; + SIDump("file leafname = " + file.leafName + "\n"); + if (!file.exists()) + { + try + { + // create the directory, if it doesn't exist + file.create(Ci.nsIFile.DIRECTORY_TYPE, 0644); + } + catch(ex) {SIDump(ex);} + } + gStreamListener.msgHdr = msgHdr; + + file.appendRelativePath(messageId + gFileExt); + //file.leafName = messageId + gFileExt; + SIDump("file path = " + file.path + "\n"); + file.create(0, 0644); + var uri = folder.getUriForMsg(msgHdr); + //SIDump("in onItemAdded messenger = " + messenger + "\n"); + var msgService = gMessenger.messageServiceFromURI(uri); + gStreamListener.outputFile = file; + + msgService.streamMessage(uri, gStreamListener, null, null, false, "", null); + } + } + catch (ex) + { + SIDump(ex); + gStreamListener.onDoneStreamingCurMessage(false); + } +} + +/* Debug function */ +var gSIDump = true; +function SIDump(str) +{ + if (gSIDump) + dump(str); +} diff --git a/mozilla/mail/components/search/nsSpotlightIntegration.js b/mozilla/mail/components/search/nsSpotlightIntegration.js index 7ac9b994d72..929e6743c63 100644 --- a/mozilla/mail/components/search/nsSpotlightIntegration.js +++ b/mozilla/mail/components/search/nsSpotlightIntegration.js @@ -1,4 +1,4 @@ -# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # @@ -20,6 +20,7 @@ # the Initial Developer. All Rights Reserved. # # Contributor(s): +# Siddharth Agarwal # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or @@ -35,216 +36,34 @@ # # ***** END LICENSE BLOCK ***** -const Cc = Components.classes; -const Ci = Components.interfaces; +#include content/searchCommon.js + const MSG_DB_LARGE_COMMIT = 1; -// Module object -var SpotlightIntegrationMod = { - firstTime: true, - cid : Components.ID("{42EFAD76-FCDC-4757-951E-26896759E87E}"), - progid: "@mozilla.org/desktop-search-integration;1", - className: "Spotlight Integration", +// The property of the header that's used to check if a message is indexed +const gHdrIndexedProperty = "indexed"; -factory: - { - createInstance: function (aOuter, aIID) - { - if (aOuter != null) - throw Components.results.NS_ERROR_NO_AGGREGATION; - if (!aIID.equals(Components.interfaces.nsISupports)) - throw Components.results.NS_ERROR_INVALID_ARG; - - InitSpotlightIntegration(); - // return the singleton - return nsSpotlightIntegration.QueryInterface(aIID); - } - }, // factory - +// The file extension that is used for support files of this component +const gFileExt = ".mozeml"; - getClassObject: function(aCompMgr, aCID, aIID) - { - if (!aIID.equals(Components.interfaces.nsIFactory)) - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - - if (aCID.equals(this.cid)) - return this.factory; - - throw Components.results.NS_ERROR_NO_INTERFACE; - }, +// The pref base +const gPrefBase = "mail.spotlight"; - registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) - { - aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); - aCompMgr.registerFactoryLocation(this.cid, this.className, this.progid, aFileSpec, aLocation, aType); - }, - - unregisterSelf: function(aCompMgr, aFileSpec, aLocation) - { - aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); - aCompMgr.unregisterFactoryLocation(this.cid, aFileSpec); - }, - - canUnload: function(aCompMgr) - { - return true; - } -}; - -function NSGetModule(aCompMgr, aFileSpec) -{ - return SpotlightIntegrationMod; -} - -var nsSpotlightIntegration = { - - QueryInterface: function(aIID) - { - if (aIID.equals(Components.interfaces.nsISupports)) - return this; - - Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; - return null; - } -} - -var gCurrentFolderToIndex; -var gLastFolderIndexedUri = ""; // this is stored in the pref "mail.spotlight.lastFolderIndexedUri" -var gHeaderEnumerator; -var gPrefBranch = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch(null); -var gIndexMsgsToSpotlight; -var gAlarm; -var gBackgroundIndexingDone; -var gMessenger; +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); function InitSpotlightIntegration() { - SIDump("initializing spotlight integration\n"); - + var enabled; try { - gIndexMsgsToSpotlight = gPrefBranch.getBoolPref("mail.spotlight.enable"); - gLastFolderIndexedUri = gPrefBranch.getCharPref("mail.spotlight.lastFolderIndexedUri"); + enabled = gPrefBranch.getBoolPref(gPrefBase + ".enable"); + gLastFolderIndexedUri = gPrefBranch.getCharPref(gPrefBase + ".lastFolderIndexedUri"); } catch (ex) {} - - if (!gIndexMsgsToSpotlight) + + if (!enabled) return; - var nsIFolderListener = Components.interfaces.nsIFolderListener; - gMessenger = Components.classes["@mozilla.org/messenger;1"].createInstance().QueryInterface(Components.interfaces.nsIMessenger); - var notificationService = Components.classes["@mozilla.org/messenger/msgnotificationservice;1"].getService(Components.interfaces.nsIMsgFolderNotificationService); - notificationService.addListener(gFolderListener); - var ObserverService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService); - ObserverService.addObserver(CreateMsgDisplayedObserver, "MsgMsgDisplayed", false); - gMsgHdrsToIndex = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray); - - restartTimer(60); -} - -function FindNextFolderToIndex() -{ - accountManager = Components.classes["@mozilla.org/messenger/account-manager;1"].getService(Components.interfaces.nsIMsgAccountManager); - var servers = accountManager.allServers; - var foundFolder = false; - var useNextFolder = false; - - for (var i = 0; i < servers.Count() && !foundFolder; i++) - { - var server = servers.QueryElementAt(i, Components.interfaces.nsIMsgIncomingServer); - var rootFolder = server.rootFolder; - var allFolders = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray); - rootFolder.ListDescendents(allFolders); - var numFolders = allFolders.Count(); - SIDump("in find next folder, gLastFolderIndexedUri = " + gLastFolderIndexedUri + "\n"); - for (var folderIndex = 0; folderIndex < numFolders && !foundFolder; folderIndex++) - { - var folder = allFolders.GetElementAt(folderIndex).QueryInterface(Components.interfaces.nsIMsgFolder); - // if no folder was indexed (or the pref's not set), just use the first folder - if (!gLastFolderIndexedUri.length || useNextFolder) - { - gCurrentFolderToIndex = folder; - foundFolder = true; - } - else - { - if (gLastFolderIndexedUri == folder.URI) - useNextFolder = true; - } - } - } - -} - -function FindNextHdrToIndex() -{ - if (!gHeaderEnumerator) - { - var msgDB = gCurrentFolderToIndex.getMsgDatabase(null); - gHeaderEnumerator = msgDB.EnumerateMessages(); - } - // iterate over the folder finding the next message to - // index... - while (gHeaderEnumerator.hasMoreElements()) - { - var msgHdr = gHeaderEnumerator.getNext().QueryInterface(Components.interfaces.nsIMsgDBHdr); - if (!msgHdr.getUint32Property("indexed")) - return msgHdr; - } - gHeaderEnumerator = null; - return null; -} - -function onTimer() -{ - var msgHdrToIndex = null; - - if (gBackgroundIndexingDone) - return; - - // find the current folder we're working on - if (!gCurrentFolderToIndex) - FindNextFolderToIndex(); - - - // 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. Mozilla has no way of telling - // us when the system is idle. - if (gCurrentFolderToIndex) - { - var msgHdrToIndex = FindNextHdrToIndex(); - } - else - { - // we've cycled through all the folders, we should take a break - // from indexing of existing messages - gBackgroundIndexingDone = true; - - } - if (!msgHdrToIndex) - { - SIDump("reached end of folder\n"); - if (gCurrentFolderToIndex) - { - gLastFolderIndexedUri = gCurrentFolderToIndex.URI; - gPrefBranch.setCharPref("mail.spotlight.lastFolderIndexedUri", gLastFolderIndexedUri); - gCurrentFolderToIndex = null; - } - } - else - { - QueueMessageToGetIndexed(msgHdrToIndex); - } - restartTimer(gMsgHdrsToIndex.Count() > 1 ? 5 : 1); -} - -function restartTimer(seconds) -{ - if (gAlarm) - gAlarm.cancel(); - var jslib = Cc["@mozilla.org/url-classifier/jslib;1"] - .getService().wrappedJSObject; - - gAlarm = new jslib.G_Alarm(onTimer, seconds*1000); + SIDump("initializing spotlight integration\n"); + InitSupportIntegration(); } function xmlEscapeString(s) @@ -252,32 +71,9 @@ function xmlEscapeString(s) s = s.replace(/&/g, "&"); s = s.replace(/>/g, ">"); s = s.replace(/\n\n\n"; var gStreamListener = { @@ -293,27 +89,22 @@ onDoneStreamingCurMessage: function(successful) { if (!successful && this.msgHdr) { - var file = GetSpotlightFileForMsgHdr(this.msgHdr); + var file = GetSupportFileForMsgHdr(this.msgHdr); if (file && file.exists()) file.remove(false); } // should we try to delete the file on disk in case not successful? - gMsgHdrsToIndex.DeleteElementAt(0); - - if (gMsgHdrsToIndex.GetElementAt(0)) - { - var msgHdr = gMsgHdrsToIndex.QueryElementAt(0, Components.interfaces.nsIMsgDBHdr); - GenerateSpotlightFile(msgHdr); - } + gMsgHdrsToIndex.shift(); + + if (gMsgHdrsToIndex.length > 0) + GenerateSupportFile(gMsgHdrsToIndex[0]); }, - - - + QueryInterface: function(aIId, instance) { - if (aIId.equals(Components.interfaces.nsIStreamListener) || - aIId.equals(Components.interfaces.nsISupports)) + if (aIId.equals(Ci.nsIStreamListener) || + aIId.equals(Ci.nsISupports)) return this; - + Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; return null; }, @@ -321,11 +112,11 @@ QueryInterface: function(aIId, instance) { onStartRequest: function(request, context) { try { - - var outputFileStream = Components.classes["@mozilla.org/network/file-output-stream;1"]. - createInstance(Components.interfaces.nsIFileOutputStream); + + var outputFileStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); outputFileStream.init(this.outputFile, -1, -1, 0); - this.outputStream = outputFileStream.QueryInterface(Components.interfaces.nsIOutputStream); + this.outputStream = outputFileStream.QueryInterface(Ci.nsIOutputStream); this.outputStream.write(fileHeader, fileHeader.length); this.outputStream.write("kMDItemLastUsedDate", 38); // need to write the date as a string @@ -333,54 +124,53 @@ onStartRequest: function(request, context) { this.outputStream.write(curTimeStr, curTimeStr.length); // need to write the subject in utf8 as the title this.outputStream.write("\nkMDItemTitle\n", 42); - + if (!this.unicodeConverter) { - this.unicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"] - .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); + this.unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Ci.nsIScriptableUnicodeConverter); this.unicodeConverter.charset = "UTF-8"; - + } var utf8Subject = this.unicodeConverter.ConvertFromUnicode(this.subject); utf8Subject += this.unicodeConverter.Finish(); utf8Subject = xmlEscapeString(utf8Subject); - this.outputStream.write(utf8Subject, utf8Subject.length); - + this.outputStream.write(utf8Subject, utf8Subject.length); + // need to write the subject in utf8 as the title this.outputStream.write("\nkMDItemDisplayName\n", 48); - this.outputStream.write(utf8Subject, utf8Subject.length); - + this.outputStream.write(utf8Subject, utf8Subject.length); + this.outputStream.write("\nkMDItemTextContent\n", 48); var author = this.msgHdr.mime2DecodedAuthor; var recipients = this.msgHdr.mime2DecodedRecipients; - + var utf8Author = this.unicodeConverter.ConvertFromUnicode(author); utf8Author += this.unicodeConverter.Finish() + " "; utf8Author = xmlEscapeString(utf8Author); var utf8Recipients = this.unicodeConverter.ConvertFromUnicode(recipients); utf8Recipients += this.unicodeConverter.Finish() + " "; utf8Recipients = xmlEscapeString(utf8Recipients); - this.outputStream.write(utf8Author, utf8Author.length); - this.outputStream.write(utf8Recipients, utf8Recipients.length); - - this.outputStream.write(utf8Subject, utf8Subject.length); + this.outputStream.write(utf8Author, utf8Author.length); + this.outputStream.write(utf8Recipients, utf8Recipients.length); + + this.outputStream.write(utf8Subject, utf8Subject.length); this.outputStream.write(" ", 1); } catch (ex) { - onDoneStreamingCurMessage(false); + onDoneStreamingCurMessage(false); } }, onStopRequest: function(request, context, status, errorMsg) { try { - // we want to write out the from, to, cc, and subject headers into the // Text Content value, so they'll be indexed. - - var stringStream = Components.classes["@mozilla.org/io/string-input-stream;1"]. - createInstance(Components.interfaces.nsIStringInputStream); + + var stringStream = Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(Ci.nsIStringInputStream); stringStream.setData(this.message, this.message.length); var temp = this.msgHdr.folder.getMsgTextFromStream(this.msgHdr, stringStream, 20000, 20000, false); temp = xmlEscapeString(temp); @@ -388,37 +178,39 @@ onStopRequest: function(request, context, status, errorMsg) { this.outputStream.write(temp, temp.length); // close out the content, dict, and plist this.outputStream.write("\n\n\n", 26); - + this.outputStream.close(); // this.outputFile. - this.msgHdr.setUint32Property("indexed", 1); + this.msgHdr.setUint32Property(gHdrIndexedProperty, 1); var msgDB = this.msgHdr.folder.getMsgDatabase(null); msgDB.Commit(MSG_DB_LARGE_COMMIT); - + this.message = ""; } catch (ex) { dump(ex); - this.onDoneStreamingCurMessage(false); + this.onDoneStreamingCurMessage(false); return; } this.onDoneStreamingCurMessage(true); }, onDataAvailable: function(request, context, inputStream, offset, count) { - try + try { - - // ignore stuff after the first 20K or so + var inStream = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + inStream.init(inputStream); + + // It is necessary to read in data from the input stream + var inData = inStream.read(count); + + // ignore stuff after the first 20K or so if (this.message && this.message.length > 20000) return 0; - var inStream = Components.classes["@mozilla.org/scriptableinputstream;1"]. - createInstance(Components.interfaces.nsIScriptableInputStream); - - inStream.init(inputStream); - - this.message += inStream.read(count); + + this.message += inData; return 0; } catch (ex) @@ -430,217 +222,39 @@ onDataAvailable: function(request, context, inputStream, offset, count) { } -// the folderListener object -var gFolderListener = { - - itemAdded: function(aItem) +/* XPCOM boilerplate code */ +function SpotlightIntegration() { } +SpotlightIntegration.prototype = { + classDescription: "Spotlight Integration", + classID: Components.ID("{cc9c2a34-567b-451a-a942-1a1c3ec26e07}"), + contractID: "@mozilla.org/spotlight-search-integration;1", + + _xpcom_categories: [{ + category: "app-startup", + service: true + }], + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]), + + observe : function(aSubject, aTopic, aData) { - restartTimer(30); - SIDump("itemAdded\n"); - var msgHdr; - try + switch(aTopic) { - msgHdr = aItem.QueryInterface(Components.interfaces.nsIMsgDBHdr); + case "app-startup": + var obsSvc = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + obsSvc.addObserver(this, "profile-after-change", false); + break; + case "profile-after-change": + try { InitSpotlightIntegration(); } + catch(err) { SIDump("Could not initialize spotlight component"); } + break; + default: + throw Components.Exception("Unknown topic: " + aTopic); } - catch (ex) {} - if (msgHdr) - QueueMessageToGetIndexed(msgHdr); - }, - - // folder or msg deleted (no trash) - itemDeleted: function(aItem) - { - SIDump("in itemDeleted\n"); - // mail getting deleted, we're not idle, so restart timer. - restartTimer(60); - var msgHdr; - try{ - msgHdr = aItem.QueryInterface(Components.interfaces.nsIMsgDBHdr); - } catch (ex) {} - - if (msgHdr) - { - var file = GetSpotlightFileForMsgHdr(msgHdr); - if (file.exists()) - file.remove(false); - } - else - { - var folder = aItem.QueryInterface(Components.interfaces.nsIMsgFolder); - if (folder) - { - var srcFile = folder.filePath; - srcFile.leafName = srcFile.leafName + ".mozmsgs"; - srcFile.remove(true); - - } - } - - }, - - itemMoveCopyCompleted: function(aMove, aSrcItems, aDestFolder) - { - var folder; - try { - folder = aSrcItems.QueryElementAt(0, Components.interfaces.nsIMsgFolder); - } catch (ex) { } - if (folder) - { - var destFile = aDestFolder.filePath; - var srcFile = folder.filePath; - srcFile.leafName = srcFile.leafName + ".mozmsgs"; - destFile.leafName += ".sbd"; - SIDump ("dst file path = " + destFile.path + "\n"); - SIDump ("src file path = " + srcFile.path + "\n"); - if (srcFile.exists()) - { - if (aMove) - srcFile.moveTo(destFile, ""); - else - srcFile.copyTo(destFile, ""); - } - } - else - { - var msg = aSrcItems.QueryElementAt(0, Components.interfaces.nsIMsgDBHdr); - if (msg) - { - var numMsgs = aSrcItems.Count(); - for (var msgIndex = 0; msgIndex < numMsgs; msgIndex++) - { - msg = aSrcItems.QueryElementAt(msgIndex, Components.interfaces.nsIMsgDBHdr); - var srcFile = GetSpotlightFileForMsgHdr(msg); - if (srcFile && srcFile.exists()) - { - var destFile = aDestFolder.filePath; - destFile.leafName = destFile.leafName + ".mozmsgs"; - if (!destFile.exists()) - { - try - { - // create the directory, if it doesn't exist - destFile.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0644); - } - catch(ex) {dump(ex);} - } - SIDump ("dst file path = " + destFile.path + "\n"); - SIDump ("src file path = " + srcFile.path + "\n"); - if (destFile.exists()) - if (aMove) - srcFile.moveTo(destFile, ""); - else - srcFile.copyTo(destfile, ""); - - } - } - - } - } - restartTimer(30); - SIDump("moveCopyCompleted move = " + aMove + "\n"); - }, - - folderRenamed: function(aOrigFolder, aNewFolder) - { - - }, - // extensibility hook - itemEvent: function(aItem, aEvent, aData) - { - - }, + } }; - - -function QueueMessageToGetIndexed(msgHdr) +var components = [SpotlightIntegration]; +function NSGetModule(aCompMgr, aFileSpec) { - var isupportsHdr = msgHdr.QueryInterface(Components.interfaces.nsISupports); - gMsgHdrsToIndex.AppendElement(isupportsHdr); - if (gMsgHdrsToIndex.Count() == 1) - { - SIDump("generating spotlight file\n"); - GenerateSpotlightFile(msgHdr); - } - else - SIDump("queueing spotlight file generation\n"); -} - -function GetSpotlightFileForMsgHdr(msgHdr) -{ - var folder = msgHdr.folder; - if (folder) - { - var messageId = msgHdr.messageId; - messageId = encodeURIComponent(messageId); - SIDump("encoded message id = " + messageId + "\n"); - // this should work on the trunk, but not in 2.0 -// messageId = netUtils.escapeString(messageId, 3 /* netUtils.ESCAPE_URL_PATH */); - if (folder) - { - var file = folder.filePath; - file.leafName = file.leafName + ".mozmsgs"; - file.appendRelativePath(messageId + ".mozeml"); - SIDump("getting spotlight file path = " + file.path + "\n"); - return file; - } - } - return nsnull; -} - -const MSG_FLAG_HAS_RE = 0x0010; - -function GenerateSpotlightFile(msgHdr) -{ - try - { - var folder = msgHdr.folder; - if (folder) - { - var messageId = msgHdr.messageId; - // for the trunk, this should work - // var netUtils = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsINetUtil); - // messageId = netUtils.escapeString(messageId, netUtils.ESCAPE_URL_PATH); - messageId = encodeURIComponent(messageId); - gStreamListener.subject = ((msgHdr.flags & MSG_FLAG_HAS_RE) ? "Re: " : "") + msgHdr.mime2DecodedSubject; - SIDump("generate spotlight file subject = " + gStreamListener.subject + "message id = " + messageId + "\n"); - var file = folder.filePath; - - file.leafName = file.leafName + ".mozmsgs"; - SIDump("file leafname = " + file.leafName + "\n"); - if (!file.exists()) - { - try - { - // create the directory, if it doesn't exist - file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0644); - } - catch(ex) {dump(ex);} - } - gStreamListener.msgHdr = msgHdr; - file.appendRelativePath(messageId + ".mozeml"); - //file.leafName = messageId + ".mozeml"; - SIDump("file path = " + file.path + "\n"); - file.create(0, 0644); - var uri = folder.getUriForMsg(msgHdr); - //SIDump("in onItemAdded messenger = " + messenger + "\n"); - var msgService = gMessenger.messageServiceFromURI(uri); - gStreamListener.outputFile = file; - - msgService.streamMessage(uri, gStreamListener, null, null, false, "", null); - } - } - catch (ex) - { - dump(ex); - gStreamListener.onDoneStreamingCurMessage(false); - } - -}; - -var gSIDump = true; - -function SIDump(str) -{ - if (gSIDump) - dump(str); + return XPCOMUtils.generateModule(components); } diff --git a/mozilla/mail/components/search/nsWinSearchIntegration.js b/mozilla/mail/components/search/nsWinSearchIntegration.js new file mode 100644 index 00000000000..328dc06ea84 --- /dev/null +++ b/mozilla/mail/components/search/nsWinSearchIntegration.js @@ -0,0 +1,250 @@ +# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is spotlight integration code. +# +# The Initial Developer of the Original Code is +# David Bienvenu +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Siddharth Agarwal +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +#include content/searchCommon.js + +const MSG_DB_LARGE_COMMIT = 1; +const MSG_FLAG_ATTACHMENT = 0x10000000; +const CRLF="\r\n"; + +// The property of the header that's used to check if a message is indexed +const gHdrIndexedProperty = "wds_indexed"; + +// The file extension that is used for support files of this component +const gFileExt = ".wdseml"; + +// The pref base +const gPrefBase = "mail.winsearch"; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function InitWinSearchIntegration() +{ + var enabled; + try { + enabled = gPrefBranch.getBoolPref(gPrefBase + ".enable"); + gLastFolderIndexedUri = gPrefBranch.getCharPref(gPrefBase + ".lastFolderIndexedUri"); + } catch (ex) {} + + if (!enabled) + return; + + SIDump("Initializing Windows Search integration\n"); + InitSupportIntegration(); +} + +// The stream listener to read messages +var gStreamListener = { +_buffer: "", +outputFile: null, +outputStream: null, +unicodeConverter: null, +// subject: null, +message: null, +msgHdr: null, +mimeHdrObj: null, +mimeHdrParamObj: null, + +onDoneStreamingCurMessage: function(successful) +{ + if (this.outputStream) + this.outputStream.close(); + if (!successful && this.msgHdr) + { + var file = GetSupportFileForMsgHdr(this.msgHdr); + if (file && file.exists()) + file.remove(false); + } + // should we try to delete the file on disk in case not successful? + gMsgHdrsToIndex.shift(); + + if (gMsgHdrsToIndex.length > 0) + GenerateSupportFile(gMsgHdrsToIndex[0]); +}, + +QueryInterface: function(aIId, instance) { + if (aIId.equals(Ci.nsIStreamListener) || aIId.equals(Ci.nsISupports)) + return this; + + Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; + return null; +}, + +onStartRequest: function(request, context) { + try + { + var 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", 0, 0x0000); + } + catch (ex) + { + onDoneStreamingCurMessage(false); + } +}, + +onStopRequest: function(request, context, status, errorMsg) { + try + { + // If, for some reason, the first four characters are "null", then remove them + if (this.message.substring(0, 4) == "null") + this.message = this.message.substring(4); + + // First get all the headers (everything till a double CRLF) + var headerLength = this.message.indexOf(CRLF+CRLF)+1; + + // XXX the below has to be replaced with getMsgTextFromStream + + // Filter out attachments. First locate the content-type header. + if (!this.mimeHdrObj) + this.mimeHdrObj = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders); + this.mimeHdrObj.initialize(this.message.substring(0, headerLength), headerLength); + var contentTypeHeader = this.mimeHdrObj.extractHeader("Content-Type", false); + + if (!this.mimeHdrParamObj) + this.mimeHdrParamObj = Cc["@mozilla.org/network/mime-hdrparam;1"].createInstance(Ci.nsIMIMEHeaderParam); + // If a multipart header, then strip out attachments + var contentType = this.mimeHdrParamObj.getParameter(contentTypeHeader, null, null, true, {}); + if (contentType.substring(0,10) == "multipart/") + { + var boundary = this.mimeHdrParamObj.getParameter(contentTypeHeader, "boundary", null, true, {}); + + // Search for the boundary after the headers. Remove everything from the second occurrence. + boundary = CRLF + "--" + boundary; + var getRidFrom = this.message.indexOf(boundary, headerLength); + getRidFrom = this.message.indexOf(boundary, getRidFrom + boundary.length); + + // In case we've got more than one boundary so far + if (getRidFrom != -1) { + this.message = this.message.substring(0, getRidFrom); + + // Add RFC required stuff to the end + this.message += boundary + "--" + CRLF; + } + } + + // We just write whatever we have in |message| out to the file + this.outputStream.writeString(this.message); + + this.msgHdr.setUint32Property(gHdrIndexedProperty, 1); + var msgDB = this.msgHdr.folder.getMsgDatabase(null); + msgDB.Commit(MSG_DB_LARGE_COMMIT); + + this.message = ""; + SIDump("Successfully written file\n"); + } + catch (ex) + { + SIDump(ex); + this.onDoneStreamingCurMessage(false); + return; + } + this.onDoneStreamingCurMessage(true); +}, + +onDataAvailable: function(request, context, inputStream, offset, count) { + try + { + var inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); + inStream.init(inputStream); + + // It is necessary to read in data from the input stream + var inData = inStream.read(count); + + // If we've already reached the attachments, safely ignore. + if (this.filteredAttachments) + return 0; + + // Also ignore stuff after the first 20K or so + if (this.message && this.message.length > 20000) + return 0; + var inStream = Cc["@mozilla.org/scriptableinputstream;1"]. + createInstance(Ci.nsIScriptableInputStream); + + inStream.init(inputStream); + + this.message += inData; + return 0; + } + catch (ex) + { + SIDump(ex); + onDoneStreamingCurMessage(false); + } +} +}; + +/* XPCOM boilerplate code */ +function WinSearchIntegration() { } +WinSearchIntegration.prototype = { + classDescription: "Windows Search Integration", + classID: Components.ID("{451a70f0-1b4f-11dd-bd0b-0800200c9a66}"), + contractID: "@mozilla.org/windows-search-integration;1", + + _xpcom_categories: [{ + category: "app-startup", + service: true + }], + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]), + + observe : function(aSubject, aTopic, aData) + { + switch(aTopic) + { + case "app-startup": + var obsSvc = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + obsSvc.addObserver(this, "profile-after-change", false); + break; + case "profile-after-change": + try { InitWinSearchIntegration(); } + catch(err) { SIDump("Could not initialize winsearch component"); } + break; + default: + throw Components.Exception("Unknown topic: " + aTopic); + } + } +}; +var components = [WinSearchIntegration]; +function NSGetModule(aCompMgr, aFileSpec) +{ + return XPCOMUtils.generateModule(components); +}