/* -*- 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 "nsIFile.h"
#include "nsMailDatabase.h"
#include "nsDBFolderInfo.h"
#include "nsIMsgNewsFolder.h"
#include "nsMsgThread.h"
#include "nsIMsgSearchTerm.h"
#include "nsIMdbFactoryFactory.h"
#include "mozilla/Logging.h"
#include "mozilla/Telemetry.h"
#include "prprf.h"
#include "nsMsgFolderFlags.h"
#include "nsIMsgAccountManager.h"
#include "nsIMsgDBView.h"
#include "nsIMsgFolderCache.h"
#include "nsIMsgFolderCacheElement.h"
#include "MailNewsTypes2.h"
#include "nsMsgUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsMemory.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsPrintfCString.h"
#include "nsMsgDatabaseEnumerators.h"
#include "nsIMemoryReporter.h"
#include "nsIWeakReferenceUtils.h"
#include "nsMailDirServiceDefs.h"
#include "mozilla/Components.h"
#include "mozilla/mailnews/MimeHeaderParser.h"
#include "mozilla/intl/LocaleService.h"

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;

LazyLogModule DBLog("MsgDB");

PRTime nsMsgDatabase::gLastUseTime;

/**
 * mozilla::intl APIs require sizeable buffers. This class abstracts over
 * the nsTArray.
 */
class nsTArrayU8Buffer {
 public:
  using CharType = uint8_t;

  // Do not allow copy or move. Move could be added in the future if needed.
  nsTArrayU8Buffer(const nsTArrayU8Buffer&) = delete;
  nsTArrayU8Buffer& operator=(const nsTArrayU8Buffer&) = delete;

  explicit nsTArrayU8Buffer(nsTArray<CharType>& aArray) : mArray(aArray) {}

  /**
   * Ensures the buffer has enough space to accommodate |size| elements.
   */
  [[nodiscard]] bool reserve(size_t size) {
    mArray.SetCapacity(size);
    // nsTArray::SetCapacity returns void, return true to keep the API the same
    // as the other Buffer implementations.
    return true;
  }

  /**
   * Returns the raw data inside the buffer.
   */
  CharType* data() { return mArray.Elements(); }

  /**
   * Returns the count of elements written into the buffer.
   */
  size_t length() const { return mArray.Length(); }

  /**
   * Returns the buffer's overall capacity.
   */
  size_t capacity() const { return mArray.Capacity(); }

  /**
   * Resizes the buffer to the given amount of written elements.
   */
  void written(size_t amount) {
    MOZ_ASSERT(amount <= mArray.Capacity());
    // This sets |mArray|'s internal size so that it matches how much was
    // written. This is necessary because the write happens across FFI
    // boundaries.
    mArray.SetLengthAndRetainStorage(amount);
  }

 private:
  nsTArray<CharType>& mArray;
};

NS_IMPL_ISUPPORTS(nsMsgDBService, nsIMsgDBService)

nsMsgDBService::nsMsgDBService() {}

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");
#  ifndef MOZILLA_OFFICIAL
  // Only print this on local builds since it causes crashes,
  // see bug 1468691, bug 1377692 and bug 1342858.
  for (uint32_t i = 0; i < m_dbCache.Length(); i++) {
    nsMsgDatabase* pMessageDB = m_dbCache.ElementAt(i);
    if (pMessageDB)
      printf("db left open %s\n",
             pMessageDB->m_dbFile->HumanReadablePath().get());
  }
#  endif
#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("@mozilla.org/nsMsgDatabase/msgDB-");
  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;
}

