/* -*- 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 nsAddrDatabase interface using the MDB Interface.

#include "nsAddrDatabase.h"
#include "nsStringGlue.h"
#include "nsAutoPtr.h"
#include "nsUnicharUtils.h"
#include "nsAbBaseCID.h"
#include "nsIAbMDBDirectory.h"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsMsgUtils.h"
#include "nsMorkCID.h"
#include "nsIMdbFactoryFactory.h"
#include "prprf.h"
#include "nsIMutableArray.h"
#include "nsArrayUtils.h"
#include "nsIPromptService.h"
#include "nsIStringBundle.h"
#include "nsIFile.h"
#include "nsEmbedCID.h"
#include "nsIProperty.h"
#include "nsIVariant.h"
#include "nsCOMArray.h"
#include "nsArrayEnumerator.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIAbManager.h"
#include "mozilla/Services.h"
#include <algorithm>

#define ID_PAB_TABLE            1
#define ID_DELETEDCARDS_TABLE           2

// There's two books by default, although Mac may have one more, so set this
// to three. Its not going to affect much, but will save us a few reallocations
// when the cache is allocated.
const uint32_t kInitialAddrDBCacheSize = 3;

const int32_t kAddressBookDBVersion = 1;

static const char kPabTableKind[] = "ns:addrbk:db:table:kind:pab";
static const char kDeletedCardsTableKind[] = "ns:addrbk:db:table:kind:deleted"; // this table is used to keep the deleted cards

static const char kCardRowScope[] = "ns:addrbk:db:row:scope:card:all";
static const char kListRowScope[] = "ns:addrbk:db:row:scope:list:all";
static const char kDataRowScope[] = "ns:addrbk:db:row:scope:data:all";

#define DATAROW_ROWID 1

#define COLUMN_STR_MAX 16

#define PURGE_CUTOFF_COUNT 50

static const char kRecordKeyColumn[] = "RecordKey";
static const char kLastRecordKeyColumn[] = "LastRecordKey";
static const char kRowIDProperty[] = "DbRowID";

static const char kMailListTotalLists[] = "ListTotalLists";    // total number of mail list in a mailing list
static const char kLowerListNameColumn[] = "LowercaseListName";

struct mdbOid gAddressBookTableOID;

static const char kMailListAddressFormat[] = "Address%d";

nsAddrDatabase::nsAddrDatabase()
    : m_mdbEnv(nullptr), m_mdbStore(nullptr),
      m_mdbPabTable(nullptr),
      m_mdbDeletedCardsTable(nullptr),
      m_mdbTokensInitialized(false),
      m_PabTableKind(0),
      m_MailListTableKind(0),
      m_DeletedCardsTableKind(0),
      m_CardRowScopeToken(0),
      m_FirstNameColumnToken(0),
      m_LastNameColumnToken(0),
      m_PhoneticFirstNameColumnToken(0),
      m_PhoneticLastNameColumnToken(0),
      m_DisplayNameColumnToken(0),
      m_NickNameColumnToken(0),
      m_PriEmailColumnToken(0),
      m_2ndEmailColumnToken(0),
      m_WorkPhoneColumnToken(0),
      m_HomePhoneColumnToken(0),
      m_FaxColumnToken(0),
      m_PagerColumnToken(0),
      m_CellularColumnToken(0),
      m_WorkPhoneTypeColumnToken(0),
      m_HomePhoneTypeColumnToken(0),
      m_FaxTypeColumnToken(0),
      m_PagerTypeColumnToken(0),
      m_CellularTypeColumnToken(0),
      m_HomeAddressColumnToken(0),
      m_HomeAddress2ColumnToken(0),
      m_HomeCityColumnToken(0),
      m_HomeStateColumnToken(0),
      m_HomeZipCodeColumnToken(0),
      m_HomeCountryColumnToken(0),
      m_WorkAddressColumnToken(0),
      m_WorkAddress2ColumnToken(0),
      m_WorkCityColumnToken(0),
      m_WorkStateColumnToken(0),
      m_WorkZipCodeColumnToken(0),
      m_WorkCountryColumnToken(0),
      m_CompanyColumnToken(0),
      m_AimScreenNameColumnToken(0),
      m_AnniversaryYearColumnToken(0),
      m_AnniversaryMonthColumnToken(0),
      m_AnniversaryDayColumnToken(0),
      m_SpouseNameColumnToken(0),
      m_FamilyNameColumnToken(0),
      m_DefaultAddressColumnToken(0),
      m_CategoryColumnToken(0),
      m_WebPage1ColumnToken(0),
      m_WebPage2ColumnToken(0),
      m_BirthYearColumnToken(0),
      m_BirthMonthColumnToken(0),
      m_BirthDayColumnToken(0),
      m_Custom1ColumnToken(0),
      m_Custom2ColumnToken(0),
      m_Custom3ColumnToken(0),
      m_Custom4ColumnToken(0),
      m_NotesColumnToken(0),
      m_LastModDateColumnToken(0),
      m_MailFormatColumnToken(0),
      m_PopularityIndexColumnToken(0),
      m_AddressCharSetColumnToken(0),
      m_LastRecordKey(0),
      m_dbDirectory(nullptr)
{
}

nsAddrDatabase::~nsAddrDatabase()
{
    Close(false);    // better have already been closed.

    // better not be any listeners, because we're going away.
    NS_ASSERTION(m_ChangeListeners.Length() == 0, "shouldn't have any listeners");

    RemoveFromCache(this);
    // clean up after ourself!
    if (m_mdbPabTable)
      m_mdbPabTable->Release();
    if (m_mdbDeletedCardsTable)
      m_mdbDeletedCardsTable->Release();
    NS_IF_RELEASE(m_mdbStore);
    NS_IF_RELEASE(m_mdbEnv);
}

NS_IMPL_ISUPPORTS(nsAddrDatabase, nsIAddrDatabase, nsIAddrDBAnnouncer)

NS_IMETHODIMP nsAddrDatabase::AddListener(nsIAddrDBListener *listener)
{
  NS_ENSURE_ARG_POINTER(listener);
  return m_ChangeListeners.AppendElement(listener) ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP nsAddrDatabase::RemoveListener(nsIAddrDBListener *listener)
{
  NS_ENSURE_ARG_POINTER(listener);
  return m_ChangeListeners.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP nsAddrDatabase::NotifyCardAttribChange(uint32_t abCode)
{
  NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
                                     OnCardAttribChange, (abCode));
  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::NotifyCardEntryChange(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent)
{
  int32_t currentDisplayNameVersion = 0;

  //Update "mail.displayname.version" prefernce
  nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));

  prefs->GetIntPref("mail.displayname.version",&currentDisplayNameVersion);

  prefs->SetIntPref("mail.displayname.version",++currentDisplayNameVersion);

  NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
                                     OnCardEntryChange, (aAbCode, aCard, aParent));
  return NS_OK;
}

nsresult nsAddrDatabase::NotifyListEntryChange(uint32_t abCode, nsIAbDirectory *dir)
{
  NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
                                     OnListEntryChange, (abCode, dir));
  return NS_OK;
}


NS_IMETHODIMP nsAddrDatabase::NotifyAnnouncerGoingAway(void)
{
  NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener,
                                     OnAnnouncerGoingAway, ());
  return NS_OK;
}


// Apparently its not good for nsTArray to be allocated as static. Don't know
// why it isn't but its not, so don't think about making it a static variable.
// Maybe bz knows.
nsTArray<nsAddrDatabase*>* nsAddrDatabase::m_dbCache = nullptr;

nsTArray<nsAddrDatabase*>*
nsAddrDatabase::GetDBCache()
{
  if (!m_dbCache)
    m_dbCache = new nsAutoTArray<nsAddrDatabase*, kInitialAddrDBCacheSize>;

  return m_dbCache;
}

void
nsAddrDatabase::CleanupCache()
{
  if (m_dbCache)
  {
    for (int32_t i = m_dbCache->Length() - 1; i >= 0; --i)
    {
      nsAddrDatabase* pAddrDB = m_dbCache->ElementAt(i);
      if (pAddrDB)
        pAddrDB->ForceClosed();
    }
    //        NS_ASSERTION(m_dbCache.Length() == 0, "some msg dbs left open");    // better not be any open db's.
    delete m_dbCache;
    m_dbCache = nullptr;
  }
}

//----------------------------------------------------------------------
// FindInCache - this addrefs the db it finds.
//----------------------------------------------------------------------
nsAddrDatabase* nsAddrDatabase::FindInCache(nsIFile *dbName)
{
  nsTArray<nsAddrDatabase*>* dbCache = GetDBCache();
  uint32_t length = dbCache->Length();
  for (uint32_t i = 0; i < length; ++i)
  {
    nsAddrDatabase* pAddrDB = dbCache->ElementAt(i);
    if (pAddrDB->MatchDbName(dbName))
    {
      NS_ADDREF(pAddrDB);
      return pAddrDB;
    }
  }
  return nullptr;
}

bool nsAddrDatabase::MatchDbName(nsIFile* dbName)    // returns true if they match
{
    bool dbMatches = false;

    nsresult rv = m_dbName->Equals(dbName, &dbMatches);
    if (NS_FAILED(rv))
      return false;

    return dbMatches;
}

//----------------------------------------------------------------------
// RemoveFromCache
//----------------------------------------------------------------------
void nsAddrDatabase::RemoveFromCache(nsAddrDatabase* pAddrDB)
{
  if (m_dbCache)
    m_dbCache->RemoveElement(pAddrDB);
}

void nsAddrDatabase::GetMDBFactory(nsIMdbFactory ** aMdbFactory)
{
  if (!mMdbFactory)
  {
    nsresult rv;
    nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv);
    if (NS_SUCCEEDED(rv) && mdbFactoryService)
      rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
  }
  NS_IF_ADDREF(*aMdbFactory = mMdbFactory);
}

/* caller need to delete *aDbPath */
NS_IMETHODIMP nsAddrDatabase::GetDbPath(nsIFile* *aDbPath)
{
    if (!aDbPath)
        return NS_ERROR_NULL_POINTER;

    return m_dbName->Clone(aDbPath);
}

NS_IMETHODIMP nsAddrDatabase::SetDbPath(nsIFile* aDbPath)
{
    return aDbPath->Clone(getter_AddRefs(m_dbName));
}

NS_IMETHODIMP nsAddrDatabase::Open
(nsIFile *aMabFile, bool aCreate, bool upgrading /* unused */, nsIAddrDatabase** pAddrDB)
{
  *pAddrDB = nullptr;

  nsAddrDatabase *pAddressBookDB = FindInCache(aMabFile);

  if (pAddressBookDB) {
    *pAddrDB = pAddressBookDB;
    return NS_OK;
  }

  nsresult rv = OpenInternal(aMabFile, aCreate, pAddrDB);
  if (NS_SUCCEEDED(rv))
    return NS_OK;

  if (rv == NS_ERROR_FILE_ACCESS_DENIED)
  {
    static bool gAlreadyAlerted;
     // only do this once per session to avoid annoying the user
    if (!gAlreadyAlerted)
    {
      gAlreadyAlerted = true;
      nsAutoString mabFileName;
      rv = aMabFile->GetLeafName(mabFileName);
      NS_ENSURE_SUCCESS(rv, rv);
      AlertAboutLockedMabFile(mabFileName.get());

      // We just overwrote rv, so return the proper value here.
      return NS_ERROR_FILE_ACCESS_DENIED;
    }
  }
  // try one more time
  // but first rename corrupt mab file
  // and prompt the user
  else if (aCreate)
  {
    nsCOMPtr<nsIFile> dummyBackupMabFile;
    nsCOMPtr<nsIFile> actualBackupMabFile;

    // First create a clone of the corrupt mab file that we'll
    // use to generate the name for the backup file that we are
    // going to move it to.
    rv = aMabFile->Clone(getter_AddRefs(dummyBackupMabFile));
    NS_ENSURE_SUCCESS(rv, rv);

    // Now create a second clone that we'll use to do the move
    // (this allows us to leave the original name intact)
    rv = aMabFile->Clone(getter_AddRefs(actualBackupMabFile));
    NS_ENSURE_SUCCESS(rv, rv);

    // Now we try and generate a new name for the corrupt mab
    // file using the dummy backup mab file

    // First append .bak - we have to do this the long way as
    // AppendNative is to the path, not the LeafName.
    nsAutoCString dummyBackupMabFileName;
    rv = dummyBackupMabFile->GetNativeLeafName(dummyBackupMabFileName);
    NS_ENSURE_SUCCESS(rv, rv);

    dummyBackupMabFileName.Append(NS_LITERAL_CSTRING(".bak"));

    rv = dummyBackupMabFile->SetNativeLeafName(dummyBackupMabFileName);
    NS_ENSURE_SUCCESS(rv, rv);

    // Now see if we can create it unique
    rv = dummyBackupMabFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
    NS_ENSURE_SUCCESS(rv, rv);

    // Now get the new name
    nsAutoCString backupMabFileName;
    rv = dummyBackupMabFile->GetNativeLeafName(backupMabFileName);
    NS_ENSURE_SUCCESS(rv, rv);

    // And the parent directory
    nsCOMPtr<nsIFile> parentDir;
    rv = dummyBackupMabFile->GetParent(getter_AddRefs(parentDir));
    NS_ENSURE_SUCCESS(rv, rv);

    // Now move the corrupt file to its backup location
    rv = actualBackupMabFile->MoveToNative(parentDir, backupMabFileName);
    NS_ASSERTION(NS_SUCCEEDED(rv), "failed to rename corrupt mab file");

    if (NS_SUCCEEDED(rv)) {
      // now we can try to recreate the original mab file
      rv = OpenInternal(aMabFile, aCreate, pAddrDB);
      NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create .mab file, after rename");

      if (NS_SUCCEEDED(rv)) {
        nsAutoString originalMabFileName;
        rv = aMabFile->GetLeafName(originalMabFileName);
        NS_ENSURE_SUCCESS(rv, rv);

        // if this fails, we don't care
        (void)AlertAboutCorruptMabFile(originalMabFileName.get(),
          NS_ConvertASCIItoUTF16(backupMabFileName).get());
      }
    }
  }
  return rv;
}

