Source code

Revision control

Other Tools

/* -*- 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 "nsITransactionManager.h"
#include "nsParseMailbox.h"
#include "nsIMsgAccountManager.h"
#include "nsIMsgWindow.h"
#include "nsCOMPtr.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 "nsString.h"
#include "nsIMsgFolderCacheElement.h"
#include "nsUnicharUtils.h"
#include "nsMsgUtils.h"
#include "nsICopyMessageStreamListener.h"
#include "nsIMsgCopyService.h"
#include "nsMsgTxn.h"
#include "nsIMessenger.h"
#include "nsMsgBaseCID.h"
#include "nsNativeCharsetUtils.h"
#include "nsIDocShell.h"
#include "nsIPrompt.h"
#include "nsIPop3URL.h"
#include "nsIMsgMailSession.h"
#include "nsIMsgFolderCompactor.h"
#include "nsNetCID.h"
#include "nsISpamSettings.h"
#include "nsNativeCharsetUtils.h"
#include "nsMailHeaders.h"
#include "nsCOMArray.h"
#include "nsIRssIncomingServer.h"
#include "nsNetUtil.h"
#include "nsIMsgFolderNotificationService.h"
#include "nsReadLine.h"
#include "nsArrayUtils.h"
#include "nsIStringEnumerator.h"
#include "nsIURIMutator.h"
#include "mozilla/Services.h"
#include "mozilla/UniquePtr.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<nsIMsgFolder> 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)
////////////////////////////////////////////////////////////////////////////////
nsresult nsMsgLocalMailFolder::CreateChildFromURI(const nsACString& uri,
nsIMsgFolder** folder) {
nsMsgLocalMailFolder* newFolder = new nsMsgLocalMailFolder;
if (!newFolder) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*folder = newFolder);
newFolder->Init(uri);
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<nsIMsgFolderNotificationService> 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<nsIMsgPluggableStore> 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(nsTArray<RefPtr<nsIMsgFolder>>& folders) {
if (!mInitialized) {
nsCOMPtr<nsIMsgIncomingServer> server;
nsresult rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
nsCOMPtr<nsIMsgPluggableStore> 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);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> 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<nsIMsgIncomingServer> server;
rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
nsCOMPtr<nsILocalMailIncomingServer> 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 nsMsgDBFolder::GetSubFolders(folders);
}
nsresult nsMsgLocalMailFolder::GetDatabase() {
nsCOMPtr<nsIMsgDatabase> 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<nsIFile> 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<nsIMsgDBService> 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<nsIDBFolderInfo> dbFolderInfo;
nsCOMPtr<nsIDBFolderInfo> 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<nsIFile> 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) {
// we need to null out the db so that parsing gets kicked off again.
mDatabase->RemoveListener(this);
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<nsIMsgDatabase> returnedDb;
rv = GetDatabaseWithReparse(this, aWindow, getter_AddRefs(returnedDb));
if (NS_SUCCEEDED(rv)) NotifyFolderEvent(kFolderLoaded);
} 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(kFolderLoaded);
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(kFolderLoaded);
}
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::GetFolderURL(nsACString& aUrl) {
nsresult rv;
nsCOMPtr<nsIFile> 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 = NS_OK;
nsCOMPtr<nsIMsgFolder> 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);
rv = GetOrCreateFolder(parentName, getter_AddRefs(msgParent));
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<nsIMsgFolder> newFolder;
nsresult rv =
CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newFolder));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgFolderNotificationService> 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<nsIMsgPluggableStore> 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)) {
// we need to notify explicitly the flag change because it failed when we
// did AddSubfolder
(*aNewFolder)->OnFlagChange(mFlags);
(*aNewFolder)
->SetPrettyName(
folderName); // because empty trash will create a new trash folder
NotifyItemAdded(*aNewFolder);
}
return rv;
}
NS_IMETHODIMP nsMsgLocalMailFolder::CompactAll(nsIUrlListener* aListener,
nsIMsgWindow* aMsgWindow,
bool aCompactOfflineAlso) {
nsresult rv = NS_OK;
nsCOMPtr<nsIMsgFolder> rootFolder;
rv = GetRootFolder(getter_AddRefs(rootFolder));
nsCOMPtr<nsIMsgPluggableStore> msgStore;
GetMsgStore(getter_AddRefs(msgStore));
bool storeSupportsCompaction;
msgStore->GetSupportsCompaction(&storeSupportsCompaction);
if (!storeSupportsCompaction) return NotifyCompactCompleted();
nsTArray<RefPtr<nsIMsgFolder>> folderArray;
if (NS_SUCCEEDED(rv) && rootFolder) {
nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
rv = rootFolder->GetDescendants(allDescendants);
NS_ENSURE_SUCCESS(rv, rv);
int64_t expungedBytes = 0;
for (auto folder : allDescendants) {
expungedBytes = 0;
if (folder) rv = folder->GetExpungedBytes(&expungedBytes);
NS_ENSURE_SUCCESS(rv, rv);
if (expungedBytes > 0) folderArray.AppendElement(folder);
}
if (folderArray.IsEmpty()) return NotifyCompactCompleted();
}
nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
return folderCompactor->CompactFolders(folderArray, {}, aListener,
aMsgWindow);
}
NS_IMETHODIMP nsMsgLocalMailFolder::Compact(nsIUrlListener* aListener,
nsIMsgWindow* aMsgWindow) {
nsCOMPtr<nsIMsgPluggableStore> 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<nsIMsgFolder> trashFolder;
rv = GetTrashFolder(getter_AddRefs(trashFolder));
if (NS_SUCCEEDED(rv)) {
uint32_t flags;
trashFolder->GetFlags(&flags);
int32_t totalMessages = 0;
rv = trashFolder->GetTotalMessages(true, &totalMessages);
if (totalMessages <= 0) {
// Any folders to deal with?
nsTArray<RefPtr<nsIMsgFolder>> subFolders;
rv = trashFolder->GetSubFolders(subFolders);
NS_ENSURE_SUCCESS(rv, rv);
if (subFolders.IsEmpty()) {
return NS_OK;
}
}
nsCOMPtr<nsIMsgFolder> parentFolder;
rv = trashFolder->GetParent(getter_AddRefs(parentFolder));
if (NS_SUCCEEDED(rv) && parentFolder) {
nsCOMPtr<nsIDBFolderInfo> transferInfo;
trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo));
trashFolder->SetParent(nullptr);
parentFolder->PropagateDelete(trashFolder, true, msgWindow);
parentFolder->CreateSubfolder(u"Trash"_ns, nullptr);
nsCOMPtr<nsIMsgFolder> newTrashFolder;
rv = GetTrashFolder(getter_AddRefs(newTrashFolder));
if (NS_SUCCEEDED(rv) && newTrashFolder) {
nsCOMPtr<nsIMsgLocalMailFolder> 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<nsIDBFolderInfo> dbFolderInfo;
nsCOMPtr<nsIMsgDatabase> 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<nsIMsgFolder> parentFolder;
nsCOMPtr<nsIMsgFolder> thisFolder;
rv = QueryInterface(NS_GET_IID(nsIMsgFolder), 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::DeleteSelf(nsIMsgWindow* msgWindow) {
nsresult rv;
bool isChildOfTrash;
IsChildOfTrash(&isChildOfTrash);
uint32_t folderFlags = 0;
GetFlags(&folderFlags);
// when deleting from trash, or virtual folder, just delete it.
if (isChildOfTrash || folderFlags & nsMsgFolderFlags::Virtual)
return nsMsgDBFolder::DeleteSelf(msgWindow);
nsCOMPtr<nsIMsgFolder> trashFolder;
rv = GetTrashFolder(getter_AddRefs(trashFolder));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIMsgCopyService> copyService(
do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
rv =
copyService->CopyFolders({this}, 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<nsIDocShell> docShell;
aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
if (docShell) {
bool confirmDeletion = true;
nsresult rv;
nsCOMPtr<nsIPrefBranch> pPrefBranch(
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
pPrefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash",
&confirmDeletion);
if (confirmDeletion) {
nsCOMPtr<nsIStringBundleService> bundleService =
mozilla::services::GetStringBundleService();
NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIStringBundle> bundle;
rv = bundleService->CreateBundle(
getter_AddRefs(bundle));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString folderName;
rv = aFolder->GetName(folderName);
NS_ENSURE_SUCCESS(rv, rv);
AutoTArray<nsString, 1> formatStrings = {folderName};
nsAutoString deleteFolderDialogTitle;
rv = bundle->GetStringFromName("pop3DeleteFolderDialogTitle",
deleteFolderDialogTitle);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString deleteFolderButtonLabel;
rv = bundle->GetStringFromName("pop3DeleteFolderButtonLabel",
deleteFolderButtonLabel);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString confirmationStr;
rv = bundle->FormatStringFromName("pop3MoveFolderToTrash", formatStrings,
confirmationStr);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrompt> 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<nsIMsgFolder> 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<nsIMsgPluggableStore> msgStore;
nsCOMPtr<nsIMsgFolder> 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 /*case-insensitive*/,
&changed);
if (changed) AlertFilterChanged(msgWindow);
if (count > 0) newFolder->RenameSubFolders(msgWindow, this);
// Discover the subfolders inside this folder (this is recursive)
nsTArray<RefPtr<nsIMsgFolder>> dummy;
newFolder->GetSubFolders(dummy);
// the newFolder should have the same flags
newFolder->SetFlags(mFlags);
if (parentFolder) {
SetParent(nullptr);
parentFolder->PropagateDelete(this, false, msgWindow);
parentFolder->NotifyItemAdded(newFolder);
}
// Forget our path, since this folder object renamed itself.
SetFilePath(nullptr);
newFolder->NotifyFolderEvent(kRenameCompleted);
nsCOMPtr<nsIMsgFolderNotificationService> 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);
nsTArray<RefPtr<nsIMsgFolder>> subFolders;
rv = oldFolder->GetSubFolders(subFolders);
NS_ENSURE_SUCCESS(rv, rv);
for (nsIMsgFolder* msgFolder : subFolders) {
nsString folderName;
rv = msgFolder->GetName(folderName);
nsCOMPtr<nsIMsgFolder> newFolder;
AddSubfolder(folderName, getter_AddRefs(newFolder));
if (newFolder) {
newFolder->SetPrettyName(folderName);
bool changed = false;
msgFolder->MatchOrChangeFilterDestination(
newFolder, true /*case-insensitive*/, &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<nsIMsgDBService> msgDBService =
do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> 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<nsIMsgFolder> 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<nsIMsgDatabase> 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(kFolderSize, 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<nsIFile> 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<nsIMsgFolder> 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(
nsTArray<RefPtr<nsIMsgDBHdr>> const& msgHeaders, nsIMsgWindow* msgWindow,
bool deleteStorage, bool isMove, nsIMsgCopyServiceListener* listener,
bool allowUndo) {
nsresult rv;
// shift delete case - (delete to trash is handled in EndMove)
// this is also the case when applying retention settings.
if (deleteStorage && !isMove) {
MarkMsgsOnPop3Server(msgHeaders, POP3_DELETE);
}
bool isTrashFolder = mFlags & nsMsgFolderFlags::Trash;
// notify on delete from trash and shift-delete
if (!isMove && (deleteStorage || isTrashFolder)) {
nsCOMPtr<nsIMsgFolderNotificationService> notifier(
do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
if (notifier) {
if (listener) {
listener->OnStartCopy();
listener->OnStopCopy(NS_OK);
}
notifier->NotifyMsgsDeleted(msgHeaders);
}
}
if (!deleteStorage && !isTrashFolder) {
nsCOMPtr<nsIMsgFolder> trashFolder;
rv = GetTrashFolder(getter_AddRefs(trashFolder));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIMsgCopyService> copyService =
do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
return copyService->CopyMessages(this, msgHeaders, trashFolder, true,
listener, msgWindow, allowUndo);
}
} else {
nsCOMPtr<nsIMsgDatabase> msgDB;
rv = GetDatabaseWOReparse(getter_AddRefs(msgDB));
if (NS_SUCCEEDED(rv)) {
if (deleteStorage && isMove && GetDeleteFromServerOnMove())
MarkMsgsOnPop3Server(msgHeaders, POP3_DELETE);
nsCOMPtr<nsISupports> msgSupport;
rv = EnableNotifications(allMessageCountNotifications, false);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIMsgPluggableStore> msgStore;
rv = GetMsgStore(getter_AddRefs(msgStore));
if (NS_SUCCEEDED(rv)) {
rv = msgStore->DeleteMessages(msgHeaders);
for (auto hdr : msgHeaders) {
rv = msgDB->DeleteHeader(hdr, 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);
if (!isMove)
NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
: kDeleteOrMoveMsgFailed);
if (msgWindow && !isMove) AutoCompact(msgWindow);
}
}
if (msgWindow && !isMove && (deleteStorage || isTrashFolder)) {
// Clear undo and redo stack.
nsCOMPtr<nsITransactionManager> 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<nsIMsgPluggableStore> msgStore;
rv = GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
return msgStore->ChangeFlags({aMessage}, msgFlag, true);
}
NS_IMETHODIMP
nsMsgLocalMailFolder::MarkMessagesRead(
const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, bool aMarkRead) {
nsresult rv = nsMsgDBFolder::MarkMessagesRead(aMessages, aMarkRead);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgPluggableStore> msgStore;
rv = GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Read, aMarkRead);
}
NS_IMETHODIMP
nsMsgLocalMailFolder::MarkMessagesFlagged(
const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, bool aMarkFlagged) {
nsresult rv = nsMsgDBFolder::MarkMessagesFlagged(aMessages, aMarkFlagged);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgPluggableStore> msgStore;
rv = GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Marked,
aMarkFlagged);
}
NS_IMETHODIMP
nsMsgLocalMailFolder::MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) {
nsresult rv = GetDatabase();
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<nsMsgKey> thoseMarked;
EnableNotifications(allMessageCountNotifications, false);
rv = mDatabase->MarkAllRead(thoseMarked);
EnableNotifications(allMessageCountNotifications, true);
NS_ENSURE_SUCCESS(rv, rv);
if (thoseMarked.IsEmpty()) {
return NS_OK;
}
nsTArray<RefPtr<nsIMsgDBHdr>> messages;
rv = MsgGetHeadersFromKeys(mDatabase, thoseMarked, messages);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgPluggableStore> msgStore;
rv = GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true);
NS_ENSURE_SUCCESS(rv, rv);
mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
// Setup a undo-state
if (aMsgWindow)
rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(),
thoseMarked.Length());
return rv;
}
NS_IMETHODIMP nsMsgLocalMailFolder::MarkThreadRead(nsIMsgThread* thread) {
nsresult rv = GetDatabase();
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<nsMsgKey> thoseMarked;
rv = mDatabase->MarkThreadRead(thread, nullptr, thoseMarked);
NS_ENSURE_SUCCESS(rv, rv);
if (thoseMarked.IsEmpty()) {
return NS_OK;
}
nsTArray<RefPtr<nsIMsgDBHdr>> messages;
rv = MsgGetHeadersFromKeys(mDatabase, thoseMarked, messages);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgPluggableStore> msgStore;
rv = GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true);
NS_ENSURE_SUCCESS(rv, rv);
mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
return rv;
}
nsresult nsMsgLocalMailFolder::InitCopyState(
nsISupports* aSupport, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow* msgWindow,
bool isFolder, bool allowUndo) {
nsCOMPtr<nsIFile> 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<nsIMsgDatabase> msgDB;
GetDatabaseWOReparse(getter_AddRefs(msgDB));
bool isLocked;
GetLocked(&isLocked);
if (isLocked) return NS_MSG_FOLDER_BUSY;
AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(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.Clone();
mCopyState->m_curCopyIndex = 0;
mCopyState->m_isMove = isMove;
mCopyState->m_isFolder = isFolder;
mCopyState->m_allowUndo = allowUndo;
mCopyState->m_msgWindow = msgWindow;
mCopyState->m_totalMsgCount = messages.Length();
if (listener) mCopyState->m_listener = listener;
mCopyState->m_copyingMultipleMessages = false;
mCopyState->m_wholeMsgInStream = false;
return NS_OK;
}
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(kFolderLoaded);
(void)RefreshSizeOnDisk();
// we are the destination folder for a move/copy
bool haveSemaphore;
nsresult rv =
TestSemaphore(static_cast<nsIMsgLocalMailFolder*>(this), &haveSemaphore);
if (NS_SUCCEEDED(rv) && haveSemaphore)
ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this));
if (mCopyState && !mCopyState->m_newMsgKeywords.IsEmpty() &&
mCopyState->m_newHdr) {
AddKeywordsToMessages({&*mCopyState->m_newHdr},
mCopyState->m_newMsgKeywords);
}
if (moveCopySucceeded && mDatabase) {
mDatabase->SetSummaryValid(true);
(void)CloseDBIfFolderNotOpen();
}
delete mCopyState;
mCopyState = nullptr;
nsCOMPtr<nsIMsgCopyService> 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(kDeleteOrMoveMsgFailed);
OnCopyCompleted(srcSupports, false);
return false;
}
return true;
}
NS_IMETHODIMP
nsMsgLocalMailFolder::CopyMessages(nsIMsgFolder* srcFolder,
nsTArray<RefPtr<nsIMsgDBHdr>> const& srcHdrs,
bool isMove, nsIMsgWindow* msgWindow,
nsIMsgCopyServiceListener* listener,
bool isFolder, bool allowUndo) {
nsCOMPtr<nsISupports> 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(kDeleteOrMoveMsgFailed);
return OnCopyCompleted(srcSupport, false);
}
UpdateTimestamps(allowUndo);
nsCString protocolType;
rv = srcFolder->GetURI(protocolType);
protocolType.SetLength(protocolType.FindChar(':'));
// If we're offline and the source folder is imap or news, to do the
// copy the message bodies MUST reside in offline storage.
bool needOfflineBodies =
(WeAreOffline() && (protocolType.LowerCaseEqualsLiteral("imap") ||
protocolType.LowerCaseEqualsLiteral("news")));
int64_t totalMsgSize = 0;
bool allMsgsHaveOfflineStore = true;
for (auto message : srcHdrs) {
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;
// Check if each source folder message has offline storage regardless
// of whether we're online or offline.
message->GetMessageKey(&key);
bool hasMsgOffline = false;
srcFolder->HasMsgOffline(key, &hasMsgOffline);
allMsgsHaveOfflineStore = allMsgsHaveOfflineStore && hasMsgOffline;
// If we're offline and not all messages are in offline storage, the copy
// or move can't occur and a notification for the user to download the
// messages is posted.
if (needOfflineBodies && !hasMsgOffline) {
if (isMove) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
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<nsIMsgPluggableStore> msgStore;
rv = GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsITransaction> undoTxn;
nsTArray<RefPtr<nsIMsgDBHdr>> dstHdrs;
rv = msgStore->CopyMessages(isMove, srcHdrs, this, listener, dstHdrs,
getter_AddRefs(undoTxn), &storeDidCopy);
if (storeDidCopy) {
NS_ASSERTION(undoTxn, "if store does copy, it needs to add undo action");
if (msgWindow && undoTxn) {
nsCOMPtr<nsITransactionManager> txnMgr;
msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
if (txnMgr) txnMgr->DoTransaction(undoTxn);
}
if (isMove)
srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
: kDeleteOrMoveMsgFailed);
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<nsIMsgLocalMailFolder> 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);
// sort the message array by key
nsTArray<nsMsgKey> keyArray(srcHdrs.Length());
nsTArray<RefPtr<nsIMsgDBHdr>> sortedMsgs(srcHdrs.Length());
for (nsIMsgDBHdr* aMessage : srcHdrs) {
nsMsgKey key;
aMessage->GetMessageKey(&key);
keyArray.AppendElement(key);
}
keyArray.Sort();
rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs);
NS_ENSURE_SUCCESS(rv, rv);
rv = InitCopyState(srcSupport, sortedMsgs, isMove, listener, msgWindow,
isFolder, allowUndo);
if (NS_FAILED(rv)) {
ThrowAlertMsg("operationFailedFolderBusy", msgWindow);
(void)OnCopyCompleted(srcSupport, false);
return rv;
}
if (!protocolType.LowerCaseEqualsLiteral("mailbox")) {
mCopyState->m_dummyEnvelopeNeeded = true;
nsParseMailMessageState* parseMsgState = new nsParseMailMessageState();
if (parseMsgState) {
nsCOMPtr<nsIMsgDatabase> 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<nsLocalMoveCopyMsgTxn> 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 (srcHdrs.Length() > 1 &&
((protocolType.LowerCaseEqualsLiteral("imap") &&
!allMsgsHaveOfflineStore) ||
protocolType.LowerCaseEqualsLiteral("mailbox"))) {
// For an imap source folder with more than one message to be copied that
// are not all in offline storage, this fetches all the messages from the
// imap server to do the copy. When source folder is "mailbox", this is not
// a concern since source messages are in local storage.
mCopyState->m_copyingMultipleMessages = true;
rv = CopyMessagesTo(keyArray, msgWindow, this, isMove);
if (NS_FAILED(rv)) {
NS_ERROR("copy message failed");
(void)OnCopyCompleted(srcSupport, false);
}
} else {
// This obtains the source messages from local/offline storage to do the
// copy. Note: CopyMessageTo() actually handles one or more messages.
nsIMsgDBHdr* msgSupport = 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(kDeleteOrMoveMsgFailed);
EnableNotifications(allMessageCountNotifications, true);
}
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<nsIMsgFolder> newMsgFolder;
nsresult rv = CreateSubfolderInternal(folderName, msgWindow,
getter_AddRefs(newMsgFolder));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgEnumerator> messages;
rv = srcFolder->GetMessages(getter_AddRefs(messages));
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<RefPtr<nsIMsgDBHdr>> msgArray;
bool hasMoreElements = false;
if (messages) rv = messages->HasMoreElements(&hasMoreElements);
while (NS_SUCCEEDED(rv) && hasMoreElements) {
nsCOMPtr<nsIMsgDBHdr> msg;
rv = messages->GetNext(getter_AddRefs(msg));
NS_ENSURE_SUCCESS(rv, rv);
msgArray.AppendElement(msg);
rv = messages->HasMoreElements(&hasMoreElements);
NS_ENSURE_SUCCESS(rv, rv);
}
if (msgArray.Length() > 0) // if only srcFolder has messages..
newMsgFolder->CopyMessages(srcFolder, msgArray, false, msgWindow, listener,
true /* is folder*/, false /* allowUndo */);
else {
nsCOMPtr<nsIMsgLocalMailFolder> 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<nsISupports> srcSupports = do_QueryInterface(srcFolder);
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) {
nsTArray<RefPtr<nsIMsgFolder>> subFolders;
nsresult rv = srcFolder->GetSubFolders(subFolders);
NS_ENSURE_SUCCESS(rv, rv);
for (nsIMsgFolder* folder : subFolders) {
CopyFolderAcrossServer(folder, msgWindow, listener);
}
return NS_OK;
}
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<nsIMsgPluggableStore> 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<nsISupports> fileSupport(do_QueryInterface(aFile, &rv));
aFile->GetFileSize(&fileSize);
if (!CheckIfSpaceForCopy(msgWindow, nullptr, fileSupport, false, fileSize))
return NS_OK;
nsTArray<RefPtr<nsIMsgDBHdr>> messages;
if (msgToReplace) messages.AppendElement(msgToReplace);
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<nsIMsgDatabase> msgDb;
mCopyState->m_parseMsgState = parseMsgState;
GetDatabaseWOReparse(getter_AddRefs(msgDb));
if (msgDb) parseMsgState->SetMailDB(msgDb);
nsCOMPtr<nsIInputStream> 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<nsISeekableStream> 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<nsIMsgDBHdr> 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<nsIMsgIncomingServer> server;
nsresult rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
nsCOMPtr<nsILocalMailIncomingServer> 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<nsIRssIncomingServer> rssServer = do_QueryInterface(server, &rv);
mozilla::Unused << rssServer;
if (NS_SUCCEEDED(rv))
return localMailServer->GetNewMail(aWindow, aListener, this, nullptr);
nsCOMPtr<nsIMsgFolder> inbox;
nsCOMPtr<nsIMsgFolder> rootFolder;
rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder));
if (NS_SUCCEEDED(rv) && rootFolder) {
rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
getter_AddRefs(inbox));
}
nsCOMPtr<nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox, &rv);
if (NS_SUCCEEDED(rv)) {
bool valid = false;
nsCOMPtr<nsIMsgDatabase> 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<nsISeekableStream> 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
char statusStrBuf[50];
if (mCopyState->m_curCopyIndex < mCopyState->m_messages.Length()) {
uint32_t dbFlags = 0;
mCopyState->m_messages[mCopyState->m_curCopyIndex]->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() {
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<nsISeekableStream> 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
if (messageIndex < (int32_t)mCopyState->m_messages.Length()) {
mCopyState->m_message = mCopyState->m_messages[messageIndex];
} else {
mCopyState->m_message = nullptr;
}
// 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<nsIMsgLocalMailFolder*>(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<nsISeekableStream> 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<nsIPrefBranch> 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",
dontPreserve);
else
prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy",
dontPreserve);
CopyHdrPropertiesWithSkipList(destHdr, srcHdr, dontPreserve);
}
void nsMsgLocalMailFolder::CopyHdrPropertiesWithSkipList(
nsIMsgDBHdr* destHdr, nsIMsgDBHdr* srcHdr, const nsCString& skipList) {
nsCOMPtr<nsIUTF8StringEnumerator> 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);
dontPreserveEx.Append(skipList);
dontPreserveEx.Append(' ');
nsAutoCString property;
nsCString sourceString;
bool hasMore;
while (NS_SUCCEEDED(propertyEnumerator->HasMore(&hasMore)) && hasMore) {
propertyEnumerator->GetNext(property);
nsAutoCString propertyEx(" "_ns);
propertyEx.Append(property);
propertyEx.Append(' ');
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);
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY 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);
}
return NS_OK;
}
bool multipleCopiesFinished =
(mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount);
RefPtr<nsLocalMoveCopyMsgTxn> 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<nsISeekableStream> seekableStream(
do_QueryInterface(mCopyState->m_fileStream));
if (seekableStream) {
seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
rv = FinishNewLocalMessage(mCopyState->m_fileStream, mCopyState->m_newHdr,
mCopyState->m_msgStore,
mCopyState->m_parseMsgState);
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<nsIMsgDBHdr> newHdr;
if (!mCopyState->m_parseMsgState) {
if (mCopyState->m_destDB) {
if (mCopyState->m_newHdr) {
newHdr = mCopyState->m_newHdr;
CopyHdrPropertiesWithSkipList(newHdr, mCopyState->m_message,
"storeToken msgOffset"_ns);
// 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);
}
}
// 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);