/**
 * 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_NOT_FOUND) 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("@mozilla.org/nsMsgDatabase/msgDB-");
  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(
    nsTArray<RefPtr<nsIMsgDatabase>>& aOpenDBs) {
  aOpenDBs.Clear();
  aOpenDBs.SetCapacity(m_dbCache.Length());
  for (auto db : m_dbCache) {
    aOpenDBs.AppendElement(db);
  }
  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;
  bool exists;
  NS_ENSURE_TRUE(m_dbFile, NS_ERROR_NULL_POINTER);
  rv = m_dbFile->Exists(&exists);
  if (NS_SUCCEEDED(rv)) {
    if (exists)
      rv = m_dbFile->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;
}

// Invalidate any outstanding message enumerators using this db.
void nsMsgDatabase::InvalidateEnumerators() {
  RefPtr<nsMsgDatabase> kungFuDeathGrip(this);
  // Work in reverse, as the enumerators remove themselves from the list.
  {
    auto n = m_msgEnumerators.Length();
    for (auto i = n; i > 0; --i) {
      m_msgEnumerators[i - 1]->Invalidate();
    }
  }
  // And again for thread enumerators.
  {
    auto n = m_threadEnumerators.Length();
    for (auto i = n; i > 0; --i) {
      m_threadEnumerators[i - 1]->Invalidate();
    }
  }
}

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(),
           m_dbFile->HumanReadablePath().get());
  }
#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) {
  new (KnownNotNull, aTo)
      MsgHdrHashElement(std::move(*((MsgHdrHashElement*)aFrom)));
}

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* dbFile)  // returns true if they match
{
  NS_ENSURE_TRUE(m_dbFile, false);
  return dbFile->NativePath().Equals(m_dbFile->NativePath());
}

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, ("%zu open DBs", m_dbCache.Length()));
  for (uint32_t i = 0; i < m_dbCache.Length(); i++) {
    db = m_dbCache.ElementAt(i);
    MOZ_LOG(DBLog, LogLevel::Info,
            ("%s - %" PRIu32 " hdrs in use",
             db->m_dbFile->HumanReadablePath().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 {
  nsWeakPtr mDatabase;

 public:
  explicit MsgDBReporter(nsMsgDatabase* db)
      : mDatabase(do_GetWeakReference(db)) {}

  NS_DECL_ISUPPORTS
  NS_IMETHOD GetName(nsACString& aName) {
    aName.AssignLiteral("msg-database-objects");
    return NS_OK;
  }

  NS_IMETHOD CollectReports(nsIHandleReportCallback* aCb, nsISupports* aClosure,
                            bool aAnonymize) override {
    nsCString path;
    GetPath(path, aAnonymize);
    nsCOMPtr<nsIMsgDatabase> database = do_QueryReferent(mDatabase);
    nsMsgDatabase* db =
        database ? static_cast<nsMsgDatabase*>(database.get()) : nullptr;
    return aCb->Callback(EmptyCString(), path, nsIMemoryReporter::KIND_HEAP,
                         nsIMemoryReporter::UNITS_BYTES,
                         db ? db->SizeOfIncludingThis(GetMallocSize) : 0,
                         "Memory used for the folder database."_ns, aClosure);
  }

  void GetPath(nsACString& memoryPath, bool aAnonymize) {
    memoryPath.AssignLiteral("explicit/maildb/database(");
    nsCOMPtr<nsIMsgDatabase> database = do_QueryReferent(mDatabase);
    nsCOMPtr<nsIMsgFolder> folder;
    if (database) database->GetFolder(getter_AddRefs(folder));
    if (folder) {
      if (aAnonymize)
        memoryPath.AppendLiteral("<anonymized>");
      else {
        nsAutoCString folderURL;
        folder->GetFolderURL(folderURL);
        folderURL.ReplaceChar('/', '\\');
        memoryPath += folderURL;
      }
    } else {
      memoryPath.AppendLiteral("UNKNOWN-FOLDER");
    }
    memoryPath.Append(')');
  }

 private:
  ~MsgDBReporter() {}
};

NS_IMPL_ISUPPORTS(MsgDBReporter, nsIMemoryReporter)
}  // namespace mailnews
}  // namespace mozilla

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_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_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);
  mMemReporter = nullptr;
  //  Close(FALSE);  // better have already been closed.
  ClearCachedObjects(true);
  InvalidateEnumerators();
  delete m_cachedHeaders;
  delete m_headersInUse;

  if (m_msgReferences) {
    delete m_msgReferences;
    m_msgReferences = nullptr;
  }

  MOZ_LOG(DBLog, LogLevel::Info,
          ("closing database    %s", m_dbFile->HumanReadablePath().get()));

  nsCOMPtr<nsIMsgDBService> serv(
      do_GetService("@mozilla.org/msgDatabase/msgDBService;1"));
  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();
  m_dbFolderInfo = nullptr;

  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, nsIMsgOfflineOpsDatabase,
                  nsIDBChangeAnnouncer)

nsresult nsMsgDatabase::GetMDBFactory(nsIMdbFactory** aMdbFactory) {
  if (!mMdbFactory) {
    nsresult rv;
    nsCOMPtr<nsIMdbFactoryService> mdbFactoryService =
        do_GetService("@mozilla.org/db/mork;1", &rv);
    if (NS_SUCCEEDED(rv) && mdbFactoryService) {
      rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
      NS_ENSURE_SUCCESS(rv, rv);
      if (!mMdbFactory) return NS_ERROR_FAILURE;
    }
  }
  NS_ADDREF(*aMdbFactory = mMdbFactory);
  return NS_OK;
}

// 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) {
  MOZ_LOG(DBLog, LogLevel::Info,
          ("nsMsgDatabase::Open(%s, %s, %p, %s)",
           summaryFile->HumanReadablePath().get(), aCreate ? "TRUE" : "FALSE",
           this, aLeaveInvalidDB ? "TRUE" : "FALSE"));

  nsresult rv = OpenMDB(summaryFile, aCreate, sync);
  if (NS_FAILED(rv))
    MOZ_LOG(DBLog, LogLevel::Info,
            ("error opening db %" PRIx32, static_cast<uint32_t>(rv)));

  if (MOZ_LOG_TEST(DBLog, LogLevel::Debug)) aDBService->DumpCache();

  if (rv == NS_ERROR_FILE_NOT_FOUND) 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 = 0;
  summaryFile->Exists(&exists);
  if (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 if (err != NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) {
    // No point declaring it out-of-date and trying to delete it
    // if it's missing.
    // We get here with NS_ERROR_FAILURE when Mork can't open the
    // file due to too many open files. In this case there is no
    // point to blow away the MSF file.
    err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
    if (!m_leaveInvalidDB) deleteInvalidDB = true;
  }

  if (deleteInvalidDB) {
    // this will make the db folder info release its ref to the mail db...
    m_dbFolderInfo = nullptr;
    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, upgrade 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(nsIFile* dbFile, bool create, bool sync) {
  nsCOMPtr<nsIMdbFactory> mdbFactory;
  nsresult ret = GetMDBFactory(getter_AddRefs(mdbFactory));
  NS_ENSURE_SUCCESS(ret, ret);

  ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv);
  if (NS_SUCCEEDED(ret)) {
    nsIMdbHeap* dbHeap = nullptr;

    if (m_mdbEnv) m_mdbEnv->SetAutoClear(true);
    PathString dbName = dbFile->NativePath();
    ret = dbFile->Clone(getter_AddRefs(m_dbFile));
    NS_ENSURE_SUCCESS(ret, ret);
    bool exists = false;
    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.get(),
          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.get(), &newFile);
      if (NS_FAILED(ret)) ret = NS_ERROR_FILE_NOT_FOUND;
      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
      }
    }
  }

  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.
  NS_ADDREF_THIS();
  NotifyAnnouncerGoingAway();
  // make sure dbFolderInfo isn't holding onto mork stuff because mork db is
  // going away
  if (m_dbFolderInfo) m_dbFolderInfo->ReleaseExternalReferences();
  m_dbFolderInfo = nullptr;

  err = CloseMDB(true);  // Backup DB will try to recover info, so commit
  ClearCachedObjects(true);
  InvalidateEnumerators();
  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;
  }

  // There'd better not be any listeners, because we're going away.
  NS_ASSERTION(m_ChangeListeners.IsEmpty(),
               "shouldn't have any listeners left");
  m_ChangeListeners.Clear();

  NS_RELEASE_THIS();
  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("@mozilla.org/messenger/account-manager;1", &rv);
  if (NS_SUCCEEDED(rv) && accountManager) {
    nsCOMPtr<nsIMsgFolderCache> folderCache;

    rv = accountManager->GetFolderCache(getter_AddRefs(folderCache));
    if (NS_SUCCEEDED(rv) && folderCache) {
      nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
      nsCString persistentPath;
      NS_ENSURE_TRUE(m_dbFile, NS_ERROR_NULL_POINTER);
      rv = m_dbFile->GetPersistentDescriptor(persistentPath);
      NS_ENSURE_SUCCESS(rv, err);
      rv = folderCache->GetCacheElement(persistentPath, 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->SetCachedInt32("totalMsgs", totalMessages);
        cacheElement->SetCachedInt32("totalUnreadMsgs", unreadMessages);
        cacheElement->SetCachedInt32("pendingMsgs", pendingMessages);
        cacheElement->SetCachedInt32("pendingUnreadMsgs",
                                     pendingUnreadMessages);
      }
    }
  }

  return err;
}

NS_IMETHODIMP nsMsgDatabase::Close(bool forceCommit /* = TRUE */) {
  InvalidateEnumerators();
  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* 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) {
      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) {
        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<nsIMsgEnumerator> enumerator;
      err = EnumerateMessages(getter_AddRefs(enumerator));
      if (NS_SUCCEEDED(err) && enumerator) {
        bool hasMore;

        while (NS_SUCCEEDED(err = enumerator->HasMoreElements(&hasMore)) &&
               hasMore) {
          nsCOMPtr<nsIMsgDBHdr> msgHdr;
          err = enumerator->GetNext(getter_AddRefs(msgHdr));
          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(), 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) {
  *pmsgHdr = nullptr;
  NS_ENSURE_ARG_POINTER(pmsgHdr);
  NS_ENSURE_STATE(m_folder);
  NS_ENSURE_STATE(m_mdbAllMsgHeadersTable);
  NS_ENSURE_STATE(m_mdbStore);

  // 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;

  nsresult rv = GetHdrFromUseCache(key, pmsgHdr);
  if (NS_SUCCEEDED(rv) && *pmsgHdr) return rv;

  mdbOid rowObjectId;
  rowObjectId.mOid_Id = key;
  rowObjectId.mOid_Scope = m_hdrRowScopeToken;
  mdb_bool hasOid;
  rv = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
  if (NS_SUCCEEDED(rv) /* && hasOid */) {
    nsIMdbRow* hdrRow;
    rv = m_mdbStore->GetRow(GetEnv(), &rowObjectId, &hdrRow);
    if (NS_SUCCEEDED(rv)) {
      if (!hdrRow) {
        rv = NS_ERROR_NULL_POINTER;
      } else {
        rv = CreateMsgHdr(hdrRow, key, pmsgHdr);
      }
    }
  }
  return rv;
}

NS_IMETHODIMP nsMsgDatabase::DeleteMessage(nsMsgKey key,
                                           nsIDBChangeListener* instigator,
                                           bool commit) {
  nsCOMPtr<nsIMsgDBHdr> msgHdr;
  GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
  if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;

  return DeleteHeader(msgHdr, instigator, commit, true);
}

NS_IMETHODIMP nsMsgDatabase::DeleteMessages(nsTArray<nsMsgKey> const& nsMsgKeys,
                                            nsIDBChangeListener* instigator) {
  nsresult err = NS_OK;

  uint32_t kindex;
  for (kindex = 0; kindex < nsMsgKeys.Length(); kindex++) {
    nsMsgKey key = nsMsgKeys[kindex];
    nsCOMPtr<nsIMsgDBHdr> msgHdr;

    bool hasKey;

    if (NS_SUCCEEDED(ContainsKey(key, &hasKey)) && hasKey) {
      GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
      if (!msgHdr) {
        err = NS_MSG_MESSAGE_NOT_FOUND;
        break;
      }
      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) {
    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) {
    // Force deleted flag, so SetHdrFlag won't bail out because deleted flag
    // isn't set.
    uint32_t result;
    aMsgHdr->OrFlags(nsMsgMessageFlags::Expunged, &result);
    SetHdrFlag(aMsgHdr, 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) {
    ret = thread->RemoveChildHdr(msgHdr, this);
  }
  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 notifications 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->ClearCachedValues();
  return ret;
}

nsresult nsMsgDatabase::IsRead(nsMsgKey key, bool* pRead) {
  nsCOMPtr<nsIMsgDBHdr> msgHdr;
  GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
  if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;

  return IsHeaderRead(msgHdr, pRead);
}

uint32_t nsMsgDatabase::GetStatusFlags(nsIMsgDBHdr* msgHdr,
                                       nsMsgMessageFlagType 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;

  GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
  if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;

  uint32_t flags;
  (void)msgHdr->GetFlags(&flags);
  *pMarked = !!(flags & nsMsgMessageFlags::Marked);
  return NS_OK;
}

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;

  GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
  if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;

  uint32_t flags;
  (void)msgHdr->GetFlags(&flags);
  *pHasThem = !!(flags & nsMsgMessageFlags::Attachment);
  return NS_OK;
}

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) {
  nsCOMPtr<nsIMsgDBHdr> msgHdr;

  GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
  if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;

  return MarkHdrRead(msgHdr, bRead, instigator);
}

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::MarkRedirected(
    nsMsgKey key, bool bRedirected,
    nsIDBChangeListener* instigator /* = NULL */) {
  return SetKeyFlag(key, bRedirected, nsMsgMessageFlags::Redirected,
                    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,
                              nsTArray<nsMsgKey>& aThoseMarkedRead) {
  NS_ENSURE_ARG_POINTER(thread);
  aThoseMarkedRead.ClearAndRetainStorage();
  nsresult rv = NS_OK;

  uint32_t numChildren;
  thread->GetNumChildren(&numChildren);
  aThoseMarkedRead.SetCapacity(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)))
          aThoseMarkedRead.AppendElement(key);
        MarkHdrRead(child, true, instigator);
      }
    }
  }
  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;
  GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
  NS_ENSURE_TRUE(msg, NS_MSG_MESSAGE_NOT_FOUND);

  // We'll add the message flags to the thread flags when notifying, since
  // notifications are supposed to be about messages, not threads.
  uint32_t msgFlags;
  msg->GetFlags(&msgFlags);

  return NotifyHdrChangeAll(msg, oldThreadFlags | msgFlags,
                            threadFlags | msgFlags, 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;
  thread->SetFlags(threadFlags);

  nsCOMPtr<nsIMsgDBHdr> msg;
  GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
  if (!msg) return NS_MSG_MESSAGE_NOT_FOUND;

  // We'll add the message flags to the thread flags when notifying, since
  // notifications are supposed to be about messages, not threads.
  uint32_t msgFlags;
  msg->GetFlags(&msgFlags);

  return NotifyHdrChangeAll(msg, oldThreadFlags | msgFlags,
                            threadFlags | msgFlags, instigator);
}

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 nsACString& aValue) {
  nsCOMPtr<nsIMsgDBHdr> msgHdr;
  GetMsgHdrForKey(aKey, getter_AddRefs(msgHdr));
  if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;
  return SetStringPropertyByHdr(msgHdr, aProperty, aValue);
}

