Bug 854791 - Check free disk space looks enough before starting compacting mbox folder. r=rkent, ui-r=bwinton, a=Ratty on a SeaMonkey CLOSED TREE
authoraceman <acelists@atlas.sk>
Tue, 17 Feb 2015 14:18:54 +0100
changeset 17311 a85a4ebc9890ab73a54f742bdde931b0721b578f
parent 17310 bc4533660d85dd258450230c757e99e34cda99b5
child 17312 99137515f12e4dfd620221e48e96cb4af13a2d84
push id1326
push usermbanner@mozilla.com
push dateMon, 30 Mar 2015 20:10:12 +0000
treeherdercomm-esr52@20dbdba45e07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrkent, bwinton, Ratty
bugs854791
Bug 854791 - Check free disk space looks enough before starting compacting mbox folder. r=rkent, ui-r=bwinton, a=Ratty on a SeaMonkey CLOSED TREE
mail/locales/en-US/chrome/messenger/messenger.properties
mailnews/base/src/nsMsgFolderCompactor.cpp
mailnews/base/src/nsMsgFolderCompactor.h
mailnews/db/msgdb/public/nsIMsgDatabase.idl
mailnews/db/msgdb/src/nsMsgDatabase.cpp
suite/locales/en-US/chrome/mailnews/messenger.properties
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -78,16 +78,17 @@ confirmFolderDeletionForFilter=Deleting 
 alertFilterChanged=Filters associated with this folder will be updated.
 filterDisabled=The folder '%S' could not be found, so filter(s) associated with this folder will be disabled. Verify that the folder exists, and that filters point to a valid destination folder.
 filterFolderDeniedLocked=The messages could not be filtered to folder '%S' because another operation is in progress.
 parsingFolderFailed=Unable to open the folder %S because it is in use by some other operation. Please wait for that operation to finish and then select the folder again.
 deletingMsgsFailed=Unable to delete messages in folder %S because it is in use by some other operation. Please wait for that operation to finish and then try again.
 alertFilterCheckbox=Do not warn me again.
 compactFolderDeniedLock=The folder '%S' cannot be compacted because another operation is in progress. Please try again later.
 compactFolderWriteFailed=The folder '%S' could not be compacted because writing to folder failed. Verify that you have enough disk space, and that you have write privileges to the file system, then try again.
+compactFolderInsufficientSpace=Some folders (e.g. '%S') cannot be compacted because there is not enough free disk space. Please delete some files and try again.
 filterFolderHdrAddFailed=The messages could not be filtered to folder '%S' because adding a message to it failed. Verify that the folder is displaying properly or try to repair it from the folder properties.
 filterFolderWriteFailed=The messages could not be filtered to folder '%S' because writing to folder failed. Verify that you have enough disk space, and that you have write privileges to the file system, then try again.
 copyMsgWriteFailed=The messages could not be moved or copied to folder '%S' because writing to folder failed. To gain disk space, from the File menu, first choose Empty Trash, and then choose Compact Folders, and then try again.
 cantMoveMsgWOBodyOffline=While working offline, you cannot move or copy messages that were not downloaded for offline use. From the Mail window, open the File menu, choose Offline, then uncheck Work Offline, and then try again.
 operationFailedFolderBusy=The operation failed because another operation is using the folder. Please wait for that operation to finish and then try again.
 folderRenameFailed=The folder could not be renamed. Perhaps the folder is being reparsed, or the new name is not a valid folder name.
 # LOCALIZATION NOTE(verboseFolderFormat): %1$S is folder name, %2$S is server name
 verboseFolderFormat=%1$S on %2$S
--- a/mailnews/base/src/nsMsgFolderCompactor.cpp
+++ b/mailnews/base/src/nsMsgFolderCompactor.cpp
@@ -28,16 +28,17 @@
 #include "nsIMsgDatabase.h"
 #include "nsArrayUtils.h"
 #include "nsMsgMessageFlags.h"
 #include "nsIMsgStatusFeedback.h"
 #include "nsMsgBaseCID.h"
 #include "nsIMsgFolderNotificationService.h"
 #include "nsIMsgPluggableStore.h"
 #include "nsMsgFolderCompactor.h"
