Bug 648972: Allow anyone to use StreamMessage *and* fetch parts on demand r=bienvenu
authorJonathan Protzenko <jonathan.protzenko@gmail.com>
Sat, 23 Apr 2011 10:57:38 +0200
changeset 7603 cd22a42875a915305033197e05c03c4c593f1cd4
parent 7602 387093f0adb66722da2d9a9cc043251f1670daa4
child 7604 b066f8c46d19c96a153a96db4df3cd0dc3007593
push id5829
push userjonathan.protzenko@gmail.com
push dateSat, 23 Apr 2011 08:56:54 +0000
treeherdercomm-central@cd22a42875a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbienvenu
bugs648972
Bug 648972: Allow anyone to use StreamMessage *and* fetch parts on demand r=bienvenu
mailnews/db/gloda/components/jsmimeemitter.js
mailnews/db/gloda/modules/mimemsg.js
mailnews/imap/src/nsImapService.cpp
mailnews/imap/test/unit/test_partsOnDemand.js
--- a/mailnews/db/gloda/components/jsmimeemitter.js
+++ b/mailnews/db/gloda/components/jsmimeemitter.js
@@ -99,17 +99,17 @@ function MimeMessageEmitter() {
   this._writeBody = false;
 }
 
 const deathToNewlines = /\n/g;
 
 MimeMessageEmitter.prototype = {
   classID: Components.ID("{8cddbbbc-7ced-46b0-a936-8cddd1928c24}"),
 
-  _partRE: new RegExp("^[^?]+\\?(?:[^&]+&)*part=([^&]+)(?:&[^&]+)*$"),
+  _partRE: new RegExp("^[^?]+\\?(?:/;section=\\d+\\?)?(?:[^&]+&)*part=([^&]+)(?:&[^&]+)*$"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMimeEmitter]),
 
   initialize: function mime_emitter_initialize(aUrl, aChannel, aFormat) {
     this._url = aUrl;
     this._curPart = new this._mimeMsg.MimeMessage();
     // the partName is intentionally ""!  not a place-holder!
     this._curPart.partName = "";
@@ -340,17 +340,17 @@ MimeMessageEmitter.prototype = {
   //  which time we receive the messages, both bodies and headers).  Our caller
   //  traverses the libmime child object hierarchy, emitting an attachment for
   //  each leaf object or sub-message.
   startAttachment: function mime_emitter_startAttachment(aName, aContentType,
       aUrl, aIsExternalAttachment) {
     this._state = kStateInAttachment;
 
     // we need to strip our magic flags from the URL
-    aUrl = aUrl.replace("header=filter&emitter=js&", "");
+    aUrl = aUrl.replace(/header=filter&emitter=js(&fetchCompleteMessage=false)?&?/, "");
     // the url should contain a part= piece that tells us the part name, which
     // we then use to figure out where to place that part if it's a real
     // attachment.
     let partMatch = this._partRE.exec(aUrl);
     let partName = partMatch && partMatch[1];
     this._curAttachment = partName;
 
     if (aContentType == "message/rfc822") {
--- a/mailnews/db/gloda/modules/mimemsg.js
+++ b/mailnews/db/gloda/modules/mimemsg.js
@@ -185,27 +185,34 @@ let gMessenger = Cc["@mozilla.org/messen
  *     the message is not available offline, we will propagate an exception
  *     thrown by the underlying code.
  * @param [aOptions] Optional options.
  * @param [aOptions.saneBodySize] Limit body sizes to a 'reasonable' size in
  *     order to combat corrupt offline/message stores creating pathological
  *     situtations where we have erroneously multi-megabyte messages.  This
  *     also likely reduces the impact of legitimately ridiculously large
  *     messages.
+ * @param [aOptions.partsOnDemand] If this is a message stored on an IMAP
+ *     server, and for whatever reason, it isn't available locally, then setting
+ *     this option to true will make sure that attachments aren't downloaded.
+ *     This makes sure the message is available quickly.
  */
 function MsgHdrToMimeMessage(aMsgHdr, aCallbackThis, aCallback,
                              aAllowDownload, aOptions) {
   shutdownCleanupObserver.ensureInitialized();
 
   let requireOffline = !aAllowDownload;
 
   let msgURI = aMsgHdr.folder.getUriForMsg(aMsgHdr);
   let msgService = gMessenger.messageServiceFromURI(msgURI);
 
   MsgHdrToMimeMessage.OPTION_TUNNEL = aOptions;
+  let partsOnDemandStr = (aOptions && aOptions.partsOnDemand)
+    ? "&fetchCompleteMessage=false"
+    : "";
 
   // if we're already streaming this msg, just add the callback
   // to the listener.
   let listenerForURI = activeStreamListeners[msgURI];
   if (listenerForURI != undefined) {
     listenerForURI._callbacks.push(aCallback ? aCallback : aCallbackThis);
     listenerForURI._callbacksThis.push(aCallback ? aCallbackThis : null);
     return;
@@ -216,17 +223,17 @@ function MsgHdrToMimeMessage(aMsgHdr, aC
   try {
     let streamURI = msgService.streamMessage(
       msgURI,
       streamListener, // consumer
       null, // nsIMsgWindow
       dumbUrlListener, // nsIUrlListener
       true, // have them create the converter
       // additional uri payload, note that "header=" is prepended automatically
-      "filter&emitter=js",
+      "filter&emitter=js"+partsOnDemandStr,
       requireOffline);
   } catch (ex) {
     // If streamMessage throws an exception, we should make sure to clear the
     // activeStreamListener, or any subsequent attempt at sreaming this URI
     // will silently fail
     if (activeStreamListeners[msgURI]) {
       delete activeStreamListeners[msgURI];
     }
--- a/mailnews/imap/src/nsImapService.cpp
+++ b/mailnews/imap/src/nsImapService.cpp
@@ -1178,18 +1178,25 @@ NS_IMETHODIMP nsImapService::StreamMessa
       nsCAutoString urlSpec;
       char hierarchyDelimiter = GetHierarchyDelimiter(folder);
       rv = CreateStartOfImapUrl(nsDependentCString(aMessageURI), getter_AddRefs(imapUrl), 
                                 folder, aUrlListener, urlSpec, hierarchyDelimiter);
       NS_ENSURE_SUCCESS(rv, rv);
       nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(imapUrl));
       nsCOMPtr<nsIURI> url(do_QueryInterface(imapUrl));
 
+      // This option is used by the JS Mime Emitter, in case we want a cheap
+      // streaming, for example, if we just want a quick look at some header,
+      // without having to download all the attachments...
+      nsCAutoString additionalHeader(aAdditionalHeader);
+      PRInt32 fetchOnDemand = additionalHeader.Find("&fetchCompleteMessage=false");
+      imapUrl->SetFetchPartsOnDemand(fetchOnDemand != kNotFound);
+
       // We need to add the fetch command here for the cache lookup to behave correctly
-      rv = AddImapFetchToUrl(url, folder, msgKey, aAdditionalHeader);
+      rv = AddImapFetchToUrl(url, folder, msgKey, additionalHeader);
       NS_ENSURE_SUCCESS(rv, rv);
 
       nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
 
       msgurl->SetMsgWindow(aMsgWindow);
       rv = msgurl->GetServer(getter_AddRefs(aMsgIncomingServer));
 
       // Try to check if the message is offline
@@ -1208,17 +1215,16 @@ NS_IMETHODIMP nsImapService::StreamMessa
           rv = IsMsgInMemCache(url, folder, nsnull, &isMsgInMemCache);
           NS_ENSURE_SUCCESS(rv, rv);
 
           if (!isMsgInMemCache)
             return NS_ERROR_FAILURE;
         }
       }
 
-      imapUrl->SetFetchPartsOnDemand(PR_FALSE);
       msgurl->SetAddToMemoryCache(PR_TRUE);
 
       PRBool shouldStoreMsgOffline = PR_FALSE;
       folder->ShouldStoreMsgOffline(key, &shouldStoreMsgOffline);
       imapUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
       rv = GetMessageFromUrl(imapUrl, nsIImapUrl::nsImapMsgFetchPeek, folder,
                              imapMessageSink, aMsgWindow, aConsumer,
                              aConvertData, aURL);
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/test_partsOnDemand.js
@@ -0,0 +1,132 @@
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Jonathan Protzenko <jonathan.protzenko@gmail.com>
+ *
+ * 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 ***** */
+
+/*
+ * Tests that you can stream a message without the attachments. Tests the
+ * MsgHdrToMimeMessage API that exposes this.
+ */
+
+load("../../../resources/logHelper.js");
+load("../../../resources/mailTestUtils.js");
+load("../../../resources/asyncTestUtils.js");
+load("../../../resources/messageGenerator.js");
+
+// javascript mime emitter functions
+mimeMsg = {};
+Components.utils.import("resource:///modules/gloda/mimemsg.js", mimeMsg);
+
+// IMAP pump
+load("../../../resources/IMAPpump.js");
+
+setupIMAPPump();
+
+var tests = [
+  setPrefs,
+  loadImapMessage,
+  startMime,
+  endTest
+]
+
+// make sure we are in the optimal conditions!
+function setPrefs() {
+  var prefBranch = Cc["@mozilla.org/preferences-service;1"]
+                     .getService(Ci.nsIPrefBranch);
+  prefBranch.setIntPref("mail.imap.mime_parts_on_demand_threshold", 20);
+  prefBranch.setBoolPref("mail.imap.mime_parts_on_demand", true);
+  prefBranch.setBoolPref("mail.server.server1.autosync_offline_stores", false);
+  prefBranch.setBoolPref("mail.server.server1.offline_download", false);
+  prefBranch.setBoolPref("mail.server.server1.download_on_biff", false);
+  prefBranch.setIntPref("browser.cache.disk.capacity", 0);
+
+  yield true;
+}
+
+// load and update a message in the imap fake server
+function loadImapMessage()
+{
+  let gMessageGenerator = new MessageGenerator();
+
+  let ioService = Cc["@mozilla.org/network/io-service;1"]
+                  .getService(Ci.nsIIOService);
+  let file = do_get_file("../../../data/bodystructuretest1");
+  let msgURI = ioService.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+
+  let imapInbox =  gIMAPDaemon.getMailbox("INBOX")
+  let message = new imapMessage(msgURI.spec, imapInbox.uidnext++, []);
+  gIMAPMailbox.addMessage(message);
+  gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+
+  do_check_eq(1, gIMAPInbox.getTotalMessages(false));
+  let msgHdr = firstMsgHdr(gIMAPInbox);
+  do_check_true(msgHdr instanceof Ci.nsIMsgDBHdr);
+  yield true;
+}
+
+// process the message through mime
+function startMime()
+{
+  let msgHdr = firstMsgHdr(gIMAPInbox);
+
+  mimeMsg.MsgHdrToMimeMessage(msgHdr, this, function (aMsgHdr, aMimeMessage) {
+    let url = aMimeMessage.allUserAttachments[0].url;
+    // A URL containing this string indicates that the attachment will be
+    // downloaded on demand.
+    do_check_true(url.indexOf("/;section=") >= 0);
+    async_driver();
+  }, true /* allowDownload */, { partsOnDemand: true });
+  yield false;
+}
+
+// Cleanup
+function endTest()
+{
+  teardownIMAPPump();
+}
+
+function run_test()
+{
+  async_run_tests(tests);
+}
+
+// get the first message header found in a folder
+function firstMsgHdr(folder) {
+  let enumerator = folder.msgDatabase.EnumerateMessages();
+  if (enumerator.hasMoreElements())
+    return enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+  return null;
+}