NS_IMETHODIMP nsMsgDatabase::SetStringPropertyByHdr(nsIMsgDBHdr* msgHdr,
                                                    const char* aProperty,
                                                    const nsACString& 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, &notify);

  nsCString oldValue;
  nsresult rv = msgHdr->GetStringProperty(aProperty, 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());
  nsCOMPtr<nsIDBChangeListener> listener;
  if (notify) {
    nsTObserverArray<nsCOMPtr<nsIDBChangeListener>>::ForwardIterator listeners(
        m_ChangeListeners);
    while (listeners.HasMore()) {
      listener = listeners.GetNext();
      // initialize |status| because some implementations of
      // OnHdrPropertyChanged does not set the value.
      uint32_t status = 0;
      (void)listener->OnHdrPropertyChanged(msgHdr, nsCString(aProperty), 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() && aValue.Equals("0")))
      NotifyJunkScoreChanged(nullptr);

    nsTObserverArray<nsCOMPtr<nsIDBChangeListener>>::ForwardIterator listeners(
        m_ChangeListeners);
    for (uint32_t i = 0; listeners.HasMore() && i < statusArray.Length(); i++) {
      listener = listeners.GetNext();
      uint32_t status = statusArray[i];
      (void)listener->OnHdrPropertyChanged(msgHdr, nsCString(aProperty), 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, &notify);

  // Precall OnHdrPropertyChanged to store prechange status.
  nsTArray<uint32_t> statusArray(m_ChangeListeners.Length());
  nsCOMPtr<nsIDBChangeListener> listener;
  if (notify) {
    nsTObserverArray<nsCOMPtr<nsIDBChangeListener>>::ForwardIterator listeners(
        m_ChangeListeners);
    while (listeners.HasMore()) {
      listener = listeners.GetNext();
      // initialize |status| because some implementations of
      // OnHdrPropertyChanged does not set the value.
      uint32_t status = 0;
      (void)listener->OnHdrPropertyChanged(aMsgHdr, nsCString(aProperty), 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();
      uint32_t status = statusArray[i];
      (void)listener->OnHdrPropertyChanged(aMsgHdr, nsCString(aProperty), false,
                                           &status, nullptr);
      // Ignore errors.
    }
  }

  return NS_OK;
}

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);
}

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;

  GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
  if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;

  uint32_t flags;
  (void)msgHdr->GetFlags(&flags);
  *pSent = !!(flags & nsMsgMessageFlags::MDNReportSent);
  return NS_OK;
}

