Bug 840418 - really store the X-Mozilla-Status value in the mbox file on reply/forward of a message. r=rkent, r=mkmelin
authoraceman <acelists@atlas.sk>
Tue, 17 Feb 2015 14:17:43 +0100
changeset 19581 bc4533660d85dd258450230c757e99e34cda99b5
parent 19580 42903800f7392df8e5568fa4522807d83b4964d8
child 19582 a85a4ebc9890ab73a54f742bdde931b0721b578f
push id1349
push usermbanner@mozilla.com
push dateMon, 23 Feb 2015 20:33:22 +0000
treeherdercomm-aurora@48557e9e56e0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrkent, mkmelin
bugs840418
Bug 840418 - really store the X-Mozilla-Status value in the mbox file on reply/forward of a message. r=rkent, r=mkmelin
mail/test/mozmill/folder-display/test-message-commands-on-msgstore.js
mail/test/mozmill/shared-modules/test-window-helpers.js
mailnews/base/util/nsMsgUtils.cpp
mailnews/base/util/nsMsgUtils.h
mailnews/local/src/nsLocalMailFolder.cpp
mailnews/local/src/nsLocalMailFolder.h
mailnews/local/src/nsMsgLocalStoreUtils.cpp
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-message-commands-on-msgstore.js
@@ -0,0 +1,245 @@
+/* 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/. */
+
+/**
+ * This tests some commands on messages via the UI. But we specifically check,
+ * whether the commands have an effect in the message store on disk, i.e. the
+ * markings on the messages are stored in the msgStore, not only in the database.
+ * For now, it checks for bug 840418.
+ */
+const MODULE_NAME = "test-message-commands-on-msgstore";
+
+const RELATIVE_ROOT = "../shared-modules";
+const MODULE_REQUIRES = ["folder-display-helpers",
+                         "compose-helpers",
+                         "window-helpers"];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/mailServices.js");
+Cu.import("resource:///modules/IOUtils.js");
+
+const statusHeader = "X-Mozilla-Status: ";
+const nsMsgMessageFlags = Ci.nsMsgMessageFlags;
+
+var gInbox;
+var gOutbox;
+var gAutoRead;
+
+function setupModule(module) {
+  for (let lib of MODULE_REQUIRES) {
+    collector.getModule(lib).installInto(module);
+  }
+
+  gAutoRead = Services.prefs.getBoolPref("mailnews.mark_message_read.auto");
+  Services.prefs.setBoolPref("mailnews.mark_message_read.auto", false);
+
+  gOutbox = MailServices.accounts.localFoldersServer.rootFolder.getChildNamed("Outbox");
+  gInbox = MailServices.accounts.localFoldersServer.rootFolder.getChildNamed("Inbox");
+  make_new_sets_in_folder(gInbox, [ {count: 6} ]);
+
+  // We delete the first message so that we have to compact anything.
+  be_in_folder(gInbox);
+  let curMessage = select_click_row(0);
+  press_delete(mc);
+  assert_not_equals(curMessage, select_click_row(0));
+
+  let urlListener = {
+    compactDone: false,
+
+    OnStartRunningUrl: function (aUrl) {
+    },
+    OnStopRunningUrl: function (aUrl, aExitCode) {
+      assert_equals(aExitCode, 0);
+      assert_true(gInbox.msgDatabase.summaryValid);
+      this.compactDone = true;
+    }
+  };
+
+  // Compaction adds the X-Mozilla-Status rows into the messages
+  // that we will need later on.
+  assert_true(gInbox.msgStore.supportsCompaction);
+  gInbox.compact(urlListener, null);
+
+  mc.waitFor(function() { return urlListener.compactDone; },
+             "Timeout waiting for compact to complete", 10000, 100);
+}
+
+/**
+ * Checks that a message has particular status stored in the data file.
+ * Either the aMsgHdr or the aOffset+aStatusOffset must be non-null.
+ *
+ * @param aMsgHdr        The nsIMsgDBHdr header of the message to check. Optional.
+ * @param aOffset        Offset in the file where the message data starts. Optional.
+ * @param aStatusOffset  Offset from the start of the message where the status line is. Optional.
+ * @param aStatus        The required status of the message.
+ */
+function check_status(aMsgHdr, aOffset, aStatusOffset, aStatus) {
+  if (aOffset == null)
+    aOffset = aMsgHdr.messageOffset;
+  if (aStatusOffset == null)
+    aStatusOffset = aMsgHdr.statusOffset;
+
+  let folder = (aMsgHdr == null) ? gInbox : aMsgHdr.folder;
+
+  let mboxstring = IOUtils.loadFileToString(folder.filePath);
+
+  let expectedStatusString = aStatus.toString(16);
+  while (expectedStatusString.length < 4) {
+    expectedStatusString = '0' + expectedStatusString;
+  }
+
+  assert_equals(mboxstring.substr(aOffset + aStatusOffset, statusHeader.length),
+                statusHeader,
+                "The header '" + statusHeader + "' not found at offset: " + aOffset + ", statusOffset: " + aStatusOffset);
+  assert_equals(mboxstring.substr(aOffset + aStatusOffset + statusHeader.length, 4),
+                expectedStatusString);
+}
+
+function test_mark_messages_read() {
+  // 5 messages in the folder
+  be_in_folder(gInbox);
+  let curMessage = select_click_row(0);
+  // Store the values because they will be unavailable via the hdr
+  // after the message is deleted.
+  let offset = curMessage.messageOffset;
+  let statusOffset = curMessage.statusOffset;
+  check_status(curMessage, null, null, 0); // status = unread
+  press_delete(mc);
+  assert_not_equals(curMessage, select_click_row(0));
+  check_status(null, offset, statusOffset,
+               nsMsgMessageFlags.Read + nsMsgMessageFlags.Expunged);
+
+  // 4 messages in the folder.
+  curMessage = select_click_row(0);
+  check_status(curMessage, null, null, 0); // status = unread
+
+  // Make sure we can mark all read with >0 messages unread.
+  right_click_on_row(0);
+  mc.click_menus_in_sequence(mc.e("mailContext"), [{id: "mailContext-mark"},
+                                                   {id: "mailContext-markAllRead"}]);
+
+  // All the 4 messages should now be read.
+  assert_true(curMessage.isRead, "Message should have been marked Read!");
+  check_status(curMessage, null, null, nsMsgMessageFlags.Read);
+  curMessage = select_click_row(1);
+  assert_true(curMessage.isRead, "Message should have been marked Read!");
+  check_status(curMessage, null, null, nsMsgMessageFlags.Read);
+  curMessage = select_click_row(2);
+  assert_true(curMessage.isRead, "Message should have been marked Read!");
+  check_status(curMessage, null, null, nsMsgMessageFlags.Read);
+  curMessage = select_click_row(3);
+  assert_true(curMessage.isRead, "Message should have been marked Read!");
+  check_status(curMessage, null, null, nsMsgMessageFlags.Read);
+
+  // Let's have the last message unread.
+  right_click_on_row(3);
+  mc.click_menus_in_sequence(mc.e("mailContext"), [{id: "mailContext-mark"},
+                                                   {id: "mailContext-markUnread"}]);
+  assert_false(curMessage.isRead, "Message should have not been marked Read!");
+  check_status(curMessage, null, null, 0);
+}
+
+function test_mark_messages_flagged() {
+  // Mark a message with the star.
+  let curMessage = select_click_row(1);
+  right_click_on_row(1);
+  mc.click_menus_in_sequence(mc.e("mailContext"), [{id: "mailContext-mark"},
+                                                   {id: "mailContext-markFlagged"}]);
+  assert_true(curMessage.isFlagged, "Message should have been marked Flagged!");
+  check_status(curMessage, null, null, nsMsgMessageFlags.Read + nsMsgMessageFlags.Marked);
+}
+
+function subtest_check_queued_message() {
+  // Always check the last message in the Outbox for the correct flag.
+  let outbox = MailServices.accounts.localFoldersServer.rootFolder.getChildNamed("Outbox");
+  be_in_folder(outbox);
+  let queued = outbox.messages;
+  while (queued.hasMoreElements()) {
+    let msg = queued.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+    if (!queued.hasMoreElements()) {
+      check_status(msg, null, null, nsMsgMessageFlags.Queued);
+    }
+  }
+}
+
+/**
+ * Create a reply or forward of a message and queue it for sending later.
+ *
+ * @param aMsgRow  Row index of message in Inbox that is to be replied/forwarded.
+ * @param aReply   true = reply, false = forward.
+ */
+function reply_forward_message(aMsgRow, aReply) {
+  be_in_folder(gInbox);
+  select_click_row(aMsgRow);
+  let cwc;
+  if (aReply)
+    // Reply to the message.
+    cwc = open_compose_with_reply();
+  else {
+    // Forward the message.
+    cwc = open_compose_with_forward();
+    // Type in some recipient.
+    cwc.type(cwc.eid("addressCol2#1"), "somewhere@host.invalid");
+  }
+
+  // Send it later.
+  plan_for_window_close(cwc);
+  // Ctrl+Shift+Return = Send Later
+  cwc.keypress(null, "VK_RETURN", {shiftKey: true, accelKey: true});
+  wait_for_window_close(cwc);
+
+  subtest_check_queued_message();
+
+  // Now this is hacky. We can't get the message to be sent out of TB because there
+  // is no fake SMTP server support yet.
+  // But we know that upon real sending of the message, the code would/should call
+  // .addMessageDispositionState(). So call it directly and check the expected
+  // flags were set. This is risky as the real code could change and call
+  // a different function and the purpose of this test would be lost.
+  be_in_folder(gInbox);
+  let curMessage = select_click_row(aMsgRow);
+  let disposition = aReply ? gInbox.nsMsgDispositionState_Replied :
+                             gInbox.nsMsgDispositionState_Forwarded;
+  gInbox.addMessageDispositionState(curMessage, disposition);
+}
+
+function test_mark_messages_replied() {
+  reply_forward_message(2, true);
+  let curMessage = select_click_row(2);
+  check_status(curMessage, null, null,
+               nsMsgMessageFlags.Replied + nsMsgMessageFlags.Read);
+}
+
+function test_mark_messages_forwarded() {
+  be_in_folder(gInbox);
+  // Forward a clean message.
+  reply_forward_message(3, false);
+  let curMessage = select_click_row(3);
+  check_status(curMessage, null, null, nsMsgMessageFlags.Forwarded);
+
+  // Forward a message that is read and already replied to.
+  reply_forward_message(2, false);
+  curMessage = select_click_row(2);
+  check_status(curMessage, null, null,
+               nsMsgMessageFlags.Forwarded + nsMsgMessageFlags.Replied +
+               nsMsgMessageFlags.Read);
+}
+
+function clear_folder(aFolder) {
+  be_in_folder(aFolder);
+  let msgCount = 0;
+  while ((msgCount = aFolder.getTotalMessages(false)) > 0) {
+    let curMessage = select_click_row(0);
+    press_delete(mc)
+  }
+}
+
+function teardownModule(module) {
+  Services.prefs.setBoolPref("mailnews.mark_message_read.auto", gAutoRead);
+  // Clear all the created messages.
+  be_in_folder(gInbox.parent);
+  clear_folder(gInbox);
+  clear_folder(gOutbox);
+  gInbox.server.rootFolder.emptyTrash(null, null);
+}
--- a/mail/test/mozmill/shared-modules/test-window-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-window-helpers.js
@@ -947,17 +947,17 @@ var AugmentEverybodyWith = {
      *     wait for it to open if it is in the process.
      * @param aActions A list of objects where each object has a single
      *     attribute with a single value.  We pick the menu option whose DOM
      *     node has an attribute with that name and value.  We click whatever we
      *     find.  We throw if we don't find what you were asking for.
      * @param aKeepOpen  If set to true the popups are not closed after last click.
      *
      * @return  An array of popup elements that were left open. It will be
-     *          an empty array if aKeepOpen was set to true.
+     *          an empty array if aKeepOpen was set to false.
      */
     click_menus_in_sequence: function _click_menus(aRootPopup, aActions, aKeepOpen) {
       if (aRootPopup.state == "closed")
         aRootPopup.openPopup(null, "", 0, 0, true, true);
       if (aRootPopup.state != "open") { // handle "showing"
         utils.waitFor(function() { return aRootPopup.state == "open"; },
                       "Popup never opened! id=" + aRootPopup.id +
                       ", state=" + aRootPopup.state, 5000, 50);
--- a/mailnews/base/util/nsMsgUtils.cpp
+++ b/mailnews/base/util/nsMsgUtils.cpp
@@ -2020,34 +2020,64 @@ NS_MSG_BASE nsresult MsgGetHeadersFromKe
   NS_ENSURE_ARG_POINTER(aDB);
 
   uint32_t count = aMsgKeys.Length();
   nsresult rv = NS_OK;
 
   for (uint32_t kindex = 0; kindex < count; kindex++)
   {
     nsMsgKey key = aMsgKeys.ElementAt(kindex);
-    nsCOMPtr<nsIMsgDBHdr> msgHdr;
+
     bool hasKey;
     rv = aDB->ContainsKey(key, &hasKey);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // This function silently skips when the key is not found. This is an expected case.
     if (hasKey)
     {
+      nsCOMPtr<nsIMsgDBHdr> msgHdr;
       rv = aDB->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
       NS_ENSURE_SUCCESS(rv, rv);
 
       aHeaders->AppendElement(msgHdr, false);
     }
   }
 
   return rv;
 }
 
+NS_MSG_BASE nsresult MsgGetHdrsFromKeys(nsIMsgDatabase *aDB, nsMsgKey *aMsgKeys,
+                                        uint32_t aNumKeys, nsIMutableArray **aHeaders)
+{
+  NS_ENSURE_ARG_POINTER(aDB);
+  NS_ENSURE_ARG_POINTER(aMsgKeys);
+  NS_ENSURE_ARG_POINTER(aHeaders);
+
+  nsresult rv;
+  nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  for (uint32_t kindex = 0; kindex < aNumKeys; kindex++) {
+    nsMsgKey key = aMsgKeys[kindex];
+    bool hasKey;
+    rv = aDB->ContainsKey(key, &hasKey);
+    // This function silently skips when the key is not found. This is an expected case.
+    if (NS_SUCCEEDED(rv) && hasKey)
+    {
+      nsCOMPtr<nsIMsgDBHdr> msgHdr;
+      rv = aDB->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+      if (NS_SUCCEEDED(rv))
+        messages->AppendElement(msgHdr, false);
+    }
+  }
+
+  messages.forget(aHeaders);
+  return NS_OK;
+}
+
 bool MsgAdvanceToNextLine(const char *buffer, uint32_t &bufferOffset, uint32_t maxBufferOffset)
 {
   bool result = false;
   for (; bufferOffset < maxBufferOffset; bufferOffset++)
   {
     if (buffer[bufferOffset] == '\r' || buffer[bufferOffset] == '\n')
     {
       bufferOffset++;
--- a/mailnews/base/util/nsMsgUtils.h
+++ b/mailnews/base/util/nsMsgUtils.h
@@ -218,21 +218,26 @@ NS_MSG_BASE nsresult MsgEscapeString(con
                                      uint32_t aType, nsACString &aResult);
 
 NS_MSG_BASE nsresult MsgUnescapeString(const nsACString &aStr, 
                                        uint32_t aFlags, nsACString &aResult);
 
 NS_MSG_BASE nsresult MsgEscapeURL(const nsACString &aStr, uint32_t aFlags,
                                   nsACString &aResult);
 
-// Converts an array of nsMsgKeys plus a database, to an array of nsIMsgDBHdrs.
-NS_MSG_BASE nsresult MsgGetHeadersFromKeys(nsIMsgDatabase *aDB, 
+// Converts an nsTArray of nsMsgKeys plus a database, to an array of nsIMsgDBHdrs.
+NS_MSG_BASE nsresult MsgGetHeadersFromKeys(nsIMsgDatabase *aDB,
                                            const nsTArray<nsMsgKey> &aKeys,
                                            nsIMutableArray *aHeaders);
- 
+// Converts an array of nsMsgKeys plus a database, to an array of nsIMsgDBHdrs.
+NS_MSG_BASE nsresult MsgGetHdrsFromKeys(nsIMsgDatabase *aDB,
+                                        nsMsgKey *aKeys,
+                                        uint32_t aNumKeys,
+                                        nsIMutableArray **aHeaders);
+
 NS_MSG_BASE nsresult MsgExamineForProxy(nsIChannel *channel,
                                         nsIProxyInfo **proxyInfo);
 
 NS_MSG_BASE int32_t MsgFindCharInSet(const nsCString &aString,
                                      const char* aChars, uint32_t aOffset = 0);
 NS_MSG_BASE int32_t MsgFindCharInSet(const nsString &aString,
                                      const char* aChars, uint32_t aOffset = 0);
 
--- a/mailnews/local/src/nsLocalMailFolder.cpp
+++ b/mailnews/local/src/nsLocalMailFolder.cpp
@@ -1231,16 +1231,43 @@ nsMsgLocalMailFolder::DeleteMessages(nsI
       if (msgWindow && !isMove)
         AutoCompact(msgWindow);
     }
   }
   return rv;
 }
 
 NS_IMETHODIMP
+nsMsgLocalMailFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag)
+{
+  nsMsgMessageFlagType msgFlag = 0;
+  switch (aDispositionFlag) {
+    case nsIMsgFolder::nsMsgDispositionState_Replied:
+      msgFlag = nsMsgMessageFlags::Replied;
+      break;
+    case nsIMsgFolder::nsMsgDispositionState_Forwarded:
+      msgFlag = nsMsgMessageFlags::Forwarded;
+      break;
+    default:
+      return NS_ERROR_UNEXPECTED;
+  }
+
+  nsresult rv = nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIMsgPluggableStore> msgStore;
+  rv = GetMsgStore(getter_AddRefs(msgStore));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+  messages->AppendElement(aMessage, false);
+  return msgStore->ChangeFlags(messages, msgFlag, true);
+}
+
+NS_IMETHODIMP
 nsMsgLocalMailFolder::MarkMessagesRead(nsIArray *aMessages, bool aMarkRead)
 {
   nsresult rv = nsMsgDBFolder::MarkMessagesRead(aMessages, aMarkRead);
   NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr<nsIMsgPluggableStore> msgStore;
   rv = GetMsgStore(getter_AddRefs(msgStore));
   NS_ENSURE_SUCCESS(rv, rv);
   return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Read, aMarkRead);
@@ -1254,16 +1281,89 @@ nsMsgLocalMailFolder::MarkMessagesFlagge
   NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr<nsIMsgPluggableStore> msgStore;
   rv = GetMsgStore(getter_AddRefs(msgStore));
   NS_ENSURE_SUCCESS(rv, rv);
   return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Marked,
                                aMarkFlagged);
 }
 
+NS_IMETHODIMP
+nsMsgLocalMailFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow)
+{
+  nsresult rv = GetDatabase();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsMsgKey *thoseMarked = nullptr;
+  uint32_t numMarked = 0;
+  EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/);
+  rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked);
+  EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+  if (NS_FAILED(rv) || !numMarked || !thoseMarked)
+    return rv;
+
+  do {
+    nsCOMPtr<nsIMutableArray> messages;
+    rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages));
+    if (NS_FAILED(rv))
+      break;
+
+    nsCOMPtr<nsIMsgPluggableStore> msgStore;
+    rv = GetMsgStore(getter_AddRefs(msgStore));
+    if (NS_FAILED(rv))
+      break;
+
+    rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true);
+    if (NS_FAILED(rv))
+      break;
+
+    mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+
+    // Setup a undo-state
+    if (aMsgWindow)
+      rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked);
+  } while (false);
+
+  nsMemory::Free(thoseMarked);
+  return rv;
+}
+
+NS_IMETHODIMP nsMsgLocalMailFolder::MarkThreadRead(nsIMsgThread *thread)
+{
+  nsresult rv = GetDatabase();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsMsgKey *thoseMarked = nullptr;
+  uint32_t numMarked = 0;
+  rv = mDatabase->MarkThreadRead(thread, nullptr, &numMarked, &thoseMarked);
+  if (NS_FAILED(rv) || !numMarked || !thoseMarked)
+    return rv;
+
+  do {
+    nsCOMPtr<nsIMutableArray> messages;
+    rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages));
+    if (NS_FAILED(rv))
+      break;
+
+    nsCOMPtr<nsIMsgPluggableStore> msgStore;
+    rv = GetMsgStore(getter_AddRefs(msgStore));
+    if (NS_FAILED(rv))
+      break;
+
+    rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true);
+    if (NS_FAILED(rv))
+      break;
+
+    mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+  } while (false);
+
+  nsMemory::Free(thoseMarked);
+  return rv;
+}
+
 nsresult
 nsMsgLocalMailFolder::InitCopyState(nsISupports* aSupport,
                                     nsIArray* messages,
                                     bool isMove,
                                     nsIMsgCopyServiceListener* listener,
                                     nsIMsgWindow *msgWindow, bool isFolder,
                                     bool allowUndo)
 {
--- a/mailnews/local/src/nsLocalMailFolder.h
+++ b/mailnews/local/src/nsLocalMailFolder.h
@@ -148,18 +148,22 @@ public:
   NS_IMETHOD CopyFolder(nsIMsgFolder *srcFolder, bool isMoveFolder, nsIMsgWindow *msgWindow,
                           nsIMsgCopyServiceListener* listener) MOZ_OVERRIDE;
   NS_IMETHOD CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* msgToReplace,
                              bool isDraftOrTemplate, 
                              uint32_t newMsgFlags,
                              const nsACString &aNewMsgKeywords,
                              nsIMsgWindow *msgWindow,
                              nsIMsgCopyServiceListener* listener) MOZ_OVERRIDE;