nsresult nsAddrDatabase::DisplayAlert(const char16_t *titleName, const char16_t *alertStringName, const char16_t **formatStrings, int32_t numFormatStrings)
{
  nsresult rv;
  nsCOMPtr<nsIStringBundleService> bundleService =
    mozilla::services::GetStringBundleService();
  NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);

  nsCOMPtr<nsIStringBundle> bundle;
  rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle));
  NS_ENSURE_SUCCESS(rv, rv);

  nsString alertMessage;
  rv = bundle->FormatStringFromName(alertStringName, formatStrings, numFormatStrings,
    getter_Copies(alertMessage));
  NS_ENSURE_SUCCESS(rv, rv);

  nsString alertTitle;
  rv = bundle->GetStringFromName(titleName, getter_Copies(alertTitle));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIPromptService> prompter =
      do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return prompter->Alert(nullptr /* we don't know the parent window */, alertTitle.get(), alertMessage.get());
}

nsresult nsAddrDatabase::AlertAboutCorruptMabFile(const char16_t *aOldFileName, const char16_t *aNewFileName)
{
  const char16_t *formatStrings[] = { aOldFileName, aOldFileName, aNewFileName };
  return DisplayAlert(MOZ_UTF16("corruptMabFileTitle"),
    MOZ_UTF16("corruptMabFileAlert"), formatStrings, 3);
}

nsresult nsAddrDatabase::AlertAboutLockedMabFile(const char16_t *aFileName)
{
  const char16_t *formatStrings[] = { aFileName };
  return DisplayAlert(MOZ_UTF16("lockedMabFileTitle"),
    MOZ_UTF16("lockedMabFileAlert"), formatStrings, 1);
}

nsresult
nsAddrDatabase::OpenInternal(nsIFile *aMabFile, bool aCreate, nsIAddrDatabase** pAddrDB)
{
  nsAddrDatabase *pAddressBookDB = new nsAddrDatabase();
  if (!pAddressBookDB) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  NS_ADDREF(pAddressBookDB);

  nsresult rv = pAddressBookDB->OpenMDB(aMabFile, aCreate);
  if (NS_SUCCEEDED(rv))
  {
    pAddressBookDB->SetDbPath(aMabFile);
    GetDBCache()->AppendElement(pAddressBookDB);
    *pAddrDB = pAddressBookDB;
  }
  else
  {
    *pAddrDB = nullptr;
    pAddressBookDB->ForceClosed();
    NS_IF_RELEASE(pAddressBookDB);
    pAddressBookDB = nullptr;
  }
  return rv;
}

// Open the MDB database synchronously. If successful, this routine
// will set up the m_mdbStore and m_mdbEnv of the database object
// so other database calls can work.
NS_IMETHODIMP nsAddrDatabase::OpenMDB(nsIFile *dbName, bool create)
{
  nsresult ret;
  nsCOMPtr<nsIMdbFactory> mdbFactory;
  GetMDBFactory(getter_AddRefs(mdbFactory));
  NS_ENSURE_TRUE(mdbFactory, NS_ERROR_FAILURE);

  ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv);
  if (NS_SUCCEEDED(ret))
  {
    nsIMdbThumb *thumb = nullptr;
    nsAutoCString filePath;

    ret = dbName->GetNativePath(filePath);
    NS_ENSURE_SUCCESS(ret, ret);

    nsIMdbHeap* dbHeap = nullptr;

    if (m_mdbEnv)
      m_mdbEnv->SetAutoClear(true);

    bool dbNameExists = false;
    ret = dbName->Exists(&dbNameExists);
    NS_ENSURE_SUCCESS(ret, ret);

    if (!dbNameExists)
      ret = NS_ERROR_FILE_NOT_FOUND;
    else
    {
      mdbOpenPolicy inOpenPolicy;
      mdb_bool    canOpen;
      mdbYarn        outFormatVersion;
      nsIMdbFile* oldFile = nullptr;
      int64_t fileSize;
      ret = dbName->GetFileSize(&fileSize);
      NS_ENSURE_SUCCESS(ret, ret);

      ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, filePath.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, &thumb);
          }
          else if (fileSize != 0)
            ret = NS_ERROR_FILE_ACCESS_DENIED;
        }
        NS_RELEASE(oldFile); // always release our file ref, store has own
      }
      if (NS_FAILED(ret))
        ret = NS_ERROR_FILE_ACCESS_DENIED;
    }

    if (NS_SUCCEEDED(ret) && thumb)
    {
      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 = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
        if (NS_FAILED(ret))
        {
          outDone = true;
          break;
        }
      }
      while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
      if (NS_SUCCEEDED(ret) && outDone)
      {
        ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore);
        if (NS_SUCCEEDED(ret) && m_mdbStore)
        {
          ret = InitExistingDB();
          create = false;
        }
      }
    }
    else if (create && ret != NS_ERROR_FILE_ACCESS_DENIED)
    {
      nsIMdbFile* newFile = 0;
      ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, filePath.get(), &newFile);
      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 = InitNewDB();
        }
        NS_RELEASE(newFile); // always release our file ref, store has own
      }
    }
    NS_IF_RELEASE(thumb);
  }
  return ret;
}

NS_IMETHODIMP nsAddrDatabase::CloseMDB(bool commit)
{
    if (commit)
        Commit(nsAddrDBCommitType::kSessionCommit);
//???    RemoveFromCache(this);  // if we've closed it, better not leave it in the cache.
    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 nsAddrDatabase::ForceClosed()
{
    nsresult    err = NS_OK;
    nsCOMPtr<nsIAddrDatabase> aDb(do_QueryInterface(this, &err));

    // make sure someone has a reference so object won't get deleted out from under us.
    AddRef();
    NotifyAnnouncerGoingAway();
    // OK, remove from cache first and close the store.
    RemoveFromCache(this);

    err = CloseMDB(false);    // since we're about to delete it, no need to commit.
    NS_IF_RELEASE(m_mdbStore);
    Release();
    return err;
}

NS_IMETHODIMP nsAddrDatabase::Commit(uint32_t commitType)
{
  nsresult err = NS_OK;
  nsIMdbThumb *commitThumb = nullptr;

  if (commitType == nsAddrDBCommitType::kLargeCommit ||
      commitType == nsAddrDBCommitType::kSessionCommit)
  {
    mdb_percent outActualWaste = 0;
    mdb_bool outShould;
    if (m_mdbStore && m_mdbEnv)
    {
      // check how much space would be saved by doing a compress commit.
      // If it's more than 30%, go for it.
      // N.B. - I'm not sure this calls works in Mork for all cases.
      err = m_mdbStore->ShouldCompress(m_mdbEnv, 30, &outActualWaste, &outShould);
      if (NS_SUCCEEDED(err) && outShould)
      {
        commitType = nsAddrDBCommitType::kCompressCommit;
      }
    }
  }

  if (m_mdbStore && m_mdbEnv)
  {
    switch (commitType)
    {
      case nsAddrDBCommitType::kLargeCommit:
        err = m_mdbStore->LargeCommit(m_mdbEnv, &commitThumb);
        break;
      case nsAddrDBCommitType::kSessionCommit:
        // comment out until persistence works.
        err = m_mdbStore->SessionCommit(m_mdbEnv, &commitThumb);
        break;
      case nsAddrDBCommitType::kCompressCommit:
        err = m_mdbStore->CompressCommit(m_mdbEnv, &commitThumb);
        break;
      }
  }
  if (commitThumb && m_mdbEnv)
  {
    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(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
    }
    NS_RELEASE(commitThumb);
  }
  // ### do something with error, but clear it now because mork errors out on commits.
  if (m_mdbEnv)
    m_mdbEnv->ClearErrors();
  return err;
}

NS_IMETHODIMP nsAddrDatabase::Close(bool forceCommit /* = TRUE */)
{
    return CloseMDB(forceCommit);
}

// set up empty tablesetc.
nsresult nsAddrDatabase::InitNewDB()
{
  nsresult err = InitMDBInfo();
  if (NS_SUCCEEDED(err))
  {
    err = InitPabTable();
    err = InitLastRecorKey();
    Commit(nsAddrDBCommitType::kLargeCommit);
  }
  return err;
}

nsresult nsAddrDatabase::AddRowToDeletedCardsTable(nsIAbCard *card, nsIMdbRow **pCardRow)
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult rv = NS_OK;
  if (!m_mdbDeletedCardsTable)
    rv = InitDeletedCardsTable(true);

  if (NS_SUCCEEDED(rv)) {
    // lets first purge old records if there are more than PURGE_CUTOFF_COUNT records
    PurgeDeletedCardTable();
    nsCOMPtr<nsIMdbRow> cardRow;
    rv = GetNewRow(getter_AddRefs(cardRow));
    if (NS_SUCCEEDED(rv) && cardRow) {
      nsresult merror = m_mdbDeletedCardsTable->AddRow(m_mdbEnv, cardRow);
      NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);
      nsString unicodeStr;
      card->GetFirstName(unicodeStr);
      AddFirstName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());

      card->GetLastName(unicodeStr);
      AddLastName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());

      card->GetDisplayName(unicodeStr);
      AddDisplayName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get());

      card->GetPrimaryEmail(unicodeStr);
      if (!unicodeStr.IsEmpty())
        AddUnicodeToColumn(cardRow, m_PriEmailColumnToken, m_LowerPriEmailColumnToken, unicodeStr.get());

      card->GetPropertyAsAString(k2ndEmailProperty, unicodeStr);
      if (!unicodeStr.IsEmpty())
        AddUnicodeToColumn(cardRow, m_2ndEmailColumnToken, m_Lower2ndEmailColumnToken, unicodeStr.get());

      uint32_t nowInSeconds;
      PRTime now = PR_Now();
      PRTime2Seconds(now, &nowInSeconds);
      AddIntColumn(cardRow, m_LastModDateColumnToken, nowInSeconds);

      nsString value;
      GetCardValue(card, CARD_ATTRIB_PALMID, getter_Copies(value));
      if (!value.IsEmpty())
      {
        nsCOMPtr<nsIAbCard> addedCard;
        rv = CreateCardFromDeletedCardsTable(cardRow, 0, getter_AddRefs(addedCard));
        if (NS_SUCCEEDED(rv))
          SetCardValue(addedCard, CARD_ATTRIB_PALMID, value.get(), false);
      }
      NS_IF_ADDREF(*pCardRow = cardRow);
    }
    Commit(nsAddrDBCommitType::kLargeCommit);
  }
  return rv;
}

nsresult nsAddrDatabase::DeleteRowFromDeletedCardsTable(nsIMdbRow *pCardRow)
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult merror = NS_OK;
  if (m_mdbDeletedCardsTable) {
    pCardRow->CutAllColumns(m_mdbEnv);
    merror = m_mdbDeletedCardsTable->CutRow(m_mdbEnv, pCardRow);
  }
  return merror;
}


nsresult nsAddrDatabase::InitDeletedCardsTable(bool aCreate)
{
  nsresult mdberr = NS_OK;
  if (!m_mdbDeletedCardsTable)
  {
    struct mdbOid deletedCardsTableOID;
    deletedCardsTableOID.mOid_Scope = m_CardRowScopeToken;
    deletedCardsTableOID.mOid_Id = ID_DELETEDCARDS_TABLE;
    if (m_mdbStore && m_mdbEnv)
    {
      m_mdbStore->GetTable(m_mdbEnv, &deletedCardsTableOID, &m_mdbDeletedCardsTable);
      // if deletedCardsTable does not exist and bCreate is set, create a new one
      if (!m_mdbDeletedCardsTable && aCreate)
      {
        mdberr = (nsresult) m_mdbStore->NewTableWithOid(m_mdbEnv, &deletedCardsTableOID,
                                                        m_DeletedCardsTableKind,
                                                        true, (const mdbOid*)nullptr,
                                                        &m_mdbDeletedCardsTable);
      }
    }
  }
  return mdberr;
}

nsresult nsAddrDatabase::InitPabTable()
{
  return m_mdbStore && m_mdbEnv ? m_mdbStore->NewTableWithOid(m_mdbEnv,
                                                  &gAddressBookTableOID,
                                                  m_PabTableKind,
                                                  false,
                                                  (const mdbOid*)nullptr,
                                                  &m_mdbPabTable)
      : NS_ERROR_NULL_POINTER;
}