nsresult nsMsgDatabase::SetKeyFlag(nsMsgKey key, bool set,
                                   nsMsgMessageFlagType flag,
                                   nsIDBChangeListener* instigator) {
  nsCOMPtr<nsIMsgDBHdr> msgHdr;

  GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
  if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;

  return SetMsgHdrFlag(msgHdr, set, flag, instigator);
}

nsresult nsMsgDatabase::SetMsgHdrFlag(nsIMsgDBHdr* msgHdr, bool set,
                                      nsMsgMessageFlagType flag,
                                      nsIDBChangeListener* instigator) {
  uint32_t oldFlags;
  (void)msgHdr->GetFlags(&oldFlags);

  if (!SetHdrFlag(msgHdr, set, flag)) return NS_OK;

  uint32_t flags;
  (void)msgHdr->GetFlags(&flags);

  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);
    }

#ifndef MOZ_SUITE
    if (bRead) {
      Telemetry::ScalarAdd(Telemetry::ScalarID::TB_MAILS_READ, 1);
    }
#endif

    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(nsTArray<nsMsgKey>& aThoseMarked) {
  aThoseMarked.ClearAndRetainStorage();

  nsCOMPtr<nsIMsgEnumerator> hdrs;
  nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
  NS_ENSURE_SUCCESS(rv, rv);
  bool hasMore = false;

  while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
    nsCOMPtr<nsIMsgDBHdr> msg;
    rv = hdrs->GetNext(getter_AddRefs(msg));
    if (NS_FAILED(rv)) break;

    bool isRead;
    IsHeaderRead(msg, &isRead);

    if (!isRead) {
      nsMsgKey key;
      (void)msg->GetMessageKey(&key);
      aThoseMarked.AppendElement(key);
      rv = MarkHdrRead(msg, true, nullptr);  // ### dmb - blow off error?
    }
  }

  // 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 */) {
  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;
      GetMsgHdrForKey(lastNewKey, getter_AddRefs(msgHdr));
      if (msgHdr) {
        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 NS_OK;
}

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;
}

