fix bug 686087, add ability to stream just the headers of a message, r=protz, sr=asuth
authorDavid Bienvenu <bienvenu@nventure.com>
Thu, 06 Oct 2011 07:52:37 -0700
changeset 9447 b83fba0bc529ab4fcab3a920dc0ac1cb2e4d385a
parent 9446 007485db1000460d0ab4515613c47ddf885af42f
child 9448 b7be236800c64f9b40f758b7cc3ce7cd968e310a
push id281
push userbugzilla@standard8.plus.com
push dateWed, 21 Dec 2011 12:08:36 +0000
treeherdercomm-beta@6d973fa5c2e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersprotz, asuth
bugs686087
fix bug 686087, add ability to stream just the headers of a message, r=protz, sr=asuth
mailnews/base/public/nsIMsgMessageService.idl
mailnews/base/util/nsMsgUtils.cpp
mailnews/base/util/nsMsgUtils.h
mailnews/imap/src/nsImapService.cpp
mailnews/imap/test/unit/test_imapHdrStreaming.js
mailnews/imap/test/unit/xpcshell.ini
mailnews/local/src/nsMailboxService.cpp
mailnews/news/src/nsNntpService.cpp
--- a/mailnews/base/public/nsIMsgMessageService.idl
+++ b/mailnews/base/public/nsIMsgMessageService.idl
@@ -47,17 +47,17 @@ interface nsIMsgSearchSession;
 interface nsIMsgDBHdr;
 interface nsIStreamConverter;
 interface nsICacheEntryDescriptor;
 
 %{C++
 #include "MailNewsTypes.h"
 %}
 
