fix 471682, local summary files getting out of date when messages moved/copied to them, r/sr=bienvenu
authorKent James <kent@caspia.com>
Tue, 27 Jan 2009 08:06:42 -0800
changeset 1772 45a9d5cedf9459fbbf8cc8569cf8adde570c660a
parent 1771 daa8b76471bff769faf7b05d3b536753489bdfe7
child 1773 c7f75214e9f46a8d4ba9197a861d82fa31bd9743
push idunknown
push userunknown
push dateunknown
bugs471682
fix 471682, local summary files getting out of date when messages moved/copied to them, r/sr=bienvenu
mailnews/base/test/unit/test_bug471682.js
mailnews/db/msgdb/public/nsMailDatabase.h
mailnews/db/msgdb/src/nsMailDatabase.cpp
mailnews/local/src/nsLocalMailFolder.cpp
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/test_bug471682.js
@@ -0,0 +1,133 @@
+/* ***** 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
+ * Kent James <kent@caspia.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * 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 ***** */
+
+/*
+ * Test of message database validity on local copy from bug 471682. What
+ * we want to do here is to copy a couple of message to a new folder, and
+ * then compare the date and filesize of the folder file with the
+ * stored result in dbfolderinfo. If they don't match, that's bad.
+ */
+const copyService = Cc["@mozilla.org/messenger/messagecopyservice;1"]
+                      .getService(Ci.nsIMsgCopyService);
+const bugmail1 = do_get_file("../mailnews/test/data/bugmail1");
+var gHdr; // header of test message in local folder
+
+loadLocalMailAccount();
+// create a subfolder as a target for copies
+var gSubfolder = gLocalInboxFolder.addSubfolder("subfolder");
+
+function run_test()
+{
+  do_test_pending();
+  dump(" step 1 ");
+  // step 1: copy a message into the local inbox
+  copyService.CopyFileMessage(bugmail1, gLocalInboxFolder, null, false, 0,
+                              "", step2, null);
+  return;
+}
+
+// step 2: copy one message into a subfolder to establish an
+//         mbox file time and size
+// nsIMsgCopyServiceListener implementation
+var step2 = 
+{
+  OnStartCopy: function() {},
+  OnProgress: function(aProgress, aProgressMax) {},
+  SetMessageKey: function(aKey)
+  {
+    gHdr = gLocalInboxFolder.GetMessageHeader(aKey);
+  },
+  SetMessageId: function(aMessageId) {},
+  OnStopCopy: function(aStatus)
+  {
+    dump(" step 2 ");
+    // copy the message into the subfolder
+    var messages = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+    messages.appendElement(gHdr, false);
+    copyService.CopyMessages(gLocalInboxFolder, messages, gSubfolder, false,
+                             step3, null, false);
+  }
+};
+
+// step 3: after the copy, delay to allow copy to complete and allow possible
+//         file error time
+// nsIMsgCopyServiceListener implementation
+var step3 = 
+{
+  OnStartCopy: function() {},
+  OnProgress: function(aProgress, aProgressMax) {},
+  SetMessageKey: function(aKey) {},
+  SetMessageId: function(aMessageId) {},
+  OnStopCopy: function(aStatus)
+  {
+    dump(" step 3 ");
+    do_timeout(2000, "step4();");
+  }
+}
+
+// step 4: start a second copy
+function step4()
+{
+  dump(" step 4 ");
+  var messages = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+  messages.appendElement(gHdr, false);
+  copyService.CopyMessages(gLocalInboxFolder, messages, gSubfolder, false,
+                           step5, null, false);
+}
+
+// step 5:  actual tests of file size and date
+// nsIMsgCopyServiceListener implementation
+var step5 = 
+{
+  OnStartCopy: function() {},
+  OnProgress: function(aProgress, aProgressMax) {},
+  SetMessageKey: function(aKey) {},
+  SetMessageId: function(aMessageId) {},
+  OnStopCopy: function(aStatus)
+  {
+    dump(" step 5 ");
+    var dbSize = gSubfolder.msgDatabase.dBFolderInfo.folderSize;
+    var dbDate = gSubfolder.msgDatabase.dBFolderInfo.folderDate;
+    var filePath = gSubfolder.filePath;
+    var date = parseInt(filePath.lastModifiedTime/1000);
+    var size = filePath.fileSize;
+    dump("\nactual size: " + size + " recorded size: " + dbSize);
+    do_check_eq(size, dbSize);
+    dump("\nactual date: " + date + " recorded date: " + dbDate);
+    do_check_eq(date, dbDate);
+    do_test_finished();
+  }
+}
--- a/mailnews/db/msgdb/public/nsMailDatabase.h
+++ b/mailnews/db/msgdb/public/nsMailDatabase.h
@@ -78,17 +78,19 @@ public:
 
   NS_IMETHOD SetFolderStream(nsIOutputStream *aFileStream);
   NS_IMETHOD GetFolderStream(nsIOutputStream **aFileStream);
 
   friend class nsMsgOfflineOpEnumerator;
 protected:
 
   nsresult        GetAllOfflineOpsTable(); // get this on demand