NS_IMETHODIMP
nsMsgDatabase::EnumerateMessages(nsIMsgEnumerator** result) {
  RememberLastUseTime();
  NS_ENSURE_ARG_POINTER(result);
  NS_ADDREF(*result = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
                                            nullptr, nullptr));
  return NS_OK;
}

NS_IMETHODIMP
nsMsgDatabase::ReverseEnumerateMessages(nsIMsgEnumerator** result) {
  NS_ENSURE_ARG_POINTER(result);
  NS_ADDREF(*result = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
                                            nullptr, nullptr, false));
  return NS_OK;
}

NS_IMETHODIMP
nsMsgDatabase::GetFilterEnumerator(
    const nsTArray<RefPtr<nsIMsgSearchTerm>>& searchTerms, bool aReverse,
    nsIMsgEnumerator** aResult) {
  NS_ENSURE_ARG_POINTER(aResult);
  RefPtr<nsMsgFilteredDBEnumerator> e =
      new nsMsgFilteredDBEnumerator(this, m_mdbAllMsgHeadersTable, aReverse);

  NS_ENSURE_TRUE(e, NS_ERROR_OUT_OF_MEMORY);
  nsresult rv = e->InitSearchSession(searchTerms, m_folder);
  NS_ENSURE_SUCCESS(rv, rv);

  e.forget(aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsMsgDatabase::SyncCounts() {
  nsCOMPtr<nsIMsgEnumerator> 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<nsIMsgDBHdr> header;
    rv = hdrs->GetNext(getter_AddRefs(header));
    NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
    if (NS_FAILED(rv)) break;

    bool isRead;
    IsHeaderRead(header, &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(nsTArray<nsMsgKey>& keys) {
  nsresult rv = NS_OK;
  nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
  RememberLastUseTime();
  keys.Clear();

  if (m_mdbAllMsgHeadersTable) {
    uint32_t numMsgs = 0;
    m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numMsgs);
    keys.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)) keys.AppendElement(outOid.mOid_Id);
    }
  }
  return rv;
}