//save the last record number, store in m_DataRowScopeToken, row 1
nsresult nsAddrDatabase::InitLastRecorKey()
{
  if (!m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsIMdbRow *pDataRow = nullptr;
  mdbOid dataRowOid;
  dataRowOid.mOid_Scope = m_DataRowScopeToken;
  dataRowOid.mOid_Id = DATAROW_ROWID;
  nsresult err = m_mdbStore->NewRowWithOid(m_mdbEnv, &dataRowOid, &pDataRow);

  if (NS_SUCCEEDED(err) && pDataRow)
  {
    m_LastRecordKey = 0;
    err = AddIntColumn(pDataRow, m_LastRecordKeyColumnToken, 0);
    err = m_mdbPabTable->AddRow(m_mdbEnv, pDataRow);
    NS_RELEASE(pDataRow);
  }
  return err;
}

nsresult nsAddrDatabase::GetDataRow(nsIMdbRow **pDataRow)
{
  if (!m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsIMdbRow *pRow = nullptr;
  mdbOid dataRowOid;
  dataRowOid.mOid_Scope = m_DataRowScopeToken;
  dataRowOid.mOid_Id = DATAROW_ROWID;
  m_mdbStore->GetRow(m_mdbEnv, &dataRowOid, &pRow);
  *pDataRow = pRow;

  return pRow ? NS_OK : NS_ERROR_FAILURE;
}

nsresult nsAddrDatabase::GetLastRecordKey()
{
    if (!m_mdbPabTable)
        return NS_ERROR_NULL_POINTER;

    nsCOMPtr <nsIMdbRow> pDataRow;
    nsresult err = GetDataRow(getter_AddRefs(pDataRow));

    if (NS_SUCCEEDED(err) && pDataRow)
    {
        m_LastRecordKey = 0;
        err = GetIntColumn(pDataRow, m_LastRecordKeyColumnToken, &m_LastRecordKey, 0);
        if (NS_FAILED(err))
            err = NS_ERROR_NOT_AVAILABLE;
        return NS_OK;
    }

    return NS_ERROR_NOT_AVAILABLE;
}

nsresult nsAddrDatabase::UpdateLastRecordKey()
{
  if (!m_mdbPabTable || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsCOMPtr <nsIMdbRow> pDataRow;
  nsresult err = GetDataRow(getter_AddRefs(pDataRow));

  if (NS_SUCCEEDED(err) && pDataRow)
  {
    err = AddIntColumn(pDataRow, m_LastRecordKeyColumnToken, m_LastRecordKey);
    err = m_mdbPabTable->AddRow(m_mdbEnv, pDataRow);
    return NS_OK;
  }
  else if (!pDataRow)
    err = InitLastRecorKey();
  else
    return NS_ERROR_NOT_AVAILABLE;
  return err;
}

nsresult nsAddrDatabase::InitExistingDB()
{
  nsresult err = InitMDBInfo();
  if (NS_SUCCEEDED(err))
  {
    if (!m_mdbStore || !m_mdbEnv)
      return NS_ERROR_NULL_POINTER;

    err = m_mdbStore->GetTable(m_mdbEnv, &gAddressBookTableOID, &m_mdbPabTable);
    if (NS_SUCCEEDED(err) && m_mdbPabTable)
    {
      err = GetLastRecordKey();
      if (err == NS_ERROR_NOT_AVAILABLE)
        CheckAndUpdateRecordKey();
      UpdateLowercaseEmailListName();
    }
  }
  return err;
}

nsresult nsAddrDatabase::CheckAndUpdateRecordKey()
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = NS_OK;
  nsIMdbTableRowCursor* rowCursor = nullptr;
  nsIMdbRow* findRow = nullptr;
  mdb_pos    rowPos = 0;

  nsresult merror = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);

  NS_ENSURE_TRUE(NS_SUCCEEDED(merror) && rowCursor, NS_ERROR_FAILURE);

  nsCOMPtr <nsIMdbRow> pDataRow;
  err = GetDataRow(getter_AddRefs(pDataRow));
  if (NS_FAILED(err))
    InitLastRecorKey();

  do
  {  //add key to each card and mailing list row
    merror = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos);
    if (NS_SUCCEEDED(merror) && findRow)
    {
      mdbOid rowOid;

      if (NS_SUCCEEDED(findRow->GetOid(m_mdbEnv, &rowOid)))
      {
        if (!IsDataRowScopeToken(rowOid.mOid_Scope))
        {
          m_LastRecordKey++;
          err = AddIntColumn(findRow, m_RecordKeyColumnToken, m_LastRecordKey);
        }
      }
    }
  } while (findRow);

  UpdateLastRecordKey();
  Commit(nsAddrDBCommitType::kLargeCommit);
  return NS_OK;
}

nsresult nsAddrDatabase::UpdateLowercaseEmailListName()
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = NS_OK;
  nsIMdbTableRowCursor* rowCursor = nullptr;
  nsIMdbRow* findRow = nullptr;
  mdb_pos    rowPos = 0;
  bool commitRequired = false;

  nsresult merror = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);

  NS_ENSURE_TRUE(NS_SUCCEEDED(merror) && rowCursor, NS_ERROR_FAILURE);

  do
  { // Add lowercase primary+secondary email to each card and mailing list row.
    merror = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos);
    if (NS_SUCCEEDED(merror) && findRow)
    {
      mdbOid rowOid;

      if (NS_SUCCEEDED(findRow->GetOid(m_mdbEnv, &rowOid)))
      {
        nsAutoString tempString;
        if (IsCardRowScopeToken(rowOid.mOid_Scope))
        {
          err = GetStringColumn(findRow, m_LowerPriEmailColumnToken, tempString);
          if (NS_FAILED(err)) // not set yet
          {
            err = ConvertAndAddLowercaseColumn(findRow, m_PriEmailColumnToken,
              m_LowerPriEmailColumnToken);
            commitRequired = commitRequired || NS_SUCCEEDED(err);
          }

          err = GetStringColumn(findRow, m_Lower2ndEmailColumnToken, tempString);
          if (NS_FAILED(err)) // not set yet
          {
            err = ConvertAndAddLowercaseColumn(findRow, m_2ndEmailColumnToken,
              m_Lower2ndEmailColumnToken);
            commitRequired = commitRequired || NS_SUCCEEDED(err);
          }
        }
        else if (IsListRowScopeToken(rowOid.mOid_Scope))
        {
          err = GetStringColumn(findRow, m_LowerListNameColumnToken, tempString);
          if (NS_SUCCEEDED(err)) // already set up
            continue;

          err = ConvertAndAddLowercaseColumn(findRow, m_ListNameColumnToken,
            m_LowerListNameColumnToken);
          commitRequired = commitRequired || NS_SUCCEEDED(err);
        }
      }
      findRow->Release();
    }
  } while (findRow);

  if (findRow)
    findRow->Release();
  rowCursor->Release();
  if (commitRequired)
    Commit(nsAddrDBCommitType::kLargeCommit);
  return NS_OK;
}

/*
We store UTF8 strings in the database.  We need to convert the UTF8
string into unicode string, then convert to lower case.  Before storing
back into the database,  we need to convert the lowercase unicode string
into UTF8 string.
*/
nsresult nsAddrDatabase::ConvertAndAddLowercaseColumn
(nsIMdbRow * row, mdb_token fromCol, mdb_token toCol)
{
    nsAutoString colString;

    nsresult rv = GetStringColumn(row, fromCol, colString);
    if (!colString.IsEmpty())
    {
        rv = AddLowercaseColumn(row, toCol, NS_ConvertUTF16toUTF8(colString).get());
    }
    return rv;
}

// Change the unicode string to lowercase, then convert to UTF8 string to store in db
nsresult nsAddrDatabase::AddUnicodeToColumn(nsIMdbRow * row, mdb_token aColToken, mdb_token aLowerCaseColToken, const char16_t* aUnicodeStr)
{
  nsresult rv = AddCharStringColumn(row, aColToken, NS_ConvertUTF16toUTF8(aUnicodeStr).get());
  NS_ENSURE_SUCCESS(rv,rv);

  rv = AddLowercaseColumn(row, aLowerCaseColToken, NS_ConvertUTF16toUTF8(aUnicodeStr).get());
  NS_ENSURE_SUCCESS(rv,rv);
  return rv;
}

// initialize the various tokens and tables in our db's env
nsresult nsAddrDatabase::InitMDBInfo()
{
  nsresult err = NS_OK;

  if (!m_mdbTokensInitialized && m_mdbStore && m_mdbEnv)
  {
    m_mdbTokensInitialized = true;
    err = m_mdbStore->StringToToken(m_mdbEnv, kCardRowScope, &m_CardRowScopeToken);
    err = m_mdbStore->StringToToken(m_mdbEnv, kListRowScope, &m_ListRowScopeToken);
    err = m_mdbStore->StringToToken(m_mdbEnv, kDataRowScope, &m_DataRowScopeToken);
    gAddressBookTableOID.mOid_Scope = m_CardRowScopeToken;
    gAddressBookTableOID.mOid_Id = ID_PAB_TABLE;
    if (NS_SUCCEEDED(err))
    {
      m_mdbStore->StringToToken(m_mdbEnv,  kFirstNameProperty, &m_FirstNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kLastNameProperty, &m_LastNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kPhoneticFirstNameProperty, &m_PhoneticFirstNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kPhoneticLastNameProperty, &m_PhoneticLastNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kDisplayNameProperty, &m_DisplayNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kNicknameProperty, &m_NickNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kPriEmailProperty, &m_PriEmailColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kLowerPriEmailColumn, &m_LowerPriEmailColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  k2ndEmailProperty, &m_2ndEmailColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kLower2ndEmailColumn, &m_Lower2ndEmailColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kPreferMailFormatProperty, &m_MailFormatColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kPopularityIndexProperty, &m_PopularityIndexColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkPhoneProperty, &m_WorkPhoneColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomePhoneProperty, &m_HomePhoneColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kFaxProperty, &m_FaxColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kPagerProperty, &m_PagerColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kCellularProperty, &m_CellularColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkPhoneTypeProperty, &m_WorkPhoneTypeColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomePhoneTypeProperty, &m_HomePhoneTypeColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kFaxTypeProperty, &m_FaxTypeColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kPagerTypeProperty, &m_PagerTypeColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kCellularTypeProperty, &m_CellularTypeColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomeAddressProperty, &m_HomeAddressColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomeAddress2Property, &m_HomeAddress2ColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomeCityProperty, &m_HomeCityColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomeStateProperty, &m_HomeStateColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomeZipCodeProperty, &m_HomeZipCodeColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomeCountryProperty, &m_HomeCountryColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkAddressProperty, &m_WorkAddressColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkAddress2Property, &m_WorkAddress2ColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkCityProperty, &m_WorkCityColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkStateProperty, &m_WorkStateColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkZipCodeProperty, &m_WorkZipCodeColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkCountryProperty, &m_WorkCountryColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kJobTitleProperty, &m_JobTitleColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kDepartmentProperty, &m_DepartmentColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kCompanyProperty, &m_CompanyColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kScreenNameProperty, &m_AimScreenNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kAnniversaryYearProperty, &m_AnniversaryYearColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kAnniversaryMonthProperty, &m_AnniversaryMonthColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kAnniversaryDayProperty, &m_AnniversaryDayColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kSpouseNameProperty, &m_SpouseNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kFamilyNameProperty, &m_FamilyNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kWorkWebPageProperty, &m_WebPage1ColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kHomeWebPageProperty, &m_WebPage2ColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kBirthYearProperty, &m_BirthYearColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kBirthMonthProperty, &m_BirthMonthColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kBirthDayProperty, &m_BirthDayColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kCustom1Property, &m_Custom1ColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kCustom2Property, &m_Custom2ColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kCustom3Property, &m_Custom3ColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kCustom4Property, &m_Custom4ColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kNotesProperty, &m_NotesColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kLastModifiedDateProperty, &m_LastModDateColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kRecordKeyColumn, &m_RecordKeyColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kAddressCharSetColumn, &m_AddressCharSetColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kLastRecordKeyColumn, &m_LastRecordKeyColumnToken);

      err = m_mdbStore->StringToToken(m_mdbEnv, kPabTableKind, &m_PabTableKind);

      m_mdbStore->StringToToken(m_mdbEnv,  kMailListName, &m_ListNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kMailListNickName, &m_ListNickNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kMailListDescription, &m_ListDescriptionColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kMailListTotalAddresses, &m_ListTotalColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kLowerListNameColumn, &m_LowerListNameColumnToken);
      m_mdbStore->StringToToken(m_mdbEnv,  kDeletedCardsTableKind, &m_DeletedCardsTableKind);
    }
  }
  return err;
}

////////////////////////////////////////////////////////////////////////////////

nsresult nsAddrDatabase::AddRecordKeyColumnToRow(nsIMdbRow *pRow)
{
  if (pRow && m_mdbEnv)
  {
    m_LastRecordKey++;
    nsresult err = AddIntColumn(pRow, m_RecordKeyColumnToken, m_LastRecordKey);
    NS_ENSURE_SUCCESS(err, err);

    err = m_mdbPabTable->AddRow(m_mdbEnv, pRow);
    UpdateLastRecordKey();
    return err;
  }
  return NS_ERROR_NULL_POINTER;
}