-[scriptable, uuid(ac7b56c2-cf42-4ee3-a3b1-9ae64a90861c)]
+[scriptable, uuid(3aa7080a-73ac-4394-9636-fc00e182319b)]
 interface nsIMsgMessageService : nsISupports {
      
   /**
    * If you want a handle on the running task, pass in a valid nsIURI 
    * ptr. You can later interrupt this action by asking the netlib 
    * service manager to interrupt the url you are given back. 
    * Remember to release aURL when you are done with it. Pass nsnull
    * in for aURL if you don't care about the returned URL.
@@ -214,17 +214,38 @@ interface nsIMsgMessageService : nsISupp
    */
   nsIURI streamMessage(in string aMessageURI, in nsISupports aConsumer, 
                     in nsIMsgWindow aMsgWindow,
                     in nsIUrlListener aUrlListener, 
                     in boolean aConvertData,
                     in ACString aAdditionalHeader,
                     [optional] in boolean aLocalOnly);
 
-  
+  /** 
+   * This method streams a message's headers to the passed in consumer.
+   * This is for consumers who want a particular header but don't
+   * want to stream the whole message.
+   *
+   * @param aMessageURI  uri of message whose headers we are to stream
+   * @param aConsumer    a stream listener listening to the message
+                         headers.
+   * @param aUrlListener gets notified when url starts and stops, if we run a url.
+   * @param aLocalOnly whether data should be retrieved only from local caches
+   *        If streaming over the network is required and this is true, then
+   *        an exception is thrown. This defaults to false.
+   *
+   * @note If we're offline, then even if aLocalOnly is false, we won't stream over the
+   *       network
+   *
+   * @return the URL that gets run, if any.
+   */
+  nsIURI streamHeaders(in string aMessageURI, in nsIStreamListener aConsumer,
+                       in nsIUrlListener aUrlListener, 
+                       [optional] in boolean aLocalOnly);
+
   /**
    * Determines whether a message is in the memory cache. Local folders
    * don't implement this.
    *
    * @param aUrl The URL of the message, possibly with an appropriate command in it
    * @param aFolder The folder this message is in
    * @param aCacheEntry If a cache entry is found, then a pointer to it
    * 
--- a/mailnews/base/util/nsMsgUtils.cpp
+++ b/mailnews/base/util/nsMsgUtils.cpp
@@ -87,16 +87,18 @@
 #include "nsIStringBundle.h"
 #include "nsIMsgWindow.h"
 #include "nsIWindowWatcher.h"
 #include "nsIPrompt.h"
 #include "nsISupportsArray.h"
 #include "nsIMsgSearchTerm.h"
 #include "nsTextFormatter.h"
 #include "nsIAtomService.h"
+#include "nsIStreamListener.h"
+#include "nsReadLine.h"
 
 static NS_DEFINE_CID(kImapUrlCID, NS_IMAPURL_CID);
 static NS_DEFINE_CID(kCMailboxUrl, NS_MAILBOXURL_CID);
 static NS_DEFINE_CID(kCNntpUrlCID, NS_NNTPURL_CID);
 
 #define ILLEGAL_FOLDER_CHARS ";#"
 #define ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER "."
 #define ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER  ".~ "
@@ -2173,8 +2175,44 @@ NS_MSG_BASE PRUint64 ParseUint64Str(cons
   {
     char *endPtr;
     return _strtoui64(str, &endPtr, 10);
   }
 #else
   return strtoull(str, nsnull, 10);
 #endif
 }
+
+NS_MSG_BASE nsresult
+MsgStreamMsgHeaders(nsIInputStream *aInputStream, nsIStreamListener *aConsumer)
+{
+  nsLineBuffer<char> *lineBuffer;
+  nsresult rv = NS_InitLineBuffer(&lineBuffer);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCAutoString msgHeaders;
+  nsCAutoString curLine;
+
+  bool more = PR_TRUE;
+
+  // We want to NS_ReadLine until we get to a blank line (the end of the headers)
+  while (more)
+  {
+    rv = NS_ReadLine(aInputStream, lineBuffer, curLine, &more);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (curLine.IsEmpty())
+      break;
+    msgHeaders.Append(curLine);
+    msgHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+  }
+  PR_Free(lineBuffer);
+  nsCOMPtr<nsIStringInputStream> hdrsStream =
+        do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  hdrsStream->SetData(msgHeaders.get(), msgHeaders.Length());
+  nsCOMPtr<nsIInputStreamPump> pump;
+  rv = NS_NewInputStreamPump(getter_AddRefs(pump), hdrsStream);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return pump->AsyncRead(aConsumer, nsnull);
+
+}
+
--- a/mailnews/base/util/nsMsgUtils.h
+++ b/mailnews/base/util/nsMsgUtils.h
@@ -61,16 +61,17 @@ class nsIMsgMessageService;
 class nsIUrlListener;
 class nsIOutputStream;
 class nsIInputStream;
 class nsIMsgDatabase;
 class nsIMutableArray;
 class nsIProxyInfo;
 class nsIMsgWindow;
 class nsISupportsArray;
+class nsIStreamListener;
 
 //These are utility functions that can used throughout the mailnews code
 
 NS_MSG_BASE nsresult GetMessageServiceContractIDForURI(const char *uri, nsCString &contractID);
 
 NS_MSG_BASE nsresult GetMessageServiceFromURI(const nsACString& uri, nsIMsgMessageService **aMessageService);
 
 NS_MSG_BASE nsresult GetMsgDBHdrFromURI(const char *uri, nsIMsgDBHdr **msgHdr);
@@ -284,16 +285,19 @@ NS_MSG_BASE PRTime MsgConvertAgeInDaysTo
  * Converts the passed in term list to its string representation.
  *
  * @param      aTermList    Array of nsIMsgSearchTerms
  * @param[out] aOutString   result representation of search terms.
  *
  */
 NS_MSG_BASE nsresult MsgTermListToString(nsISupportsArray *aTermList, nsCString &aOutString);
 
+NS_MSG_BASE nsresult
+MsgStreamMsgHeaders(nsIInputStream *aInputStream, nsIStreamListener *aConsumer);
+
 /**
  * convert string to PRUint64
  *
  * @param str conveted string
  * @returns   PRUint64 vaule for success, 0 for parse failure
  */
 NS_MSG_BASE PRUint64 ParseUint64Str(const char *str);
 
--- a/mailnews/imap/src/nsImapService.cpp
+++ b/mailnews/imap/src/nsImapService.cpp
@@ -1235,16 +1235,74 @@ NS_IMETHODIMP nsImapService::StreamMessa
       rv = GetMessageFromUrl(imapUrl, nsIImapUrl::nsImapMsgFetchPeek, folder,
                              imapMessageSink, aMsgWindow, aConsumer,
                              aConvertData, aURL);
     }
   }
   return rv;
 }
 
