diff options
Diffstat (limited to 'mailnews/db/msgdb/src/nsMsgDatabase.cpp')
-rw-r--r-- | mailnews/db/msgdb/src/nsMsgDatabase.cpp | 5915 |
1 files changed, 5915 insertions, 0 deletions
diff --git a/mailnews/db/msgdb/src/nsMsgDatabase.cpp b/mailnews/db/msgdb/src/nsMsgDatabase.cpp new file mode 100644 index 000000000..8b366ab5c --- /dev/null +++ b/mailnews/db/msgdb/src/nsMsgDatabase.cpp @@ -0,0 +1,5915 @@ +/* -*- 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/. */ + +// this file implements the nsMsgDatabase interface using the MDB Interface. + +#include "nscore.h" +#include "msgCore.h" +#include "nsMailDatabase.h" +#include "nsDBFolderInfo.h" +#include "nsMsgKeySet.h" +#include "nsMsgThread.h" +#include "nsIMsgSearchTerm.h" +#include "nsMsgBaseCID.h" +#include "nsMorkCID.h" +#include "nsIMdbFactoryFactory.h" +#include "mozilla/Logging.h" +#include "prprf.h" +#include "nsMsgDBCID.h" +#include "nsILocale.h" +#include "nsMsgMimeCID.h" +#include "nsILocaleService.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgDBView.h" +#include "nsIMsgFolderCache.h" +#include "nsIMsgFolderCacheElement.h" +#include "MailNewsTypes2.h" +#include "nsMsgUtils.h" +#include "nsMsgKeyArray.h" +#include "nsIMutableArray.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsICollation.h" +#include "nsCollationCID.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMsgPluggableStore.h" +#include "nsAlgorithm.h" +#include "nsArrayEnumerator.h" +#include "nsIMemoryReporter.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/mailnews/Services.h" +#include <algorithm> + +using namespace mozilla::mailnews; +using namespace mozilla; + +#if defined(DEBUG_sspitzer_) || defined(DEBUG_seth_) +#define DEBUG_MSGKEYSET 1 +#endif + +#define MSG_HASH_SIZE 512 + +// This will be used on discovery, since we don't know total. +const int32_t kMaxHdrsInCache = 512; + +// special keys +static const nsMsgKey kAllMsgHdrsTableKey = 1; +static const nsMsgKey kTableKeyForThreadOne = 0xfffffffe; +static const nsMsgKey kAllThreadsTableKey = 0xfffffffd; +static const nsMsgKey kFirstPseudoKey = 0xfffffff0; +static const nsMsgKey kIdStartOfFake = 0xffffff80; +static const nsMsgKey kForceReparseKey = 0xfffffff0; + +static PRLogModuleInfo* DBLog; + +PRTime nsMsgDatabase::gLastUseTime; + +NS_IMPL_ISUPPORTS(nsMsgDBService, nsIMsgDBService) + +nsMsgDBService::nsMsgDBService() +{ + DBLog = PR_NewLogModule("MSGDB"); +} + + +nsMsgDBService::~nsMsgDBService() +{ +#ifdef DEBUG + // If you hit this warning, it means that some code is holding onto + // a db at shutdown. + NS_WARNING_ASSERTION(!m_dbCache.Length(), "some msg dbs left open"); + for (uint32_t i = 0; i < m_dbCache.Length(); i++) + { + nsMsgDatabase* pMessageDB = m_dbCache.ElementAt(i); + if (pMessageDB) + printf("db left open %s\n", (const char *) pMessageDB->m_dbName.get()); + } +#endif +} + +NS_IMETHODIMP nsMsgDBService::OpenFolderDB(nsIMsgFolder *aFolder, + bool aLeaveInvalidDB, + nsIMsgDatabase **_retval) +{ + NS_ENSURE_ARG(aFolder); + nsCOMPtr<nsIMsgIncomingServer> incomingServer; + nsresult rv = aFolder->GetServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> summaryFilePath; + rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgDatabase *cacheDB = FindInCache(summaryFilePath); + if (cacheDB) + { + // this db could have ended up in the folder cache w/o an m_folder pointer via + // OpenMailDBFromFile. If so, take this chance to fix the folder. + if (!cacheDB->m_folder) + cacheDB->m_folder = aFolder; + cacheDB->RememberLastUseTime(); + *_retval = cacheDB; // FindInCache already addRefed. + // if m_thumb is set, someone is asynchronously opening the db. But our + // caller wants to synchronously open it, so just do it. + if (cacheDB->m_thumb) + return cacheDB->Open(this, summaryFilePath, false, aLeaveInvalidDB); + return NS_OK; + } + + nsCString localDatabaseType; + incomingServer->GetLocalDatabaseType(localDatabaseType); + nsAutoCString dbContractID(NS_MSGDB_CONTRACTID); + dbContractID.Append(localDatabaseType.get()); + nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Don't try to create the database yet--let the createNewDB call do that. + nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get()); + msgDatabase->m_folder = aFolder; + rv = msgDatabase->Open(this, summaryFilePath, false, aLeaveInvalidDB); + if (NS_FAILED(rv) && rv != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + return rv; + + NS_ADDREF(*_retval = msgDB); + + if (NS_FAILED(rv)) + { +#ifdef DEBUG + // Doing these checks for debug only as we don't want to report certain + // errors in debug mode, but in release mode we wouldn't report them either + + // These errors are expected. + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + return rv; + + // If it isn't one of the expected errors, throw a warning. + NS_ENSURE_SUCCESS(rv, rv); +#endif + return rv; + } + + FinishDBOpen(aFolder, msgDatabase); + return rv; +} + +NS_IMETHODIMP nsMsgDBService::AsyncOpenFolderDB(nsIMsgFolder *aFolder, + bool aLeaveInvalidDB, + nsIMsgDatabase **_retval) +{ + NS_ENSURE_ARG(aFolder); + + nsCOMPtr <nsIFile> summaryFilePath; + nsresult rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgDatabase *cacheDB = FindInCache(summaryFilePath); + if (cacheDB) + { + // this db could have ended up in the folder cache w/o an m_folder pointer via + // OpenMailDBFromFile. If so, take this chance to fix the folder. + if (!cacheDB->m_folder) + cacheDB->m_folder = aFolder; + *_retval = cacheDB; // FindInCache already addRefed. + // We don't care if an other consumer is thumbing the store. In that + // case, they'll both thumb the store. + return NS_OK; + } + + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + rv = aFolder->GetServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString localDatabaseType; + incomingServer->GetLocalDatabaseType(localDatabaseType); + nsAutoCString dbContractID(NS_MSGDB_CONTRACTID); + dbContractID.Append(localDatabaseType.get()); + nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get()); + rv = msgDatabase->OpenInternal(this, summaryFilePath, false, aLeaveInvalidDB, + false /* open asynchronously */); + + NS_ADDREF(*_retval = msgDB); + msgDatabase->m_folder = aFolder; + + if (NS_FAILED(rv)) + { +#ifdef DEBUG + // Doing these checks for debug only as we don't want to report certain + // errors in debug mode, but in release mode we wouldn't report them either + + // These errors are expected. + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + return rv; + + // If it isn't one of the expected errors, throw a warning. + NS_ENSURE_SUCCESS(rv, rv); +#endif + return rv; + } + + FinishDBOpen(aFolder, msgDatabase); + return rv; +} + +NS_IMETHODIMP nsMsgDBService::OpenMore(nsIMsgDatabase *aDB, + uint32_t aTimeHint, + bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(aDB); + NS_ENSURE_TRUE(msgDatabase, NS_ERROR_INVALID_ARG); + // Check if this db has been opened. + if (!msgDatabase->m_thumb) + { + *_retval = true; + return NS_OK; + } + nsresult rv; + *_retval = false; + PRIntervalTime startTime = PR_IntervalNow(); + do + { + mdb_count outTotal; // total somethings to do in operation + mdb_count outCurrent; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken; // is operation irreparably dead and broken? + rv = msgDatabase->m_thumb->DoMore(msgDatabase->m_mdbEnv, + &outTotal, &outCurrent, &outDone, + &outBroken); + if (NS_FAILED(rv)) + break; + if (outDone) + { + nsCOMPtr<nsIMdbFactory> mdbFactory; + msgDatabase->GetMDBFactory(getter_AddRefs(mdbFactory)); + NS_ENSURE_TRUE(mdbFactory, NS_ERROR_FAILURE); + rv = mdbFactory->ThumbToOpenStore(msgDatabase->m_mdbEnv, msgDatabase->m_thumb, &msgDatabase->m_mdbStore); + msgDatabase->m_thumb = nullptr; + nsCOMPtr<nsIFile> folderPath; + (void) msgDatabase->m_folder->GetFilePath(getter_AddRefs(folderPath)); + nsCOMPtr <nsIFile> summaryFile; + (void) GetSummaryFileLocation(folderPath, getter_AddRefs(summaryFile)); + + if (NS_SUCCEEDED(rv)) + rv = (msgDatabase->m_mdbStore) ? msgDatabase->InitExistingDB() : NS_ERROR_FAILURE; + if (NS_SUCCEEDED(rv)) + rv = msgDatabase->CheckForErrors(rv, false, this, summaryFile); + + FinishDBOpen(msgDatabase->m_folder, msgDatabase); + break; + } + } + while (PR_IntervalToMilliseconds(PR_IntervalNow() - startTime) <= aTimeHint); + *_retval = !msgDatabase->m_thumb; + return rv; +} + +/** + * When a db is opened, we need to hook up any pending listeners for + * that db, and notify them. + */ +void nsMsgDBService::HookupPendingListeners(nsIMsgDatabase *db, + nsIMsgFolder *folder) +{ + for (int32_t listenerIndex = 0; + listenerIndex < m_foldersPendingListeners.Count(); listenerIndex++) + { + // check if we have a pending listener on this db, and if so, add it. + if (m_foldersPendingListeners[listenerIndex] == folder) + { + db->AddListener(m_pendingListeners.ObjectAt(listenerIndex)); + m_pendingListeners.ObjectAt(listenerIndex)->OnEvent(db, "DBOpened"); + } + } +} + +void nsMsgDBService::FinishDBOpen(nsIMsgFolder *aFolder, nsMsgDatabase *aMsgDB) +{ + uint32_t folderFlags; + aFolder->GetFlags(&folderFlags); + + if (! (folderFlags & nsMsgFolderFlags::Virtual) && + aMsgDB->m_mdbAllMsgHeadersTable) + { + mdb_count numHdrsInTable = 0; + int32_t numMessages; + aMsgDB->m_mdbAllMsgHeadersTable->GetCount(aMsgDB->GetEnv(), + &numHdrsInTable); + aMsgDB->m_dbFolderInfo->GetNumMessages(&numMessages); + if (numMessages != (int32_t) numHdrsInTable) + aMsgDB->SyncCounts(); + } + HookupPendingListeners(aMsgDB, aFolder); + aMsgDB->RememberLastUseTime(); +} + +//---------------------------------------------------------------------- +// FindInCache - this addrefs the db it finds. +//---------------------------------------------------------------------- +nsMsgDatabase* nsMsgDBService::FindInCache(nsIFile *dbName) +{ + for (uint32_t i = 0; i < m_dbCache.Length(); i++) + { + nsMsgDatabase* pMessageDB = m_dbCache[i]; + if (pMessageDB->MatchDbName(dbName)) + { + if (pMessageDB->m_mdbStore) // don't return db without store + { + NS_ADDREF(pMessageDB); + return pMessageDB; + } + } + } + return nullptr; +} + +// This method is called when the caller is trying to create a db without +// having a corresponding nsIMsgFolder object. This happens in a few +// situations, including imap folder discovery, compacting local folders, +// and copying local folders. +NS_IMETHODIMP nsMsgDBService::OpenMailDBFromFile(nsIFile *aFolderName, + nsIMsgFolder *aFolder, + bool aCreate, + bool aLeaveInvalidDB, + nsIMsgDatabase** pMessageDB) +{ + if (!aFolderName) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr <nsIFile> dbPath; + nsresult rv = GetSummaryFileLocation(aFolderName, getter_AddRefs(dbPath)); + NS_ENSURE_SUCCESS(rv, rv); + + *pMessageDB = FindInCache(dbPath); + if (*pMessageDB) + return NS_OK; + + RefPtr<nsMailDatabase> msgDB = new nsMailDatabase; + NS_ENSURE_TRUE(msgDB, NS_ERROR_OUT_OF_MEMORY); + rv = msgDB->Open(this, dbPath, aCreate, aLeaveInvalidDB); + if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) + return rv; + NS_IF_ADDREF(*pMessageDB = msgDB); + if (aCreate && msgDB && rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + rv = NS_OK; + if (NS_SUCCEEDED(rv)) + msgDB->m_folder = aFolder; + return rv; +} + +NS_IMETHODIMP nsMsgDBService::CreateNewDB(nsIMsgFolder *aFolder, + nsIMsgDatabase **_retval) +{ + NS_ENSURE_ARG(aFolder); + + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + nsresult rv = aFolder->GetServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> summaryFilePath; + rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString localDatabaseType; + incomingServer->GetLocalDatabaseType(localDatabaseType); + nsAutoCString dbContractID(NS_MSGDB_CONTRACTID); + dbContractID.Append(localDatabaseType.get()); + + nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get()); + + msgDatabase->m_folder = aFolder; + rv = msgDatabase->Open(this, summaryFilePath, true, true); + + // We are trying to create a new database, but that implies that it did not + // already exist. Open returns NS_MSG_ERROR_FOLDER_SUMMARY_MISSING for the + // successful creation of a new database. But if it existed for some + // reason, then we would get rv = NS_OK instead. That is a "failure" + // from our perspective, so we want to return a failure since we are not + // returning a valid database object. + NS_ENSURE_TRUE(rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING, + NS_SUCCEEDED(rv) ? NS_ERROR_FILE_ALREADY_EXISTS : rv); + + NS_ADDREF(*_retval = msgDB); + + HookupPendingListeners(msgDB, aFolder); + + msgDatabase->RememberLastUseTime(); + + return NS_OK; +} + +/* void registerPendingListener (in nsIMsgFolder aFolder, in nsIDBChangeListener aListener); */ +NS_IMETHODIMP nsMsgDBService::RegisterPendingListener(nsIMsgFolder *aFolder, nsIDBChangeListener *aListener) +{ + // need to make sure we don't hold onto these forever. Maybe a shutdown listener? + // if there is a db open on this folder already, we should register the listener. + m_foldersPendingListeners.AppendObject(aFolder); + m_pendingListeners.AppendObject(aListener); + nsCOMPtr <nsIMsgDatabase> openDB; + CachedDBForFolder(aFolder, getter_AddRefs(openDB)); + if (openDB) + openDB->AddListener(aListener); + return NS_OK; +} + +/* void unregisterPendingListener (in nsIDBChangeListener aListener); */ +NS_IMETHODIMP nsMsgDBService::UnregisterPendingListener(nsIDBChangeListener *aListener) +{ + int32_t listenerIndex = m_pendingListeners.IndexOfObject(aListener); + if (listenerIndex != -1) + { + nsCOMPtr<nsIMsgDatabase> msgDB; + CachedDBForFolder(m_foldersPendingListeners[listenerIndex], getter_AddRefs(msgDB)); + if (msgDB) + msgDB->RemoveListener(aListener); + m_foldersPendingListeners.RemoveObjectAt(listenerIndex); + m_pendingListeners.RemoveObjectAt(listenerIndex); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgDBService::CachedDBForFolder(nsIMsgFolder *aFolder, nsIMsgDatabase **aRetDB) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aRetDB); + + nsCOMPtr<nsIFile> summaryFilePath; + nsresult rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath)); + NS_ENSURE_SUCCESS(rv, rv); + + *aRetDB = FindInCache(summaryFilePath); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBService::ForceFolderDBClosed(nsIMsgFolder *aFolder) +{ + nsCOMPtr<nsIMsgDatabase> mailDB; + nsresult rv = CachedDBForFolder(aFolder, getter_AddRefs(mailDB)); + if (mailDB) + { + mailDB->ForceClosed(); + } + return rv; +} + +NS_IMETHODIMP nsMsgDBService::GetOpenDBs(nsIArray **aOpenDBs) +{ + NS_ENSURE_ARG_POINTER(aOpenDBs); + nsresult rv; + nsCOMPtr<nsIMutableArray> openDBs(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < m_dbCache.Length(); i++) + openDBs->AppendElement(m_dbCache[i], false); + + openDBs.forget(aOpenDBs); + return NS_OK; +} + +static bool gGotGlobalPrefs = false; +static bool gThreadWithoutRe = true; +static bool gStrictThreading = false; +static bool gCorrectThreading = false; + +void nsMsgDatabase::GetGlobalPrefs() +{ + if (!gGotGlobalPrefs) + { + GetBoolPref("mail.thread_without_re", &gThreadWithoutRe); + GetBoolPref("mail.strict_threading", &gStrictThreading); + GetBoolPref("mail.correct_threading", &gCorrectThreading); + gGotGlobalPrefs = true; + } +} + +nsresult nsMsgDatabase::AddHdrToCache(nsIMsgDBHdr *hdr, nsMsgKey key) // do we want key? We could get it from hdr +{ + if (m_bCacheHeaders) + { + if (!m_cachedHeaders) + m_cachedHeaders = new PLDHashTable(&gMsgDBHashTableOps, sizeof(struct MsgHdrHashElement), m_cacheSize); + if (m_cachedHeaders) + { + if (key == nsMsgKey_None) + hdr->GetMessageKey(&key); + if (m_cachedHeaders->EntryCount() > m_cacheSize) + ClearHdrCache(true); + PLDHashEntryHdr *entry = m_cachedHeaders->Add((void *)(uintptr_t) key, mozilla::fallible); + if (!entry) + return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory + + MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry); + element->mHdr = hdr; + element->mKey = key; + NS_ADDREF(hdr); // make the cache hold onto the header + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + + +NS_IMETHODIMP nsMsgDatabase::SetMsgHdrCacheSize(uint32_t aSize) +{ + m_cacheSize = aSize; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetMsgHdrCacheSize(uint32_t *aSize) +{ + NS_ENSURE_ARG_POINTER(aSize); + *aSize = m_cacheSize; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetLastUseTime(PRTime *aTime) +{ + NS_ENSURE_ARG_POINTER(aTime); + *aTime = m_lastUseTime; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::SetLastUseTime(PRTime aTime) +{ + gLastUseTime = m_lastUseTime = aTime; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetDatabaseSize(int64_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + nsCOMPtr<nsIFile> summaryFilePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = summaryFilePath->InitWithNativePath(m_dbName); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = summaryFilePath->Exists(&exists); + if (NS_SUCCEEDED(rv)) + { + if (exists) + rv = summaryFilePath->GetFileSize(_retval); + else + *_retval = 0; + } + + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::ClearCachedHdrs() +{ + ClearCachedObjects(false); +#ifdef DEBUG_bienvenu1 + if (mRefCnt > 1) + { + NS_ASSERTION(false, ""); + printf("someone's holding onto db - refs = %ld\n", mRefCnt); + } +#endif + return NS_OK; +} + +void nsMsgDatabase::ClearEnumerators() +{ + // clear out existing enumerators + nsTArray<nsMsgDBEnumerator *> copyEnumerators; + copyEnumerators.SwapElements(m_enumerators); + + uint32_t numEnums = copyEnumerators.Length(); + for (uint32_t i = 0; i < numEnums; i++) + copyEnumerators[i]->Clear(); +} + +nsMsgThread *nsMsgDatabase::FindExistingThread(nsMsgKey threadId) +{ + uint32_t numThreads = m_threads.Length(); + for (uint32_t i = 0; i < numThreads; i++) + if (m_threads[i]->m_threadKey == threadId) + return m_threads[i]; + + return nullptr; +} + +void nsMsgDatabase::ClearThreads() +{ + // clear out existing threads + nsTArray<nsMsgThread *> copyThreads; + copyThreads.SwapElements(m_threads); + + uint32_t numThreads = copyThreads.Length(); + for (uint32_t i = 0; i < numThreads; i++) + copyThreads[i]->Clear(); +} + +void nsMsgDatabase::ClearCachedObjects(bool dbGoingAway) +{ + ClearHdrCache(false); +#ifdef DEBUG_DavidBienvenu + if (m_headersInUse && m_headersInUse->EntryCount() > 0) + { + NS_ASSERTION(false, "leaking headers"); + printf("leaking %d headers in %s\n", m_headersInUse->EntryCount(), (const char *) m_dbName); + } +#endif + m_cachedThread = nullptr; + m_cachedThreadId = nsMsgKey_None; + // We should only clear the use hdr cache when the db is going away, or we could + // end up with multiple copies of the same logical msg hdr, which will lead to + // ref-counting problems. + if (dbGoingAway) + { + ClearUseHdrCache(); + ClearThreads(); + } + m_thumb = nullptr; +} + +nsresult nsMsgDatabase::ClearHdrCache(bool reInit) +{ + if (m_cachedHeaders) + { + // save this away in case we renter this code. + PLDHashTable *saveCachedHeaders = m_cachedHeaders; + m_cachedHeaders = nullptr; + for (auto iter = saveCachedHeaders->Iter(); !iter.Done(); iter.Next()) { + auto element = static_cast<MsgHdrHashElement*>(iter.Get()); + if (element) + NS_IF_RELEASE(element->mHdr); + } + + if (reInit) + { + saveCachedHeaders->ClearAndPrepareForLength(m_cacheSize); + m_cachedHeaders = saveCachedHeaders; + } + else + { + delete saveCachedHeaders; + } + } + return NS_OK; +} + +nsresult nsMsgDatabase::RemoveHdrFromCache(nsIMsgDBHdr *hdr, nsMsgKey key) +{ + if (m_cachedHeaders) + { + if (key == nsMsgKey_None) + hdr->GetMessageKey(&key); + + PLDHashEntryHdr *entry = + m_cachedHeaders->Search((const void *)(uintptr_t) key); + if (entry) + { + m_cachedHeaders->Remove((void *)(uintptr_t) key); + NS_RELEASE(hdr); // get rid of extra ref the cache was holding. + } + + } + return NS_OK; +} + + +nsresult nsMsgDatabase::GetHdrFromUseCache(nsMsgKey key, nsIMsgDBHdr* *result) +{ + if (!result) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_ERROR_FAILURE; + + *result = nullptr; + + if (m_headersInUse) + { + PLDHashEntryHdr *entry = + m_headersInUse->Search((const void *)(uintptr_t) key); + if (entry) + { + MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry); + *result = element->mHdr; + } + if (*result) + { + NS_ADDREF(*result); + rv = NS_OK; + } + } + return rv; +} + +PLDHashTableOps nsMsgDatabase::gMsgDBHashTableOps = +{ + HashKey, + MatchEntry, + MoveEntry, + ClearEntry, + nullptr +}; + +// HashKey is supposed to maximize entropy in the low order bits, and the key +// as is, should do that. +PLDHashNumber +nsMsgDatabase::HashKey(const void* aKey) +{ + return PLDHashNumber(NS_PTR_TO_INT32(aKey)); +} + +bool +nsMsgDatabase::MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) +{ + const MsgHdrHashElement* hdr = static_cast<const MsgHdrHashElement*>(aEntry); + return aKey == (const void *)(uintptr_t) hdr->mKey; // ### or get the key from the hdr... +} + +void +nsMsgDatabase::MoveEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, PLDHashEntryHdr* aTo) +{ + const MsgHdrHashElement* from = static_cast<const MsgHdrHashElement*>(aFrom); + MsgHdrHashElement* to = static_cast<MsgHdrHashElement*>(aTo); + // ### eh? Why is this needed? I don't think we have a copy operator? + *to = *from; +} + +void +nsMsgDatabase::ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) +{ + MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(aEntry); + element->mHdr = nullptr; // eh? Need to release this or not? + element->mKey = nsMsgKey_None; // eh? +} + + +nsresult nsMsgDatabase::AddHdrToUseCache(nsIMsgDBHdr *hdr, nsMsgKey key) +{ + if (!m_headersInUse) + { + mdb_count numHdrs = MSG_HASH_SIZE; + if (m_mdbAllMsgHeadersTable) + m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs); + m_headersInUse = new PLDHashTable(&gMsgDBHashTableOps, sizeof(struct MsgHdrHashElement), std::max((mdb_count)MSG_HASH_SIZE, numHdrs)); + } + if (m_headersInUse) + { + if (key == nsMsgKey_None) + hdr->GetMessageKey(&key); + PLDHashEntryHdr *entry = m_headersInUse->Add((void *)(uintptr_t) key, mozilla::fallible); + if (!entry) + return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory + + MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry); + element->mHdr = hdr; + element->mKey = key; + // the hash table won't add ref, we'll do it ourselves + // stand for the addref that CreateMsgHdr normally does. + NS_ADDREF(hdr); + return NS_OK; + } + + return NS_ERROR_OUT_OF_MEMORY; +} + +nsresult nsMsgDatabase::ClearUseHdrCache() +{ + if (m_headersInUse) + { + // clear mdb row pointers of any headers still in use, because the + // underlying db is going away. + for (auto iter = m_headersInUse->Iter(); !iter.Done(); iter.Next()) { + auto element = static_cast<const MsgHdrHashElement*>(iter.Get()); + if (element && element->mHdr) { + nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(element->mHdr); // closed system, so this is ok + // clear out m_mdbRow member variable - the db is going away, which means that this member + // variable might very well point to a mork db that is gone. + NS_IF_RELEASE(msgHdr->m_mdbRow); + // NS_IF_RELEASE(msgHdr->m_mdb); + } + } + delete m_headersInUse; + m_headersInUse = nullptr; + } + return NS_OK; +} + +nsresult nsMsgDatabase::RemoveHdrFromUseCache(nsIMsgDBHdr *hdr, nsMsgKey key) +{ + if (m_headersInUse) + { + if (key == nsMsgKey_None) + hdr->GetMessageKey(&key); + + m_headersInUse->Remove((void *)(uintptr_t) key); + } + return NS_OK; +} + + +nsresult +nsMsgDatabase::CreateMsgHdr(nsIMdbRow* hdrRow, nsMsgKey key, nsIMsgDBHdr* *result) +{ + NS_ENSURE_ARG_POINTER(hdrRow); + NS_ENSURE_ARG_POINTER(result); + + nsresult rv = GetHdrFromUseCache(key, result); + if (NS_SUCCEEDED(rv) && *result) + { + hdrRow->Release(); + return rv; + } + + nsMsgHdr *msgHdr = new nsMsgHdr(this, hdrRow); + if(!msgHdr) + return NS_ERROR_OUT_OF_MEMORY; + msgHdr->SetMessageKey(key); + // don't need to addref here; GetHdrFromUseCache addrefs. + *result = msgHdr; + + AddHdrToCache(msgHdr, key); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::AddListener(nsIDBChangeListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + m_ChangeListeners.AppendElementUnlessExists(aListener); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::RemoveListener(nsIDBChangeListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + m_ChangeListeners.RemoveElement(aListener); + return NS_OK; +} + +// XXX should we return rv for listener->propertyfunc_? +#define NOTIFY_LISTENERS(propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator iter(m_ChangeListeners); \ + nsCOMPtr<nsIDBChangeListener> listener; \ + while (iter.HasMore()) { \ + listener = iter.GetNext(); \ + listener->propertyfunc_ params_; \ + } \ + PR_END_MACRO + +// change announcer methods - just broadcast to all listeners. +NS_IMETHODIMP nsMsgDatabase::NotifyHdrChangeAll(nsIMsgDBHdr *aHdrChanged, + uint32_t aOldFlags, + uint32_t aNewFlags, + nsIDBChangeListener *aInstigator) +{ + // We will only notify the change if the header exists in the database. + // This allows database functions to be usable in both the case where the + // header is in the db, or the header is not so no notifications should be + // given. + nsMsgKey key; + bool inDb = false; + if (aHdrChanged) + { + aHdrChanged->GetMessageKey(&key); + ContainsKey(key, &inDb); + } + if (inDb) + NOTIFY_LISTENERS(OnHdrFlagsChanged, + (aHdrChanged, aOldFlags, aNewFlags, aInstigator)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::NotifyReadChanged(nsIDBChangeListener *aInstigator) +{ + NOTIFY_LISTENERS(OnReadChanged, (aInstigator)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::NotifyJunkScoreChanged(nsIDBChangeListener *aInstigator) +{ + NOTIFY_LISTENERS(OnJunkScoreChanged, (aInstigator)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::NotifyHdrDeletedAll(nsIMsgDBHdr *aHdrDeleted, + nsMsgKey aParentKey, + int32_t aFlags, + nsIDBChangeListener *aInstigator) +{ + NOTIFY_LISTENERS(OnHdrDeleted, (aHdrDeleted, aParentKey, aFlags, aInstigator)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::NotifyHdrAddedAll(nsIMsgDBHdr *aHdrAdded, + nsMsgKey aParentKey, + int32_t aFlags, + nsIDBChangeListener *aInstigator) +{ +#ifdef DEBUG_bienvenu1 + printf("notifying add of %ld parent %ld\n", keyAdded, parentKey); +#endif + NOTIFY_LISTENERS(OnHdrAdded, (aHdrAdded, aParentKey, aFlags, aInstigator)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::NotifyParentChangedAll(nsMsgKey aKeyReparented, + nsMsgKey aOldParent, + nsMsgKey aNewParent, + nsIDBChangeListener *aInstigator) +{ + NOTIFY_LISTENERS(OnParentChanged, + (aKeyReparented, aOldParent, aNewParent, aInstigator)); + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDatabase::NotifyAnnouncerGoingAway(void) +{ + NOTIFY_LISTENERS(OnAnnouncerGoingAway, (this)); + return NS_OK; +} + +bool nsMsgDatabase::MatchDbName(nsIFile *dbName) // returns true if they match +{ + nsCString dbPath; + dbName->GetNativePath(dbPath); + return dbPath.Equals(m_dbName); +} + +void nsMsgDBService::AddToCache(nsMsgDatabase* pMessageDB) +{ +#ifdef DEBUG_David_Bienvenu + NS_ASSERTION(m_dbCache.Length() < 50, "50 or more open db's"); +#endif +#ifdef DEBUG + if (pMessageDB->m_folder) + { + nsCOMPtr<nsIMsgDatabase> msgDB; + CachedDBForFolder(pMessageDB->m_folder, getter_AddRefs(msgDB)); + NS_ASSERTION(!msgDB, "shouldn't have db in cache"); + } +#endif + m_dbCache.AppendElement(pMessageDB); +} + +/** + * Log the open db's, and how many headers are in memory. + */ +void nsMsgDBService::DumpCache() +{ + nsMsgDatabase* db = nullptr; + MOZ_LOG(DBLog, LogLevel::Info, ("%d open DB's\n", m_dbCache.Length())); + for (uint32_t i = 0; i < m_dbCache.Length(); i++) + { + db = m_dbCache.ElementAt(i); + MOZ_LOG(DBLog, LogLevel::Info, ("%s - %ld hdrs in use\n", + (const char*)db->m_dbName.get(), + db->m_headersInUse ? db->m_headersInUse->EntryCount() : 0)); + } +} + +// Memory Reporting implementations + +size_t nsMsgDatabase::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t totalSize = 0; + if (m_dbFolderInfo) + totalSize += m_dbFolderInfo->SizeOfExcludingThis(aMallocSizeOf); + if (m_mdbEnv) + { + nsIMdbHeap *morkHeap = nullptr; + m_mdbEnv->GetHeap(&morkHeap); + if (morkHeap) + totalSize += morkHeap->GetUsedSize(); + } + totalSize += m_newSet.ShallowSizeOfExcludingThis(aMallocSizeOf); + totalSize += m_ChangeListeners.ShallowSizeOfExcludingThis(aMallocSizeOf); + totalSize += m_threads.ShallowSizeOfExcludingThis(aMallocSizeOf); + // We have two tables of header objects, but every header in m_cachedHeaders + // should be in m_headersInUse. + // double-counting... + size_t headerSize = 0; + if (m_headersInUse) + { + headerSize = m_headersInUse->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto iter = m_headersInUse->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<MsgHdrHashElement*>(iter.Get()); + // Sigh, this is dangerous, but so long as this is a closed system, this + // is safe. + headerSize += static_cast<nsMsgHdr*>(entry->mHdr)->SizeOfIncludingThis(aMallocSizeOf); + } + } + totalSize += headerSize; + if (m_msgReferences) + totalSize += m_msgReferences->ShallowSizeOfIncludingThis(aMallocSizeOf); + return totalSize; +} + +namespace mozilla { +namespace mailnews { + +MOZ_DEFINE_MALLOC_SIZE_OF(GetMallocSize) + +class MsgDBReporter final : public nsIMemoryReporter +{ + nsMsgDatabase *mDatabase; +public: + MsgDBReporter(nsMsgDatabase *db) : mDatabase(db) {} + + NS_DECL_ISUPPORTS + NS_IMETHOD GetName(nsACString &aName) + { + aName.AssignLiteral("msg-database-objects"); + return NS_OK; + } + + NS_IMETHOD CollectReports(nsIMemoryReporterCallback*aCb, + nsISupports* aClosure, + bool aAnonymize) override + { + nsCString path; + GetPath(path, aAnonymize); + return aCb->Callback(EmptyCString(), path, + nsIMemoryReporter::KIND_HEAP, + nsIMemoryReporter::UNITS_BYTES, + mDatabase->SizeOfIncludingThis(GetMallocSize), + NS_LITERAL_CSTRING("Memory used for the folder database."), + aClosure); + } + + void GetPath(nsACString &memoryPath, bool aAnonymize) + { + memoryPath.AssignLiteral("explicit/maildb/database("); + nsCOMPtr<nsIMsgFolder> folder; + mDatabase->GetFolder(getter_AddRefs(folder)); + if (folder) + { + if (aAnonymize) + memoryPath.AppendLiteral("<anonymized>"); + else + { + nsAutoCString folderURL; + folder->GetFolderURL(folderURL); + MsgReplaceChar(folderURL, '/', '\\'); + memoryPath += folderURL; + } + } else { + memoryPath.AppendLiteral("UNKNOWN-FOLDER"); + } + memoryPath.Append(')'); + } + +private: + ~MsgDBReporter() {} +}; + +NS_IMPL_ISUPPORTS(MsgDBReporter, nsIMemoryReporter) +} +} + +nsMsgDatabase::nsMsgDatabase() + : m_dbFolderInfo(nullptr), + m_nextPseudoMsgKey(kFirstPseudoKey), + m_mdbEnv(nullptr), m_mdbStore(nullptr), + m_mdbAllMsgHeadersTable(nullptr), m_mdbAllThreadsTable(nullptr), + m_create(false), + m_leaveInvalidDB(false), + m_dbName(""), + m_mdbTokensInitialized(false), + m_hdrRowScopeToken(0), + m_hdrTableKindToken(0), + m_threadTableKindToken(0), + m_subjectColumnToken(0), + m_senderColumnToken(0), + m_messageIdColumnToken(0), + m_referencesColumnToken(0), + m_recipientsColumnToken(0), + m_dateColumnToken(0), + m_messageSizeColumnToken(0), + m_flagsColumnToken(0), + m_priorityColumnToken(0), + m_labelColumnToken(0), + m_statusOffsetColumnToken(0), + m_numLinesColumnToken(0), + m_ccListColumnToken(0), + m_bccListColumnToken(0), + m_threadFlagsColumnToken(0), + m_threadIdColumnToken(0), + m_threadChildrenColumnToken(0), + m_threadUnreadChildrenColumnToken(0), + m_messageThreadIdColumnToken(0), + m_threadSubjectColumnToken(0), + m_messageCharSetColumnToken(0), + m_threadParentColumnToken(0), + m_threadRootKeyColumnToken(0), + m_threadNewestMsgDateColumnToken(0), + m_offlineMsgOffsetColumnToken(0), + m_offlineMessageSizeColumnToken(0), + m_headersInUse(nullptr), + m_cachedHeaders(nullptr), + m_bCacheHeaders(true), + m_cachedThreadId(nsMsgKey_None), + m_msgReferences(nullptr), + m_cacheSize(kMaxHdrsInCache) +{ + mMemReporter = new mozilla::mailnews::MsgDBReporter(this); + mozilla::RegisterWeakMemoryReporter(mMemReporter); +} + +nsMsgDatabase::~nsMsgDatabase() +{ + mozilla::UnregisterWeakMemoryReporter(mMemReporter); + // Close(FALSE); // better have already been closed. + ClearCachedObjects(true); + ClearEnumerators(); + delete m_cachedHeaders; + delete m_headersInUse; + + if (m_msgReferences) + { + delete m_msgReferences; + m_msgReferences = nullptr; + } + + MOZ_LOG(DBLog, LogLevel::Info, ("closing database %s\n", + (const char*)m_dbName.get())); + + nsCOMPtr<nsIMsgDBService> serv(do_GetService(NS_MSGDB_SERVICE_CONTRACTID)); + if (serv) + static_cast<nsMsgDBService*>(serv.get())->RemoveFromCache(this); + + // if the db folder info refers to the mdb db, we must clear it because + // the reference will be a dangling one soon. + if (m_dbFolderInfo) + m_dbFolderInfo->ReleaseExternalReferences(); + + NS_IF_RELEASE(m_dbFolderInfo); + if (m_mdbAllMsgHeadersTable) + m_mdbAllMsgHeadersTable->Release(); + + if (m_mdbAllThreadsTable) + m_mdbAllThreadsTable->Release(); + + if (m_mdbStore) + m_mdbStore->Release(); + + if (m_mdbEnv) + { + m_mdbEnv->Release(); //??? is this right? + m_mdbEnv = nullptr; + } + m_ChangeListeners.Clear(); +} + +NS_IMPL_ISUPPORTS(nsMsgDatabase, nsIMsgDatabase, nsIDBChangeAnnouncer) + +void nsMsgDatabase::GetMDBFactory(nsIMdbFactory ** aMdbFactory) +{ + if (!mMdbFactory) + { + nsresult rv; + nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && mdbFactoryService) + mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory)); + } + NS_IF_ADDREF(*aMdbFactory = mMdbFactory); +} + +// aLeaveInvalidDB: true if caller wants back a db even out of date. +// If so, they'll extract out the interesting info from the db, close it, +// delete it, and then try to open the db again, prior to reparsing. +nsresult nsMsgDatabase::Open(nsMsgDBService *aDBService, nsIFile *aFolderName, + bool aCreate, bool aLeaveInvalidDB) +{ + return nsMsgDatabase::OpenInternal(aDBService, aFolderName, aCreate, aLeaveInvalidDB, + true /* open synchronously */); +} + +nsresult nsMsgDatabase::OpenInternal(nsMsgDBService *aDBService, + nsIFile *summaryFile, bool aCreate, + bool aLeaveInvalidDB, bool sync) +{ + nsAutoCString summaryFilePath; + summaryFile->GetNativePath(summaryFilePath); + + MOZ_LOG(DBLog, LogLevel::Info, ("nsMsgDatabase::Open(%s, %s, %p, %s)\n", + (const char*)summaryFilePath.get(), aCreate ? "TRUE":"FALSE", + this, aLeaveInvalidDB ? "TRUE":"FALSE")); + + + nsresult rv = OpenMDB(summaryFilePath.get(), aCreate, sync); + if (NS_FAILED(rv)) + MOZ_LOG(DBLog, LogLevel::Info, ("error opening db %lx", rv)); + + if (MOZ_LOG_TEST(DBLog, LogLevel::Debug)) + aDBService->DumpCache(); + + if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) + return rv; + + m_create = aCreate; + m_leaveInvalidDB = aLeaveInvalidDB; + if (!sync && NS_SUCCEEDED(rv)) + { + aDBService->AddToCache(this); + // remember open options for when the parsing is complete. + return rv; + } + return CheckForErrors(rv, true, aDBService, summaryFile); +} + +nsresult nsMsgDatabase::CheckForErrors(nsresult err, bool sync, + nsMsgDBService *aDBService, + nsIFile *summaryFile) +{ + nsCOMPtr<nsIDBFolderInfo> folderInfo; + bool summaryFileExists; + bool newFile = false; + bool deleteInvalidDB = false; + + bool exists; + int64_t fileSize; + summaryFile->Exists(&exists); + summaryFile->GetFileSize(&fileSize); + // if the old summary doesn't exist, we're creating a new one. + if ((!exists || !fileSize) && m_create) + newFile = true; + + summaryFileExists = exists && fileSize > 0; + + if (NS_SUCCEEDED(err)) + { + if (!m_dbFolderInfo) + { + err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + else + { + if (!newFile && summaryFileExists) + { + bool valid = false; + nsresult rv = GetSummaryValid(&valid); + if (NS_FAILED(rv) || !valid) + err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + // compare current version of db versus filed out version info. + uint32_t version; + m_dbFolderInfo->GetVersion(&version); + if (GetCurVersion() != version) + err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + + // Check if we should force a reparse because, for example, we have + // reached the key limit. + bool forceReparse; + m_dbFolderInfo->GetBooleanProperty("forceReparse", false, &forceReparse); + if (forceReparse) + { + NS_WARNING("Forcing a reparse presumably because key limit reached"); + err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + + } + if (NS_FAILED(err) && !m_leaveInvalidDB) + deleteInvalidDB = true; + } + else + { + err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + deleteInvalidDB = true; + } + + if (deleteInvalidDB) + { + // this will make the db folder info release its ref to the mail db... + NS_IF_RELEASE(m_dbFolderInfo); + ForceClosed(); + if (err == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + summaryFile->Remove(false); + } + if (NS_FAILED(err) || newFile) + { + // if we couldn't open file, or we have a blank one, and we're supposed + // to upgrade, updgrade it. + if (newFile && !m_leaveInvalidDB) // caller is upgrading, and we have empty summary file, + { // leave db around and open so caller can upgrade it. + err = NS_MSG_ERROR_FOLDER_SUMMARY_MISSING; + } + else if (NS_FAILED(err) && err != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + { + Close(false); + summaryFile->Remove(false); // blow away the db if it's corrupt. + } + } + if (sync && (NS_SUCCEEDED(err) || err == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)) + aDBService->AddToCache(this); + return (summaryFileExists) ? err : NS_MSG_ERROR_FOLDER_SUMMARY_MISSING; +} + +/** + * Open the MDB database synchronously or async based on sync argument. + * If successful, this routine will set up the m_mdbStore and m_mdbEnv of + * the database object so other database calls can work. + */ +nsresult nsMsgDatabase::OpenMDB(const char *dbName, bool create, bool sync) +{ + nsresult ret = NS_OK; + nsCOMPtr<nsIMdbFactory> mdbFactory; + GetMDBFactory(getter_AddRefs(mdbFactory)); + if (mdbFactory) + { + ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv); + if (NS_SUCCEEDED(ret)) + { + nsIMdbHeap* dbHeap = nullptr; + + if (m_mdbEnv) + m_mdbEnv->SetAutoClear(true); + m_dbName = dbName; + bool exists = false; + nsCOMPtr<nsIFile> dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &ret); + if (NS_SUCCEEDED(ret) && dbFile) { + ret = dbFile->InitWithNativePath(m_dbName); + if (NS_SUCCEEDED(ret)) + ret = dbFile->Exists(&exists); + } + if (!exists) + { + ret = NS_MSG_ERROR_FOLDER_SUMMARY_MISSING; + } + // If m_thumb is set, we're asynchronously opening the db already. + else if (!m_thumb) + { + mdbOpenPolicy inOpenPolicy; + mdb_bool canOpen; + mdbYarn outFormatVersion; + + nsIMdbFile* oldFile = nullptr; + ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, dbName, + mdbBool_kFalse, // not readonly, we want modifiable + &oldFile); + if (oldFile) + { + if (NS_SUCCEEDED(ret)) + { + ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // the file to investigate + &canOpen, &outFormatVersion); + if (NS_SUCCEEDED(ret) && canOpen) + { + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap, + oldFile, &inOpenPolicy, getter_AddRefs(m_thumb)); + } + else + ret = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + NS_RELEASE(oldFile); // always release our file ref, store has own + } + } + if (NS_SUCCEEDED(ret) && m_thumb && sync) + { + mdb_count outTotal; // total somethings to do in operation + mdb_count outCurrent; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken; // is operation irreparably dead and broken? + do + { + ret = m_thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken); + if (NS_FAILED(ret)) + {// mork isn't really doing NS errors yet. + outDone = true; + break; + } + } + while (NS_SUCCEEDED(ret) && !outBroken && !outDone); + // m_mdbEnv->ClearErrors(); // ### temporary... + // only 0 is a non-error return. + if (NS_SUCCEEDED(ret) && outDone) + { + ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, m_thumb, &m_mdbStore); + if (NS_SUCCEEDED(ret)) + ret = (m_mdbStore) ? InitExistingDB() : NS_ERROR_FAILURE; + } +#ifdef DEBUG_bienvenu1 + DumpContents(); +#endif + m_thumb = nullptr; + } + else if (create) // ### need error code saying why open file store failed + { + nsIMdbFile* newFile = 0; + ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, dbName, &newFile); + if (NS_FAILED(ret)) + ret = NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + if ( newFile ) + { + if (NS_SUCCEEDED(ret)) + { + mdbOpenPolicy inOpenPolicy; + + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap, + newFile, &inOpenPolicy, &m_mdbStore); + if (NS_SUCCEEDED(ret)) + ret = (m_mdbStore) ? InitNewDB() : NS_ERROR_FAILURE; + } + NS_RELEASE(newFile); // always release our file ref, store has own + } + } + } + } +#ifdef DEBUG_David_Bienvenu +// NS_ASSERTION(NS_SUCCEEDED(ret), "failed opening mdb"); +#endif + return ret; +} + +nsresult nsMsgDatabase::CloseMDB(bool commit) +{ + if (commit) + Commit(nsMsgDBCommitType::kSessionCommit); + return(NS_OK); +} + + +// force the database to close - this'll flush out anybody holding onto +// a database without having a listener! +// This is evil in the com world, but there are times we need to delete the file. +NS_IMETHODIMP nsMsgDatabase::ForceClosed() +{ + nsresult err = NS_OK; + + // make sure someone has a reference so object won't get deleted out from under us. + AddRef(); + NotifyAnnouncerGoingAway(); + // make sure dbFolderInfo isn't holding onto mork stuff because mork db is going away + if (m_dbFolderInfo) + m_dbFolderInfo->ReleaseExternalReferences(); + NS_IF_RELEASE(m_dbFolderInfo); + + err = CloseMDB(true); // Backup DB will try to recover info, so commit + ClearCachedObjects(true); + ClearEnumerators(); +if (m_mdbAllMsgHeadersTable) + { + m_mdbAllMsgHeadersTable->Release(); + m_mdbAllMsgHeadersTable = nullptr; + } + if (m_mdbAllThreadsTable) + { + m_mdbAllThreadsTable->Release(); + m_mdbAllThreadsTable = nullptr; + } + if (m_mdbStore) + { + m_mdbStore->Release(); + m_mdbStore = nullptr; + } + + // better not be any listeners, because we're going away. + NS_ASSERTION(m_ChangeListeners.IsEmpty(), "shouldn't have any listeners left"); + + Release(); + return err; +} + +NS_IMETHODIMP nsMsgDatabase::GetDBFolderInfo(nsIDBFolderInfo **result) +{ + if (!m_dbFolderInfo) + { + NS_ERROR("db must be corrupt"); + return NS_ERROR_NULL_POINTER; + } + NS_ADDREF(*result = m_dbFolderInfo); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetFolder(nsIMsgFolder **aFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + NS_IF_ADDREF(*aFolder = m_folder); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::Commit(nsMsgDBCommit commitType) +{ + nsresult err = NS_OK; + nsCOMPtr<nsIMdbThumb> commitThumb; + + RememberLastUseTime(); + if (commitType == nsMsgDBCommitType::kLargeCommit || commitType == nsMsgDBCommitType::kSessionCommit) + { + mdb_percent outActualWaste = 0; + mdb_bool outShould; + if (m_mdbStore) { + err = m_mdbStore->ShouldCompress(GetEnv(), 30, &outActualWaste, &outShould); + if (NS_SUCCEEDED(err) && outShould) + commitType = nsMsgDBCommitType::kCompressCommit; + } + } + // commitType = nsMsgDBCommitType::kCompressCommit; // ### until incremental writing works. + + if (m_mdbStore) + { + switch (commitType) + { + case nsMsgDBCommitType::kLargeCommit: + err = m_mdbStore->LargeCommit(GetEnv(), getter_AddRefs(commitThumb)); + break; + case nsMsgDBCommitType::kSessionCommit: + err = m_mdbStore->SessionCommit(GetEnv(), getter_AddRefs(commitThumb)); + break; + case nsMsgDBCommitType::kCompressCommit: + err = m_mdbStore->CompressCommit(GetEnv(), getter_AddRefs(commitThumb)); + break; + } + } + if (commitThumb) + { + mdb_count outTotal = 0; // total somethings to do in operation + mdb_count outCurrent = 0; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken = false; // is operation irreparably dead and broken? + while (!outDone && !outBroken && NS_SUCCEEDED(err)) + { + err = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone, &outBroken); + } + + } + // ### do something with error, but clear it now because mork errors out on commits. + if (GetEnv()) + GetEnv()->ClearErrors(); + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && accountManager) + { + nsCOMPtr<nsIMsgFolderCache> folderCache; + + rv = accountManager->GetFolderCache(getter_AddRefs(folderCache)); + if (NS_SUCCEEDED(rv) && folderCache) + { + nsCOMPtr <nsIMsgFolderCacheElement> cacheElement; + rv = folderCache->GetCacheElement(m_dbName, false, getter_AddRefs(cacheElement)); + if (NS_SUCCEEDED(rv) && cacheElement && m_dbFolderInfo) + { + int32_t totalMessages, unreadMessages, pendingMessages, pendingUnreadMessages; + + m_dbFolderInfo->GetNumMessages(&totalMessages); + m_dbFolderInfo->GetNumUnreadMessages(&unreadMessages); + m_dbFolderInfo->GetImapUnreadPendingMessages(&pendingUnreadMessages); + m_dbFolderInfo->GetImapTotalPendingMessages(&pendingMessages); + cacheElement->SetInt32Property("totalMsgs", totalMessages); + cacheElement->SetInt32Property("totalUnreadMsgs", unreadMessages); + cacheElement->SetInt32Property("pendingMsgs", pendingMessages); + cacheElement->SetInt32Property("pendingUnreadMsgs", pendingUnreadMessages); + folderCache->Commit(false); + } + } + } + + return err; +} + +NS_IMETHODIMP nsMsgDatabase::Close(bool forceCommit /* = TRUE */) +{ + return CloseMDB(forceCommit); +} + +const char *kMsgHdrsScope = "ns:msg:db:row:scope:msgs:all"; // scope for all headers table +const char *kMsgHdrsTableKind = "ns:msg:db:table:kind:msgs"; +const char *kThreadTableKind = "ns:msg:db:table:kind:thread"; +const char *kThreadHdrsScope = "ns:msg:db:row:scope:threads:all"; // scope for all threads table +const char *kAllThreadsTableKind = "ns:msg:db:table:kind:allthreads"; // kind for table of all threads +const char *kSubjectColumnName = "subject"; +const char *kSenderColumnName = "sender"; +const char *kMessageIdColumnName = "message-id"; +const char *kReferencesColumnName = "references"; +const char *kRecipientsColumnName = "recipients"; +const char *kDateColumnName = "date"; +const char *kMessageSizeColumnName = "size"; +const char *kFlagsColumnName = "flags"; +const char *kPriorityColumnName = "priority"; +const char *kLabelColumnName = "label"; +const char *kStatusOffsetColumnName = "statusOfset"; +const char *kNumLinesColumnName = "numLines"; +const char *kCCListColumnName = "ccList"; +const char *kBCCListColumnName = "bccList"; +const char *kMessageThreadIdColumnName = "msgThreadId"; +const char *kThreadFlagsColumnName = "threadFlags"; +const char *kThreadIdColumnName = "threadId"; +const char *kThreadChildrenColumnName = "children"; +const char *kThreadUnreadChildrenColumnName = "unreadChildren"; +const char *kThreadSubjectColumnName = "threadSubject"; +const char *kMessageCharSetColumnName = "msgCharSet"; +const char *kThreadParentColumnName = "threadParent"; +const char *kThreadRootColumnName = "threadRoot"; +const char *kThreadNewestMsgDateColumnName = "threadNewestMsgDate"; +const char *kOfflineMsgOffsetColumnName = "msgOffset"; +const char *kOfflineMsgSizeColumnName = "offlineMsgSize"; +struct mdbOid gAllMsgHdrsTableOID; +struct mdbOid gAllThreadsTableOID; +const char *kFixedBadRefThreadingProp = "fixedBadRefThreading"; + +// set up empty tables, dbFolderInfo, etc. +nsresult nsMsgDatabase::InitNewDB() +{ + nsresult err = NS_OK; + + err = InitMDBInfo(); + if (NS_SUCCEEDED(err)) + { + nsDBFolderInfo *dbFolderInfo = new nsDBFolderInfo(this); + if (dbFolderInfo) + { + NS_ADDREF(dbFolderInfo); + err = dbFolderInfo->AddToNewMDB(); + dbFolderInfo->SetVersion(GetCurVersion()); + dbFolderInfo->SetBooleanProperty("forceReparse", false); + dbFolderInfo->SetBooleanProperty(kFixedBadRefThreadingProp, true); + nsIMdbStore *store = GetStore(); + // create the unique table for the dbFolderInfo. + struct mdbOid allMsgHdrsTableOID; + struct mdbOid allThreadsTableOID; + if (!store) + return NS_ERROR_NULL_POINTER; + + allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken; + allMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey; + allThreadsTableOID.mOid_Scope = m_threadRowScopeToken; + allThreadsTableOID.mOid_Id = kAllThreadsTableKey; + + // TODO: check this error value? + (void) store->NewTableWithOid(GetEnv(), &allMsgHdrsTableOID, m_hdrTableKindToken, + false, nullptr, &m_mdbAllMsgHeadersTable); + + // error here is not fatal. + (void) store->NewTableWithOid(GetEnv(), &allThreadsTableOID, m_allThreadsTableKindToken, + false, nullptr, &m_mdbAllThreadsTable); + + m_dbFolderInfo = dbFolderInfo; + + } + else + err = NS_ERROR_OUT_OF_MEMORY; + } + return err; +} + +nsresult nsMsgDatabase::GetTableCreateIfMissing(const char *scope, const char *kind, nsIMdbTable **table, + mdb_token &scopeToken, mdb_token &kindToken) +{ + struct mdbOid tableOID; + + if (!m_mdbStore) + return NS_ERROR_FAILURE; + (void) m_mdbStore->StringToToken(GetEnv(), scope, &scopeToken); + (void) m_mdbStore->StringToToken(GetEnv(), kind, &kindToken); + tableOID.mOid_Scope = scopeToken; + tableOID.mOid_Id = 1; + + nsresult rv = m_mdbStore->GetTable(GetEnv(), &tableOID, table); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // create new all all offline ops table, if it doesn't exist. + if (NS_SUCCEEDED(rv) && !*table) + { + rv = m_mdbStore->NewTable(GetEnv(), scopeToken,kindToken, + false, nullptr, table); + if (NS_FAILED(rv) || !*table) + rv = NS_ERROR_FAILURE; + } + NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't create offline ops table"); + return rv; +} + +nsresult nsMsgDatabase::InitExistingDB() +{ + nsresult err = NS_OK; + + err = InitMDBInfo(); + if (NS_SUCCEEDED(err)) + { + err = GetStore()->GetTable(GetEnv(), &gAllMsgHdrsTableOID, &m_mdbAllMsgHeadersTable); + if (NS_SUCCEEDED(err)) + { + m_dbFolderInfo = new nsDBFolderInfo(this); + if (m_dbFolderInfo) + { + NS_ADDREF(m_dbFolderInfo); + err = m_dbFolderInfo->InitFromExistingDB(); + } + } + else + err = NS_ERROR_FAILURE; + + NS_ASSERTION(NS_SUCCEEDED(err), "failed initing existing db"); + NS_ENSURE_SUCCESS(err, err); + // create new all msg hdrs table, if it doesn't exist. + if (NS_SUCCEEDED(err) && !m_mdbAllMsgHeadersTable) + { + struct mdbOid allMsgHdrsTableOID; + allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken; + allMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey; + + nsresult mdberr = GetStore()->NewTableWithOid(GetEnv(), &allMsgHdrsTableOID, m_hdrTableKindToken, + false, nullptr, &m_mdbAllMsgHeadersTable); + if (NS_FAILED(mdberr) || !m_mdbAllMsgHeadersTable) + err = NS_ERROR_FAILURE; + } + struct mdbOid allThreadsTableOID; + allThreadsTableOID.mOid_Scope = m_threadRowScopeToken; + allThreadsTableOID.mOid_Id = kAllThreadsTableKey; + err = GetStore()->GetTable(GetEnv(), &gAllThreadsTableOID, &m_mdbAllThreadsTable); + if (!m_mdbAllThreadsTable) + { + + nsresult mdberr = GetStore()->NewTableWithOid(GetEnv(), &allThreadsTableOID, m_allThreadsTableKindToken, + false, nullptr, &m_mdbAllThreadsTable); + if (NS_FAILED(mdberr) || !m_mdbAllThreadsTable) + err = NS_ERROR_FAILURE; + } + } + if (NS_SUCCEEDED(err) && m_dbFolderInfo) + { + bool fixedBadRefThreading; + m_dbFolderInfo->GetBooleanProperty(kFixedBadRefThreadingProp, false, &fixedBadRefThreading); + if (!fixedBadRefThreading) + { + nsCOMPtr <nsISimpleEnumerator> enumerator; + err = EnumerateMessages(getter_AddRefs(enumerator)); + if (NS_SUCCEEDED(err) && enumerator) + { + bool hasMore; + + while (NS_SUCCEEDED(err = enumerator->HasMoreElements(&hasMore)) && + hasMore) + { + nsCOMPtr <nsISupports> supports; + err = enumerator->GetNext(getter_AddRefs(supports)); + NS_ASSERTION(NS_SUCCEEDED(err), "nsMsgDBEnumerator broken"); + nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(supports); + if (msgHdr && NS_SUCCEEDED(err)) + { + nsCString messageId; + nsAutoCString firstReference; + msgHdr->GetMessageId(getter_Copies(messageId)); + msgHdr->GetStringReference(0, firstReference); + if (messageId.Equals(firstReference)) + { + err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + break; + } + } + } + } + + m_dbFolderInfo->SetBooleanProperty(kFixedBadRefThreadingProp, true); + } + + } + return err; +} + +// initialize the various tokens and tables in our db's env +nsresult nsMsgDatabase::InitMDBInfo() +{ + nsresult err = NS_OK; + + if (!m_mdbTokensInitialized && GetStore()) + { + m_mdbTokensInitialized = true; + err = GetStore()->StringToToken(GetEnv(), kMsgHdrsScope, &m_hdrRowScopeToken); + if (NS_SUCCEEDED(err)) + { + GetStore()->StringToToken(GetEnv(), kSubjectColumnName, &m_subjectColumnToken); + GetStore()->StringToToken(GetEnv(), kSenderColumnName, &m_senderColumnToken); + GetStore()->StringToToken(GetEnv(), kMessageIdColumnName, &m_messageIdColumnToken); + // if we just store references as a string, we won't get any savings from the + // fact there's a lot of duplication. So we may want to break them up into + // multiple columns, r1, r2, etc. + GetStore()->StringToToken(GetEnv(), kReferencesColumnName, &m_referencesColumnToken); + // similarly, recipients could be tokenized properties + GetStore()->StringToToken(GetEnv(), kRecipientsColumnName, &m_recipientsColumnToken); + GetStore()->StringToToken(GetEnv(), kDateColumnName, &m_dateColumnToken); + GetStore()->StringToToken(GetEnv(), kMessageSizeColumnName, &m_messageSizeColumnToken); + GetStore()->StringToToken(GetEnv(), kFlagsColumnName, &m_flagsColumnToken); + GetStore()->StringToToken(GetEnv(), kPriorityColumnName, &m_priorityColumnToken); + GetStore()->StringToToken(GetEnv(), kLabelColumnName, &m_labelColumnToken); + GetStore()->StringToToken(GetEnv(), kStatusOffsetColumnName, &m_statusOffsetColumnToken); + GetStore()->StringToToken(GetEnv(), kNumLinesColumnName, &m_numLinesColumnToken); + GetStore()->StringToToken(GetEnv(), kCCListColumnName, &m_ccListColumnToken); + GetStore()->StringToToken(GetEnv(), kBCCListColumnName, &m_bccListColumnToken); + GetStore()->StringToToken(GetEnv(), kMessageThreadIdColumnName, &m_messageThreadIdColumnToken); + GetStore()->StringToToken(GetEnv(), kThreadIdColumnName, &m_threadIdColumnToken); + GetStore()->StringToToken(GetEnv(), kThreadFlagsColumnName, &m_threadFlagsColumnToken); + GetStore()->StringToToken(GetEnv(), kThreadNewestMsgDateColumnName, &m_threadNewestMsgDateColumnToken); + GetStore()->StringToToken(GetEnv(), kThreadChildrenColumnName, &m_threadChildrenColumnToken); + GetStore()->StringToToken(GetEnv(), kThreadUnreadChildrenColumnName, &m_threadUnreadChildrenColumnToken); + GetStore()->StringToToken(GetEnv(), kThreadSubjectColumnName, &m_threadSubjectColumnToken); + GetStore()->StringToToken(GetEnv(), kMessageCharSetColumnName, &m_messageCharSetColumnToken); + err = GetStore()->StringToToken(GetEnv(), kMsgHdrsTableKind, &m_hdrTableKindToken); + if (NS_SUCCEEDED(err)) + err = GetStore()->StringToToken(GetEnv(), kThreadTableKind, &m_threadTableKindToken); + err = GetStore()->StringToToken(GetEnv(), kAllThreadsTableKind, &m_allThreadsTableKindToken); + err = GetStore()->StringToToken(GetEnv(), kThreadHdrsScope, &m_threadRowScopeToken); + err = GetStore()->StringToToken(GetEnv(), kThreadParentColumnName, &m_threadParentColumnToken); + err = GetStore()->StringToToken(GetEnv(), kThreadRootColumnName, &m_threadRootKeyColumnToken); + err = GetStore()->StringToToken(GetEnv(), kOfflineMsgOffsetColumnName, &m_offlineMsgOffsetColumnToken); + err = GetStore()->StringToToken(GetEnv(), kOfflineMsgSizeColumnName, &m_offlineMessageSizeColumnToken); + + if (NS_SUCCEEDED(err)) + { + // The table of all message hdrs will have table id 1. + gAllMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken; + gAllMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey; + gAllThreadsTableOID.mOid_Scope = m_threadRowScopeToken; + gAllThreadsTableOID.mOid_Id = kAllThreadsTableKey; + + } + } + } + return err; +} + +// Returns if the db contains this key +NS_IMETHODIMP nsMsgDatabase::ContainsKey(nsMsgKey key, bool *containsKey) +{ + + nsresult err = NS_OK; + mdb_bool hasOid; + mdbOid rowObjectId; + + if (!containsKey || !m_mdbAllMsgHeadersTable) + return NS_ERROR_NULL_POINTER; + *containsKey = false; + + rowObjectId.mOid_Id = key; + rowObjectId.mOid_Scope = m_hdrRowScopeToken; + err = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid); + if(NS_SUCCEEDED(err)) + *containsKey = hasOid; + + return err; +} + +// get a message header for the given key. Caller must release()! +NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForKey(nsMsgKey key, nsIMsgDBHdr **pmsgHdr) +{ + nsresult err = NS_OK; + mdb_bool hasOid; + mdbOid rowObjectId; + + // Because this may be called a lot, and we don't want gettimeofday() to show + // up in trace logs, we just remember the most recent time any db was used, + // which should be close enough for our purposes. + m_lastUseTime = gLastUseTime; + +#ifdef DEBUG_bienvenu1 + NS_ASSERTION(m_folder, "folder should be set"); +#endif + + if (!pmsgHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore) + return NS_ERROR_NULL_POINTER; + + *pmsgHdr = NULL; + err = GetHdrFromUseCache(key, pmsgHdr); + if (NS_SUCCEEDED(err) && *pmsgHdr) + return err; + + rowObjectId.mOid_Id = key; + rowObjectId.mOid_Scope = m_hdrRowScopeToken; + err = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid); + if (NS_SUCCEEDED(err) /* && hasOid */) + { + nsIMdbRow *hdrRow; + err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, &hdrRow); + + if (NS_SUCCEEDED(err)) + { + if (!hdrRow) + { + err = NS_ERROR_NULL_POINTER; + } + else + { + // NS_ASSERTION(hasOid, "we had oid, right?"); + err = CreateMsgHdr(hdrRow, key, pmsgHdr); + } + } + } + + return err; +} + +NS_IMETHODIMP nsMsgDatabase::StartBatch() +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::EndBatch() +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::DeleteMessage(nsMsgKey key, nsIDBChangeListener *instigator, bool commit) +{ + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (!msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; + + rv = DeleteHeader(msgHdr, instigator, commit, true); + return rv; +} + + +NS_IMETHODIMP nsMsgDatabase::DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys, nsIDBChangeListener *instigator) +{ + nsresult err = NS_OK; + + uint32_t kindex; + for (kindex = 0; kindex < aNumKeys; kindex++) + { + nsMsgKey key = nsMsgKeys[kindex]; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + bool hasKey; + + if (NS_SUCCEEDED(ContainsKey(key, &hasKey)) && hasKey) + { + err = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(err)) + { + err = NS_MSG_MESSAGE_NOT_FOUND; + break; + } + if (msgHdr) + err = DeleteHeader(msgHdr, instigator, kindex % 300 == 0, true); + if (NS_FAILED(err)) + break; + } + } + return err; +} + +nsresult nsMsgDatabase::AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr) +{ + uint32_t size = 0; + (void)msgHdr->GetMessageSize(&size); + return m_dbFolderInfo->ChangeExpungedBytes (size); +} + +NS_IMETHODIMP nsMsgDatabase::DeleteHeader(nsIMsgDBHdr *msg, nsIDBChangeListener *instigator, bool commit, bool notify) +{ + if (!msg) + return NS_ERROR_NULL_POINTER; + + nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(msg); // closed system, so this is ok + nsMsgKey key; + (void)msg->GetMessageKey(&key); + // only need to do this for mail - will this speed up news expiration? + SetHdrFlag(msg, true, nsMsgMessageFlags::Expunged); // tell mailbox (mail) + + bool hdrWasNew = m_newSet.BinaryIndexOf(key) != m_newSet.NoIndex; + m_newSet.RemoveElement(key); + + if (m_dbFolderInfo != NULL) + { + bool isRead; + m_dbFolderInfo->ChangeNumMessages(-1); + IsRead(key, &isRead); + if (!isRead) + m_dbFolderInfo->ChangeNumUnreadMessages(-1); + AdjustExpungedBytesOnDelete(msg); + } + + uint32_t flags; + nsMsgKey threadParent; + + //Save off flags and threadparent since they will no longer exist after we remove the header from the db. + if (notify) + { + (void)msg->GetFlags(&flags); + msg->GetThreadParent(&threadParent); + } + + RemoveHeaderFromThread(msgHdr); + if (notify) + { + // If deleted hdr was new, restore the new flag on flags + // so saved searches will know to reduce their new msg count. + if (hdrWasNew) + flags |= nsMsgMessageFlags::New; + NotifyHdrDeletedAll(msg, threadParent, flags, instigator); // tell listeners + } + // if (!onlyRemoveFromThread) // to speed up expiration, try this. But really need to do this in RemoveHeaderFromDB + nsresult ret = RemoveHeaderFromDB(msgHdr); + + + if (commit) + Commit(nsMsgDBCommitType::kLargeCommit); // ### dmb is this a good time to commit? + return ret; +} + +NS_IMETHODIMP +nsMsgDatabase::UndoDelete(nsIMsgDBHdr *aMsgHdr) +{ + if (aMsgHdr) + { + nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(aMsgHdr); // closed system, so this is ok + // force deleted flag, so SetHdrFlag won't bail out because deleted flag isn't set + msgHdr->m_flags |= nsMsgMessageFlags::Expunged; + SetHdrFlag(msgHdr, false, nsMsgMessageFlags::Expunged); // clear deleted flag in db + } + return NS_OK; +} + +nsresult nsMsgDatabase::RemoveHeaderFromThread(nsMsgHdr *msgHdr) +{ + if (!msgHdr) + return NS_ERROR_NULL_POINTER; + nsresult ret = NS_OK; + nsCOMPtr <nsIMsgThread> thread ; + ret = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread)); + if (NS_SUCCEEDED(ret) && thread) + { + nsCOMPtr <nsIDBChangeAnnouncer> announcer = do_QueryInterface(this); + ret = thread->RemoveChildHdr(msgHdr, announcer); + } + return ret; +} + +NS_IMETHODIMP nsMsgDatabase::RemoveHeaderMdbRow(nsIMsgDBHdr *msg) +{ + NS_ENSURE_ARG_POINTER(msg); + nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(msg); // closed system, so this is ok + return RemoveHeaderFromDB(msgHdr); +} + +// This is a lower level routine which doesn't send notifcations or +// update folder info. One use is when a rule fires moving a header +// from one db to another, to remove it from the first db. + +nsresult nsMsgDatabase::RemoveHeaderFromDB(nsMsgHdr *msgHdr) +{ + if (!msgHdr) + return NS_ERROR_NULL_POINTER; + nsresult ret = NS_OK; + + RemoveHdrFromCache(msgHdr, nsMsgKey_None); + if (UseCorrectThreading()) + RemoveMsgRefsFromHash(msgHdr); + nsIMdbRow* row = msgHdr->GetMDBRow(); + if (row) + { + ret = m_mdbAllMsgHeadersTable->CutRow(GetEnv(), row); + row->CutAllColumns(GetEnv()); + } + msgHdr->m_initedValues = 0; // invalidate cached values. + return ret; +} + +nsresult nsMsgDatabase::IsRead(nsMsgKey key, bool *pRead) +{ + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv) || !msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv? + rv = IsHeaderRead(msgHdr, pRead); + return rv; +} + +uint32_t nsMsgDatabase::GetStatusFlags(nsIMsgDBHdr *msgHdr, uint32_t origFlags) +{ + uint32_t statusFlags = origFlags; + bool isRead = true; + + nsMsgKey key; + (void)msgHdr->GetMessageKey(&key); + if ((!m_newSet.IsEmpty() && m_newSet[m_newSet.Length() - 1] == key) || + (m_newSet.BinaryIndexOf(key) != m_newSet.NoIndex)) + statusFlags |= nsMsgMessageFlags::New; + if (NS_SUCCEEDED(IsHeaderRead(msgHdr, &isRead)) && isRead) + statusFlags |= nsMsgMessageFlags::Read; + return statusFlags; +} + +nsresult nsMsgDatabase::IsHeaderRead(nsIMsgDBHdr *msgHdr, bool *pRead) +{ + if (!msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; + + nsMsgHdr* hdr = static_cast<nsMsgHdr*>(msgHdr); // closed system, cast ok + // can't call GetFlags, because it will be recursive. + uint32_t flags; + hdr->GetRawFlags(&flags); + *pRead = !!(flags & nsMsgMessageFlags::Read); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::IsMarked(nsMsgKey key, bool *pMarked) +{ + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv)) + return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv? + + uint32_t flags; + (void)msgHdr->GetFlags(&flags); + *pMarked = !!(flags & nsMsgMessageFlags::Marked); + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::IsIgnored(nsMsgKey key, bool *pIgnored) +{ + NS_ENSURE_ARG_POINTER(pIgnored); + + nsCOMPtr <nsIMsgThread> threadHdr; + + nsresult rv = GetThreadForMsgKey(key, getter_AddRefs(threadHdr)); + // This should be very surprising, but we leave that up to the caller + // to determine for now. + if (!threadHdr) + return NS_MSG_MESSAGE_NOT_FOUND; + + uint32_t threadFlags; + threadHdr->GetFlags(&threadFlags); + *pIgnored = !!(threadFlags & nsMsgMessageFlags::Ignored); + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::IsWatched(nsMsgKey key, bool *pWatched) +{ + NS_ENSURE_ARG_POINTER(pWatched); + + nsCOMPtr <nsIMsgThread> threadHdr; + + nsresult rv = GetThreadForMsgKey(key, getter_AddRefs(threadHdr)); + // This should be very surprising, but we leave that up to the caller + // to determine for now. + if (!threadHdr) + return NS_MSG_MESSAGE_NOT_FOUND; + + uint32_t threadFlags; + threadHdr->GetFlags(&threadFlags); + *pWatched = !!(threadFlags & nsMsgMessageFlags::Watched); + return rv; +} + +nsresult nsMsgDatabase::HasAttachments(nsMsgKey key, bool *pHasThem) +{ + NS_ENSURE_ARG_POINTER(pHasThem); + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv)) + return rv; + + uint32_t flags; + (void)msgHdr->GetFlags(&flags); + *pHasThem = !!(flags & nsMsgMessageFlags::Attachment); + return rv; +} + +bool nsMsgDatabase::SetHdrReadFlag(nsIMsgDBHdr *msgHdr, bool bRead) +{ + return SetHdrFlag(msgHdr, bRead, nsMsgMessageFlags::Read); +} + +nsresult nsMsgDatabase::MarkHdrReadInDB(nsIMsgDBHdr *msgHdr, bool bRead, + nsIDBChangeListener *instigator) +{ + nsresult rv; + nsMsgKey key; + uint32_t oldFlags; + bool hdrInDB; + (void)msgHdr->GetMessageKey(&key); + msgHdr->GetFlags(&oldFlags); + + m_newSet.RemoveElement(key); + (void) ContainsKey(key, &hdrInDB); + if (hdrInDB && m_dbFolderInfo) + { + if (bRead) + m_dbFolderInfo->ChangeNumUnreadMessages(-1); + else + m_dbFolderInfo->ChangeNumUnreadMessages(1); + } + + SetHdrReadFlag(msgHdr, bRead); // this will cause a commit, at least for local mail, so do it after we change + // the folder counts above, so they will get committed too. + uint32_t flags; + rv = msgHdr->GetFlags(&flags); + flags &= ~nsMsgMessageFlags::New; + msgHdr->SetFlags(flags); + if (NS_FAILED(rv)) return rv; + + if (oldFlags == flags) + return NS_OK; + + return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator); +} + +NS_IMETHODIMP nsMsgDatabase::MarkRead(nsMsgKey key, bool bRead, + nsIDBChangeListener *instigator) +{ + nsresult rv; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv) || !msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv? + + rv = MarkHdrRead(msgHdr, bRead, instigator); + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::MarkReplied(nsMsgKey key, bool bReplied, + nsIDBChangeListener *instigator /* = NULL */) +{ + return SetKeyFlag(key, bReplied, nsMsgMessageFlags::Replied, instigator); +} + +NS_IMETHODIMP nsMsgDatabase::MarkForwarded(nsMsgKey key, bool bForwarded, + nsIDBChangeListener *instigator /* = NULL */) +{ + return SetKeyFlag(key, bForwarded, nsMsgMessageFlags::Forwarded, instigator); +} + +NS_IMETHODIMP nsMsgDatabase::MarkHasAttachments(nsMsgKey key, bool bHasAttachments, + nsIDBChangeListener *instigator) +{ + return SetKeyFlag(key, bHasAttachments, nsMsgMessageFlags::Attachment, instigator); +} + +NS_IMETHODIMP +nsMsgDatabase::MarkThreadRead(nsIMsgThread *thread, nsIDBChangeListener *instigator, + uint32_t *aNumMarked, nsMsgKey **aThoseMarked) +{ + NS_ENSURE_ARG_POINTER(thread); + NS_ENSURE_ARG_POINTER(aNumMarked); + NS_ENSURE_ARG_POINTER(aThoseMarked); + nsresult rv = NS_OK; + + uint32_t numChildren; + nsTArray<nsMsgKey> thoseMarked; + thread->GetNumChildren(&numChildren); + for (uint32_t curChildIndex = 0; curChildIndex < numChildren; curChildIndex++) + { + nsCOMPtr <nsIMsgDBHdr> child; + + rv = thread->GetChildHdrAt(curChildIndex, getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + bool isRead = true; + IsHeaderRead(child, &isRead); + if (!isRead) + { + nsMsgKey key; + if (NS_SUCCEEDED(child->GetMessageKey(&key))) + thoseMarked.AppendElement(key); + MarkHdrRead(child, true, instigator); + } + } + } + + *aNumMarked = thoseMarked.Length(); + + if (thoseMarked.Length()) + { + *aThoseMarked = + (nsMsgKey *) nsMemory::Clone(&thoseMarked[0], + thoseMarked.Length() * sizeof(nsMsgKey)); + if (!*aThoseMarked) + return NS_ERROR_OUT_OF_MEMORY; + } + else + *aThoseMarked = nullptr; + + return rv; +} + +NS_IMETHODIMP +nsMsgDatabase::MarkThreadIgnored(nsIMsgThread *thread, nsMsgKey threadKey, bool bIgnored, + nsIDBChangeListener *instigator) +{ + NS_ENSURE_ARG(thread); + uint32_t threadFlags; + thread->GetFlags(&threadFlags); + uint32_t oldThreadFlags = threadFlags; // not quite right, since we probably want msg hdr flags. + if (bIgnored) + { + threadFlags |= nsMsgMessageFlags::Ignored; + threadFlags &= ~nsMsgMessageFlags::Watched; // ignore is implicit un-watch + } + else + threadFlags &= ~nsMsgMessageFlags::Ignored; + thread->SetFlags(threadFlags); + + nsCOMPtr <nsIMsgDBHdr> msg; + nsresult rv = GetMsgHdrForKey(threadKey, getter_AddRefs(msg)); + NS_ENSURE_SUCCESS(rv, rv); + return NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator); +} + +NS_IMETHODIMP +nsMsgDatabase::MarkHeaderKilled(nsIMsgDBHdr *msg, bool bIgnored, + nsIDBChangeListener *instigator) +{ + uint32_t msgFlags; + msg->GetFlags(&msgFlags); + uint32_t oldFlags = msgFlags; + if (bIgnored) + msgFlags |= nsMsgMessageFlags::Ignored; + else + msgFlags &= ~nsMsgMessageFlags::Ignored; + msg->SetFlags(msgFlags); + + return NotifyHdrChangeAll(msg, oldFlags, msgFlags, instigator); +} + +NS_IMETHODIMP +nsMsgDatabase::MarkThreadWatched(nsIMsgThread *thread, nsMsgKey threadKey, bool bWatched, + nsIDBChangeListener *instigator) +{ + NS_ENSURE_ARG(thread); + uint32_t threadFlags; + thread->GetFlags(&threadFlags); + uint32_t oldThreadFlags = threadFlags; // not quite right, since we probably want msg hdr flags. + if (bWatched) + { + threadFlags |= nsMsgMessageFlags::Watched; + threadFlags &= ~nsMsgMessageFlags::Ignored; // watch is implicit un-ignore + } + else + threadFlags &= ~nsMsgMessageFlags::Watched; + + nsCOMPtr <nsIMsgDBHdr> msg; + GetMsgHdrForKey(threadKey, getter_AddRefs(msg)); + + nsresult rv = NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator); + thread->SetFlags(threadFlags); + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::MarkMarked(nsMsgKey key, bool mark, + nsIDBChangeListener *instigator) +{ + return SetKeyFlag(key, mark, nsMsgMessageFlags::Marked, instigator); +} + +NS_IMETHODIMP nsMsgDatabase::MarkOffline(nsMsgKey key, bool offline, + nsIDBChangeListener *instigator) +{ + return SetKeyFlag(key, offline, nsMsgMessageFlags::Offline, instigator); +} + +NS_IMETHODIMP nsMsgDatabase::SetStringProperty(nsMsgKey aKey, const char *aProperty, const char *aValue) +{ + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgHdrForKey(aKey, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv) || !msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv? + return SetStringPropertyByHdr(msgHdr, aProperty, aValue); +} + +NS_IMETHODIMP nsMsgDatabase::SetStringPropertyByHdr(nsIMsgDBHdr *msgHdr, const char *aProperty, const char *aValue) +{ + // don't do notifications if message not yet added to database. + // Ignore errors (consequences of failure are minor). + bool notify = true; + nsMsgKey key = nsMsgKey_None; + msgHdr->GetMessageKey(&key); + ContainsKey(key, ¬ify); + + nsCString oldValue; + nsresult rv = msgHdr->GetStringProperty(aProperty, getter_Copies(oldValue)); + NS_ENSURE_SUCCESS(rv,rv); + + // if no change to this string property, bail out + if (oldValue.Equals(aValue)) + return NS_OK; + + // Precall OnHdrPropertyChanged to store prechange status + nsTArray<uint32_t> statusArray(m_ChangeListeners.Length()); + uint32_t status; + nsCOMPtr<nsIDBChangeListener> listener; + if (notify) + { + nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners); + while (listeners.HasMore()) + { + listener = listeners.GetNext(); + listener->OnHdrPropertyChanged(msgHdr, true, &status, nullptr); + // ignore errors, but append element to keep arrays in sync + statusArray.AppendElement(status); + } + } + + rv = msgHdr->SetStringProperty(aProperty, aValue); + NS_ENSURE_SUCCESS(rv,rv); + + //Postcall OnHdrPropertyChanged to process the change + if (notify) + { + // if this is the junk score property notify, as long as we're not going + // from no value to non junk + if (!strcmp(aProperty, "junkscore") && !(oldValue.IsEmpty() && !strcmp(aValue, "0"))) + NotifyJunkScoreChanged(nullptr); + + nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners); + for (uint32_t i = 0; listeners.HasMore(); i++) + { + listener = listeners.GetNext(); + status = statusArray[i]; + listener->OnHdrPropertyChanged(msgHdr, false, &status, nullptr); + // ignore errors + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDatabase::SetUint32PropertyByHdr(nsIMsgDBHdr *aMsgHdr, + const char *aProperty, + uint32_t aValue) +{ + // If no change to this property, bail out. + uint32_t oldValue; + nsresult rv = aMsgHdr->GetUint32Property(aProperty, &oldValue); + NS_ENSURE_SUCCESS(rv, rv); + if (oldValue == aValue) + return NS_OK; + + // Don't do notifications if message not yet added to database. + bool notify = true; + nsMsgKey key = nsMsgKey_None; + aMsgHdr->GetMessageKey(&key); + ContainsKey(key, ¬ify); + + // Precall OnHdrPropertyChanged to store prechange status. + nsTArray<uint32_t> statusArray(m_ChangeListeners.Length()); + uint32_t status; + nsCOMPtr<nsIDBChangeListener> listener; + if (notify) + { + nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners); + while (listeners.HasMore()) + { + listener = listeners.GetNext(); + listener->OnHdrPropertyChanged(aMsgHdr, true, &status, nullptr); + // Ignore errors, but append element to keep arrays in sync. + statusArray.AppendElement(status); + } + } + + rv = aMsgHdr->SetUint32Property(aProperty, aValue); + NS_ENSURE_SUCCESS(rv, rv); + + // Postcall OnHdrPropertyChanged to process the change. + if (notify) + { + nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners); + for (uint32_t i = 0; listeners.HasMore(); i++) + { + listener = listeners.GetNext(); + status = statusArray[i]; + listener->OnHdrPropertyChanged(aMsgHdr, false, &status, nullptr); + // Ignore errors. + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::SetLabel(nsMsgKey key, nsMsgLabelValue label) +{ + nsresult rv; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv) || !msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; + nsMsgLabelValue oldLabel; + msgHdr->GetLabel(&oldLabel); + + msgHdr->SetLabel(label); + // clear old label + if (oldLabel != label) + { + if (oldLabel != 0) + rv = SetKeyFlag(key, false, oldLabel << 25, nullptr); + // set the flag in the x-mozilla-status2 line. + rv = SetKeyFlag(key, true, label << 25, nullptr); + } + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::MarkImapDeleted(nsMsgKey key, bool deleted, + nsIDBChangeListener *instigator) +{ + return SetKeyFlag(key, deleted, nsMsgMessageFlags::IMAPDeleted, instigator); +} + +NS_IMETHODIMP nsMsgDatabase::MarkMDNNeeded(nsMsgKey key, bool bNeeded, + nsIDBChangeListener *instigator /* = NULL */) +{ + return SetKeyFlag(key, bNeeded, nsMsgMessageFlags::MDNReportNeeded, instigator); +} + +NS_IMETHODIMP nsMsgDatabase::IsMDNNeeded(nsMsgKey key, bool *pNeeded) +{ + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv) || !msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv? + + uint32_t flags; + (void)msgHdr->GetFlags(&flags); + *pNeeded = !!(flags & nsMsgMessageFlags::MDNReportNeeded); + return rv; +} + + +nsresult nsMsgDatabase::MarkMDNSent(nsMsgKey key, bool bSent, + nsIDBChangeListener *instigator /* = NULL */) +{ + return SetKeyFlag(key, bSent, nsMsgMessageFlags::MDNReportSent, instigator); +} + + +nsresult nsMsgDatabase::IsMDNSent(nsMsgKey key, bool *pSent) +{ + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv) || !msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv? + + uint32_t flags; + (void)msgHdr->GetFlags(&flags); + *pSent = !!(flags & nsMsgMessageFlags::MDNReportSent); + return rv; +} + + +nsresult nsMsgDatabase::SetKeyFlag(nsMsgKey key, bool set, uint32_t flag, + nsIDBChangeListener *instigator) +{ + nsresult rv; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv) || !msgHdr) + return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv? + + uint32_t oldFlags; + msgHdr->GetFlags(&oldFlags); + + SetHdrFlag(msgHdr, set, flag); + + uint32_t flags; + (void)msgHdr->GetFlags(&flags); + + if (oldFlags == flags) + return NS_OK; + + return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator); +} + +nsresult nsMsgDatabase::SetMsgHdrFlag(nsIMsgDBHdr *msgHdr, bool set, uint32_t flag, nsIDBChangeListener *instigator) +{ + uint32_t oldFlags; + msgHdr->GetFlags(&oldFlags); + + SetHdrFlag(msgHdr, set, flag); + + uint32_t flags; + (void)msgHdr->GetFlags(&flags); + + if (oldFlags == flags) + return NS_OK; + + return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator); +} + +// Helper routine - lowest level of flag setting - returns true if flags change, +// false otherwise. +bool nsMsgDatabase::SetHdrFlag(nsIMsgDBHdr *msgHdr, bool bSet, nsMsgMessageFlagType flag) +{ + uint32_t statusFlags; + (void)msgHdr->GetFlags(&statusFlags); + uint32_t currentStatusFlags = GetStatusFlags(msgHdr, statusFlags); + bool flagAlreadySet = (currentStatusFlags & flag) != 0; + + if ((flagAlreadySet && !bSet) || (!flagAlreadySet && bSet)) + { + uint32_t resultFlags; + if (bSet) + msgHdr->OrFlags(flag, &resultFlags); + else + msgHdr->AndFlags(~flag, &resultFlags); + return true; + } + return false; +} + + +NS_IMETHODIMP nsMsgDatabase::MarkHdrRead(nsIMsgDBHdr *msgHdr, bool bRead, + nsIDBChangeListener *instigator) +{ + bool isReadInDB = true; + nsresult rv = nsMsgDatabase::IsHeaderRead(msgHdr, &isReadInDB); + NS_ENSURE_SUCCESS(rv, rv); + + bool isRead = true; + rv = IsHeaderRead(msgHdr, &isRead); + NS_ENSURE_SUCCESS(rv, rv); + + // if the flag is already correct in the db, don't change it. + // Check msg flags as well as IsHeaderRead in case it's a newsgroup + // and the msghdr flags are out of sync with the newsrc settings. + // (we could override this method for news db's, but it's a trivial fix here. + if (bRead != isRead || isRead != isReadInDB) + { + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + + bool inDB = false; + (void)ContainsKey(msgKey, &inDB); + + if (inDB) + { + nsCOMPtr <nsIMsgThread> threadHdr; + rv = GetThreadForMsgKey(msgKey, getter_AddRefs(threadHdr)); + if (threadHdr) + threadHdr->MarkChildRead(bRead); + } + return MarkHdrReadInDB(msgHdr, bRead, instigator); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::MarkHdrReplied(nsIMsgDBHdr *msgHdr, bool bReplied, + nsIDBChangeListener *instigator) +{ + return SetMsgHdrFlag(msgHdr, bReplied, nsMsgMessageFlags::Replied, instigator); +} + + +NS_IMETHODIMP nsMsgDatabase::MarkHdrMarked(nsIMsgDBHdr *msgHdr, bool mark, + nsIDBChangeListener *instigator) +{ + return SetMsgHdrFlag(msgHdr, mark, nsMsgMessageFlags::Marked, instigator); +} + +NS_IMETHODIMP +nsMsgDatabase::MarkHdrNotNew(nsIMsgDBHdr *aMsgHdr, + nsIDBChangeListener *aInstigator) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + nsMsgKey msgKey; + aMsgHdr->GetMessageKey(&msgKey); + m_newSet.RemoveElement(msgKey); + return SetMsgHdrFlag(aMsgHdr, false, nsMsgMessageFlags::New, aInstigator); +} + +NS_IMETHODIMP nsMsgDatabase::MarkAllRead(uint32_t *aNumKeys, nsMsgKey **aThoseMarked) +{ + NS_ENSURE_ARG_POINTER(aNumKeys); + NS_ENSURE_ARG_POINTER(aThoseMarked); + nsMsgHdr *pHeader; + + nsCOMPtr<nsISimpleEnumerator> hdrs; + nsTArray<nsMsgKey> thoseMarked; + nsresult rv = EnumerateMessages(getter_AddRefs(hdrs)); + if (NS_FAILED(rv)) + return rv; + bool hasMore = false; + + while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) + { + rv = hdrs->GetNext((nsISupports**)&pHeader); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + if (NS_FAILED(rv)) + break; + + bool isRead; + IsHeaderRead(pHeader, &isRead); + + if (!isRead) + { + nsMsgKey key; + (void)pHeader->GetMessageKey(&key); + thoseMarked.AppendElement(key); + rv = MarkHdrRead(pHeader, true, nullptr); // ### dmb - blow off error? + } + NS_RELEASE(pHeader); + } + + *aNumKeys = thoseMarked.Length(); + + if (thoseMarked.Length()) + { + *aThoseMarked = (nsMsgKey *) nsMemory::Clone(&thoseMarked[0], + thoseMarked.Length() * sizeof(nsMsgKey)); + if (!*aThoseMarked) + return NS_ERROR_OUT_OF_MEMORY; + } + else + *aThoseMarked = nullptr; + + // force num new to 0. + int32_t numUnreadMessages; + + rv = m_dbFolderInfo->GetNumUnreadMessages(&numUnreadMessages); + if (NS_SUCCEEDED(rv)) + m_dbFolderInfo->ChangeNumUnreadMessages(-numUnreadMessages); + // caller will Commit the db, so no need to do it here. + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::AddToNewList(nsMsgKey key) +{ + // we add new keys in increasing order... + if (m_newSet.IsEmpty() || (m_newSet[m_newSet.Length() - 1] < key)) + m_newSet.AppendElement(key); + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDatabase::ClearNewList(bool notify /* = FALSE */) +{ + nsresult err = NS_OK; + if (notify && !m_newSet.IsEmpty()) // need to update view + { + nsTArray<nsMsgKey> saveNewSet; + // clear m_newSet so that the code that's listening to the key change + // doesn't think we have new messages and send notifications all over + // that we have new messages. + saveNewSet.SwapElements(m_newSet); + for (uint32_t elementIndex = saveNewSet.Length() - 1; ; elementIndex--) + { + nsMsgKey lastNewKey = saveNewSet.ElementAt(elementIndex); + nsCOMPtr <nsIMsgDBHdr> msgHdr; + err = GetMsgHdrForKey(lastNewKey, getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(err)) + { + uint32_t flags; + (void)msgHdr->GetFlags(&flags); + + if ((flags | nsMsgMessageFlags::New) != flags) + { + msgHdr->AndFlags(~nsMsgMessageFlags::New, &flags); + NotifyHdrChangeAll(msgHdr, flags | nsMsgMessageFlags::New, flags, nullptr); + } + } + if (elementIndex == 0) + break; + } + } + return err; +} + +NS_IMETHODIMP nsMsgDatabase::HasNew(bool *_retval) +{ + if (!_retval) return NS_ERROR_NULL_POINTER; + + *_retval = (m_newSet.Length() > 0); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetFirstNew(nsMsgKey *result) +{ + bool hasnew; + nsresult rv = HasNew(&hasnew); + if (NS_FAILED(rv)) return rv; + *result = (hasnew) ? m_newSet.ElementAt(0) : nsMsgKey_None; + return NS_OK; +} + + +//////////////////////////////////////////////////////////////////////////////// + +nsMsgDBEnumerator::nsMsgDBEnumerator(nsMsgDatabase* db, + nsIMdbTable *table, + nsMsgDBEnumeratorFilter filter, + void* closure, + bool iterateForwards) + : mDB(db), + mDone(false), + mIterateForwards(iterateForwards), + mFilter(filter), + mClosure(closure), + mStopPos(-1) +{ + mNextPrefetched = false; + mTable = table; + mRowPos = 0; + mDB->m_enumerators.AppendElement(this); +} + +nsMsgDBEnumerator::~nsMsgDBEnumerator() +{ + Clear(); +} + +void nsMsgDBEnumerator::Clear() +{ + mRowCursor = nullptr; + mTable = nullptr; + mResultHdr = nullptr; + if (mDB) + mDB->m_enumerators.RemoveElement(this); + mDB = nullptr; +} + +NS_IMPL_ISUPPORTS(nsMsgDBEnumerator, nsISimpleEnumerator) + +nsresult nsMsgDBEnumerator::GetRowCursor() +{ + mDone = false; + + if (!mDB || !mTable) + return NS_ERROR_NULL_POINTER; + + if (mIterateForwards) + { + mRowPos = -1; + } + else + { + mdb_count numRows; + mTable->GetCount(mDB->GetEnv(), &numRows); + mRowPos = numRows; // startPos is 0 relative. + } + return mTable->GetTableRowCursor(mDB->GetEnv(), mRowPos, getter_AddRefs(mRowCursor)); +} + +NS_IMETHODIMP nsMsgDBEnumerator::GetNext(nsISupports **aItem) +{ + if (!aItem) + return NS_ERROR_NULL_POINTER; + nsresult rv = NS_OK; + if (!mNextPrefetched) + rv = PrefetchNext(); + if (NS_SUCCEEDED(rv)) + { + if (mResultHdr) + { + *aItem = mResultHdr; + NS_ADDREF(*aItem); + mNextPrefetched = false; + } + } + return rv; +} + +nsresult nsMsgDBEnumerator::PrefetchNext() +{ + nsresult rv = NS_OK; + nsIMdbRow* hdrRow; + uint32_t flags; + + if (!mRowCursor) + { + rv = GetRowCursor(); + if (NS_FAILED(rv)) + return rv; + } + + do + { + mResultHdr = nullptr; + if (mIterateForwards) + rv = mRowCursor->NextRow(mDB->GetEnv(), &hdrRow, &mRowPos); + else + rv = mRowCursor->PrevRow(mDB->GetEnv(), &hdrRow, &mRowPos); + if (!hdrRow) + { + mDone = true; + return NS_ERROR_FAILURE; + } + if (NS_FAILED(rv)) + { + mDone = true; + return rv; + } + //Get key from row + mdbOid outOid; + nsMsgKey key = nsMsgKey_None; + rv = hdrRow->GetOid(mDB->GetEnv(), &outOid); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + key = outOid.mOid_Id; + + rv = mDB->GetHdrFromUseCache(key, getter_AddRefs(mResultHdr)); + if (NS_SUCCEEDED(rv) && mResultHdr) + hdrRow->Release(); + else { + rv = mDB->CreateMsgHdr(hdrRow, key, getter_AddRefs(mResultHdr)); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + } + + if (mResultHdr) + mResultHdr->GetFlags(&flags); + else + flags = 0; + } + while (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure)) && !(flags & nsMsgMessageFlags::Expunged)); + + if (mResultHdr) + { + mNextPrefetched = true; + return NS_OK; + } + else + mNextPrefetched = false; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgDBEnumerator::HasMoreElements(bool *aResult) +{ + if (!aResult) + return NS_ERROR_NULL_POINTER; + + if (!mNextPrefetched && (NS_FAILED(PrefetchNext()))) + mDone = true; + *aResult = !mDone; + return NS_OK; +} + +nsMsgFilteredDBEnumerator::nsMsgFilteredDBEnumerator(nsMsgDatabase* db, + nsIMdbTable *table, + bool reverse, + nsIArray *searchTerms) + : nsMsgDBEnumerator(db, table, nullptr, nullptr, !reverse) +{ +} + +nsMsgFilteredDBEnumerator::~nsMsgFilteredDBEnumerator() +{ +} + +/** + * Create the search session for the enumerator, + * add the scope term for "folder" to the search session, and add the search + * terms in the array to the search session. + */ +nsresult nsMsgFilteredDBEnumerator::InitSearchSession(nsIArray *searchTerms, nsIMsgFolder *folder) +{ + nsresult rv; + m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, folder); + // add each item in termsArray to the search session + uint32_t numTerms; + rv = searchTerms->GetLength(&numTerms); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < numTerms; i++) + { + nsCOMPtr <nsIMsgSearchTerm> searchTerm; + searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm), getter_AddRefs(searchTerm)); + m_searchSession->AppendTerm(searchTerm); + } + return NS_OK; +} + +nsresult nsMsgFilteredDBEnumerator::PrefetchNext() +{ + nsresult rv; + do + { + rv = nsMsgDBEnumerator::PrefetchNext(); + if (NS_SUCCEEDED(rv) && mResultHdr) + { + bool matches; + rv = m_searchSession->MatchHdr(mResultHdr, mDB, &matches); + if (NS_SUCCEEDED(rv) && matches) + break; + mResultHdr = nullptr; + } + else + break; + } while (mStopPos == -1 || mRowPos != mStopPos); + + if (!mResultHdr) + mNextPrefetched = false; + + return rv; +} + + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsMsgDatabase::EnumerateMessages(nsISimpleEnumerator* *result) +{ + RememberLastUseTime(); + NS_ENSURE_ARG_POINTER(result); + nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, + nullptr, nullptr); + if (!e) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*result = e); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDatabase::ReverseEnumerateMessages(nsISimpleEnumerator* *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, + nullptr, nullptr, false); + if (!e) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*result = e); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDatabase::GetFilterEnumerator(nsIArray *searchTerms, bool aReverse, + nsISimpleEnumerator **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + RefPtr<nsMsgFilteredDBEnumerator> e = + new nsMsgFilteredDBEnumerator(this, m_mdbAllMsgHeadersTable, aReverse, + searchTerms); + + NS_ENSURE_TRUE(e, NS_ERROR_OUT_OF_MEMORY); + nsresult rv = e->InitSearchSession(searchTerms, m_folder); + NS_ENSURE_SUCCESS(rv, rv); + return CallQueryInterface(e.get(), aResult); +} + +NS_IMETHODIMP +nsMsgDatabase::NextMatchingHdrs(nsISimpleEnumerator *aEnumerator, + int32_t aNumHdrsToLookAt, int32_t aMaxResults, + nsIMutableArray *aMatchingHdrs, + int32_t *aNumMatches, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aEnumerator); + NS_ENSURE_ARG_POINTER(aResult); + nsMsgFilteredDBEnumerator *enumerator = + static_cast<nsMsgFilteredDBEnumerator *> (aEnumerator); + + // Force mRowPos to be initialized. + if (!enumerator->mRowCursor) + enumerator->GetRowCursor(); + + if (aNumHdrsToLookAt) + { + enumerator->mStopPos = enumerator->mIterateForwards ? + enumerator->mRowPos + aNumHdrsToLookAt : + enumerator->mRowPos - aNumHdrsToLookAt; + if (enumerator->mStopPos < 0) + enumerator->mStopPos = 0; + } + int32_t numMatches = 0; + nsresult rv; + do + { + nsCOMPtr <nsISupports> supports; + rv = enumerator->GetNext(getter_AddRefs(supports)); + nsCOMPtr <nsIMsgDBHdr> nextMessage = do_QueryInterface(supports); + if (NS_SUCCEEDED(rv) && nextMessage) + { + if (aMatchingHdrs) + aMatchingHdrs->AppendElement(nextMessage, false); + ++numMatches; + if (aMaxResults && numMatches == aMaxResults) + break; + } + else + break; + } + while (true); + + if (aNumMatches) + *aNumMatches = numMatches; + + *aResult = !enumerator->mDone; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDatabase::SyncCounts() +{ + nsCOMPtr <nsISimpleEnumerator> hdrs; + nsresult rv = EnumerateMessages(getter_AddRefs(hdrs)); + if (NS_FAILED(rv)) + return rv; + bool hasMore = false; + + mdb_count numHdrsInTable = 0; + int32_t numUnread = 0; + int32_t numHdrs = 0; + + if (m_mdbAllMsgHeadersTable) + m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrsInTable); + else + return NS_ERROR_NULL_POINTER; + + while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr <nsISupports> supports; + rv = hdrs->GetNext(getter_AddRefs(supports)); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + if (NS_FAILED(rv)) + break; + + nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports); + + bool isRead; + IsHeaderRead(pHeader, &isRead); + if (!isRead) + numUnread++; + numHdrs++; + } + + int32_t oldTotal, oldUnread; + (void) m_dbFolderInfo->GetNumUnreadMessages(&oldUnread); + (void) m_dbFolderInfo->GetNumMessages(&oldTotal); + if (oldUnread != numUnread) + m_dbFolderInfo->ChangeNumUnreadMessages(numUnread - oldUnread); + if (oldTotal != numHdrs) + m_dbFolderInfo->ChangeNumMessages(numHdrs - oldTotal); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::ListAllKeys(nsIMsgKeyArray *aKeys) +{ + NS_ENSURE_ARG_POINTER(aKeys); + nsresult rv = NS_OK; + nsCOMPtr<nsIMdbTableRowCursor> rowCursor; + + RememberLastUseTime(); + + if (m_mdbAllMsgHeadersTable) + { + uint32_t numMsgs = 0; + m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numMsgs); + aKeys->SetCapacity(numMsgs); + rv = m_mdbAllMsgHeadersTable->GetTableRowCursor(GetEnv(), -1, + getter_AddRefs(rowCursor)); + while (NS_SUCCEEDED(rv) && rowCursor) + { + mdbOid outOid; + mdb_pos outPos; + + rv = rowCursor->NextRowOid(GetEnv(), &outOid, &outPos); + // is this right? Mork is returning a 0 id, but that should valid. + if (outPos < 0 || outOid.mOid_Id == (mdb_id) -1) + break; + if (NS_SUCCEEDED(rv)) + aKeys->AppendElement(outOid.mOid_Id); + } + } + return rv; +} + +class nsMsgDBThreadEnumerator : public nsISimpleEnumerator, public nsIDBChangeListener +{ +public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods: + NS_DECL_NSISIMPLEENUMERATOR + + NS_DECL_NSIDBCHANGELISTENER + + // nsMsgDBEnumerator methods: + typedef nsresult (*nsMsgDBThreadEnumeratorFilter)(nsIMsgThread* thread); + + nsMsgDBThreadEnumerator(nsMsgDatabase* db, nsMsgDBThreadEnumeratorFilter filter); + +protected: + virtual ~nsMsgDBThreadEnumerator(); + nsresult GetTableCursor(void); + nsresult PrefetchNext(); + nsMsgDatabase* mDB; + nsCOMPtr <nsIMdbPortTableCursor> mTableCursor; + nsIMsgThread* mResultThread; + bool mDone; + bool mNextPrefetched; + nsMsgDBThreadEnumeratorFilter mFilter; +}; + +nsMsgDBThreadEnumerator::nsMsgDBThreadEnumerator(nsMsgDatabase* db, + nsMsgDBThreadEnumeratorFilter filter) + : mDB(db), mTableCursor(nullptr), mResultThread(nullptr), mDone(false), + mFilter(filter) +{ + mDB->AddListener(this); + mNextPrefetched = false; +} + +nsMsgDBThreadEnumerator::~nsMsgDBThreadEnumerator() +{ + mTableCursor = nullptr; + NS_IF_RELEASE(mResultThread); + if (mDB) + mDB->RemoveListener(this); +} + +NS_IMPL_ISUPPORTS(nsMsgDBThreadEnumerator, nsISimpleEnumerator, nsIDBChangeListener) + + +/* void OnHdrFlagsChanged (in nsIMsgDBHdr aHdrChanged, in unsigned long aOldFlags, in unsigned long aNewFlags, in nsIDBChangeListener aInstigator); */ +NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +//void OnHdrPropertyChanged(in nsIMsgDBHdr aHdrToChange, in bool aPreChange, +// inout uint32_t aStatus, in nsIDBChangeListener aInstigator); +NS_IMETHODIMP +nsMsgDBThreadEnumerator::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus, + nsIDBChangeListener * aInstigator) +{ + return NS_OK; +} + +/* void onHdrDeleted (in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags, in nsIDBChangeListener aInstigator); */ +NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +/* void onHdrAdded (in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags, in nsIDBChangeListener aInstigator); */ +NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +/* void onParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener aInstigator); */ +NS_IMETHODIMP nsMsgDBThreadEnumerator::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +/* void onAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */ +NS_IMETHODIMP nsMsgDBThreadEnumerator::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) +{ + mTableCursor = nullptr; + NS_IF_RELEASE(mResultThread); + mDB->RemoveListener(this); + mDB = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBThreadEnumerator::OnEvent(nsIMsgDatabase *aDB, const char *aEvent) +{ + return NS_OK; +} + +/* void onReadChanged (in nsIDBChangeListener aInstigator); */ +NS_IMETHODIMP nsMsgDBThreadEnumerator::OnReadChanged(nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +/* void onJunkScoreChanged (in nsIDBChangeListener aInstigator); */ +NS_IMETHODIMP nsMsgDBThreadEnumerator::OnJunkScoreChanged(nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +nsresult nsMsgDBThreadEnumerator::GetTableCursor(void) +{ + nsresult rv = NS_OK; + + if (!mDB || !mDB->m_mdbStore) + return NS_ERROR_NULL_POINTER; + + mDB->m_mdbStore->GetPortTableCursor(mDB->GetEnv(), mDB->m_hdrRowScopeToken, mDB->m_threadTableKindToken, + getter_AddRefs(mTableCursor)); + + if (NS_FAILED(rv)) + return rv; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBThreadEnumerator::GetNext(nsISupports **aItem) +{ + NS_ENSURE_ARG_POINTER(aItem); + + *aItem = nullptr; + nsresult rv = NS_OK; + if (!mNextPrefetched) + rv = PrefetchNext(); + if (NS_SUCCEEDED(rv)) + { + if (mResultThread) + { + *aItem = mResultThread; + NS_ADDREF(mResultThread); + mNextPrefetched = false; + } + } + return rv; +} + + +nsresult nsMsgDBThreadEnumerator::PrefetchNext() +{ + nsresult rv; + nsCOMPtr<nsIMdbTable> table; + + if (!mDB) + return NS_ERROR_NULL_POINTER; + + if (!mTableCursor) + { + rv = GetTableCursor(); + if (NS_FAILED(rv)) + return rv; + } + while (true) + { + NS_IF_RELEASE(mResultThread); + mResultThread = nullptr; + rv = mTableCursor->NextTable(mDB->GetEnv(), getter_AddRefs(table)); + if (!table) + { + mDone = true; + return NS_ERROR_FAILURE; + } + if (NS_FAILED(rv)) + { + mDone = true; + return rv; + } + + mdbOid tableId; + table->GetOid(mDB->GetEnv(), &tableId); + + mResultThread = mDB->FindExistingThread(tableId.mOid_Id); + if (!mResultThread) + mResultThread = new nsMsgThread(mDB, table); + + if (mResultThread) + { + uint32_t numChildren = 0; + NS_ADDREF(mResultThread); + mResultThread->GetNumChildren(&numChildren); + // we've got empty thread; don't tell caller about it. + if (numChildren == 0) + continue; + } + if (mFilter && NS_FAILED(mFilter(mResultThread))) + continue; + else + break; + } + if (mResultThread) + { + mNextPrefetched = true; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgDBThreadEnumerator::HasMoreElements(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (!mNextPrefetched) + PrefetchNext(); + *aResult = !mDone; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDatabase::EnumerateThreads(nsISimpleEnumerator* *result) +{ + RememberLastUseTime(); + nsMsgDBThreadEnumerator* e = new nsMsgDBThreadEnumerator(this, nullptr); + if (e == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*result = e); + return NS_OK; +} + +// only return headers with a particular flag set +static nsresult +nsMsgFlagSetFilter(nsIMsgDBHdr *msg, void *closure) +{ + uint32_t msgFlags, desiredFlags; + desiredFlags = * (uint32_t *) closure; + msg->GetFlags(&msgFlags); + return (msgFlags & desiredFlags) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +nsMsgDatabase::EnumerateMessagesWithFlag(nsISimpleEnumerator* *result, uint32_t *pFlag) +{ + RememberLastUseTime(); + + nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsMsgFlagSetFilter, pFlag); + if (!e) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*result = e); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::CreateNewHdr(nsMsgKey key, nsIMsgDBHdr **pnewHdr) +{ + nsresult err = NS_OK; + nsIMdbRow *hdrRow = nullptr; + struct mdbOid allMsgHdrsTableOID; + + if (!pnewHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore) + return NS_ERROR_NULL_POINTER; + + if (key != nsMsgKey_None) + { + allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken; + allMsgHdrsTableOID.mOid_Id = key; // presumes 0 is valid key value + + err = m_mdbStore->GetRow(GetEnv(), &allMsgHdrsTableOID, &hdrRow); + if (!hdrRow) + err = m_mdbStore->NewRowWithOid(GetEnv(), &allMsgHdrsTableOID, &hdrRow); + } + else + { + // Mork will assign an ID to the new row, generally the next available ID. + err = m_mdbStore->NewRow(GetEnv(), m_hdrRowScopeToken, &hdrRow); + if (hdrRow) + { + struct mdbOid oid; + hdrRow->GetOid(GetEnv(), &oid); + key = oid.mOid_Id; + } + else + { + // We failed to create a new row. That can happen if we run out of keys, + // which will force a reparse. + RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray; + if (NS_SUCCEEDED(ListAllKeys(keys))) + { + uint32_t numKeys; + keys->GetLength(&numKeys); + for (uint32_t i = 0; i < numKeys; i++) + { + if (keys->m_keys[i] >= kForceReparseKey) + { + // Force a reparse. + if (m_dbFolderInfo) + m_dbFolderInfo->SetBooleanProperty("forceReparse", true); + break; + } + } + } + err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + } + if (NS_FAILED(err)) + return err; + err = CreateMsgHdr(hdrRow, key, pnewHdr); + return err; +} + +NS_IMETHODIMP nsMsgDatabase::AddNewHdrToDB(nsIMsgDBHdr *newHdr, bool notify) +{ + NS_ENSURE_ARG_POINTER(newHdr); + nsMsgHdr* hdr = static_cast<nsMsgHdr*>(newHdr); // closed system, cast ok + bool newThread; + bool hasKey; + ContainsKey(hdr->m_messageKey, &hasKey); + if (hasKey) + { + NS_ERROR("adding hdr that already exists"); + return NS_ERROR_FAILURE; + } + nsresult err = ThreadNewHdr(hdr, newThread); + // we thread header before we add it to the all headers table + // so that subject and reference threading will work (otherwise, + // when we try to find the first header with the same subject or + // reference, we get the new header!) + if (NS_SUCCEEDED(err)) + { + nsMsgKey key; + uint32_t flags; + + newHdr->GetMessageKey(&key); + hdr->GetRawFlags(&flags); + // use raw flags instead of GetFlags, because GetFlags will + // pay attention to what's in m_newSet, and this new hdr isn't + // in m_newSet yet. + if (flags & nsMsgMessageFlags::New) + { + uint32_t newFlags; + newHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags); // make sure not filed out + AddToNewList(key); + } + if (m_dbFolderInfo != NULL) + { + m_dbFolderInfo->ChangeNumMessages(1); + bool isRead = true; + IsHeaderRead(newHdr, &isRead); + if (!isRead) + m_dbFolderInfo->ChangeNumUnreadMessages(1); + m_dbFolderInfo->OnKeyAdded(key); + } + + err = m_mdbAllMsgHeadersTable->AddRow(GetEnv(), hdr->GetMDBRow()); + if (notify) + { + nsMsgKey threadParent; + + newHdr->GetThreadParent(&threadParent); + NotifyHdrAddedAll(newHdr, threadParent, flags, NULL); + } + + if (UseCorrectThreading()) + err = AddMsgRefsToHash(newHdr); + } + NS_ASSERTION(NS_SUCCEEDED(err), "error creating thread"); + return err; +} + +NS_IMETHODIMP nsMsgDatabase::CopyHdrFromExistingHdr(nsMsgKey key, nsIMsgDBHdr *existingHdr, bool addHdrToDB, nsIMsgDBHdr **newHdr) +{ + nsresult err = NS_OK; + + if (existingHdr) + { + nsMsgHdr* sourceMsgHdr = static_cast<nsMsgHdr*>(existingHdr); // closed system, cast ok + nsMsgHdr *destMsgHdr = nullptr; + CreateNewHdr(key, (nsIMsgDBHdr **) &destMsgHdr); + nsIMdbRow *sourceRow = sourceMsgHdr->GetMDBRow(); + if (!destMsgHdr || !sourceRow) + return NS_MSG_MESSAGE_NOT_FOUND; + + nsIMdbRow *destRow = destMsgHdr->GetMDBRow(); + if (!destRow) + return NS_ERROR_UNEXPECTED; + + err = destRow->SetRow(GetEnv(), sourceRow); + if (NS_SUCCEEDED(err)) + { + // we may have gotten the header from a cache - calling SetRow + // basically invalidates any cached values, so invalidate them. + destMsgHdr->m_initedValues = 0; + if(addHdrToDB) + err = AddNewHdrToDB(destMsgHdr, true); + if (NS_SUCCEEDED(err) && newHdr) + *newHdr = destMsgHdr; + } + } + return err; +} + +nsresult nsMsgDatabase::RowCellColumnTonsString(nsIMdbRow *hdrRow, mdb_token columnToken, nsAString &resultStr) +{ + NS_ENSURE_ARG_POINTER(hdrRow); + + struct mdbYarn yarn; + nsresult rv = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn); + NS_ENSURE_SUCCESS(rv, rv); + YarnTonsString(&yarn, resultStr); + return NS_OK; +} + +// as long as the row still exists, and isn't changed, the returned const char ** will be valid. +// But be very careful using this data - the caller should never return it in turn to another caller. +nsresult nsMsgDatabase::RowCellColumnToConstCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, const char **ptr) +{ + NS_ENSURE_ARG_POINTER(hdrRow); + + struct mdbYarn yarn; + nsresult rv = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn); + NS_ENSURE_SUCCESS(rv, rv); + *ptr = (const char*)yarn.mYarn_Buf; + return NS_OK; +} + +nsIMimeConverter *nsMsgDatabase::GetMimeConverter() +{ + if (!m_mimeConverter) + { + // apply mime decode + m_mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID); + } + return m_mimeConverter; +} + +nsresult nsMsgDatabase::GetEffectiveCharset(nsIMdbRow *row, nsACString &resultCharset) +{ + resultCharset.Truncate(); + bool characterSetOverride; + m_dbFolderInfo->GetCharacterSetOverride(&characterSetOverride); + nsresult rv = RowCellColumnToCharPtr(row, m_messageCharSetColumnToken, getter_Copies(resultCharset)); + if (NS_FAILED(rv) || resultCharset.IsEmpty() || + resultCharset.Equals("us-ascii") || characterSetOverride) + { + rv = m_dbFolderInfo->GetEffectiveCharacterSet(resultCharset); + } + return rv; +} + +nsresult nsMsgDatabase::RowCellColumnToMime2DecodedString(nsIMdbRow *row, mdb_token columnToken, nsAString &resultStr) +{ + nsresult err = NS_OK; + const char *nakedString = nullptr; + err = RowCellColumnToConstCharPtr(row, columnToken, &nakedString); + if (NS_SUCCEEDED(err) && nakedString && strlen(nakedString)) + { + GetMimeConverter(); + if (m_mimeConverter) + { + nsAutoString decodedStr; + nsCString charSet; + GetEffectiveCharset(row, charSet); + + err = m_mimeConverter->DecodeMimeHeader(nakedString, charSet.get(), + false, true, resultStr); + } + } + return err; +} + +nsresult nsMsgDatabase::RowCellColumnToAddressCollationKey(nsIMdbRow *row, mdb_token colToken, uint8_t **result, uint32_t *len) +{ + nsString sender; + nsresult rv = RowCellColumnToMime2DecodedString(row, colToken, sender); + NS_ENSURE_SUCCESS(rv, rv); + + nsString name; + ExtractName(DecodedHeader(sender), name); + return CreateCollationKey(name, len, result); +} + +nsresult nsMsgDatabase::GetCollationKeyGenerator() +{ + nsresult err = NS_OK; + if (!m_collationKeyGenerator) + { + nsCOMPtr <nsILocale> locale; + nsAutoString localeName; + + // get a locale service + nsCOMPtr <nsILocaleService> localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &err); + if (NS_SUCCEEDED(err)) + { + // do this for a new db if no UI to be provided for locale selection + err = localeService->GetApplicationLocale(getter_AddRefs(locale)); + + if (locale) + { + // or generate a locale from a stored locale name ("en_US", "fr_FR") + //err = localeFactory->NewLocale(&localeName, &locale); + + nsCOMPtr <nsICollationFactory> f = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &err); + if (NS_SUCCEEDED(err) && f) + { + // get a collation interface instance + err = f->CreateCollation(locale, getter_AddRefs(m_collationKeyGenerator)); + } + } + } + } + return err; +} + +nsresult nsMsgDatabase::RowCellColumnToCollationKey(nsIMdbRow *row, mdb_token columnToken, uint8_t **result, uint32_t *len) +{ + const char *nakedString = nullptr; + nsresult err; + + err = RowCellColumnToConstCharPtr(row, columnToken, &nakedString); + if (!nakedString) + nakedString = ""; + if (NS_SUCCEEDED(err)) + { + GetMimeConverter(); + if (m_mimeConverter) + { + nsCString decodedStr; + nsCString charSet; + GetEffectiveCharset(row, charSet); + + err = m_mimeConverter->DecodeMimeHeaderToUTF8( + nsDependentCString(nakedString), charSet.get(), false, + true, decodedStr); + if (NS_SUCCEEDED(err)) + err = CreateCollationKey(NS_ConvertUTF8toUTF16(decodedStr), len, result); + } + } + return err; +} + +NS_IMETHODIMP +nsMsgDatabase::CompareCollationKeys(uint32_t len1, uint8_t *key1, uint32_t len2, + uint8_t *key2, int32_t *result) +{ + nsresult rv = GetCollationKeyGenerator(); + NS_ENSURE_SUCCESS(rv,rv); + if (!m_collationKeyGenerator) return NS_ERROR_FAILURE; + + rv = m_collationKeyGenerator->CompareRawSortKey(key1,len1,key2,len2,result); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +NS_IMETHODIMP +nsMsgDatabase::CreateCollationKey(const nsAString& sourceString, uint32_t *len, + uint8_t **result) +{ + nsresult err = GetCollationKeyGenerator(); + NS_ENSURE_SUCCESS(err,err); + if (!m_collationKeyGenerator) return NS_ERROR_FAILURE; + + err = m_collationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, sourceString, result, len); + NS_ENSURE_SUCCESS(err,err); + return err; +} + +nsresult nsMsgDatabase::RowCellColumnToUInt32(nsIMdbRow *hdrRow, mdb_token columnToken, uint32_t &uint32Result, uint32_t defaultValue) +{ + return RowCellColumnToUInt32(hdrRow, columnToken, &uint32Result, defaultValue); +} + +nsresult nsMsgDatabase::RowCellColumnToUInt32(nsIMdbRow *hdrRow, mdb_token columnToken, uint32_t *uint32Result, uint32_t defaultValue) +{ + nsresult err = NS_OK; + + if (uint32Result) + *uint32Result = defaultValue; + if (hdrRow) // ### probably should be an error if hdrRow is NULL... + { + struct mdbYarn yarn; + err = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn); + if (NS_SUCCEEDED(err)) + YarnToUInt32(&yarn, uint32Result); + } + return err; +} + +nsresult nsMsgDatabase::UInt32ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint32_t value) +{ + struct mdbYarn yarn; + char yarnBuf[100]; + + if (!row) + return NS_ERROR_NULL_POINTER; + + yarn.mYarn_Buf = (void *) yarnBuf; + yarn.mYarn_Size = sizeof(yarnBuf); + yarn.mYarn_Fill = yarn.mYarn_Size; + yarn.mYarn_Form = 0; + yarn.mYarn_Grow = NULL; + return row->AddColumn(GetEnv(), columnToken, UInt32ToYarn(&yarn, value)); +} + +nsresult nsMsgDatabase::UInt64ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint64_t value) +{ + NS_ENSURE_ARG_POINTER(row); + struct mdbYarn yarn; + char yarnBuf[17]; // max string is 16 bytes, + 1 for null. + + yarn.mYarn_Buf = (void *) yarnBuf; + yarn.mYarn_Size = sizeof(yarnBuf); + yarn.mYarn_Form = 0; + yarn.mYarn_Grow = NULL; + PR_snprintf((char *) yarn.mYarn_Buf, yarn.mYarn_Size, "%llx", value); + yarn.mYarn_Fill = PL_strlen((const char *) yarn.mYarn_Buf); + return row->AddColumn(GetEnv(), columnToken, &yarn); +} + +nsresult +nsMsgDatabase::RowCellColumnToUInt64(nsIMdbRow *hdrRow, mdb_token columnToken, + uint64_t *uint64Result, + uint64_t defaultValue) +{ + nsresult err = NS_OK; + + if (uint64Result) + *uint64Result = defaultValue; + if (hdrRow) // ### probably should be an error if hdrRow is NULL... + { + struct mdbYarn yarn; + err = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn); + if (NS_SUCCEEDED(err)) + YarnToUInt64(&yarn, uint64Result); + } + return err; +} + +nsresult nsMsgDatabase::CharPtrToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, const char *charPtr) +{ + if (!row) + return NS_ERROR_NULL_POINTER; + + struct mdbYarn yarn; + yarn.mYarn_Buf = (void *) charPtr; + yarn.mYarn_Size = PL_strlen((const char *) yarn.mYarn_Buf) + 1; + yarn.mYarn_Fill = yarn.mYarn_Size - 1; + yarn.mYarn_Form = 0; // what to do with this? we're storing csid in the msg hdr... + + return row->AddColumn(GetEnv(), columnToken, &yarn); +} + +// caller must NS_Free result +nsresult nsMsgDatabase::RowCellColumnToCharPtr(nsIMdbRow *row, mdb_token columnToken, char **result) +{ + nsresult err = NS_ERROR_NULL_POINTER; + + if (row && result) + { + struct mdbYarn yarn; + err = row->AliasCellYarn(GetEnv(), columnToken, &yarn); + if (NS_SUCCEEDED(err)) + { + *result = (char *)NS_Alloc(yarn.mYarn_Fill + 1); + if (*result) + { + if (yarn.mYarn_Fill > 0) + memcpy(*result, yarn.mYarn_Buf, yarn.mYarn_Fill); + (*result)[yarn.mYarn_Fill] = '\0'; + } + else + err = NS_ERROR_OUT_OF_MEMORY; + + } + } + return err; +} + + + +/* static */struct mdbYarn *nsMsgDatabase::nsStringToYarn(struct mdbYarn *yarn, const nsAString &str) +{ + yarn->mYarn_Buf = ToNewCString(NS_ConvertUTF16toUTF8(str)); + yarn->mYarn_Size = str.Length() + 1; + yarn->mYarn_Fill = yarn->mYarn_Size - 1; + yarn->mYarn_Form = 0; // what to do with this? we're storing csid in the msg hdr... + return yarn; +} + +/* static */struct mdbYarn *nsMsgDatabase::UInt32ToYarn(struct mdbYarn *yarn, uint32_t i) +{ + PR_snprintf((char *) yarn->mYarn_Buf, yarn->mYarn_Size, "%lx", i); + yarn->mYarn_Fill = PL_strlen((const char *) yarn->mYarn_Buf); + yarn->mYarn_Form = 0; // what to do with this? Should be parsed out of the mime2 header? + return yarn; +} + +/* static */struct mdbYarn *nsMsgDatabase::UInt64ToYarn(struct mdbYarn *yarn, uint64_t i) +{ + PR_snprintf((char *) yarn->mYarn_Buf, yarn->mYarn_Size, "%llx", i); + yarn->mYarn_Fill = PL_strlen((const char *) yarn->mYarn_Buf); + yarn->mYarn_Form = 0; + return yarn; +} + +/* static */void nsMsgDatabase::YarnTonsString(struct mdbYarn *yarn, nsAString &str) +{ + const char* buf = (const char*)yarn->mYarn_Buf; + if (buf) + CopyASCIItoUTF16(Substring(buf, buf + yarn->mYarn_Fill), str); + else + str.Truncate(); +} + +/* static */void nsMsgDatabase::YarnTonsCString(struct mdbYarn *yarn, nsACString &str) +{ + const char* buf = (const char*)yarn->mYarn_Buf; + if (buf) + str.Assign(buf, yarn->mYarn_Fill); + else + str.Truncate(); +} + +// WARNING - if yarn is empty, *pResult will not be changed!!!! +// this is so we can leave default values as they were. +/* static */void nsMsgDatabase::YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult) +{ + uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill); + + if (numChars == 0) + return; + + *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars); +} + +// WARNING - if yarn is empty, *pResult will not be changed!!!! +// this is so we can leave default values as they were. +/* static */void nsMsgDatabase::YarnToUInt64(struct mdbYarn *yarn, uint64_t *pResult) +{ + uint8_t numChars = std::min<mdb_fill>(16, yarn->mYarn_Fill); + + if (numChars == 0) + return; + + *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars); +} + +nsresult nsMsgDatabase::GetProperty(nsIMdbRow *row, const char *propertyName, char **result) +{ + nsresult err = NS_OK; + mdb_token property_token; + + if (m_mdbStore) + err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token); + else + err = NS_ERROR_NULL_POINTER; + if (NS_SUCCEEDED(err)) + err = RowCellColumnToCharPtr(row, property_token, result); + + return err; +} + +nsresult nsMsgDatabase::SetProperty(nsIMdbRow *row, const char *propertyName, const char *propertyVal) +{ + nsresult err = NS_OK; + mdb_token property_token; + + NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us. + if (!row) + return NS_ERROR_NULL_POINTER; + + err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(err)) + CharPtrToRowCellColumn(row, property_token, propertyVal); + return err; +} + +nsresult nsMsgDatabase::GetPropertyAsNSString(nsIMdbRow *row, const char *propertyName, nsAString &result) +{ + nsresult err = NS_OK; + mdb_token property_token; + + NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us. + if (!row) + return NS_ERROR_NULL_POINTER; + + err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(err)) + err = RowCellColumnTonsString(row, property_token, result); + + return err; +} + +nsresult nsMsgDatabase::SetPropertyFromNSString(nsIMdbRow *row, const char *propertyName, const nsAString &propertyVal) +{ + nsresult err = NS_OK; + mdb_token property_token; + + NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us. + if (!row) + return NS_ERROR_NULL_POINTER; + + err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(err)) + return SetNSStringPropertyWithToken(row, property_token, propertyVal); + + return err; +} + + +nsresult nsMsgDatabase::GetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t *result, uint32_t defaultValue) +{ + nsresult err = NS_OK; + mdb_token property_token; + + NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us. + if (!row) + return NS_ERROR_NULL_POINTER; + + err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(err)) + err = RowCellColumnToUInt32(row, property_token, result, defaultValue); + + return err; +} + +nsresult nsMsgDatabase::GetUint64Property(nsIMdbRow *row, const char *propertyName, uint64_t *result, uint64_t defaultValue) +{ + nsresult err = NS_OK; + mdb_token property_token; + + NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us. + if (!row) + return NS_ERROR_NULL_POINTER; + + err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(err)) + err = RowCellColumnToUInt64(row, property_token, result, defaultValue); + + return err; +} + +nsresult nsMsgDatabase::SetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t propertyVal) +{ + struct mdbYarn yarn; + char int32StrBuf[20]; + yarn.mYarn_Buf = int32StrBuf; + yarn.mYarn_Size = sizeof(int32StrBuf); + yarn.mYarn_Fill = sizeof(int32StrBuf); + + NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us. + if (!row) + return NS_ERROR_NULL_POINTER; + + mdb_token property_token; + + nsresult err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(err)) + { + UInt32ToYarn(&yarn, propertyVal); + err = row->AddColumn(GetEnv(), property_token, &yarn); + } + return err; +} + +nsresult nsMsgDatabase::SetUint64Property(nsIMdbRow *row, + const char *propertyName, + uint64_t propertyVal) +{ + struct mdbYarn yarn; + char int64StrBuf[100]; + yarn.mYarn_Buf = int64StrBuf; + yarn.mYarn_Size = sizeof(int64StrBuf); + yarn.mYarn_Fill = sizeof(int64StrBuf); + + NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us. + if (!row) + return NS_ERROR_NULL_POINTER; + + mdb_token property_token; + + nsresult err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(err)) + { + UInt64ToYarn(&yarn, propertyVal); + err = row->AddColumn(GetEnv(), property_token, &yarn); + } + return err; +} + +nsresult nsMsgDatabase::GetBooleanProperty(nsIMdbRow *row, const char *propertyName, + bool *result, + bool defaultValue /* = false */) +{ + uint32_t res; + nsresult rv = GetUint32Property(row, propertyName, &res, (uint32_t) defaultValue); + *result = !!res; + return rv; +} + +nsresult nsMsgDatabase::SetBooleanProperty(nsIMdbRow *row, const char *propertyName, + bool propertyVal) +{ + return SetUint32Property(row, propertyName, (uint32_t) propertyVal); +} + +nsresult nsMsgDatabase::SetNSStringPropertyWithToken(nsIMdbRow *row, mdb_token aProperty, const nsAString &propertyStr) +{ + NS_ENSURE_ARG(row); + struct mdbYarn yarn; + + yarn.mYarn_Grow = NULL; + nsresult err = row->AddColumn(GetEnv(), aProperty, nsStringToYarn(&yarn, propertyStr)); + free((char *)yarn.mYarn_Buf); // won't need this when we have nsCString + return err; +} + + +uint32_t nsMsgDatabase::GetCurVersion() +{ + return kMsgDBVersion; +} + +NS_IMETHODIMP nsMsgDatabase::SetSummaryValid(bool valid /* = true */) +{ + // If the file was invalid when opened (for example in folder compact), then it may + // not have been added to the cache. Add it now if missing. + if (valid) + { + nsCOMPtr<nsIMsgDBService> serv(mozilla::services::GetDBService()); + static_cast<nsMsgDBService*>(serv.get())->EnsureCached(this); + } + // setting the version to 0 ought to make it pretty invalid. + if (m_dbFolderInfo) + m_dbFolderInfo->SetVersion(valid ? GetCurVersion() : 0); + + // for default db (and news), there's no nothing to set to make it it valid + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDatabase::GetSummaryValid(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = true; + return NS_OK; +} + + +// protected routines + +// should we thread messages with common subjects that don't start with Re: together? +// I imagine we might have separate preferences for mail and news, so this is a virtual method. +bool nsMsgDatabase::ThreadBySubjectWithoutRe() +{ + GetGlobalPrefs(); + return gThreadWithoutRe; +} + +bool nsMsgDatabase::UseStrictThreading() +{ + GetGlobalPrefs(); + return gStrictThreading; +} + +// Should we make sure messages are always threaded correctly (see bug 181446) +bool nsMsgDatabase::UseCorrectThreading() +{ + GetGlobalPrefs(); + return gCorrectThreading; +} + +// adapted from removed PL_DHashFreeStringKey +static void +msg_DHashFreeStringKey(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) +{ + const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry; + free((void*)stub->key); + PLDHashTable::ClearEntryStub(aTable, aEntry); +} + +PLDHashTableOps nsMsgDatabase::gRefHashTableOps = +{ + PLDHashTable::HashStringKey, + PLDHashTable::MatchStringKey, + PLDHashTable::MoveEntryStub, + msg_DHashFreeStringKey, + nullptr +}; + +nsresult nsMsgDatabase::GetRefFromHash(nsCString &reference, nsMsgKey *threadId) +{ + // Initialize the reference hash + if (!m_msgReferences) + { + nsresult rv = InitRefHash(); + if (NS_FAILED(rv)) + return rv; + } + + // Find reference from the hash + PLDHashEntryHdr *entry = + m_msgReferences->Search((const void *) reference.get()); + if (entry) + { + RefHashElement *element = static_cast<RefHashElement *>(entry); + *threadId = element->mThreadId; + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +nsresult nsMsgDatabase::AddRefToHash(nsCString &reference, nsMsgKey threadId) +{ + if (m_msgReferences) + { + PLDHashEntryHdr *entry = m_msgReferences->Add((void *) reference.get(), mozilla::fallible); + if (!entry) + return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory + + RefHashElement *element = static_cast<RefHashElement *>(entry); + if (!element->mRef) + { + element->mRef = ToNewCString(reference); // Will be freed in msg_DHashFreeStringKey() + element->mThreadId = threadId; + element->mCount = 1; + } + else + element->mCount++; + } + + return NS_OK; +} + +nsresult nsMsgDatabase::AddMsgRefsToHash(nsIMsgDBHdr *msgHdr) +{ + uint16_t numReferences = 0; + nsMsgKey threadId; + nsresult rv = NS_OK; + + msgHdr->GetThreadId(&threadId); + msgHdr->GetNumReferences(&numReferences); + + for (int32_t i = 0; i < numReferences; i++) + { + nsAutoCString reference; + + msgHdr->GetStringReference(i, reference); + if (reference.IsEmpty()) + break; + + rv = AddRefToHash(reference, threadId); + if (NS_FAILED(rv)) + break; + } + + return rv; +} + +nsresult nsMsgDatabase::RemoveRefFromHash(nsCString &reference) +{ + if (m_msgReferences) + { + PLDHashEntryHdr *entry = + m_msgReferences->Search((const void *) reference.get()); + if (entry) + { + RefHashElement *element = static_cast<RefHashElement *>(entry); + if (--element->mCount == 0) + m_msgReferences->Remove((void *) reference.get()); + } + } + return NS_OK; +} + +// Filter only messages with one or more references +nsresult nsMsgDatabase::RemoveMsgRefsFromHash(nsIMsgDBHdr *msgHdr) +{ + uint16_t numReferences = 0; + nsresult rv = NS_OK; + + msgHdr->GetNumReferences(&numReferences); + + for (int32_t i = 0; i < numReferences; i++) + { + nsAutoCString reference; + + msgHdr->GetStringReference(i, reference); + if (reference.IsEmpty()) + break; + + rv = RemoveRefFromHash(reference); + if (NS_FAILED(rv)) + break; + } + + return rv; +} + +static nsresult nsReferencesOnlyFilter(nsIMsgDBHdr *msg, void *closure) +{ + uint16_t numReferences = 0; + msg->GetNumReferences(&numReferences); + return (numReferences) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsMsgDatabase::InitRefHash() +{ + // Delete an existing table just in case + if (m_msgReferences) + delete m_msgReferences; + + // Create new table + m_msgReferences = new PLDHashTable(&gRefHashTableOps, sizeof(struct RefHashElement), MSG_HASH_SIZE); + if (!m_msgReferences) + return NS_ERROR_OUT_OF_MEMORY; + + // Create enumerator to go through all messages with references + nsCOMPtr <nsISimpleEnumerator> enumerator; + enumerator = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsReferencesOnlyFilter, nullptr); + if (enumerator == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + // Populate table with references of existing messages + bool hasMore; + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr <nsISupports> supports; + rv = enumerator->GetNext(getter_AddRefs(supports)); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(supports); + if (msgHdr && NS_SUCCEEDED(rv)) + rv = AddMsgRefsToHash(msgHdr); + if (NS_FAILED(rv)) + break; + } + + return rv; +} + +nsresult nsMsgDatabase::CreateNewThread(nsMsgKey threadId, const char *subject, nsMsgThread **pnewThread) +{ + nsresult err = NS_OK; + nsCOMPtr<nsIMdbTable> threadTable; + struct mdbOid threadTableOID; + struct mdbOid allThreadsTableOID; + + if (!pnewThread || !m_mdbStore) + return NS_ERROR_NULL_POINTER; + + threadTableOID.mOid_Scope = m_hdrRowScopeToken; + threadTableOID.mOid_Id = threadId; + + // Under some circumstances, mork seems to reuse an old table when we create one. + // Prevent problems from that by finding any old table first, and deleting its rows. + nsresult res = GetStore()->GetTable(GetEnv(), &threadTableOID, getter_AddRefs(threadTable)); + if (NS_SUCCEEDED(res) && threadTable) + threadTable->CutAllRows(GetEnv()); + + err = GetStore()->NewTableWithOid(GetEnv(), &threadTableOID, m_threadTableKindToken, + false, nullptr, getter_AddRefs(threadTable)); + if (NS_FAILED(err)) + return err; + + allThreadsTableOID.mOid_Scope = m_threadRowScopeToken; + allThreadsTableOID.mOid_Id = threadId; + + // add a row for this thread in the table of all threads that we'll use + // to do our mapping between subject strings and threads. + nsCOMPtr<nsIMdbRow> threadRow; + + err = m_mdbStore->GetRow(GetEnv(), &allThreadsTableOID, + getter_AddRefs(threadRow)); + if (!threadRow) + { + err = m_mdbStore->NewRowWithOid(GetEnv(), &allThreadsTableOID, + getter_AddRefs(threadRow)); + if (NS_SUCCEEDED(err) && threadRow) + { + if (m_mdbAllThreadsTable) + m_mdbAllThreadsTable->AddRow(GetEnv(), threadRow); + err = CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken, subject); + } + } + else + { +#ifdef DEBUG_David_Bienvenu + NS_WARNING("odd that thread row already exists"); +#endif + threadRow->CutAllColumns(GetEnv()); + nsCOMPtr<nsIMdbRow> metaRow; + threadTable->GetMetaRow(GetEnv(), nullptr, nullptr, getter_AddRefs(metaRow)); + if (metaRow) + metaRow->CutAllColumns(GetEnv()); + + CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken, subject); + } + + + *pnewThread = new nsMsgThread(this, threadTable); + if (*pnewThread) + { + (*pnewThread)->SetThreadKey(threadId); + m_cachedThread = *pnewThread; + m_cachedThreadId = threadId; + } + return err; +} + + +nsIMsgThread *nsMsgDatabase::GetThreadForReference(nsCString &msgID, nsIMsgDBHdr **pMsgHdr) +{ + nsMsgKey threadId; + nsIMsgDBHdr *msgHdr = nullptr; + GetMsgHdrForMessageID(msgID.get(), &msgHdr); + nsIMsgThread *thread = NULL; + + if (msgHdr != NULL) + { + if (NS_SUCCEEDED(msgHdr->GetThreadId(&threadId))) + { + // find thread header for header whose message id we matched. + thread = GetThreadForThreadId(threadId); + } + if (pMsgHdr) + *pMsgHdr = msgHdr; + else + msgHdr->Release(); + } + // Referenced message not found, check if there are messages that reference same message + else if (UseCorrectThreading()) + { + if (NS_SUCCEEDED(GetRefFromHash(msgID, &threadId))) + thread = GetThreadForThreadId(threadId); + } + + return thread; +} + +nsIMsgThread * nsMsgDatabase::GetThreadForSubject(nsCString &subject) +{ + nsIMsgThread *thread = nullptr; + + mdbYarn subjectYarn; + + subjectYarn.mYarn_Buf = (void*)subject.get(); + subjectYarn.mYarn_Fill = PL_strlen(subject.get()); + subjectYarn.mYarn_Form = 0; + subjectYarn.mYarn_Size = subjectYarn.mYarn_Fill; + + nsCOMPtr <nsIMdbRow> threadRow; + mdbOid outRowId; + if (m_mdbStore) + { + nsresult result = m_mdbStore->FindRow(GetEnv(), m_threadRowScopeToken, + m_threadSubjectColumnToken, &subjectYarn, &outRowId, getter_AddRefs(threadRow)); + if (NS_SUCCEEDED(result) && threadRow) + { + //Get key from row + mdbOid outOid; + nsMsgKey key = nsMsgKey_None; + if (NS_SUCCEEDED(threadRow->GetOid(GetEnv(), &outOid))) + key = outOid.mOid_Id; + // find thread header for header whose message id we matched. + // It is fine if key was not found, + // GetThreadForThreadId(nsMsgKey_None) returns nullptr. + thread = GetThreadForThreadId(key); + } +#ifdef DEBUG_bienvenu1 + else + { + nsresult rv; + RefPtr<nsMsgThread> pThread; + + nsCOMPtr <nsIMdbPortTableCursor> tableCursor; + m_mdbStore->GetPortTableCursor(GetEnv(), m_hdrRowScopeToken, m_threadTableKindToken, + getter_AddRefs(tableCursor)); + + nsCOMPtr<nsIMdbTable> table; + + while (true) + { + rv = tableCursor->NextTable(GetEnv(), getter_AddRefs(table)); + if (!table) + break; + if (NS_FAILED(rv)) + break; + + pThread = new nsMsgThread(this, table); + if (pThread) + { + nsCString curSubject; + pThread->GetSubject(curSubject); + if (subject.Equals(curSubject)) + { + NS_ERROR("thread with subject exists, but FindRow didn't find it\n"); + break; + } + } + else + break; + } + } +#endif + } + return thread; +} + +// Returns thread that contains a message that references the passed message ID +nsIMsgThread *nsMsgDatabase::GetThreadForMessageId(nsCString &msgId) +{ + nsIMsgThread *thread = NULL; + nsMsgKey threadId; + + if (NS_SUCCEEDED(GetRefFromHash(msgId, &threadId))) + thread = GetThreadForThreadId(threadId); + + return thread; +} + +nsresult nsMsgDatabase::ThreadNewHdr(nsMsgHdr* newHdr, bool &newThread) +{ + nsresult result=NS_ERROR_UNEXPECTED; + nsCOMPtr <nsIMsgThread> thread; + nsCOMPtr <nsIMsgDBHdr> replyToHdr; + nsMsgKey threadId = nsMsgKey_None, newHdrKey; + + if (!newHdr) + return NS_ERROR_NULL_POINTER; + + newHdr->SetThreadParent(nsMsgKey_None); // if we're undoing, could have a thread parent + uint16_t numReferences = 0; + uint32_t newHdrFlags = 0; + + // use raw flags instead of GetFlags, because GetFlags will + // pay attention to what's in m_newSet, and this new hdr isn't + // in m_newSet yet. + newHdr->GetRawFlags(&newHdrFlags); + newHdr->GetNumReferences(&numReferences); + newHdr->GetMessageKey(&newHdrKey); + + // try reference threading first + for (int32_t i = numReferences - 1; i >= 0; i--) + { + nsAutoCString reference; + + newHdr->GetStringReference(i, reference); + // first reference we have hdr for is best top-level hdr. + // but we have to handle case of promoting new header to top-level + // in case the top-level header comes after a reply. + + if (reference.IsEmpty()) + break; + + thread = dont_AddRef(GetThreadForReference(reference, getter_AddRefs(replyToHdr))) ; + if (thread) + { + if (replyToHdr) + { + nsMsgKey replyToKey; + replyToHdr->GetMessageKey(&replyToKey); + // message claims to be a reply to itself - ignore that since it leads to corrupt threading. + if (replyToKey == newHdrKey) + { + // bad references - throw them all away. + newHdr->SetMessageId(""); + thread = nullptr; + break; + } + } + thread->GetThreadKey(&threadId); + newHdr->SetThreadId(threadId); + result = AddToThread(newHdr, thread, replyToHdr, true); + break; + } + } + // if user hasn't said "only thread by ref headers", thread by subject + if (!thread && !UseStrictThreading()) + { + // try subject threading if we couldn't find a reference and the subject starts with Re: + nsCString subject; + newHdr->GetSubject(getter_Copies(subject)); + if (ThreadBySubjectWithoutRe() || (newHdrFlags & nsMsgMessageFlags::HasRe)) + { + nsAutoCString cSubject(subject); + thread = dont_AddRef(GetThreadForSubject(cSubject)); + if(thread) + { + thread->GetThreadKey(&threadId); + newHdr->SetThreadId(threadId); + //TRACE("threading based on subject %s\n", (const char *) msgHdr->m_subject); + // if we move this and do subject threading after, ref threading, + // don't thread within children, since we know it won't work. But for now, pass TRUE. + result = AddToThread(newHdr, thread, nullptr, true); + } + } + } + + // Check if this is a new parent to an existing message (that has a reference to this message) + if (!thread && UseCorrectThreading()) + { + nsCString msgId; + newHdr->GetMessageId(getter_Copies(msgId)); + + thread = dont_AddRef(GetThreadForMessageId(msgId)); + if (thread) + { + thread->GetThreadKey(&threadId); + newHdr->SetThreadId(threadId); + result = AddToThread(newHdr, thread, nullptr, true); + } + } + + if (!thread) + { + // Not a parent or child, make it a new thread for now + result = AddNewThread(newHdr); + newThread = true; + } + else + { + newThread = false; + } + return result; +} + +nsresult nsMsgDatabase::AddToThread(nsMsgHdr *newHdr, nsIMsgThread *thread, nsIMsgDBHdr *inReplyTo, bool threadInThread) +{ + // don't worry about real threading yet. + nsCOMPtr <nsIDBChangeAnnouncer> announcer = do_QueryInterface(this); + + return thread->AddChild(newHdr, inReplyTo, threadInThread, announcer); +} + +nsMsgHdr * nsMsgDatabase::GetMsgHdrForReference(nsCString &reference) +{ + NS_ASSERTION(false, "not implemented yet."); + return nullptr; +} + +NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForMessageID(const char *aMsgID, nsIMsgDBHdr **aHdr) +{ + NS_ENSURE_ARG_POINTER(aHdr); + NS_ENSURE_ARG_POINTER(aMsgID); + nsIMsgDBHdr *msgHdr = nullptr; + nsresult rv = NS_OK; + mdbYarn messageIdYarn; + + messageIdYarn.mYarn_Buf = (void *) aMsgID; + messageIdYarn.mYarn_Fill = PL_strlen(aMsgID); + messageIdYarn.mYarn_Form = 0; + messageIdYarn.mYarn_Size = messageIdYarn.mYarn_Fill; + + nsIMdbRow *hdrRow; + mdbOid outRowId; + nsresult result; + if (m_mdbStore) + result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken, + m_messageIdColumnToken, &messageIdYarn, &outRowId, + &hdrRow); + else + return NS_ERROR_FAILURE; + if (NS_SUCCEEDED(result) && hdrRow) + { + //Get key from row + mdbOid outOid; + nsMsgKey key = nsMsgKey_None; + rv = hdrRow->GetOid(GetEnv(), &outOid); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + key = outOid.mOid_Id; + + rv = GetHdrFromUseCache(key, &msgHdr); + if (NS_SUCCEEDED(rv) && msgHdr) + hdrRow->Release(); + else { + rv = CreateMsgHdr(hdrRow, key, &msgHdr); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + } + } + *aHdr = msgHdr; // already addreffed above. + return NS_OK; // it's not an error not to find a msg hdr. +} + +NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForGMMsgID(const char *aGMMsgId, nsIMsgDBHdr **aHdr) +{ + NS_ENSURE_ARG_POINTER(aGMMsgId); + NS_ENSURE_ARG_POINTER(aHdr); + nsIMsgDBHdr *msgHdr = nullptr; + nsresult rv = NS_OK; + mdbYarn gMailMessageIdYarn; + gMailMessageIdYarn.mYarn_Buf = (void *) aGMMsgId; + gMailMessageIdYarn.mYarn_Fill = strlen(aGMMsgId); + gMailMessageIdYarn.mYarn_Form = 0; + gMailMessageIdYarn.mYarn_Size = gMailMessageIdYarn.mYarn_Fill; + + nsIMdbRow *hdrRow; + mdbOid outRowId; + nsresult result; + mdb_token property_token; + NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER); + result = m_mdbStore->StringToToken(GetEnv(), "X-GM-MSGID", + &property_token); + NS_ENSURE_SUCCESS(result, result); + result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken, + property_token, &gMailMessageIdYarn, &outRowId, &hdrRow); + if (NS_SUCCEEDED(result) && hdrRow) + { + // Get key from row + mdbOid outOid; + rv = hdrRow->GetOid(GetEnv(), &outOid); + NS_ENSURE_SUCCESS(rv, rv); + nsMsgKey key = outOid.mOid_Id; + rv = GetHdrFromUseCache(key, &msgHdr); + if ((NS_SUCCEEDED(rv) && msgHdr)) + hdrRow->Release(); + else { + rv = CreateMsgHdr(hdrRow, key, &msgHdr); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + } + } + *aHdr = msgHdr; + return NS_OK; // it's not an error not to find a msg hdr. +} + +nsIMsgDBHdr *nsMsgDatabase::GetMsgHdrForSubject(nsCString &subject) +{ + nsIMsgDBHdr *msgHdr = nullptr; + nsresult rv = NS_OK; + mdbYarn subjectYarn; + + subjectYarn.mYarn_Buf = (void*)subject.get(); + subjectYarn.mYarn_Fill = PL_strlen(subject.get()); + subjectYarn.mYarn_Form = 0; + subjectYarn.mYarn_Size = subjectYarn.mYarn_Fill; + + nsIMdbRow *hdrRow; + mdbOid outRowId; + nsresult result = GetStore()->FindRow(GetEnv(), m_hdrRowScopeToken, + m_subjectColumnToken, &subjectYarn, &outRowId, + &hdrRow); + if (NS_SUCCEEDED(result) && hdrRow) + { + //Get key from row + mdbOid outOid; + nsMsgKey key = nsMsgKey_None; + rv = hdrRow->GetOid(GetEnv(), &outOid); + if (NS_WARN_IF(NS_FAILED(rv))) + return nullptr; + key = outOid.mOid_Id; + + rv = GetHdrFromUseCache(key, &msgHdr); + if (NS_SUCCEEDED(rv) && msgHdr) + hdrRow->Release(); + else { + rv = CreateMsgHdr(hdrRow, key, &msgHdr); + if (NS_WARN_IF(NS_FAILED(rv))) + return nullptr; + } + } + return msgHdr; +} + +NS_IMETHODIMP nsMsgDatabase::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **result) +{ + NS_ENSURE_ARG_POINTER(msgHdr); + NS_ENSURE_ARG_POINTER(result); + + *result = nullptr; + nsMsgKey threadId = nsMsgKey_None; + (void)msgHdr->GetThreadId(&threadId); + if (threadId != nsMsgKey_None) + *result = GetThreadForThreadId(threadId); + + // if we can't find the thread, try using the msg key as the thread id, + // because the msg hdr might not have the thread id set correctly + // Or maybe the message was deleted? + if (!*result) + { + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + *result = GetThreadForThreadId(msgKey); + } + // failure is normal when message was deleted + return (*result) ? NS_OK : NS_ERROR_FAILURE; +} + + +nsresult nsMsgDatabase::GetThreadForMsgKey(nsMsgKey msgKey, nsIMsgThread **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr <nsIMsgDBHdr> msg; + nsresult rv = GetMsgHdrForKey(msgKey, getter_AddRefs(msg)); + + if (NS_SUCCEEDED(rv) && msg) + rv = GetThreadContainingMsgHdr(msg, aResult); + + return rv; +} + +// caller needs to unrefer. +nsIMsgThread * nsMsgDatabase::GetThreadForThreadId(nsMsgKey threadId) +{ + + nsIMsgThread *retThread = (threadId == m_cachedThreadId && m_cachedThread) ? + m_cachedThread.get() : FindExistingThread(threadId); + if (retThread) + { + NS_ADDREF(retThread); + return retThread; + } + if (m_mdbStore) + { + mdbOid tableId; + tableId.mOid_Id = threadId; + tableId.mOid_Scope = m_hdrRowScopeToken; + + nsCOMPtr<nsIMdbTable> threadTable; + nsresult res = m_mdbStore->GetTable(GetEnv(), &tableId, + getter_AddRefs(threadTable)); + + if (NS_SUCCEEDED(res) && threadTable) + { + retThread = new nsMsgThread(this, threadTable); + if (retThread) + { + NS_ADDREF(retThread); + m_cachedThread = retThread; + m_cachedThreadId = threadId; + } + } + } + return retThread; +} + +// make the passed in header a thread header +nsresult nsMsgDatabase::AddNewThread(nsMsgHdr *msgHdr) +{ + + if (!msgHdr) + return NS_ERROR_NULL_POINTER; + + nsMsgThread *threadHdr = nullptr; + + nsCString subject; + nsMsgKey threadKey = msgHdr->m_messageKey; + // can't have a thread with key 1 since that's the table id of the all msg hdr table, + // so give it kTableKeyForThreadOne (0xfffffffe). + if (threadKey == kAllMsgHdrsTableKey) + threadKey = kTableKeyForThreadOne; + + nsresult err = msgHdr->GetSubject(getter_Copies(subject)); + + err = CreateNewThread(threadKey, subject.get(), &threadHdr); + msgHdr->SetThreadId(threadKey); + if (threadHdr) + { + threadHdr->AddRef(); + // err = msgHdr->GetSubject(subject); + // threadHdr->SetThreadKey(msgHdr->m_messageKey); + // threadHdr->SetSubject(subject.get()); + // need to add the thread table to the db. + AddToThread(msgHdr, threadHdr, nullptr, false); + threadHdr->Release(); + } + return err; +} + +nsresult nsMsgDatabase::GetBoolPref(const char *prefName, bool *result) +{ + bool prefValue = false; + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + rv = pPrefBranch->GetBoolPref(prefName, &prefValue); + *result = prefValue; + } + return rv; +} + +nsresult nsMsgDatabase::GetIntPref(const char *prefName, int32_t *result) +{ + int32_t prefValue = 0; + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + rv = pPrefBranch->GetIntPref(prefName, &prefValue); + *result = prefValue; + } + return rv; +} + + +nsresult nsMsgDatabase::ListAllThreads(nsTArray<nsMsgKey> *threadIds) +{ + nsresult rv; + nsMsgThread *pThread; + + nsCOMPtr <nsISimpleEnumerator> threads; + rv = EnumerateThreads(getter_AddRefs(threads)); + if (NS_FAILED(rv)) return rv; + bool hasMore = false; + + while (NS_SUCCEEDED(rv = threads->HasMoreElements(&hasMore)) && hasMore) + { + rv = threads->GetNext((nsISupports**)&pThread); + NS_ENSURE_SUCCESS(rv,rv); + + if (threadIds) { + nsMsgKey key; + (void)pThread->GetThreadKey(&key); + threadIds->AppendElement(key); + } + // NS_RELEASE(pThread); + pThread = nullptr; + } + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::SetAttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property, + const char *propertyVal) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDatabase::SetUint32AttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property, + uint32_t propertyVal) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgDatabase::SetUint64AttributeOnPendingHdr(nsIMsgDBHdr *aPendingHdr, + const char *aProperty, + uint64_t aPropertyVal) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgDatabase::UpdatePendingAttributes(nsIMsgDBHdr *aNewHdr) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetOfflineOpForKey(nsMsgKey msgKey, bool create, nsIMsgOfflineImapOperation **offlineOp) +{ + NS_ASSERTION(false, "overridden by nsMailDatabase"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDatabase::RemoveOfflineOp(nsIMsgOfflineImapOperation *op) +{ + NS_ASSERTION(false, "overridden by nsMailDatabase"); + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP nsMsgDatabase::ListAllOfflineMsgs(nsIMsgKeyArray *aKeys) +{ + NS_ENSURE_ARG_POINTER(aKeys); + nsCOMPtr <nsISimpleEnumerator> enumerator; + uint32_t flag = nsMsgMessageFlags::Offline; + // if we change this routine to return an enumerator that generates the keys + // one by one, we'll need to somehow make a copy of flag for the enumerator + // to own, since the enumerator will persist past the life of flag on the stack. + nsresult rv = EnumerateMessagesWithFlag(getter_AddRefs(enumerator), &flag); + if (NS_SUCCEEDED(rv) && enumerator) + { + bool hasMoreElements; + while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && hasMoreElements) + { + nsCOMPtr<nsISupports> childSupports; + rv = enumerator->GetNext(getter_AddRefs(childSupports)); + if(NS_FAILED(rv)) + return rv; + + // clear out db hdr, because it won't be valid when we get rid of the .msf file + nsCOMPtr<nsIMsgDBHdr> dbMessage(do_QueryInterface(childSupports, &rv)); + if(NS_SUCCEEDED(rv) && dbMessage) + { + nsMsgKey msgKey; + dbMessage->GetMessageKey(&msgKey); + aKeys->AppendElement(msgKey); + } + } + } + return rv; +} + +NS_IMETHODIMP nsMsgDatabase::EnumerateOfflineOps(nsISimpleEnumerator **enumerator) +{ + NS_ASSERTION(false, "overridden by nsMailDatabase"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDatabase::ListAllOfflineOpIds(nsTArray<nsMsgKey> *offlineOpIds) +{ + NS_ASSERTION(false, "overridden by nsMailDatabase"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDatabase::ListAllOfflineDeletes(nsTArray<nsMsgKey> *offlineDeletes) +{ + nsresult ret = NS_OK; + if (!offlineDeletes) + return NS_ERROR_NULL_POINTER; + + // technically, notimplemented, but no one's putting offline ops in anyway. + return ret; +} +NS_IMETHODIMP nsMsgDatabase::GetHighWaterArticleNum(nsMsgKey *key) +{ + if (!m_dbFolderInfo) + return NS_ERROR_NULL_POINTER; + return m_dbFolderInfo->GetHighWater(key); +} + +NS_IMETHODIMP nsMsgDatabase::GetLowWaterArticleNum(nsMsgKey *key) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* attribute nsMsgKey NextPseudoMsgKey */ + +NS_IMETHODIMP nsMsgDatabase::GetNextPseudoMsgKey(nsMsgKey *nextPseudoMsgKey) +{ + NS_ENSURE_ARG_POINTER(nextPseudoMsgKey); + *nextPseudoMsgKey = m_nextPseudoMsgKey--; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::SetNextPseudoMsgKey(nsMsgKey nextPseudoMsgKey) +{ + m_nextPseudoMsgKey = nextPseudoMsgKey; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetNextFakeOfflineMsgKey(nsMsgKey *nextFakeOfflineMsgKey) +{ + NS_ENSURE_ARG_POINTER(nextFakeOfflineMsgKey); + // iterate over hdrs looking for first non-existant fake offline msg key + nsMsgKey fakeMsgKey = kIdStartOfFake; + + bool containsKey; + do + { + ContainsKey(fakeMsgKey, &containsKey); + if (!containsKey) + break; + fakeMsgKey--; + } + while (containsKey); + + *nextFakeOfflineMsgKey = fakeMsgKey; + return NS_OK; +} + +#ifdef DEBUG +nsresult nsMsgDatabase::DumpContents() +{ + nsMsgKey key; + uint32_t i; + + RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray; + if (!keys) + return NS_ERROR_OUT_OF_MEMORY; + nsresult rv = ListAllKeys(keys); + uint32_t numKeys; + keys->GetLength(&numKeys); + for (i = 0; i < numKeys; i++) { + key = keys->m_keys[i]; + nsIMsgDBHdr *msg = NULL; + rv = GetMsgHdrForKey(key, &msg); + nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(msg); // closed system, cast ok + if (NS_SUCCEEDED(rv)) + { + nsCString author; + nsCString subject; + + msgHdr->GetMessageKey(&key); + msgHdr->GetAuthor(getter_Copies(author)); + msgHdr->GetSubject(getter_Copies(subject)); + printf("hdr key = %u, author = %s subject = %s\n", key, author.get(), subject.get()); + NS_RELEASE(msgHdr); + } + } + nsTArray<nsMsgKey> threads; + rv = ListAllThreads(&threads); + for ( i = 0; i < threads.Length(); i++) + { + key = threads[i]; + printf("thread key = %u\n", key); + // DumpThread(key); + } + return NS_OK; +} + +nsresult nsMsgDatabase::DumpMsgChildren(nsIMsgDBHdr *msgHdr) +{ + return NS_OK; +} + +nsresult nsMsgDatabase::DumpThread(nsMsgKey threadId) +{ + nsresult rv = NS_OK; + nsIMsgThread *thread = nullptr; + + thread = GetThreadForThreadId(threadId); + if (thread) + { + nsISimpleEnumerator *enumerator = nullptr; + + rv = thread->EnumerateMessages(nsMsgKey_None, &enumerator); + if (NS_SUCCEEDED(rv) && enumerator) + { + bool hasMore = false; + + while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && + hasMore) + { + nsCOMPtr <nsISupports> supports; + rv = enumerator->GetNext(getter_AddRefs(supports)); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + nsCOMPtr <nsIMsgDBHdr> pMessage = do_QueryInterface(supports); + if (NS_FAILED(rv) || !pMessage) + break; + } + NS_RELEASE(enumerator); + + } + } + return rv; +} +#endif /* DEBUG */ + +NS_IMETHODIMP nsMsgDatabase::SetMsgRetentionSettings(nsIMsgRetentionSettings *retentionSettings) +{ + m_retentionSettings = retentionSettings; + if (retentionSettings && m_dbFolderInfo) + { + nsresult rv; + + nsMsgRetainByPreference retainByPreference; + uint32_t daysToKeepHdrs; + uint32_t numHeadersToKeep; + uint32_t daysToKeepBodies; + bool cleanupBodiesByDays; + bool useServerDefaults; + bool applyToFlaggedMessages; + + rv = retentionSettings->GetRetainByPreference(&retainByPreference); + NS_ENSURE_SUCCESS(rv, rv); + rv = retentionSettings->GetDaysToKeepHdrs(&daysToKeepHdrs); + NS_ENSURE_SUCCESS(rv, rv); + rv = retentionSettings->GetNumHeadersToKeep(&numHeadersToKeep); + NS_ENSURE_SUCCESS(rv, rv); + rv = retentionSettings->GetDaysToKeepBodies(&daysToKeepBodies); + NS_ENSURE_SUCCESS(rv, rv); + (void) retentionSettings->GetCleanupBodiesByDays(&cleanupBodiesByDays); + (void) retentionSettings->GetUseServerDefaults(&useServerDefaults); + rv = retentionSettings->GetApplyToFlaggedMessages(&applyToFlaggedMessages); + NS_ENSURE_SUCCESS(rv, rv); + // need to write this to the db. We'll just use the dbfolderinfo to write properties. + m_dbFolderInfo->SetUint32Property("retainBy", retainByPreference); + m_dbFolderInfo->SetUint32Property("daysToKeepHdrs", daysToKeepHdrs); + m_dbFolderInfo->SetUint32Property("numHdrsToKeep", numHeadersToKeep); + m_dbFolderInfo->SetUint32Property("daysToKeepBodies", daysToKeepBodies); + m_dbFolderInfo->SetBooleanProperty("cleanupBodies", cleanupBodiesByDays); + m_dbFolderInfo->SetBooleanProperty("useServerDefaults", useServerDefaults); + m_dbFolderInfo->SetBooleanProperty("applyToFlaggedMessages", applyToFlaggedMessages); + } + Commit(nsMsgDBCommitType::kLargeCommit); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetMsgRetentionSettings(nsIMsgRetentionSettings **retentionSettings) +{ + NS_ENSURE_ARG_POINTER(retentionSettings); + if (!m_retentionSettings) + { + // create a new one, and initialize it from the db. + m_retentionSettings = new nsMsgRetentionSettings; + if (m_retentionSettings && m_dbFolderInfo) + { + nsMsgRetainByPreference retainByPreference; + uint32_t daysToKeepHdrs = 0; + uint32_t numHeadersToKeep = 0; + bool useServerDefaults; + uint32_t daysToKeepBodies = 0; + bool cleanupBodiesByDays = false; + bool applyToFlaggedMessages; + + m_dbFolderInfo->GetUint32Property("retainBy", nsIMsgRetentionSettings::nsMsgRetainAll, &retainByPreference); + m_dbFolderInfo->GetUint32Property("daysToKeepHdrs", 0, &daysToKeepHdrs); + m_dbFolderInfo->GetUint32Property("numHdrsToKeep", 0, &numHeadersToKeep); + m_dbFolderInfo->GetUint32Property("daysToKeepBodies", 0, &daysToKeepBodies); + m_dbFolderInfo->GetBooleanProperty("useServerDefaults", true, &useServerDefaults); + m_dbFolderInfo->GetBooleanProperty("cleanupBodies", false, &cleanupBodiesByDays); + m_dbFolderInfo->GetBooleanProperty("applyToFlaggedMessages", false, + &applyToFlaggedMessages); + m_retentionSettings->SetRetainByPreference(retainByPreference); + m_retentionSettings->SetDaysToKeepHdrs(daysToKeepHdrs); + m_retentionSettings->SetNumHeadersToKeep(numHeadersToKeep); + m_retentionSettings->SetDaysToKeepBodies(daysToKeepBodies); + m_retentionSettings->SetUseServerDefaults(useServerDefaults); + m_retentionSettings->SetCleanupBodiesByDays(cleanupBodiesByDays); + m_retentionSettings->SetApplyToFlaggedMessages(applyToFlaggedMessages); + } + } + *retentionSettings = m_retentionSettings; + NS_IF_ADDREF(*retentionSettings); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::SetMsgDownloadSettings(nsIMsgDownloadSettings *downloadSettings) +{ + m_downloadSettings = downloadSettings; + if (downloadSettings && m_dbFolderInfo) + { + nsresult rv; + + bool useServerDefaults; + bool downloadByDate; + uint32_t ageLimitOfMsgsToDownload; + bool downloadUnreadOnly; + + rv = downloadSettings->GetUseServerDefaults(&useServerDefaults); + NS_ENSURE_SUCCESS(rv, rv); + rv = downloadSettings->GetDownloadByDate(&downloadByDate); + NS_ENSURE_SUCCESS(rv, rv); + rv = downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly); + NS_ENSURE_SUCCESS(rv, rv); + rv = downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload); + NS_ENSURE_SUCCESS(rv, rv); + // need to write this to the db. We'll just use the dbfolderinfo to write properties. + m_dbFolderInfo->SetBooleanProperty("useServerDefaults", useServerDefaults); + m_dbFolderInfo->SetBooleanProperty("downloadByDate", downloadByDate); + m_dbFolderInfo->SetBooleanProperty("downloadUnreadOnly", downloadUnreadOnly); + m_dbFolderInfo->SetUint32Property("ageLimit", ageLimitOfMsgsToDownload); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetMsgDownloadSettings(nsIMsgDownloadSettings **downloadSettings) +{ + NS_ENSURE_ARG_POINTER(downloadSettings); + if (!m_downloadSettings) + { + // create a new one, and initialize it from the db. + m_downloadSettings = new nsMsgDownloadSettings; + if (m_downloadSettings && m_dbFolderInfo) + { + bool useServerDefaults; + bool downloadByDate; + uint32_t ageLimitOfMsgsToDownload; + bool downloadUnreadOnly; + + m_dbFolderInfo->GetBooleanProperty("useServerDefaults", true, &useServerDefaults); + m_dbFolderInfo->GetBooleanProperty("downloadByDate", false, &downloadByDate); + m_dbFolderInfo->GetBooleanProperty("downloadUnreadOnly", false, &downloadUnreadOnly); + m_dbFolderInfo->GetUint32Property("ageLimit", 0, &ageLimitOfMsgsToDownload); + + m_downloadSettings->SetUseServerDefaults(useServerDefaults); + m_downloadSettings->SetDownloadByDate(downloadByDate); + m_downloadSettings->SetDownloadUnreadOnly(downloadUnreadOnly); + m_downloadSettings->SetAgeLimitOfMsgsToDownload(ageLimitOfMsgsToDownload); + } + } + *downloadSettings = m_downloadSettings; + NS_IF_ADDREF(*downloadSettings); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::ApplyRetentionSettings(nsIMsgRetentionSettings *aMsgRetentionSettings, + bool aDeleteViaFolder) +{ + NS_ENSURE_ARG_POINTER(aMsgRetentionSettings); + nsresult rv = NS_OK; + + if (!m_folder) + return NS_ERROR_NULL_POINTER; + + bool isDraftsTemplatesOutbox; + uint32_t dtoFlags = nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates | + nsMsgFolderFlags::Queue; + (void) m_folder->IsSpecialFolder(dtoFlags, true, &isDraftsTemplatesOutbox); + // Never apply retention settings to Drafts/Templates/Outbox. + if (isDraftsTemplatesOutbox) + return NS_OK; + + nsCOMPtr <nsIMutableArray> msgHdrsToDelete; + if (aDeleteViaFolder) + { + msgHdrsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + nsMsgRetainByPreference retainByPreference; + aMsgRetentionSettings->GetRetainByPreference(&retainByPreference); + + bool applyToFlaggedMessages = false; + aMsgRetentionSettings->GetApplyToFlaggedMessages(&applyToFlaggedMessages); + + uint32_t daysToKeepHdrs = 0; + uint32_t numHeadersToKeep = 0; + switch (retainByPreference) + { + case nsIMsgRetentionSettings::nsMsgRetainAll: + break; + case nsIMsgRetentionSettings::nsMsgRetainByAge: + aMsgRetentionSettings->GetDaysToKeepHdrs(&daysToKeepHdrs); + rv = PurgeMessagesOlderThan(daysToKeepHdrs, + applyToFlaggedMessages, msgHdrsToDelete); + break; + case nsIMsgRetentionSettings::nsMsgRetainByNumHeaders: + aMsgRetentionSettings->GetNumHeadersToKeep(&numHeadersToKeep); + rv = PurgeExcessMessages(numHeadersToKeep, + applyToFlaggedMessages, msgHdrsToDelete); + break; + } + if (m_folder) + { + // update the time we attempted to purge this folder + char dateBuf[100]; + dateBuf[0] = '\0'; + PRExplodedTime exploded; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y", &exploded); + m_folder->SetStringProperty("LastPurgeTime", nsDependentCString(dateBuf)); + } + if (msgHdrsToDelete) + { + uint32_t count; + msgHdrsToDelete->GetLength(&count); + if (count > 0) + rv = m_folder->DeleteMessages(msgHdrsToDelete, nullptr, true, false, nullptr, false); + } + return rv; +} + +nsresult nsMsgDatabase::PurgeMessagesOlderThan(uint32_t daysToKeepHdrs, + bool applyToFlaggedMessages, + nsIMutableArray *hdrsToDelete) +{ + nsresult rv = NS_OK; + nsMsgHdr *pHeader; + nsCOMPtr <nsISimpleEnumerator> hdrs; + rv = EnumerateMessages(getter_AddRefs(hdrs)); + nsTArray<nsMsgKey> keysToDelete; + + if (NS_FAILED(rv)) + return rv; + bool hasMore = false; + + PRTime cutOffDay = PR_Now() - daysToKeepHdrs * PR_USEC_PER_DAY; + + // so now cutOffDay is the PRTime cut-off point. Any msg with a date less than that will get purged. + while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) + { + bool purgeHdr = false; + + rv = hdrs->GetNext((nsISupports**)&pHeader); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + if (NS_FAILED(rv)) + break; + + if (!applyToFlaggedMessages) + { + uint32_t flags; + (void)pHeader->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Marked) + continue; + } + + if (!purgeHdr) + { + PRTime date; + pHeader->GetDate(&date); + if (date < cutOffDay) + purgeHdr = true; + } + if (purgeHdr) + { + nsMsgKey msgKey; + pHeader->GetMessageKey(&msgKey); + keysToDelete.AppendElement(msgKey); + if (hdrsToDelete) + hdrsToDelete->AppendElement(pHeader, false); + } + NS_RELEASE(pHeader); + } + + if (!hdrsToDelete) + { + DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr); + + if (keysToDelete.Length() > 10) // compress commit if we deleted more than 10 + Commit(nsMsgDBCommitType::kCompressCommit); + else if (!keysToDelete.IsEmpty()) + Commit(nsMsgDBCommitType::kLargeCommit); + } + return rv; +} + +nsresult nsMsgDatabase::PurgeExcessMessages(uint32_t numHeadersToKeep, + bool applyToFlaggedMessages, + nsIMutableArray *hdrsToDelete) +{ + nsresult rv = NS_OK; + nsMsgHdr *pHeader; + nsCOMPtr <nsISimpleEnumerator> hdrs; + rv = EnumerateMessages(getter_AddRefs(hdrs)); + if (NS_FAILED(rv)) + return rv; + bool hasMore = false; + nsTArray<nsMsgKey> keysToDelete; + + mdb_count numHdrs = 0; + if (m_mdbAllMsgHeadersTable) + m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs); + else + return NS_ERROR_NULL_POINTER; + + while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) + { + bool purgeHdr = false; + rv = hdrs->GetNext((nsISupports**)&pHeader); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + if (NS_FAILED(rv)) + break; + + if (!applyToFlaggedMessages) + { + uint32_t flags; + (void)pHeader->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Marked) + continue; + } + + // this isn't quite right - we want to prefer unread messages (keep all of those we can) + if (numHdrs > numHeadersToKeep) + purgeHdr = true; + + if (purgeHdr) + { + nsMsgKey msgKey; + pHeader->GetMessageKey(&msgKey); + keysToDelete.AppendElement(msgKey); + numHdrs--; + if (hdrsToDelete) + hdrsToDelete->AppendElement(pHeader, false); + } + NS_RELEASE(pHeader); + } + + if (!hdrsToDelete) + { + int32_t numKeysToDelete = keysToDelete.Length(); + if (numKeysToDelete > 0) + { + DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr); + if (numKeysToDelete > 10) // compress commit if we deleted more than 10 + Commit(nsMsgDBCommitType::kCompressCommit); + else + Commit(nsMsgDBCommitType::kLargeCommit); + } + } + return rv; +} + +NS_IMPL_ISUPPORTS(nsMsgRetentionSettings, nsIMsgRetentionSettings) + +// Initialise the member variables to resonable defaults. +nsMsgRetentionSettings::nsMsgRetentionSettings() +: m_retainByPreference(1), + m_daysToKeepHdrs(0), + m_numHeadersToKeep(0), + m_useServerDefaults(true), + m_cleanupBodiesByDays(false), + m_daysToKeepBodies(0), + m_applyToFlaggedMessages(false) +{ +} + +nsMsgRetentionSettings::~nsMsgRetentionSettings() +{ +} + +/* attribute unsigned long retainByPreference */ + +NS_IMETHODIMP nsMsgRetentionSettings::GetRetainByPreference(nsMsgRetainByPreference *retainByPreference) +{ + NS_ENSURE_ARG_POINTER(retainByPreference); + *retainByPreference = m_retainByPreference; + return NS_OK; +} + +NS_IMETHODIMP nsMsgRetentionSettings::SetRetainByPreference(nsMsgRetainByPreference retainByPreference) +{ + m_retainByPreference = retainByPreference; + return NS_OK; +} + +/* attribute long daysToKeepHdrs; */ +NS_IMETHODIMP nsMsgRetentionSettings::GetDaysToKeepHdrs(uint32_t *aDaysToKeepHdrs) +{ + NS_ENSURE_ARG_POINTER(aDaysToKeepHdrs); + *aDaysToKeepHdrs = m_daysToKeepHdrs; + return NS_OK; +} + +NS_IMETHODIMP nsMsgRetentionSettings::SetDaysToKeepHdrs(uint32_t aDaysToKeepHdrs) +{ + m_daysToKeepHdrs = aDaysToKeepHdrs; + return NS_OK; +} + +/* attribute long numHeadersToKeep; */ +NS_IMETHODIMP nsMsgRetentionSettings::GetNumHeadersToKeep(uint32_t *aNumHeadersToKeep) +{ + NS_ENSURE_ARG_POINTER(aNumHeadersToKeep); + *aNumHeadersToKeep = m_numHeadersToKeep; + return NS_OK; +} +NS_IMETHODIMP nsMsgRetentionSettings::SetNumHeadersToKeep(uint32_t aNumHeadersToKeep) +{ + m_numHeadersToKeep = aNumHeadersToKeep; + return NS_OK; +} +/* attribute boolean useServerDefaults; */ +NS_IMETHODIMP nsMsgRetentionSettings::GetUseServerDefaults(bool *aUseServerDefaults) +{ + NS_ENSURE_ARG_POINTER(aUseServerDefaults); + *aUseServerDefaults = m_useServerDefaults; + return NS_OK; +} +NS_IMETHODIMP nsMsgRetentionSettings::SetUseServerDefaults(bool aUseServerDefaults) +{ + m_useServerDefaults = aUseServerDefaults; + return NS_OK; +} + +/* attribute boolean cleanupBodiesByDays; */ +NS_IMETHODIMP nsMsgRetentionSettings::GetCleanupBodiesByDays(bool *aCleanupBodiesByDays) +{ + NS_ENSURE_ARG_POINTER(aCleanupBodiesByDays); + *aCleanupBodiesByDays = m_cleanupBodiesByDays; + return NS_OK; +} +NS_IMETHODIMP nsMsgRetentionSettings::SetCleanupBodiesByDays(bool aCleanupBodiesByDays) +{ + m_cleanupBodiesByDays = aCleanupBodiesByDays; + return NS_OK; +} + +/* attribute long daysToKeepBodies; */ +NS_IMETHODIMP nsMsgRetentionSettings::GetDaysToKeepBodies(uint32_t *aDaysToKeepBodies) +{ + NS_ENSURE_ARG_POINTER(aDaysToKeepBodies); + *aDaysToKeepBodies = m_daysToKeepBodies; + return NS_OK; +} +NS_IMETHODIMP nsMsgRetentionSettings::SetDaysToKeepBodies(uint32_t aDaysToKeepBodies) +{ + m_daysToKeepBodies = aDaysToKeepBodies; + return NS_OK; +} + +/* attribute boolean applyToFlaggedMessages; */ +NS_IMETHODIMP nsMsgRetentionSettings::GetApplyToFlaggedMessages(bool *aApplyToFlaggedMessages) +{ + NS_ENSURE_ARG_POINTER(aApplyToFlaggedMessages); + *aApplyToFlaggedMessages = m_applyToFlaggedMessages; + return NS_OK; +} +NS_IMETHODIMP nsMsgRetentionSettings::SetApplyToFlaggedMessages(bool aApplyToFlaggedMessages) +{ + m_applyToFlaggedMessages = aApplyToFlaggedMessages; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsMsgDownloadSettings, nsIMsgDownloadSettings) + +nsMsgDownloadSettings::nsMsgDownloadSettings() +{ + m_useServerDefaults = false; + m_downloadUnreadOnly = false; + m_downloadByDate = false; + m_ageLimitOfMsgsToDownload = 0; +} + +nsMsgDownloadSettings::~nsMsgDownloadSettings() +{ +} + +/* attribute boolean useServerDefaults; */ +NS_IMETHODIMP nsMsgDownloadSettings::GetUseServerDefaults(bool *aUseServerDefaults) +{ + NS_ENSURE_ARG_POINTER(aUseServerDefaults); + *aUseServerDefaults = m_useServerDefaults; + return NS_OK; +} +NS_IMETHODIMP nsMsgDownloadSettings::SetUseServerDefaults(bool aUseServerDefaults) +{ + m_useServerDefaults = aUseServerDefaults; + return NS_OK; +} + + +/* attribute boolean downloadUnreadOnly; */ +NS_IMETHODIMP nsMsgDownloadSettings::GetDownloadUnreadOnly(bool *aDownloadUnreadOnly) +{ + NS_ENSURE_ARG_POINTER(aDownloadUnreadOnly); + *aDownloadUnreadOnly = m_downloadUnreadOnly; + return NS_OK; +} +NS_IMETHODIMP nsMsgDownloadSettings::SetDownloadUnreadOnly(bool aDownloadUnreadOnly) +{ + m_downloadUnreadOnly = aDownloadUnreadOnly; + return NS_OK; +} + +/* attribute boolean downloadByDate; */ +NS_IMETHODIMP nsMsgDownloadSettings::GetDownloadByDate(bool *aDownloadByDate) +{ + NS_ENSURE_ARG_POINTER(aDownloadByDate); + *aDownloadByDate = m_downloadByDate; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDownloadSettings::SetDownloadByDate(bool aDownloadByDate) +{ + m_downloadByDate = aDownloadByDate; + return NS_OK; +} + + +/* attribute long ageLimitOfMsgsToDownload; */ +NS_IMETHODIMP nsMsgDownloadSettings::GetAgeLimitOfMsgsToDownload(uint32_t *ageLimitOfMsgsToDownload) +{ + NS_ENSURE_ARG_POINTER(ageLimitOfMsgsToDownload); + *ageLimitOfMsgsToDownload = m_ageLimitOfMsgsToDownload; + return NS_OK; +} +NS_IMETHODIMP nsMsgDownloadSettings::SetAgeLimitOfMsgsToDownload(uint32_t ageLimitOfMsgsToDownload) +{ + m_ageLimitOfMsgsToDownload = ageLimitOfMsgsToDownload; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetDefaultViewFlags(nsMsgViewFlagsTypeValue *aDefaultViewFlags) +{ + NS_ENSURE_ARG_POINTER(aDefaultViewFlags); + GetIntPref("mailnews.default_view_flags", aDefaultViewFlags); + if (*aDefaultViewFlags < nsMsgViewFlagsType::kNone || + *aDefaultViewFlags > (nsMsgViewFlagsType::kThreadedDisplay | + nsMsgViewFlagsType::kShowIgnored | + nsMsgViewFlagsType::kUnreadOnly | + nsMsgViewFlagsType::kExpandAll | + nsMsgViewFlagsType::kGroupBySort)) + *aDefaultViewFlags = nsMsgViewFlagsType::kNone; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetDefaultSortType(nsMsgViewSortTypeValue *aDefaultSortType) +{ + NS_ENSURE_ARG_POINTER(aDefaultSortType); + GetIntPref("mailnews.default_sort_type", aDefaultSortType); + if (*aDefaultSortType < nsMsgViewSortType::byDate || + *aDefaultSortType > nsMsgViewSortType::byAccount) + *aDefaultSortType = nsMsgViewSortType::byDate; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::GetDefaultSortOrder(nsMsgViewSortOrderValue *aDefaultSortOrder) +{ + NS_ENSURE_ARG_POINTER(aDefaultSortOrder); + GetIntPref("mailnews.default_sort_order", aDefaultSortOrder); + if (*aDefaultSortOrder != nsMsgViewSortOrder::descending) + *aDefaultSortOrder = nsMsgViewSortOrder::ascending; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::ResetHdrCacheSize(uint32_t aSize) +{ + if (m_cacheSize > aSize) + { + m_cacheSize = aSize; + ClearHdrCache(false); + } + return NS_OK; +} + +/** + void getNewList(out unsigned long count, [array, size_is(count)] out long newKeys); + */ +NS_IMETHODIMP +nsMsgDatabase::GetNewList(uint32_t *aCount, nsMsgKey **aNewKeys) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aNewKeys); + + *aCount = m_newSet.Length(); + if (*aCount > 0) + { + *aNewKeys = static_cast<nsMsgKey *>(moz_xmalloc(*aCount * sizeof(nsMsgKey))); + if (!*aNewKeys) + return NS_ERROR_OUT_OF_MEMORY; + memcpy(*aNewKeys, m_newSet.Elements(), *aCount * sizeof(nsMsgKey)); + return NS_OK; + } + // if there were no new messages, signal this by returning a null pointer + // + *aNewKeys = nullptr; + return NS_OK; +} + +nsresult nsMsgDatabase::GetSearchResultsTable(const char *searchFolderUri, bool createIfMissing, nsIMdbTable **table) +{ + mdb_kind kindToken; + mdb_count numTables; + mdb_bool mustBeUnique; + NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER); + + nsresult err = m_mdbStore->StringToToken(GetEnv(), searchFolderUri, &kindToken); + err = m_mdbStore->GetTableKind(GetEnv(), m_hdrRowScopeToken, kindToken, + &numTables, &mustBeUnique, table); + if ((!*table || NS_FAILED(err)) && createIfMissing) + err = m_mdbStore->NewTable(GetEnv(), m_hdrRowScopeToken, kindToken, true, nullptr, table); + + return *table ? err : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMsgDatabase::GetCachedHits(const char *aSearchFolderUri, nsISimpleEnumerator **aEnumerator) +{ + nsCOMPtr <nsIMdbTable> table; + (void) GetSearchResultsTable(aSearchFolderUri, false, getter_AddRefs(table)); + if (!table) + return NS_ERROR_FAILURE; // expected result for no cached hits + nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, table, nullptr, nullptr); + if (e == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aEnumerator = e); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDatabase::RefreshCache(const char *aSearchFolderUri, uint32_t aNumKeys, nsMsgKey *aNewHits, uint32_t *aNumBadHits, nsMsgKey **aStaleHits) +{ + nsCOMPtr <nsIMdbTable> table; + nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(err, err); + // update the table so that it just contains aNewHits. + // And, keep track of the headers in the original table but not in aNewHits, so we + // can put those in aStaleHits. + // both aNewHits and the db table are sorted by uid/key. + // So, start at the beginning of the table and the aNewHits array. + uint32_t newHitIndex = 0; + uint32_t tableRowIndex = 0; + + uint32_t rowCount; + table->GetCount(GetEnv(), &rowCount); + nsTArray<nsMsgKey> staleHits; + // should assert that each array is sorted + while (newHitIndex < aNumKeys || tableRowIndex < rowCount) + { + mdbOid oid; + nsMsgKey tableRowKey = nsMsgKey_None; + if (tableRowIndex < rowCount) + { + nsresult ret = table->PosToOid (GetEnv(), tableRowIndex, &oid); + if (NS_FAILED(ret)) + { + tableRowIndex++; + continue; + } + tableRowKey = oid.mOid_Id; // ### TODO need the real key for the 0th key problem. + } + + if (newHitIndex < aNumKeys && aNewHits[newHitIndex] == tableRowKey) + { + newHitIndex++; + tableRowIndex++; + continue; + } + else if (tableRowIndex >= rowCount || (newHitIndex < aNumKeys && aNewHits[newHitIndex] < tableRowKey)) + { + nsCOMPtr <nsIMdbRow> hdrRow; + mdbOid rowObjectId; + + rowObjectId.mOid_Id = aNewHits[newHitIndex]; + rowObjectId.mOid_Scope = m_hdrRowScopeToken; + err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, getter_AddRefs(hdrRow)); + if (hdrRow) + { + table->AddRow(GetEnv(), hdrRow); + mdb_pos newPos; + table->MoveRow(GetEnv(), hdrRow, rowCount, tableRowIndex, &newPos); + rowCount++; + tableRowIndex++; + } + newHitIndex++; + continue; + } + else if (newHitIndex >= aNumKeys || aNewHits[newHitIndex] > tableRowKey) + { + staleHits.AppendElement(tableRowKey); + table->CutOid(GetEnv(), &oid); + rowCount--; + continue; // don't increment tableRowIndex since we removed that row. + } + } + *aNumBadHits = staleHits.Length(); + if (*aNumBadHits) + { + *aStaleHits = static_cast<nsMsgKey *>(moz_xmalloc(*aNumBadHits * sizeof(nsMsgKey))); + if (!*aStaleHits) + return NS_ERROR_OUT_OF_MEMORY; + memcpy(*aStaleHits, staleHits.Elements(), *aNumBadHits * sizeof(nsMsgKey)); + } + else + *aStaleHits = nullptr; + +#ifdef DEBUG_David_Bienvenu + printf("after refreshing cache\n"); + // iterate over table and assert that it's in id order + table->GetCount(GetEnv(), &rowCount); + mdbOid oid; + tableRowIndex = 0; + mdb_id prevId = 0; + while (tableRowIndex < rowCount) + { + nsresult ret = table->PosToOid (m_mdbEnv, tableRowIndex++, &oid); + if (tableRowIndex > 1 && oid.mOid_Id <= prevId) + { + NS_ASSERTION(false, "inserting row into cached hits table, not sorted correctly"); + printf("key %lx is before or equal %lx\n", prevId, oid.mOid_Id); + } + prevId = oid.mOid_Id; + } + +#endif + Commit(nsMsgDBCommitType::kLargeCommit); + return NS_OK; +} + +// search sorted table +mdb_pos nsMsgDatabase::FindInsertIndexInSortedTable(nsIMdbTable *table, mdb_id idToInsert) +{ + mdb_pos searchPos = 0; + uint32_t rowCount; + table->GetCount(GetEnv(), &rowCount); + mdb_pos hi = rowCount; + mdb_pos lo = 0; + + while (hi > lo) + { + mdbOid outOid; + searchPos = (lo + hi - 1) / 2; + table->PosToOid(GetEnv(), searchPos, &outOid); + if (outOid.mOid_Id == idToInsert) + { + NS_ASSERTION(false, "id shouldn't be in table"); + return hi; + } + if (outOid.mOid_Id > idToInsert) + hi = searchPos; + else // if (outOid.mOid_Id < idToInsert) + lo = searchPos + 1; + } + return hi; +} +NS_IMETHODIMP +nsMsgDatabase::UpdateHdrInCache(const char *aSearchFolderUri, nsIMsgDBHdr *aHdr, bool aAdd) +{ + nsCOMPtr <nsIMdbTable> table; + nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(err, err); + nsMsgKey key; + aHdr->GetMessageKey(&key); + nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(aHdr); // closed system, so this is ok + if (NS_SUCCEEDED(err) && m_mdbStore && msgHdr->m_mdbRow) + { + if (!aAdd) + { + table->CutRow(m_mdbEnv, msgHdr->m_mdbRow); + } + else + { + mdbOid rowId; + msgHdr->m_mdbRow->GetOid(m_mdbEnv, &rowId); + mdb_pos insertPos = FindInsertIndexInSortedTable(table, rowId.mOid_Id); + uint32_t rowCount; + table->GetCount(m_mdbEnv, &rowCount); + table->AddRow(m_mdbEnv, msgHdr->m_mdbRow); + mdb_pos newPos; + table->MoveRow(m_mdbEnv, msgHdr->m_mdbRow, rowCount, insertPos, &newPos); + } + } + +// if (aAdd) + // if we need to add this hdr, we need to insert it in key order. + return NS_OK; +} +NS_IMETHODIMP +nsMsgDatabase::HdrIsInCache(const char* aSearchFolderUri, nsIMsgDBHdr *aHdr, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + nsCOMPtr <nsIMdbTable> table; + nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table)); + NS_ENSURE_SUCCESS(err, err); + nsMsgKey key; + aHdr->GetMessageKey(&key); + mdbOid rowObjectId; + rowObjectId.mOid_Id = key; + rowObjectId.mOid_Scope = m_hdrRowScopeToken; + mdb_bool hasOid; + err = table->HasOid(GetEnv(), &rowObjectId, &hasOid); + *aResult = hasOid; + return err; +} + |