nsresult nsAddrDatabase::AddAttributeColumnsToRow(nsIAbCard *card, nsIMdbRow *cardRow)
{
  nsresult rv = NS_OK;

  if ((!card && !cardRow) || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  mdbOid rowOid;
  cardRow->GetOid(m_mdbEnv, &rowOid);

  card->SetPropertyAsUint32(kRowIDProperty, rowOid.mOid_Id);

  // add the row to the singleton table.
  if (card && cardRow)
  {
    nsCOMPtr<nsISimpleEnumerator> properties;
    rv = card->GetProperties(getter_AddRefs(properties));
    NS_ENSURE_SUCCESS(rv, rv);

    bool hasMore;
    while (NS_SUCCEEDED(properties->HasMoreElements(&hasMore)) && hasMore)
    {
      nsCOMPtr<nsISupports> next;
      rv = properties->GetNext(getter_AddRefs(next));
      NS_ENSURE_SUCCESS(rv, rv);

      nsCOMPtr<nsIProperty> prop = do_QueryInterface(next);
      nsAutoString name;
      prop->GetName(name);

      nsCOMPtr<nsIVariant> variant;
      prop->GetValue(getter_AddRefs(variant));
      
      // We can't get as a char * because that messes up UTF8 stuff
      nsAutoCString value;
      variant->GetAsAUTF8String(value);

      mdb_token token;
      rv = m_mdbStore->StringToToken(m_mdbEnv, NS_ConvertUTF16toUTF8(name).get(), &token);
      NS_ENSURE_SUCCESS(rv, rv);
 
      rv = AddCharStringColumn(cardRow, token, value.get());
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // Primary email is special: it is stored lowercase as well as in its
    // original format.
    nsAutoString primaryEmail;
    card->GetPrimaryEmail(primaryEmail);
    AddPrimaryEmail(cardRow, NS_ConvertUTF16toUTF8(primaryEmail).get());
  }

  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::CreateNewCardAndAddToDB(nsIAbCard *aNewCard, bool aNotify /* = FALSE */, nsIAbDirectory *aParent)
{
  nsCOMPtr <nsIMdbRow> cardRow;

  if (!aNewCard || !m_mdbPabTable || !m_mdbEnv || !m_mdbStore)
    return NS_ERROR_NULL_POINTER;

  // Per the UUID requirements, we want to try to reuse the local id if at all
  // possible. nsACString::ToInteger probably won't fail if the local id looks
  // like "23bozo" (returning 23 instead), but it's okay since we aren't going
  // to overwrite anything with 23 if it already exists and the id for the row
  // doesn't matter otherwise.
  nsresult rv;

  nsAutoCString id;
  aNewCard->GetLocalId(id);

  mdbOid rowId;
  rowId.mOid_Scope = m_CardRowScopeToken;
  rowId.mOid_Id = id.ToInteger(&rv);
  if (NS_SUCCEEDED(rv))
  {
    // Mork is being very naughty here. If the table does not have the oid, we
    // should be able to reuse it. To be on the safe side, however, we're going
    // to reference the store's reference count.
    mdb_count rowCount = 1;
    m_mdbStore->GetRowRefCount(m_mdbEnv, &rowId, &rowCount);
    if (rowCount == 0)
    {
      // So apparently, the row can have a count of 0 yet still exist (probably
      // meaning we haven't flushed it out of memory). In this case, we need to
      // get the row and cut its cells.
      rv = m_mdbStore->GetRow(m_mdbEnv, &rowId, getter_AddRefs(cardRow));
      if (NS_SUCCEEDED(rv) && cardRow)
        cardRow->CutAllColumns(m_mdbEnv);
      else
        rv = m_mdbStore->NewRowWithOid(m_mdbEnv, &rowId, getter_AddRefs(cardRow));
    }
  }

  // If we don't have a cardRow yet, just get one with any ol' id.
  if (!cardRow)
    rv = GetNewRow(getter_AddRefs(cardRow));

  if (NS_SUCCEEDED(rv) && cardRow)
  {
    AddAttributeColumnsToRow(aNewCard, cardRow);
    AddRecordKeyColumnToRow(cardRow);

    // we need to do this for dnd
    uint32_t key = 0;
    rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0);
    if (NS_SUCCEEDED(rv))
      aNewCard->SetPropertyAsUint32(kRecordKeyColumn, key);
    
    aNewCard->GetPropertyAsAUTF8String(kRowIDProperty, id);
    aNewCard->SetLocalId(id);

    nsCOMPtr<nsIAbDirectory> abDir = do_QueryReferent(m_dbDirectory);
    if (abDir)
      abDir->GetUuid(id);

    aNewCard->SetDirectoryId(id);

    nsresult merror = m_mdbPabTable->AddRow(m_mdbEnv, cardRow);
    NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);
  }
  else
    return rv;

  //  do notification
  if (aNotify)
  {
    NotifyCardEntryChange(AB_NotifyInserted, aNewCard, aParent);
  }
  return rv;
}

NS_IMETHODIMP nsAddrDatabase::CreateNewListCardAndAddToDB(nsIAbDirectory *aList, uint32_t listRowID, nsIAbCard *newCard, bool notify /* = FALSE */)
{
  if (!newCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
        return NS_ERROR_NULL_POINTER;

  nsIMdbRow* pListRow = nullptr;
  mdbOid listRowOid;
  listRowOid.mOid_Scope = m_ListRowScopeToken;
  listRowOid.mOid_Id = listRowID;
  nsresult rv = m_mdbStore->GetRow(m_mdbEnv, &listRowOid, &pListRow);
  NS_ENSURE_SUCCESS(rv,rv);

  if (!pListRow)
    return NS_OK;

  nsCOMPtr<nsIMutableArray> addressList;
  rv = aList->GetAddressLists(getter_AddRefs(addressList));
  NS_ENSURE_SUCCESS(rv,rv);

  uint32_t count;
  addressList->GetLength(&count);

  nsAutoString newEmail;
  rv = newCard->GetPrimaryEmail(newEmail);
  NS_ENSURE_SUCCESS(rv,rv);

  uint32_t i;
  for (i = 0; i < count; i++) {
    nsCOMPtr<nsIAbCard> currentCard = do_QueryElementAt(addressList, i, &rv);
    NS_ENSURE_SUCCESS(rv,rv);

    bool equals;
    rv = newCard->Equals(currentCard, &equals);
    NS_ENSURE_SUCCESS(rv,rv);

    if (equals) {
      // card is already in list, bail out.
      // this can happen when dropping a card on a mailing list from the directory that contains the mailing list
      return NS_OK;
    }

    nsAutoString currentEmail;
    rv = currentCard->GetPrimaryEmail(currentEmail);
    NS_ENSURE_SUCCESS(rv,rv);

    if (newEmail.Equals(currentEmail)) {
      // card is already in list, bail out
      // this can happen when dropping a card on a mailing list from another directory (not the one that contains the mailing list
      // or if you have multiple cards on a directory, with the same primary email address.
      return NS_OK;
    }
  }

  // start from 1
  uint32_t totalAddress = GetListAddressTotal(pListRow) + 1;
  SetListAddressTotal(pListRow, totalAddress);
  nsCOMPtr<nsIAbCard> pNewCard;
  rv = AddListCardColumnsToRow(newCard, pListRow, totalAddress, getter_AddRefs(pNewCard), true /* aInMailingList */, aList, nullptr);
  NS_ENSURE_SUCCESS(rv,rv);

  addressList->AppendElement(newCard, false);

  if (notify)
    NotifyCardEntryChange(AB_NotifyInserted, newCard, aList);

    return rv;
}

NS_IMETHODIMP nsAddrDatabase::AddListCardColumnsToRow
(nsIAbCard *aPCard, nsIMdbRow *aPListRow, uint32_t aPos, nsIAbCard** aPNewCard, bool aInMailingList, nsIAbDirectory *aParent, nsIAbDirectory *aRoot)
{
  if (!aPCard || !aPListRow || !m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult    err = NS_OK;
  nsString email;
  aPCard->GetPrimaryEmail(email);
  if (!email.IsEmpty())
  {
    nsIMdbRow    *pCardRow = nullptr;
    // Please DO NOT change the 3rd param of GetRowFromAttribute() call to
    // true (ie, case insensitive) without reading bugs #128535 and #121478.
    err = GetRowFromAttribute(kPriEmailProperty, NS_ConvertUTF16toUTF8(email),
                              false /* retain case */, &pCardRow, nullptr);
    bool cardWasAdded = false;
    if (NS_FAILED(err) || !pCardRow)
    {
      //New Email, then add a new row with this email
      err  = GetNewRow(&pCardRow);

      if (NS_SUCCEEDED(err) && pCardRow)
      {
        AddPrimaryEmail(pCardRow, NS_ConvertUTF16toUTF8(email).get());
        err = m_mdbPabTable->AddRow(m_mdbEnv, pCardRow);
        // Create a key for this row as well.
        if (NS_SUCCEEDED(err))
          AddRecordKeyColumnToRow(pCardRow);
      }

      cardWasAdded = true;
    }

    NS_ENSURE_TRUE(pCardRow, NS_ERROR_NULL_POINTER);

    nsString name;
    aPCard->GetDisplayName(name);
    if (!name.IsEmpty()) {
      AddDisplayName(pCardRow, NS_ConvertUTF16toUTF8(name).get());
      err = m_mdbPabTable->AddRow(m_mdbEnv, pCardRow);
    }

    nsCOMPtr<nsIAbCard> newCard;
    CreateABCard(pCardRow, 0, getter_AddRefs(newCard));
    NS_IF_ADDREF(*aPNewCard = newCard);

    if (cardWasAdded) {
      NotifyCardEntryChange(AB_NotifyInserted, newCard, aParent);
      if (aRoot)
        NotifyCardEntryChange(AB_NotifyInserted, newCard, aRoot);
    }
    else if (!aInMailingList) {
      nsresult rv;
      nsCOMPtr<nsIAddrDBListener> parentListener(do_QueryInterface(aParent, &rv));

      // Ensure the parent is in the listener list (and hence wants to be notified)
      if (NS_SUCCEEDED(rv) && m_ChangeListeners.Contains(parentListener))
        parentListener->OnCardEntryChange(AB_NotifyInserted, aPCard, aParent);
    }
    else {
      NotifyCardEntryChange(AB_NotifyPropertyChanged, aPCard, aParent);
    }

    //add a column with address row id to the list row
    mdb_token listAddressColumnToken;

    char columnStr[COLUMN_STR_MAX];
    PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, aPos);
    m_mdbStore->StringToToken(m_mdbEnv,  columnStr, &listAddressColumnToken);

    mdbOid outOid;

    if (NS_SUCCEEDED(pCardRow->GetOid(m_mdbEnv, &outOid)))
    {
      //save address row ID to the list row
      err = AddIntColumn(aPListRow, listAddressColumnToken, outOid.mOid_Id);
    }
    NS_RELEASE(pCardRow);

  }

  return NS_OK;
}

nsresult nsAddrDatabase::AddListAttributeColumnsToRow(nsIAbDirectory *list, nsIMdbRow *listRow, nsIAbDirectory *aParent)
{
    nsresult    err = NS_OK;

    if ((!list && !listRow) || !m_mdbEnv)
        return NS_ERROR_NULL_POINTER;

    mdbOid rowOid, tableOid;
    m_mdbPabTable->GetOid(m_mdbEnv, &tableOid);
    listRow->GetOid(m_mdbEnv, &rowOid);

    nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list,&err));
    if (NS_SUCCEEDED(err))
        dblist->SetDbRowID(rowOid.mOid_Id);

    // add the row to the singleton table.
    if (NS_SUCCEEDED(err) && listRow)
    {
        nsString unicodeStr;

        list->GetDirName(unicodeStr);
        if (!unicodeStr.IsEmpty())
            AddUnicodeToColumn(listRow, m_ListNameColumnToken, m_LowerListNameColumnToken, unicodeStr.get());

        list->GetListNickName(unicodeStr);
        AddListNickName(listRow, NS_ConvertUTF16toUTF8(unicodeStr).get());

        list->GetDescription(unicodeStr);
        AddListDescription(listRow, NS_ConvertUTF16toUTF8(unicodeStr).get());

        // XXX todo, this code has problems if you manually enter duplicate emails.
        nsCOMPtr<nsIMutableArray> pAddressLists;
        list->GetAddressLists(getter_AddRefs(pAddressLists));

        uint32_t count;
        pAddressLists->GetLength(&count);

        nsAutoString email;
        uint32_t i, total;
        total = 0;
        for (i = 0; i < count; i++)
        {
            nsCOMPtr<nsIAbCard> pCard(do_QueryElementAt(pAddressLists, i, &err));

            if (NS_FAILED(err))
                continue;

            pCard->GetPrimaryEmail(email);
            if (!email.IsEmpty())
                total++;
        }
        SetListAddressTotal(listRow, total);

        uint32_t pos;
        for (i = 0; i < count; i++)
        {
            nsCOMPtr<nsIAbCard> pCard(do_QueryElementAt(pAddressLists, i, &err));

            if (NS_FAILED(err))
                continue;

            bool listHasCard = false;
            err = list->HasCard(pCard, &listHasCard);

            // start from 1
            pos = i + 1;
            pCard->GetPrimaryEmail(email);
            if (!email.IsEmpty())
            {
                nsCOMPtr<nsIAbCard> pNewCard;
                err = AddListCardColumnsToRow(pCard, listRow, pos, getter_AddRefs(pNewCard), listHasCard, list, aParent);
                if (pNewCard)
                    pAddressLists->ReplaceElementAt(pNewCard, i, false);
            }
        }
    }
    return NS_OK;
}

uint32_t nsAddrDatabase::GetListAddressTotal(nsIMdbRow* listRow)
{
    uint32_t count = 0;
    GetIntColumn(listRow, m_ListTotalColumnToken, &count, 0);
    return count;
}

NS_IMETHODIMP nsAddrDatabase::SetListAddressTotal(nsIMdbRow* aListRow, uint32_t aTotal)
{
    return AddIntColumn(aListRow, m_ListTotalColumnToken, aTotal);
}

NS_IMETHODIMP nsAddrDatabase::FindRowByCard(nsIAbCard * aCard,nsIMdbRow **aRow)
{
    nsString primaryEmail;
    aCard->GetPrimaryEmail(primaryEmail);
    return GetRowForCharColumn(primaryEmail.get(), m_PriEmailColumnToken,
                               true, true, aRow, nullptr);
}

nsresult nsAddrDatabase::GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, nsIMdbRow** cardRow)
{
  if (!m_mdbStore || !listRow || !cardRow || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  mdb_token listAddressColumnToken;

  char columnStr[COLUMN_STR_MAX];
  PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
  m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);

  nsAutoString tempString;
  mdb_id rowID;
  nsresult err = GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
  NS_ENSURE_SUCCESS(err, err);

  return GetCardRowByRowID(rowID, cardRow);
}

NS_IMETHODIMP nsAddrDatabase::CreateMailListAndAddToDB(nsIAbDirectory *aNewList, bool aNotify /* = FALSE */, nsIAbDirectory *aParent)
{
    if (!aNewList || !m_mdbPabTable || !m_mdbEnv)
        return NS_ERROR_NULL_POINTER;

    nsIMdbRow *listRow;
    nsresult err = GetNewListRow(&listRow);

    if (NS_SUCCEEDED(err) && listRow)
    {
        AddListAttributeColumnsToRow(aNewList, listRow, aParent);
        AddRecordKeyColumnToRow(listRow);
        nsresult merror = m_mdbPabTable->AddRow(m_mdbEnv, listRow);
        NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE);

        nsCOMPtr<nsIAbCard> listCard;
        CreateABListCard(listRow, getter_AddRefs(listCard));
        NotifyCardEntryChange(AB_NotifyInserted, listCard, aParent);

        NS_RELEASE(listRow);
        return NS_OK;
    }

    return NS_ERROR_FAILURE;
}

