diff options
Diffstat (limited to 'mailnews/base/src/nsMsgFolderCompactor.cpp')
-rw-r--r-- | mailnews/base/src/nsMsgFolderCompactor.cpp | 1348 |
1 files changed, 1348 insertions, 0 deletions
diff --git a/mailnews/base/src/nsMsgFolderCompactor.cpp b/mailnews/base/src/nsMsgFolderCompactor.cpp new file mode 100644 index 000000000..4b0dc3ad5 --- /dev/null +++ b/mailnews/base/src/nsMsgFolderCompactor.cpp @@ -0,0 +1,1348 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "msgCore.h" // precompiled header... +#include "nsCOMPtr.h" +#include "nsIMsgFolder.h" +#include "nsAutoPtr.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsIMsgHdr.h" +#include "nsIStreamListener.h" +#include "nsIMsgMessageService.h" +#include "nsMsgDBCID.h" +#include "nsMsgUtils.h" +#include "nsISeekableStream.h" +#include "nsIDBFolderInfo.h" +#include "nsIDocShell.h" +#include "nsIPrompt.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgImapMailFolder.h" +#include "nsMailHeaders.h" +#include "nsMsgI18N.h" +#include "prprf.h" +#include "nsMsgLocalFolderHdrs.h" +#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> +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsPrintfCString.h" + + +////////////////////////////////////////////////////////////////////////////// +// nsFolderCompactState +////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsFolderCompactState, nsIMsgFolderCompactor, nsIRequestObserver, nsIStreamListener, nsICopyMessageStreamListener, nsIUrlListener) + +nsFolderCompactState::nsFolderCompactState() +{ + m_fileStream = nullptr; + m_size = 0; + m_curIndex = 0; + m_status = NS_OK; + 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(); + // if for some reason we failed remove the temp folder and database + } +} + +void nsFolderCompactState::CloseOutputStream() +{ + if (m_fileStream) + { + m_fileStream->Close(); + m_fileStream = nullptr; + } + +} + +void nsFolderCompactState::CleanupTempFilesAfterError() +{ + CloseOutputStream(); + if (m_db) + m_db->ForceClosed(); + nsCOMPtr <nsIFile> summaryFile; + GetSummaryFileLocation(m_file, getter_AddRefs(summaryFile)); + m_file->Remove(false); + summaryFile->Remove(false); +} + +nsresult nsFolderCompactState::BuildMessageURI(const char *baseURI, nsMsgKey key, nsCString& uri) +{ + uri.Append(baseURI); + uri.Append('#'); + uri.AppendInt(key); + + return NS_OK; +} + + +nsresult +nsFolderCompactState::InitDB(nsIMsgDatabase *db) +{ + nsCOMPtr<nsIMsgDatabase> mailDBFactory; + nsresult rv = db->ListAllKeys(m_keyArray); + NS_ENSURE_SUCCESS(rv, rv); + m_size = m_keyArray->m_keys.Length(); + + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgDBService->OpenMailDBFromFile(m_file, m_folder, true, false, + getter_AddRefs(m_db)); + + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE || + rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + // if it's out of date then reopen with upgrade. + return msgDBService->OpenMailDBFromFile(m_file, + m_folder, true, true, + getter_AddRefs(m_db)); + return rv; +} + +NS_IMETHODIMP nsFolderCompactState::CompactFolders(nsIArray *aArrayOfFoldersToCompact, + nsIArray *aOfflineFolderArray, + nsIUrlListener *aUrlListener, + nsIMsgWindow *aMsgWindow) +{ + m_window = aMsgWindow; + m_listener = aUrlListener; + if (aArrayOfFoldersToCompact) + m_folderArray = aArrayOfFoldersToCompact; + else if (aOfflineFolderArray) + { + m_folderArray = aOfflineFolderArray; + m_compactingOfflineFolders = true; + aOfflineFolderArray = nullptr; + } + if (!m_folderArray) + return NS_OK; + + m_compactAll = true; + m_compactOfflineAlso = aOfflineFolderArray != nullptr; + if (m_compactOfflineAlso) + m_offlineFolderArray = aOfflineFolderArray; + + m_folderIndex = 0; + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgFolder> firstFolder = do_QueryElementAt(m_folderArray, + m_folderIndex, &rv); + + if (NS_SUCCEEDED(rv) && firstFolder) + Compact(firstFolder, m_compactingOfflineFolders, aUrlListener, + aMsgWindow); //start with first folder from here. + + return rv; +} + +NS_IMETHODIMP +nsFolderCompactState::Compact(nsIMsgFolder *folder, bool aOfflineStore, + nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) +{ + NS_ENSURE_ARG_POINTER(folder); + m_listener = aListener; + if (!m_compactingOfflineFolders && !aOfflineStore) + { + nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder); + if (imapFolder) + return imapFolder->Expunge(this, aMsgWindow); + } + + m_window = aMsgWindow; + nsresult rv; + nsCOMPtr<nsIMsgDatabase> db; + 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) + { + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + { + m_folder = folder; //will be used to compact + m_parsingFolder = true; + rv = localFolder->ParseFolder(m_window, this); + } + return rv; + } + else + { + bool valid; + rv = db->GetSummaryValid(&valid); + if (!valid) //we are probably parsing the folder because we selected it. + { + folder->NotifyCompactCompleted(); + if (m_compactAll) + return CompactNextFolder(); + else + return NS_OK; + } + } + } + else + { + rv = folder->GetMsgDatabase(getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = folder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + do { + bool exists = false; + rv = path->Exists(&exists); + if (!exists) { + // No need to compact if the local file does not exist. + // Can happen e.g. on IMAP when the folder is not marked for offline use. + break; + } + + int64_t expunged = 0; + folder->GetExpungedBytes(&expunged); + if (expunged == 0) { + // No need to compact if nothing would be expunged. + break; + } + + int64_t diskSize; + rv = folder->GetSizeOnDisk(&diskSize); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t diskFree; + rv = path->GetDiskSpaceAvailable(&diskFree); + if (NS_FAILED(rv)) { + // If GetDiskSpaceAvailable() failed, better bail out fast. + if (rv != NS_ERROR_NOT_IMPLEMENTED) + return rv; + // Some platforms do not have GetDiskSpaceAvailable implemented. + // In that case skip the preventive free space analysis and let it + // fail in compact later if space actually wasn't available. + } else { + // 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); + + int32_t totalMsgs; + rv = folder->GetTotalMessages(false, &totalMsgs); + NS_ENSURE_SUCCESS(rv, rv); + int64_t expectedDBSize = std::min<int64_t>(dbSize, ((int64_t)totalMsgs) * 1024); + if (diskFree < diskSize - expunged + expectedDBSize) + { + if (!m_alreadyWarnedDiskSpace) + { + folder->ThrowAlertMsg("compactFolderInsufficientSpace", m_window); + m_alreadyWarnedDiskSpace = true; + } + break; + } + } + + 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 = true; + m_folder->GetLocked(&isLocked); + if (isLocked) + { + CleanupTempFilesAfterError(); + m_folder->ThrowAlertMsg("compactFolderDeniedLock", m_window); + break; + } + + // If we got here start the real compacting. + nsCOMPtr<nsISupports> supports = do_QueryInterface(static_cast<nsIMsgFolderCompactor*>(this)); + m_folder->AcquireSemaphore(supports); + m_totalExpungedBytes += expunged; + return StartCompacting(); + + } while(false); // block for easy skipping the compaction using 'break' + + folder->NotifyCompactCompleted(); + if (m_compactAll) + return CompactNextFolder(); + else + return NS_OK; +} + +nsresult nsFolderCompactState::ShowStatusMsg(const nsString& aMsg) +{ + nsCOMPtr <nsIMsgStatusFeedback> statusFeedback; + if (m_window) + { + m_window->GetStatusFeedback(getter_AddRefs(statusFeedback)); + if (statusFeedback && !aMsg.IsEmpty()) + return statusFeedback->SetStatusString(aMsg); + } + return NS_OK; +} + +nsresult +nsFolderCompactState::Init(nsIMsgFolder *folder, const char *baseMsgUri, nsIMsgDatabase *db, + nsIFile *path, nsIMsgWindow *aMsgWindow) +{ + nsresult rv; + + m_folder = folder; + m_baseMessageUri = baseMsgUri; + m_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_file->InitWithFile(path); + // need to make sure the temp file goes in the same real directory + // as the original file, so resolve sym links. + m_file->SetFollowLinks(true); + + m_file->SetNativeLeafName(NS_LITERAL_CSTRING("nstmp")); + rv = m_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); //make sure we are not crunching existing nstmp file + NS_ENSURE_SUCCESS(rv, rv); + + m_window = aMsgWindow; + m_keyArray = new nsMsgKeyArray; + m_size = 0; + m_totalMsgSize = 0; + rv = InitDB(db); + if (NS_FAILED(rv)) + { + CleanupTempFilesAfterError(); + return rv; + } + + m_curIndex = 0; + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_fileStream), m_file, -1, 00600); + if (NS_FAILED(rv)) + m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window); + else + rv = GetMessageServiceFromURI(nsDependentCString(baseMsgUri), + getter_AddRefs(m_messageService)); + if (NS_FAILED(rv)) + { + m_status = rv; + } + return rv; +} + +void nsFolderCompactState::ShowCompactingStatusMsg() +{ + nsString statusString; + nsresult rv = m_folder->GetStringWithFolderNameFromBundle("compactingFolder", statusString); + if (!statusString.IsEmpty() && NS_SUCCEEDED(rv)) + ShowStatusMsg(statusString); +} + +NS_IMETHODIMP nsFolderCompactState::OnStartRunningUrl(nsIURI *url) +{ + return NS_OK; +} + +NS_IMETHODIMP nsFolderCompactState::OnStopRunningUrl(nsIURI *url, nsresult status) +{ + if (m_parsingFolder) + { + m_parsingFolder = false; + if (NS_SUCCEEDED(status)) + status = Compact(m_folder, m_compactingOfflineFolders, m_listener, m_window); + else if (m_compactAll) + CompactNextFolder(); + } + else if (m_compactAll) // this should be the imap case only + { + nsCOMPtr <nsIMsgFolder> prevFolder = do_QueryElementAt(m_folderArray, + m_folderIndex); + if (prevFolder) + prevFolder->SetMsgDatabase(nullptr); + CompactNextFolder(); + } + else if (m_listener) + { + CompactCompleted(status); + } + return NS_OK; +} + +nsresult nsFolderCompactState::StartCompacting() +{ + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsCOMPtr<nsIMsgIncomingServer> server; + + nsresult rv = m_folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + // Notify that compaction is beginning. We do this even if there are no + // messages to be copied because the summary database still gets blown away + // which is still pretty interesting. (And we like consistency.) + nsCOMPtr<nsIMsgFolderNotificationService> + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyItemEvent(m_folder, + NS_LITERAL_CSTRING("FolderCompactStart"), + nullptr); + + // TODO: test whether sorting the messages (m_keyArray) by messageOffset + // would improve performance on large files (less seeks). + // The m_keyArray is in the order as stored in DB and on IMAP or News + // the messages stored on the mbox file are not necessarily in the same order. + if (m_size > 0) + { + nsCOMPtr<nsIURI> notUsed; + ShowCompactingStatusMsg(); + AddRef(); + rv = m_messageService->CopyMessages(m_size, m_keyArray->m_keys.Elements(), + m_folder, this, + false, nullptr, m_window, + getter_AddRefs(notUsed)); + } + else + { // no messages to copy with + FinishCompact(); +// Release(); // we don't "own" ourselves yet. + } + return rv; +} + +nsresult +nsFolderCompactState::FinishCompact() +{ + NS_ENSURE_TRUE(m_folder, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(m_file, NS_ERROR_NOT_INITIALIZED); + + // All okay time to finish up the compact process + nsCOMPtr<nsIFile> path; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + + // get leaf name and database name of the folder + nsresult rv = m_folder->GetFilePath(getter_AddRefs(path)); + nsCOMPtr <nsIFile> folderPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = folderPath->InitWithFile(path); + NS_ENSURE_SUCCESS(rv, rv); + // need to make sure we put the .msf file in the same directory + // as the original mailbox, so resolve symlinks. + folderPath->SetFollowLinks(true); + + nsCOMPtr <nsIFile> oldSummaryFile; + rv = GetSummaryFileLocation(folderPath, getter_AddRefs(oldSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString dbName; + oldSummaryFile->GetNativeLeafName(dbName); + nsAutoCString folderName; + path->GetNativeLeafName(folderName); + + // close down the temp file stream; preparing for deleting the old folder + // and its database; then rename the temp folder and database + if (m_fileStream) + { + m_fileStream->Flush(); + m_fileStream->Close(); + m_fileStream = nullptr; + } + + // make sure the new database is valid. + // Close it so we can rename the .msf file. + if (m_db) + { + m_db->ForceClosed(); + m_db = nullptr; + } + + nsCOMPtr <nsIFile> newSummaryFile; + rv = GetSummaryFileLocation(m_file, getter_AddRefs(newSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIDBFolderInfo> transferInfo; + m_folder->GetDBTransferInfo(getter_AddRefs(transferInfo)); + + // close down database of the original folder + m_folder->ForceDBClosed(); + + nsCOMPtr<nsIFile> cloneFile; + int64_t fileSize = 0; + rv = m_file->Clone(getter_AddRefs(cloneFile)); + if (NS_SUCCEEDED(rv)) + rv = cloneFile->GetFileSize(&fileSize); + bool tempFileRightSize = ((uint64_t)fileSize == m_totalMsgSize); + NS_WARNING_ASSERTION(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. + nsCOMPtr<nsIFile> tempSummaryFile; + rv = oldSummaryFile->Clone(getter_AddRefs(tempSummaryFile)); + if (NS_SUCCEEDED(rv)) + rv = tempSummaryFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + + nsAutoCString tempSummaryFileName; + if (NS_SUCCEEDED(rv)) + rv = tempSummaryFile->GetNativeLeafName(tempSummaryFileName); + + if (NS_SUCCEEDED(rv)) + rv = oldSummaryFile->MoveToNative((nsIFile*) nullptr, tempSummaryFileName); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "error moving compacted folder's db out of the way"); + if (NS_SUCCEEDED(rv)) + { + // Now we've successfully moved the summary file out the way, try moving + // the newly compacted message file over the old one. + rv = m_file->MoveToNative((nsIFile *) nullptr, folderName); + folderRenameSucceeded = NS_SUCCEEDED(rv); + NS_WARNING_ASSERTION(folderRenameSucceeded, "error renaming compacted folder"); + if (folderRenameSucceeded) + { + // That worked, so land the new summary file in the right place. + nsCOMPtr<nsIFile> renamedCompactedSummaryFile; + newSummaryFile->Clone(getter_AddRefs(renamedCompactedSummaryFile)); + if (renamedCompactedSummaryFile) + { + rv = renamedCompactedSummaryFile->MoveToNative((nsIFile *) nullptr, dbName); + msfRenameSucceeded = NS_SUCCEEDED(rv); + } + NS_WARNING_ASSERTION(msfRenameSucceeded, "error renaming compacted folder's db"); + } + + if (!msfRenameSucceeded) + { + // Do our best to put the summary file back to where it was + rv = tempSummaryFile->MoveToNative((nsIFile*) nullptr, dbName); + if (NS_SUCCEEDED(rv)) + tempSummaryFile = nullptr; // flagging that a renamed db no longer exists + else + NS_WARNING("error restoring uncompacted folder's db"); + } + } + // We don't want any temporarily renamed summary file to lie around + if (tempSummaryFile) + tempSummaryFile->Remove(false); + } + + NS_WARNING_ASSERTION(msfRenameSucceeded, "compact failed"); + nsresult rvReleaseFolderLock = ReleaseFolderLock(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvReleaseFolderLock),"folder lock not released successfully"); + rv = NS_FAILED(rv) ? rv : rvReleaseFolderLock; + + // Cleanup of nstmp-named compacted files if failure + if (!folderRenameSucceeded) + { + // remove the abandoned compacted version with the wrong name + m_file->Remove(false); + } + if (!msfRenameSucceeded) + { + // remove the abandoned compacted summary file + newSummaryFile->Remove(false); + } + + if (msfRenameSucceeded) + { + // Transfer local db information from transferInfo + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgDBService->OpenFolderDB(m_folder, true, getter_AddRefs(m_db)); + NS_ENSURE_TRUE(m_db, NS_FAILED(rv) ? rv : NS_ERROR_FAILURE); + // These errors are expected. + rv = (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) ? NS_OK : rv; + m_db->SetSummaryValid(true); + m_folder->SetDBTransferInfo(transferInfo); + + // since we're transferring info from the old db, we need to reset the expunged bytes + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if(dbFolderInfo) + dbFolderInfo->SetExpungedBytes(0); + } + if (m_db) + m_db->Close(true); + m_db = nullptr; + + // Notify that compaction of the folder is completed. + nsCOMPtr<nsIMsgFolderNotificationService> + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyItemEvent(m_folder, + NS_LITERAL_CSTRING("FolderCompactFinish"), + nullptr); + m_folder->NotifyCompactCompleted(); + + if (m_compactAll) + rv = CompactNextFolder(); + else + CompactCompleted(rv); + + return rv; +} + +nsresult +GetBaseStringBundle(nsIStringBundle **aBundle) +{ + NS_ENSURE_ARG_POINTER(aBundle); + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + return bundleService->CreateBundle( + "chrome://messenger/locale/messenger.properties", aBundle); +} + +void nsFolderCompactState::CompactCompleted(nsresult exitCode) +{ + NS_WARNING_ASSERTION(NS_SUCCEEDED(exitCode), + "nsFolderCompactState::CompactCompleted failed"); + if (m_listener) + m_listener->OnStopRunningUrl(nullptr, exitCode); + ShowDoneStatus(); +} + +nsresult +nsFolderCompactState::ReleaseFolderLock() +{ + nsresult result = NS_OK; + if (!m_folder) return result; + bool haveSemaphore; + nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMsgFolderCompactor*>(this)); + result = m_folder->TestSemaphore(supports, &haveSemaphore); + if(NS_SUCCEEDED(result) && haveSemaphore) + result = m_folder->ReleaseSemaphore(supports); + return result; +} + +void nsFolderCompactState::ShowDoneStatus() +{ + if (m_folder) + { + nsString statusString; + nsCOMPtr <nsIStringBundle> bundle; + nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS_VOID(rv); + nsAutoString expungedAmount; + FormatFileSize(m_totalExpungedBytes, true, expungedAmount); + const char16_t* params[] = { expungedAmount.get() }; + rv = bundle->FormatStringFromName(u"compactingDone", + params, 1, getter_Copies(statusString)); + + if (!statusString.IsEmpty() && NS_SUCCEEDED(rv)) + ShowStatusMsg(statusString); + } +} + +nsresult +nsFolderCompactState::CompactNextFolder() +{ + m_folderIndex++; + uint32_t cnt = 0; + nsresult rv = m_folderArray->GetLength(&cnt); + NS_ENSURE_SUCCESS(rv, rv); + // m_folderIndex might be > cnt if we compact offline stores, + // and get back here from OnStopRunningUrl. + if (m_folderIndex >= cnt) + { + if (!m_compactOfflineAlso || m_compactingOfflineFolders) + { + CompactCompleted(NS_OK); + return rv; + } + m_compactingOfflineFolders = true; + nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(m_folderArray, + m_folderIndex-1, &rv); + if (NS_SUCCEEDED(rv) && folder) + return folder->CompactAllOfflineStores(this, m_window, m_offlineFolderArray); + else + NS_WARNING("couldn't get folder to compact offline stores"); + + } + nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(m_folderArray, + m_folderIndex, &rv); + + if (NS_SUCCEEDED(rv) && folder) + rv = Compact(folder, m_compactingOfflineFolders, m_listener, m_window); + else + CompactCompleted(rv); + return rv; +} + +nsresult +nsFolderCompactState::GetMessage(nsIMsgDBHdr **message) +{ + return GetMsgDBHdrFromURI(m_messageUri.get(), message); +} + + +NS_IMETHODIMP +nsFolderCompactState::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + return StartMessage(); +} + +NS_IMETHODIMP +nsFolderCompactState::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult status) +{ + nsCOMPtr<nsIMsgDBHdr> msgHdr; + if (NS_FAILED(status)) + { + m_status = status; // set the m_status to status so the destructor can remove the + // temp folder and database + CleanupTempFilesAfterError(); + m_folder->NotifyCompactCompleted(); + ReleaseFolderLock(); + m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window); + } + else + { + EndCopy(nullptr, status); + if (m_curIndex >= m_size) + { + msgHdr = nullptr; + // no more to copy finish it up + FinishCompact(); + } + else + { + // in case we're not getting an error, we still need to pretend we did get an error, + // because the compact did not successfully complete. + m_folder->NotifyCompactCompleted(); + CleanupTempFilesAfterError(); + ReleaseFolderLock(); + } + } + Release(); // kill self + return status; +} + +NS_IMETHODIMP +nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, uint32_t count) +{ + if (!m_fileStream || !inStr) + return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + uint32_t msgFlags; + bool checkForKeyword = m_startOfMsg; + bool addKeywordHdr = false; + uint32_t needToGrowKeywords = 0; + uint32_t statusOffset; + nsCString msgHdrKeywords; + + if (m_startOfMsg) + { + m_statusOffset = 0; + m_addedHeaderSize = 0; + m_messageUri.Truncate(); // clear the previous message uri + if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex], + m_messageUri))) + { + rv = GetMessage(getter_AddRefs(m_curSrcHdr)); + NS_ENSURE_SUCCESS(rv, rv); + if (m_curSrcHdr) + { + (void) m_curSrcHdr->GetFlags(&msgFlags); + (void) m_curSrcHdr->GetStatusOffset(&statusOffset); + + if (statusOffset == 0) + m_needStatusLine = true; + // x-mozilla-status lines should be at the start of the headers, and the code + // below assumes everything will fit in m_dataBuffer - if there's not + // room, skip the keyword stuff. + if (statusOffset > sizeof(m_dataBuffer) - 1024) + { + checkForKeyword = false; + NS_ASSERTION(false, "status offset past end of read buffer size"); + + } + } + } + m_startOfMsg = false; + } + uint32_t maxReadCount, readCount, writeCount; + uint32_t bytesWritten; + + while (NS_SUCCEEDED(rv) && (int32_t) count > 0) + { + maxReadCount = count > sizeof(m_dataBuffer) - 1 ? sizeof(m_dataBuffer) - 1 : count; + writeCount = 0; + rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount); + + // if status offset is past the number of bytes we read, it's probably bogus, + // and we shouldn't do any of the keyword stuff. + if (statusOffset + X_MOZILLA_STATUS_LEN > readCount) + checkForKeyword = false; + + if (NS_SUCCEEDED(rv)) + { + if (checkForKeyword) + { + // make sure that status offset really points to x-mozilla-status line + if (!strncmp(m_dataBuffer + statusOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN)) + { + const char *keywordHdr = PL_strnrstr(m_dataBuffer, HEADER_X_MOZILLA_KEYWORDS, readCount); + if (keywordHdr) + m_curSrcHdr->GetUint32Property("growKeywords", &needToGrowKeywords); + else + addKeywordHdr = true; + m_curSrcHdr->GetStringProperty("keywords", getter_Copies(msgHdrKeywords)); + } + checkForKeyword = false; + } + uint32_t blockOffset = 0; + if (m_needStatusLine) + { + m_needStatusLine = false; + // we need to parse out the "From " header, write it out, then + // write out the x-mozilla-status headers, and set the + // status offset of the dest hdr for later use + // in OnEndCopy). + if (!strncmp(m_dataBuffer, "From ", 5)) + { + blockOffset = 5; + // skip from line + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); + char statusLine[50]; + m_fileStream->Write(m_dataBuffer, blockOffset, &writeCount); + m_statusOffset = blockOffset; + PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF); + m_fileStream->Write(statusLine, strlen(statusLine), &m_addedHeaderSize); + PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000); + m_fileStream->Write(statusLine, strlen(statusLine), &bytesWritten); + m_addedHeaderSize += bytesWritten; + } + else + { + NS_ASSERTION(false, "not an envelope"); + // try to mark the db as invalid so it will be reparsed. + nsCOMPtr <nsIMsgDatabase> srcDB; + m_folder->GetMsgDatabase(getter_AddRefs(srcDB)); + if (srcDB) + { + srcDB->SetSummaryValid(false); + srcDB->ForceClosed(); + } + } + } +#define EXTRA_KEYWORD_HDR " " MSG_LINEBREAK + + // if status offset isn't in the first block, this code won't work. There's no good reason + // for the status offset not to be at the beginning of the message anyway. + if (addKeywordHdr) + { + // if blockOffset is set, we added x-mozilla-status headers so + // file pointer is already past them. + if (!blockOffset) + { + blockOffset = statusOffset; + // skip x-mozilla-status and status2 lines. + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); + // need to rewrite the headers up to and including the x-mozilla-status2 header + m_fileStream->Write(m_dataBuffer, blockOffset, &writeCount); + } + // we should write out the existing keywords from the msg hdr, if any. + if (msgHdrKeywords.IsEmpty()) + { // no keywords, so write blank header + m_fileStream->Write(X_MOZILLA_KEYWORDS, sizeof(X_MOZILLA_KEYWORDS) - 1, &bytesWritten); + m_addedHeaderSize += bytesWritten; + } + else + { + if (msgHdrKeywords.Length() < sizeof(X_MOZILLA_KEYWORDS) - sizeof(HEADER_X_MOZILLA_KEYWORDS) + 10 /* allow some slop */) + { // keywords fit in normal blank header, so replace blanks in keyword hdr with keywords + nsAutoCString keywordsHdr(X_MOZILLA_KEYWORDS); + keywordsHdr.Replace(sizeof(HEADER_X_MOZILLA_KEYWORDS) + 1, msgHdrKeywords.Length(), msgHdrKeywords); + m_fileStream->Write(keywordsHdr.get(), keywordsHdr.Length(), &bytesWritten); + m_addedHeaderSize += bytesWritten; + } + else + { // keywords don't fit, so write out keywords on one line and an extra blank line + nsCString newKeywordHeader(HEADER_X_MOZILLA_KEYWORDS ": "); + newKeywordHeader.Append(msgHdrKeywords); + newKeywordHeader.Append(MSG_LINEBREAK EXTRA_KEYWORD_HDR); + m_fileStream->Write(newKeywordHeader.get(), newKeywordHeader.Length(), &bytesWritten); + m_addedHeaderSize += bytesWritten; + } + } + addKeywordHdr = false; + } + else if (needToGrowKeywords) + { + blockOffset = statusOffset; + if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN)) + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status hdr + if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN)) + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status2 hdr + uint32_t preKeywordBlockOffset = blockOffset; + if (!strncmp(m_dataBuffer + blockOffset, HEADER_X_MOZILLA_KEYWORDS, sizeof(HEADER_X_MOZILLA_KEYWORDS) - 1)) + { + do + { + // skip x-mozilla-keywords hdr and any existing continuation headers + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); + } + while (m_dataBuffer[blockOffset] == ' '); + } + int32_t oldKeywordSize = blockOffset - preKeywordBlockOffset; + + // rewrite the headers up to and including the x-mozilla-status2 header + m_fileStream->Write(m_dataBuffer, preKeywordBlockOffset, &writeCount); + // let's just rewrite all the keywords on several lines and add a blank line, + // instead of worrying about which are missing. + bool done = false; + nsAutoCString keywordHdr(HEADER_X_MOZILLA_KEYWORDS ": "); + int32_t nextBlankOffset = 0; + int32_t curHdrLineStart = 0; + int32_t newKeywordSize = 0; + while (!done) + { + nextBlankOffset = msgHdrKeywords.FindChar(' ', nextBlankOffset); + if (nextBlankOffset == kNotFound) + { + nextBlankOffset = msgHdrKeywords.Length(); + done = true; + } + if (nextBlankOffset - curHdrLineStart > 90 || done) + { + keywordHdr.Append(nsDependentCSubstring(msgHdrKeywords, curHdrLineStart, msgHdrKeywords.Length() - curHdrLineStart)); + keywordHdr.Append(MSG_LINEBREAK); + m_fileStream->Write(keywordHdr.get(), keywordHdr.Length(), &bytesWritten); + newKeywordSize += bytesWritten; + curHdrLineStart = nextBlankOffset; + keywordHdr.Assign(' '); + } + nextBlankOffset++; + } + m_fileStream->Write(EXTRA_KEYWORD_HDR, sizeof(EXTRA_KEYWORD_HDR) - 1, &bytesWritten); + newKeywordSize += bytesWritten; + m_addedHeaderSize += newKeywordSize - oldKeywordSize; + m_curSrcHdr->SetUint32Property("growKeywords", 0); + needToGrowKeywords = false; + writeCount += blockOffset - preKeywordBlockOffset; // fudge writeCount + + } + if (readCount <= blockOffset) + { + NS_ASSERTION(false, "bad block offset"); + // not sure what to do to handle this. + + } + m_fileStream->Write(m_dataBuffer + blockOffset, readCount - blockOffset, &bytesWritten); + writeCount += bytesWritten; + count -= readCount; + if (writeCount != readCount) + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + } + return rv; +} + +nsOfflineStoreCompactState::nsOfflineStoreCompactState() +{ +} + +nsOfflineStoreCompactState::~nsOfflineStoreCompactState() +{ +} + + +nsresult +nsOfflineStoreCompactState::InitDB(nsIMsgDatabase *db) +{ + // Start with the list of messages we have offline as the possible + // message to keep when compacting the offline store. + db->ListAllOfflineMsgs(m_keyArray); + m_size = m_keyArray->m_keys.Length(); + m_db = db; + return NS_OK; +} + +/** + * This will copy one message to the offline store, but if it fails to + * copy the next message, it will keep trying messages until it finds one + * it can copy, or it runs out of messages. + */ +nsresult nsOfflineStoreCompactState::CopyNextMessage(bool &done) +{ + while (m_curIndex < m_size) + { + // Filter out msgs that have the "pendingRemoval" attribute set. + nsCOMPtr<nsIMsgDBHdr> hdr; + nsString pendingRemoval; + nsresult rv = m_db->GetMsgHdrForKey(m_keyArray->m_keys[m_curIndex], getter_AddRefs(hdr)); + NS_ENSURE_SUCCESS(rv, rv); + hdr->GetProperty("pendingRemoval", pendingRemoval); + if (!pendingRemoval.IsEmpty()) + { + m_curIndex++; + // Turn off offline flag for message, since after the compact is completed; + // we won't have the message in the offline store. + uint32_t resultFlags; + hdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags); + // We need to clear this in case the user changes the offline retention + // settings. + hdr->SetStringProperty("pendingRemoval", ""); + continue; + } + m_messageUri.Truncate(); // clear the previous message uri + rv = BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex], + m_messageUri); + NS_ENSURE_SUCCESS(rv, rv); + m_startOfMsg = true; + nsCOMPtr<nsISupports> thisSupports; + QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(thisSupports)); + nsCOMPtr<nsIURI> dummyNull; + rv = m_messageService->StreamMessage(m_messageUri.get(), thisSupports, m_window, nullptr, + false, EmptyCString(), true, getter_AddRefs(dummyNull)); + // if copy fails, we clear the offline flag on the source message. + if (NS_FAILED(rv)) + { + nsCOMPtr<nsIMsgDBHdr> hdr; + GetMessage(getter_AddRefs(hdr)); + if (hdr) + { + uint32_t resultFlags; + hdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags); + } + m_curIndex++; + continue; + } + else + break; + } + done = m_curIndex >= m_size; + // In theory, we might be able to stream the next message, so + // return NS_OK, not rv. + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineStoreCompactState::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult status) +{ + nsresult rv = status; + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr <nsIMsgStatusFeedback> statusFeedback; + bool done = false; + + // The NS_MSG_ERROR_MSG_NOT_OFFLINE error should allow us to continue, so we + // check for it specifically and don't terminate the compaction. + if (NS_FAILED(rv) && rv != NS_MSG_ERROR_MSG_NOT_OFFLINE) + goto done; + uri = do_QueryInterface(ctxt, &rv); + if (NS_FAILED(rv)) goto done; + rv = GetMessage(getter_AddRefs(msgHdr)); + if (NS_FAILED(rv)) goto done; + + // This is however an unexpected condition, so let's print a warning. + if (rv == NS_MSG_ERROR_MSG_NOT_OFFLINE) { + nsAutoCString spec; + uri->GetSpec(spec); + nsPrintfCString msg("Message expectedly not available offline: %s", spec.get()); + NS_WARNING(msg.get()); + } + + if (msgHdr) + { + if (NS_SUCCEEDED(status)) + { + msgHdr->SetMessageOffset(m_startOfNewMsg); + char storeToken[100]; + PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_startOfNewMsg); + msgHdr->SetStringProperty("storeToken", storeToken); + msgHdr->SetOfflineMessageSize(m_offlineMsgSize); + } + else + { + uint32_t resultFlags; + msgHdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags); + } + } + + if (m_window) + { + m_window->GetStatusFeedback(getter_AddRefs(statusFeedback)); + if (statusFeedback) + statusFeedback->ShowProgress(100 * m_curIndex / m_size); + } + // advance to next message + m_curIndex++; + rv = CopyNextMessage(done); + if (done) + { + m_db->Commit(nsMsgDBCommitType::kCompressCommit); + msgHdr = nullptr; + // no more to copy finish it up + ReleaseFolderLock(); + FinishCompact(); + Release(); // kill self + } + +done: + if (NS_FAILED(rv)) { + m_status = rv; // set the status to rv so the destructor can remove the + // temp folder and database + ReleaseFolderLock(); + Release(); // kill self + return rv; + } + return rv; +} + + +nsresult +nsOfflineStoreCompactState::FinishCompact() +{ + // All okay time to finish up the compact process + nsCOMPtr<nsIFile> path; + uint32_t flags; + + // get leaf name and database name of the folder + m_folder->GetFlags(&flags); + nsresult rv = m_folder->GetFilePath(getter_AddRefs(path)); + + nsCString leafName; + path->GetNativeLeafName(leafName); + + if (m_fileStream) + { + // close down the temp file stream; preparing for deleting the old folder + // and its database; then rename the temp folder and database + m_fileStream->Flush(); + m_fileStream->Close(); + m_fileStream = nullptr; + } + + // make sure the new database is valid + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if (dbFolderInfo) + dbFolderInfo->SetExpungedBytes(0); + // this forces the m_folder to update mExpungedBytes from the db folder info. + int64_t expungedBytes; + m_folder->GetExpungedBytes(&expungedBytes); + m_folder->UpdateSummaryTotals(true); + m_db->SetSummaryValid(true); + + // remove the old folder + path->Remove(false); + + // rename the copied folder to be the original folder + m_file->MoveToNative((nsIFile *) nullptr, leafName); + + ShowStatusMsg(EmptyString()); + m_folder->NotifyCompactCompleted(); + if (m_compactAll) + rv = CompactNextFolder(); + return rv; +} + + +NS_IMETHODIMP +nsFolderCompactState::Init(nsIMsgFolder *srcFolder, nsICopyMessageListener *destination, nsISupports *listenerData) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsFolderCompactState::StartMessage() +{ + nsresult rv = NS_ERROR_FAILURE; + NS_ASSERTION(m_fileStream, "Fatal, null m_fileStream...\n"); + if (m_fileStream) + { + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(m_fileStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // this will force an internal flush, but not a sync. Tell should really do an internal flush, + // but it doesn't, and I'm afraid to change that nsIFileStream.cpp code anymore. + seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0); + // record the new message key for the message + int64_t curStreamPos; + seekableStream->Tell(&curStreamPos); + m_startOfNewMsg = curStreamPos; + rv = NS_OK; + } + return rv; +} + +NS_IMETHODIMP +nsFolderCompactState::EndMessage(nsMsgKey key) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsFolderCompactState::EndCopy(nsISupports *url, nsresult aStatus) +{ + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr<nsIMsgDBHdr> newMsgHdr; + + if (m_curIndex >= m_size) + { + NS_ASSERTION(false, "m_curIndex out of bounds"); + return NS_OK; + } + + /** + * Done with the current message; copying the existing message header + * to the new database. + */ + if (m_curSrcHdr) + { + nsMsgKey key; + m_curSrcHdr->GetMessageKey(&key); + m_db->CopyHdrFromExistingHdr(key, m_curSrcHdr, true, + getter_AddRefs(newMsgHdr)); + } + m_curSrcHdr = nullptr; + if (newMsgHdr) + { + if (m_statusOffset != 0) + newMsgHdr->SetStatusOffset(m_statusOffset); + + char storeToken[100]; + PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_startOfNewMsg); + newMsgHdr->SetStringProperty("storeToken", storeToken); + newMsgHdr->SetMessageOffset(m_startOfNewMsg); + + uint32_t msgSize; + (void) newMsgHdr->GetMessageSize(&msgSize); + if (m_addedHeaderSize) + { + msgSize += m_addedHeaderSize; + newMsgHdr->SetMessageSize(msgSize); + } + m_totalMsgSize += msgSize; + } + +// m_db->Commit(nsMsgDBCommitType::kLargeCommit); // no sense commiting until the end + // advance to next message + m_curIndex ++; + m_startOfMsg = true; + nsCOMPtr <nsIMsgStatusFeedback> statusFeedback; + if (m_window) + { + m_window->GetStatusFeedback(getter_AddRefs(statusFeedback)); + if (statusFeedback) + statusFeedback->ShowProgress(100 * m_curIndex / m_size); + } + return NS_OK; +} + +nsresult nsOfflineStoreCompactState::StartCompacting() +{ + nsresult rv = NS_OK; + if (m_size > 0 && m_curIndex == 0) + { + AddRef(); // we own ourselves, until we're done, anyway. + ShowCompactingStatusMsg(); + bool done = false; + rv = CopyNextMessage(done); + if (!done) + return rv; + } + ReleaseFolderLock(); + FinishCompact(); + return rv; +} + +NS_IMETHODIMP +nsOfflineStoreCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, uint32_t count) +{ + if (!m_fileStream || !inStr) + return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + + if (m_startOfMsg) + { + m_statusOffset = 0; + m_offlineMsgSize = 0; + m_messageUri.Truncate(); // clear the previous message uri + if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex], + m_messageUri))) + { + rv = GetMessage(getter_AddRefs(m_curSrcHdr)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + uint32_t maxReadCount, readCount, writeCount; + uint32_t bytesWritten; + + while (NS_SUCCEEDED(rv) && (int32_t) count > 0) + { + maxReadCount = count > sizeof(m_dataBuffer) - 1 ? sizeof(m_dataBuffer) - 1 : count; + writeCount = 0; + rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount); + + if (NS_SUCCEEDED(rv)) + { + if (m_startOfMsg) + { + m_startOfMsg = false; + // check if there's an envelope header; if not, write one. + if (strncmp(m_dataBuffer, "From ", 5)) + { + m_fileStream->Write("From " CRLF, 7, &bytesWritten); + m_offlineMsgSize += bytesWritten; + } + } + m_fileStream->Write(m_dataBuffer, readCount, &bytesWritten); + m_offlineMsgSize += bytesWritten; + writeCount += bytesWritten; + count -= readCount; + if (writeCount != readCount) + { + m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window); + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + } + } + return rv; +} + |