+#include <algorithm>
 
 //////////////////////////////////////////////////////////////////////////////
 // nsFolderCompactState
 //////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(nsFolderCompactState, nsIMsgFolderCompactor, nsIRequestObserver, nsIStreamListener, nsICopyMessageStreamListener, nsIUrlListener)
 
 nsFolderCompactState::nsFolderCompactState()
@@ -49,16 +50,17 @@ nsFolderCompactState::nsFolderCompactSta
   m_compactAll = false;
   m_compactOfflineAlso = false;
   m_compactingOfflineFolders = false;
   m_parsingFolder=false;
   m_folderIndex = 0;
   m_startOfMsg = true;
   m_needStatusLine = false;
   m_totalExpungedBytes = 0;
+  m_alreadyWarnedDiskSpace = false;
 }
 
 nsFolderCompactState::~nsFolderCompactState()
 {
   CloseOutputStream();
   if (NS_FAILED(m_status))
   {
     CleanupTempFilesAfterError();
@@ -162,24 +164,19 @@ nsFolderCompactState::Compact(nsIMsgFold
   m_listener = aListener;
   if (!m_compactingOfflineFolders && !aOfflineStore)
   {
     nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
     if (imapFolder)
       return imapFolder->Expunge(this, aMsgWindow);
   }
 
-   int64_t expunged;
-   folder->GetExpungedBytes(&expunged);
-   m_totalExpungedBytes += expunged;
    m_window = aMsgWindow;
    nsresult rv;
    nsCOMPtr<nsIMsgDatabase> db;
-   nsCOMPtr<nsIDBFolderInfo> folderInfo;
-   nsCOMPtr<nsIMsgDatabase> mailDBFactory;
    nsCOMPtr<nsIFile> path;
    nsCString baseMessageURI;
 
    nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder, &rv);
    if (NS_SUCCEEDED(rv) && localFolder)
    {
      rv=localFolder->GetDatabaseWOReparse(getter_AddRefs(db));
      if (NS_FAILED(rv) || !db)
@@ -207,38 +204,87 @@ nsFolderCompactState::Compact(nsIMsgFold
        }
      }
    }
    else
    {
      rv = folder->GetMsgDatabase(getter_AddRefs(db));
      NS_ENSURE_SUCCESS(rv, rv);
    }
+
    rv = folder->GetFilePath(getter_AddRefs(path));
    NS_ENSURE_SUCCESS(rv, rv);
 
-   rv = folder->GetBaseMessageURI(baseMessageURI);
+   int64_t expunged;
+   folder->GetExpungedBytes(&expunged);
+
+   bool abortCompactFolder = false;
+   int64_t diskSize;
+   rv = folder->GetSizeOnDisk(&diskSize);
+   NS_ENSURE_SUCCESS(rv, rv);
+
+   int64_t diskFree;
+   rv = path->GetDiskSpaceAvailable(&diskFree);
    NS_ENSURE_SUCCESS(rv, rv);
-    
-   rv = Init(folder, baseMessageURI.get(), db, path, m_window);
+
+   // Let's try to not even start compact if there is really low free space.
+   // It may still fail later as we do not know how big exactly the folder DB will
+   // end up being.
+   // The DB already doesn't contain references to messages that are already deleted.
+   // So theoretically it shouldn't shrink with compact. But in practice,
+   // the automatic shrinking of the DB may still have not yet happened.
+   // So we cap the final size at 1KB per message.
+   db->Commit(nsMsgDBCommitType::kCompressCommit);
+
+   int64_t dbSize;
+   rv = db->GetDatabaseSize(&dbSize);
    NS_ENSURE_SUCCESS(rv, rv);
 
-   bool isLocked;
-   m_folder->GetLocked(&isLocked);
-   if(!isLocked)
+   int32_t totalMsgs;
+   rv = folder->GetTotalMessages(false, &totalMsgs);
+   NS_ENSURE_SUCCESS(rv, rv);
+   int64_t expectedDBSize = std::min<int64_t>(dbSize, totalMsgs * 1024);
+   if (diskFree < diskSize - expunged + expectedDBSize)
+   {
+     if (!m_alreadyWarnedDiskSpace)
+     {
+       folder->ThrowAlertMsg("compactFolderInsufficientSpace", m_window);
+       m_alreadyWarnedDiskSpace = true;
+     }
+     abortCompactFolder = true;
+   }
+
+   if (!abortCompactFolder)
+   {
+     rv = folder->GetBaseMessageURI(baseMessageURI);
+     NS_ENSURE_SUCCESS(rv, rv);
+
+     rv = Init(folder, baseMessageURI.get(), db, path, m_window);
+     NS_ENSURE_SUCCESS(rv, rv);
+
+     bool isLocked;
+     m_folder->GetLocked(&isLocked);
+     if (isLocked)
+     {
+       m_folder->NotifyCompactCompleted();
+       CleanupTempFilesAfterError();
+       m_folder->ThrowAlertMsg("compactFolderDeniedLock", m_window);
+       abortCompactFolder = true;
+     }
+   }
+
+   if (!abortCompactFolder)
    {
      nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMsgFolderCompactor*>(this));
      m_folder->AcquireSemaphore(supports);
+     m_totalExpungedBytes += expunged;
      return StartCompacting();
    }
    else
    {
-     m_folder->NotifyCompactCompleted();
-     CleanupTempFilesAfterError();
-     m_folder->ThrowAlertMsg("compactFolderDeniedLock", m_window);
      if (m_compactAll)
        return CompactNextFolder();
      else
        return NS_OK;
    }
 }
 
 nsresult nsFolderCompactState::ShowStatusMsg(const nsString& aMsg)