void nsAddrDatabase::DeleteCardFromAllMailLists(mdb_id cardRowID)
{
  if (!m_mdbEnv)
    return;

    nsCOMPtr <nsIMdbTableRowCursor> rowCursor;
    m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));

    if (rowCursor)
    {
        nsCOMPtr <nsIMdbRow> pListRow;
        mdb_pos rowPos;
        do
        {
            nsresult err = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(pListRow), &rowPos);

            if (NS_SUCCEEDED(err) && pListRow)
            {
                mdbOid rowOid;

                if (NS_SUCCEEDED(pListRow->GetOid(m_mdbEnv, &rowOid)))
                {
                    if (IsListRowScopeToken(rowOid.mOid_Scope))
                        DeleteCardFromListRow(pListRow, cardRowID);
                }
            }
        } while (pListRow);
    }
}

NS_IMETHODIMP nsAddrDatabase::DeleteCard(nsIAbCard *aCard, bool aNotify, nsIAbDirectory *aParent)
{
  if (!aCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = NS_OK;
  bool bIsMailList = false;
  aCard->GetIsMailList(&bIsMailList);

  // get the right row
  nsIMdbRow* pCardRow = nullptr;
  mdbOid rowOid;

  rowOid.mOid_Scope = bIsMailList ? m_ListRowScopeToken : m_CardRowScopeToken;

  err = aCard->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
  NS_ENSURE_SUCCESS(err, err);

  err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, &pCardRow);
  NS_ENSURE_SUCCESS(err,err);
  if (!pCardRow)
    return NS_OK;

  // Reset the directory id
  aCard->SetDirectoryId(EmptyCString());

  // Add the deleted card to the deletedcards table
  nsCOMPtr <nsIMdbRow> cardRow;
  AddRowToDeletedCardsTable(aCard, getter_AddRefs(cardRow));
  err = DeleteRow(m_mdbPabTable, pCardRow);

  //delete the person card from all mailing list
  if (!bIsMailList)
    DeleteCardFromAllMailLists(rowOid.mOid_Id);

  if (NS_SUCCEEDED(err)) {
    if (aNotify)
      NotifyCardEntryChange(AB_NotifyDeleted, aCard, aParent);
  }
  else
    DeleteRowFromDeletedCardsTable(cardRow);

  NS_RELEASE(pCardRow);
  return NS_OK;
}

nsresult nsAddrDatabase::DeleteCardFromListRow(nsIMdbRow* pListRow, mdb_id cardRowID)
{
  NS_ENSURE_ARG_POINTER(pListRow);
  if (!m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = NS_OK;

  uint32_t totalAddress = GetListAddressTotal(pListRow);

  uint32_t pos;
  for (pos = 1; pos <= totalAddress; pos++)
  {
    mdb_token listAddressColumnToken;
    mdb_id rowID;

    char columnStr[COLUMN_STR_MAX];
    PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
    m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);

    err = GetIntColumn(pListRow, listAddressColumnToken, (uint32_t*)&rowID, 0);

    if (cardRowID == rowID)
    {
      if (pos == totalAddress)
        err = pListRow->CutColumn(m_mdbEnv, listAddressColumnToken);
      else
      {
        //replace the deleted one with the last one and delete the last one
        mdb_id lastRowID;
        mdb_token lastAddressColumnToken;
        PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, totalAddress);
        m_mdbStore->StringToToken(m_mdbEnv, columnStr, &lastAddressColumnToken);

        err = GetIntColumn(pListRow, lastAddressColumnToken, (uint32_t*)&lastRowID, 0);
        NS_ENSURE_SUCCESS(err, err);

        err = AddIntColumn(pListRow, listAddressColumnToken, lastRowID);
        NS_ENSURE_SUCCESS(err, err);

        err = pListRow->CutColumn(m_mdbEnv, lastAddressColumnToken);
        NS_ENSURE_SUCCESS(err, err);
      }

      // Reset total count after the card has been deleted.
      SetListAddressTotal(pListRow, totalAddress-1);
      break;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::DeleteCardFromMailList(nsIAbDirectory *mailList, nsIAbCard *card, bool aNotify)
{
  if (!card || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = NS_OK;

  // get the right row
  nsIMdbRow* pListRow = nullptr;
  mdbOid listRowOid;
  listRowOid.mOid_Scope = m_ListRowScopeToken;

  nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList,&err));
  NS_ENSURE_SUCCESS(err, err);

  dbmailList->GetDbRowID((uint32_t*)&listRowOid.mOid_Id);

  err = m_mdbStore->GetRow(m_mdbEnv, &listRowOid, &pListRow);
  NS_ENSURE_SUCCESS(err,err);
  if (!pListRow)
    return NS_OK;

  uint32_t cardRowID;

  err = card->GetPropertyAsUint32(kRowIDProperty, &cardRowID);
  if (NS_FAILED(err))
    return NS_ERROR_NULL_POINTER;

  err = DeleteCardFromListRow(pListRow, cardRowID);
  if (NS_SUCCEEDED(err) && aNotify) {
    NotifyCardEntryChange(AB_NotifyDeleted, card, mailList);
  }
  NS_RELEASE(pListRow);
  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::SetCardValue(nsIAbCard *card, const char *name, const char16_t *value, bool notify)
{
  NS_ENSURE_ARG_POINTER(card);
  NS_ENSURE_ARG_POINTER(name);
  NS_ENSURE_ARG_POINTER(value);
  if (!m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult rv = NS_OK;

  nsCOMPtr <nsIMdbRow> cardRow;
  mdbOid rowOid;
  rowOid.mOid_Scope = m_CardRowScopeToken;

  // it might be that the caller always has a nsAbMDBCard
  rv = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
  NS_ENSURE_SUCCESS(rv, rv);

  if (!cardRow)
    return NS_OK;

  mdb_token token;
  rv = m_mdbStore->StringToToken(m_mdbEnv, name, &token);
  NS_ENSURE_SUCCESS(rv, rv);

  return AddCharStringColumn(cardRow, token, NS_ConvertUTF16toUTF8(value).get());
}

NS_IMETHODIMP nsAddrDatabase::GetCardValue(nsIAbCard *card, const char *name, char16_t **value)
{
  if (!m_mdbStore || !card || !name || !value || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult rv = NS_OK;

  nsCOMPtr <nsIMdbRow> cardRow;
  mdbOid rowOid;
  rowOid.mOid_Scope = m_CardRowScopeToken;

  // it might be that the caller always has a nsAbMDBCard
  rv = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
  NS_ENSURE_SUCCESS(rv, rv);

  if (!cardRow) {
    *value = nullptr;
    // this can happen when adding cards when editing a mailing list
    return NS_OK;
  }

  mdb_token token;
  m_mdbStore->StringToToken(m_mdbEnv, name, &token);

  // XXX fix me
  // avoid extra copying and allocations (did dmb already do this on the trunk?)
  nsAutoString tempString;
  rv = GetStringColumn(cardRow, token, tempString);
  if (NS_FAILED(rv)) {
    // not all cards are going this column
    *value = nullptr;
    return NS_OK;
  }

  *value = NS_strdup(tempString.get());
  if (!*value)
    return NS_ERROR_OUT_OF_MEMORY;
  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::GetDeletedCardList(nsIArray **aResult)
{
  if (!m_mdbEnv || !aResult)
    return NS_ERROR_NULL_POINTER;

  *aResult = nullptr;

  nsresult rv;
  nsCOMPtr<nsIMutableArray> resultCardArray =
    do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  // make sure the member is set properly
  InitDeletedCardsTable(false);
  if (m_mdbDeletedCardsTable)
  {
    nsCOMPtr<nsIMdbTableRowCursor>      rowCursor;
    mdb_pos                             rowPos;
    bool                                done = false;
    nsCOMPtr<nsIMdbRow>                 currentRow;

    m_mdbDeletedCardsTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));
    if (!rowCursor)
        return NS_ERROR_FAILURE;
    while (!done)
    {
      rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
      if (currentRow && NS_SUCCEEDED(rv))
      {
        mdbOid rowOid;
        if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)))
        {
          nsCOMPtr<nsIAbCard>    card;
          rv = CreateCardFromDeletedCardsTable(currentRow, 0, getter_AddRefs(card));
          if (NS_SUCCEEDED(rv)) {
            resultCardArray->AppendElement(card, false);
          }
        }
      }
      else
          done = true;
    }
  }

  NS_IF_ADDREF(*aResult = resultCardArray);

  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::GetDeletedCardCount(uint32_t *aCount)
{
    // initialize count first
    *aCount = 0;
    InitDeletedCardsTable(false);
    if (m_mdbDeletedCardsTable)
      return m_mdbDeletedCardsTable->GetCount(m_mdbEnv, aCount);
    return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::PurgeDeletedCardTable()
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

    if (m_mdbDeletedCardsTable) {
        mdb_count cardCount=0;
        // if not too many cards let it be
        m_mdbDeletedCardsTable->GetCount(m_mdbEnv, &cardCount);
        if(cardCount < PURGE_CUTOFF_COUNT)
            return NS_OK;
        uint32_t purgeTimeInSec;
        PRTime2Seconds(PR_Now(), &purgeTimeInSec);
        purgeTimeInSec -= (182*24*60*60);  // six months in seconds
        nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
        nsresult rv = m_mdbDeletedCardsTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor));
        while(NS_SUCCEEDED(rv)) {
            nsCOMPtr<nsIMdbRow> currentRow;
            mdb_pos rowPos;
            rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
            if(currentRow) {
                uint32_t deletedTimeStamp = 0;
                GetIntColumn(currentRow, m_LastModDateColumnToken, &deletedTimeStamp, 0);
                // if record was deleted more than six months earlier, purge it
                if(deletedTimeStamp && (deletedTimeStamp < purgeTimeInSec)) {
                    if(NS_SUCCEEDED(currentRow->CutAllColumns(m_mdbEnv)))
                        m_mdbDeletedCardsTable->CutRow(m_mdbEnv, currentRow);
                }
                else
                    // since the ordering in Mork is maintained and thus
                    // the cards added later appear on the top when retrieved
                    break;
            }
            else
                break; // no more row
        }
    }

    return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::EditCard(nsIAbCard *aCard, bool aNotify, nsIAbDirectory *aParent)
{
  // XXX make sure this isn't getting called when we're just editing one or two well known fields
  if (!aCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = NS_OK;

  nsCOMPtr <nsIMdbRow> cardRow;
  mdbOid rowOid;
  rowOid.mOid_Scope = m_CardRowScopeToken;

  uint32_t nowInSeconds;
  PRTime now = PR_Now();
  PRTime2Seconds(now, &nowInSeconds);
  aCard->SetPropertyAsUint32(kLastModifiedDateProperty, nowInSeconds);
  err = aCard->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
  NS_ENSURE_SUCCESS(err, err);

  err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow));
  NS_ENSURE_SUCCESS(err, err);

  if (!cardRow)
    return NS_OK;

  err = AddAttributeColumnsToRow(aCard, cardRow);
  NS_ENSURE_SUCCESS(err, err);

  if (aNotify)
    NotifyCardEntryChange(AB_NotifyPropertyChanged, aCard, aParent);

  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::ContainsCard(nsIAbCard *card, bool *hasCard)
{
    if (!card || !m_mdbPabTable || !m_mdbEnv)
        return NS_ERROR_NULL_POINTER;

    nsresult err = NS_OK;
    mdb_bool hasOid;
    mdbOid rowOid;
    bool bIsMailList;

    card->GetIsMailList(&bIsMailList);

    if (bIsMailList)
        rowOid.mOid_Scope = m_ListRowScopeToken;
    else
        rowOid.mOid_Scope = m_CardRowScopeToken;

    err = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id);
    NS_ENSURE_SUCCESS(err, err);

    err = m_mdbPabTable->HasOid(m_mdbEnv, &rowOid, &hasOid);
    if (NS_SUCCEEDED(err))
    {
        *hasCard = hasOid;
    }

    return err;
}

NS_IMETHODIMP nsAddrDatabase::DeleteMailList(nsIAbDirectory *aMailList,
                                             nsIAbDirectory *aParent)
{
  if (!aMailList || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = NS_OK;

  // get the row
  nsCOMPtr<nsIMdbRow> pListRow;
  mdbOid rowOid;
  rowOid.mOid_Scope = m_ListRowScopeToken;

  nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(aMailList, &err));
  NS_ENSURE_SUCCESS(err, err);
  dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);

  err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(pListRow));
  NS_ENSURE_SUCCESS(err,err);

  if (!pListRow)
    return NS_OK;

  nsCOMPtr<nsIAbCard> card;
  err = CreateABListCard(pListRow, getter_AddRefs(card));
  NS_ENSURE_SUCCESS(err, err);

  err = DeleteRow(m_mdbPabTable, pListRow);

  if (NS_SUCCEEDED(err) && aParent)
    NotifyCardEntryChange(AB_NotifyDeleted, card, aParent);

  return err;
}