NS_IMETHODIMP
nsMsgDatabase::EnumerateThreads(nsIMsgThreadEnumerator** result) {
  RememberLastUseTime();
  NS_ADDREF(*result = new nsMsgDBThreadEnumerator(this, nullptr));
  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(nsIMsgEnumerator** result,
                                                  uint32_t* pFlag) {
  RememberLastUseTime();
  NS_ADDREF(*result = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
                                            nsMsgFlagSetFilter, pFlag));
  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.
      nsTArray<nsMsgKey> keys;
      if (NS_SUCCEEDED(ListAllKeys(keys))) {
        for (nsMsgKey key : keys) {
          if (key >= 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 = false;
  nsMsgKey msgKey = nsMsgKey_None;
  (void)hdr->GetMessageKey(&msgKey);
  (void)ContainsKey(msgKey, &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.

    // do this after we've put the new hdr in the thread
    nsCOMPtr<nsIMsgThread> thread;
    uint32_t threadFlags = 0;
    nsresult rv = GetThreadContainingMsgHdr(hdr, getter_AddRefs(thread));
    if (NS_SUCCEEDED(rv)) {
      thread->GetFlags(&threadFlags);
    }
    bool isIgnored = false;
    hdr->GetIsKilled(&isIgnored);
    isIgnored |= threadFlags & nsMsgMessageFlags::Ignored;

    if (flags & nsMsgMessageFlags::New) {
      uint32_t newFlags;
      newHdr->AndFlags(~nsMsgMessageFlags::New,
                       &newFlags);  // make sure not filed out
      if (!isIgnored) {
        AddToNewList(key);
      } else {
        flags &= ~nsMsgMessageFlags::New;
      }
    }
    if (m_dbFolderInfo) {
      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 (isIgnored) {
      AutoTArray<RefPtr<nsIMsgDBHdr>, 1> msg;
      msg.AppendElement(newHdr);
      // This marks the message as read and, in the case of an IMAP folder,
      // synchronizes its flags.
      m_folder->MarkMessagesRead(msg, true);
    }

    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->ClearCachedValues();
      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("@mozilla.org/messenger/mimeconverter;1");
  }
  return m_mimeConverter;
}

nsresult nsMsgDatabase::GetEffectiveCharset(nsIMdbRow* row,
                                            nsACString& resultCharset) {
  resultCharset.Truncate();
  nsresult rv = RowCellColumnToCharPtr(row, m_messageCharSetColumnToken,
                                       getter_Copies(resultCharset));
  if (NS_FAILED(rv) || resultCharset.IsEmpty() ||
      resultCharset.EqualsLiteral("us-ascii")) {
    resultCharset.AssignLiteral("UTF-8");
    nsCOMPtr<nsIMsgNewsFolder> newsfolder(do_QueryInterface(m_folder));
    if (newsfolder) newsfolder->GetCharset(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, nsTArray<uint8_t>& result) {
  nsString sender;
  nsresult rv = RowCellColumnToMime2DecodedString(row, colToken, sender);
  NS_ENSURE_SUCCESS(rv, rv);

  nsString name;
  ExtractName(DecodedHeader(sender), name);
  return CreateCollationKey(name, result);
}

nsresult nsMsgDatabase::GetCollationKeyGenerator() {
  if (!m_collationKeyGenerator) {
    auto result = mozilla::intl::LocaleService::TryCreateComponent<Collator>();
    if (result.isErr()) {
      NS_WARNING("Could not create mozilla::intl::Collation.");
      return NS_ERROR_FAILURE;
    }

    m_collationKeyGenerator = result.unwrap();

    // Sort in a case-insensitive way, where "base" letters are considered
    // equal, e.g: a = á, a = A, a ≠ b.
    Collator::Options options{};
    options.sensitivity = Collator::Sensitivity::Base;
    auto optResult = m_collationKeyGenerator->SetOptions(options);

    if (optResult.isErr()) {
      NS_WARNING("Could not configure the mozilla::intl::Collation.");
      m_collationKeyGenerator = nullptr;
      return NS_ERROR_FAILURE;
    }
  }
  return NS_OK;
}

nsresult nsMsgDatabase::RowCellColumnToCollationKey(nsIMdbRow* row,
                                                    mdb_token columnToken,
                                                    nsTArray<uint8_t>& result) {
  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), result);
    }
  }
  return err;
}

NS_IMETHODIMP
nsMsgDatabase::CompareCollationKeys(const nsTArray<uint8_t>& key1,
                                    const nsTArray<uint8_t>& key2,
                                    int32_t* result) {
  nsresult rv = GetCollationKeyGenerator();
  NS_ENSURE_SUCCESS(rv, rv);
  if (!m_collationKeyGenerator) return NS_ERROR_FAILURE;

  *result = m_collationKeyGenerator->CompareSortKeys(key1, key2);
  return NS_OK;
}

NS_IMETHODIMP
nsMsgDatabase::CreateCollationKey(const nsAString& sourceString,
                                  nsTArray<uint8_t>& key) {
  nsresult err = GetCollationKeyGenerator();
  NS_ENSURE_SUCCESS(err, err);
  if (!m_collationKeyGenerator) return NS_ERROR_FAILURE;

  nsTArrayU8Buffer buffer(key);

  auto result = m_collationKeyGenerator->GetSortKey(sourceString, buffer);
  NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE);

  return NS_OK;
}

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 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*)moz_xmalloc(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 = 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 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)
    CopyUTF8toUTF16(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::components::DB::Service());
    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<nsIMsgEnumerator> 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<nsIMsgDBHdr> msgHdr;
    rv = enumerator->GetNext(getter_AddRefs(msgHdr));
    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");
            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(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.
  return thread->AddChild(newHdr, inReplyTo, threadInThread, this);
}

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 = 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 = 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 = 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;
  GetMsgHdrForKey(msgKey, getter_AddRefs(msg));
  if (!msg) return NS_MSG_MESSAGE_NOT_FOUND;

  return GetThreadContainingMsgHdr(msg, aResult);
}

// 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->GetMessageKey(&threadKey);
  // 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(subject);

  err = CreateNewThread(threadKey, subject.get(), &threadHdr);
  msgHdr->SetThreadId(threadKey);
  if (threadHdr) {
    NS_ADDREF(threadHdr);
    // 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);
    NS_RELEASE(threadHdr);
  }
  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;
}

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(nsTArray<nsMsgKey>& keys) {
  keys.Clear();
  nsCOMPtr<nsIMsgEnumerator> 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) {
      // clear out db hdr, because it won't be valid when we get rid of the .msf
      // file
      nsCOMPtr<nsIMsgDBHdr> dbMessage;
      rv = enumerator->GetNext(getter_AddRefs(dbMessage));
      if (NS_SUCCEEDED(rv) && dbMessage) {
        nsMsgKey msgKey;
        dbMessage->GetMessageKey(&msgKey);
        keys.AppendElement(msgKey);
      }
    }
  }
  return rv;
}

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) {
  // technically, notimplemented, but no one's putting offline ops in anyway.
  return NS_OK;
}
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-existent 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() {
  nsTArray<nsMsgKey> keys;
  nsresult rv = ListAllKeys(keys);
  NS_ENSURE_SUCCESS(rv, rv);
  for (nsMsgKey key : keys) {
    nsCOMPtr<nsIMsgDBHdr> msgHdr;
    GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
    if (msgHdr) {
      nsCString author;
      nsCString subject;

      msgHdr->GetMessageKey(&key);
      msgHdr->GetAuthor(getter_Copies(author));
      msgHdr->GetSubject(subject);
      printf("hdr key = %u, author = %s subject = %s\n", key, author.get(),
             subject.get());
    }
  }

  nsCOMPtr<nsIMsgThreadEnumerator> threads;
  rv = EnumerateThreads(getter_AddRefs(threads));
  NS_ENSURE_SUCCESS(rv, rv);
  bool hasMore = false;
  while (NS_SUCCEEDED(rv = threads->HasMoreElements(&hasMore)) && hasMore) {
    nsCOMPtr<nsIMsgThread> thread;
    rv = threads->GetNext(getter_AddRefs(thread));
    NS_ENSURE_SUCCESS(rv, rv);

    nsMsgKey key;
    thread->GetThreadKey(&key);
    printf("thread key = %u\n", key);
    // DumpThread(key);
  }
  return NS_OK;
}
#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);
    }
  }
  NS_IF_ADDREF(*retentionSettings = m_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);
    }
  }
  NS_IF_ADDREF(*downloadSettings = m_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;

  nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsToDelete;
  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 = FindMessagesOlderThan(daysToKeepHdrs, applyToFlaggedMessages,
                                 msgHdrsToDelete);
      break;
    case nsIMsgRetentionSettings::nsMsgRetainByNumHeaders:
      aMsgRetentionSettings->GetNumHeadersToKeep(&numHeadersToKeep);
      rv = FindExcessMessages(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));
  }
  NS_ENSURE_SUCCESS(rv, rv);

  if (msgHdrsToDelete.IsEmpty()) {
    return NS_OK;  // No action required.
  }

  if (aDeleteViaFolder) {
    // The folder delete will also delete headers from the DB.
    rv = m_folder->DeleteMessages(msgHdrsToDelete, nullptr, true, false,
                                  nullptr, false);
  } else {
    // We're just deleting headers in the DB.
    uint32_t kindex = 0;
    for (nsIMsgDBHdr* hdr : msgHdrsToDelete) {
      // Commit after every 300.
      rv = DeleteHeader(hdr, nullptr, kindex % 300, true);
      if (NS_FAILED(rv)) {
        break;
      }
    }
    // compress commit if we deleted more than 10
    if (msgHdrsToDelete.Length() > 10) {
      Commit(nsMsgDBCommitType::kCompressCommit);
    } else {
      Commit(nsMsgDBCommitType::kLargeCommit);
    }
  }
  return rv;
}