@@ -428,17 +474,17 @@ nsFolderCompactState::FinishCompact()
   // close down database of the original folder
   m_folder->ForceDBClosed();
 
   nsCOMPtr<nsIFile> cloneFile;
   int64_t fileSize;
   rv = m_file->Clone(getter_AddRefs(cloneFile));
   if (NS_SUCCEEDED(rv))
     rv = cloneFile->GetFileSize(&fileSize);
-  bool tempFileRightSize = (fileSize == m_totalMsgSize);
+  bool tempFileRightSize = ((uint64_t)fileSize == m_totalMsgSize);
   NS_WARN_IF_FALSE(tempFileRightSize, "temp file not of expected size in compact");
 
   bool folderRenameSucceeded = false;
   bool msfRenameSucceeded = false;
   if (NS_SUCCEEDED(rv) && tempFileRightSize)
   {
     // First we're going to try and move the old summary file out the way.
     // We don't delete it yet, as we want to keep the files in sync.
--- a/mailnews/base/src/nsMsgFolderCompactor.h
+++ b/mailnews/base/src/nsMsgFolderCompactor.h
@@ -83,16 +83,17 @@ protected:
   bool m_parsingFolder; //flag for parsing local folders;
   // these members are used to add missing status lines to compacted messages.
   bool m_needStatusLine;
   bool m_startOfMsg;
   int32_t m_statusOffset;
   uint32_t m_addedHeaderSize;
   nsCOMPtr<nsIArray> m_offlineFolderArray;
   nsCOMPtr<nsIUrlListener> m_listener;
+  bool m_alreadyWarnedDiskSpace;
 };
 
 class nsOfflineStoreCompactState : public nsFolderCompactState
 {
 public:
 
   nsOfflineStoreCompactState(void);
   virtual ~nsOfflineStoreCompactState(void);
--- a/mailnews/db/msgdb/public/nsIMsgDatabase.idl
+++ b/mailnews/db/msgdb/public/nsIMsgDatabase.idl
@@ -238,29 +238,32 @@ interface nsIMsgDBService : nsISupports
    * @param aFolder   The folder to close the cached (open) db for.
    */
   void forceFolderDBClosed(in nsIMsgFolder aFolder);
 
   /// an enumerator to iterate over the open dbs.
   readonly attribute nsIArray openDBs;
 };
 
-[scriptable, uuid(2dbc9e31-5f8c-408c-960e-a91cecde368c)]
+[scriptable, uuid(b64e66f8-4717-423a-be42-482658fb2199)]
 interface nsIMsgDatabase : nsIDBChangeAnnouncer {
   void Close(in boolean aForceCommit);
 
   void Commit(in nsMsgDBCommit commitType);
   // Force closed is evil, and we should see if we can do without it.
   // In 4.x, it was mainly used to remove corrupted databases.
   void ForceClosed();
   void clearCachedHdrs();
   void resetHdrCacheSize(in unsigned long size);
 
   readonly attribute nsIDBFolderInfo  dBFolderInfo;
 
+  /// Size of the database file in bytes.
+  readonly attribute long long databaseSize;
+
   /// Folder this db was opened on.
   readonly attribute nsIMsgFolder folder;
 
   /**
    * This is used when deciding which db's to close to free up memory
    * and other resources in an LRU manner. It doesn't track every operation
    * on every object from the db, but high level things like open, commit,
    * and perhaps some of the list methods. Commit should be a proxy for all
--- a/mailnews/db/msgdb/src/nsMsgDatabase.cpp
+++ b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
@@ -574,16 +574,39 @@ NS_IMETHODIMP nsMsgDatabase::GetLastUseT
 }
 
 NS_IMETHODIMP nsMsgDatabase::SetLastUseTime(PRTime aTime)
 {
   gLastUseTime = m_lastUseTime = aTime;
   return NS_OK;
 }
 
+NS_IMETHODIMP nsMsgDatabase::GetDatabaseSize(int64_t *_retval)
+{
+  NS_ENSURE_ARG_POINTER(_retval);
+
+  nsresult rv;
+  nsCOMPtr<nsIFile> summaryFilePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = summaryFilePath->InitWithNativePath(m_dbName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool exists;
+  rv = summaryFilePath->Exists(&exists);
+  if (NS_SUCCEEDED(rv))
+  {
+    if (exists)
+      rv = summaryFilePath->GetFileSize(_retval);
+    else
+      *_retval = 0;
+  }
+
+  return rv;
+}
+
 NS_IMETHODIMP nsMsgDatabase::ClearCachedHdrs()
 {
   ClearCachedObjects(false);
 #ifdef DEBUG_bienvenu1
   if (mRefCnt > 1)
   {
     NS_ASSERTION(false, "");
     printf("someone's holding onto db - refs = %ld\n", mRefCnt);
--- a/suite/locales/en-US/chrome/mailnews/messenger.properties
+++ b/suite/locales/en-US/chrome/mailnews/messenger.properties
@@ -75,16 +75,17 @@ confirmFolderDeletionForFilter=Deleting 
 alertFilterChanged=Filters associated with this folder will be updated.
 filterDisabled=The folder '%S' could not be found, so filter(s) associated with this folder will be disabled. Verify that the folder exists, and that filters point to a valid destination folder.
 filterFolderDeniedLocked=The messages could not be filtered to folder '%S' because another operation is in progress.
 parsingFolderFailed=Unable to open the folder %S because it is in use by some other operation. Please wait for that operation to finish and then select the folder again.
 deletingMsgsFailed=Unable to delete messages in folder %S because it is in use by some other operation. Please wait for that operation to finish and then try again.
 alertFilterCheckbox=Do not warn me again.
 compactFolderDeniedLock=The folder '%S' cannot be compacted because another operation is in progress. Please try again later.
 compactFolderWriteFailed=The folder '%S' could not be compacted because writing to folder failed. Verify that you have enough disk space, and that you have write privileges to the file system, then try again.
+compactFolderInsufficientSpace=Some folders (e.g. '%S') cannot be compacted because there is not enough free disk space. Please delete some files and try again.
 filterFolderHdrAddFailed=The messages could not be filtered to folder '%S' because adding a message to it failed. Verify that the folder is displaying properly or try to repair it from the folder properties.
 filterFolderWriteFailed=The messages could not be filtered to folder '%S' because writing to folder failed. Verify that you have enough disk space, and that you have write privileges to the file system, then try again.
 copyMsgWriteFailed=The messages could not be moved or copied to folder '%S' because writing to folder failed. To gain disk space, from the File menu, first choose Empty Trash, and then choose Compact Folders, and then try again.
 cantMoveMsgWOBodyOffline=While working offline, you cannot move or copy messages that were not downloaded for offline use. From the Mail window, open the File menu, choose Offline, then uncheck Work Offline, and then try again.
 operationFailedFolderBusy=The operation failed because another operation is using the folder. Please wait for that operation to finish and then try again.
 folderRenameFailed=The folder could not be renamed. Perhaps the folder is being reparsed, or the new name is not a valid folder name.
 # LOCALIZATION NOTE(verboseFolderFormat): %1$S is folder name, %2$S is server name
 verboseFolderFormat=%1$S on %2$S