NS_IMETHODIMP nsAddrDatabase::EditMailList(nsIAbDirectory *mailList, nsIAbCard *listCard, bool notify)
{
  if (!mailList || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = NS_OK;

  nsIMdbRow* pListRow = nullptr;
  mdbOid rowOid;
  rowOid.mOid_Scope = m_ListRowScopeToken;

  nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList, &err));
  NS_ENSURE_SUCCESS(err, err);
  dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);

  err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, &pListRow);
  NS_ENSURE_SUCCESS(err, err);

  if (!pListRow)
    return NS_OK;

  err = AddListAttributeColumnsToRow(mailList, pListRow, mailList);
  NS_ENSURE_SUCCESS(err, err);

  if (notify)
  {
    NotifyListEntryChange(AB_NotifyPropertyChanged, mailList);

    if (listCard)
    {
      NotifyCardEntryChange(AB_NotifyPropertyChanged, listCard, mailList);
    }
  }

  NS_RELEASE(pListRow);
  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::ContainsMailList(nsIAbDirectory *mailList, bool *hasList)
{
    if (!mailList || !m_mdbPabTable || !m_mdbEnv)
        return NS_ERROR_NULL_POINTER;

    nsresult err = NS_OK;
    mdb_bool hasOid;
    mdbOid rowOid;

    rowOid.mOid_Scope = m_ListRowScopeToken;

    nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList,&err));
    NS_ENSURE_SUCCESS(err, err);
    dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id);

    err = m_mdbPabTable->HasOid(m_mdbEnv, &rowOid, &hasOid);
    if (NS_SUCCEEDED(err))
        *hasList = hasOid;

    return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP nsAddrDatabase::GetNewRow(nsIMdbRow * *newRow)
{
  if (!m_mdbStore || !newRow || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  return m_mdbStore->NewRow(m_mdbEnv, m_CardRowScopeToken, newRow);
}

NS_IMETHODIMP nsAddrDatabase::GetNewListRow(nsIMdbRow * *newRow)
{
  if (!m_mdbStore || !newRow || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  return m_mdbStore->NewRow(m_mdbEnv, m_ListRowScopeToken, newRow);
}

NS_IMETHODIMP nsAddrDatabase::AddCardRowToDB(nsIMdbRow *newRow)
{
  if (m_mdbPabTable && m_mdbEnv)
  {
    if (NS_SUCCEEDED(m_mdbPabTable->AddRow(m_mdbEnv, newRow)))
    {
      AddRecordKeyColumnToRow(newRow);
      return NS_OK;
    }
  }

  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP nsAddrDatabase::AddLdifListMember(nsIMdbRow* listRow, const char* value)
{
  if (!m_mdbStore || !listRow || !value || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  uint32_t total = GetListAddressTotal(listRow);
  //add member
  nsAutoCString valueString(value);
  nsAutoCString email;
  int32_t emailPos = valueString.Find("mail=");
  emailPos += strlen("mail=");
  email = Substring(valueString, emailPos);
  nsCOMPtr <nsIMdbRow> cardRow;
  // Please DO NOT change the 3rd param of GetRowFromAttribute() call to
  // true (ie, case insensitive) without reading bugs #128535 and #121478.
  nsresult rv = GetRowFromAttribute(kPriEmailProperty, email, false /* retain case */,
                                    getter_AddRefs(cardRow), nullptr);
  if (NS_SUCCEEDED(rv) && cardRow)
  {
    mdbOid outOid;
    mdb_id rowID = 0;
    if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
      rowID = outOid.mOid_Id;

    // start from 1
    total += 1;
    mdb_token listAddressColumnToken;
    char columnStr[COLUMN_STR_MAX];
    PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, total);
    m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);

    rv = AddIntColumn(listRow, listAddressColumnToken, rowID);
    NS_ENSURE_SUCCESS(rv, rv);

    SetListAddressTotal(listRow, total);
  }
  return NS_OK;
}


void nsAddrDatabase::GetCharStringYarn(char* str, struct mdbYarn* strYarn)
{
    strYarn->mYarn_Grow = nullptr;
    strYarn->mYarn_Buf = str;
    strYarn->mYarn_Size = PL_strlen((const char *) strYarn->mYarn_Buf) + 1;
    strYarn->mYarn_Fill = strYarn->mYarn_Size - 1;
    strYarn->mYarn_Form = 0;
}

void nsAddrDatabase::GetStringYarn(const nsAString & aStr, struct mdbYarn* strYarn)
{
    strYarn->mYarn_Buf = ToNewUTF8String(aStr);
    strYarn->mYarn_Size = PL_strlen((const char *) strYarn->mYarn_Buf) + 1;
    strYarn->mYarn_Fill = strYarn->mYarn_Size - 1;
    strYarn->mYarn_Form = 0;
}

void nsAddrDatabase::GetIntYarn(uint32_t nValue, struct mdbYarn* intYarn)
{
    intYarn->mYarn_Fill = intYarn->mYarn_Size;
    intYarn->mYarn_Form = 0;
    intYarn->mYarn_Grow = nullptr;

    PR_snprintf((char*)intYarn->mYarn_Buf, intYarn->mYarn_Size, "%lx", nValue);
    intYarn->mYarn_Fill = PL_strlen((const char *) intYarn->mYarn_Buf);
}

nsresult nsAddrDatabase::AddCharStringColumn(nsIMdbRow* cardRow, mdb_column inColumn, const char* str)
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  struct mdbYarn yarn;

  GetCharStringYarn((char *) str, &yarn);
  nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);

  return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
}

nsresult nsAddrDatabase::AddStringColumn(nsIMdbRow* aCardRow, mdb_column aInColumn, const nsAString & aStr)
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  struct mdbYarn yarn;

  GetStringYarn(aStr, &yarn);
  nsresult err = aCardRow->AddColumn(m_mdbEnv, aInColumn, &yarn);

  return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
}

nsresult nsAddrDatabase::AddIntColumn(nsIMdbRow* cardRow, mdb_column inColumn, uint32_t nValue)
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  struct mdbYarn yarn;
  char    yarnBuf[100];

  yarn.mYarn_Buf = (void *) yarnBuf;
  yarn.mYarn_Size = sizeof(yarnBuf);
  GetIntYarn(nValue, &yarn);
  nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);

  return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
}

nsresult nsAddrDatabase::AddBoolColumn(nsIMdbRow* cardRow, mdb_column inColumn, bool bValue)
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  struct mdbYarn yarn;
  char    yarnBuf[100];

  yarn.mYarn_Buf = (void *) yarnBuf;
  yarn.mYarn_Size = sizeof(yarnBuf);

  GetIntYarn(bValue ? 1 : 0, &yarn);

  nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn);

  return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
}

nsresult nsAddrDatabase::GetStringColumn(nsIMdbRow *cardRow, mdb_token outToken, nsString& str)
{
  nsresult    err = NS_ERROR_NULL_POINTER;
  nsIMdbCell    *cardCell;

  if (cardRow && m_mdbEnv)
  {
    err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
    if (NS_SUCCEEDED(err) && cardCell)
    {
      struct mdbYarn yarn;
      cardCell->AliasYarn(m_mdbEnv, &yarn);
      NS_ConvertUTF8toUTF16 uniStr((const char*) yarn.mYarn_Buf, yarn.mYarn_Fill);
      if (!uniStr.IsEmpty())
        str.Assign(uniStr);
      else
        err = NS_ERROR_FAILURE;
      cardCell->Release(); // always release ref
    }
    else
      err = NS_ERROR_FAILURE;
  }
  return err;
}

void nsAddrDatabase::YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult)
{
    uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill);
    *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars);
}

nsresult nsAddrDatabase::GetIntColumn
(nsIMdbRow *cardRow, mdb_token outToken, uint32_t* pValue, uint32_t defaultValue)
{
    nsresult    err = NS_ERROR_NULL_POINTER;
    nsIMdbCell    *cardCell;

    if (pValue)
        *pValue = defaultValue;
    if (cardRow && m_mdbEnv)
    {
        err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
        if (NS_SUCCEEDED(err) && cardCell)
        {
            struct mdbYarn yarn;
            cardCell->AliasYarn(m_mdbEnv, &yarn);
            YarnToUInt32(&yarn, pValue);
            cardCell->Release();
        }
        else
            err = NS_ERROR_FAILURE;
    }
    return err;
}

nsresult nsAddrDatabase::GetBoolColumn(nsIMdbRow *cardRow, mdb_token outToken, bool* pValue)
{
  NS_ENSURE_ARG_POINTER(pValue);

    nsresult    err = NS_ERROR_NULL_POINTER;
    nsIMdbCell    *cardCell;
    uint32_t nValue = 0;

    if (cardRow && m_mdbEnv)
    {
        err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
        if (NS_SUCCEEDED(err) && cardCell)
        {
            struct mdbYarn yarn;
            cardCell->AliasYarn(m_mdbEnv, &yarn);
            YarnToUInt32(&yarn, &nValue);
            cardCell->Release();
        }
        else
            err = NS_ERROR_FAILURE;
    }

    *pValue = nValue ? true : false;
    return err;
}

/*  value is UTF8 string */
NS_IMETHODIMP nsAddrDatabase::AddPrimaryEmail(nsIMdbRow *aRow, const char *aValue)
{
  NS_ENSURE_ARG_POINTER(aValue);

  nsresult rv = AddCharStringColumn(aRow, m_PriEmailColumnToken, aValue);
  NS_ENSURE_SUCCESS(rv,rv);

  rv = AddLowercaseColumn(aRow, m_LowerPriEmailColumnToken, aValue);
  NS_ENSURE_SUCCESS(rv,rv);
  return rv;
}

/*  value is UTF8 string */
NS_IMETHODIMP nsAddrDatabase::Add2ndEmail(nsIMdbRow *aRow, const char *aValue)
{
  NS_ENSURE_ARG_POINTER(aValue);

  nsresult rv = AddCharStringColumn(aRow, m_2ndEmailColumnToken, aValue);
  NS_ENSURE_SUCCESS(rv,rv);

  rv = AddLowercaseColumn(aRow, m_Lower2ndEmailColumnToken, aValue);
  NS_ENSURE_SUCCESS(rv,rv);
  return rv;
}

/*  value is UTF8 string */
NS_IMETHODIMP nsAddrDatabase::AddListName(nsIMdbRow *aRow, const char *aValue)
{
  NS_ENSURE_ARG_POINTER(aValue);

  nsresult rv = AddCharStringColumn(aRow, m_ListNameColumnToken, aValue);
  NS_ENSURE_SUCCESS(rv,rv);

  rv = AddLowercaseColumn(aRow, m_LowerListNameColumnToken, aValue);
  NS_ENSURE_SUCCESS(rv,rv);
  return rv;
}

/*
columnValue is UTF8 string, need to convert back to lowercase unicode then
back to UTF8 string
*/
nsresult nsAddrDatabase::AddLowercaseColumn
(nsIMdbRow * row, mdb_token columnToken, const char* columnValue)
{
  nsresult rv = NS_OK;
  if (columnValue)
  {
    NS_ConvertUTF8toUTF16 newUnicodeString(columnValue);
    ToLowerCase(newUnicodeString);
    rv = AddCharStringColumn(row, columnToken, NS_ConvertUTF16toUTF8(newUnicodeString).get());
  }
  return rv;
}

NS_IMETHODIMP nsAddrDatabase::InitCardFromRow(nsIAbCard *newCard, nsIMdbRow* cardRow)
{
  nsresult rv = NS_OK;
  if (!newCard || !cardRow || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsCOMPtr<nsIMdbRowCellCursor> cursor;
  nsCOMPtr<nsIMdbCell> cell;

  rv = cardRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(cursor));
  NS_ENSURE_SUCCESS(rv, rv);

  mdb_column columnNumber;
  char columnName[100];
  struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr};
  struct mdbYarn cellYarn;

  do
  {
    rv = cursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &columnNumber, nullptr);
    NS_ENSURE_SUCCESS(rv, rv);

    if (!cell)
      break;

    // Get the value of the cell
    cell->AliasYarn(m_mdbEnv, &cellYarn);
    NS_ConvertUTF8toUTF16 value(static_cast<const char*>(cellYarn.mYarn_Buf),
        cellYarn.mYarn_Fill);

    if (!value.IsEmpty())
    {
      // Get the column of the cell
      // Mork makes this so hard...
      rv = m_mdbStore->TokenToString(m_mdbEnv, columnNumber, &colYarn);
      NS_ENSURE_SUCCESS(rv, rv);

      char *name = PL_strndup(static_cast<char *>(colYarn.mYarn_Buf),
          colYarn.mYarn_Fill);
      newCard->SetPropertyAsAString(name, value);
      PL_strfree(name);
    }
  } while (true);
 
  uint32_t key = 0;
  rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0);
  if (NS_SUCCEEDED(rv))
    newCard->SetPropertyAsUint32(kRecordKeyColumn, key);

  return NS_OK;
}

nsresult nsAddrDatabase::GetListCardFromDB(nsIAbCard *listCard, nsIMdbRow* listRow)
{
    nsresult    err = NS_OK;
    if (!listCard || !listRow)
        return NS_ERROR_NULL_POINTER;

    nsAutoString tempString;

    err = GetStringColumn(listRow, m_ListNameColumnToken, tempString);
    if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
    {
        listCard->SetDisplayName(tempString);
        listCard->SetLastName(tempString);
    }
    err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString);
    if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
    {
        listCard->SetPropertyAsAString(kNicknameProperty, tempString);
    }
    err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString);
    if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
    {
        listCard->SetPropertyAsAString(kNotesProperty, tempString);
    }
    uint32_t key = 0;
    err = GetIntColumn(listRow, m_RecordKeyColumnToken, &key, 0);
    if (NS_SUCCEEDED(err))
      listCard->SetPropertyAsUint32(kRecordKeyColumn, key);
    return err;
}

nsresult nsAddrDatabase::GetListFromDB(nsIAbDirectory *newList, nsIMdbRow* listRow)
{
  nsresult    err = NS_OK;
  if (!newList || !listRow || !m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsAutoString tempString;

  err = GetStringColumn(listRow, m_ListNameColumnToken, tempString);
  if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
  {
    newList->SetDirName(tempString);
  }
  err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString);
  if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
  {
    newList->SetListNickName(tempString);
  }
  err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString);
  if (NS_SUCCEEDED(err) && !tempString.IsEmpty())
  {
    newList->SetDescription(tempString);
  }

  nsCOMPtr<nsIAbMDBDirectory> dbnewList(do_QueryInterface(newList, &err));
  NS_ENSURE_SUCCESS(err, err);

  uint32_t totalAddress = GetListAddressTotal(listRow);
  uint32_t pos;
  for (pos = 1; pos <= totalAddress; ++pos)
  {
    mdb_token listAddressColumnToken;
    mdb_id rowID;

    char columnStr[COLUMN_STR_MAX];
    PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
    m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);

    nsCOMPtr <nsIMdbRow> cardRow;
    err = GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
    NS_ENSURE_SUCCESS(err, err);
    err = GetCardRowByRowID(rowID, getter_AddRefs(cardRow));
    NS_ENSURE_SUCCESS(err, err);

    if (cardRow)
    {
      nsCOMPtr<nsIAbCard> card;
      err = CreateABCard(cardRow, 0, getter_AddRefs(card));

      if(NS_SUCCEEDED(err))
        dbnewList->AddAddressToList(card);
    }