+
+  NS_IMETHOD AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag) MOZ_OVERRIDE;
   NS_IMETHOD MarkMessagesRead(nsIArray *aMessages, bool aMarkRead) MOZ_OVERRIDE;
   NS_IMETHOD MarkMessagesFlagged(nsIArray *aMessages, bool aMarkFlagged) MOZ_OVERRIDE;
+  NS_IMETHOD MarkAllMessagesRead(nsIMsgWindow *aMsgWindow) MOZ_OVERRIDE;
+  NS_IMETHOD MarkThreadRead(nsIMsgThread *thread) MOZ_OVERRIDE;
   NS_IMETHOD GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener) MOZ_OVERRIDE;
   NS_IMETHOD NotifyCompactCompleted() MOZ_OVERRIDE;
   NS_IMETHOD Shutdown(bool shutdownChildren) MOZ_OVERRIDE;
 
   NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement *element) MOZ_OVERRIDE;
   NS_IMETHOD ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element) MOZ_OVERRIDE;
 
   NS_IMETHOD GetName(nsAString& aName) MOZ_OVERRIDE;
--- a/mailnews/local/src/nsMsgLocalStoreUtils.cpp
+++ b/mailnews/local/src/nsMsgLocalStoreUtils.cpp
@@ -180,24 +180,24 @@ nsMsgLocalStoreUtils::ChangeKeywordsHelp
 
 nsresult
 nsMsgLocalStoreUtils::UpdateFolderFlag(nsIMsgDBHdr *mailHdr, bool bSet,
                                        nsMsgMessageFlagType flag,
                                        nsIOutputStream *fileStream)
 {
   uint32_t statusOffset;
   uint64_t msgOffset;
-  (void) mailHdr->GetStatusOffset(&statusOffset);
+  nsresult rv = mailHdr->GetStatusOffset(&statusOffset);
   // This probably means there's no x-mozilla-status header, so
   // we just ignore this.
-  if (statusOffset == 0)
+  if (NS_FAILED(rv) || (statusOffset == 0))
     return NS_OK;
-  (void)mailHdr->GetMessageOffset(&msgOffset);
+  rv = mailHdr->GetMessageOffset(&msgOffset);
+  NS_ENSURE_SUCCESS(rv, rv);
   uint64_t statusPos = msgOffset + statusOffset;
-  nsresult rv;
   nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(fileStream, &rv));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, statusPos);
   NS_ENSURE_SUCCESS(rv, rv);
   char buf[50];
   buf[0] = '\0';
   nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(fileStream, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -238,18 +238,18 @@ nsMsgLocalStoreUtils::UpdateFolderFlag(n
       PR_snprintf(buf, sizeof(buf), X_MOZILLA_STATUS_FORMAT,
         flags & 0x0000FFFF);
       int32_t lineLen = PL_strlen(buf);
       uint64_t status2Pos = statusPos + lineLen;
       fileStream->Write(buf, lineLen, &bytesWritten);
 
       if (flag & 0xFFFF0000)
       {
-        // time to upate x-mozilla-status2
-        // first find it by finding end of previous line, see bug 234935
+        // Time to update x-mozilla-status2,
+        // first find it by finding end of previous line, see bug 234935.
         seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos);
         do
         {
           rv = inputStream->Read(buf, 1, &bytesRead);
           status2Pos++;
         } while (NS_SUCCEEDED(rv) && (*buf == '\n' || *buf == '\r'));
         status2Pos--;
         seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos);