fix
bug 686087, add ability to stream just the headers of a message, r=protz, sr=asuth
--- 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;