//        NS_IF_ADDREF(card);
  }

  return err;
}

class nsAddrDBEnumerator : public nsISimpleEnumerator, public nsIAddrDBListener
{
public:
    NS_DECL_ISUPPORTS

    // nsISimpleEnumerator methods:
    NS_DECL_NSISIMPLEENUMERATOR
    NS_DECL_NSIADDRDBLISTENER
    // nsAddrDBEnumerator methods:

    nsAddrDBEnumerator(nsAddrDatabase* aDb);
    void Clear();
protected:
    virtual ~nsAddrDBEnumerator();
    nsRefPtr<nsAddrDatabase> mDb;
    nsIMdbTable *mDbTable;
    nsCOMPtr<nsIMdbTableRowCursor> mRowCursor;
    nsCOMPtr<nsIMdbRow> mCurrentRow;
    mdb_pos mRowPos;
};

nsAddrDBEnumerator::nsAddrDBEnumerator(nsAddrDatabase* aDb)
    : mDb(aDb),
      mDbTable(aDb->GetPabTable()),
      mRowPos(-1)
{
  if (aDb)
    aDb->AddListener(this);
}

nsAddrDBEnumerator::~nsAddrDBEnumerator()
{
  Clear();
}

void nsAddrDBEnumerator::Clear()
{
  mRowCursor = nullptr;
  mCurrentRow = nullptr;
  mDbTable = nullptr;
  if (mDb)
    mDb->RemoveListener(this);
}

NS_IMPL_ISUPPORTS(nsAddrDBEnumerator, nsISimpleEnumerator, nsIAddrDBListener)

NS_IMETHODIMP
nsAddrDBEnumerator::HasMoreElements(bool *aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
    *aResult = false;

    if (!mDbTable || !mDb->GetEnv())
    {
        return NS_ERROR_NULL_POINTER;
    }

    nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
    mDbTable->GetTableRowCursor(mDb->GetEnv(), mRowPos,
                                getter_AddRefs(rowCursor));
    NS_ENSURE_TRUE(rowCursor, NS_ERROR_FAILURE);

    mdbOid rowOid;
    rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr);
    while (rowOid.mOid_Id != (mdb_id)-1)
    {
        if (mDb->IsListRowScopeToken(rowOid.mOid_Scope) ||
            mDb->IsCardRowScopeToken(rowOid.mOid_Scope))
        {
            *aResult = true;

            return NS_OK;
        }

        if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope))
        {
            return NS_ERROR_FAILURE;
        }

        rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr);
    }

    return NS_OK;
}

NS_IMETHODIMP
nsAddrDBEnumerator::GetNext(nsISupports **aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

    *aResult = nullptr;

    if (!mDbTable || !mDb->GetEnv())
    {
        return NS_ERROR_NULL_POINTER;
    }

    if (!mRowCursor)
    {
         mDbTable->GetTableRowCursor(mDb->GetEnv(), -1,
                                     getter_AddRefs(mRowCursor));
         NS_ENSURE_TRUE(mRowCursor, NS_ERROR_FAILURE);
    }

    nsCOMPtr<nsIAbCard> resultCard;
    mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), &mRowPos);
    while (mCurrentRow)
    {
        mdbOid rowOid;
        if (NS_SUCCEEDED(mCurrentRow->GetOid(mDb->GetEnv(), &rowOid)))
        {
            nsresult rv;
            if (mDb->IsListRowScopeToken(rowOid.mOid_Scope))
            {
                rv = mDb->CreateABListCard(mCurrentRow,
                                           getter_AddRefs(resultCard));
                NS_ENSURE_SUCCESS(rv, rv);
            }
            else if (mDb->IsCardRowScopeToken(rowOid.mOid_Scope))
            {
                rv = mDb->CreateABCard(mCurrentRow, 0,
                                       getter_AddRefs(resultCard));
                NS_ENSURE_SUCCESS(rv, rv);
            }
            else if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope))
            {
                return NS_ERROR_FAILURE;
            }

            if (resultCard)
            {
                return CallQueryInterface(resultCard, aResult);
            }
        }

        mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow),
                            &mRowPos);
    }

    return NS_ERROR_FAILURE;
}

NS_IMETHODIMP nsAddrDBEnumerator::OnCardAttribChange(uint32_t abCode)
{
  return NS_OK;
}

/* void onCardEntryChange (in unsigned long aAbCode, in nsIAbCard aCard, in nsIAbDirectory aParent); */
NS_IMETHODIMP nsAddrDBEnumerator::OnCardEntryChange(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent)
{
  return NS_OK;
}

/* void onListEntryChange (in unsigned long abCode, in nsIAbDirectory list); */
NS_IMETHODIMP nsAddrDBEnumerator::OnListEntryChange(uint32_t abCode, nsIAbDirectory *list)
{
  return NS_OK;
}

/* void onAnnouncerGoingAway (); */
NS_IMETHODIMP nsAddrDBEnumerator::OnAnnouncerGoingAway()
{
  Clear();
  return NS_OK;
}

class nsListAddressEnumerator MOZ_FINAL : public nsISimpleEnumerator
{
public:
    NS_DECL_ISUPPORTS

    // nsISimpleEnumerator methods:
    NS_DECL_NSISIMPLEENUMERATOR

    // nsListAddressEnumerator methods:

    nsListAddressEnumerator(nsAddrDatabase* aDb, mdb_id aRowID);

protected:
    ~nsListAddressEnumerator() {}
    nsRefPtr<nsAddrDatabase> mDb;
    nsIMdbTable *mDbTable;
    nsCOMPtr<nsIMdbRow> mListRow;
    mdb_id mListRowID;
    uint32_t mAddressTotal;
    uint16_t mAddressPos;
};

nsListAddressEnumerator::nsListAddressEnumerator(nsAddrDatabase* aDb,
                                                 mdb_id aRowID)
    : mDb(aDb),
      mDbTable(aDb->GetPabTable()),
      mListRowID(aRowID),
      mAddressPos(0)
{
    mDb->GetListRowByRowID(mListRowID, getter_AddRefs(mListRow));
    mAddressTotal = aDb->GetListAddressTotal(mListRow);
}

NS_IMPL_ISUPPORTS(nsListAddressEnumerator, nsISimpleEnumerator)

NS_IMETHODIMP
nsListAddressEnumerator::HasMoreElements(bool *aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  *aResult = false;

  if (!mDbTable || !mDb->GetEnv())
  {
    return NS_ERROR_NULL_POINTER;
  }

  // In some cases it is possible that GetAddressRowByPos returns success,
  // but currentRow is null. This is typically due to the fact that a card
  // has been deleted from the parent and not the list. Whilst we have fixed
  // that there are still a few dbs around there that we need to support
  // correctly. Therefore, whilst processing lists ensure that we don't return
  // false if the only thing stopping us is a blank row, just skip it and try
  // the next one.
  while (mAddressPos < mAddressTotal)
  {
    nsCOMPtr<nsIMdbRow> currentRow;
    nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos + 1,
                                          getter_AddRefs(currentRow));
    NS_ENSURE_SUCCESS(rv, rv);

    if (currentRow)
    {
      *aResult = true;
      break;
    }

    ++mAddressPos;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsListAddressEnumerator::GetNext(nsISupports **aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

    *aResult = nullptr;

    if (!mDbTable || !mDb->GetEnv())
    {
        return NS_ERROR_NULL_POINTER;
    }

    if (++mAddressPos <= mAddressTotal)
    {
        nsCOMPtr<nsIMdbRow> currentRow;
        nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos,
                                              getter_AddRefs(currentRow));
        NS_ENSURE_SUCCESS(rv, rv);

        nsCOMPtr<nsIAbCard> resultCard;
        rv = mDb->CreateABCard(currentRow, mListRowID,
                               getter_AddRefs(resultCard));
        NS_ENSURE_SUCCESS(rv, rv);

        return CallQueryInterface(resultCard, aResult);
    }

    return NS_ERROR_FAILURE;
}

////////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP nsAddrDatabase::EnumerateCards(nsIAbDirectory *directory, nsISimpleEnumerator **result)
{
    nsAddrDBEnumerator* e = new nsAddrDBEnumerator(this);
    m_dbDirectory = do_GetWeakReference(directory);
    if (!e)
        return NS_ERROR_OUT_OF_MEMORY;
    NS_ADDREF(e);
    *result = e;
    return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::GetMailingListsFromDB(nsIAbDirectory *parentDir)
{
    nsCOMPtr<nsIAbDirectory>    resultList;
    nsIMdbTableRowCursor*       rowCursor = nullptr;
    nsCOMPtr<nsIMdbRow> currentRow;
     mdb_pos                        rowPos;
    bool                        done = false;

  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  m_dbDirectory = do_GetWeakReference(parentDir);

    nsIMdbTable*                dbTable = GetPabTable();

    if (!dbTable)
        return NS_ERROR_FAILURE;

    dbTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);
    if (!rowCursor)
        return NS_ERROR_FAILURE;

    while (!done)
    {
                nsresult rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
        if (currentRow && NS_SUCCEEDED(rv))
        {
            mdbOid rowOid;

            if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)))
            {
                if (IsListRowScopeToken(rowOid.mOid_Scope))
                    rv = CreateABList(currentRow, getter_AddRefs(resultList));
            }
        }
        else
            done = true;
    }
        NS_IF_RELEASE(rowCursor);
    return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::EnumerateListAddresses(nsIAbDirectory *directory, nsISimpleEnumerator **result)
{
    nsresult rv = NS_OK;
    mdb_id rowID;

    nsCOMPtr<nsIAbMDBDirectory> dbdirectory(do_QueryInterface(directory,&rv));

    if(NS_SUCCEEDED(rv))
    {
    dbdirectory->GetDbRowID((uint32_t*)&rowID);

    nsListAddressEnumerator* e = new nsListAddressEnumerator(this, rowID);
    m_dbDirectory = do_GetWeakReference(directory);
    if (!e)
        return NS_ERROR_OUT_OF_MEMORY;
    NS_ADDREF(e);
    *result = e;
    }
    return rv;
}

nsresult nsAddrDatabase::CreateCardFromDeletedCardsTable(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
{
  if (!cardRow || !m_mdbEnv || !result)
    return NS_ERROR_NULL_POINTER;

    nsresult rv = NS_OK;

    mdbOid outOid;
    mdb_id rowID = 0;

    if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
        rowID = outOid.mOid_Id;

    if(NS_SUCCEEDED(rv))
    {
        nsCOMPtr<nsIAbCard> personCard;
        personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
        NS_ENSURE_SUCCESS(rv,rv);

        InitCardFromRow(personCard, cardRow);
        personCard->SetPropertyAsUint32(kRowIDProperty, rowID);

        NS_IF_ADDREF(*result = personCard);
    }

    return rv;
}

nsresult nsAddrDatabase::CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
{
  if (!cardRow || !m_mdbEnv || !result)
    return NS_ERROR_NULL_POINTER;

    nsresult rv = NS_OK;

    mdbOid outOid;
    mdb_id rowID = 0;

    if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid)))
        rowID = outOid.mOid_Id;

    if(NS_SUCCEEDED(rv))
    {
        nsCOMPtr<nsIAbCard> personCard;
      personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
      NS_ENSURE_SUCCESS(rv,rv);

        InitCardFromRow(personCard, cardRow);
        personCard->SetPropertyAsUint32(kRowIDProperty, rowID);

        nsAutoCString id;
        id.AppendInt(rowID);
        personCard->SetLocalId(id);

        nsCOMPtr<nsIAbDirectory> abDir(do_QueryReferent(m_dbDirectory));
        if (abDir)
         abDir->GetUuid(id);

        personCard->SetDirectoryId(id);

        NS_IF_ADDREF(*result = personCard);
    }

    return rv;
}

nsresult nsAddrDatabase::CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result)
{
    return CreateCard(cardRow, listRowID, result);
}

/* create a card for mailing list in the address book */
nsresult nsAddrDatabase::CreateABListCard(nsIMdbRow* listRow, nsIAbCard **result)
{
  if (!listRow || !m_mdbEnv || !result)
    return NS_ERROR_NULL_POINTER;

    nsresult rv = NS_OK;

    mdbOid outOid;
    mdb_id rowID = 0;

    if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid)))
        rowID = outOid.mOid_Id;

    char* listURI = nullptr;

    nsAutoString fileName;
    rv = m_dbName->GetLeafName(fileName);
    NS_ENSURE_SUCCESS(rv, rv);
    listURI = PR_smprintf("%s%s/MailList%ld", kMDBDirectoryRoot, NS_ConvertUTF16toUTF8(fileName).get(), rowID);

    nsCOMPtr<nsIAbCard> personCard;
    nsCOMPtr<nsIAbMDBDirectory> dbm_dbDirectory(do_QueryReferent(m_dbDirectory,
                                                                 &rv));
    if (NS_SUCCEEDED(rv) && dbm_dbDirectory)
    {
        personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv);
        NS_ENSURE_SUCCESS(rv,rv);

        if (personCard)
        {
            GetListCardFromDB(personCard, listRow);

            personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
            personCard->SetIsMailList(true);
            personCard->SetMailListURI(listURI);

            nsAutoCString id;
            id.AppendInt(rowID);
            personCard->SetLocalId(id);

            nsCOMPtr<nsIAbDirectory> abDir(do_QueryReferent(m_dbDirectory));
            if (abDir)
             abDir->GetUuid(id);
            personCard->SetDirectoryId(id);
        }

        NS_IF_ADDREF(*result = personCard);
    }
    if (listURI)
        PR_smprintf_free(listURI);

    return rv;
}