-  PRUint32        GetMailboxModDate(); 
+
+  // get the time and date of the mailbox file
+  void            GetMailboxModProperties(PRInt64 *aSize, PRUint32 *aDate); 
 
   nsCOMPtr <nsIMdbTable>  m_mdbAllOfflineOpsTable;
   mdb_token       m_offlineOpsRowScopeToken;
   mdb_token       m_offlineOpsTableKindToken;
 
   virtual PRBool  SetHdrFlag(nsIMsgDBHdr *, PRBool bSet, nsMsgMessageFlagType flag);
   virtual void    UpdateFolderFlag(nsIMsgDBHdr *msgHdr, PRBool bSet, 
                                    nsMsgMessageFlagType flag, nsIOutputStream **ppFileStream);
--- a/mailnews/db/msgdb/src/nsMailDatabase.cpp
+++ b/mailnews/db/msgdb/src/nsMailDatabase.cpp
@@ -369,67 +369,71 @@ void nsMailDatabase::UpdateFolderFlag(ns
     }
     if (!m_folderStream)
       *ppFileStream = fileStream; // This tells the caller that we opened the file, and please to close it.
     else if (!m_ownFolderStream)
       seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, folderStreamPos);
   }
 }
 
-PRUint32 nsMailDatabase::GetMailboxModDate()
+// Get the current attributes of the mbox file, corrected for caching
+void nsMailDatabase::GetMailboxModProperties(PRInt64 *aSize, PRUint32 *aDate)
 {
-  PRUint32 retModTime = 0;
+  // We'll simply return 0 on errors.
+  *aDate = 0;
+  *aSize = 0;
+  if (!m_folderFile)
+    return;
+
+  // clone file because nsLocalFile caches sizes and dates.
+  nsCOMPtr<nsIFile> copyFolderFile;
+  nsresult rv = m_folderFile->Clone(getter_AddRefs(copyFolderFile));
+  if (NS_FAILED(rv) || !copyFolderFile)
+    return;
+
+  if (NS_FAILED(copyFolderFile->GetFileSize(aSize))
+    return;
+
   PRInt64 lastModTime;
-  if (m_folderFile)
-  {
-    nsresult rv = m_folderFile->GetLastModifiedTime(&lastModTime);
-    if (NS_SUCCEEDED(rv))
-    {
+  if (NS_FAILED(copyFolderFile->GetLastModifiedTime(&lastModTime))
+    return;
 
-      PRTime  temp64;
-      PRInt64 thousand;
-      LL_I2L(thousand, PR_MSEC_PER_SEC);
-      LL_DIV(temp64, lastModTime, thousand);
-      LL_L2UI(retModTime, temp64);
-    }
-  }
-  return retModTime;
+  PRTime  temp64;
+  PRInt64 thousand;
+  LL_I2L(thousand, PR_MSEC_PER_SEC);
+  LL_DIV(temp64, lastModTime, thousand);
+  LL_L2UI(*aDate, temp64);
+  return;
 }
 
 NS_IMETHODIMP nsMailDatabase::GetSummaryValid(PRBool *aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
   PRUint32 folderSize;
   PRUint32  folderDate;
-  PRUint32  actualFolderTimeStamp;
   PRInt32 numUnreadMessages;
   nsAutoString errorMsg;
 
-        
   *aResult = PR_FALSE;
-  
+
   if (m_folderFile && m_dbFolderInfo)
   {
-    actualFolderTimeStamp = GetMailboxModDate();
-  
     m_dbFolderInfo->GetNumUnreadMessages(&numUnreadMessages);
     m_dbFolderInfo->GetFolderSize(&folderSize);
     m_dbFolderInfo->GetFolderDate(&folderDate);
 
     // compare current version of db versus filed out version info, 
     // and file size in db vs file size on disk.
     PRUint32 version;
+    m_dbFolderInfo->GetVersion(&version);
 
-    m_dbFolderInfo->GetVersion(&version);
-    nsCOMPtr <nsIFile> copyFolderFile;
-    // clone file because nsLocalFile caches sizes.
-    nsresult rv = m_folderFile->Clone(getter_AddRefs(copyFolderFile));
-    NS_ENSURE_SUCCESS(rv, rv);
     PRInt64 fileSize;
-    copyFolderFile->GetFileSize(&fileSize);
+    PRUint32 actualFolderTimeStamp;
+    GetMailboxModProperties(&fileSize, &actualFolderTimeStamp);
+
     if (folderSize == fileSize &&
         numUnreadMessages >= 0 && GetCurVersion() == version)
     {
       GetGlobalPrefs();
       // if those values are ok, check time stamp
       if (gTimeStampLeeway == 0)
         *aResult = folderDate == actualFolderTimeStamp;
       else
@@ -479,23 +483,19 @@ NS_IMETHODIMP nsMailDatabase::SetSummary
   m_folderFile->Exists(&exists);
   if (!exists) 
     return NS_MSG_ERROR_FOLDER_MISSING;
   
   if (m_dbFolderInfo)
   {
     if (valid)
     {
-      PRUint32 actualFolderTimeStamp = GetMailboxModDate();
+      PRUint32 actualFolderTimeStamp;
       PRInt64 fileSize;
-      nsCOMPtr <nsIFile> copyFolderFile;
-      // clone file because nsLocalFile caches sizes.
-      rv = m_folderFile->Clone(getter_AddRefs(copyFolderFile));
-      NS_ENSURE_SUCCESS(rv, rv);
-      copyFolderFile->GetFileSize(&fileSize);
+      GetMailboxModProperties(&fileSize, &actualFolderTimeStamp);
       m_dbFolderInfo->SetFolderSize((PRUint32) fileSize);
       m_dbFolderInfo->SetFolderDate(actualFolderTimeStamp);
       m_dbFolderInfo->SetVersion(GetCurVersion());
     }
     else
     {
       m_dbFolderInfo->SetVersion(0);	// that ought to do the trick.
     }
@@ -730,19 +730,19 @@ nsresult nsMailDatabase::SetFolderInfoVa
 #ifdef DEBUG
     printf("Exception opening summary file\n");
 #endif
     return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
   }
   
   {
     pMessageDB->m_folderFile = folderName;
-    PRUint32 actualFolderTimeStamp = pMessageDB->GetMailboxModDate();
+    PRUint32 actualFolderTimeStamp;
     PRInt64 fileSize;
-    folderName->GetFileSize(&fileSize);
+    pMessageDB->GetMailboxModProperties(&fileSize, &actualFolderTimeStamp);
     pMessageDB->m_dbFolderInfo->SetFolderSize((PRUint32) fileSize);
     pMessageDB->m_dbFolderInfo->SetFolderDate(actualFolderTimeStamp);
     pMessageDB->m_dbFolderInfo->ChangeNumUnreadMessages(numunread);
     pMessageDB->m_dbFolderInfo->ChangeNumMessages(num);
   }
   // if we opened the db, then we'd better close it. Otherwise, we found it in the cache,
   // so just commit and release.
   if (bOpenedDB)
--- a/mailnews/local/src/nsLocalMailFolder.cpp
+++ b/mailnews/local/src/nsLocalMailFolder.cpp
@@ -2445,21 +2445,30 @@ NS_IMETHODIMP nsMsgLocalMailFolder::EndC
 
   nsCOMPtr <nsISeekableStream> seekableStream;
   if (mCopyState)
   {
     NS_ASSERTION(mCopyState->m_leftOver == 0, "whoops, something wrong with previous copy");
     mCopyState->m_leftOver = 0; // reset to 0.
     // need to reset this in case we're move/copying multiple msgs.
     mCopyState->m_fromLineSeen = PR_FALSE;
-    // flush the copied message.
+
+    // flush the copied message. Seeking causes a flush, w/o syncing. But we
+    // need a flush at the end to get the file size and time updated correctly.
     if (mCopyState->m_fileStream)
     {
-      seekableStream = do_QueryInterface(mCopyState->m_fileStream);
-      seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0); // seeking causes a flush, w/o syncing
+      if (multipleCopiesFinished)
+      {
+        mCopyState->m_fileStream->Flush();
+      }
+      else
+      {
+        seekableStream = do_QueryInterface(mCopyState->m_fileStream);
+        seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
+      }
     }
   }
   //Copy the header to the new database
   if (copySucceeded && mCopyState->m_message)
   {
     //  CopyMessages() goes here; CopyFileMessage() never gets in here because
     //  the mCopyState->m_message will be always null for file message
     nsCOMPtr<nsIMsgDBHdr> newHdr;