+// this method streams a message's headers to the passed in consumer.
+NS_IMETHODIMP nsImapService::StreamHeaders(const char *aMessageURI,
+                                           nsIStreamListener *aConsumer,
+                                           nsIUrlListener *aUrlListener,
+                                           bool aLocalOnly,
+                                           nsIURI **aURL)
+{
+  NS_ENSURE_ARG_POINTER(aMessageURI);
+  NS_ENSURE_ARG_POINTER(aConsumer);
+  nsCOMPtr<nsIMsgFolder> folder;
+  nsCAutoString msgKey;
+  nsCAutoString folderURI;
+  nsCString mimePart;
+  nsMsgKey key;
+
+  nsresult rv = DecomposeImapURI(nsDependentCString(aMessageURI), getter_AddRefs(folder), msgKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (msgKey.IsEmpty())
+    return NS_MSG_MESSAGE_NOT_FOUND;
+  rv = nsParseImapMessageURI(aMessageURI, folderURI, &key, getter_Copies(mimePart));
+  if (NS_SUCCEEDED(rv))
+  {
+    nsCOMPtr<nsIInputStream> inputStream;
+    bool hasMsgOffline = false;
+    folder->HasMsgOffline(key, &hasMsgOffline);
+    if (hasMsgOffline)
+    {
+      PRUint64 messageOffset;
+      PRUint32 messageSize;
+      folder->GetOfflineFileStream(key, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+      if (inputStream)
+        return MsgStreamMsgHeaders(inputStream, aConsumer);
+    }
+    nsCOMPtr<nsIImapUrl> imapUrl;
+    nsCAutoString urlSpec;
+    char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+    rv = CreateStartOfImapUrl(nsDependentCString(aMessageURI), getter_AddRefs(imapUrl), 
+                              folder, aUrlListener, urlSpec, hierarchyDelimiter);
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsIURI> url(do_QueryInterface(imapUrl));
+    nsCOMPtr<nsICacheEntryDescriptor> cacheEntry;
+    bool msgInMemCache = false;
+    rv = IsMsgInMemCache(url, folder, getter_AddRefs(cacheEntry), &msgInMemCache);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (msgInMemCache)
+    {
+      rv = cacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+      if (NS_SUCCEEDED(rv))
+        return MsgStreamMsgHeaders(inputStream, aConsumer);
+    }
+  }
+  if (aLocalOnly)
+    return NS_ERROR_FAILURE;
+  return rv;
+}
+
 NS_IMETHODIMP nsImapService::IsMsgInMemCache(nsIURI *aUrl,
                                              nsIMsgFolder *aImapMailFolder,
                                              nsICacheEntryDescriptor **aCacheEntry,
                                              bool *aResult)
 {
   NS_ENSURE_ARG_POINTER(aUrl);
   NS_ENSURE_ARG_POINTER(aImapMailFolder);
   *aResult = PR_FALSE;
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/test/unit/test_imapHdrStreaming.js
@@ -0,0 +1,120 @@
+/**
+ * This test checks if the imap message service code streams headers correctly.
+ * It checks thst streaming headers for messages stored for offline use works.
+ * It doesn't test streaming messages that haven't been stored for offline use
+ * because that's not implemented yet, and it's unclear if anyone will want it.
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+load("../../../resources/logHelper.js");
+load("../../../resources/mailTestUtils.js");
+load("../../../resources/asyncTestUtils.js");
+
+load("../../../resources/messageGenerator.js");
+load("../../../resources/messageModifier.js");
+load("../../../resources/messageInjection.js");
+
+// IMAP pump
+load("../../../resources/IMAPpump.js");
+
+setupIMAPPump();
+
+
+const nsMsgMessageFlags = Ci.nsMsgMessageFlags;
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+const gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+
+// We use this as a display consumer
+var streamListener =
+{
+  _data: "",
+  _stream : null,
+
+  QueryInterface:
+    XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIRequestObserver]),
+
+  // nsIRequestObserver
+  onStartRequest: function(aRequest, aContext) {
+  },
+  onStopRequest: function(aRequest, aContext, aStatusCode) {
+    do_check_eq(aStatusCode, 0);
+    do_check_neq(this._data.indexOf("Content-Type"), -1);
+    async_driver();
+  },
+
+  // nsIStreamListener
+  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+    if (this._stream == null) {
+      this._stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+      this._stream.init(aInputStream);
+    }
+    this._data += this._stream.read(aCount);
+  }
+};
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox)
+{
+  let ioService = Cc["@mozilla.org/network/io-service;1"]
+                    .getService(Ci.nsIIOService);
+
+  // For every message we have, we need to convert it to a file:/// URI
+  messages.forEach(function (message)
+  {
+    let URI = ioService.newFileURI(message.file).QueryInterface(Ci.nsIFileURL);
+    message.spec = URI.spec;
+  });
+
+  // Create the imapMessages and store them on the mailbox
+  messages.forEach(function (message)
+  {
+    mailbox.addMessage(new imapMessage(message.spec, mailbox.uidnext++, []));
+  });
+}
+
+var incomingServer, server;
+function run_test() {
+
+  // Add a couple of messages to the INBOX
+  // this is synchronous, afaik
+  addMessagesToServer([{file: gMsgFile1, messageId: gMsgId1}],
+                        gIMAPDaemon.getMailbox("INBOX"));
+  Services.prefs.setBoolPref("mail.server.server1.autosync_offline_stores", false);
+  async_run_tests(tests);
+ }
+
+var gIMAPService;
+
+var tests = [
+  test_updateFolder,
+  test_downloadForOffline,
+  test_streamHeaders,
+  endTest
+]
+
+function test_updateFolder() {
+  gIMAPInbox.updateFolderWithListener(null, asyncUrlListener);
+  yield false;
+}
+
+function test_downloadForOffline() {
+  gIMAPInbox.downloadAllForOffline(asyncUrlListener, null);
+  yield false;
+}
+
+function test_streamHeaders()
+{
+  let newMsgHdr = gIMAPInbox.GetMessageHeader(1);
+  let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+  let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+  let msgServ = messenger.messageServiceFromURI(msgURI);
+  msgServ.streamHeaders(msgURI, streamListener, asyncUrlListener,true);
+  yield false;
+}
+
+function endTest()
+{
+  teardownIMAPPump();
+}
--- a/mailnews/imap/test/unit/xpcshell.ini
+++ b/mailnews/imap/test/unit/xpcshell.ini
@@ -16,16 +16,17 @@ tail = tail_imap.js
 [test_imapAuthMethods.js]
 [test_imapAutoSync.js]
 [test_imapContentLength.js]
 [test_imapCopyTimeout.js]
 [test_imapFilterActions.js]
 [test_imapFlagChange.js]
 [test_imapFolderCopy.js]
 [test_imapHdrChunking.js]
+[test_imapHdrStreaming.js]
 [test_imapHighWater.js]
 [test_imapID.js]
 [test_imapMove.js]
 [test_imapPasswordFailure.js]
 [test_imapProtocols.js]
 [test_imapStatusCloseDBs.js]
 [test_imapStoreMsgOffline.js]
 [test_imapUndo.js]
--- a/mailnews/local/src/nsMailboxService.cpp
+++ b/mailnews/local/src/nsMailboxService.cpp
@@ -327,16 +327,40 @@ nsMailboxService::StreamMessage(const ch
       aURIString += "header=";
       aURIString += aAdditionalHeader;
     }
 
     return FetchMessage(aURIString.get(), aConsumer, aMsgWindow, aUrlListener, nsnull,
                                         nsIMailboxUrl::ActionFetchMessage, nsnull, aURL);
 }
 
+NS_IMETHODIMP nsMailboxService::StreamHeaders(const char *aMessageURI,
+                                              nsIStreamListener *aConsumer,
+                                              nsIUrlListener *aUrlListener,
+                                              bool aLocalOnly,
+                                              nsIURI **aURL)
+{
+  NS_ENSURE_ARG_POINTER(aMessageURI);
+  NS_ENSURE_ARG_POINTER(aConsumer);
+  nsCAutoString folderURI;
+  nsMsgKey msgKey;
+  nsCOMPtr<nsIMsgFolder> folder;
+  nsresult rv = DecomposeMailboxURI(aMessageURI, getter_AddRefs(folder), &msgKey);
+  if (msgKey == nsMsgKey_None)
+    return NS_MSG_MESSAGE_NOT_FOUND;
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  PRUint64 messageOffset;
+  PRUint32 messageSize;
+  rv = folder->GetOfflineFileStream(msgKey, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+  return MsgStreamMsgHeaders(inputStream, aConsumer);
+}
+
+
 NS_IMETHODIMP nsMailboxService::IsMsgInMemCache(nsIURI *aUrl,
                                                 nsIMsgFolder *aFolder,
                                                 nsICacheEntryDescriptor **aCacheEntry,
                                                 bool *aResult)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
--- a/mailnews/news/src/nsNntpService.cpp
+++ b/mailnews/news/src/nsNntpService.cpp
@@ -1555,16 +1555,70 @@ nsNntpService::StreamMessage(const char 
     }
 
     if (aURL)
       NS_IF_ADDREF(*aURL = url);
 
     return GetMessageFromUrl(url, aMsgWindow, aConsumer);
 }
 
+NS_IMETHODIMP nsNntpService::StreamHeaders(const char *aMessageURI,
+                                           nsIStreamListener *aConsumer,
+                                           nsIUrlListener *aUrlListener,
+                                           bool aLocalOnly,
+                                           nsIURI **aURL)
+{
+  NS_ENSURE_ARG_POINTER(aMessageURI);
+  NS_ENSURE_ARG_POINTER(aConsumer);
+  nsCOMPtr<nsIMsgFolder> folder;
+  nsMsgKey key;
+
+  nsresult rv = DecomposeNewsMessageURI(aMessageURI, getter_AddRefs(folder), &key);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (key == nsMsgKey_None)
+    return NS_MSG_MESSAGE_NOT_FOUND;
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  bool hasMsgOffline = false;
+  folder->HasMsgOffline(key, &hasMsgOffline);
+  if (hasMsgOffline)
+  {
+    PRUint64 messageOffset;
+    PRUint32 messageSize;
+    folder->GetOfflineFileStream(key, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+    if (inputStream)
+      return MsgStreamMsgHeaders(inputStream, aConsumer);
+  }
+  nsCAutoString urlStr;
+  rv = CreateMessageIDURL(folder, key, getter_Copies(urlStr));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsNewsAction action = nsINntpUrl::ActionFetchArticle;
+
+  nsCOMPtr<nsIURI> url;
+  rv = ConstructNntpUrl(urlStr.get(), aUrlListener, nsnull, aMessageURI,
+                        action, getter_AddRefs(url));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsICacheEntryDescriptor> cacheEntry;
+  bool msgInMemCache = false;
+  rv = IsMsgInMemCache(url, folder, getter_AddRefs(cacheEntry), &msgInMemCache);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (msgInMemCache)
+  {
+    rv = cacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+    if (NS_SUCCEEDED(rv))
+      return MsgStreamMsgHeaders(inputStream, aConsumer);
+  }
+  if (aLocalOnly)
+    return NS_ERROR_FAILURE;
+  return rv;
+}
+
 NS_IMETHODIMP nsNntpService::IsMsgInMemCache(nsIURI *aUrl,
                                              nsIMsgFolder *aFolder,
                                              nsICacheEntryDescriptor **aCacheEntry,
                                              bool *aResult)
 {
   NS_ENSURE_ARG_POINTER(aUrl);
   NS_ENSURE_ARG_POINTER(aFolder);
   *aResult = PR_FALSE;