/* create a sub directory for mailing list in the address book left pane */
nsresult nsAddrDatabase::CreateABList(nsIMdbRow* listRow, nsIAbDirectory **result)
{
    nsresult rv = NS_OK;

    if (!listRow || !m_mdbEnv || !result)
        return NS_ERROR_NULL_POINTER;

    mdbOid outOid;
    mdb_id rowID = 0;

    if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid)))
        rowID = outOid.mOid_Id;

    char* listURI = nullptr;

    nsAutoString fileName;
    m_dbName->GetLeafName(fileName);
    NS_ENSURE_SUCCESS(rv, rv);

    listURI = PR_smprintf("%s%s/MailList%ld", kMDBDirectoryRoot, NS_ConvertUTF16toUTF8(fileName).get(), rowID);

    nsCOMPtr<nsIAbDirectory> mailList;
    nsCOMPtr<nsIAbMDBDirectory> dbm_dbDirectory(do_QueryReferent(m_dbDirectory,
                                                                 &rv));
    if (NS_SUCCEEDED(rv) && dbm_dbDirectory)
    {
        rv = dbm_dbDirectory->AddDirectory(listURI, getter_AddRefs(mailList));

        nsCOMPtr<nsIAbMDBDirectory> dbmailList (do_QueryInterface(mailList, &rv));

        if (mailList)
        {
            // if we are using turbo, and we "exit" and restart with the same profile
            // the current mailing list will still be in memory, so when we do
            // GetResource() and QI, we'll get it again.
            // in that scenario, the mailList that we pass in will already be
            // be a mailing list, with a valid row and all the entries
            // in that scenario, we can skip GetListFromDB(), which would have
            // have added all the cards to the list again.
            // see bug #134743
            mdb_id existingID;
            dbmailList->GetDbRowID(&existingID);
            if (existingID != rowID) {
              // Ensure IsMailList is set up first.
              mailList->SetIsMailList(true);
              GetListFromDB(mailList, listRow);
              dbmailList->SetDbRowID(rowID);
            }

            dbm_dbDirectory->AddMailListToDirectory(mailList);
            NS_IF_ADDREF(*result = mailList);
        }
    }

    if (listURI)
        PR_smprintf_free(listURI);

    return rv;
}

nsresult nsAddrDatabase::GetCardRowByRowID(mdb_id rowID, nsIMdbRow **dbRow)
{
  if (!m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  mdbOid rowOid;
  rowOid.mOid_Scope = m_CardRowScopeToken;
  rowOid.mOid_Id = rowID;

  return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow);
}

nsresult nsAddrDatabase::GetListRowByRowID(mdb_id rowID, nsIMdbRow **dbRow)
{
  if (!m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  mdbOid rowOid;
  rowOid.mOid_Scope = m_ListRowScopeToken;
  rowOid.mOid_Id = rowID;

  return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow);
}

nsresult nsAddrDatabase::GetRowFromAttribute(const char *aName,
                                             const nsACString &aUTF8Value,
                                             bool aCaseInsensitive,
                                             nsIMdbRow **aCardRow,
                                             mdb_pos *aRowPos)
{
  NS_ENSURE_ARG_POINTER(aName);
  NS_ENSURE_ARG_POINTER(aCardRow);
  if (!m_mdbStore || !m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  mdb_token token;
  m_mdbStore->StringToToken(m_mdbEnv, aName, &token);
  NS_ConvertUTF8toUTF16 newUnicodeString(aUTF8Value);

  return GetRowForCharColumn(newUnicodeString.get(), token, true,
                             aCaseInsensitive, aCardRow, aRowPos);
}

NS_IMETHODIMP nsAddrDatabase::GetCardFromAttribute(nsIAbDirectory *aDirectory,
                                                   const char *aName,
                                                   const nsACString &aUTF8Value,
                                                   bool aCaseInsensitive,
                                                   nsIAbCard **aCardResult)
{
  NS_ENSURE_ARG_POINTER(aCardResult);

  m_dbDirectory = do_GetWeakReference(aDirectory);
  nsCOMPtr<nsIMdbRow> cardRow;
  if (NS_SUCCEEDED(GetRowFromAttribute(aName, aUTF8Value, aCaseInsensitive,
                                       getter_AddRefs(cardRow), nullptr)) && cardRow)
    return CreateABCard(cardRow, 0, aCardResult);

  *aCardResult = nullptr;
  return NS_OK;
}

NS_IMETHODIMP nsAddrDatabase::GetCardsFromAttribute(nsIAbDirectory *aDirectory,
                                                    const char *aName,
                                                    const nsACString & aUTF8Value,
                                                    bool aCaseInsensitive,
                                                    nsISimpleEnumerator **cards)
{
  NS_ENSURE_ARG_POINTER(cards);

  m_dbDirectory = do_GetWeakReference(aDirectory);
  nsCOMPtr<nsIMdbRow> row;
  bool done = false;
  nsCOMArray<nsIAbCard> list;
  nsCOMPtr<nsIAbCard> card;
  mdb_pos rowPos = -1;

  do
  {
    if (NS_SUCCEEDED(GetRowFromAttribute(aName, aUTF8Value, aCaseInsensitive,
                                         getter_AddRefs(row), &rowPos)) && row)
    {
      if (NS_FAILED(CreateABCard(row, 0, getter_AddRefs(card))))
        continue;
      list.AppendObject(card);
    }
    else
      done = true;
  } while (!done);

  return NS_NewArrayEnumerator(cards, list);
}

NS_IMETHODIMP nsAddrDatabase::AddListDirNode(nsIMdbRow * listRow)
{
  nsresult rv = NS_OK;

  nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));

  if (NS_SUCCEEDED(rv))
  {
    nsAutoString parentURI;
    rv = m_dbName->GetLeafName(parentURI);
    NS_ENSURE_SUCCESS(rv, rv);

    parentURI.Replace(0, 0, NS_LITERAL_STRING(kMDBDirectoryRoot));

    nsCOMPtr<nsIAbDirectory> parentDir;
    rv = abManager->GetDirectory(NS_ConvertUTF16toUTF8(parentURI),
                                 getter_AddRefs(parentDir));
    NS_ENSURE_SUCCESS(rv, rv);

    if (parentDir)
    {
      m_dbDirectory = do_GetWeakReference(parentDir);
      nsCOMPtr<nsIAbDirectory> mailList;
      rv = CreateABList(listRow, getter_AddRefs(mailList));
      if (mailList)
      {
        nsCOMPtr<nsIAbMDBDirectory> dbparentDir(do_QueryInterface(parentDir, &rv));
        if(NS_SUCCEEDED(rv))
          dbparentDir->NotifyDirItemAdded(mailList);
      }
    }
  }
  return rv;
}

NS_IMETHODIMP nsAddrDatabase::FindMailListbyUnicodeName(const char16_t *listName, bool *exist)
{
  nsAutoString unicodeString(listName);
  ToLowerCase(unicodeString);

  nsCOMPtr <nsIMdbRow> listRow;
  nsresult rv = GetRowForCharColumn(unicodeString.get(),
                                    m_LowerListNameColumnToken, false,
                                    false, getter_AddRefs(listRow), nullptr);
  *exist = (NS_SUCCEEDED(rv) && listRow);
  return rv;
}

NS_IMETHODIMP nsAddrDatabase::GetCardCount(uint32_t *count)
{
    nsresult rv;
    mdb_count c;
    rv = m_mdbPabTable->GetCount(m_mdbEnv, &c);
    if (NS_SUCCEEDED(rv))
        *count = c - 1;  // Don't count LastRecordKey

    return rv;
}

bool
nsAddrDatabase::HasRowButDeletedForCharColumn(const char16_t *unicodeStr, mdb_column findColumn, bool aIsCard, nsIMdbRow **aFindRow)
{
  if (!m_mdbStore || !aFindRow || !m_mdbEnv)
    return false;

  mdbYarn    sourceYarn;

  NS_ConvertUTF16toUTF8 UTF8String(unicodeStr);
  sourceYarn.mYarn_Buf = (void *) UTF8String.get();
  sourceYarn.mYarn_Fill = UTF8String.Length();
  sourceYarn.mYarn_Form = 0;
  sourceYarn.mYarn_Size = sourceYarn.mYarn_Fill;

  mdbOid        outRowId;
  nsresult rv;

  if (aIsCard)
  {
    rv = m_mdbStore->FindRow(m_mdbEnv, m_CardRowScopeToken,
      findColumn, &sourceYarn,  &outRowId, aFindRow);

    // no such card, so bail out early
    if (NS_FAILED(rv) || !*aFindRow)
      return false;

    // we might not have loaded the "delete cards" table yet
    // so do that (but don't create it, if we don't have one),
    // so we can see if the row is really a delete card.
    if (!m_mdbDeletedCardsTable)
      rv = InitDeletedCardsTable(false);

    // if still no deleted cards table, there are no deleted cards
    if (!m_mdbDeletedCardsTable)
      return false;

    mdb_bool hasRow = false;
    rv = m_mdbDeletedCardsTable->HasRow(m_mdbEnv, *aFindRow, &hasRow);
    return (NS_SUCCEEDED(rv) && hasRow);
  }

  rv = m_mdbStore->FindRow(m_mdbEnv, m_ListRowScopeToken,
    findColumn, &sourceYarn,  &outRowId, aFindRow);
  return (NS_SUCCEEDED(rv) && *aFindRow);
}

/* @param aRowPos Contains the row position for multiple calls. Should be
 *                instantiated to -1 on the first call. Or can be null
 *                if you are not making multiple calls.
 */
nsresult
nsAddrDatabase::GetRowForCharColumn(const char16_t *unicodeStr,
                                    mdb_column findColumn, bool aIsCard,
                                    bool aCaseInsensitive,
                                    nsIMdbRow **aFindRow,
                                    mdb_pos *aRowPos)
{
  NS_ENSURE_ARG_POINTER(unicodeStr);
  NS_ENSURE_ARG_POINTER(aFindRow);
  NS_ENSURE_TRUE(m_mdbEnv && m_mdbPabTable, NS_ERROR_NULL_POINTER);

  *aFindRow = nullptr;

  // see bug #198303
  // the addition of the m_mdbDeletedCardsTable table has complicated life in the addressbook
  // (it was added for palm sync).  until we fix the underlying problem, we have to jump through hoops
  // in order to know if we have a row (think card) for a given column value (think email=foo@bar.com)
  // there are 4 scenarios:
  //   1) no cards with a match
  //   2) at least one deleted card with a match, but no non-deleted cards
  //   3) at least one non-deleted card with a match, but no deleted cards
  //   4) at least one deleted card, and one non-deleted card with a match.
  //
  // if we have no cards that match (FindRow() returns nothing), we can bail early
  // but if FindRow() returns something, we have to check if it is in the deleted table
  // if not in the deleted table we can return the row (we found a non-deleted card)
  // but if so, we have to search through the table of non-deleted cards
  // for a match.  If we find one, we return it.  but if not, we report that there are no
  // non-deleted cards.  This is the expensive part.  The worse case scenario is to have
  // deleted lots of cards, and then have a lot of non-deleted cards.
  // we'd have to call FindRow(), HasRow(), and then search the list of non-deleted cards
  // each time we call GetRowForCharColumn().
  if (!aRowPos && !HasRowButDeletedForCharColumn(unicodeStr, findColumn, aIsCard, aFindRow))
  {
    // If we have a row, it's the row for the non-delete card, so return NS_OK.
    // If we don't have a row, there are two possible conditions: either the
    // card does not exist, or we are doing case-insensitive searching and the
    // value isn't lowercase.

    // Valid result, return.
    if (*aFindRow)
      return NS_OK;

    // We definitely don't have anything at this point if case-sensitive.
    if (!aCaseInsensitive)
      return NS_ERROR_FAILURE;
  }

  // check if there is a non-deleted card
  nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
  mdb_pos rowPos = -1;
  bool done = false;
  nsCOMPtr<nsIMdbRow> currentRow;
  nsAutoString columnValue;

  if (aRowPos)
    rowPos = *aRowPos;

  mdb_scope targetScope = aIsCard ? m_CardRowScopeToken : m_ListRowScopeToken;

  m_mdbPabTable->GetTableRowCursor(m_mdbEnv, rowPos, getter_AddRefs(rowCursor));
  if (!rowCursor)
    return NS_ERROR_FAILURE;

  while (!done)
  {
    nsresult rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos);
    if (currentRow && NS_SUCCEEDED(rv))
    {
      mdbOid rowOid;
      if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)) && (rowOid.mOid_Scope == targetScope))
      {
        rv = GetStringColumn(currentRow, findColumn, columnValue);

        bool equals = aCaseInsensitive ?
          columnValue.Equals(unicodeStr, nsCaseInsensitiveStringComparator()) :
          columnValue.Equals(unicodeStr);

        if (NS_SUCCEEDED(rv) && equals)
        {
          NS_IF_ADDREF(*aFindRow = currentRow);
          if (aRowPos)
            *aRowPos = rowPos;
          return NS_OK;
        }
      }
    }
    else
      done = true;
  }
  return NS_ERROR_FAILURE;
}

nsresult nsAddrDatabase::DeleteRow(nsIMdbTable* dbTable, nsIMdbRow* dbRow)
{
  if (!m_mdbEnv)
    return NS_ERROR_NULL_POINTER;

  nsresult err = dbRow->CutAllColumns(m_mdbEnv);
  err = dbTable->CutRow(m_mdbEnv, dbRow);

  return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
}