nsresult nsMsgDatabase::FindMessagesOlderThan(
    uint32_t daysToKeepHdrs, bool applyToFlaggedMessages,
    nsTArray<RefPtr<nsIMsgDBHdr>>& hdrsToDelete) {
  nsresult rv = NS_OK;
  hdrsToDelete.Clear();

  nsCOMPtr<nsIMsgEnumerator> hdrs;
  rv = EnumerateMessages(getter_AddRefs(hdrs));
  NS_ENSURE_SUCCESS(rv, rv);

  // cutOffDay is the PRTime cut-off point. Any msg with a date less than
  // that will get purged.
  PRTime cutOffDay = PR_Now() - daysToKeepHdrs * PR_USEC_PER_DAY;

  bool hasMore = false;
  while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
    nsCOMPtr<nsIMsgDBHdr> msg;
    rv = hdrs->GetNext(getter_AddRefs(msg));
    NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
    NS_ENSURE_SUCCESS(rv, rv);

    if (!applyToFlaggedMessages) {
      uint32_t flags;
      (void)msg->GetFlags(&flags);
      if (flags & nsMsgMessageFlags::Marked) {
        continue;
      }
    }

    PRTime date;
    msg->GetDate(&date);
    if (date < cutOffDay) {
      hdrsToDelete.AppendElement(msg);
    }
  }

  return NS_OK;
}

nsresult nsMsgDatabase::FindExcessMessages(
    uint32_t numHeadersToKeep, bool applyToFlaggedMessages,
    nsTArray<RefPtr<nsIMsgDBHdr>>& hdrsToDelete) {
  nsresult rv = NS_OK;
  hdrsToDelete.Clear();

  nsCOMPtr<nsIMsgEnumerator> hdrs;
  rv = EnumerateMessages(getter_AddRefs(hdrs));
  NS_ENSURE_SUCCESS(rv, rv);

  mdb_count numHdrs = 0;
  if (m_mdbAllMsgHeadersTable)
    m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs);
  else
    return NS_ERROR_NULL_POINTER;

  bool hasMore = false;
  while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
    nsCOMPtr<nsIMsgDBHdr> msg;
    rv = hdrs->GetNext(getter_AddRefs(msg));
    NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
    NS_ENSURE_SUCCESS(rv, rv);

    if (!applyToFlaggedMessages) {
      uint32_t flags;
      (void)msg->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) {
      numHdrs--;
      hdrsToDelete.AppendElement(msg);
    }
  }

  return NS_OK;
}

NS_IMPL_ISUPPORTS(nsMsgRetentionSettings, nsIMsgRetentionSettings)

// Initialise the member variables to reasonable 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;
}

NS_IMETHODIMP
nsMsgDatabase::GetNewList(nsTArray<nsMsgKey>& aNewKeys) {
  aNewKeys = m_newSet.Clone();
  return NS_OK;
}

nsresult nsMsgDatabase::GetSearchResultsTable(const nsACString& 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(), PromiseFlatCString(searchFolderUri).get(), &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 nsACString& aSearchFolderUri,
                             nsIMsgEnumerator** aEnumerator) {
  nsCOMPtr<nsIMdbTable> table;
  (void)GetSearchResultsTable(aSearchFolderUri, false, getter_AddRefs(table));
  if (!table) return NS_ERROR_FAILURE;  // expected result for no cached hits
  NS_ADDREF(*aEnumerator =
                new nsMsgDBEnumerator(this, table, nullptr, nullptr));
  return NS_OK;
}

NS_IMETHODIMP nsMsgDatabase::RefreshCache(const nsACString& aSearchFolderUri,
                                          nsTArray<nsMsgKey> const& aNewHits,
                                          nsTArray<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);
  aStaleHits.Clear();

#ifdef DEBUG
  for (uint64_t i = 1; i < aNewHits.Length(); i++) {
    NS_ASSERTION(aNewHits[i - 1] < aNewHits[i],
                 "cached hits for storage not sorted correctly");
  }
#endif

  while (newHitIndex < aNewHits.Length() || 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 < aNewHits.Length() &&
        aNewHits[newHitIndex] == tableRowKey) {
      newHitIndex++;
      tableRowIndex++;
      continue;
    } else if (tableRowIndex >= rowCount ||
               (newHitIndex < aNewHits.Length() &&
                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 >= aNewHits.Length() ||
               aNewHits[newHitIndex] > tableRowKey) {
      aStaleHits.AppendElement(tableRowKey);
      table->CutOid(GetEnv(), &oid);
      rowCount--;
      continue;  // don't increment tableRowIndex since we removed that row.
    }
  }

#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 nsACString& aSearchFolderUri,
                                nsIMsgDBHdr* aHdr, bool aAdd) {
  nsCOMPtr<nsIMdbTable> table;
  nsresult err =
      GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
  NS_ENSURE_SUCCESS(err, err);
  nsMsgKey key;
  err = aHdr->GetMessageKey(&key);
  nsMsgHdr* msgHdr =
      static_cast<nsMsgHdr*>(aHdr);  // closed system, so this is ok
  nsIMdbRow* hdrRow = msgHdr->GetMDBRow();
  if (NS_SUCCEEDED(err) && m_mdbStore && hdrRow) {
    if (!aAdd) {
      table->CutRow(m_mdbEnv, hdrRow);
    } else {
      mdbOid rowId;
      hdrRow->GetOid(m_mdbEnv, &rowId);
      mdb_pos insertPos = FindInsertIndexInSortedTable(table, rowId.mOid_Id);
      uint32_t rowCount;
      table->GetCount(m_mdbEnv, &rowCount);
      table->AddRow(m_mdbEnv, hdrRow);
      mdb_pos newPos;
      table->MoveRow(m_mdbEnv, hdrRow, 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 nsACString& 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;
}
