From 302bf1b523012e11b60425d6eee1221ebc2724eb Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Sun, 3 Nov 2019 00:17:46 -0400 Subject: Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1 --- mailnews/local/src/nsLocalMailFolder.cpp | 3852 ++++++++++++++++++++++++++++++ 1 file changed, 3852 insertions(+) create mode 100644 mailnews/local/src/nsLocalMailFolder.cpp (limited to 'mailnews/local/src/nsLocalMailFolder.cpp') diff --git a/mailnews/local/src/nsLocalMailFolder.cpp b/mailnews/local/src/nsLocalMailFolder.cpp new file mode 100644 index 000000000..14135fe46 --- /dev/null +++ b/mailnews/local/src/nsLocalMailFolder.cpp @@ -0,0 +1,3852 @@ +/* -*- 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 "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "prlog.h" + +#include "msgCore.h" // precompiled header... +#include "nsArrayEnumerator.h" +#include "nsLocalMailFolder.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgMessageFlags.h" +#include "prprf.h" +#include "prmem.h" +#include "nsIArray.h" +#include "nsIServiceManager.h" +#include "nsIMailboxService.h" +#include "nsParseMailbox.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgWindow.h" +#include "nsCOMPtr.h" +#include "nsIRDFService.h" +#include "nsMsgDBCID.h" +#include "nsMsgUtils.h" +#include "nsLocalUtils.h" +#include "nsIPop3IncomingServer.h" +#include "nsILocalMailIncomingServer.h" +#include "nsIMsgIncomingServer.h" +#include "nsMsgBaseCID.h" +#include "nsMsgLocalCID.h" +#include "nsStringGlue.h" +#include "nsIMsgFolderCacheElement.h" +#include "nsUnicharUtils.h" +#include "nsMsgUtils.h" +#include "nsICopyMsgStreamListener.h" +#include "nsIMsgCopyService.h" +#include "nsMsgTxn.h" +#include "nsIMessenger.h" +#include "nsMsgBaseCID.h" +#include "nsNativeCharsetUtils.h" +#include "nsIDocShell.h" +#include "nsIPrompt.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPop3URL.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgFolderCompactor.h" +#include "nsNetCID.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsISpamSettings.h" +#include "nsINoIncomingServer.h" +#include "nsNativeCharsetUtils.h" +#include "nsMailHeaders.h" +#include "nsCOMArray.h" +#include "nsILineInputStream.h" +#include "nsIFileStreams.h" +#include "nsAutoPtr.h" +#include "nsIRssIncomingServer.h" +#include "nsNetUtil.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsReadLine.h" +#include "nsArrayUtils.h" +#include "nsIMsgTraitService.h" +#include "nsIStringEnumerator.h" +#include "mozilla/Services.h" + +////////////////////////////////////////////////////////////////////////////// +// nsLocal +///////////////////////////////////////////////////////////////////////////// + +nsLocalMailCopyState::nsLocalMailCopyState() : + m_flags(0), + m_lastProgressTime(PR_IntervalToMilliseconds(PR_IntervalNow())), + m_curDstKey(nsMsgKey_None), + m_curCopyIndex(0), + m_totalMsgCount(0), + m_dataBufferSize(0), + m_leftOver(0), + m_isMove(false), + m_dummyEnvelopeNeeded(false), + m_fromLineSeen(false), + m_writeFailed(false), + m_notifyFolderLoaded(false) +{ +} + +nsLocalMailCopyState::~nsLocalMailCopyState() +{ + PR_Free(m_dataBuffer); + if (m_fileStream) + m_fileStream->Close(); + if (m_messageService) + { + nsCOMPtr srcFolder = do_QueryInterface(m_srcSupport); + if (srcFolder && m_message) + { + nsCString uri; + srcFolder->GetUriForMsg(m_message, uri); + } + } +} + +nsLocalFolderScanState::nsLocalFolderScanState() : m_uidl(nullptr) +{ +} + +nsLocalFolderScanState::~nsLocalFolderScanState() +{ +} + +/////////////////////////////////////////////////////////////////////////////// +// nsMsgLocalMailFolder interface +/////////////////////////////////////////////////////////////////////////////// + +nsMsgLocalMailFolder::nsMsgLocalMailFolder(void) + : mCopyState(nullptr), mHaveReadNameFromDB(false), + mInitialized(false), + mCheckForNewMessagesAfterParsing(false), m_parsingFolder(false), + mDownloadState(DOWNLOAD_STATE_NONE) +{ +} + +nsMsgLocalMailFolder::~nsMsgLocalMailFolder(void) +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(nsMsgLocalMailFolder, + nsMsgDBFolder, + nsICopyMessageListener, + nsIMsgLocalMailFolder) + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsMsgLocalMailFolder::Init(const char* aURI) +{ + return nsMsgDBFolder::Init(aURI); +} + +nsresult nsMsgLocalMailFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder) +{ + nsMsgLocalMailFolder *newFolder = new nsMsgLocalMailFolder; + if (!newFolder) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*folder = newFolder); + newFolder->Init(uri.get()); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CreateLocalSubfolder(const nsAString &aFolderName, + nsIMsgFolder **aChild) +{ + NS_ENSURE_ARG_POINTER(aChild); + nsresult rv = CreateSubfolderInternal(aFolderName, nullptr, aChild); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr notifier( + do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyFolderAdded(*aChild); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetManyHeadersToDownload(bool *retval) +{ + bool isLocked; + // if the folder is locked, we're probably reparsing - let's build the + // view when we've finished reparsing. + GetLocked(&isLocked); + if (isLocked) + { + *retval = true; + return NS_OK; + } + + return nsMsgDBFolder::GetManyHeadersToDownload(retval); +} + +//run the url to parse the mailbox +NS_IMETHODIMP nsMsgLocalMailFolder::ParseFolder(nsIMsgWindow *aMsgWindow, + nsIUrlListener *aListener) +{ + nsCOMPtr msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + if (aListener != this) + mReparseListener = aListener; + // if parsing is synchronous, we need to set m_parsingFolder to + // true before starting. And we need to open the db before + // setting m_parsingFolder to true. +// OpenDatabase(); + rv = msgStore->RebuildIndex(this, mDatabase, aMsgWindow, this); + if (NS_SUCCEEDED(rv)) + m_parsingFolder = true; + + return rv; +} + +// this won't force a reparse of the folder if the db is invalid. +NS_IMETHODIMP +nsMsgLocalMailFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase) +{ + return GetDatabaseWOReparse(aMsgDatabase); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetSubFolders(nsISimpleEnumerator **aResult) +{ + if (!mInitialized) + { + nsCOMPtr server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + nsCOMPtr msgStore; + // need to set this flag here to avoid infinite recursion + mInitialized = true; + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + // This should add all existing folders as sub-folders of this folder. + rv = msgStore->DiscoverSubFolders(this, true); + + nsCOMPtr path; + rv = GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) + return rv; + + bool directory; + path->IsDirectory(&directory); + if (directory) + { + SetFlag(nsMsgFolderFlags::Mail | nsMsgFolderFlags::Elided | + nsMsgFolderFlags::Directory); + + bool isServer; + GetIsServer(&isServer); + if (isServer) + { + nsCOMPtr server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr localMailServer; + localMailServer = do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + // first create the folders on disk (as empty files) + rv = localMailServer->CreateDefaultMailboxes(); + if (NS_FAILED(rv) && rv != NS_MSG_FOLDER_EXISTS) + return rv; + + // must happen after CreateSubFolders, or the folders won't exist. + rv = localMailServer->SetFlagsOnDefaultMailboxes(); + if (NS_FAILED(rv)) + return rv; + } + } + UpdateSummaryTotals(false); + } + + return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER; +} + +nsresult nsMsgLocalMailFolder::GetDatabase() +{ + nsCOMPtr msgDB; + return GetDatabaseWOReparse(getter_AddRefs(msgDB)); +} + +//we treat failure as null db returned +NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWOReparse(nsIMsgDatabase **aDatabase) +{ + NS_ENSURE_ARG(aDatabase); + if (m_parsingFolder) + return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + + nsresult rv = NS_OK; + if (!mDatabase) + { + rv = OpenDatabase(); + if (mDatabase) + { + mDatabase->AddListener(this); + UpdateNewMessages(); + } + } + NS_IF_ADDREF(*aDatabase = mDatabase); + if (mDatabase) + mDatabase->SetLastUseTime(PR_Now()); + return rv; +} + + +// Makes sure the database is open and exists. If the database is out of date, +// then this call will run an async url to reparse the folder. The passed in +// url listener will get called when the url is done. +NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWithReparse(nsIUrlListener *aReparseUrlListener, nsIMsgWindow *aMsgWindow, + nsIMsgDatabase **aMsgDatabase) +{ + nsresult rv = NS_OK; + // if we're already reparsing, just remember the listener so we can notify it + // when we've finished. + if (m_parsingFolder) + { + NS_ASSERTION(!mReparseListener, "can't have an existing listener"); + mReparseListener = aReparseUrlListener; + return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + + if (!mDatabase) + { + nsCOMPtr pathFile; + rv = GetFilePath(getter_AddRefs(pathFile)); + if (NS_FAILED(rv)) + return rv; + + bool exists; + rv = pathFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv,rv); + if (!exists) + return NS_ERROR_NULL_POINTER; //mDatabase will be null at this point. + + nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsresult folderOpen = msgDBService->OpenFolderDB(this, true, + getter_AddRefs(mDatabase)); + if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + { + nsCOMPtr dbFolderInfo; + nsCOMPtr transferInfo; + if (mDatabase) + { + mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if (dbFolderInfo) + { + dbFolderInfo->SetNumMessages(0); + dbFolderInfo->SetNumUnreadMessages(0); + dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo)); + } + dbFolderInfo = nullptr; + + // A backup message database might have been created earlier, for example + // if the user requested a reindex. We'll use the earlier one if we can, + // otherwise we'll try to backup at this point. + if (NS_FAILED(OpenBackupMsgDatabase())) + { + CloseAndBackupFolderDB(EmptyCString()); + if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase) + { + mBackupDatabase->RemoveListener(this); + mBackupDatabase = nullptr; + } + } + else + mDatabase->ForceClosed(); + + mDatabase = nullptr; + } + nsCOMPtr summaryFile; + rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + // Remove summary file. + summaryFile->Remove(false); + + // if it's out of date then reopen with upgrade. + rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + + if (transferInfo && mDatabase) + { + SetDBTransferInfo(transferInfo); + mDatabase->SetSummaryValid(false); + } + } + else if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + { + rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); + } + + if (mDatabase) + { + if (mAddListener) + mDatabase->AddListener(this); + + // if we have to regenerate the folder, run the parser url. + if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + { + if (NS_FAILED(rv = ParseFolder(aMsgWindow, aReparseUrlListener))) + { + if (rv == NS_MSG_FOLDER_BUSY) + { + mDatabase->RemoveListener(this); //we need to null out the db so that parsing gets kicked off again. + mDatabase = nullptr; + ThrowAlertMsg("parsingFolderFailed", aMsgWindow); + } + return rv; + } + + return NS_ERROR_NOT_INITIALIZED; + } + + // We have a valid database so lets extract necessary info. + UpdateSummaryTotals(true); + } + } + NS_IF_ADDREF(*aMsgDatabase = mDatabase); + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::UpdateFolder(nsIMsgWindow *aWindow) +{ + (void) RefreshSizeOnDisk(); + nsresult rv; + + if (!PromptForMasterPasswordIfNecessary()) + return NS_ERROR_FAILURE; + + //If we don't currently have a database, get it. Otherwise, the folder has been updated (presumably this + //changes when we download headers when opening inbox). If it's updated, send NotifyFolderLoaded. + if (!mDatabase) + { + // return of NS_ERROR_NOT_INITIALIZED means running parsing URL + // We don't need the return value, and assigning it to mDatabase which + // is already set internally leaks. + nsCOMPtr returnedDb; + rv = GetDatabaseWithReparse(this, aWindow, getter_AddRefs(returnedDb)); + if (NS_SUCCEEDED(rv)) + NotifyFolderEvent(mFolderLoadedAtom); + } + else + { + bool valid; + rv = mDatabase->GetSummaryValid(&valid); + // don't notify folder loaded or try compaction if db isn't valid + // (we're probably reparsing or copying msgs to it) + if (NS_SUCCEEDED(rv) && valid) + NotifyFolderEvent(mFolderLoadedAtom); + else if (mCopyState) + mCopyState->m_notifyFolderLoaded = true; //defer folder loaded notification + else if (!m_parsingFolder)// if the db was already open, it's probably OK to load it if not parsing + NotifyFolderEvent(mFolderLoadedAtom); + } + bool filtersRun; + bool hasNewMessages; + GetHasNewMessages(&hasNewMessages); + if (mDatabase) + ApplyRetentionSettings(); + // if we have new messages, try the filter plugins. + if (NS_SUCCEEDED(rv) && hasNewMessages) + (void) CallFilterPlugins(aWindow, &filtersRun); + // Callers should rely on folder loaded event to ensure completion of loading. So we'll + // return NS_OK even if parsing is still in progress + if (rv == NS_ERROR_NOT_INITIALIZED) + rv = NS_OK; + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetMessages(nsISimpleEnumerator **result) +{ + nsCOMPtr msgDB; + nsresult rv = GetDatabaseWOReparse(getter_AddRefs(msgDB)); + return NS_SUCCEEDED(rv) ? msgDB->EnumerateMessages(result) : rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetFolderURL(nsACString& aUrl) +{ + nsresult rv; + nsCOMPtr path; + rv = GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) + return rv; + + rv = NS_GetURLSpecFromFile(path, aUrl); + NS_ENSURE_SUCCESS(rv, rv); + + aUrl.Replace(0, strlen("file:"), "mailbox:"); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CreateStorageIfMissing(nsIUrlListener* aUrlListener) +{ + nsresult rv; + nsCOMPtr msgParent; + GetParent(getter_AddRefs(msgParent)); + + // parent is probably not set because *this* was probably created by rdf + // and not by folder discovery. So, we have to compute the parent. + if (!msgParent) + { + nsAutoCString folderName(mURI); + nsAutoCString uri; + int32_t leafPos = folderName.RFindChar('/'); + nsAutoCString parentName(folderName); + if (leafPos > 0) + { + // If there is a hierarchy, there is a parent. + // Don't strip off slash if it's the first character + parentName.SetLength(leafPos); + // get the corresponding RDF resource + // RDF will create the folder resource if it doesn't already exist + nsCOMPtr rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr resource; + rv = rdf->GetResource(parentName, getter_AddRefs(resource)); + NS_ENSURE_SUCCESS(rv,rv); + + msgParent = do_QueryInterface(resource, &rv); + NS_ENSURE_SUCCESS(rv,rv); + } + } + + if (msgParent) + { + nsString folderName; + GetName(folderName); + rv = msgParent->CreateSubfolder(folderName, nullptr); + // by definition, this is OK. + if (rv == NS_MSG_FOLDER_EXISTS) + return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CreateSubfolder(const nsAString& folderName, nsIMsgWindow *msgWindow) +{ + nsCOMPtr newFolder; + nsresult rv = CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyFolderAdded(newFolder); + + return NS_OK; +} + +nsresult +nsMsgLocalMailFolder::CreateSubfolderInternal(const nsAString& folderName, + nsIMsgWindow *msgWindow, + nsIMsgFolder **aNewFolder) +{ + nsresult rv = CheckIfFolderExists(folderName, this, msgWindow); + // No need for an assertion: we already throw an alert. + if (NS_FAILED(rv)) + return rv; + nsCOMPtr msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgStore->CreateFolder(this, folderName, aNewFolder); + if (rv == NS_MSG_ERROR_INVALID_FOLDER_NAME) + { + ThrowAlertMsg("folderCreationFailed", msgWindow); + } + else if (rv == NS_MSG_FOLDER_EXISTS) + { + ThrowAlertMsg("folderExists", msgWindow); + } + + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr child = *aNewFolder; + //we need to notify explicitly the flag change because it failed when we did AddSubfolder + child->OnFlagChange(mFlags); + child->SetPrettyName(folderName); //because empty trash will create a new trash folder + NotifyItemAdded(child); + if (aNewFolder) + child.swap(*aNewFolder); + } + + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CompactAll(nsIUrlListener *aListener, + nsIMsgWindow *aMsgWindow, + bool aCompactOfflineAlso) +{ + nsresult rv = NS_OK; + nsCOMPtr folderArray; + nsCOMPtr rootFolder; + nsCOMPtr allDescendents; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + nsCOMPtr msgStore; + GetMsgStore(getter_AddRefs(msgStore)); + bool storeSupportsCompaction; + msgStore->GetSupportsCompaction(&storeSupportsCompaction); + if (!storeSupportsCompaction) + return NotifyCompactCompleted(); + + if (NS_SUCCEEDED(rv) && rootFolder) + { + rv = rootFolder->GetDescendants(getter_AddRefs(allDescendents)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t cnt = 0; + rv = allDescendents->GetLength(&cnt); + NS_ENSURE_SUCCESS(rv, rv); + folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + int64_t expungedBytes = 0; + for (uint32_t i = 0; i < cnt; i++) + { + nsCOMPtr folder = do_QueryElementAt(allDescendents, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + expungedBytes = 0; + if (folder) + rv = folder->GetExpungedBytes(&expungedBytes); + + NS_ENSURE_SUCCESS(rv, rv); + + if (expungedBytes > 0) + rv = folderArray->AppendElement(folder, false); + } + rv = folderArray->GetLength(&cnt); + NS_ENSURE_SUCCESS(rv,rv); + if (cnt == 0) + return NotifyCompactCompleted(); + } + nsCOMPtr folderCompactor = do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderCompactor->CompactFolders(folderArray, nullptr, + aListener, aMsgWindow); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) +{ + nsCOMPtr msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + bool supportsCompaction; + msgStore->GetSupportsCompaction(&supportsCompaction); + if (supportsCompaction) + return msgStore->CompactFolder(this, aListener, aMsgWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::EmptyTrash(nsIMsgWindow *msgWindow, + nsIUrlListener *aListener) +{ + nsresult rv; + nsCOMPtr trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) + { + uint32_t flags; + nsCString trashUri; + trashFolder->GetURI(trashUri); + trashFolder->GetFlags(&flags); + int32_t totalMessages = 0; + rv = trashFolder->GetTotalMessages(true, &totalMessages); + if (totalMessages <= 0) + { + nsCOMPtr enumerator; + rv = trashFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv,rv); + // Any folders to deal with? + bool hasMore; + rv = enumerator->HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) + return NS_OK; + } + nsCOMPtr parentFolder; + rv = trashFolder->GetParent(getter_AddRefs(parentFolder)); + if (NS_SUCCEEDED(rv) && parentFolder) + { + nsCOMPtr transferInfo; + trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo)); + trashFolder->SetParent(nullptr); + parentFolder->PropagateDelete(trashFolder, true, msgWindow); + parentFolder->CreateSubfolder(NS_LITERAL_STRING("Trash"), nullptr); + nsCOMPtr newTrashFolder; + rv = GetTrashFolder(getter_AddRefs(newTrashFolder)); + if (NS_SUCCEEDED(rv) && newTrashFolder) + { + nsCOMPtr localTrash = do_QueryInterface(newTrashFolder); + newTrashFolder->SetDBTransferInfo(transferInfo); + if (localTrash) + localTrash->RefreshSizeOnDisk(); + // update the summary totals so the front end will + // show the right thing for the new trash folder + // see bug #161999 + nsCOMPtr dbFolderInfo; + nsCOMPtr db; + newTrashFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db)); + if (dbFolderInfo) + { + dbFolderInfo->SetNumUnreadMessages(0); + dbFolderInfo->SetNumMessages(0); + } + newTrashFolder->UpdateSummaryTotals(true); + } + } + } + return rv; +} + +nsresult nsMsgLocalMailFolder::IsChildOfTrash(bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + uint32_t parentFlags = 0; + *result = false; + bool isServer; + nsresult rv = GetIsServer(&isServer); + if (NS_FAILED(rv) || isServer) + return NS_OK; + + rv= GetFlags(&parentFlags); //this is the parent folder + if (parentFlags & nsMsgFolderFlags::Trash) + { + *result = true; + return rv; + } + + nsCOMPtr parentFolder; + nsCOMPtr thisFolder; + rv = QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) getter_AddRefs(thisFolder)); + + while (!isServer) + { + thisFolder->GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) + return NS_OK; + + rv = parentFolder->GetIsServer(&isServer); + if (NS_FAILED(rv) || isServer) + return NS_OK; + + rv = parentFolder->GetFlags(&parentFlags); + if (NS_FAILED(rv)) + return NS_OK; + + if (parentFlags & nsMsgFolderFlags::Trash) + { + *result = true; + return rv; + } + + thisFolder = parentFolder; + } + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Delete() +{ + nsresult rv; + nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgDBService->CachedDBForFolder(this, getter_AddRefs(mDatabase)); + if (mDatabase) + { + mDatabase->ForceClosed(); + mDatabase = nullptr; + } + + nsCOMPtr server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr msgStore; + + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr summaryFile; + rv = GetSummaryFile(getter_AddRefs(summaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + //Clean up .sbd folder if it exists. + // Remove summary file. + rv = summaryFile->Remove(false); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Could not delete msg summary file"); + + rv = msgStore->DeleteFolder(this); + if (rv == NS_ERROR_FILE_NOT_FOUND || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) + rv = NS_OK; // virtual folders do not have a msgStore file + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::DeleteSubFolders(nsIArray *folders, nsIMsgWindow *msgWindow) +{ + nsresult rv; + bool isChildOfTrash; + IsChildOfTrash(&isChildOfTrash); + + // we don't allow multiple folder selection so this is ok. + nsCOMPtr folder = do_QueryElementAt(folders, 0); + uint32_t folderFlags = 0; + if (folder) + folder->GetFlags(&folderFlags); + // when deleting from trash, or virtual folder, just delete it. + if (isChildOfTrash || folderFlags & nsMsgFolderFlags::Virtual) + return nsMsgDBFolder::DeleteSubFolders(folders, msgWindow); + + nsCOMPtr trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) + { + if (folder) + { + nsCOMPtr copyService(do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = copyService->CopyFolders(folders, trashFolder, true, nullptr, msgWindow); + } + } + return rv; +} + +nsresult nsMsgLocalMailFolder::ConfirmFolderDeletion(nsIMsgWindow *aMsgWindow, + nsIMsgFolder *aFolder, bool *aResult) +{ + NS_ENSURE_ARG(aResult); + NS_ENSURE_ARG(aMsgWindow); + NS_ENSURE_ARG(aFolder); + nsCOMPtr docShell; + aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) + { + bool confirmDeletion = true; + nsresult rv; + nsCOMPtr pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + pPrefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", &confirmDeletion); + if (confirmDeletion) + { + nsCOMPtr bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString folderName; + rv = aFolder->GetName(folderName); + NS_ENSURE_SUCCESS(rv, rv); + const char16_t *formatStrings[1] = { folderName.get() }; + + nsAutoString deleteFolderDialogTitle; + rv = bundle->GetStringFromName( + u"pop3DeleteFolderDialogTitle", + getter_Copies(deleteFolderDialogTitle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString deleteFolderButtonLabel; + rv = bundle->GetStringFromName( + u"pop3DeleteFolderButtonLabel", + getter_Copies(deleteFolderButtonLabel)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString confirmationStr; + rv = bundle->FormatStringFromName( + u"pop3MoveFolderToTrash", formatStrings, 1, + getter_Copies(confirmationStr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr dialog(do_GetInterface(docShell)); + if (dialog) + { + int32_t buttonPressed = 0; + // Default the dialog to "cancel". + const uint32_t buttonFlags = + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1); + bool dummyValue = false; + rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(), confirmationStr.get(), + buttonFlags, deleteFolderButtonLabel.get(), + nullptr, nullptr, nullptr, &dummyValue, + &buttonPressed); + NS_ENSURE_SUCCESS(rv, rv); + *aResult = !buttonPressed; // "ok" is in position 0 + } + } + else + *aResult = true; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Rename(const nsAString& aNewName, nsIMsgWindow *msgWindow) +{ + // Renaming to the same name is easy + if (mName.Equals(aNewName)) + return NS_OK; + + nsCOMPtr parentFolder; + nsresult rv = GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) + return NS_ERROR_NULL_POINTER; + + rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr msgStore; + nsCOMPtr newFolder; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgStore->RenameFolder(this, aNewName, getter_AddRefs(newFolder)); + if (NS_FAILED(rv)) + { + if (msgWindow) + (void) ThrowAlertMsg((rv == NS_MSG_FOLDER_EXISTS) ? + "folderExists" : "folderRenameFailed", msgWindow); + return rv; + } + + int32_t count = mSubFolders.Count(); + if (newFolder) + { + // Because we just renamed the db, w/o setting the pretty name in it, + // we need to force the pretty name to be correct. + // SetPrettyName won't write the name to the db if it doesn't think the + // name has changed. This hack forces the pretty name to get set in the db. + // We could set the new pretty name on the db before renaming the .msf file, + // but if the rename failed, it would be out of sync. + newFolder->SetPrettyName(EmptyString()); + newFolder->SetPrettyName(aNewName); + bool changed = false; + MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &changed); + if (changed) + AlertFilterChanged(msgWindow); + + if (count > 0) + newFolder->RenameSubFolders(msgWindow, this); + + // Discover the subfolders inside this folder (this is recursive) + nsCOMPtr dummy; + newFolder->GetSubFolders(getter_AddRefs(dummy)); + + // the newFolder should have the same flags + newFolder->SetFlags(mFlags); + if (parentFolder) + { + SetParent(nullptr); + parentFolder->PropagateDelete(this, false, msgWindow); + parentFolder->NotifyItemAdded(newFolder); + } + SetFilePath(nullptr); // forget our path, since this folder object renamed itself + nsCOMPtr folderRenameAtom = MsgGetAtom("RenameCompleted"); + newFolder->NotifyFolderEvent(folderRenameAtom); + + nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyFolderRenamed(this, newFolder); + } + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::RenameSubFolders(nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder) +{ + nsresult rv =NS_OK; + mInitialized = true; + + uint32_t flags; + oldFolder->GetFlags(&flags); + SetFlags(flags); + + nsCOMPtr enumerator; + rv = oldFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr msgFolder(do_QueryInterface(item)); + if (!msgFolder) + continue; + + nsString folderName; + rv = msgFolder->GetName(folderName); + nsCOMPtr newFolder; + AddSubfolder(folderName, getter_AddRefs(newFolder)); + if (newFolder) + { + newFolder->SetPrettyName(folderName); + bool changed = false; + msgFolder->MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &changed); + if (changed) + msgFolder->AlertFilterChanged(msgWindow); + newFolder->RenameSubFolders(msgWindow, msgFolder); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetPrettyName(nsAString& prettyName) +{ + return nsMsgDBFolder::GetPrettyName(prettyName); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::SetPrettyName(const nsAString& aName) +{ + nsresult rv = nsMsgDBFolder::SetPrettyName(aName); + NS_ENSURE_SUCCESS(rv, rv); + nsCString folderName; + rv = GetStringProperty("folderName", folderName); + NS_ConvertUTF16toUTF8 utf8FolderName(mName); + return NS_FAILED(rv) || !folderName.Equals(utf8FolderName) ? SetStringProperty("folderName", utf8FolderName) : rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetName(nsAString& aName) +{ + ReadDBFolderInfo(false); + return nsMsgDBFolder::GetName(aName); +} + +nsresult nsMsgLocalMailFolder::OpenDatabase() +{ + nsresult rv; + nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr file; + rv = GetFilePath(getter_AddRefs(file)); + + rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + { + // check if we're a real folder by looking at the parent folder. + nsCOMPtr parent; + GetParent(getter_AddRefs(parent)); + if (parent) + { + // This little dance creates an empty .msf file and then checks + // if the db is valid - this works if the folder is empty, which + // we don't have a direct way of checking. + nsCOMPtr db; + rv = msgDBService->CreateNewDB(this, getter_AddRefs(db)); + if (db) + { + UpdateSummaryTotals(true); + db->Close(true); + mDatabase = nullptr; + db = nullptr; + rv = msgDBService->OpenFolderDB(this, false, + getter_AddRefs(mDatabase)); + if (NS_FAILED(rv)) + mDatabase = nullptr; + } + } + } + else if (NS_FAILED(rv)) + mDatabase = nullptr; + + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db) +{ + if (!db || !folderInfo || !mPath || mIsServer) + return NS_ERROR_NULL_POINTER; //ducarroz: should we use NS_ERROR_INVALID_ARG? + + nsresult rv; + if (mDatabase) + rv = NS_OK; + else + { + rv = OpenDatabase(); + + if (mAddListener && mDatabase) + mDatabase->AddListener(this); + } + + NS_IF_ADDREF(*db = mDatabase); + if (NS_SUCCEEDED(rv) && *db) + rv = (*db)->GetDBFolderInfo(folderInfo); + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element) +{ + NS_ENSURE_ARG_POINTER(element); + nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element); + NS_ENSURE_SUCCESS(rv, rv); + nsCString utf8Name; + rv = element->GetStringProperty("folderName", utf8Name); + NS_ENSURE_SUCCESS(rv, rv); + CopyUTF8toUTF16(utf8Name, mName); + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element) +{ + NS_ENSURE_ARG_POINTER(element); + nsMsgDBFolder::WriteToFolderCacheElem(element); + return element->SetStringProperty("folderName", NS_ConvertUTF16toUTF8(mName)); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetDeletable(bool *deletable) +{ + NS_ENSURE_ARG_POINTER(deletable); + + bool isServer; + GetIsServer(&isServer); + *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::RefreshSizeOnDisk() +{ + int64_t oldFolderSize = mFolderSize; + // we set this to unknown to force it to get recalculated from disk + mFolderSize = kSizeUnknown; + if (NS_SUCCEEDED(GetSizeOnDisk(&mFolderSize))) + NotifyIntPropertyChanged(kFolderSizeAtom, oldFolderSize, mFolderSize); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetSizeOnDisk(int64_t *aSize) +{ + NS_ENSURE_ARG_POINTER(aSize); + + bool isServer = false; + nsresult rv = GetIsServer(&isServer); + // If this is the rootFolder, return 0 as a safe value. + if (NS_FAILED(rv) || isServer) + mFolderSize = 0; + + if (mFolderSize == kSizeUnknown) + { + nsCOMPtr file; + rv = GetFilePath(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + // Use a temporary variable so that we keep mFolderSize on kSizeUnknown + // if GetFileSize() fails. + int64_t folderSize; + rv = file->GetFileSize(&folderSize); + NS_ENSURE_SUCCESS(rv, rv); + + mFolderSize = folderSize; + } + *aSize = mFolderSize; + return NS_OK; +} + +nsresult +nsMsgLocalMailFolder::GetTrashFolder(nsIMsgFolder** result) +{ + NS_ENSURE_ARG_POINTER(result); + nsresult rv; + nsCOMPtr rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv)) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, result); + if (!*result) + rv = NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::DeleteMessages(nsIArray *messages, + nsIMsgWindow *msgWindow, + bool deleteStorage, bool isMove, + nsIMsgCopyServiceListener* listener, bool allowUndo) +{ + NS_ENSURE_ARG_POINTER(messages); + + uint32_t messageCount; + nsresult rv = messages->GetLength(&messageCount); + NS_ENSURE_SUCCESS(rv, rv); + + // shift delete case - (delete to trash is handled in EndMove) + // this is also the case when applying retention settings. + if (deleteStorage && !isMove) + { + MarkMsgsOnPop3Server(messages, POP3_DELETE); + } + + bool isTrashFolder = mFlags & nsMsgFolderFlags::Trash; + + // notify on delete from trash and shift-delete + if (!isMove && (deleteStorage || isTrashFolder)) + { + nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgsDeleted(messages); + } + + if (!deleteStorage && !isTrashFolder) + { + nsCOMPtr trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return copyService->CopyMessages(this, messages, trashFolder, + true, listener, msgWindow, allowUndo); + } + } + else + { + nsCOMPtr msgDB; + rv = GetDatabaseWOReparse(getter_AddRefs(msgDB)); + if (NS_SUCCEEDED(rv)) + { + if (deleteStorage && isMove && GetDeleteFromServerOnMove()) + MarkMsgsOnPop3Server(messages, POP3_DELETE); + + nsCOMPtr msgSupport; + rv = EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(rv)) + { + rv = msgStore->DeleteMessages(messages); + nsCOMPtr msgDBHdr; + for (uint32_t i = 0; i < messageCount; ++i) + { + msgDBHdr = do_QueryElementAt(messages, i, &rv); + rv = msgDB->DeleteHeader(msgDBHdr, nullptr, false, true); + } + } + } + else if (rv == NS_MSG_FOLDER_BUSY) + ThrowAlertMsg("deletingMsgsFailed", msgWindow); + + // we are the source folder here for a move or shift delete + //enable notifications because that will close the file stream + // we've been caching, mark the db as valid, and commit it. + EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/); + if (!isMove) + NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom); + if (msgWindow && !isMove) + AutoCompact(msgWindow); + } + } + + if (msgWindow && !isMove && (deleteStorage || isTrashFolder)) { + // Clear undo and redo stack. + nsCOMPtr txnMgr; + msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) { + txnMgr->Clear(); + } + } + 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 msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Read, aMarkRead); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkMessagesFlagged(nsIArray *aMessages, + bool aMarkFlagged) +{ + nsresult rv = nsMsgDBFolder::MarkMessagesFlagged(aMessages, aMarkFlagged); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 messages; + rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr 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); + + 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 messages; + rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr 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); + + free(thoseMarked); + return rv; +} + +nsresult +nsMsgLocalMailFolder::InitCopyState(nsISupports* aSupport, + nsIArray* messages, + bool isMove, + nsIMsgCopyServiceListener* listener, + nsIMsgWindow *msgWindow, bool isFolder, + bool allowUndo) +{ + nsCOMPtr path; + + NS_ASSERTION(!mCopyState, "already copying a msg into this folder"); + if (mCopyState) + return NS_ERROR_FAILURE; // already has a copy in progress + + // get mDatabase set, so we can use it to add new hdrs to this db. + // calling GetDatabase will set mDatabase - we use the comptr + // here to avoid doubling the refcnt on mDatabase. We don't care if this + // fails - we just want to give it a chance. It will definitely fail in + // nsLocalMailFolder::EndCopy because we will have written data to the folder + // and changed its size. + nsCOMPtr msgDB; + GetDatabaseWOReparse(getter_AddRefs(msgDB)); + bool isLocked; + + GetLocked(&isLocked); + if (isLocked) + return NS_MSG_FOLDER_BUSY; + + AcquireSemaphore(static_cast(this)); + + mCopyState = new nsLocalMailCopyState(); + NS_ENSURE_TRUE(mCopyState, NS_ERROR_OUT_OF_MEMORY); + + mCopyState->m_dataBuffer = (char*) PR_CALLOC(COPY_BUFFER_SIZE+1); + NS_ENSURE_TRUE(mCopyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCopyState->m_dataBufferSize = COPY_BUFFER_SIZE; + mCopyState->m_destDB = msgDB; + + //Before we continue we should verify that there is enough diskspace. + //XXX How do we do this? + nsresult rv; + mCopyState->m_srcSupport = do_QueryInterface(aSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mCopyState->m_messages = messages; + mCopyState->m_curCopyIndex = 0; + mCopyState->m_isMove = isMove; + mCopyState->m_isFolder = isFolder; + mCopyState->m_allowUndo = allowUndo; + mCopyState->m_msgWindow = msgWindow; + rv = messages->GetLength(&mCopyState->m_totalMsgCount); + if (listener) + mCopyState->m_listener = do_QueryInterface(listener, &rv); + mCopyState->m_copyingMultipleMessages = false; + mCopyState->m_wholeMsgInStream = false; + + // If we have source messages then we need destination messages too. + if (messages) + mCopyState->m_destMessages = do_CreateInstance(NS_ARRAY_CONTRACTID); + + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) +{ + if (mCopyState) + mCopyState->m_destDB = nullptr; + return nsMsgDBFolder::OnAnnouncerGoingAway(instigator); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::OnCopyCompleted(nsISupports *srcSupport, bool moveCopySucceeded) +{ + if (mCopyState && mCopyState->m_notifyFolderLoaded) + NotifyFolderEvent(mFolderLoadedAtom); + + (void) RefreshSizeOnDisk(); + // we are the destination folder for a move/copy + bool haveSemaphore; + nsresult rv = TestSemaphore(static_cast(this), &haveSemaphore); + if (NS_SUCCEEDED(rv) && haveSemaphore) + ReleaseSemaphore(static_cast(this)); + + if (mCopyState && !mCopyState->m_newMsgKeywords.IsEmpty() && + mCopyState->m_newHdr) + { + nsCOMPtr messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_TRUE(messageArray, rv); + messageArray->AppendElement(mCopyState->m_newHdr, false); + AddKeywordsToMessages(messageArray, mCopyState->m_newMsgKeywords); + } + if (moveCopySucceeded && mDatabase) + { + mDatabase->SetSummaryValid(true); + (void) CloseDBIfFolderNotOpen(); + } + + delete mCopyState; + mCopyState = nullptr; + nsCOMPtr copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return copyService->NotifyCompletion(srcSupport, this, moveCopySucceeded ? NS_OK : NS_ERROR_FAILURE); +} + +bool nsMsgLocalMailFolder::CheckIfSpaceForCopy(nsIMsgWindow *msgWindow, + nsIMsgFolder *srcFolder, + nsISupports *srcSupports, + bool isMove, + int64_t totalMsgSize) +{ + bool spaceNotAvailable = true; + nsresult rv = WarnIfLocalFileTooBig(msgWindow, totalMsgSize, &spaceNotAvailable); + if (NS_FAILED(rv) || spaceNotAvailable) + { + if (isMove && srcFolder) + srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom); + OnCopyCompleted(srcSupports, false); + return false; + } + return true; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyMessages(nsIMsgFolder* srcFolder, nsIArray* + messages, bool isMove, + nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener* listener, + bool isFolder, bool allowUndo) +{ + nsCOMPtr srcSupport = do_QueryInterface(srcFolder); + bool isServer; + nsresult rv = GetIsServer(&isServer); + if (NS_SUCCEEDED(rv) && isServer) + { + NS_ERROR("Destination is the root folder. Cannot move/copy here"); + if (isMove) + srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom); + return OnCopyCompleted(srcSupport, false); + } + + UpdateTimestamps(allowUndo); + nsCString protocolType; + rv = srcFolder->GetURI(protocolType); + protocolType.SetLength(protocolType.FindChar(':')); + + bool needOfflineBody = (WeAreOffline() && + (MsgLowerCaseEqualsLiteral(protocolType, "imap") || + MsgLowerCaseEqualsLiteral(protocolType, "news"))); + int64_t totalMsgSize = 0; + uint32_t numMessages = 0; + messages->GetLength(&numMessages); + for (uint32_t i = 0; i < numMessages; i++) + { + nsCOMPtr message(do_QueryElementAt(messages, i, &rv)); + if (NS_SUCCEEDED(rv) && message) + { + nsMsgKey key; + uint32_t msgSize; + message->GetMessageSize(&msgSize); + + /* 200 is a per-message overhead to account for any extra data added + to the message. + */ + totalMsgSize += msgSize + 200; + + if (needOfflineBody) + { + bool hasMsgOffline = false; + message->GetMessageKey(&key); + srcFolder->HasMsgOffline(key, &hasMsgOffline); + if (!hasMsgOffline) + { + if (isMove) + srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom); + ThrowAlertMsg("cantMoveMsgWOBodyOffline", msgWindow); + return OnCopyCompleted(srcSupport, false); + } + } + } + } + + if (!CheckIfSpaceForCopy(msgWindow, srcFolder, srcSupport, isMove, + totalMsgSize)) + return NS_OK; + + NS_ENSURE_SUCCESS(rv, rv); + bool storeDidCopy = false; + nsCOMPtr msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr undoTxn; + nsCOMPtr dstHdrs; + rv = msgStore->CopyMessages(isMove, messages, this, listener, + getter_AddRefs(dstHdrs), getter_AddRefs(undoTxn), + &storeDidCopy); + if (storeDidCopy) + { + NS_ASSERTION(undoTxn, "if store does copy, it needs to add undo action"); + if (msgWindow && undoTxn) + { + nsCOMPtr txnMgr; + msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) + txnMgr->DoTransaction(undoTxn); + } + if (isMove) + srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : + mDeleteOrMoveMsgFailedAtom); + + if (NS_SUCCEEDED(rv)) { + // If the store did the copy, like maildir, we need to mark messages on the server. + // Otherwise that's done in EndMove(). + nsCOMPtr localDstFolder; + QueryInterface(NS_GET_IID(nsIMsgLocalMailFolder), getter_AddRefs(localDstFolder)); + if (localDstFolder) + { + // If we are the trash and a local msg is being moved to us, mark the source + // for delete from server, if so configured. + if (mFlags & nsMsgFolderFlags::Trash) + { + // If we're deleting on all moves, we'll mark this message for deletion when + // we call DeleteMessages on the source folder. So don't mark it for deletion + // here, in that case. + if (!GetDeleteFromServerOnMove()) + localDstFolder->MarkMsgsOnPop3Server(dstHdrs, POP3_DELETE); + } + } + } + return rv; + } + // If the store doesn't do the copy, we'll stream the source messages into + // the target folder, using getMsgInputStream and getNewMsgOutputStream. + + // don't update the counts in the dest folder until it is all over + EnableNotifications(allMessageCountNotifications, false, false /*dbBatching*/); //dest folder doesn't need db batching + + // sort the message array by key + uint32_t numMsgs = 0; + messages->GetLength(&numMsgs); + nsTArray keyArray(numMsgs); + if (numMsgs > 1) + { + for (uint32_t i = 0; i < numMsgs; i++) + { + nsCOMPtr aMessage = do_QueryElementAt(messages, i, &rv); + if (NS_SUCCEEDED(rv) && aMessage) + { + nsMsgKey key; + aMessage->GetMessageKey(&key); + keyArray.AppendElement(key); + } + } + + keyArray.Sort(); + + nsCOMPtr sortedMsgs(do_CreateInstance(NS_ARRAY_CONTRACTID)); + rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs); + NS_ENSURE_SUCCESS(rv, rv); + + rv = InitCopyState(srcSupport, sortedMsgs, isMove, listener, msgWindow, isFolder, allowUndo); + } + else + rv = InitCopyState(srcSupport, messages, isMove, listener, msgWindow, isFolder, allowUndo); + + if (NS_FAILED(rv)) + { + ThrowAlertMsg("operationFailedFolderBusy", msgWindow); + (void) OnCopyCompleted(srcSupport, false); + return rv; + } + + if (!MsgLowerCaseEqualsLiteral(protocolType, "mailbox")) + { + mCopyState->m_dummyEnvelopeNeeded = true; + nsParseMailMessageState* parseMsgState = new nsParseMailMessageState(); + if (parseMsgState) + { + nsCOMPtr msgDb; + mCopyState->m_parseMsgState = parseMsgState; + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) + parseMsgState->SetMailDB(msgDb); + } + } + + // undo stuff + if (allowUndo) //no undo for folder move/copy or or move/copy from search window + { + RefPtr msgTxn = new nsLocalMoveCopyMsgTxn; + if (msgTxn && NS_SUCCEEDED(msgTxn->Init(srcFolder, this, isMove))) + { + msgTxn->SetMsgWindow(msgWindow); + if (isMove) + { + if (mFlags & nsMsgFolderFlags::Trash) + msgTxn->SetTransactionType(nsIMessenger::eDeleteMsg); + else + msgTxn->SetTransactionType(nsIMessenger::eMoveMsg); + } + else + msgTxn->SetTransactionType(nsIMessenger::eCopyMsg); + msgTxn.swap(mCopyState->m_undoMsgTxn); + } + } + + if (numMsgs > 1 && ((MsgLowerCaseEqualsLiteral(protocolType, "imap") && !WeAreOffline()) || + MsgLowerCaseEqualsLiteral(protocolType, "mailbox"))) + { + mCopyState->m_copyingMultipleMessages = true; + rv = CopyMessagesTo(mCopyState->m_messages, keyArray, msgWindow, this, isMove); + if (NS_FAILED(rv)) + { + NS_ERROR("copy message failed"); + (void) OnCopyCompleted(srcSupport, false); + } + } + else + { + nsCOMPtr msgSupport = do_QueryElementAt(mCopyState->m_messages, 0); + if (msgSupport) + { + rv = CopyMessageTo(msgSupport, this, msgWindow, isMove); + if (NS_FAILED(rv)) + { + NS_ASSERTION(false, "copy message failed"); + (void) OnCopyCompleted(srcSupport, false); + } + } + } + // if this failed immediately, need to turn back on notifications and inform FE. + if (NS_FAILED(rv)) + { + if (isMove) + srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom); + EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching + } + return rv; +} +// for srcFolder that are on different server than the dstFolder. +// "this" is the parent of the new dest folder. +nsresult +nsMsgLocalMailFolder::CopyFolderAcrossServer(nsIMsgFolder* srcFolder, nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener *listener ) +{ + mInitialized = true; + + nsString folderName; + srcFolder->GetName(folderName); + + nsCOMPtr newMsgFolder; + nsresult rv = CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr messages; + rv = srcFolder->GetMessages(getter_AddRefs(messages)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + + bool hasMoreElements = false; + nsCOMPtr aSupport; + + if (messages) + rv = messages->HasMoreElements(&hasMoreElements); + + while (NS_SUCCEEDED(rv) && hasMoreElements) + { + rv = messages->GetNext(getter_AddRefs(aSupport)); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgArray->AppendElement(aSupport, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = messages->HasMoreElements(&hasMoreElements); + } + + uint32_t numMsgs=0; + msgArray->GetLength(&numMsgs); + + if (numMsgs > 0 ) //if only srcFolder has messages.. + newMsgFolder->CopyMessages(srcFolder, msgArray, false, msgWindow, listener, true /* is folder*/, false /* allowUndo */); + else + { + nsCOMPtr localFolder = do_QueryInterface(newMsgFolder); + if (localFolder) + { + // normally these would get called from ::EndCopy when the last message + // was finished copying. But since there are no messages, we have to call + // them explicitly. + nsCOMPtr srcSupports = do_QueryInterface(newMsgFolder); + localFolder->CopyAllSubFolders(srcFolder, msgWindow, listener); + return localFolder->OnCopyCompleted(srcSupports, true); + } + } + return NS_OK; // otherwise the front-end will say Exception::CopyFolder +} + +nsresult //copy the sub folders +nsMsgLocalMailFolder::CopyAllSubFolders(nsIMsgFolder *srcFolder, + nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener *listener ) +{ + nsCOMPtr enumerator; + nsresult rv = srcFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr folder(do_QueryInterface(item)); + if (folder) + CopyFolderAcrossServer(folder, msgWindow, listener); + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyFolder( nsIMsgFolder* srcFolder, bool isMoveFolder, + nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener* listener) +{ + NS_ENSURE_ARG_POINTER(srcFolder); + // isMoveFolder == true when "this" and srcFolder are on same server + return isMoveFolder ? CopyFolderLocal(srcFolder, isMoveFolder, msgWindow, listener ) : + CopyFolderAcrossServer(srcFolder, msgWindow, listener ); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyFolderLocal(nsIMsgFolder *srcFolder, + bool isMoveFolder, + nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener *aListener) +{ + mInitialized = true; + bool isChildOfTrash; + nsresult rv = IsChildOfTrash(&isChildOfTrash); + if (NS_SUCCEEDED(rv) && isChildOfTrash) + { + // do it just for the parent folder (isMoveFolder is true for parent only) if we are deleting/moving a folder tree + // don't confirm for rss folders. + if (isMoveFolder) + { + // if there's a msgWindow, confirm the deletion + if (msgWindow) + { + + bool okToDelete = false; + ConfirmFolderDeletion(msgWindow, srcFolder, &okToDelete); + if (!okToDelete) + return NS_MSG_ERROR_COPY_FOLDER_ABORTED; + } + // if we are moving a favorite folder to trash, we should clear the favorites flag + // so it gets removed from the view. + srcFolder->ClearFlag(nsMsgFolderFlags::Favorite); + } + + bool match = false; + rv = srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match); + if (match && msgWindow) + { + bool confirmed = false; + srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed); + if (!confirmed) + return NS_MSG_ERROR_COPY_FOLDER_ABORTED; + } + } + + nsAutoString newFolderName; + nsAutoString folderName; + rv = srcFolder->GetName(folderName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isMoveFolder) { + rv = CheckIfFolderExists(folderName, this, msgWindow); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + else + { + // If folder name already exists in destination, generate a new unique name. + bool containsChild = true; + uint32_t i; + for (i = 1; containsChild; i++) { + newFolderName.Assign(folderName); + if (i > 1) { + // This could be localizable but Toolkit is fine without it, see + // mozilla/toolkit/content/contentAreaUtils.js::uniqueFile() + newFolderName.Append('('); + newFolderName.AppendInt(i); + newFolderName.Append(')'); + } + rv = ContainsChildNamed(newFolderName, &containsChild); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // 'i' is one more than the number of iterations done + // and the number tacked onto the name of the folder. + if (i > 2 && !isChildOfTrash) { + // Folder name already exists, ask if rename is OK. + // If moving to Trash, don't ask and do it. + if (!ConfirmAutoFolderRename(msgWindow, folderName, newFolderName)) + return NS_MSG_ERROR_COPY_FOLDER_ABORTED; + } + } + + nsCOMPtr msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return msgStore->CopyFolder(srcFolder, this, isMoveFolder, msgWindow, + aListener, newFolderName); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyFileMessage(nsIFile* aFile, + nsIMsgDBHdr *msgToReplace, + bool isDraftOrTemplate, + uint32_t newMsgFlags, + const nsACString &aNewMsgKeywords, + nsIMsgWindow *msgWindow, + nsIMsgCopyServiceListener* listener) +{ + NS_ENSURE_ARG_POINTER(aFile); + nsresult rv = NS_ERROR_NULL_POINTER; + nsParseMailMessageState* parseMsgState = nullptr; + int64_t fileSize = 0; + + nsCOMPtr fileSupport(do_QueryInterface(aFile, &rv)); + + aFile->GetFileSize(&fileSize); + if (!CheckIfSpaceForCopy(msgWindow, nullptr, fileSupport, false, fileSize)) + return NS_OK; + + nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID)); + + if (msgToReplace) + messages->AppendElement(msgToReplace, false); + + rv = InitCopyState(fileSupport, messages, msgToReplace ? true : false, + listener, msgWindow, false, false); + if (NS_SUCCEEDED(rv)) + { + if (mCopyState) + mCopyState->m_newMsgKeywords = aNewMsgKeywords; + + parseMsgState = new nsParseMailMessageState(); + NS_ENSURE_TRUE(parseMsgState, NS_ERROR_OUT_OF_MEMORY); + nsCOMPtr msgDb; + mCopyState->m_parseMsgState = parseMsgState; + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) + parseMsgState->SetMailDB(msgDb); + + nsCOMPtr inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + + // All or none for adding a message file to the store + if (NS_SUCCEEDED(rv) && fileSize > PR_INT32_MAX) + rv = NS_ERROR_ILLEGAL_VALUE; // may need error code for max msg size + + if (NS_SUCCEEDED(rv) && inputStream) + { + char buffer[5]; + uint32_t readCount; + rv = inputStream->Read(buffer, 5, &readCount); + if (NS_SUCCEEDED(rv)) + { + if (strncmp(buffer, "From ", 5)) + mCopyState->m_dummyEnvelopeNeeded = true; + nsCOMPtr seekableStream = do_QueryInterface(inputStream, &rv); + if (NS_SUCCEEDED(rv)) + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + } + } + + mCopyState->m_wholeMsgInStream = true; + if (NS_SUCCEEDED(rv)) + rv = BeginCopy(nullptr); + + if (NS_SUCCEEDED(rv)) + rv = CopyData(inputStream, (int32_t) fileSize); + + if (NS_SUCCEEDED(rv)) + rv = EndCopy(true); + + //mDatabase should have been initialized above - if we got msgDb + // If we were going to delete, here is where we would do it. But because + // existing code already supports doing those deletes, we are just going + // to end the copy. + if (NS_SUCCEEDED(rv) && msgToReplace && mDatabase) + rv = OnCopyCompleted(fileSupport, true); + + if (inputStream) + inputStream->Close(); + } + + if (NS_FAILED(rv)) + (void) OnCopyCompleted(fileSupport, false); + + return rv; +} + +nsresult nsMsgLocalMailFolder::DeleteMessage(nsISupports *message, + nsIMsgWindow *msgWindow, + bool deleteStorage, bool commit) +{ + nsresult rv = NS_OK; + if (deleteStorage) + { + nsCOMPtr msgDBHdr(do_QueryInterface(message, &rv)); + + if (NS_SUCCEEDED(rv)) + { + GetDatabase(); + if (mDatabase) + rv = mDatabase->DeleteHeader(msgDBHdr, nullptr, commit, true); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener) +{ + nsCOMPtr server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr localMailServer = do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + // XXX todo, move all this into nsILocalMailIncomingServer's GetNewMail + // so that we don't have to have RSS foo here. + nsCOMPtr rssServer = do_QueryInterface(server, &rv); + if (NS_SUCCEEDED(rv)) + return localMailServer->GetNewMail(aWindow, aListener, this, nullptr); + + nsCOMPtr inbox; + nsCOMPtr rootFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inbox)); + } + nsCOMPtr localInbox = do_QueryInterface(inbox, &rv); + if (NS_SUCCEEDED(rv)) + { + bool valid = false; + nsCOMPtr db; + // this will kick off a reparse if the db is out of date. + rv = localInbox->GetDatabaseWithReparse(nullptr, aWindow, getter_AddRefs(db)); + if (NS_SUCCEEDED(rv)) + { + db->GetSummaryValid(&valid); + rv = valid ? localMailServer->GetNewMail(aWindow, aListener, inbox, nullptr) : + localInbox->SetCheckForNewMessagesAfterParsing(true); + } + } + return rv; +} + +nsresult nsMsgLocalMailFolder::WriteStartOfNewMessage() +{ + nsCOMPtr seekableStream = do_QueryInterface(mCopyState->m_fileStream); + int64_t filePos; + seekableStream->Tell(&filePos); + + // CopyFileMessage() and CopyMessages() from servers other than pop3 + if (mCopyState->m_parseMsgState) + { + if (mCopyState->m_parseMsgState->m_newMsgHdr) + mCopyState->m_parseMsgState->m_newMsgHdr->GetMessageKey(&mCopyState->m_curDstKey); + mCopyState->m_parseMsgState->SetEnvelopePos(filePos); + mCopyState->m_parseMsgState->SetState(nsIMsgParseMailMsgState::ParseHeadersState); + } + if (mCopyState->m_dummyEnvelopeNeeded) + { + nsCString result; + nsAutoCString nowStr; + MsgGenerateNowStr(nowStr); + result.AppendLiteral("From - "); + result.Append(nowStr); + result.Append(MSG_LINEBREAK); + + // *** jt - hard code status line for now; come back later + nsresult rv; + nsCOMPtr curSourceMessage = do_QueryElementAt(mCopyState->m_messages, + mCopyState->m_curCopyIndex, &rv); + + char statusStrBuf[50]; + if (curSourceMessage) + { + uint32_t dbFlags = 0; + curSourceMessage->GetFlags(&dbFlags); + + // write out x-mozilla-status, but make sure we don't write out nsMsgMessageFlags::Offline + PR_snprintf(statusStrBuf, sizeof(statusStrBuf), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, + dbFlags & ~(nsMsgMessageFlags::RuntimeOnly | nsMsgMessageFlags::Offline) & 0x0000FFFF); + } + else + strcpy(statusStrBuf, "X-Mozilla-Status: 0001" MSG_LINEBREAK); + uint32_t bytesWritten; + mCopyState->m_fileStream->Write(result.get(), result.Length(), &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine( + result.get(), result.Length()); + mCopyState->m_fileStream->Write(statusStrBuf, strlen(statusStrBuf), &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine( + statusStrBuf, strlen(statusStrBuf)); + result = "X-Mozilla-Status2: 00000000" MSG_LINEBREAK; + mCopyState->m_fileStream->Write(result.get(), result.Length(), &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine( + result.get(), result.Length()); + result = X_MOZILLA_KEYWORDS; + mCopyState->m_fileStream->Write(result.get(), result.Length(), &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine( + result.get(), result.Length()); + mCopyState->m_fromLineSeen = true; + } + else + mCopyState->m_fromLineSeen = false; + + mCopyState->m_curCopyIndex++; + return NS_OK; +} + +nsresult nsMsgLocalMailFolder::InitCopyMsgHdrAndFileStream() +{ + nsCOMPtr msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(mCopyState->m_msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + bool reusable; + rv = mCopyState->m_msgStore-> + GetNewMsgOutputStream(this, getter_AddRefs(mCopyState->m_newHdr), + &reusable, + getter_AddRefs(mCopyState->m_fileStream)); + NS_ENSURE_SUCCESS(rv, rv); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->SetNewMsgHdr(mCopyState->m_newHdr); + return rv; +} + +//nsICopyMessageListener +NS_IMETHODIMP nsMsgLocalMailFolder::BeginCopy(nsIMsgDBHdr *message) +{ + if (!mCopyState) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + if (!mCopyState->m_copyingMultipleMessages) + { + rv = InitCopyMsgHdrAndFileStream(); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCOMPtr seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv); + + // XXX ToDo: When copying multiple messages from a non-offline-enabled IMAP + // server, this fails. (The copy succeeds because the file stream is created + // subsequently in StartMessage) We should not be warning on an expected error. + // Perhaps there are unexpected consequences of returning early? + NS_ENSURE_SUCCESS(rv, rv); + seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + + int32_t messageIndex = (mCopyState->m_copyingMultipleMessages) ? mCopyState->m_curCopyIndex - 1 : mCopyState->m_curCopyIndex; + NS_ASSERTION(!mCopyState->m_copyingMultipleMessages || messageIndex >= 0, "messageIndex invalid"); + // by the time we get here, m_curCopyIndex is 1 relative because WriteStartOfNewMessage increments it + mCopyState->m_messages->QueryElementAt(messageIndex, NS_GET_IID(nsIMsgDBHdr), + (void **)getter_AddRefs(mCopyState->m_message)); + // The flags of the source message can get changed when it is deleted, so + // save them here. + if (mCopyState->m_message) + mCopyState->m_message->GetFlags(&(mCopyState->m_flags)); + DisplayMoveCopyStatusMsg(); + if (mCopyState->m_listener) + mCopyState->m_listener->OnProgress(mCopyState->m_curCopyIndex, mCopyState->m_totalMsgCount); + // if we're copying more than one message, StartMessage will handle this. + return !mCopyState->m_copyingMultipleMessages ? WriteStartOfNewMessage() : rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CopyData(nsIInputStream *aIStream, int32_t aLength) +{ + //check to make sure we have control of the write. + bool haveSemaphore; + nsresult rv = NS_OK; + + rv = TestSemaphore(static_cast(this), &haveSemaphore); + if (NS_FAILED(rv)) + return rv; + if (!haveSemaphore) + return NS_MSG_FOLDER_BUSY; + + if (!mCopyState) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t readCount; + //allocate one extra byte for '\0' at the end and another extra byte at the + //front to insert a '>' if we have a "From" line + //allocate 2 more for crlf that may be needed for those without crlf at end of file + if ( aLength + mCopyState->m_leftOver + 4 > mCopyState->m_dataBufferSize ) + { + char *newBuffer = (char *) PR_REALLOC(mCopyState->m_dataBuffer, aLength + mCopyState->m_leftOver + 4); + if (!newBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + mCopyState->m_dataBuffer = newBuffer; + mCopyState->m_dataBufferSize = aLength + mCopyState->m_leftOver + 3; + } + + nsCOMPtr seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + + rv = aIStream->Read(mCopyState->m_dataBuffer + mCopyState->m_leftOver + 1, aLength, &readCount); + NS_ENSURE_SUCCESS(rv, rv); + mCopyState->m_leftOver += readCount; + mCopyState->m_dataBuffer[mCopyState->m_leftOver + 1] ='\0'; + char *start = mCopyState->m_dataBuffer + 1; + char *endBuffer = mCopyState->m_dataBuffer + mCopyState->m_leftOver + 1; + + uint32_t lineLength; + uint32_t bytesWritten; + + while (1) + { + char *end = PL_strnpbrk(start, "\r\n", endBuffer - start); + if (!end) + { + mCopyState->m_leftOver -= (start - mCopyState->m_dataBuffer - 1); + // In CopyFileMessage, a complete message is being copied in a single + // call to CopyData, and if it does not have a LINEBREAK at the EOF, + // then end will be null after reading the last line, and we need + // to append the LINEBREAK to the buffer to enable transfer of the last line. + if (mCopyState->m_wholeMsgInStream) + { + end = start + mCopyState->m_leftOver; + memcpy(end, MSG_LINEBREAK "\0", MSG_LINEBREAK_LEN + 1); + } + else + { + memmove (mCopyState->m_dataBuffer + 1, start, mCopyState->m_leftOver); + break; + } + } + + //need to set the linebreak_len each time + uint32_t linebreak_len = 1; //assume CR or LF + if (*end == '\r' && *(end+1) == '\n') + linebreak_len = 2; //CRLF + + if (!mCopyState->m_fromLineSeen) + { + mCopyState->m_fromLineSeen = true; + NS_ASSERTION(strncmp(start, "From ", 5) == 0, + "Fatal ... bad message format\n"); + } + else if (strncmp(start, "From ", 5) == 0) + { + //if we're at the beginning of the buffer, we've reserved a byte to + //insert a '>'. If we're in the middle, we're overwriting the previous + //line ending, but we've already written it to m_fileStream, so it's OK. + *--start = '>'; + } + + lineLength = end - start + linebreak_len; + rv = mCopyState->m_fileStream->Write(start, lineLength, &bytesWritten); + if (bytesWritten != lineLength || NS_FAILED(rv)) + { + ThrowAlertMsg("copyMsgWriteFailed", mCopyState->m_msgWindow); + mCopyState->m_writeFailed = true; + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(start, lineLength); + + start = end + linebreak_len; + if (start >= endBuffer) + { + mCopyState->m_leftOver = 0; + break; + } + } + return rv; +} + +void nsMsgLocalMailFolder::CopyPropertiesToMsgHdr(nsIMsgDBHdr *destHdr, + nsIMsgDBHdr *srcHdr, + bool aIsMove) +{ + nsresult rv; + nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCString dontPreserve; + + // These preferences exist so that extensions can control which properties + // are preserved in the database when a message is moved or copied. All + // properties are preserved except those listed in these preferences + if (aIsMove) + prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove", + getter_Copies(dontPreserve)); + else + prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy", + getter_Copies(dontPreserve)); + + CopyHdrPropertiesWithSkipList(destHdr, srcHdr, dontPreserve); +} + +void +nsMsgLocalMailFolder::CopyHdrPropertiesWithSkipList(nsIMsgDBHdr *destHdr, + nsIMsgDBHdr *srcHdr, + const nsCString &skipList) +{ + nsCOMPtr propertyEnumerator; + nsresult rv = srcHdr->GetPropertyEnumerator(getter_AddRefs(propertyEnumerator)); + NS_ENSURE_SUCCESS_VOID(rv); + + // We'll add spaces at beginning and end so we can search for space-name-space + nsCString dontPreserveEx(NS_LITERAL_CSTRING(" ")); + dontPreserveEx.Append(skipList); + dontPreserveEx.AppendLiteral(" "); + + nsAutoCString property; + nsCString sourceString; + bool hasMore; + while (NS_SUCCEEDED(propertyEnumerator->HasMore(&hasMore)) && hasMore) + { + propertyEnumerator->GetNext(property); + nsAutoCString propertyEx(NS_LITERAL_CSTRING(" ")); + propertyEx.Append(property); + propertyEx.AppendLiteral(" "); + if (dontPreserveEx.Find(propertyEx) != -1) // -1 is not found + continue; + + srcHdr->GetStringProperty(property.get(), getter_Copies(sourceString)); + destHdr->SetStringProperty(property.get(), sourceString.get()); + } + + nsMsgLabelValue label = 0; + srcHdr->GetLabel(&label); + destHdr->SetLabel(label); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::EndCopy(bool aCopySucceeded) +{ + if (!mCopyState) + return NS_OK; + + // we are the destination folder for a move/copy + nsresult rv = aCopySucceeded ? NS_OK : NS_ERROR_FAILURE; + + if (!aCopySucceeded || mCopyState->m_writeFailed) + { + if (mCopyState->m_fileStream) + { + if (mCopyState->m_curDstKey != nsMsgKey_None) + mCopyState->m_msgStore->DiscardNewMessage(mCopyState->m_fileStream, + mCopyState->m_newHdr); + mCopyState->m_fileStream->Close(); + } + + if (!mCopyState->m_isMove) + { + // passing true because the messages that have been successfully + // copied have their corresponding hdrs in place. The message that has + // failed has been truncated so the msf file and berkeley mailbox + // are in sync. + (void) OnCopyCompleted(mCopyState->m_srcSupport, true); + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching + } + return NS_OK; + } + + bool multipleCopiesFinished = (mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount); + + RefPtr localUndoTxn = mCopyState->m_undoMsgTxn; + + 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 = false; + + // flush the copied message. We need a close at the end to get the + // file size and time updated correctly. + // + // These filestream closes are handled inconsistently in the code. In some + // cases, this is done in EndMessage, while in others it is done here in + // EndCopy. When we do the close in EndMessage, we'll set + // mCopyState->m_fileStream to null since it is no longer needed, and detect + // here the null stream so we know that we don't have to close it here. + // + // Similarly, m_parseMsgState->GetNewMsgHdr() returns a null hdr if the hdr + // has already been processed by EndMessage so it is not doubly added here. + + nsCOMPtr seekableStream(do_QueryInterface(mCopyState->m_fileStream)); + if (seekableStream) + { + if (mCopyState->m_dummyEnvelopeNeeded) + { + uint32_t bytesWritten; + seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + mCopyState->m_fileStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(CRLF, MSG_LINEBREAK_LEN); + } + rv = mCopyState->m_msgStore->FinishNewMessage(mCopyState->m_fileStream, + mCopyState->m_newHdr); + if (NS_SUCCEEDED(rv) && mCopyState->m_newHdr) + mCopyState->m_newHdr->GetMessageKey(&mCopyState->m_curDstKey); + if (multipleCopiesFinished) + mCopyState->m_fileStream->Close(); + else + mCopyState->m_fileStream->Flush(); + } + //Copy the header to the new database + if (mCopyState->m_message) + { + // CopyMessages() goes here, and CopyFileMessages() with metadata to save; + nsCOMPtr newHdr; + if (!mCopyState->m_parseMsgState) + { + if (mCopyState->m_destDB) + { + if (mCopyState->m_newHdr) + { + newHdr = mCopyState->m_newHdr; + CopyHdrPropertiesWithSkipList(newHdr, mCopyState->m_message, + NS_LITERAL_CSTRING("storeToken msgOffset")); +// UpdateNewMsgHdr(mCopyState->m_message, newHdr); + // We need to copy more than just what UpdateNewMsgHdr does. In fact, + // I think we want to copy almost every property other than + // storeToken and msgOffset. + mCopyState->m_destDB->AddNewHdrToDB(newHdr, true); + } + else + { + rv = mCopyState->m_destDB->CopyHdrFromExistingHdr(mCopyState->m_curDstKey, + mCopyState->m_message, true, + getter_AddRefs(newHdr)); + } + uint32_t newHdrFlags; + if (newHdr) + { + // turn off offline flag - it's not valid for local mail folders. + newHdr->AndFlags(~nsMsgMessageFlags::Offline, &newHdrFlags); + mCopyState->m_destMessages->AppendElement(newHdr, false); + } + } + // we can do undo with the dest folder db, see bug #198909 + //else + // mCopyState->m_undoMsgTxn = nullptr; //null out the transaction because we can't undo w/o the msg db + } + + // if we plan on allowing undo, (if we have a mCopyState->m_parseMsgState or not) + // we need to save the source and dest keys on the undo txn. + // see bug #179856 for details + bool isImap; + if (NS_SUCCEEDED(rv) && localUndoTxn) { + localUndoTxn->GetSrcIsImap(&isImap); + if (!isImap || !mCopyState->m_copyingMultipleMessages) + { + nsMsgKey aKey; + uint32_t statusOffset; + mCopyState->m_message->GetMessageKey(&aKey); + mCopyState->m_message->GetStatusOffset(&statusOffset); + localUndoTxn->AddSrcKey(aKey); + localUndoTxn->AddSrcStatusOffset(statusOffset); + localUndoTxn->AddDstKey(mCopyState->m_curDstKey); + } + } + } + nsCOMPtr newHdr; + // CopyFileMessage() and CopyMessages() from servers other than mailbox + if (mCopyState->m_parseMsgState) + { + nsCOMPtr msgDb; + mCopyState->m_parseMsgState->FinishHeader(); + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) + { + nsresult result = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr)); + // we need to copy newHdr because mCopyState will get cleared + // in OnCopyCompleted, but we need OnCopyCompleted to know about + // the newHdr, via mCopyState. And we send a notification about newHdr + // after OnCopyCompleted. + mCopyState->m_newHdr = newHdr; + if (NS_SUCCEEDED(result) && newHdr) + { + // Copy message metadata. + if (mCopyState->m_message) + { + // Propagate the new flag on an imap to local folder filter action + // Flags may get changed when deleting the original source message in + // IMAP. We have a copy of the original flags, but parseMsgState has + // already tried to decide what those flags should be. Who to believe? + // Let's only deal here with the flags that might get changed, Read + // and New, and trust upstream code for everything else. + uint32_t readAndNew = nsMsgMessageFlags::New | nsMsgMessageFlags::Read; + uint32_t newFlags; + newHdr->GetFlags(&newFlags); + newHdr->SetFlags( (newFlags & ~readAndNew) | + ((mCopyState->m_flags) & readAndNew)); + + // Copy other message properties. + CopyPropertiesToMsgHdr(newHdr, mCopyState->m_message, mCopyState->m_isMove); + } + msgDb->AddNewHdrToDB(newHdr, true); + if (localUndoTxn) + { + // ** jt - recording the message size for possible undo use; the + // message size is different for pop3 and imap4 messages + uint32_t msgSize; + newHdr->GetMessageSize(&msgSize); + localUndoTxn->AddDstMsgSize(msgSize); + } + + mCopyState->m_destMessages->AppendElement(newHdr, false); + } + // msgDb->SetSummaryValid(true); + // msgDb->Commit(nsMsgDBCommitType::kLargeCommit); + } + else + mCopyState->m_undoMsgTxn = nullptr; //null out the transaction because we can't undo w/o the msg db + + mCopyState->m_parseMsgState->Clear(); + if (mCopyState->m_listener) // CopyFileMessage() only + mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey); + } + + if (!multipleCopiesFinished && !mCopyState->m_copyingMultipleMessages) + { + // CopyMessages() goes here; CopyFileMessage() never gets in here because + // curCopyIndex will always be less than the mCopyState->m_totalMsgCount + nsCOMPtr aSupport = do_QueryElementAt(mCopyState->m_messages, mCopyState->m_curCopyIndex); + rv = CopyMessageTo(aSupport, this, mCopyState->m_msgWindow, mCopyState->m_isMove); + } + else + { + // If we have some headers, then there is a source, so notify itemMoveCopyCompleted. + // If we don't have any headers already, (eg save as draft, send) then notify itemAdded. + // This notification is done after the messages are deleted, so that saving a new draft + // of a message works correctly -- first an itemDeleted is sent for the old draft, then + // an itemAdded for the new draft. + uint32_t numHdrs; + mCopyState->m_messages->GetLength(&numHdrs); + + if (multipleCopiesFinished && numHdrs && !mCopyState->m_isFolder) + { + // we need to send this notification before we delete the source messages, + // because deleting the source messages clears out the src msg db hdr. + nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgsMoveCopyCompleted(mCopyState->m_isMove, + mCopyState->m_messages, + this, mCopyState->m_destMessages); + } + + if (!mCopyState->m_isMove) + { + if (multipleCopiesFinished) + { + nsCOMPtr srcFolder; + srcFolder = do_QueryInterface(mCopyState->m_srcSupport); + if (mCopyState->m_isFolder) + CopyAllSubFolders(srcFolder, nullptr, nullptr); //Copy all subfolders then notify completion + + if (mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn) + { + nsCOMPtr txnMgr; + mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) + txnMgr->DoTransaction(mCopyState->m_undoMsgTxn); + } + + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching + if (srcFolder && !mCopyState->m_isFolder) + { + // I'm not too sure of the proper location of this event. It seems to need to be + // after the EnableNotifications, or the folder counts can be incorrect + // during the mDeleteOrMoveMsgCompletedAtom call. + srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom); + } + (void) OnCopyCompleted(mCopyState->m_srcSupport, true); + } + } + // Send the itemAdded notification in case we didn't send the itemMoveCopyCompleted notification earlier. + // Posting news messages involves this, yet doesn't have the newHdr initialized, so don't send any + // notifications in that case. + if (!numHdrs && newHdr) + { + nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + { + notifier->NotifyMsgAdded(newHdr); + // We do not appear to trigger classification in this case, so let's + // paper over the abyss by just sending the classification notification. + nsCOMPtr oneHeaderArray = + do_CreateInstance(NS_ARRAY_CONTRACTID); + oneHeaderArray->AppendElement(newHdr, false); + notifier->NotifyMsgsClassified(oneHeaderArray, false, false); + // (We do not add the NotReportedClassified processing flag since we + // just reported it!) + } + } + } + return rv; +} + +static bool gGotGlobalPrefs; +static bool gDeleteFromServerOnMove; + +bool nsMsgLocalMailFolder::GetDeleteFromServerOnMove() +{ + if (!gGotGlobalPrefs) + { + nsCOMPtr pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + { + pPrefBranch->GetBoolPref("mail.pop3.deleteFromServerOnMove", &gDeleteFromServerOnMove); + gGotGlobalPrefs = true; + } + } + return gDeleteFromServerOnMove; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::EndMove(bool moveSucceeded) +{ + nsresult rv; + if (!mCopyState) + return NS_OK; + + if (!moveSucceeded || mCopyState->m_writeFailed) + { + //Notify that a completion finished. + nsCOMPtr srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom); + + /* passing true because the messages that have been successfully copied have their corressponding + hdrs in place. The message that has failed has been truncated so the msf file and berkeley mailbox + are in sync*/ + + (void) OnCopyCompleted(mCopyState->m_srcSupport, true); + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/ ); //dest folder doesn't need db batching + return NS_OK; + } + + if (mCopyState && mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount) + { + //Notify that a completion finished. + nsCOMPtr srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr localSrcFolder = do_QueryInterface(srcFolder); + if (localSrcFolder) + { + // if we are the trash and a local msg is being moved to us, mark the source + // for delete from server, if so configured. + if (mFlags & nsMsgFolderFlags::Trash) + { + // if we're deleting on all moves, we'll mark this message for deletion when + // we call DeleteMessages on the source folder. So don't mark it for deletion + // here, in that case. + if (!GetDeleteFromServerOnMove()) + localSrcFolder->MarkMsgsOnPop3Server(mCopyState->m_messages, POP3_DELETE); + } + } + // lets delete these all at once - much faster that way + rv = srcFolder->DeleteMessages(mCopyState->m_messages, mCopyState->m_msgWindow, true, true, nullptr, mCopyState->m_allowUndo); + AutoCompact(mCopyState->m_msgWindow); + + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching + // I'm not too sure of the proper location of this event. It seems to need to be + // after the EnableNotifications, or the folder counts can be incorrect + // during the mDeleteOrMoveMsgCompletedAtom call. + srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom); + + if (NS_SUCCEEDED(rv) && mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn) + { + nsCOMPtr txnMgr; + mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) + txnMgr->DoTransaction(mCopyState->m_undoMsgTxn); + } + (void) OnCopyCompleted(mCopyState->m_srcSupport, NS_SUCCEEDED(rv) ? true : false); //clear the copy state so that the next message from a different folder can be move + } + + return NS_OK; + +} + +// this is the beginning of the next message copied +NS_IMETHODIMP nsMsgLocalMailFolder::StartMessage() +{ + // We get crashes that we don't understand (bug 284876), so stupidly prevent that. + NS_ENSURE_ARG_POINTER(mCopyState); + nsresult rv = InitCopyMsgHdrAndFileStream(); + NS_ENSURE_SUCCESS(rv, rv); + return WriteStartOfNewMessage(); +} + +// just finished the current message. +NS_IMETHODIMP nsMsgLocalMailFolder::EndMessage(nsMsgKey key) +{ + NS_ENSURE_ARG_POINTER(mCopyState); + + RefPtr localUndoTxn = mCopyState->m_undoMsgTxn; + nsCOMPtr msgWindow; + nsresult rv; + + if (localUndoTxn) + { + localUndoTxn->GetMsgWindow(getter_AddRefs(msgWindow)); + localUndoTxn->AddSrcKey(key); + localUndoTxn->AddDstKey(mCopyState->m_curDstKey); + } + + // I think this is always true for online to offline copy + mCopyState->m_dummyEnvelopeNeeded = true; + nsCOMPtr seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + uint32_t bytesWritten; + mCopyState->m_fileStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(CRLF, MSG_LINEBREAK_LEN); + + rv = mCopyState->m_msgStore->FinishNewMessage(mCopyState->m_fileStream, + mCopyState->m_newHdr); + mCopyState->m_fileStream->Close(); + mCopyState->m_fileStream = nullptr; // all done with the file stream + + // CopyFileMessage() and CopyMessages() from servers other than mailbox + if (mCopyState->m_parseMsgState) + { + nsCOMPtr msgDb; + nsCOMPtr newHdr; + + mCopyState->m_parseMsgState->FinishHeader(); + + rv = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr)); + if (NS_SUCCEEDED(rv) && newHdr) + { + nsCOMPtr srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr srcDB; + srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + if (srcDB) + { + nsCOMPtr srcMsgHdr; + srcDB->GetMsgHdrForKey(key, getter_AddRefs(srcMsgHdr)); + if (srcMsgHdr) + CopyPropertiesToMsgHdr(newHdr, srcMsgHdr, mCopyState->m_isMove); + } + rv = GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (NS_SUCCEEDED(rv) && msgDb) + { + msgDb->AddNewHdrToDB(newHdr, true); + if (localUndoTxn) + { + // ** jt - recording the message size for possible undo use; the + // message size is different for pop3 and imap4 messages + uint32_t msgSize; + newHdr->GetMessageSize(&msgSize); + localUndoTxn->AddDstMsgSize(msgSize); + } + } + else + mCopyState->m_undoMsgTxn = nullptr; //null out the transaction because we can't undo w/o the msg db + } + mCopyState->m_parseMsgState->Clear(); + + if (mCopyState->m_listener) // CopyFileMessage() only + mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey); + } + + if (mCopyState->m_fileStream) + mCopyState->m_fileStream->Flush(); + return NS_OK; +} + + +nsresult nsMsgLocalMailFolder::CopyMessagesTo(nsIArray *messages, nsTArray &keyArray, + nsIMsgWindow *aMsgWindow, nsIMsgFolder *dstFolder, + bool isMove) +{ + if (!mCopyState) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + + nsCOMPtr copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr copyListener(do_QueryInterface(dstFolder, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + nsCOMPtr srcFolder(do_QueryInterface(mCopyState->m_srcSupport, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + rv = copyStreamListener->Init(srcFolder, copyListener, nullptr); + if (NS_FAILED(rv)) + return rv; + + if (!mCopyState->m_messageService) + { + nsCString uri; + srcFolder->GetURI(uri); + rv = GetMessageServiceFromURI(uri, getter_AddRefs(mCopyState->m_messageService)); + } + + if (NS_SUCCEEDED(rv) && mCopyState->m_messageService) + { + nsCOMPtr streamListener(do_QueryInterface(copyStreamListener, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + mCopyState->m_curCopyIndex = 0; + // we need to kick off the first message - subsequent messages + // are kicked off by nsMailboxProtocol when it finishes a message + // before starting the next message. Only do this if the source folder + // is a local folder, however. IMAP will handle calling StartMessage for + // each message that gets downloaded, and news doesn't go through here + // because news only downloads one message at a time, and this routine + // is for multiple message copy. + nsCOMPtr srcLocalFolder = do_QueryInterface(srcFolder); + if (srcLocalFolder) + StartMessage(); + nsCOMPtr dummyNull; + rv = mCopyState->m_messageService->CopyMessages(keyArray.Length(), + keyArray.Elements(), + srcFolder, streamListener, + isMove, nullptr, aMsgWindow, + getter_AddRefs(dummyNull)); + } + return rv; +} + +nsresult nsMsgLocalMailFolder::CopyMessageTo(nsISupports *message, + nsIMsgFolder *dstFolder /* dst same as "this" */, + nsIMsgWindow *aMsgWindow, + bool isMove) +{ + if (!mCopyState) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + nsCOMPtr msgHdr(do_QueryInterface(message, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + mCopyState->m_message = do_QueryInterface(msgHdr, &rv); + + nsCOMPtr srcFolder(do_QueryInterface(mCopyState->m_srcSupport, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + nsCString uri; + srcFolder->GetUriForMsg(msgHdr, uri); + + nsCOMPtr copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr copyListener(do_QueryInterface(dstFolder, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + rv = copyStreamListener->Init(srcFolder, copyListener, nullptr); + if (NS_FAILED(rv)) + return rv; + + if (!mCopyState->m_messageService) + rv = GetMessageServiceFromURI(uri, getter_AddRefs(mCopyState->m_messageService)); + + if (NS_SUCCEEDED(rv) && mCopyState->m_messageService) + { + nsCOMPtr streamListener(do_QueryInterface(copyStreamListener, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + nsCOMPtr dummyNull; + rv = mCopyState->m_messageService->CopyMessage(uri.get(), streamListener, isMove, nullptr, aMsgWindow, + getter_AddRefs(dummyNull)); + } + return rv; +} + +// A message is being deleted from a POP3 mail file, so check and see if we have the message +// being deleted in the server. If so, then we need to remove the message from the server as well. +// We have saved the UIDL of the message in the popstate.dat file and we must match this uidl, so +// read the message headers and see if we have it, then mark the message for deletion from the server. +// The next time we look at mail the message will be deleted from the server. + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkMsgsOnPop3Server(nsIArray *aMessages, int32_t aMark) +{ + nsLocalFolderScanState folderScanState; + nsCOMPtr curFolderPop3MailServer; + nsCOMArray pop3Servers; // servers with msgs deleted... + + nsCOMPtr incomingServer; + nsresult rv = GetServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // I wonder if we should run through the pop3 accounts and see if any of them have + // leave on server set. If not, we could short-circuit some of this. + + curFolderPop3MailServer = do_QueryInterface(incomingServer, &rv); + rv = GetFolderScanState(&folderScanState); + NS_ENSURE_SUCCESS(rv,rv); + + uint32_t srcCount; + aMessages->GetLength(&srcCount); + + // Filter delete requests are always honored, others are subject + // to the deleteMailLeftOnServer preference. + int32_t mark; + mark = (aMark == POP3_FORCE_DEL) ? POP3_DELETE : aMark; + + for (uint32_t i = 0; i < srcCount; i++) + { + /* get uidl for this message */ + nsCOMPtr msgDBHdr (do_QueryElementAt(aMessages, i, &rv)); + + uint32_t flags = 0; + + if (msgDBHdr) + { + msgDBHdr->GetFlags(&flags); + nsCOMPtr msgPop3Server = curFolderPop3MailServer; + bool leaveOnServer = false; + bool deleteMailLeftOnServer = false; + // set up defaults, in case there's no x-mozilla-account header + if (curFolderPop3MailServer) + { + curFolderPop3MailServer->GetDeleteMailLeftOnServer(&deleteMailLeftOnServer); + curFolderPop3MailServer->GetLeaveMessagesOnServer(&leaveOnServer); + } + + rv = GetUidlFromFolder(&folderScanState, msgDBHdr); + if (!NS_SUCCEEDED(rv)) + continue; + + if (folderScanState.m_uidl) + { + nsCOMPtr account; + rv = accountManager->GetAccount(folderScanState.m_accountKey, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) + { + account->GetIncomingServer(getter_AddRefs(incomingServer)); + nsCOMPtr curMsgPop3MailServer = do_QueryInterface(incomingServer); + if (curMsgPop3MailServer) + { + msgPop3Server = curMsgPop3MailServer; + msgPop3Server->GetDeleteMailLeftOnServer(&deleteMailLeftOnServer); + msgPop3Server->GetLeaveMessagesOnServer(&leaveOnServer); + } + } + } + // ignore this header if not partial and leaveOnServer not set... + // or if we can't find the pop3 server. + if (!msgPop3Server || (! (flags & nsMsgMessageFlags::Partial) && !leaveOnServer)) + continue; + // if marking deleted, ignore header if we're not deleting from + // server when deleting locally. + if (aMark == POP3_DELETE && leaveOnServer && !deleteMailLeftOnServer) + continue; + if (folderScanState.m_uidl) + { + msgPop3Server->AddUidlToMark(folderScanState.m_uidl, mark); + // remember this pop server in list of servers with msgs deleted + if (pop3Servers.IndexOfObject(msgPop3Server) == -1) + pop3Servers.AppendObject(msgPop3Server); + } + } + } + if (folderScanState.m_inputStream) + folderScanState.m_inputStream->Close(); + // need to do this for all pop3 mail servers that had messages deleted. + uint32_t serverCount = pop3Servers.Count(); + for (uint32_t index = 0; index < serverCount; index++) + pop3Servers[index]->MarkMessages(); + + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::DeleteDownloadMsg(nsIMsgDBHdr *aMsgHdr, bool *aDoSelect) +{ + uint32_t numMsgs; + char *newMsgId; + + // This method is only invoked thru DownloadMessagesForOffline() + if (mDownloadState != DOWNLOAD_STATE_NONE) + { + // We only remember the first key, no matter how many + // messages were originally selected. + if (mDownloadState == DOWNLOAD_STATE_INITED) + { + aMsgHdr->GetMessageKey(&mDownloadSelectKey); + mDownloadState = DOWNLOAD_STATE_GOTMSG; + } + + aMsgHdr->GetMessageId(&newMsgId); + + // Walk through all the selected headers, looking for a matching + // Message-ID. + numMsgs = mDownloadMessages.Length(); + for (uint32_t i = 0; i < numMsgs; i++) + { + nsresult rv; + nsCOMPtr msgDBHdr = mDownloadMessages[i]; + char *oldMsgId = nullptr; + msgDBHdr->GetMessageId(&oldMsgId); + + // Delete the first match and remove it from the array + if (!PL_strcmp(newMsgId, oldMsgId)) + { + rv = GetDatabase(); + if (!mDatabase) + return rv; + + UpdateNewMsgHdr(msgDBHdr, aMsgHdr); + +#if DOWNLOAD_NOTIFY_STYLE == DOWNLOAD_NOTIFY_LAST + msgDBHdr->GetMessageKey(&mDownloadOldKey); + msgDBHdr->GetThreadParent(&mDownloadOldParent); + msgDBHdr->GetFlags(&mDownloadOldFlags); + mDatabase->DeleteHeader(msgDBHdr, nullptr, false, false); + // Tell caller we want to select this message + if (aDoSelect) + *aDoSelect = true; +#else + mDatabase->DeleteHeader(msgDBHdr, nullptr, false, true); + // Tell caller we want to select this message + if (aDoSelect && mDownloadState == DOWNLOAD_STATE_GOTMSG) + *aDoSelect = true; +#endif + mDownloadMessages.RemoveElementAt(i); + break; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::SelectDownloadMsg() +{ +#if DOWNLOAD_NOTIFY_STYLE == DOWNLOAD_NOTIFY_LAST + if (mDownloadState >= DOWNLOAD_STATE_GOTMSG) + { + nsresult rv = GetDatabase(); + if (!mDatabase) + return rv; + } + mDatabase->NotifyKeyDeletedAll(mDownloadOldKey, mDownloadOldParent, mDownloadOldFlags, nullptr); + } +#endif + + if (mDownloadState == DOWNLOAD_STATE_GOTMSG && mDownloadWindow) + { + nsAutoCString newuri; + nsBuildLocalMessageURI(mBaseMessageURI.get(), mDownloadSelectKey, newuri); + nsCOMPtr windowCommands; + mDownloadWindow->GetWindowCommands(getter_AddRefs(windowCommands)); + if (windowCommands) + windowCommands->SelectMessage(newuri); + mDownloadState = DOWNLOAD_STATE_DIDSEL; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::DownloadMessagesForOffline(nsIArray *aMessages, nsIMsgWindow *aWindow) +{ + if (mDownloadState != DOWNLOAD_STATE_NONE) + return NS_ERROR_FAILURE; // already has a download in progress + + // We're starting a download... + mDownloadState = DOWNLOAD_STATE_INITED; + + MarkMsgsOnPop3Server(aMessages, POP3_FETCH_BODY); + + // Pull out all the PARTIAL messages into a new array + uint32_t srcCount; + aMessages->GetLength(&srcCount); + + nsresult rv; + for (uint32_t i = 0; i < srcCount; i++) + { + nsCOMPtr msgDBHdr (do_QueryElementAt(aMessages, i, &rv)); + if (NS_SUCCEEDED(rv)) + { + uint32_t flags = 0; + msgDBHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) + mDownloadMessages.AppendElement(msgDBHdr); + } + } + mDownloadWindow = aWindow; + + nsCOMPtr server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr localMailServer = do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + return localMailServer->GetNewMail(aWindow, this, this, nullptr); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::NotifyDelete() +{ + NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom); + return NS_OK; +} + +// TODO: once we move certain code into the IncomingServer (search for TODO) +// this method will go away. +// sometimes this gets called when we don't have the server yet, so +// that's why we're not calling GetServer() +NS_IMETHODIMP +nsMsgLocalMailFolder::GetIncomingServerType(nsACString& aServerType) +{ + nsresult rv; + if (mType.IsEmpty()) + { + nsCOMPtr url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = url->SetSpec(mURI); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr server; + // try "none" first + url->SetScheme(NS_LITERAL_CSTRING("none")); + rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + mType.AssignLiteral("none"); + else + { + // next try "pop3" + url->SetScheme(NS_LITERAL_CSTRING("pop3")); + rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + mType.AssignLiteral("pop3"); + else + { + // next try "rss" + url->SetScheme(NS_LITERAL_CSTRING("rss")); + rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + mType.AssignLiteral("rss"); + else + { +#ifdef HAVE_MOVEMAIL + // next try "movemail" + url->SetScheme(NS_LITERAL_CSTRING("movemail")); + rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + mType.AssignLiteral("movemail"); +#endif /* HAVE_MOVEMAIL */ + } + } + } + } + aServerType = mType; + return NS_OK; +} + +nsresult nsMsgLocalMailFolder::CreateBaseMessageURI(const nsACString& aURI) +{ + return nsCreateLocalBaseMessageURI(aURI, mBaseMessageURI); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::OnStartRunningUrl(nsIURI * aUrl) +{ + nsresult rv; + nsCOMPtr popurl = do_QueryInterface(aUrl, &rv); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString aSpec; + rv = aUrl->GetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (strstr(aSpec.get(), "uidl=")) + { + nsCOMPtr popsink; + rv = popurl->GetPop3Sink(getter_AddRefs(popsink)); + if (NS_SUCCEEDED(rv)) + { + popsink->SetBaseMessageUri(mBaseMessageURI.get()); + nsCString messageuri; + popurl->GetMessageUri(getter_Copies(messageuri)); + popsink->SetOrigMessageUri(messageuri); + } + } + } + return nsMsgDBFolder::OnStartRunningUrl(aUrl); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) +{ + // If we just finished a DownloadMessages call, reset... + if (mDownloadState != DOWNLOAD_STATE_NONE) + { + mDownloadState = DOWNLOAD_STATE_NONE; + mDownloadMessages.Clear(); + mDownloadWindow = nullptr; + return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode); + } + + nsresult rv; + if (NS_SUCCEEDED(aExitCode)) + { + nsCOMPtr mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + nsAutoCString aSpec; + if (aUrl) { + rv = aUrl->GetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (strstr(aSpec.get(), "uidl=")) + { + nsCOMPtr popurl = do_QueryInterface(aUrl, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCString messageuri; + rv = popurl->GetMessageUri(getter_Copies(messageuri)); + if (NS_SUCCEEDED(rv)) + { + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgDBHdr; + rv = GetMsgDBHdrFromURI(messageuri.get(), getter_AddRefs(msgDBHdr)); + if (NS_SUCCEEDED(rv)) + { + GetDatabase(); + if (mDatabase) + mDatabase->DeleteHeader(msgDBHdr, nullptr, true, true); + } + + nsCOMPtr pop3sink; + nsCString newMessageUri; + rv = popurl->GetPop3Sink(getter_AddRefs(pop3sink)); + if (NS_SUCCEEDED(rv)) + { + pop3sink->GetMessageUri(getter_Copies(newMessageUri)); + if (msgWindow) + { + nsCOMPtr windowCommands; + msgWindow->GetWindowCommands(getter_AddRefs(windowCommands)); + if (windowCommands) + windowCommands->SelectMessage(newMessageUri); + } + } + } + } + } + + if (mFlags & nsMsgFolderFlags::Inbox) + { + if (mDatabase && mCheckForNewMessagesAfterParsing) + { + bool valid = false; // GetSummaryValid may return without setting valid. + mDatabase->GetSummaryValid(&valid); + if (valid && msgWindow) + rv = GetNewMessages(msgWindow, nullptr); + mCheckForNewMessagesAfterParsing = false; + } + } + } + + if (m_parsingFolder) + { + // Clear this before calling OnStopRunningUrl, in case the url listener + // tries to get the database. + m_parsingFolder = false; + + // TODO: Updating the size should be pushed down into the msg store backend + // so that the size is recalculated as part of parsing the folder data + // (important for maildir), once GetSizeOnDisk is pushed into the msgStores + // (bug 1032360). + (void)RefreshSizeOnDisk(); + + // Update the summary totals so the front end will + // show the right thing. + UpdateSummaryTotals(true); + + if (mReparseListener) + { + nsCOMPtr saveReparseListener = mReparseListener; + mReparseListener = nullptr; + saveReparseListener->OnStopRunningUrl(aUrl, aExitCode); + } + } + if (mFlags & nsMsgFolderFlags::Inbox) + { + // if we are the inbox and running pop url + nsCOMPtr popurl = do_QueryInterface(aUrl, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr server; + GetServer(getter_AddRefs(server)); + // this is the deferred to account, in the global inbox case + if (server) + server->SetPerformingBiff(false); //biff is over + } + } + return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode); +} + +nsresult nsMsgLocalMailFolder::DisplayMoveCopyStatusMsg() +{ + nsresult rv = NS_OK; + if (mCopyState) + { + if (!mCopyState->m_statusFeedback) + { + // get msgWindow from undo txn + nsCOMPtr msgWindow; + if (mCopyState->m_undoMsgTxn) + mCopyState->m_undoMsgTxn->GetMsgWindow(getter_AddRefs(msgWindow)); + if (!msgWindow) + return NS_OK; // not a fatal error. + + msgWindow->GetStatusFeedback(getter_AddRefs(mCopyState->m_statusFeedback)); + } + + if (!mCopyState->m_stringBundle) + { + nsCOMPtr bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + rv = bundleService->CreateBundle("chrome://messenger/locale/localMsgs.properties", getter_AddRefs(mCopyState->m_stringBundle)); + NS_ENSURE_SUCCESS(rv, rv); + } + if (mCopyState->m_statusFeedback && mCopyState->m_stringBundle) + { + nsString folderName; + GetName(folderName); + nsAutoString numMsgSoFarString; + numMsgSoFarString.AppendInt((mCopyState->m_copyingMultipleMessages) ? mCopyState->m_curCopyIndex : 1); + + nsAutoString totalMessagesString; + totalMessagesString.AppendInt(mCopyState->m_totalMsgCount); + nsString finalString; + const char16_t * stringArray[] = { numMsgSoFarString.get(), totalMessagesString.get(), folderName.get() }; + rv = mCopyState->m_stringBundle->FormatStringFromName( + (mCopyState->m_isMove) ? + u"movingMessagesStatus" : + u"copyingMessagesStatus", + stringArray, 3, getter_Copies(finalString)); + int64_t nowMS = PR_IntervalToMilliseconds(PR_IntervalNow()); + + // only update status/progress every half second + if (nowMS - mCopyState->m_lastProgressTime < 500 && + mCopyState->m_curCopyIndex < mCopyState->m_totalMsgCount) + return NS_OK; + + mCopyState->m_lastProgressTime = nowMS; + mCopyState->m_statusFeedback->ShowStatusString(finalString); + mCopyState->m_statusFeedback->ShowProgress(mCopyState->m_curCopyIndex * 100 / mCopyState->m_totalMsgCount); + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::SetFlagsOnDefaultMailboxes(uint32_t flags) +{ + if (flags & nsMsgFolderFlags::Inbox) + setSubfolderFlag(NS_LITERAL_STRING("Inbox"), nsMsgFolderFlags::Inbox); + + if (flags & nsMsgFolderFlags::SentMail) + setSubfolderFlag(NS_LITERAL_STRING("Sent"), nsMsgFolderFlags::SentMail); + + if (flags & nsMsgFolderFlags::Drafts) + setSubfolderFlag(NS_LITERAL_STRING("Drafts"), nsMsgFolderFlags::Drafts); + + if (flags & nsMsgFolderFlags::Templates) + setSubfolderFlag(NS_LITERAL_STRING("Templates"), nsMsgFolderFlags::Templates); + + if (flags & nsMsgFolderFlags::Trash) + setSubfolderFlag(NS_LITERAL_STRING("Trash"), nsMsgFolderFlags::Trash); + + if (flags & nsMsgFolderFlags::Queue) + setSubfolderFlag(NS_LITERAL_STRING("Unsent Messages"), nsMsgFolderFlags::Queue); + + if (flags & nsMsgFolderFlags::Junk) + setSubfolderFlag(NS_LITERAL_STRING("Junk"), nsMsgFolderFlags::Junk); + + if (flags & nsMsgFolderFlags::Archive) + setSubfolderFlag(NS_LITERAL_STRING("Archives"), nsMsgFolderFlags::Archive); + + return NS_OK; +} + +nsresult +nsMsgLocalMailFolder::setSubfolderFlag(const nsAString& aFolderName, uint32_t flags) +{ + // FindSubFolder() expects the folder name to be escaped + // see bug #192043 + nsAutoCString escapedFolderName; + nsresult rv = NS_MsgEscapeEncodeURLPath(aFolderName, escapedFolderName); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr msgFolder; + rv = FindSubFolder(escapedFolderName, getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + // we only want to do this if the folder *really* exists, + // so check if it has a parent. Otherwise, we'll create the + // .msf file when we don't want to. + nsCOMPtr parent; + msgFolder->GetParent(getter_AddRefs(parent)); + if (!parent) + return NS_ERROR_FAILURE; + + rv = msgFolder->SetFlag(flags); + NS_ENSURE_SUCCESS(rv, rv); + return msgFolder->SetPrettyName(aFolderName); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetCheckForNewMessagesAfterParsing(bool *aCheckForNewMessagesAfterParsing) +{ + NS_ENSURE_ARG_POINTER(aCheckForNewMessagesAfterParsing); + *aCheckForNewMessagesAfterParsing = mCheckForNewMessagesAfterParsing; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::SetCheckForNewMessagesAfterParsing(bool aCheckForNewMessagesAfterParsing) +{ + mCheckForNewMessagesAfterParsing = aCheckForNewMessagesAfterParsing; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::NotifyCompactCompleted() +{ + mExpungedBytes = 0; + m_newMsgs.Clear(); // if compacted, m_newMsgs probably aren't valid. + // if compacted, processing flags probably also aren't valid. + ClearProcessingFlags(); + (void) RefreshSizeOnDisk(); + (void) CloseDBIfFolderNotOpen(); + nsCOMPtr compactCompletedAtom; + compactCompletedAtom = MsgGetAtom("CompactCompleted"); + NotifyFolderEvent(compactCompletedAtom); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Shutdown(bool shutdownChildren) +{ + mInitialized = false; + return nsMsgDBFolder::Shutdown(shutdownChildren); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::OnMessageClassified(const char *aMsgURI, + nsMsgJunkStatus aClassification, + uint32_t aJunkPercent) + +{ + nsCOMPtr server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr spamSettings; + rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString spamFolderURI; + rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI)); + NS_ENSURE_SUCCESS(rv,rv); + + if (aMsgURI) // not end of batch + { + nsCOMPtr msgHdr; + rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgKey msgKey; + rv = msgHdr->GetMessageKey(&msgKey); + NS_ENSURE_SUCCESS(rv, rv); + + // check if this message needs junk classification + uint32_t processingFlags; + GetProcessingFlags(msgKey, &processingFlags); + + if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) + { + nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification, aJunkPercent); + + if (aClassification == nsIJunkMailPlugin::JUNK) + { + bool willMoveMessage = false; + + // don't do the move when we are opening up + // the junk mail folder or the trash folder + // or when manually classifying messages in those folders + if (!(mFlags & nsMsgFolderFlags::Junk || mFlags & nsMsgFolderFlags::Trash)) + { + bool moveOnSpam = false; + rv = spamSettings->GetMoveOnSpam(&moveOnSpam); + NS_ENSURE_SUCCESS(rv,rv); + if (moveOnSpam) + { + nsCOMPtr folder; + rv = GetExistingFolder(spamFolderURI, getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv) && folder) + { + rv = folder->SetFlag(nsMsgFolderFlags::Junk); + NS_ENSURE_SUCCESS(rv,rv); + mSpamKeysToMove.AppendElement(msgKey); + willMoveMessage = true; + } + else + { + // XXX TODO + // JUNK MAIL RELATED + // the listener should do + // rv = folder->SetFlag(nsMsgFolderFlags::Junk); + // NS_ENSURE_SUCCESS(rv,rv); + // mSpamKeysToMove.AppendElement(msgKey); + // willMoveMessage = true; + rv = GetOrCreateFolder(spamFolderURI, nullptr /* aListener */); + NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateFolder failed"); + } + } + } + rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage); + NS_ENSURE_SUCCESS(rv,rv); + } + } + } + + else // end of batch + { + // Parent will apply post bayes filters. + nsMsgDBFolder::OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0); + nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID)); + if (!mSpamKeysToMove.IsEmpty()) + { + nsCOMPtr folder; + if (!spamFolderURI.IsEmpty()) + rv = GetExistingFolder(spamFolderURI, getter_AddRefs(folder)); + for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length(); keyIndex++) + { + // If an upstream filter moved this message, don't move it here. + nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex); + nsMsgProcessingFlagType processingFlags; + GetProcessingFlags(msgKey, &processingFlags); + if (folder && !(processingFlags & nsMsgProcessingFlags::FilterToMove)) + { + nsCOMPtr mailHdr; + rv = GetMessageHeader(msgKey, getter_AddRefs(mailHdr)); + if (NS_SUCCEEDED(rv) && mailHdr) + messages->AppendElement(mailHdr, false); + } + else + { + // We don't need the processing flag any more. + AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove); + } + } + + if (folder) + { + nsCOMPtr copySvc = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = copySvc->CopyMessages(this, messages, folder, true, + /*nsIMsgCopyServiceListener* listener*/ nullptr, nullptr, false /*allowUndo*/); + NS_ASSERTION(NS_SUCCEEDED(rv), "CopyMessages failed"); + if (NS_FAILED(rv)) + { + nsAutoCString logMsg("failed to copy junk messages to junk folder rv = "); + logMsg.AppendInt(static_cast(rv), 16); + spamSettings->LogJunkString(logMsg.get()); + } + } + } + int32_t numNewMessages; + GetNumNewMessages(false, &numNewMessages); + uint32_t length; + messages->GetLength(&length); + SetNumNewMessages(numNewMessages - length); + mSpamKeysToMove.Clear(); + // check if this is the inbox first... + if (mFlags & nsMsgFolderFlags::Inbox) + PerformBiffNotifications(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetFolderScanState(nsLocalFolderScanState *aState) +{ + NS_ENSURE_ARG_POINTER(aState); + + nsresult rv = GetMsgStore(getter_AddRefs(aState->m_msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + aState->m_uidl = nullptr; + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetUidlFromFolder(nsLocalFolderScanState *aState, nsIMsgDBHdr *aMsgDBHdr) +{ + bool more = false; + uint32_t size = 0, len = 0; + const char *accountKey = nullptr; + nsresult rv = GetMsgInputStream(aMsgDBHdr, &aState->m_streamReusable, + getter_AddRefs(aState->m_inputStream)); + aState->m_seekableStream = do_QueryInterface(aState->m_inputStream); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoPtr > lineBuffer(new nsLineBuffer); + NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY); + + aState->m_uidl = nullptr; + + aMsgDBHdr->GetMessageSize(&len); + while (len > 0) + { + rv = NS_ReadLine(aState->m_inputStream.get(), lineBuffer.get(), aState->m_header, &more); + if (NS_SUCCEEDED(rv)) + { + size = aState->m_header.Length(); + if (!size) + break; + // this isn't quite right - need to account for line endings + len -= size; + // account key header will always be before X_UIDL header + if (!accountKey) + { + accountKey = strstr(aState->m_header.get(), HEADER_X_MOZILLA_ACCOUNT_KEY); + if (accountKey) + { + accountKey += strlen(HEADER_X_MOZILLA_ACCOUNT_KEY) + 2; + aState->m_accountKey = accountKey; + } + } + else + { + aState->m_uidl = strstr(aState->m_header.get(), X_UIDL); + if (aState->m_uidl) + { + aState->m_uidl += X_UIDL_LEN + 2; // skip UIDL: header + break; + } + } + } + } + if (!aState->m_streamReusable) + { + aState->m_inputStream->Close(); + aState->m_inputStream = nullptr; + } + lineBuffer = nullptr; + return rv; +} + +/** + * Adds a message to the end of the folder, parsing it as it goes, and + * applying filters, if applicable. + */ +NS_IMETHODIMP +nsMsgLocalMailFolder::AddMessage(const char *aMessage, nsIMsgDBHdr **aHdr) +{ + const char *aMessages[] = {aMessage}; + nsCOMPtr hdrs; + nsresult rv = AddMessageBatch(1, aMessages, getter_AddRefs(hdrs)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr hdr(do_QueryElementAt(hdrs, 0, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + hdr.forget(aHdr); + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::AddMessageBatch(uint32_t aMessageCount, + const char **aMessages, + nsIArray **aHdrArray) +{ + NS_ENSURE_ARG_POINTER(aHdrArray); + + nsCOMPtr server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr msgStore; + nsCOMPtr outFileStream; + nsCOMPtr newHdr; + + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isLocked; + + GetLocked(&isLocked); + if (isLocked) + return NS_MSG_FOLDER_BUSY; + + AcquireSemaphore(static_cast(this)); + + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr hdrArray = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < aMessageCount; i++) + { + RefPtr newMailParser = new nsParseNewMailState; + NS_ENSURE_TRUE(newMailParser, NS_ERROR_OUT_OF_MEMORY); + if (!mGettingNewMessages) + newMailParser->DisableFilters(); + bool reusable; + rv = msgStore->GetNewMsgOutputStream(this, getter_AddRefs(newHdr), + &reusable, + getter_AddRefs(outFileStream)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get a msgWindow. Proceed without one, but filter actions to imap folders + // will silently fail if not signed in and no window for a prompt. + nsCOMPtr msgWindow; + nsCOMPtr mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + + rv = newMailParser->Init(rootFolder, this, + msgWindow, newHdr, outFileStream); + + uint32_t bytesWritten, messageLen = strlen(aMessages[i]); + outFileStream->Write(aMessages[i], messageLen, &bytesWritten); + newMailParser->BufferInput(aMessages[i], messageLen); + + msgStore->FinishNewMessage(outFileStream, newHdr); + outFileStream->Close(); + outFileStream = nullptr; + newMailParser->OnStopRequest(nullptr, nullptr, NS_OK); + newMailParser->EndMsgDownload(); + hdrArray->AppendElement(newHdr, false); + } + NS_ADDREF(*aHdrArray = hdrArray); + } + ReleaseSemaphore(static_cast(this)); + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::WarnIfLocalFileTooBig(nsIMsgWindow *aWindow, + int64_t aSpaceRequested, + bool *aTooBig) +{ + NS_ENSURE_ARG_POINTER(aTooBig); + + *aTooBig = true; + nsCOMPtr msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + bool spaceAvailable = false; + // check if we have a reasonable amount of space left + rv = msgStore->HasSpaceAvailable(this, aSpaceRequested, &spaceAvailable); + if (NS_SUCCEEDED(rv) && spaceAvailable) { + *aTooBig = false; + } else if (rv == NS_ERROR_FILE_TOO_BIG) { + ThrowAlertMsg("mailboxTooLarge", aWindow); + } else { + ThrowAlertMsg("outOfDiskSpace", aWindow); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys, + bool aLocalOnly, nsIUrlListener *aUrlListener, + bool *aAsyncResults) +{ + NS_ENSURE_ARG_POINTER(aKeysToFetch); + NS_ENSURE_ARG_POINTER(aAsyncResults); + + *aAsyncResults = false; + nsCOMPtr inputStream; + nsCOMPtr msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < aNumKeys; i++) + { + nsCOMPtr msgHdr; + nsCString prevBody; + rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + // ignore messages that already have a preview body. + msgHdr->GetStringProperty("preview", getter_Copies(prevBody)); + if (!prevBody.IsEmpty()) + continue; + + bool reusable; + rv = GetMsgInputStream(msgHdr, &reusable, getter_AddRefs(inputStream)); + NS_ENSURE_SUCCESS(rv,rv); + rv = GetMsgPreviewTextFromStream(msgHdr, inputStream); + } + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords) +{ + return ChangeKeywordForMessages(aMessages, aKeywords, true /* add */); +} +nsresult nsMsgLocalMailFolder::ChangeKeywordForMessages(nsIArray *aMessages, const nsACString& aKeywords, bool add) +{ + nsresult rv = (add) ? nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords) + : nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords); + + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgStore; + GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeKeywords(aMessages, aKeywords, add); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords) +{ + return ChangeKeywordForMessages(aMessages, aKeywords, false /* remove */); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::UpdateNewMsgHdr(nsIMsgDBHdr* aOldHdr, nsIMsgDBHdr* aNewHdr) +{ + NS_ENSURE_ARG_POINTER(aOldHdr); + NS_ENSURE_ARG_POINTER(aNewHdr); + // Preserve any properties set on the message. + CopyPropertiesToMsgHdr(aNewHdr, aOldHdr, true); + + // Preserve keywords manually, since they are set as don't preserve. + nsCString keywordString; + aOldHdr->GetStringProperty("keywords", getter_Copies(keywordString)); + aNewHdr->SetStringProperty("keywords", keywordString.get()); + + // If the junk score was set by the plugin, remove junkscore to force a new + // junk analysis, this time using the body. + nsCString junkScoreOrigin; + aOldHdr->GetStringProperty("junkscoreorigin", getter_Copies(junkScoreOrigin)); + if (junkScoreOrigin.EqualsLiteral("plugin")) + aNewHdr->SetStringProperty("junkscore", ""); + + return NS_OK; +} -- cgit v1.2.3