mailnews/imap/src/nsImapMailFolder.cpp
author Ben Campbell <benc@thunderbird.net>
Fri, 14 Sep 2018 12:03:52 +1200
changeset 33335 930dfab9478d6d09ba25ac32dd5831df204691d3
parent 33314 a87795783553295e890fdf7bacb0cb92cd24dcb8
child 33383 8aebb2ed9307360384bb2cc5c35bde65e7535ac7
permissions -rw-r--r--
Bug 1317117 - call msgStore folder deletion during imap/news empty trash. r=jorgk Also factors out some common code into nsMSgDBFolder::Delete(), and makes a couple of incidental changes to try and clarify the responsibilies of nsIMsgPluggableStore::DeleteFolder().

/* -*- Mode:
 C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "msgCore.h"
#include "prmem.h"
#include "nsMsgImapCID.h"
#include "nsImapMailFolder.h"
#include "nsIImapService.h"
#include "nsIFile.h"
#include "nsIUrlListener.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsIRDFService.h"
#include "nsRDFCID.h"
#include "nsMsgDBCID.h"
#include "nsMsgFolderFlags.h"
#include "nsISeekableStream.h"
#include "nsThreadUtils.h"
#include "nsIImapUrl.h"
#include "nsImapUtils.h"
#include "nsMsgUtils.h"
#include "nsIMsgMailSession.h"
#include "nsMsgKeyArray.h"
#include "nsMsgBaseCID.h"
#include "nsMsgLocalCID.h"
#include "nsITransactionManager.h"
#include "nsImapUndoTxn.h"
#include "nsIIMAPHostSessionList.h"
#include "nsIMsgCopyService.h"
#include "nsICopyMsgStreamListener.h"
#include "nsImapStringBundle.h"
#include "nsIMsgFolderCacheElement.h"
#include "nsTextFormatter.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsMsgI18N.h"
#include "nsIMsgFilter.h"
#include "nsIMsgFilterService.h"
#include "nsIMsgSearchCustomTerm.h"
#include "nsIMsgSearchTerm.h"
#include "nsImapMoveCoalescer.h"
#include "nsIPrompt.h"
#include "nsIDocShell.h"
#include "nsUnicharUtils.h"
#include "nsIImapFlagAndUidState.h"
#include "nsIImapHeaderXferInfo.h"
#include "nsIMessenger.h"
#include "nsIMsgSearchAdapter.h"
#include "nsIImapMockChannel.h"
#include "nsIProgressEventSink.h"
#include "nsIMsgWindow.h"
#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
#include "nsIMsgLocalMailFolder.h"
#include "nsIMsgOfflineImapOperation.h"
#include "nsImapOfflineSync.h"
#include "nsIImapMailFolderSink.h"
#include "nsIImapServerSink.h"
#include "nsIMsgAccountManager.h"
#include "nsQuickSort.h"
#include "nsIImapMockChannel.h"
#include "nsNetUtil.h"
#include "nsIMAPNamespace.h"
#include "nsIMsgFolderCompactor.h"
#include "nsMsgMessageFlags.h"
#include "nsISpamSettings.h"
#include <time.h>
#include "nsIMsgMailNewsUrl.h"
#include "nsEmbedCID.h"
#include "nsIMsgComposeService.h"
#include "nsMsgCompCID.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIMsgIdentity.h"
#include "nsIMsgFolderNotificationService.h"
#include "nsNativeCharsetUtils.h"
#include "nsIExternalProtocolService.h"
#include "nsCExternalHandlerService.h"
#include "prprf.h"
#include "nsIMutableArray.h"
#include "nsArrayUtils.h"
#include "nsArrayEnumerator.h"
#include "nsAutoSyncManager.h"
#include "nsIMsgFilterCustomAction.h"
#include "nsMsgReadStateTxn.h"
#include "nsStringEnumerator.h"
#include "nsIMsgStatusFeedback.h"
#include "nsMsgLineBuffer.h"
#include "mozilla/Logging.h"
#include "mozilla/Attributes.h"
#include "nsStringStream.h"
#include "nsIStreamListener.h"

static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID);
static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID);

extern mozilla::LazyLogModule gAutoSyncLog; // defined in nsAutoSyncManager.cpp
extern mozilla::LazyLogModule IMAP;         // defined in nsImapProtocol.cpp
extern mozilla::LazyLogModule IMAP_CS;      // For CONDSTORE, defined in nsImapProtocol.cpp

#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"

/*
    Copies the contents of srcDir into destDir.
    destDir will be created if it doesn't exist.
*/

static
nsresult RecursiveCopy(nsIFile* srcDir, nsIFile* destDir)
{
  nsresult rv;
  bool isDir;

  rv = srcDir->IsDirectory(&isDir);
  if (NS_FAILED(rv)) return rv;
  if (!isDir) return NS_ERROR_INVALID_ARG;

  bool exists;
  rv = destDir->Exists(&exists);
  if (NS_SUCCEEDED(rv) && !exists)
    rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
  if (NS_FAILED(rv)) return rv;


  nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
  rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
  if (NS_FAILED(rv)) return rv;

  bool hasMore = false;
  while (NS_SUCCEEDED(dirIterator->HasMoreElements(&hasMore)) && hasMore)
  {
    nsCOMPtr<nsIFile> dirEntry;
    rv = dirIterator->GetNextFile(getter_AddRefs(dirEntry));
    if (NS_SUCCEEDED(rv) && dirEntry)
    {
      rv = dirEntry->IsDirectory(&isDir);
      if (NS_SUCCEEDED(rv))
      {
        if (isDir)
        {
          nsCOMPtr<nsIFile> newChild;
          rv = destDir->Clone(getter_AddRefs(newChild));
          if (NS_SUCCEEDED(rv))
          {
            nsAutoString leafName;
            dirEntry->GetLeafName(leafName);
            newChild->AppendRelativePath(leafName);
            rv = newChild->Exists(&exists);
            if (NS_SUCCEEDED(rv) && !exists)
              rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775);
            rv = RecursiveCopy(dirEntry, newChild);
          }
        }
        else
          rv = dirEntry->CopyTo(destDir, EmptyString());
      }

    }
  }

  return rv;
}

nsImapMailFolder::nsImapMailFolder() :
    m_initialized(false),m_haveDiscoveredAllFolders(false),
    m_curMsgUid(0), m_nextMessageByteLength(0),
    m_urlRunning(false),
    m_verifiedAsOnlineFolder(false),
    m_explicitlyVerify(false),
    m_folderIsNamespace(false),
    m_folderNeedsSubscribing(false),
    m_folderNeedsAdded(false),
    m_folderNeedsACLListed(true),
    m_performingBiff(false),
    m_folderQuotaCommandIssued(false),
    m_folderQuotaDataIsValid(false),
    m_updatingFolder(false),
    m_compactingOfflineStore(false),
    m_expunging(false),
    m_applyIncomingFilters(false),
    m_downloadingFolderForOfflineUse(false),
    m_filterListRequiresBody(false),
    m_folderQuotaUsedKB(0),
    m_folderQuotaMaxKB(0)
{
  m_boxFlags = 0;
  m_uidValidity = kUidUnknown;
  m_numServerRecentMessages = 0;
  m_numServerUnseenMessages = 0;
  m_numServerTotalMessages = 0;
  m_nextUID = nsMsgKey_None;
  m_hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
  m_folderACL = nullptr;
  m_aclFlags = 0;
  m_supportedUserFlags = 0;
  m_namespace = nullptr;
  m_pendingPlaybackReq = nullptr;
}

nsImapMailFolder::~nsImapMailFolder()
{
  delete m_folderACL;

  // cleanup any pending request
  delete m_pendingPlaybackReq;
}

NS_IMPL_ADDREF_INHERITED(nsImapMailFolder, nsMsgDBFolder)
NS_IMPL_RELEASE_INHERITED(nsImapMailFolder, nsMsgDBFolder)
NS_IMPL_QUERY_HEAD(nsImapMailFolder)
    NS_IMPL_QUERY_BODY(nsIMsgImapMailFolder)
    NS_IMPL_QUERY_BODY(nsICopyMessageListener)
    NS_IMPL_QUERY_BODY(nsIImapMailFolderSink)
    NS_IMPL_QUERY_BODY(nsIImapMessageSink)
    NS_IMPL_QUERY_BODY(nsIUrlListener)
    NS_IMPL_QUERY_BODY(nsIMsgFilterHitNotify)
NS_IMPL_QUERY_TAIL_INHERITING(nsMsgDBFolder)

nsresult nsImapMailFolder::AddDirectorySeparator(nsIFile *path)
{
  if (mURI.Equals(kImapRootURI))
  {
    // don't concat the full separator with .sbd
  }
  else
  {
    // see if there's a dir with the same name ending with .sbd
    nsAutoString leafName;
    path->GetLeafName(leafName);
    leafName.AppendLiteral(FOLDER_SUFFIX);
    path->SetLeafName(leafName);
  }

  return NS_OK;
}

static bool
nsShouldIgnoreFile(nsString& name)
{
  if (StringEndsWith(name, NS_LITERAL_STRING(SUMMARY_SUFFIX), nsCaseInsensitiveStringComparator()))
  {
    name.SetLength(name.Length() - SUMMARY_SUFFIX_LENGTH); // truncate the string
    return false;
  }
  return true;
}

nsresult nsImapMailFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder)
{
  nsImapMailFolder *newFolder = new nsImapMailFolder;
  newFolder->Init(uri.get());
  NS_ADDREF(*folder = newFolder);
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::AddSubfolder(const nsAString& aName, nsIMsgFolder** aChild)
{
  NS_ENSURE_ARG_POINTER(aChild);

  int32_t flags = 0;
  nsresult rv;
  nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  nsAutoCString uri(mURI);
  uri.Append('/');

  // If AddSubFolder starts getting called for folders other than virtual folders,
  // we'll have to do convert those names to modified utf-7. For now, the account manager code
  // that loads the virtual folders for each account, expects utf8 not modified utf-7.
  nsAutoCString escapedName;
  rv = NS_MsgEscapeEncodeURLPath(aName, escapedName);
  NS_ENSURE_SUCCESS(rv, rv);

  uri += escapedName.get();

  nsCOMPtr <nsIMsgFolder> msgFolder;
  rv = GetChildWithURI(uri, false/*deep*/, true /*case Insensitive*/, getter_AddRefs(msgFolder));
  if (NS_SUCCEEDED(rv) && msgFolder)
    return NS_MSG_FOLDER_EXISTS;

  nsCOMPtr<nsIRDFResource> res;
  rv = rdf->GetResource(uri, getter_AddRefs(res));
  if (NS_FAILED(rv))
    return rv;

  nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr <nsIFile> path;
  rv = CreateDirectoryForFolder(getter_AddRefs(path));
  NS_ENSURE_SUCCESS(rv, rv);

  folder->GetFlags((uint32_t *)&flags);

  flags |= nsMsgFolderFlags::Mail;

  nsCOMPtr<nsIImapIncomingServer> imapServer;
  GetImapIncomingServer(getter_AddRefs(imapServer));
  if (imapServer)
  {
    bool setNewFoldersForOffline = false;
    rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
    if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
      flags |= nsMsgFolderFlags::Offline;
  }

  folder->SetParent(this);

  folder->SetFlags(flags);

  mSubFolders.AppendObject(folder);
  folder.forget(aChild);

  nsCOMPtr <nsIMsgImapMailFolder> imapChild = do_QueryInterface(*aChild);
  if (imapChild)
  {
    imapChild->SetOnlineName(NS_LossyConvertUTF16toASCII(aName));
    imapChild->SetHierarchyDelimiter(m_hierarchyDelimiter);
  }
  NotifyItemAdded(*aChild);
  return rv;
}

nsresult nsImapMailFolder::AddSubfolderWithPath(nsAString& name, nsIFile *dbPath,
                                             nsIMsgFolder **child, bool brandNew)
{
  NS_ENSURE_ARG_POINTER(child);
  nsresult rv;
  nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString uri(mURI);
  uri.Append('/');
  AppendUTF16toUTF8(name, uri);

  bool isServer;
  rv = GetIsServer(&isServer);
  NS_ENSURE_SUCCESS(rv, rv);

  bool isInbox = isServer && name.LowerCaseEqualsLiteral("inbox");

  //will make sure mSubFolders does not have duplicates because of bogus msf files.
  nsCOMPtr <nsIMsgFolder> msgFolder;
  rv = GetChildWithURI(uri, false/*deep*/, isInbox /*case Insensitive*/, getter_AddRefs(msgFolder));
  if (NS_SUCCEEDED(rv) && msgFolder)
    return NS_MSG_FOLDER_EXISTS;

  nsCOMPtr<nsIRDFResource> res;
  rv = rdf->GetResource(uri, getter_AddRefs(res));
  if (NS_FAILED(rv))
    return rv;

  nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  folder->SetFilePath(dbPath);
  nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
  mozilla::Unused << imapFolder;
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t flags = 0;
  folder->GetFlags(&flags);

  folder->SetParent(this);
  flags |= nsMsgFolderFlags::Mail;

  uint32_t pFlags;
  GetFlags(&pFlags);
  bool isParentInbox = pFlags & nsMsgFolderFlags::Inbox;

  nsCOMPtr<nsIImapIncomingServer> imapServer;
  rv = GetImapIncomingServer(getter_AddRefs(imapServer));
  NS_ENSURE_SUCCESS(rv, rv);

  //Only set these if these are top level children or parent is inbox
  if (isInbox)
    flags |= nsMsgFolderFlags::Inbox;
  else if (isServer || isParentInbox)
  {
    nsMsgImapDeleteModel deleteModel;
    imapServer->GetDeleteModel(&deleteModel);
    if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
    {
      nsAutoString trashName;
      GetTrashFolderName(trashName);
      if (name.Equals(trashName))
        flags |= nsMsgFolderFlags::Trash;
    }
  }

  // Make the folder offline if it is newly created and the offline_download
  // pref is true, unless it's the Trash or Junk folder.
  if (brandNew && !(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
  {
    bool setNewFoldersForOffline = false;
    rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
    if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
      flags |= nsMsgFolderFlags::Offline;
  }

  folder->SetFlags(flags);

  if (folder)
    mSubFolders.AppendObject(folder);
  folder.forget(child);
  return NS_OK;
}

nsresult nsImapMailFolder::CreateSubFolders(nsIFile *path)
{
  nsresult rv = NS_OK;
  nsAutoString currentFolderNameStr;    // online name
  nsAutoString currentFolderDBNameStr;  // possibly munged name
  nsCOMPtr<nsIMsgFolder> child;
  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = GetServer(getter_AddRefs(server));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIDirectoryEnumerator> children;
  rv = path->GetDirectoryEntries(getter_AddRefs(children));
  bool more = false;
  if (children)
    children->HasMoreElements(&more);

  while (more)
  {
    nsCOMPtr<nsIFile> currentFolderPath;
    rv = children->GetNextFile(getter_AddRefs(currentFolderPath));
    if (NS_FAILED(rv) || !currentFolderPath)
      break;
    rv = children->HasMoreElements(&more);
    if (NS_FAILED(rv)) return rv;

    currentFolderPath->GetLeafName(currentFolderNameStr);
    if (nsShouldIgnoreFile(currentFolderNameStr))
      continue;

    // OK, here we need to get the online name from the folder cache if we can.
    // If we can, use that to create the sub-folder
    nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
    nsCOMPtr <nsIFile> curFolder = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr <nsIFile> dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    dbFile->InitWithFile(currentFolderPath);
    curFolder->InitWithFile(currentFolderPath);
    // don't strip off the .msf in currentFolderPath.
    currentFolderPath->SetLeafName(currentFolderNameStr);
    currentFolderDBNameStr = currentFolderNameStr;
    nsAutoString utf7LeafName = currentFolderNameStr;

    if (curFolder)
    {
      rv = GetFolderCacheElemFromFile(dbFile, getter_AddRefs(cacheElement));
      if (NS_SUCCEEDED(rv) && cacheElement)
      {
        nsCString onlineFullUtf7Name;

        uint32_t folderFlags;
        rv = cacheElement->GetInt32Property("flags", (int32_t *) &folderFlags);
        if (NS_SUCCEEDED(rv) && folderFlags & nsMsgFolderFlags::Virtual) //ignore virtual folders
          continue;
        int32_t hierarchyDelimiter;
        rv = cacheElement->GetInt32Property("hierDelim", &hierarchyDelimiter);
        if (NS_SUCCEEDED(rv) && hierarchyDelimiter == kOnlineHierarchySeparatorUnknown)
        {
          currentFolderPath->Remove(false);
          continue; // blow away .msf files for folders with unknown delimiter.
        }
        rv = cacheElement->GetStringProperty("onlineName", onlineFullUtf7Name);
        if (NS_SUCCEEDED(rv) && !onlineFullUtf7Name.IsEmpty())
        {
          CopyMUTF7toUTF16(onlineFullUtf7Name, currentFolderNameStr);
          char delimiter = 0;
          GetHierarchyDelimiter(&delimiter);
          int32_t leafPos = currentFolderNameStr.RFindChar(delimiter);
          if (leafPos > 0)
            currentFolderNameStr.Cut(0, leafPos + 1);

          // take the utf7 full online name, and determine the utf7 leaf name
          CopyASCIItoUTF16(onlineFullUtf7Name, utf7LeafName);
          leafPos = utf7LeafName.RFindChar(delimiter);
          if (leafPos > 0)
            utf7LeafName.Cut(0, leafPos + 1);
        }
      }
    }
      // make the imap folder remember the file spec it was created with.
    nsCOMPtr <nsIFile> msfFilePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    msfFilePath->InitWithFile(currentFolderPath);
    if (NS_SUCCEEDED(rv) && msfFilePath)
    {
      // leaf name is the db name w/o .msf (nsShouldIgnoreFile strips it off)
      // so this trims the .msf off the file spec.
      msfFilePath->SetLeafName(currentFolderDBNameStr);
    }
    // use the utf7 name as the uri for the folder.
    AddSubfolderWithPath(utf7LeafName, msfFilePath, getter_AddRefs(child));
    if (child)
    {
      // use the unicode name as the "pretty" name. Set it so it won't be
      // automatically computed from the URI, which is in utf7 form.
      if (!currentFolderNameStr.IsEmpty())
        child->SetPrettyName(currentFolderNameStr);
      child->SetMsgDatabase(nullptr);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetSubFolders(nsISimpleEnumerator **aResult)
{
  bool isServer;
  nsresult rv = GetIsServer(&isServer);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!m_initialized)
  {
    nsCOMPtr<nsIFile> pathFile;
    rv = GetFilePath(getter_AddRefs(pathFile));
    if (NS_FAILED(rv)) return rv;

    // host directory does not need .sbd tacked on
    if (!isServer)
    {
      rv = AddDirectorySeparator(pathFile);
      if(NS_FAILED(rv)) return rv;
    }

    m_initialized = true;      // need to set this here to avoid infinite recursion from CreateSubfolders.
    // we have to treat the root folder specially, because it's name
    // doesn't end with .sbd

    int32_t newFlags = nsMsgFolderFlags::Mail;
    bool isDirectory = false;
    pathFile->IsDirectory(&isDirectory);
    if (isDirectory)
    {
        newFlags |= (nsMsgFolderFlags::Directory | nsMsgFolderFlags::Elided);
        if (!mIsServer)
          SetFlag(newFlags);
        rv = CreateSubFolders(pathFile);
    }
    if (isServer)
    {
      nsCOMPtr <nsIMsgFolder> inboxFolder;

      GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inboxFolder));
      if (!inboxFolder)
      {
        // create an inbox if we don't have one.
        CreateClientSubfolderInfo(NS_LITERAL_CSTRING("INBOX"), kOnlineHierarchySeparatorUnknown, 0, true);
      }
    }

    int32_t count = mSubFolders.Count();
    nsCOMPtr<nsISimpleEnumerator> dummy;
    for (int32_t i = 0; i < count; i++)
      mSubFolders[i]->GetSubFolders(getter_AddRefs(dummy));

    UpdateSummaryTotals(false);
    if (NS_FAILED(rv)) return rv;
  }

  return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders, NS_GET_IID(nsIMsgFolder)) : NS_ERROR_NULL_POINTER;
}

//Makes sure the database is open and exists.  If the database is valid then
//returns NS_OK.  Otherwise returns a failure error value.
nsresult nsImapMailFolder::GetDatabase()
{
  nsresult rv = NS_OK;
  if (!mDatabase)
  {
    nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    // Create the database, blowing it away if it needs to be rebuilt
    rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
    if (NS_FAILED(rv))
      rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));

    NS_ENSURE_SUCCESS(rv, rv);

    // UpdateNewMessages/UpdateSummaryTotals can null mDatabase, so we save a local copy
    nsCOMPtr<nsIMsgDatabase> database(mDatabase);
    UpdateNewMessages();
    if(mAddListener)
      database->AddListener(this);
    UpdateSummaryTotals(true);
    mDatabase = database;
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::UpdateFolder(nsIMsgWindow * inMsgWindow)
{
  return UpdateFolderWithListener(inMsgWindow, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::UpdateFolderWithListener(nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener)
{
  nsresult rv;
  bool selectFolder = false;

  // If this is the inbox, filters will be applied. Otherwise, we test the
  // inherited folder property "applyIncomingFilters" (which defaults to empty).
  // If this inherited property has the string value "true", we will apply
  // filters even if this is not the inbox folder.
  nsCString applyIncomingFilters;
  GetInheritedStringProperty("applyIncomingFilters", applyIncomingFilters);
  m_applyIncomingFilters = applyIncomingFilters.EqualsLiteral("true");

  if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters)
  {
    if (!m_filterList)
      rv = GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
    // if there's no msg window, but someone is updating the inbox, we're
    // doing something biff-like, and may download headers, so make biff notify.
    if (!aMsgWindow && mFlags & nsMsgFolderFlags::Inbox)
      SetPerformingBiff(true);
  }

  if (m_filterList)
  {
    nsCOMPtr<nsIMsgIncomingServer> server;
    rv = GetServer(getter_AddRefs(server));
    NS_ENSURE_SUCCESS(rv, rv);

    bool canFileMessagesOnServer = true;
    rv = server->GetCanFileMessagesOnServer(&canFileMessagesOnServer);
    // the mdn filter is for filing return receipts into the sent folder
    // some servers (like AOL mail servers)
    // can't file to the sent folder, so we don't add the filter for those servers
    if (canFileMessagesOnServer)
    {
      rv = server->ConfigureTemporaryFilters(m_filterList);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // If a body filter is enabled for an offline folder, delay the filter
    // application until after message has been downloaded.
    m_filterListRequiresBody = false;

    if (mFlags & nsMsgFolderFlags::Offline)
    {
      nsCOMPtr<nsIMsgFilterService> filterService =
        do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
      uint32_t filterCount = 0;
      m_filterList->GetFilterCount(&filterCount);
      for (uint32_t index = 0;
           index < filterCount && !m_filterListRequiresBody;
           ++index)
      {
        nsCOMPtr<nsIMsgFilter> filter;
        m_filterList->GetFilterAt(index, getter_AddRefs(filter));
        if (!filter)
          continue;
        nsMsgFilterTypeType filterType;
        filter->GetFilterType(&filterType);
        if (!(filterType & nsMsgFilterType::Incoming))
          continue;
        bool enabled = false;
        filter->GetEnabled(&enabled);
        if (!enabled)
          continue;
        nsCOMPtr<nsIMutableArray> searchTerms;
        uint32_t numSearchTerms = 0;
        filter->GetSearchTerms(getter_AddRefs(searchTerms));
        if (searchTerms)
          searchTerms->Count(&numSearchTerms);
        for (uint32_t termIndex = 0;
             termIndex < numSearchTerms && !m_filterListRequiresBody;
             termIndex++)
        {
          nsCOMPtr<nsIMsgSearchTerm> term = do_QueryElementAt(searchTerms, termIndex, &rv);
          NS_ENSURE_SUCCESS(rv, rv);
          nsMsgSearchAttribValue attrib;
          rv = term->GetAttrib(&attrib);
          NS_ENSURE_SUCCESS(rv, rv);
          if (attrib == nsMsgSearchAttrib::Body)
            m_filterListRequiresBody = true;
          else if (attrib == nsMsgSearchAttrib::Custom)
          {
            nsAutoCString customId;
            rv = term->GetCustomId(customId);
            nsCOMPtr<nsIMsgSearchCustomTerm> customTerm;
            if (NS_SUCCEEDED(rv) && filterService)
              rv = filterService->GetCustomTerm(customId,
                                                getter_AddRefs(customTerm));
            bool needsBody = false;
            if (NS_SUCCEEDED(rv) && customTerm)
              rv = customTerm->GetNeedsBody(&needsBody);
            if (NS_SUCCEEDED(rv) && needsBody)
              m_filterListRequiresBody = true;
          }
        }

        // Also check if filter actions need the body, as this
        // is supported in custom actions.
        uint32_t numActions = 0;
        filter->GetActionCount(&numActions);
        for (uint32_t actionIndex = 0;
             actionIndex < numActions && !m_filterListRequiresBody;
             actionIndex++)
        {
          nsCOMPtr<nsIMsgRuleAction> action;
          rv = filter->GetActionAt(actionIndex, getter_AddRefs(action));
          if (NS_FAILED(rv) || !action)
            continue;

          nsCOMPtr<nsIMsgFilterCustomAction> customAction;
          rv = action->GetCustomAction(getter_AddRefs(customAction));
          if (NS_FAILED(rv) || !customAction)
            continue;

          bool needsBody = false;
          customAction->GetNeedsBody(&needsBody);
          if (needsBody)
            m_filterListRequiresBody = true;
        }
      }
    }
  }

  selectFolder = true;

  bool isServer;
  rv = GetIsServer(&isServer);
  if (NS_SUCCEEDED(rv) && isServer)
  {
    if (!m_haveDiscoveredAllFolders)
    {
      bool hasSubFolders = false;
      GetHasSubFolders(&hasSubFolders);
      if (!hasSubFolders)
      {
        rv = CreateClientSubfolderInfo(NS_LITERAL_CSTRING("Inbox"), kOnlineHierarchySeparatorUnknown,0, false);
        NS_ENSURE_SUCCESS(rv, rv);
      }
      m_haveDiscoveredAllFolders = true;
    }
    selectFolder = false;
  }
  rv = GetDatabase();
  if (NS_FAILED(rv))
  {
    ThrowAlertMsg("errorGettingDB", aMsgWindow);
    return rv;
  }
  bool canOpenThisFolder = true;
  GetCanOpenFolder(&canOpenThisFolder);

  bool hasOfflineEvents = false;
  GetFlag(nsMsgFolderFlags::OfflineEvents, &hasOfflineEvents);

  if (!WeAreOffline())
  {
    if (hasOfflineEvents)
    {
      // hold a reference to the offline sync object. If ProcessNextOperation
      // runs a url, a reference will be added to it. Otherwise, it will get
      // destroyed when the refptr goes out of scope.
      RefPtr<nsImapOfflineSync> goOnline = new nsImapOfflineSync(aMsgWindow, this, this);
      if (goOnline)
      {
        m_urlListener = aUrlListener;
        return goOnline->ProcessNextOperation();
      }
    }
  }

  // Check it we're password protecting the local store.
  if (!PromptForMasterPasswordIfNecessary())
    return NS_ERROR_FAILURE;

  if (!canOpenThisFolder)
    selectFolder = false;
  // don't run select if we can't select the folder...
  if (NS_SUCCEEDED(rv) && !m_urlRunning && selectFolder)
  {
    nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr <nsIURI> url;
    rv = imapService->SelectFolder(this, m_urlListener, aMsgWindow, getter_AddRefs(url));
    if (NS_SUCCEEDED(rv))
    {
      m_urlRunning = true;
      m_updatingFolder = true;
    }
    if (url)
    {
      nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(url, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      mailnewsUrl->RegisterListener(this);
      m_urlListener = aUrlListener;
    }

    // Allow IMAP folder auto-compact to occur when online or offline.
    if (aMsgWindow)
      AutoCompact(aMsgWindow);

    if (rv == NS_MSG_ERROR_OFFLINE || rv == NS_BINDING_ABORTED)
    {
      rv = NS_OK;
      NotifyFolderEvent(kFolderLoaded);
    }
  }
  else if (NS_SUCCEEDED(rv))  // tell the front end that the folder is loaded if we're not going to
  {                           // actually run a url.
    if (!m_updatingFolder)    // if we're already running an update url, we'll let that one send the folder loaded
      NotifyFolderEvent(kFolderLoaded);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetMessages(nsISimpleEnumerator* *result)
{
  NS_ENSURE_ARG_POINTER(result);
  if (!mDatabase)
    GetDatabase();
  if (mDatabase)
    return mDatabase->EnumerateMessages(result);
  return NS_ERROR_UNEXPECTED;
}

NS_IMETHODIMP nsImapMailFolder::CreateSubfolder(const nsAString& folderName, nsIMsgWindow *msgWindow)
{
  if (folderName.IsEmpty())
    return NS_MSG_ERROR_INVALID_FOLDER_NAME;

  nsresult rv;
  nsAutoString trashName;
  GetTrashFolderName(trashName);
  if ( folderName.Equals(trashName))   // Trash , a special folder
  {
    ThrowAlertMsg("folderExists", msgWindow);
    return NS_MSG_FOLDER_EXISTS;
  }
  if (mIsServer && folderName.LowerCaseEqualsLiteral("inbox"))  // Inbox, a special folder
  {
    ThrowAlertMsg("folderExists", msgWindow);
    return NS_MSG_FOLDER_EXISTS;
  }

  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  return imapService->CreateFolder(this, folderName, this, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::CreateClientSubfolderInfo(const nsACString& folderName,
                                                          char hierarchyDelimiter,
                                                          int32_t flags,
                                                          bool suppressNotification)
{
  nsresult rv = NS_OK;

  //Get a directory based on our current path.
  nsCOMPtr <nsIFile> path;
  rv = CreateDirectoryForFolder(getter_AddRefs(path));
  if(NS_FAILED(rv))
    return rv;

  NS_ConvertASCIItoUTF16 leafName(folderName);
  nsAutoString folderNameStr;
  nsAutoString parentName = leafName;
  // use RFind, because folder can start with a delimiter and
  // not be a leaf folder.
  int32_t folderStart = leafName.RFindChar('/');
  if (folderStart > 0)
  {
    nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIRDFResource> res;
    nsCOMPtr<nsIMsgImapMailFolder> parentFolder;
    nsAutoCString uri (mURI);
    leafName.Assign(Substring(parentName, folderStart + 1));
    parentName.SetLength(folderStart);

    rv = CreateDirectoryForFolder(getter_AddRefs(path));
    if (NS_FAILED(rv))
      return rv;
    uri.Append('/');
    uri.Append(NS_LossyConvertUTF16toASCII(parentName));
    rv = rdf->GetResource(uri, getter_AddRefs(res));
    if (NS_FAILED(rv))
      return rv;
    parentFolder = do_QueryInterface(res, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsAutoCString leafnameC;
    LossyCopyUTF16toASCII(leafName, leafnameC);
    return parentFolder->CreateClientSubfolderInfo(leafnameC, hierarchyDelimiter,flags, suppressNotification);
  }

  // if we get here, it's really a leaf, and "this" is the parent.
  folderNameStr = leafName;

  // Create an empty database for this mail folder, set its name from the user
  nsCOMPtr<nsIMsgDatabase> mailDBFactory;
  nsCOMPtr<nsIMsgFolder> child;

  nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIMsgDatabase> unusedDB;
  nsCOMPtr <nsIFile> dbFile;

  // warning, path will be changed
  rv = CreateFileForDB(folderNameStr, path, getter_AddRefs(dbFile));
  NS_ENSURE_SUCCESS(rv,rv);

  //Now let's create the actual new folder
  rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child), true);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = msgDBService->OpenMailDBFromFile(dbFile, child, true, true,
                                        getter_AddRefs(unusedDB));
  if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
    rv = NS_OK;

  if (NS_SUCCEEDED(rv) && unusedDB)
  {
  //need to set the folder name
    nsCOMPtr <nsIDBFolderInfo> folderInfo;
    rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
    nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child, &rv);
    if (NS_SUCCEEDED(rv))
    {
      nsAutoCString onlineName(m_onlineFolderName);
      if (!onlineName.IsEmpty())
        onlineName.Append(hierarchyDelimiter);
      onlineName.Append(NS_LossyConvertUTF16toASCII(folderNameStr));
      imapFolder->SetVerifiedAsOnlineFolder(true);
      imapFolder->SetOnlineName(onlineName);
      imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
      imapFolder->SetBoxFlags(flags);

      // Now that the child is created and the boxflags are set we can be sure
      // all special folder flags are known. The child may get its flags already
      // in AddSubfolderWithPath if they were in FolderCache, but that's
      // not always the case.
      uint32_t flags = 0;
      child->GetFlags(&flags);

      // Set the offline use flag for the newly created folder if the
      // offline_download preference is true, unless it's the Trash or Junk
      // folder.
      if (!(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
      {
        nsCOMPtr<nsIImapIncomingServer> imapServer;
        rv = GetImapIncomingServer(getter_AddRefs(imapServer));
        NS_ENSURE_SUCCESS(rv, rv);
        bool setNewFoldersForOffline = false;
        rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
        if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
          flags |= nsMsgFolderFlags::Offline;
      }
      else
      {
        flags &= ~nsMsgFolderFlags::Offline; // clear offline flag if set
      }

      flags |= nsMsgFolderFlags::Elided;
      child->SetFlags(flags);

      nsString unicodeName;
      rv = CopyMUTF7toUTF16(nsCString(folderName), unicodeName);
      if (NS_SUCCEEDED(rv))
        child->SetPrettyName(unicodeName);

      // store the online name as the mailbox name in the db folder info
      // I don't think anyone uses the mailbox name, so we'll use it
      // to restore the online name when blowing away an imap db.
      if (folderInfo)
        folderInfo->SetMailboxName(NS_ConvertASCIItoUTF16(onlineName));
    }

    unusedDB->SetSummaryValid(true);
    unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
    unusedDB->Close(true);
    // don't want to hold onto this newly created db.
    child->SetMsgDatabase(nullptr);
  }

  if (!suppressNotification)
  {
    if(NS_SUCCEEDED(rv) && child)
    {
      NotifyItemAdded(child);
      child->NotifyFolderEvent(kFolderCreateCompleted);
      nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
      if (notifier)
        notifier->NotifyFolderAdded(child);
    }
    else
    {
      NotifyFolderEvent(kFolderCreateFailed);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::List()
{
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);
  return imapService->ListFolder(this, this, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::RemoveSubFolder (nsIMsgFolder *which)
{
  nsresult rv;
  nsCOMPtr<nsIMutableArray> folders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
  NS_ENSURE_TRUE(folders, rv);
  nsCOMPtr<nsISupports> folderSupport = do_QueryInterface(which, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  folders->AppendElement(folderSupport);
  rv = nsMsgDBFolder::DeleteSubFolders(folders, nullptr);
  which->Delete();
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::CreateStorageIfMissing(nsIUrlListener* urlListener)
{
  nsresult rv = NS_OK;
  nsCOMPtr <nsIMsgFolder> msgParent;
  GetParent(getter_AddRefs(msgParent));

  // parent is probably not set because *this* was probably created by rdf
  // and not by folder discovery. So, we have to compute the parent.
  if (!msgParent)
  {
    nsAutoCString folderName(mURI);

    int32_t leafPos = folderName.RFindChar('/');
    nsAutoCString parentName(folderName);

    if (leafPos > 0)
    {
      // If there is a hierarchy, there is a parent.
      // Don't strip off slash if it's the first character
      parentName.SetLength(leafPos);
      // get the corresponding RDF resource
      // RDF will create the folder resource if it doesn't already exist
      nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
      NS_ENSURE_SUCCESS(rv, rv);
      nsCOMPtr<nsIRDFResource> resource;
      rv = rdf->GetResource(parentName, getter_AddRefs(resource));
      if (NS_FAILED(rv)) return rv;
      msgParent = do_QueryInterface(resource, &rv);
    }
  }
  if (msgParent)
  {
    nsString folderName;
    GetName(folderName);
    nsresult rv;
    nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr <nsIURI> uri;
    imapService->EnsureFolderExists(msgParent, folderName, urlListener, getter_AddRefs(uri));
  }
  return rv;
}


NS_IMETHODIMP nsImapMailFolder::GetVerifiedAsOnlineFolder(bool *aVerifiedAsOnlineFolder)
{
  NS_ENSURE_ARG_POINTER(aVerifiedAsOnlineFolder);
  *aVerifiedAsOnlineFolder = m_verifiedAsOnlineFolder;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetVerifiedAsOnlineFolder(bool aVerifiedAsOnlineFolder)
{
  m_verifiedAsOnlineFolder = aVerifiedAsOnlineFolder;
  // mark ancestors as verified as well
  if (aVerifiedAsOnlineFolder)
  {
    nsCOMPtr<nsIMsgFolder> parent;
    do
    {
      GetParent(getter_AddRefs(parent));
      if (parent)
      {
        nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
        if (imapParent)
        {
          bool verifiedOnline;
          imapParent->GetVerifiedAsOnlineFolder(&verifiedOnline);
          if (verifiedOnline)
            break;
          imapParent->SetVerifiedAsOnlineFolder(true);
        }
      }
    }
    while (parent);
  }
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetOnlineDelimiter(char* onlineDelimiter)
{
  return GetHierarchyDelimiter(onlineDelimiter);
}

NS_IMETHODIMP nsImapMailFolder::SetHierarchyDelimiter(char aHierarchyDelimiter)
{
  m_hierarchyDelimiter = aHierarchyDelimiter;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetHierarchyDelimiter(char *aHierarchyDelimiter)
{
  NS_ENSURE_ARG_POINTER(aHierarchyDelimiter);
  if (mIsServer)
  {
    // if it's the root folder, we don't know the delimiter. So look at the
    // first child.
    int32_t count = mSubFolders.Count();
    if (count > 0)
    {
      nsCOMPtr<nsIMsgImapMailFolder> childFolder(do_QueryInterface(mSubFolders[0]));
      if (childFolder)
      {
        nsresult rv = childFolder->GetHierarchyDelimiter(aHierarchyDelimiter);
        // some code uses m_hierarchyDelimiter directly, so we should set it.
        m_hierarchyDelimiter = *aHierarchyDelimiter;
        return rv;
      }
    }
  }
  ReadDBFolderInfo(false); // update cache first.
  *aHierarchyDelimiter = m_hierarchyDelimiter;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetBoxFlags(int32_t aBoxFlags)
{
  ReadDBFolderInfo(false);

  m_boxFlags = aBoxFlags;
  uint32_t newFlags = mFlags;

  newFlags |= nsMsgFolderFlags::ImapBox;

  if (m_boxFlags & kNoinferiors)
    newFlags |= nsMsgFolderFlags::ImapNoinferiors;
  else
    newFlags &= ~nsMsgFolderFlags::ImapNoinferiors;
  if (m_boxFlags & kNoselect)
    newFlags |= nsMsgFolderFlags::ImapNoselect;
  else
    newFlags &= ~nsMsgFolderFlags::ImapNoselect;
  if (m_boxFlags & kPublicMailbox)
    newFlags |= nsMsgFolderFlags::ImapPublic;
  else
    newFlags &= ~nsMsgFolderFlags::ImapPublic;
  if (m_boxFlags & kOtherUsersMailbox)
    newFlags |= nsMsgFolderFlags::ImapOtherUser;
  else
    newFlags &= ~nsMsgFolderFlags::ImapOtherUser;
  if (m_boxFlags & kPersonalMailbox)
    newFlags |= nsMsgFolderFlags::ImapPersonal;
  else
    newFlags &= ~nsMsgFolderFlags::ImapPersonal;

  // The following are all flags returned by XLIST.
  // nsImapIncomingServer::DiscoveryDone checks for these folders.
  if (m_boxFlags & kImapDrafts)
    newFlags |= nsMsgFolderFlags::Drafts;

  if (m_boxFlags & kImapSpam)
    newFlags |= nsMsgFolderFlags::Junk;

  if (m_boxFlags & kImapSent)
    newFlags |= nsMsgFolderFlags::SentMail;

  if (m_boxFlags & kImapInbox)
    newFlags |= nsMsgFolderFlags::Inbox;

  if (m_boxFlags & kImapXListTrash)
  {
    nsCOMPtr<nsIImapIncomingServer> imapServer;
    nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
    (void) GetImapIncomingServer(getter_AddRefs(imapServer));
    if (imapServer)
      imapServer->GetDeleteModel(&deleteModel);
    if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
      newFlags |= nsMsgFolderFlags::Trash;
  }
  // Treat the GMail all mail folder as the archive folder.
  if (m_boxFlags & (kImapAllMail | kImapArchive))
    newFlags |= nsMsgFolderFlags::Archive;

  SetFlags(newFlags);
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetBoxFlags(int32_t *aBoxFlags)
{
  NS_ENSURE_ARG_POINTER(aBoxFlags);
  *aBoxFlags = m_boxFlags;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetExplicitlyVerify(bool *aExplicitlyVerify)
{
  NS_ENSURE_ARG_POINTER(aExplicitlyVerify);
  *aExplicitlyVerify = m_explicitlyVerify;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetExplicitlyVerify(bool aExplicitlyVerify)
{
  m_explicitlyVerify = aExplicitlyVerify;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetNoSelect(bool *aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  return GetFlag(nsMsgFolderFlags::ImapNoselect, aResult);
}

NS_IMETHODIMP nsImapMailFolder::ApplyRetentionSettings()
{
  int32_t numDaysToKeepOfflineMsgs = -1;

  // Check if we've limited the offline storage by age.
  nsCOMPtr<nsIImapIncomingServer> imapServer;
  nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
  NS_ENSURE_SUCCESS(rv, rv);
  imapServer->GetAutoSyncMaxAgeDays(&numDaysToKeepOfflineMsgs);

  nsCOMPtr<nsIMsgDatabase> holdDBOpen;
  if (numDaysToKeepOfflineMsgs > 0)
  {
    bool dbWasCached = mDatabase != nullptr;
    rv = GetDatabase();
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr <nsISimpleEnumerator> hdrs;
    rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs));
    NS_ENSURE_SUCCESS(rv, rv);
    bool hasMore = false;

    PRTime cutOffDay =
      MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs);

    nsCOMPtr <nsIMsgDBHdr> pHeader;
    // so now cutOffDay is the PRTime cut-off point. Any offline msg with
    // a date less than that will get marked for pending removal.
    while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
    {
      nsCOMPtr<nsISupports> supports;
      rv = hdrs->GetNext(getter_AddRefs(supports));
      NS_ENSURE_SUCCESS(rv, rv);
      pHeader = do_QueryInterface(supports, &rv);
      NS_ENSURE_SUCCESS(rv, rv);

      uint32_t msgFlags;
      PRTime msgDate;
      pHeader->GetFlags(&msgFlags);
      if (msgFlags & nsMsgMessageFlags::Offline)
      {
        pHeader->GetDate(&msgDate);
        MarkPendingRemoval(pHeader, msgDate < cutOffDay);
        // I'm horribly tempted to break out of the loop if we've found
        // a message after the cut-off date, because messages will most likely
        // be in date order in the db, but there are always edge cases.
      }
    }
    if (!dbWasCached)
    {
      holdDBOpen = mDatabase;
      mDatabase = nullptr;
    }
  }
  return nsMsgDBFolder::ApplyRetentionSettings();
}

/**
 * The listener will get called when both the online expunge and the offline
 * store compaction are finished (if the latter is needed).
 */
NS_IMETHODIMP nsImapMailFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
{
  GetDatabase();
  // now's a good time to apply the retention settings. If we do delete any
  // messages, the expunge is going to have to wait until the delete to
  // finish before it can run, but the multiple-connection protection code
  // should handle that.
  if (mDatabase)
    ApplyRetentionSettings();

  m_urlListener = aListener;
  // We should be able to compact the offline store now that this should
  // just be called by the UI.
  if (aMsgWindow && (mFlags & nsMsgFolderFlags::Offline))
  {
    m_compactingOfflineStore = true;
    CompactOfflineStore(aMsgWindow, this);
  }
  if (WeAreOffline())
    return NS_OK;
  m_expunging = true;
  return Expunge(this, aMsgWindow);
}

NS_IMETHODIMP
nsImapMailFolder::NotifyCompactCompleted()
{
  if (!m_expunging && m_urlListener)
  {
    m_urlListener->OnStopRunningUrl(nullptr, NS_OK);
    m_urlListener = nullptr;
  }
  m_compactingOfflineStore = false;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::MarkPendingRemoval(nsIMsgDBHdr *aHdr, bool aMark)
{
  NS_ENSURE_ARG_POINTER(aHdr);
  uint32_t offlineMessageSize;
  aHdr->GetOfflineMessageSize(&offlineMessageSize);
  aHdr->SetStringProperty("pendingRemoval", aMark ? "1" : "");
  if (!aMark)
    return NS_OK;
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
  rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
  NS_ENSURE_SUCCESS(rv, rv);
  return dbFolderInfo->ChangeExpungedBytes(offlineMessageSize);
}

NS_IMETHODIMP nsImapMailFolder::Expunge(nsIUrlListener *aListener,
                                        nsIMsgWindow *aMsgWindow)
{
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return imapService->Expunge(this, aListener, aMsgWindow, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::CompactAll(nsIUrlListener *aListener,
                                               nsIMsgWindow *aMsgWindow,
                                               bool aCompactOfflineAlso)
{
  nsresult rv;
  nsCOMPtr<nsIMutableArray> folderArray, offlineFolderArray;

  nsCOMPtr<nsIMsgFolder> rootFolder;
  nsCOMPtr<nsIArray> allDescendents;
  rv = GetRootFolder(getter_AddRefs(rootFolder));
  if (NS_SUCCEEDED(rv) && rootFolder)
  {
    rootFolder->GetDescendants(getter_AddRefs(allDescendents));
    uint32_t cnt = 0;
    rv = allDescendents->GetLength(&cnt);
    NS_ENSURE_SUCCESS(rv, rv);
    folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
    NS_ENSURE_TRUE(folderArray, rv);
    if (aCompactOfflineAlso)
    {
      offlineFolderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
      NS_ENSURE_TRUE(offlineFolderArray, rv);
    }
    for (uint32_t i = 0; i < cnt; i++)
    {
      nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      uint32_t folderFlags;
      folder->GetFlags(&folderFlags);
      if (! (folderFlags & (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect)))
      {
        rv = folderArray->AppendElement(folder);
        if (aCompactOfflineAlso)
          offlineFolderArray->AppendElement(folder);
      }
    }
    rv = folderArray->GetLength(&cnt);
    NS_ENSURE_SUCCESS(rv, rv);
    if (cnt == 0)
      return NotifyCompactCompleted();
  }
  nsCOMPtr <nsIMsgFolderCompactor> folderCompactor =
    do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  return folderCompactor->CompactFolders(folderArray, offlineFolderArray,
                                         aListener, aMsgWindow);
}

NS_IMETHODIMP nsImapMailFolder::UpdateStatus(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
{
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  nsCOMPtr <nsIURI> uri;
  rv = imapService->UpdateFolderStatus(this, aListener, getter_AddRefs(uri));
  if (uri && !aMsgWindow)
  {
    nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(uri, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    // if no msg window, we won't put up error messages (this is almost certainly a biff-inspired status)
    mailNewsUrl->SetSuppressErrorMsgs(true);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::EmptyTrash(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener)
{
  nsCOMPtr<nsIMsgFolder> trashFolder;
  nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder));
  if (NS_SUCCEEDED(rv))
  {
    nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    // if we are emptying trash on exit and we are an aol server then don't perform
    // this operation because it's causing a hang that we haven't been able to figure out yet
    // this is an rtm fix and we'll look for the right solution post rtm.
    bool empytingOnExit = false;
    accountManager->GetEmptyTrashInProgress(&empytingOnExit);
    if (empytingOnExit)
    {
      nsCOMPtr<nsIImapIncomingServer> imapServer;
      rv = GetImapIncomingServer(getter_AddRefs(imapServer));
      if (imapServer)
      {
        bool isAOLServer = false;
        imapServer->GetIsAOLServer(&isAOLServer);
        if (isAOLServer)
          return NS_ERROR_FAILURE;  // we will not be performing an empty trash....
      } // if we fetched an imap server
    } // if emptying trash on exit which is done through the account manager.

    if (WeAreOffline())
    {
      nsCOMPtr <nsIMsgDatabase> trashDB;
      rv = trashFolder->GetMsgDatabase(getter_AddRefs(trashDB));
      if (trashDB)
      {
        nsMsgKey fakeKey;
        trashDB->GetNextFakeOfflineMsgKey(&fakeKey);

        nsCOMPtr <nsIMsgOfflineImapOperation> op;
        rv = trashDB->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
        trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
        op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs);
      }
      return rv;
    }

    nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    if (aListener)
      rv = imapService->DeleteAllMessages(trashFolder, aListener, nullptr);
    else
    {
      nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(trashFolder);
      rv = imapService->DeleteAllMessages(trashFolder, urlListener, nullptr);
    }
    // Return an error if this failed. We want the empty trash on exit code
    // to know if this fails so that it doesn't block waiting for empty trash to finish.
    NS_ENSURE_SUCCESS(rv, rv);

    bool hasSubfolders = false;
    rv = trashFolder->GetHasSubFolders(&hasSubfolders);
    NS_ENSURE_SUCCESS(rv, rv);
    if (hasSubfolders)
    {
      nsCOMPtr<nsISimpleEnumerator> enumerator;
      nsCOMPtr<nsISupports> item;
      nsCOMArray<nsIMsgFolder> array;

      rv = trashFolder->GetSubFolders(getter_AddRefs(enumerator));
      NS_ENSURE_SUCCESS(rv, rv);

      bool hasMore;
      while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
      {
        rv = enumerator->GetNext(getter_AddRefs(item));
        if (NS_SUCCEEDED(rv))
        {
          nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item, &rv));
          if (NS_SUCCEEDED(rv))
            array.AppendObject(folder);
        }
      }
      for (int32_t i = array.Count() - 1; i >= 0; i--)
      {
        trashFolder->PropagateDelete(array[i], true, aMsgWindow);
        // Remove the object, presumably to free it up before we delete the next.
        array.RemoveObjectAt(i);
      }
    }

    nsCOMPtr <nsIDBFolderInfo> transferInfo;
    rv = trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo));
    NS_ENSURE_SUCCESS(rv, rv);
    // Bulk-delete all the messages by deleting the msf file and storage.
    // This is a little kludgy.
    rv = trashFolder->Delete();
    NS_ENSURE_SUCCESS(rv, rv);
    trashFolder->SetDBTransferInfo(transferInfo);
    trashFolder->SetSizeOnDisk(0);

    // The trash folder has effectively been deleted.
    nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
    if (notifier)
      notifier->NotifyFolderDeleted(trashFolder);

    return NS_OK;
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::Delete()
{
  nsresult rv = nsMsgDBFolder::Delete();

  // Should notify nsIMsgFolderListeners about the folder getting deleted?
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::Rename (const nsAString& newName, nsIMsgWindow *msgWindow)
{
  if (mFlags & nsMsgFolderFlags::Virtual)
    return nsMsgDBFolder::Rename(newName, msgWindow);
  nsresult rv;
  nsAutoString newNameStr(newName);
  if (newNameStr.FindChar(m_hierarchyDelimiter, 0) != kNotFound)
  {
    nsCOMPtr<nsIDocShell> docShell;
    if (msgWindow)
      msgWindow->GetRootDocShell(getter_AddRefs(docShell));
    if (docShell)
    {
      nsCOMPtr<nsIStringBundle> bundle;
      rv = IMAPGetStringBundle(getter_AddRefs(bundle));
      if (NS_SUCCEEDED(rv) && bundle)
      {
        const char16_t delimiter[2] = { (char16_t)m_hierarchyDelimiter, '\0' };
        const char16_t *formatStrings[] = { delimiter };
        nsString alertString;
        rv = bundle->FormatStringFromName(
          "imapSpecialChar2",
          formatStrings, 1, alertString);
        nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
        // setting up the dialog title
        nsCOMPtr<nsIMsgIncomingServer> server;
        rv = GetServer(getter_AddRefs(server));
        NS_ENSURE_SUCCESS(rv, rv);
        nsString dialogTitle;
        nsString accountName;
        rv = server->GetPrettyName(accountName);
        NS_ENSURE_SUCCESS(rv, rv);
        const char16_t *titleParams[] = { accountName.get() };
        rv = bundle->FormatStringFromName(
          "imapAlertDialogTitle",
          titleParams, 1, dialogTitle);

        if (dialog && !alertString.IsEmpty())
          dialog->Alert(dialogTitle.get(), alertString.get());
      }
    }
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr <nsIImapIncomingServer> incomingImapServer;
  GetImapIncomingServer(getter_AddRefs(incomingImapServer));
  if (incomingImapServer)
    RecursiveCloseActiveConnections(incomingImapServer);

  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);
  return imapService->RenameLeaf(this, newName, this, msgWindow, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::RecursiveCloseActiveConnections(nsIImapIncomingServer *incomingImapServer)
{
  NS_ENSURE_ARG(incomingImapServer);

  nsCOMPtr<nsIMsgImapMailFolder> folder;
  int32_t count = mSubFolders.Count();
  for (int32_t i = 0; i < count; i++)
  {
    folder = do_QueryInterface(mSubFolders[i]);
    if (folder)
      folder->RecursiveCloseActiveConnections(incomingImapServer);

    incomingImapServer->CloseConnectionForFolder(mSubFolders[i]);
  }
  return NS_OK;
}

// this is called *after* we've done the rename on the server.
NS_IMETHODIMP nsImapMailFolder::PrepareToRename()
{
  nsCOMPtr<nsIMsgImapMailFolder> folder;
  int32_t count = mSubFolders.Count();
  for (int32_t i = 0; i < count; i++)
  {
    folder = do_QueryInterface(mSubFolders[i]);
    if (folder)
      folder->PrepareToRename();
  }

  SetOnlineName(EmptyCString());
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::RenameLocal(const nsACString& newName, nsIMsgFolder *parent)
{
  // XXX Here it's assumed that IMAP folder names are stored locally
  // in modified UTF-7 (ASCII-only) as is stored remotely.  If we ever change
  // this, we have to work with nsString instead of nsCString
  // (ref. bug 264071)
  nsAutoCString leafname(newName);
  nsAutoCString parentName;
  // newName always in the canonical form "greatparent/parentname/leafname"
  int32_t leafpos = leafname.RFindChar('/');
  if (leafpos >0)
      leafname.Cut(0, leafpos+1);
  m_msgParser = nullptr;
  PrepareToRename();
  CloseAndBackupFolderDB(leafname);

  nsresult rv = NS_OK;
  nsCOMPtr<nsIFile> oldPathFile;
  rv = GetFilePath(getter_AddRefs(oldPathFile));
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIFile> parentPathFile;
  rv = parent->GetFilePath(getter_AddRefs(parentPathFile));
  NS_ENSURE_SUCCESS(rv,rv);

  bool isDirectory = false;
  parentPathFile->IsDirectory(&isDirectory);
  if (!isDirectory)
  AddDirectorySeparator(parentPathFile);

  nsCOMPtr <nsIFile> dirFile;

  int32_t count = mSubFolders.Count();
  if (count > 0)
    rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));

  nsCOMPtr <nsIFile> oldSummaryFile;
  rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString newNameStr;
  oldSummaryFile->Remove(false);
  if (count > 0)
  {
    newNameStr = leafname;
    NS_MsgHashIfNecessary(newNameStr);
    newNameStr.AppendLiteral(FOLDER_SUFFIX8);
    nsAutoCString leafName;
    dirFile->GetNativeLeafName(leafName);
    if (!leafName.Equals(newNameStr))
      return dirFile->MoveToNative(nullptr, newNameStr);      // in case of rename operation leaf names will differ

    parentPathFile->AppendNative(newNameStr);    //only for move we need to progress further in case the parent differs
    bool isDirectory = false;
    parentPathFile->IsDirectory(&isDirectory);
    if (!isDirectory) {
      rv = parentPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
      NS_ENSURE_SUCCESS(rv, rv);
    } else {
      NS_ERROR("Directory already exists.");
    }
    rv = RecursiveCopy(dirFile, parentPathFile);
    NS_ENSURE_SUCCESS(rv,rv);
    dirFile->Remove(true);                         // moving folders
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetPrettyName(nsAString& prettyName)
{
  return GetName(prettyName);
}

NS_IMETHODIMP nsImapMailFolder::UpdateSummaryTotals(bool force)
{
  // bug 72871 inserted the mIsServer check for IMAP
  return mIsServer? NS_OK : nsMsgDBFolder::UpdateSummaryTotals(force);
}

NS_IMETHODIMP nsImapMailFolder::GetDeletable (bool *deletable)
{
  NS_ENSURE_ARG_POINTER(deletable);

  bool isServer;
  GetIsServer(&isServer);

  *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetSizeOnDisk(int64_t *size)
{
  NS_ENSURE_ARG_POINTER(size);

  bool isServer = false;
  nsresult rv = GetIsServer(&isServer);
  // If this is the rootFolder, return 0 as a safe value.
  if (NS_FAILED(rv) || isServer)
    mFolderSize = 0;

  *size = mFolderSize;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::GetCanCreateSubfolders(bool *aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = !(mFlags & (nsMsgFolderFlags::ImapNoinferiors | nsMsgFolderFlags::Virtual));

  bool isServer = false;
  GetIsServer(&isServer);
  if (!isServer)
  {
    nsCOMPtr<nsIImapIncomingServer> imapServer;
    nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
    bool dualUseFolders = true;
    if (NS_SUCCEEDED(rv) && imapServer)
      imapServer->GetDualUseFolders(&dualUseFolders);
    if (!dualUseFolders && *aResult)
      *aResult = (mFlags & nsMsgFolderFlags::ImapNoselect);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::GetCanSubscribe(bool *aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = false;

  bool isImapServer = false;
  nsresult rv = GetIsServer(&isImapServer);
  if (NS_FAILED(rv)) return rv;
  // you can only subscribe to imap servers, not imap folders
  *aResult = isImapServer;
  return NS_OK;
}

nsresult nsImapMailFolder::GetServerKey(nsACString& serverKey)
{
  // look for matching imap folders, then pop folders
  nsCOMPtr<nsIMsgIncomingServer> server;
  nsresult rv = GetServer(getter_AddRefs(server));
  if (NS_SUCCEEDED(rv))
    rv = server->GetKey(serverKey);
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::GetImapIncomingServer(nsIImapIncomingServer **aImapIncomingServer)
{
  NS_ENSURE_ARG(aImapIncomingServer);
  nsCOMPtr<nsIMsgIncomingServer> server;
  if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
  {
    nsCOMPtr <nsIImapIncomingServer> incomingServer = do_QueryInterface(server);
    incomingServer.forget(aImapIncomingServer);
    return NS_OK;
  }
  return NS_ERROR_NULL_POINTER;
}

NS_IMETHODIMP
nsImapMailFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag)
{
  nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag);

  // set the mark message answered flag on the server for this message...
  if (aMessage)
  {
    nsMsgKey msgKey;
    aMessage->GetMessageKey(&msgKey);

    if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
      StoreImapFlags(kImapMsgAnsweredFlag, true, &msgKey, 1, nullptr);
    else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
      StoreImapFlags(kImapMsgForwardedFlag, true, &msgKey, 1, nullptr);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::MarkMessagesRead(nsIArray *messages, bool markRead)
{
  // tell the folder to do it, which will mark them read in the db.
  nsresult rv = nsMsgDBFolder::MarkMessagesRead(messages, markRead);
  if (NS_SUCCEEDED(rv))
  {
    nsAutoCString messageIds;
    nsTArray<nsMsgKey> keysToMarkRead;
    rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkRead);
    NS_ENSURE_SUCCESS(rv, rv);

    StoreImapFlags(kImapMsgSeenFlag, markRead, keysToMarkRead.Elements(), keysToMarkRead.Length(), nullptr);
    rv = GetDatabase();
    if (NS_SUCCEEDED(rv))
      mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::SetLabelForMessages(nsIArray *aMessages, nsMsgLabelValue aLabel)
{
  NS_ENSURE_ARG(aMessages);

  nsresult rv = nsMsgDBFolder::SetLabelForMessages(aMessages, aLabel);
  if (NS_SUCCEEDED(rv))
  {
    nsAutoCString messageIds;
    nsTArray<nsMsgKey> keysToLabel;
    nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keysToLabel);
    NS_ENSURE_SUCCESS(rv, rv);
    StoreImapFlags((aLabel << 9), true, keysToLabel.Elements(), keysToLabel.Length(), nullptr);
    rv = GetDatabase();
    if (NS_SUCCEEDED(rv))
      mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow)
{
  nsresult rv = GetDatabase();
  if(NS_SUCCEEDED(rv))
  {
    nsMsgKey *thoseMarked;
    uint32_t numMarked;
    EnableNotifications(allMessageCountNotifications, false);
    rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked);
    EnableNotifications(allMessageCountNotifications, true);
    if (NS_SUCCEEDED(rv) && numMarked)
    {
      rv = StoreImapFlags(kImapMsgSeenFlag, true, thoseMarked,
                          numMarked, nullptr);
      mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);

      // Setup a undo-state
      if (aMsgWindow)
        rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked);
      free(thoseMarked);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::MarkThreadRead(nsIMsgThread *thread)
{
  nsresult rv = GetDatabase();
  if(NS_SUCCEEDED(rv))
  {
    nsMsgKey *keys;
    uint32_t numKeys;
    rv = mDatabase->MarkThreadRead(thread, nullptr, &numKeys, &keys);
    if (NS_SUCCEEDED(rv) && numKeys)
    {
      rv = StoreImapFlags(kImapMsgSeenFlag, true, keys, numKeys, nullptr);
      mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
      free(keys);
    }
  }
  return rv;
}


NS_IMETHODIMP nsImapMailFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element)
{
  nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element);
  int32_t hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
  nsCString onlineName;

  element->GetInt32Property("boxFlags", &m_boxFlags);
  if (NS_SUCCEEDED(element->GetInt32Property("hierDelim", &hierarchyDelimiter))
      && hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)
    m_hierarchyDelimiter = (char) hierarchyDelimiter;
  rv = element->GetStringProperty("onlineName", onlineName);
  if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty())
    m_onlineFolderName.Assign(onlineName);

  m_aclFlags = kAclInvalid; // init to invalid value.
  element->GetInt32Property("aclFlags", (int32_t *) &m_aclFlags);
  element->GetInt32Property("serverTotal", &m_numServerTotalMessages);
  element->GetInt32Property("serverUnseen", &m_numServerUnseenMessages);
  element->GetInt32Property("serverRecent", &m_numServerRecentMessages);
  element->GetInt32Property("nextUID", &m_nextUID);
  int32_t lastSyncTimeInSec;
  if ( NS_FAILED(element->GetInt32Property("lastSyncTimeInSec", (int32_t *) &lastSyncTimeInSec)) )
    lastSyncTimeInSec = 0U;

  // make sure that auto-sync state object is created
  InitAutoSyncState();
  m_autoSyncStateObj->SetLastSyncTimeInSec(lastSyncTimeInSec);

  return rv;
}

NS_IMETHODIMP nsImapMailFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element)
{
  nsresult rv = nsMsgDBFolder::WriteToFolderCacheElem(element);
  element->SetInt32Property("boxFlags", m_boxFlags);
  element->SetInt32Property("hierDelim", (int32_t) m_hierarchyDelimiter);
  element->SetStringProperty("onlineName", m_onlineFolderName);
  element->SetInt32Property("aclFlags", (int32_t) m_aclFlags);
  element->SetInt32Property("serverTotal", m_numServerTotalMessages);
  element->SetInt32Property("serverUnseen", m_numServerUnseenMessages);
  element->SetInt32Property("serverRecent", m_numServerRecentMessages);
  if (m_nextUID != (int32_t) nsMsgKey_None)
    element->SetInt32Property("nextUID", m_nextUID);

  // store folder's last sync time
  if (m_autoSyncStateObj)
  {
    PRTime lastSyncTime;
    m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime);
    // store in sec
    element->SetInt32Property("lastSyncTimeInSec", (int32_t) (lastSyncTime / PR_USEC_PER_SEC));
  }

  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::MarkMessagesFlagged(nsIArray *messages, bool markFlagged)
{
  nsresult rv;
  // tell the folder to do it, which will mark them read in the db.
  rv = nsMsgDBFolder::MarkMessagesFlagged(messages, markFlagged);
  if (NS_SUCCEEDED(rv))
  {
    nsAutoCString messageIds;
    nsTArray<nsMsgKey> keysToMarkFlagged;
    rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkFlagged);
    if (NS_FAILED(rv)) return rv;
    rv = StoreImapFlags(kImapMsgFlaggedFlag, markFlagged,  keysToMarkFlagged.Elements(),
                        keysToMarkFlagged.Length(), nullptr);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = GetDatabase();
    NS_ENSURE_SUCCESS(rv, rv);
    mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::SetOnlineName(const nsACString& aOnlineFolderName)
{
  nsresult rv;
  nsCOMPtr<nsIMsgDatabase> db;
  nsCOMPtr<nsIDBFolderInfo> folderInfo;
  rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
  // do this after GetDBFolderInfoAndDB, because it crunches m_onlineFolderName (not sure why)
  m_onlineFolderName = aOnlineFolderName;
  if(NS_SUCCEEDED(rv) && folderInfo)
  {
    nsAutoString onlineName;
    CopyASCIItoUTF16(aOnlineFolderName, onlineName);
    rv = folderInfo->SetProperty("onlineName", onlineName);
    rv = folderInfo->SetMailboxName(onlineName);
    // so, when are we going to commit this? Definitely not every time!
    // We could check if the online name has changed.
    db->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  folderInfo = nullptr;
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetOnlineName(nsACString& aOnlineFolderName)
{
  ReadDBFolderInfo(false); // update cache first.
  aOnlineFolderName = m_onlineFolderName;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db)
{
  NS_ENSURE_ARG_POINTER (folderInfo);
  NS_ENSURE_ARG_POINTER (db);

  nsresult rv = GetDatabase();
  if (NS_FAILED(rv))
    return rv;

  NS_ADDREF(*db = mDatabase);

  rv = (*db)->GetDBFolderInfo(folderInfo);
  if (NS_FAILED(rv))
    return rv; //GetDBFolderInfo can't return NS_OK if !folderInfo

  nsCString onlineName;
  rv = (*folderInfo)->GetCharProperty("onlineName", onlineName);
  if (NS_FAILED(rv))
    return rv;

  if (!onlineName.IsEmpty())
    m_onlineFolderName.Assign(onlineName);
  else
  {
    nsAutoString autoOnlineName;
    (*folderInfo)->GetMailboxName(autoOnlineName);
    if (autoOnlineName.IsEmpty())
    {
      nsCString uri;
      rv = GetURI(uri);
      NS_ENSURE_SUCCESS(rv, rv);

      nsCString hostname;
      rv = GetHostname(hostname);
      NS_ENSURE_SUCCESS(rv, rv);

      nsCString onlineCName;
      rv = nsImapURI2FullName(kImapRootURI, hostname.get(), uri.get(), getter_Copies(onlineCName));
      if (m_hierarchyDelimiter != '/')
        MsgReplaceChar(onlineCName, '/', m_hierarchyDelimiter);
      m_onlineFolderName.Assign(onlineCName);
      CopyASCIItoUTF16(onlineCName, autoOnlineName);
    }
    (*folderInfo)->SetProperty("onlineName", autoOnlineName);
  }
  return rv;
}

/* static */ nsresult
nsImapMailFolder::BuildIdsAndKeyArray(nsIArray* messages,
                                      nsCString& msgIds,
                                      nsTArray<nsMsgKey>& keyArray)
{
  NS_ENSURE_ARG_POINTER(messages);
  nsresult rv;
  uint32_t count = 0;
  uint32_t i;
  rv = messages->GetLength(&count);
  if (NS_FAILED(rv)) return rv;

  // build up message keys.
  for (i = 0; i < count; i++)
  {
    nsMsgKey key;
    nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
    if (msgDBHdr)
      rv = msgDBHdr->GetMessageKey(&key);
    if (NS_SUCCEEDED(rv))
      keyArray.AppendElement(key);
  }
  return AllocateUidStringFromKeys(keyArray.Elements(), keyArray.Length(), msgIds);
}

static int CompareKey (const void *v1, const void *v2, void *)
{
  // QuickSort callback to compare array values
  nsMsgKey i1 = *(nsMsgKey *)v1;
  nsMsgKey i2 = *(nsMsgKey *)v2;
  return i1 - i2;
}

/* static */nsresult
nsImapMailFolder::AllocateUidStringFromKeys(nsMsgKey *keys, uint32_t numKeys, nsCString &msgIds)
{
  if (!numKeys)
    return NS_ERROR_INVALID_ARG;
  nsresult rv = NS_OK;
  uint32_t startSequence;
  startSequence = keys[0];
  uint32_t curSequenceEnd = startSequence;
  uint32_t total = numKeys;
  // sort keys and then generate ranges instead of singletons!
  NS_QuickSort(keys, numKeys, sizeof(nsMsgKey), CompareKey, nullptr);
  for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
  {
    uint32_t curKey = keys[keyIndex];
    uint32_t nextKey = (keyIndex + 1 < total) ? keys[keyIndex + 1] : 0xFFFFFFFF;
    bool lastKey = (nextKey == 0xFFFFFFFF);

    if (lastKey)
      curSequenceEnd = curKey;
    if (nextKey == (uint32_t) curSequenceEnd + 1 && !lastKey)
    {
      curSequenceEnd = nextKey;
      continue;
    }
    if (curSequenceEnd > startSequence)
    {
      AppendUid(msgIds, startSequence);
      msgIds += ':';
      AppendUid(msgIds,curSequenceEnd);
      if (!lastKey)
        msgIds += ',';
      startSequence = nextKey;
      curSequenceEnd = startSequence;
    }
    else
    {
      startSequence = nextKey;
      curSequenceEnd = startSequence;
      AppendUid(msgIds, keys[keyIndex]);
      if (!lastKey)
        msgIds += ',';
    }
  }
  return rv;
}

nsresult nsImapMailFolder::MarkMessagesImapDeleted(nsTArray<nsMsgKey> *keyArray, bool deleted, nsIMsgDatabase *db)
{
  for (uint32_t kindex = 0; kindex < keyArray->Length(); kindex++)
  {
    nsMsgKey key = keyArray->ElementAt(kindex);
    db->MarkImapDeleted(key, deleted, nullptr);
  }
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::DeleteMessages(nsIArray *messages,
                                               nsIMsgWindow *msgWindow,
                                               bool deleteStorage, bool isMove,
                                               nsIMsgCopyServiceListener* listener,
                                               bool allowUndo)
{
  // *** jt - assuming delete is move to the trash folder for now
  nsCOMPtr<nsIRDFResource> res;
  nsAutoCString uri;
  bool deleteImmediatelyNoTrash = false;
  nsAutoCString messageIds;
  nsTArray<nsMsgKey> srcKeyArray;
  bool deleteMsgs = true;  //used for toggling delete status - default is true
  nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
  imapMessageFlagsType messageFlags = kImapMsgDeletedFlag;

  nsCOMPtr<nsIImapIncomingServer> imapServer;
  nsresult rv = GetFlag(nsMsgFolderFlags::Trash, &deleteImmediatelyNoTrash);
  rv = GetImapIncomingServer(getter_AddRefs(imapServer));

  if (NS_SUCCEEDED(rv) && imapServer)
  {
    imapServer->GetDeleteModel(&deleteModel);
    if (deleteModel != nsMsgImapDeleteModels::MoveToTrash || deleteStorage)
      deleteImmediatelyNoTrash = true;
    // if we're deleting a message, we should pseudo-interrupt the msg
    //load of the current message.
    bool interrupted = false;
    imapServer->PseudoInterruptMsgLoad(this, msgWindow, &interrupted);
  }

  rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIMsgFolder> rootFolder;
  nsCOMPtr<nsIMsgFolder> trashFolder;

  if (!deleteImmediatelyNoTrash)
  {
    rv = GetRootFolder(getter_AddRefs(rootFolder));
    if (NS_SUCCEEDED(rv) && rootFolder)
    {
      rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
                                     getter_AddRefs(trashFolder));
      NS_ASSERTION(trashFolder, "couldn't find trash");
      // if we can't find the trash, we'll just have to do an imap delete and pretend this is the trash
      if (!trashFolder)
        deleteImmediatelyNoTrash = true;
    }
  }

  if ((NS_SUCCEEDED(rv) && deleteImmediatelyNoTrash) || deleteModel == nsMsgImapDeleteModels::IMAPDelete )
  {
    if (allowUndo)
    {
      //need to take care of these two delete models
      RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
      if (!undoMsgTxn || NS_FAILED(undoMsgTxn->Init(this, &srcKeyArray, messageIds.get(), nullptr,
                                                    true, isMove)))
        return NS_ERROR_OUT_OF_MEMORY;

      undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
      // we're adding this undo action before the delete is successful. This is evil,
      // but 4.5 did it as well.
      nsCOMPtr <nsITransactionManager> txnMgr;
      if (msgWindow)
        msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
      if (txnMgr)
        txnMgr->DoTransaction(undoMsgTxn);
    }

    if (deleteModel == nsMsgImapDeleteModels::IMAPDelete && !deleteStorage)
    {
      uint32_t cnt, flags;
      rv = messages->GetLength(&cnt);
      NS_ENSURE_SUCCESS(rv, rv);
      deleteMsgs = false;
      for (uint32_t i=0; i <cnt; i++)
      {
        nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i);
        if (msgHdr)
        {
          msgHdr->GetFlags(&flags);
          if (!(flags & nsMsgMessageFlags::IMAPDeleted))
          {
            deleteMsgs = true;
            break;
          }
        }
      }
    }
    // if copy service listener is also a url listener, pass that
    // url listener into StoreImapFlags.
    nsCOMPtr <nsIUrlListener> urlListener = do_QueryInterface(listener);
    if (deleteMsgs)
      messageFlags |= kImapMsgSeenFlag;
    rv = StoreImapFlags(messageFlags, deleteMsgs, srcKeyArray.Elements(),
                        srcKeyArray.Length(), urlListener);

    if (NS_SUCCEEDED(rv))
    {
      if (mDatabase)
      {
        nsCOMPtr<nsIMsgDatabase> database(mDatabase);
        if (deleteModel == nsMsgImapDeleteModels::IMAPDelete)
          MarkMessagesImapDeleted(&srcKeyArray, deleteMsgs, database);
        else
        {
          EnableNotifications(allMessageCountNotifications, false);  //"remove it immediately" model
          // Notify if this is an actual delete.
          if (!isMove)
          {
            nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
            if (notifier)
              notifier->NotifyMsgsDeleted(messages);
          }
          DeleteStoreMessages(messages);
          database->DeleteMessages(srcKeyArray.Length(), srcKeyArray.Elements(), nullptr);
          EnableNotifications(allMessageCountNotifications, true);
        }
        NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
      }
    }
    return rv;
  }

  // have to move the messages to the trash
  if(trashFolder)
  {
    nsCOMPtr<nsIMsgFolder> srcFolder;
    nsCOMPtr<nsISupports>srcSupport;
    uint32_t count = 0;
    rv = messages->GetLength(&count);

    rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(srcFolder));
    nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = copyService->CopyMessages(srcFolder, messages, trashFolder, true, listener, msgWindow, allowUndo);
  }

  return rv;
}

// check if folder is the trash, or a descendent of the trash
// so we can tell if the folders we're deleting from it should
// be *really* deleted.
bool
nsImapMailFolder::TrashOrDescendentOfTrash(nsIMsgFolder* folder)
{
  NS_ENSURE_TRUE(folder, false);
  nsCOMPtr<nsIMsgFolder> parent;
  nsCOMPtr<nsIMsgFolder> curFolder = folder;
  nsresult rv;
  uint32_t flags = 0;
  do
  {
    rv = curFolder->GetFlags(&flags);
    if (NS_FAILED(rv)) return false;
    if (flags & nsMsgFolderFlags::Trash)
      return true;
    curFolder->GetParent(getter_AddRefs(parent));
    if (!parent) return false;
    curFolder = parent;
  } while (NS_SUCCEEDED(rv) && curFolder);
  return false;
}
NS_IMETHODIMP
nsImapMailFolder::DeleteSubFolders(nsIArray* folders, nsIMsgWindow *msgWindow)
{
  nsCOMPtr<nsIMsgFolder> curFolder;
  nsCOMPtr<nsIUrlListener> urlListener;
  nsCOMPtr<nsIMsgFolder> trashFolder;
  int32_t i;
  uint32_t folderCount = 0;
  nsresult rv;
  // "this" is the folder we're deleting from
  bool deleteNoTrash = TrashOrDescendentOfTrash(this) || !DeleteIsMoveToTrash();
  bool confirmed = false;
  bool confirmDeletion = true;

  nsCOMPtr<nsIMutableArray> foldersRemaining(do_CreateInstance(NS_ARRAY_CONTRACTID));
  folders->GetLength(&folderCount);

  for (i = folderCount - 1; i >= 0; i--)
  {
    curFolder = do_QueryElementAt(folders, i, &rv);
    if (NS_SUCCEEDED(rv))
    {
      uint32_t folderFlags;
      curFolder->GetFlags(&folderFlags);
      if (folderFlags & nsMsgFolderFlags::Virtual)
      {
        RemoveSubFolder(curFolder);
        // since the folder pane only allows single selection, we can do this
        deleteNoTrash = confirmed = true;
        confirmDeletion = false;
      }
      else
        foldersRemaining->InsertElementAt(curFolder, 0);
    }
  }

  foldersRemaining->GetLength(&folderCount);

  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!deleteNoTrash)
  {
    rv = GetTrashFolder(getter_AddRefs(trashFolder));
    //If we can't find the trash folder and we are supposed to move it to the trash
    //return failure.
    if(NS_FAILED(rv) || !trashFolder)
      return NS_ERROR_FAILURE;
    bool canHaveSubFoldersOfTrash = true;
    trashFolder->GetCanCreateSubfolders(&canHaveSubFoldersOfTrash);
    if (canHaveSubFoldersOfTrash) // UW server doesn't set NOINFERIORS - check dual use pref
    {
      nsCOMPtr<nsIImapIncomingServer> imapServer;
      rv = GetImapIncomingServer(getter_AddRefs(imapServer));
      NS_ENSURE_SUCCESS(rv, rv);
      bool serverSupportsDualUseFolders;
      imapServer->GetDualUseFolders(&serverSupportsDualUseFolders);
      if (!serverSupportsDualUseFolders)
        canHaveSubFoldersOfTrash = false;
    }
    if (!canHaveSubFoldersOfTrash)
      deleteNoTrash = true;
    nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    prefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", &confirmDeletion);
  }
  if (!confirmed && (confirmDeletion || deleteNoTrash)) //let us alert the user if we are deleting folder immediately
  {
    nsCOMPtr<nsIStringBundle> bundle;
    rv = IMAPGetStringBundle(getter_AddRefs(bundle));
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString folderName;
    rv = curFolder->GetName(folderName);
    NS_ENSURE_SUCCESS(rv, rv);
    const char16_t *formatStrings[1] = { folderName.get() };

    nsAutoString deleteFolderDialogTitle;
    rv = bundle->GetStringFromName(
      "imapDeleteFolderDialogTitle",
      deleteFolderDialogTitle);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString deleteFolderButtonLabel;
    rv = bundle->GetStringFromName(
      "imapDeleteFolderButtonLabel",
      deleteFolderButtonLabel);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString confirmationStr;
    rv = bundle->FormatStringFromName((deleteNoTrash) ?
        "imapDeleteNoTrash" :
        "imapMoveFolderToTrash",
      formatStrings, 1, confirmationStr);
    NS_ENSURE_SUCCESS(rv, rv);
    if (!msgWindow)
      return NS_ERROR_NULL_POINTER;
    nsCOMPtr<nsIDocShell> docShell;
    msgWindow->GetRootDocShell(getter_AddRefs(docShell));
    nsCOMPtr<nsIPrompt> dialog;
    if (docShell)
      dialog = do_GetInterface(docShell);
    if (dialog)
    {
      int32_t buttonPressed = 0;
      // Default the dialog to "cancel".
      const uint32_t buttonFlags =
        (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
        (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);

      bool dummyValue = false;
      rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(), confirmationStr.get(),
                             buttonFlags,  deleteFolderButtonLabel.get(),
                             nullptr, nullptr, nullptr, &dummyValue,
                             &buttonPressed);
      NS_ENSURE_SUCCESS(rv, rv);
      confirmed = !buttonPressed; // "ok" is in position 0
    }
  }
  else
    confirmed = true;

  if (confirmed)
  {
    for (i = 0; i < (int32_t) folderCount; i++)
    {
      curFolder = do_QueryElementAt(foldersRemaining, i, &rv);
      if (NS_SUCCEEDED(rv))
      {
        urlListener = do_QueryInterface(curFolder);
        if (deleteNoTrash)
          rv = imapService->DeleteFolder(curFolder,
                                         urlListener,
                                         msgWindow,
                                         nullptr);
        else
        {
          bool confirm = false;
          bool match = false;
          rv = curFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
          if (match)
          {
            curFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirm);
            if (!confirm)
              return NS_OK;
          }
          rv = imapService->MoveFolder(curFolder,
                                       trashFolder,
                                       urlListener,
                                       msgWindow,
                                       nullptr);
        }
      }
    }
  }
  //delete subfolders only if you are  deleting things from trash
  return confirmed && deleteNoTrash ? nsMsgDBFolder::DeleteSubFolders(foldersRemaining, msgWindow) : rv;
}

// FIXME: helper function to know whether we should check all IMAP folders
// for new mail; this is necessary because of a legacy hidden preference
// mail.check_all_imap_folders_for_new (now replaced by per-server preference
// mail.server.%serverkey%.check_all_folders_for_new), still present in some
// profiles.
/*static*/
bool nsImapMailFolder::ShouldCheckAllFolders(nsIImapIncomingServer *imapServer)
{
  // Check legacy global preference to see if we should check all folders for
  // new messages, or just the inbox and marked ones.
  bool checkAllFolders = false;
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, false);
  // This pref might not exist, which is OK.
  (void) prefBranch->GetBoolPref("mail.check_all_imap_folders_for_new", &checkAllFolders);

  if (checkAllFolders)
    return true;

  // If the legacy preference doesn't exist or has its default value (False),
  // the true preference is read.
  imapServer->GetCheckAllFoldersForNew(&checkAllFolders);
  return checkAllFolders;
}

// Called by Biff, or when user presses GetMsg button.
NS_IMETHODIMP nsImapMailFolder::GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener)
{
  nsCOMPtr<nsIMsgFolder> rootFolder;
  nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
  if(NS_SUCCEEDED(rv) && rootFolder)
  {
    nsCOMPtr<nsIImapIncomingServer> imapServer;
    rv = GetImapIncomingServer(getter_AddRefs(imapServer));
    NS_ENSURE_SUCCESS(rv, rv);
    bool performingBiff = false;
    nsCOMPtr<nsIMsgIncomingServer> incomingServer = do_QueryInterface(imapServer, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    incomingServer->GetPerformingBiff(&performingBiff);
    m_urlListener = aListener;

    // See if we should check all folders for new messages, or just the inbox
    // and marked ones
    bool checkAllFolders = ShouldCheckAllFolders(imapServer);

    // Get new messages for inbox
    nsCOMPtr<nsIMsgFolder> inbox;
    rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
                                        getter_AddRefs(inbox));
    if (inbox)
    {
      nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(inbox, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      imapFolder->SetPerformingBiff(performingBiff);
      inbox->SetGettingNewMessages(true);
      rv = inbox->UpdateFolder(aWindow);
    }
    // Get new messages for other folders if marked, or all of them if the pref is set
    rv = imapServer->GetNewMessagesForNonInboxFolders(rootFolder, aWindow, checkAllFolders, performingBiff);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::Shutdown(bool shutdownChildren)
{
  m_filterList = nullptr;
  m_initialized = false;
  // mPath is used to decide if folder pathname needs to be reconstructed in GetPath().
  mPath = nullptr;
  m_moveCoalescer = nullptr;
  m_msgParser = nullptr;
  if (m_playbackTimer)
  {
    m_playbackTimer->Cancel();
    m_playbackTimer = nullptr;
  }
  m_pendingOfflineMoves.Clear();
  return nsMsgDBFolder::Shutdown(shutdownChildren);
}

nsresult nsImapMailFolder::GetBodysToDownload(nsTArray<nsMsgKey> *keysOfMessagesToDownload)
{
  NS_ENSURE_ARG(keysOfMessagesToDownload);
  NS_ENSURE_TRUE(mDatabase, NS_ERROR_FAILURE);

  nsCOMPtr <nsISimpleEnumerator> enumerator;
  nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
  if (NS_SUCCEEDED(rv) && enumerator)
  {
    bool hasMore;
    while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
    {
      nsCOMPtr <nsISupports> supports;
      rv = enumerator->GetNext(getter_AddRefs(supports));
      NS_ENSURE_SUCCESS(rv, rv);
      nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      bool shouldStoreMsgOffline = false;
      nsMsgKey msgKey;
      pHeader->GetMessageKey(&msgKey);
      // MsgFitsDownloadCriteria ignores nsMsgFolderFlags::Offline, which we want
      if (m_downloadingFolderForOfflineUse)
        MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
      else
        ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
      if (shouldStoreMsgOffline)
        keysOfMessagesToDownload->AppendElement(msgKey);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::OnNewIdleMessages()
{
  nsresult rv;
  nsCOMPtr<nsIImapIncomingServer> imapServer;
  rv = GetImapIncomingServer(getter_AddRefs(imapServer));
  NS_ENSURE_SUCCESS(rv, rv);

  bool checkAllFolders = ShouldCheckAllFolders(imapServer);

  // only trigger biff if we're checking all new folders for new messages, or this particular folder,
  // but excluding trash,junk, sent, and no select folders, by default.
  if ((checkAllFolders &&
    !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk | nsMsgFolderFlags::SentMail | nsMsgFolderFlags::ImapNoselect)))
    || (mFlags & (nsMsgFolderFlags::CheckNew|nsMsgFolderFlags::Inbox)))
    SetPerformingBiff(true);
  return UpdateFolder(nullptr);
}

NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxInfo(nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec)
{
  nsresult rv;
  ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
  ChangeNumPendingUnread(-mNumPendingUnreadMessages);
  m_numServerRecentMessages = 0; // clear this since we selected the folder.
  m_numServerUnseenMessages = 0; // clear this since we selected the folder.

  if (!mDatabase)
    GetDatabase();

  bool folderSelected;
  rv = aSpec->GetFolderSelected(&folderSelected);
  NS_ENSURE_SUCCESS(rv, rv);
  nsTArray<nsMsgKey> existingKeys;
  nsTArray<nsMsgKey> keysToDelete;
  uint32_t numNewUnread;
  nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
  int32_t imapUIDValidity = 0;
  if (mDatabase)
  {
    rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
    if (NS_SUCCEEDED(rv) && dbFolderInfo)
    {
      dbFolderInfo->GetImapUidValidity(&imapUIDValidity);
      uint64_t mailboxHighestModSeq;
      aSpec->GetHighestModSeq(&mailboxHighestModSeq);
      MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
              ("UpdateImapMailboxInfo(): Store highest MODSEQ=%" PRIu64 " for folder=%s",
               mailboxHighestModSeq, m_onlineFolderName.get()));
      char intStrBuf[40];
      PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu",  mailboxHighestModSeq);
      dbFolderInfo->SetCharProperty(kModSeqPropertyName, nsDependentCString(intStrBuf));
    }
    RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
    if (!keys)
      return NS_ERROR_OUT_OF_MEMORY;
    rv = mDatabase->ListAllKeys(keys);
    NS_ENSURE_SUCCESS(rv, rv);
    existingKeys.AppendElements(keys->m_keys);
    mDatabase->ListAllOfflineDeletes(&existingKeys);
  }
  int32_t folderValidity;
  aSpec->GetFolder_UIDVALIDITY(&folderValidity);
  nsCOMPtr <nsIImapFlagAndUidState> flagState;
  aSpec->GetFlagState(getter_AddRefs(flagState));

  // remember what the supported user flags are.
  uint32_t supportedUserFlags;
  aSpec->GetSupportedUserFlags(&supportedUserFlags);
  SetSupportedUserFlags(supportedUserFlags);

  m_uidValidity = folderValidity;

  if (imapUIDValidity != folderValidity)
  {
    NS_ASSERTION(imapUIDValidity == kUidUnknown,
                 "uid validity seems to have changed, blowing away db");
    nsCOMPtr<nsIFile> pathFile;
    rv = GetFilePath(getter_AddRefs(pathFile));
    if (NS_FAILED(rv)) return rv;

    nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr <nsIDBFolderInfo> transferInfo;
    if (dbFolderInfo)
      dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo));

    // A backup message database might have been created earlier, for example
    // if the user requested a reindex. We'll use the earlier one if we can,
    // otherwise we'll try to backup at this point.
    nsresult rvbackup = OpenBackupMsgDatabase();
    if (mDatabase)
    {
      dbFolderInfo = nullptr;
      if (NS_FAILED(rvbackup))
      {
        CloseAndBackupFolderDB(EmptyCString());
        if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase)
        {
          mBackupDatabase->RemoveListener(this);
          mBackupDatabase = nullptr;
        }
      }
      else
        mDatabase->ForceClosed();
    }
    mDatabase = nullptr;

    nsCOMPtr <nsIFile> summaryFile;
    rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile));
    // Remove summary file.
    if (NS_SUCCEEDED(rv) && summaryFile)
      summaryFile->Remove(false);

    // Create a new summary file, update the folder message counts, and
    // Close the summary file db.
    rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));

    if (NS_FAILED(rv) && mDatabase)
    {
      mDatabase->ForceClosed();
      mDatabase = nullptr;
    }
    else if (NS_SUCCEEDED(rv) && mDatabase)
    {
      if (transferInfo)
        SetDBTransferInfo(transferInfo);

      SummaryChanged();
      if (mDatabase)
      {
        if(mAddListener)
          mDatabase->AddListener(this);
        rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
      }
    }
    // store the new UIDVALIDITY value

    if (NS_SUCCEEDED(rv) && dbFolderInfo)
    {
      dbFolderInfo->SetImapUidValidity(folderValidity);
      // need to forget highest mod seq when uid validity rolls.
      MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
              ("UpdateImapMailboxInfo(): UIDVALIDITY changed, reset highest MODSEQ and UID for folder=%s",
               m_onlineFolderName.get()));
      dbFolderInfo->SetCharProperty(kModSeqPropertyName, EmptyCString());
      dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, 0);
    }
    // delete all my msgs, the keys are bogus now
    // add every message in this folder
    existingKeys.Clear();
    //      keysToDelete.CopyArray(&existingKeys);

    if (flagState)
    {
      nsTArray<nsMsgKey> no_existingKeys;
      FindKeysToAdd(no_existingKeys, m_keysToFetch, numNewUnread, flagState);
    }
    if (NS_FAILED(rv))
      pathFile->Remove(false);

  }
  else if (!flagState /*&& !NET_IsOffline() */) // if there are no messages on the server
    keysToDelete = existingKeys;
  else /* if ( !NET_IsOffline()) */
  {
    uint32_t boxFlags;
    aSpec->GetBox_flags(&boxFlags);
    // FindKeysToDelete and FindKeysToAdd require sorted lists
    existingKeys.Sort();
    FindKeysToDelete(existingKeys, keysToDelete, flagState, boxFlags);
    // if this is the result of an expunge then don't grab headers
    if (!(boxFlags & kJustExpunged))
      FindKeysToAdd(existingKeys, m_keysToFetch, numNewUnread, flagState);
  }
  m_totalKeysToFetch = m_keysToFetch.Length();
  if (!keysToDelete.IsEmpty() && mDatabase)
  {
    nsCOMPtr<nsIMutableArray> hdrsToDelete(do_CreateInstance(NS_ARRAY_CONTRACTID));
    MsgGetHeadersFromKeys(mDatabase, keysToDelete, hdrsToDelete);
    // Notify nsIMsgFolderListeners of a mass delete, but only if we actually have headers
    uint32_t numHdrs;
    hdrsToDelete->GetLength(&numHdrs);
    if (numHdrs)
    {
      nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
      if (notifier)
        notifier->NotifyMsgsDeleted(hdrsToDelete);
    }
    DeleteStoreMessages(hdrsToDelete);
    EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false);
    mDatabase->DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr);
    EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
  }
  int32_t numUnreadFromServer;
  aSpec->GetNumUnseenMessages(&numUnreadFromServer);

  bool partialUIDFetch;
  flagState->GetPartialUIDFetch(&partialUIDFetch);

  // For partial UID fetches, we can only trust the numUnread from the server.
  if (partialUIDFetch)
    numNewUnread = numUnreadFromServer;

  // If we are performing biff for this folder, tell the
  // stand-alone biff about the new high water mark
  if (m_performingBiff && numNewUnread)
  {
    // We must ensure that the server knows that we are performing biff.
    // Otherwise the stand-alone biff won't fire.
    nsCOMPtr<nsIMsgIncomingServer> server;
    if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
      server->SetPerformingBiff(true);
     SetNumNewMessages(numNewUnread);
  }
  SyncFlags(flagState);
  if (mDatabase && (int32_t) (mNumUnreadMessages + m_keysToFetch.Length()) > numUnreadFromServer)
    mDatabase->SyncCounts();

  if (!m_keysToFetch.IsEmpty() && aProtocol)
    PrepareToAddHeadersToMailDB(aProtocol);
  else
  {
    bool gettingNewMessages;
    GetGettingNewMessages(&gettingNewMessages);
    if (gettingNewMessages)
      ProgressStatusString(aProtocol, "imapNoNewMessages", nullptr);
    SetPerformingBiff(false);
  }
  aSpec->GetNumMessages(&m_numServerTotalMessages);
  aSpec->GetNumUnseenMessages(&m_numServerUnseenMessages);
  aSpec->GetNumRecentMessages(&m_numServerRecentMessages);

  // some servers don't return UIDNEXT on SELECT - don't crunch
  // existing values in that case.
  int32_t nextUID;
  aSpec->GetNextUID(&nextUID);
  if (nextUID != (int32_t) nsMsgKey_None)
    m_nextUID = nextUID;

  return rv;
}

NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxStatus(
  nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec)
{
  NS_ENSURE_ARG_POINTER(aSpec);
  int32_t numUnread, numTotal;
  aSpec->GetNumUnseenMessages(&numUnread);
  aSpec->GetNumMessages(&numTotal);
  aSpec->GetNumRecentMessages(&m_numServerRecentMessages);
  int32_t prevNextUID = m_nextUID;
  aSpec->GetNextUID(&m_nextUID);
  bool summaryChanged = false;

  // If m_numServerUnseenMessages is 0, it means
  // this is the first time we've done a Status.
  // In that case, we count all the previous pending unread messages we know about
  // as unread messages.
  // We may want to do similar things with total messages, but the total messages
  // include deleted messages if the folder hasn't been expunged.
  int32_t previousUnreadMessages = (m_numServerUnseenMessages)
    ? m_numServerUnseenMessages : mNumPendingUnreadMessages + mNumUnreadMessages;
  if (numUnread != previousUnreadMessages || m_nextUID != prevNextUID)
  {
    int32_t unreadDelta = numUnread - (mNumPendingUnreadMessages + mNumUnreadMessages);
    if (numUnread - previousUnreadMessages != unreadDelta)
       NS_WARNING("unread count should match server count");
    ChangeNumPendingUnread(unreadDelta);
    if (unreadDelta > 0 &&
        !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
    {
      SetHasNewMessages(true);
      SetNumNewMessages(unreadDelta);
      SetBiffState(nsMsgBiffState_NewMail);
    }
    summaryChanged = true;
  }
  SetPerformingBiff(false);
  if (m_numServerUnseenMessages != numUnread || m_numServerTotalMessages != numTotal)
  {
    if (numUnread > m_numServerUnseenMessages ||
        m_numServerTotalMessages > numTotal)
      NotifyHasPendingMsgs();
    summaryChanged = true;
    m_numServerUnseenMessages = numUnread;
    m_numServerTotalMessages = numTotal;
  }
  if (summaryChanged)
    SummaryChanged();

  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::ParseMsgHdrs(nsIImapProtocol *aProtocol, nsIImapHeaderXferInfo *aHdrXferInfo)
{
  NS_ENSURE_ARG_POINTER(aHdrXferInfo);
  int32_t numHdrs;
  nsCOMPtr <nsIImapHeaderInfo> headerInfo;
  nsCOMPtr <nsIImapUrl> aImapUrl;
  nsImapAction imapAction = nsIImapUrl::nsImapTest; // unused value.
  if (!mDatabase)
    GetDatabase();

  nsresult rv = aHdrXferInfo->GetNumHeaders(&numHdrs);
  if (aProtocol)
  {
    (void) aProtocol->GetRunningImapURL(getter_AddRefs(aImapUrl));
    if (aImapUrl)
      aImapUrl->GetImapAction(&imapAction);
  }
  for (uint32_t i = 0; NS_SUCCEEDED(rv) && (int32_t)i < numHdrs; i++)
  {
    rv = aHdrXferInfo->GetHeader(i, getter_AddRefs(headerInfo));
    NS_ENSURE_SUCCESS(rv, rv);
    if (!headerInfo)
      break;
    int32_t msgSize;
    nsMsgKey msgKey;
    bool containsKey;
    const char *msgHdrs;
    headerInfo->GetMsgSize(&msgSize);
    headerInfo->GetMsgUid(&msgKey);
    if (msgKey == nsMsgKey_None) // not a valid uid.
      continue;
    if (imapAction == nsIImapUrl::nsImapMsgPreview)
    {
      nsCOMPtr <nsIMsgDBHdr> msgHdr;
      headerInfo->GetMsgHdrs(&msgHdrs);
      // create an input stream based on the hdr string.
      nsCOMPtr<nsIStringInputStream> inputStream =
            do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      inputStream->ShareData(msgHdrs, strlen(msgHdrs));
      GetMessageHeader(msgKey, getter_AddRefs(msgHdr));
      if (msgHdr) {
        GetMsgPreviewTextFromStream(msgHdr, inputStream);
      }
      continue;
    }
    if (mDatabase && NS_SUCCEEDED(mDatabase->ContainsKey(msgKey, &containsKey)) && containsKey)
    {
      NS_ERROR("downloading hdrs for hdr we already have");
      continue;
    }
    nsresult rv = SetupHeaderParseStream(msgSize, EmptyCString(), nullptr);
    NS_ENSURE_SUCCESS(rv, rv);
    headerInfo->GetMsgHdrs(&msgHdrs);
    rv = ParseAdoptedHeaderLine(msgHdrs, msgKey);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = NormalEndHeaderParseStream(aProtocol, aImapUrl);
  }
  return rv;
}

nsresult nsImapMailFolder::SetupHeaderParseStream(uint32_t aSize,
                                                  const nsACString& content_type, nsIMailboxSpec *boxSpec)
{
  if (!mDatabase)
    GetDatabase();
  m_nextMessageByteLength = aSize;
  if (!m_msgParser)
  {
    nsresult rv;
    m_msgParser = do_CreateInstance(kParseMailMsgStateCID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  else
    m_msgParser->Clear();

  m_msgParser->SetMailDB(mDatabase);
  if (mBackupDatabase)
    m_msgParser->SetBackupMailDB(mBackupDatabase);
  return m_msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
}

nsresult nsImapMailFolder::ParseAdoptedHeaderLine(const char *aMessageLine, nsMsgKey aMsgKey)
{
  // we can get blocks that contain more than one line,
  // but they never contain partial lines
  const char *str = aMessageLine;
  m_curMsgUid = aMsgKey;
  m_msgParser->SetNewKey(m_curMsgUid);
  // m_envelope_pos, for local folders,
  // is the msg key. Setting this will set the msg key for the new header.

  int32_t len = strlen(str);
  char *currentEOL  = PL_strstr(str, MSG_LINEBREAK);
  const char *currentLine = str;
  while (currentLine < (str + len))
  {
    if (currentEOL)
    {
      m_msgParser->ParseAFolderLine(currentLine,
        (currentEOL + MSG_LINEBREAK_LEN) -
        currentLine);
      currentLine = currentEOL + MSG_LINEBREAK_LEN;
      currentEOL  = PL_strstr(currentLine, MSG_LINEBREAK);
    }
    else
    {
      m_msgParser->ParseAFolderLine(currentLine, PL_strlen(currentLine));
      currentLine = str + len + 1;
    }
  }
  return NS_OK;
}

nsresult nsImapMailFolder::NormalEndHeaderParseStream(nsIImapProtocol *aProtocol, nsIImapUrl* imapUrl)
{
  nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
  nsresult rv;
  NS_ENSURE_TRUE(m_msgParser, NS_ERROR_NULL_POINTER);

  nsMailboxParseState parseState;
  m_msgParser->GetState(&parseState);
  if (parseState == nsIMsgParseMailMsgState::ParseHeadersState)
    m_msgParser->ParseAFolderLine(CRLF, 2);
  rv = m_msgParser->GetNewMsgHdr(getter_AddRefs(newMsgHdr));
  NS_ENSURE_SUCCESS(rv, rv);

  char *headers;
  int32_t headersSize;

  nsCOMPtr <nsIMsgWindow> msgWindow;
  nsCOMPtr <nsIMsgMailNewsUrl> msgUrl;
  if (imapUrl)
  {
    msgUrl = do_QueryInterface(imapUrl, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
  }

  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = GetServer(getter_AddRefs(server));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
  rv = imapServer->GetIsGMailServer(&m_isGmailServer);
  NS_ENSURE_SUCCESS(rv, rv);

  newMsgHdr->SetMessageKey(m_curMsgUid);
  TweakHeaderFlags(aProtocol, newMsgHdr);
  uint32_t messageSize;
  if (NS_SUCCEEDED(newMsgHdr->GetMessageSize(&messageSize)))
    mFolderSize += messageSize;
  m_msgMovedByFilter = false;

  nsMsgKey highestUID = 0;
  nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
  if (mDatabase)
    mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
  if (dbFolderInfo)
    dbFolderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, &highestUID);

  // If this is the inbox, try to apply filters. Otherwise, test the inherited
  // folder property "applyIncomingFilters" (which defaults to empty). If this
  // inherited property has the string value "true", then apply filters even
  // if this is not the Inbox folder.
  if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters)
  {
    // Use highwater to determine whether to filter?
    bool filterOnHighwater = false;
    nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
    if (prefBranch)
      prefBranch->GetBoolPref("mail.imap.filter_on_new", &filterOnHighwater);

    uint32_t msgFlags;
    newMsgHdr->GetFlags(&msgFlags);

    bool doFilter = filterOnHighwater ?
      // Filter on largest UUID and not deleted.
      m_curMsgUid > highestUID && !(msgFlags & nsMsgMessageFlags::IMAPDeleted) :
      // Filter on unread and not deleted.
      !(msgFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted));

    if (doFilter)
    {
      int32_t duplicateAction = nsIMsgIncomingServer::keepDups;
      if (server)
        server->GetIncomingDuplicateAction(&duplicateAction);
      if ((duplicateAction != nsIMsgIncomingServer::keepDups) &&
          mFlags & nsMsgFolderFlags::Inbox)
      {
        bool isDup;
        server->IsNewHdrDuplicate(newMsgHdr, &isDup);
        if (isDup)
        {
          // we want to do something similar to applying filter hits.
          // if a dup is marked read, it shouldn't trigger biff.
          // Same for deleting it or moving it to trash.
          switch (duplicateAction)
          {
            case nsIMsgIncomingServer::deleteDups:
              {
                uint32_t newFlags;
                newMsgHdr->OrFlags(nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted, &newFlags);
                StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
                               &m_curMsgUid, 1, nullptr);
                m_msgMovedByFilter = true;
              }
              break;
            case nsIMsgIncomingServer::moveDupsToTrash:
              {
                nsCOMPtr <nsIMsgFolder> trash;
                GetTrashFolder(getter_AddRefs(trash));
                if (trash)
                {
                  nsCString trashUri;
                  trash->GetURI(trashUri);
                  nsresult err = MoveIncorporatedMessage(newMsgHdr, mDatabase, trashUri, nullptr, msgWindow);
                  if (NS_SUCCEEDED(err))
                    m_msgMovedByFilter = true;
                }
              }
              break;
            case nsIMsgIncomingServer::markDupsRead:
              {
                uint32_t newFlags;
                newMsgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);
                StoreImapFlags(kImapMsgSeenFlag, true, &m_curMsgUid, 1, nullptr);
              }
              break;
          }
          int32_t numNewMessages;
          GetNumNewMessages(false, &numNewMessages);
          SetNumNewMessages(numNewMessages - 1);
        }
      }
      rv = m_msgParser->GetAllHeaders(&headers, &headersSize);

      if (NS_SUCCEEDED(rv) && headers && !m_msgMovedByFilter &&
          !m_filterListRequiresBody)
      {
        if (m_filterList)
        {
          GetMoveCoalescer();  // not sure why we're doing this here.
          m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
                                          this, mDatabase, headers, headersSize,
                                          this, msgWindow);
          NotifyFolderEvent(kFiltersApplied);
        }
      }
    }
  }
  // here we need to tweak flags from uid state..
  if (mDatabase && (!m_msgMovedByFilter || ShowDeletedMessages()))
  {
    nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
    // Check if this header corresponds to a pseudo header
    // we have from doing a pseudo-offline move and then downloading
    // the real header from the server. In that case, we notify
    // db/folder listeners that the pseudo-header has become the new
    // header, i.e., the key has changed.
    nsCString newMessageId;
    nsMsgKey pseudoKey = nsMsgKey_None;
    newMsgHdr->GetMessageId(getter_Copies(newMessageId));
    m_pseudoHdrs.Get(newMessageId, &pseudoKey);
    if (notifier && pseudoKey != nsMsgKey_None)
    {
      notifier->NotifyMsgKeyChanged(pseudoKey, newMsgHdr);
      m_pseudoHdrs.Remove(newMessageId);
    }
    mDatabase->AddNewHdrToDB(newMsgHdr, true);
    if (notifier)
      notifier->NotifyMsgAdded(newMsgHdr);
    // mark the header as not yet reported classified
    OrProcessingFlags(m_curMsgUid, nsMsgProcessingFlags::NotReportedClassified);
  }
  // adjust highestRecordedUID
  if (dbFolderInfo)
  {
    if (m_curMsgUid > highestUID)
    {
      MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
              ("NormalEndHeaderParseStream(): Store new highest UID=%" PRIu32 " for folder=%s",
               m_curMsgUid, m_onlineFolderName.get()));
      dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, m_curMsgUid);
    }
  }

  if (m_isGmailServer)
  {
    nsCOMPtr<nsIImapFlagAndUidState> flagState;
    aProtocol->GetFlagAndUidState(getter_AddRefs(flagState));
    nsCString msgIDValue;
    nsCString threadIDValue;
    nsCString labelsValue;
    flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-MSGID"), msgIDValue);
    flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-THRID"), threadIDValue);
    flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-LABELS"), labelsValue);
    newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue.get());
    newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue.get());
    newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue.get());
  }

  m_msgParser->Clear(); // clear out parser, because it holds onto a msg hdr.
  m_msgParser->SetMailDB(nullptr); // tell it to let go of the db too.
  // I don't think we want to do this - it does bad things like set the size incorrectly.
  //    m_msgParser->FinishHeader();
    return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::AbortHeaderParseStream(nsIImapProtocol* aProtocol)
{
  nsresult rv = NS_ERROR_FAILURE;
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::BeginCopy(nsIMsgDBHdr *message)
{
  NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
  nsresult rv;
  if (m_copyState->m_tmpFile) // leftover file spec nuke it
  {
    rv = m_copyState->m_tmpFile->Remove(false);
    if (NS_FAILED(rv))
    {
      nsCString nativePath = m_copyState->m_tmpFile->HumanReadablePath();
      MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't remove prev temp file %s: %" PRIx32 "\n", nativePath.get(), static_cast<uint32_t>(rv)));
    }
    m_copyState->m_tmpFile = nullptr;
  }
  if (message)
    m_copyState->m_message = message;

  rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
                                       "nscpmsg.txt",
                                        getter_AddRefs(m_copyState->m_tmpFile));
  if (NS_FAILED(rv))
    MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't find nscpmsg.txt:%" PRIx32 "\n", static_cast<uint32_t>(rv)));
  NS_ENSURE_SUCCESS(rv, rv);

  // create a unique file, since multiple copies may be open on multiple folders
  rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
  if (NS_FAILED(rv))
  {
    MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create temp nscpmsg.txt:%" PRIx32 "\n", static_cast<uint32_t>(rv)));
    // Last ditch attempt to create a temp file, because virus checker might
    // be locking the previous temp file, and CreateUnique fails if the file
    // is locked. Use the message key to make a unique name.
    if (message)
    {
      nsCString tmpFileName("nscpmsg-");
      nsMsgKey msgKey;
      message->GetMessageKey(&msgKey);
      tmpFileName.AppendInt(msgKey);
      tmpFileName.AppendLiteral(".txt");
      m_copyState->m_tmpFile->SetNativeLeafName(tmpFileName);
      rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
      if (NS_FAILED(rv))
      {
        MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create temp nscpmsg.txt: %" PRIx32 "\n", static_cast<uint32_t>(rv)));
        OnCopyCompleted(m_copyState->m_srcSupport, rv);
        return rv;
      }
    }
  }

  nsCOMPtr<nsIOutputStream> fileOutputStream;
  rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_copyState->m_msgFileStream),
                                      m_copyState->m_tmpFile, -1, 00600);
  if (NS_FAILED(rv))
    MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create output file stream: %" PRIx32 "\n", static_cast<uint32_t>(rv)));

  if (!m_copyState->m_dataBuffer)
    m_copyState->m_dataBuffer = (char*) PR_CALLOC(COPY_BUFFER_SIZE+1);
  NS_ENSURE_TRUE(m_copyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY);
  m_copyState->m_dataBufferSize = COPY_BUFFER_SIZE;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::CopyDataToOutputStreamForAppend(nsIInputStream *aIStream,
                     int32_t aLength, nsIOutputStream *outputStream)
{
  uint32_t readCount;
  uint32_t writeCount;
  if (!m_copyState)
    m_copyState = new nsImapMailCopyState();

  if ( aLength + m_copyState->m_leftOver > m_copyState->m_dataBufferSize )
  {
    char *newBuffer = (char*) PR_REALLOC(m_copyState->m_dataBuffer, aLength + m_copyState->m_leftOver+ 1);
    NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
    m_copyState->m_dataBuffer = newBuffer;
    m_copyState->m_dataBufferSize = aLength + m_copyState->m_leftOver;
  }

  char *start, *end;
  uint32_t linebreak_len = 1;

  nsresult rv = aIStream->Read(m_copyState->m_dataBuffer+m_copyState->m_leftOver, aLength, &readCount);
  if (NS_FAILED(rv))
    return rv;

  m_copyState->m_leftOver += readCount;
  m_copyState->m_dataBuffer[m_copyState->m_leftOver] = '\0';

  start = m_copyState->m_dataBuffer;
  if (m_copyState->m_eatLF)
  {
    if (*start == '\n')
      start++;
    m_copyState->m_eatLF = false;
  }
  end = PL_strpbrk(start, "\r\n");
  if (end && *end == '\r' && *(end+1) == '\n')
    linebreak_len = 2;

  while (start && end)
  {
    if (PL_strncasecmp(start, "X-Mozilla-Status:", 17) &&
        PL_strncasecmp(start, "X-Mozilla-Status2:", 18) &&
        PL_strncmp(start, "From - ", 7))
    {
      rv = outputStream->Write(start,
                                             end-start,
                                             &writeCount);
      rv = outputStream->Write(CRLF, 2, &writeCount);
    }
    start = end+linebreak_len;
    if (start >=
        m_copyState->m_dataBuffer+m_copyState->m_leftOver)
    {
       m_copyState->m_leftOver = 0;
       break;
    }
    linebreak_len = 1;

    end = PL_strpbrk(start, "\r\n");
    if (end && *end == '\r')
    {
      if (*(end+1) == '\n')
        linebreak_len = 2;
      else if (! *(end+1)) // block might have split CRLF so remember if
        m_copyState->m_eatLF = true; // we should eat LF
    }

    if (start && !end)
    {
      m_copyState->m_leftOver -= (start - m_copyState->m_dataBuffer);
      memcpy(m_copyState->m_dataBuffer, start, m_copyState->m_leftOver+1); // including null
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::CopyDataDone()
{
  m_copyState = nullptr;
  return NS_OK;
}

// sICopyMessageListener methods, BeginCopy, CopyData, EndCopy, EndMove, StartMessage, EndMessage
NS_IMETHODIMP nsImapMailFolder::CopyData(nsIInputStream *aIStream, int32_t aLength)
{
  NS_ENSURE_TRUE(m_copyState && m_copyState->m_msgFileStream && m_copyState->m_dataBuffer, NS_ERROR_NULL_POINTER);
  nsresult rv = CopyDataToOutputStreamForAppend(aIStream, aLength,
                                                m_copyState->m_msgFileStream);
  if (NS_FAILED(rv))
  {
    MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyData failed: %" PRIx32 "\n", static_cast<uint32_t>(rv)));
    OnCopyCompleted(m_copyState->m_srcSupport, rv);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::EndCopy(bool copySucceeded)
{
  nsresult rv = copySucceeded ? NS_OK : NS_ERROR_FAILURE;
  if (copySucceeded && m_copyState && m_copyState->m_msgFileStream)
  {
    nsCOMPtr<nsIUrlListener> urlListener;
    m_copyState->m_msgFileStream->Close();
    // m_tmpFile can be stale because we wrote to it
    nsCOMPtr<nsIFile> tmpFile;
    m_copyState->m_tmpFile->Clone(getter_AddRefs(tmpFile));
    m_copyState->m_tmpFile = tmpFile;
    nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv,rv);

    rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
    nsCOMPtr<nsISupports> copySupport;
    if (m_copyState)
      copySupport = do_QueryInterface(m_copyState);
    rv = imapService->AppendMessageFromFile(m_copyState->m_tmpFile,
                                            this, EmptyCString(), true,
                                            m_copyState->m_selectedState,
                                            urlListener, nullptr,
                                            copySupport,
                                            m_copyState->m_msgWindow);
  }
  if (NS_FAILED(rv) || !copySucceeded)
    MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("EndCopy failed: %" PRIx32 "\n", static_cast<uint32_t>(rv)));
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::EndMove(bool moveSucceeded)
{
  return NS_OK;
}
// this is the beginning of the next message copied
NS_IMETHODIMP nsImapMailFolder::StartMessage()
{
  return NS_OK;
}

// just finished the current message.
NS_IMETHODIMP nsImapMailFolder::EndMessage(nsMsgKey key)
{
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindow *msgWindow, bool *applyMore)
{
  //
  //  This routine is called indirectly from ApplyFiltersToHdr in two
  //  circumstances, controlled by m_filterListRequiresBody:
  //
  //  If false, after headers are parsed in NormalEndHeaderParseStream.
  //  If true, after the message body is downloaded in NormalEndMsgWriteStream.
  //
  //  In NormalEndHeaderParseStream, the message has not been added to the
  //  database, and it is important that database notifications and count
  //  updates do not occur. In NormalEndMsgWriteStream, the message has been
  //  added to the database, and database notifications and count updates
  //  should be performed.
  //

  NS_ENSURE_ARG_POINTER(filter);
  NS_ENSURE_ARG_POINTER(applyMore);

  nsresult rv = NS_OK;

  // look at action - currently handle move
#ifdef DEBUG_bienvenu
  printf("got a rule hit!\n");
#endif

  nsCOMPtr<nsIMsgDBHdr> msgHdr;
  if (m_filterListRequiresBody)
    GetMessageHeader(m_curMsgUid, getter_AddRefs(msgHdr));
  else if (m_msgParser)
    m_msgParser->GetNewMsgHdr(getter_AddRefs(msgHdr));
  NS_ENSURE_TRUE(msgHdr, NS_ERROR_NULL_POINTER); //fatal error, cannot apply filters

  bool deleteToTrash = DeleteIsMoveToTrash();

  nsCOMPtr<nsIArray> filterActionList;

  rv = filter->GetSortedActionList(getter_AddRefs(filterActionList));
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t numActions;
  rv = filterActionList->GetLength(&numActions);
  NS_ENSURE_SUCCESS(rv, rv);

  bool loggingEnabled = false;
  if (m_filterList && numActions)
    (void)m_filterList->GetLoggingEnabled(&loggingEnabled);

  bool msgIsNew = true;

  for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++)
  {
    nsCOMPtr<nsIMsgRuleAction> filterAction = do_QueryElementAt(filterActionList, actionIndex);
    if (NS_FAILED(rv) || !filterAction)
      continue;

    nsMsgRuleActionType actionType;
    if (NS_SUCCEEDED(filterAction->GetType(&actionType)))
    {
      if (loggingEnabled)
        (void) filter->LogRuleHit(filterAction, msgHdr);

      nsCString actionTargetFolderUri;
      if (actionType == nsMsgFilterAction::MoveToFolder ||
          actionType == nsMsgFilterAction::CopyToFolder)
      {
        rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
        if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty())
        {
          NS_ASSERTION(false, "actionTargetFolderUri is empty");
          continue;
        }
      }

      uint32_t msgFlags;
      nsMsgKey    msgKey;
      nsAutoCString trashNameVal;

      msgHdr->GetFlags(&msgFlags);
      msgHdr->GetMessageKey(&msgKey);
      bool isRead = (msgFlags & nsMsgMessageFlags::Read);
      nsresult rv = GetDatabase();
      NS_ENSURE_SUCCESS(rv, rv);
      switch (actionType)
      {
        case nsMsgFilterAction::Delete:
        {
          if (deleteToTrash)
          {
            // set value to trash folder
            nsCOMPtr <nsIMsgFolder> mailTrash;
            rv = GetTrashFolder(getter_AddRefs(mailTrash));
            if (NS_SUCCEEDED(rv) && mailTrash)
              rv = mailTrash->GetURI(actionTargetFolderUri);
            // msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);  // mark read in trash.
          }
          else  // (!deleteToTrash)
          {
            mDatabase->MarkHdrRead(msgHdr, true, nullptr);
            mDatabase->MarkImapDeleted(msgKey, true, nullptr);
            StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
                           &msgKey, 1, nullptr);
            m_msgMovedByFilter = true; // this will prevent us from adding the header to the db.
          }
          msgIsNew = false;
        }
        // note that delete falls through to move.
        MOZ_FALLTHROUGH;
        case nsMsgFilterAction::MoveToFolder:
        {
          // if moving to a different file, do it.
          nsCString uri;
          rv = GetURI(uri);

          if (!actionTargetFolderUri.Equals(uri))
          {
            msgHdr->GetFlags(&msgFlags);

            if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead)
            {
               mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
               mDatabase->MarkMDNSent(msgKey, true, nullptr);
            }
            nsresult err = MoveIncorporatedMessage(msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow);
            if (NS_SUCCEEDED(err))
              m_msgMovedByFilter = true;
          }
          // don't apply any more filters, even if it was a move to the same folder
          *applyMore = false;
        }
        break;
        case nsMsgFilterAction::CopyToFolder:
        {
          nsCString uri;
          rv = GetURI(uri);

          if (!actionTargetFolderUri.Equals(uri))
          {
            // XXXshaver I'm not actually 100% what the right semantics are for
            // MDNs and copied messages, but I suspect deep down inside that
            // we probably want to suppress them only on the copies.
            msgHdr->GetFlags(&msgFlags);
            if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead)
            {
               mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
               mDatabase->MarkMDNSent(msgKey, true, nullptr);
            }

            nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
            NS_ENSURE_TRUE(messageArray, rv);
            messageArray->AppendElement(msgHdr);

            nsCOMPtr<nsIMsgFolder> dstFolder;
            rv = GetExistingFolder(actionTargetFolderUri, getter_AddRefs(dstFolder));
            NS_ENSURE_SUCCESS(rv, rv);

            nsCOMPtr<nsIMsgCopyService> copyService =
              do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
            NS_ENSURE_SUCCESS(rv, rv);
            rv = copyService->CopyMessages(this, messageArray, dstFolder,
                                           false, nullptr, msgWindow, false);
            NS_ENSURE_SUCCESS(rv, rv);
          }
        }
        break;
        case nsMsgFilterAction::MarkRead:
        {
          mDatabase->MarkHdrRead(msgHdr, true, nullptr);
          StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
          msgIsNew = false;
        }
        break;
        case nsMsgFilterAction::MarkUnread:
        {
          mDatabase->MarkHdrRead(msgHdr, false, nullptr);
          StoreImapFlags(kImapMsgSeenFlag, false, &msgKey, 1, nullptr);
          msgIsNew = true;
        }
        break;
        case nsMsgFilterAction::MarkFlagged:
        {
          mDatabase->MarkHdrMarked(msgHdr, true, nullptr);
          StoreImapFlags(kImapMsgFlaggedFlag, true, &msgKey, 1, nullptr);
        }
        break;
        case nsMsgFilterAction::KillThread:
        case nsMsgFilterAction::WatchThread:
        {
          nsCOMPtr <nsIMsgThread> msgThread;
          nsMsgKey threadKey;
          mDatabase->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(msgThread));
          if (msgThread)
          {
            msgThread->GetThreadKey(&threadKey);
            if (actionType == nsMsgFilterAction::KillThread)
              mDatabase->MarkThreadIgnored(msgThread, threadKey, true, nullptr);
            else
              mDatabase->MarkThreadWatched(msgThread, threadKey, true, nullptr);
          }
          else
          {
            if (actionType == nsMsgFilterAction::KillThread)
              msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored);
            else
              msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Watched);
          }
          if (actionType == nsMsgFilterAction::KillThread)
          {
            mDatabase->MarkHdrRead(msgHdr, true, nullptr);
            StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
            msgIsNew = false;
          }
        }
        break;
        case nsMsgFilterAction::KillSubthread:
        {
          mDatabase->MarkHeaderKilled(msgHdr, true, nullptr);
          mDatabase->MarkHdrRead(msgHdr, true, nullptr);
          StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
          msgIsNew = false;
        }
        break;
        case nsMsgFilterAction::ChangePriority:
        {
          nsMsgPriorityValue filterPriority; // a int32_t
          filterAction->GetPriority(&filterPriority);
          mDatabase->SetUint32PropertyByHdr(msgHdr, "priority",
                                            static_cast<uint32_t>(filterPriority));
        }
        break;
        case nsMsgFilterAction::Label:
        {
          nsMsgLabelValue filterLabel;
          filterAction->GetLabel(&filterLabel);
          mDatabase->SetUint32PropertyByHdr(msgHdr, "label",
                                            static_cast<uint32_t>(filterLabel));
          StoreImapFlags((filterLabel << 9), true, &msgKey, 1, nullptr);
        }
        break;
        case nsMsgFilterAction::AddTag:
        {
          nsCString keyword;
          filterAction->GetStrValue(keyword);
          nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
          NS_ENSURE_TRUE(messageArray, rv);
          messageArray->AppendElement(msgHdr);
          AddKeywordsToMessages(messageArray, keyword);
          break;
        }
        case nsMsgFilterAction::JunkScore:
        {
          nsAutoCString junkScoreStr;
          int32_t junkScore;
          filterAction->GetJunkScore(&junkScore);
          junkScoreStr.AppendInt(junkScore);
          mDatabase->SetStringProperty(msgKey, "junkscore", junkScoreStr.get());
          mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter");

          // If score is available, set up to store junk status on server.
          if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE ||
              junkScore == nsIJunkMailPlugin::IS_HAM_SCORE)
          {
            nsTArray<nsMsgKey> *keysToClassify = m_moveCoalescer->GetKeyBucket(
                       (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) ? 0 : 1);
            NS_ASSERTION(keysToClassify, "error getting key bucket");
            if (keysToClassify)
              keysToClassify->AppendElement(msgKey);
            if (msgIsNew && junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE)
            {
              msgIsNew = false;
              mDatabase->MarkHdrNotNew(msgHdr, nullptr);
              // nsMsgDBFolder::SendFlagNotifications by the call to
              // SetBiffState(nsMsgBiffState_NoMail) will reset numNewMessages
              // only if the message is also read and database notifications
              // are active, but we are not going to mark it read in this
              // action, preferring to leave the choice to the user.
              // So correct numNewMessages.
              if (m_filterListRequiresBody)
              {
                msgHdr->GetFlags(&msgFlags);
                if (!(msgFlags & nsMsgMessageFlags::Read))
                {
                  int32_t numNewMessages;
                  GetNumNewMessages(false, &numNewMessages);
                  SetNumNewMessages(--numNewMessages);
                  SetHasNewMessages(numNewMessages != 0);
                }
              }
            }
          }
        }
        break;
      case nsMsgFilterAction::Forward:
        {
          nsCString forwardTo;
          filterAction->GetStrValue(forwardTo);
          nsCOMPtr<nsIMsgIncomingServer> server;
          rv = GetServer(getter_AddRefs(server));
          NS_ENSURE_SUCCESS(rv, rv);
          if (!forwardTo.IsEmpty())
          {
            nsCOMPtr<nsIMsgComposeService> compService =
              do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
            NS_ENSURE_SUCCESS(rv, rv);
            rv = compService->ForwardMessage(NS_ConvertASCIItoUTF16(forwardTo),
                                             msgHdr, msgWindow, server,
                                             nsIMsgComposeService::kForwardAsDefault);
          }
        }
        break;

      case nsMsgFilterAction::Reply:
        {
          nsCString replyTemplateUri;
          filterAction->GetStrValue(replyTemplateUri);
          nsCOMPtr <nsIMsgIncomingServer> server;
          GetServer(getter_AddRefs(server));
          NS_ENSURE_SUCCESS(rv, rv);
          if (!replyTemplateUri.IsEmpty())
          {
            nsCOMPtr <nsIMsgComposeService> compService = do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID) ;
            if (compService) {
              rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri.get(), msgWindow, server);
              if (NS_FAILED(rv)) {
                NS_WARNING("ReplyWithTemplate failed");
                if (rv == NS_ERROR_ABORT) {
                  filter->LogRuleHitFail(filterAction, msgHdr, rv, "Sending reply aborted");
                } else {
                  filter->LogRuleHitFail(filterAction, msgHdr, rv, "Error sending reply");
                }
              }
            }
          }
        }
        break;

        case nsMsgFilterAction::StopExecution:
        {
          // don't apply any more filters
          *applyMore = false;
        }
        break;

        case nsMsgFilterAction::Custom:
        {
          nsCOMPtr<nsIMsgFilterCustomAction> customAction;
          rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
          NS_ENSURE_SUCCESS(rv, rv);

          nsAutoCString value;
          filterAction->GetStrValue(value);

          nsCOMPtr<nsIMutableArray> messageArray(
              do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
          NS_ENSURE_TRUE(messageArray, rv);
          messageArray->AppendElement(msgHdr);

          customAction->Apply(messageArray, value, nullptr,
                              nsMsgFilterType::InboxRule, msgWindow);
          // allow custom action to affect new
          msgHdr->GetFlags(&msgFlags);
          if (!(msgFlags & nsMsgMessageFlags::New))
            msgIsNew = false;
        }
        break;

        default:
          break;
      }
    }
  }
  if (!msgIsNew)
  {
    int32_t numNewMessages;
    GetNumNewMessages(false, &numNewMessages);
    // When database notifications are active, new counts will be reset
    // to zero in nsMsgDBFolder::SendFlagNotifications by the call to
    // SetBiffState(nsMsgBiffState_NoMail), so don't repeat them here.
    if (!m_filterListRequiresBody)
      SetNumNewMessages(--numNewMessages);
    if (mDatabase)
      mDatabase->MarkHdrNotNew(msgHdr, nullptr);
  }
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetImapFlags(const char *uids, int32_t flags, nsIURI **url)
{
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  return imapService->SetMessageFlags(this, this, url, nsAutoCString(uids), flags, true);
}

// "this" is the parent folder
NS_IMETHODIMP nsImapMailFolder::PlaybackOfflineFolderCreate(const nsAString& aFolderName, nsIMsgWindow *aWindow, nsIURI **url)
{
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);
  return imapService->CreateFolder(this, aFolderName, this, url);
}

NS_IMETHODIMP
nsImapMailFolder::ReplayOfflineMoveCopy(nsMsgKey *aMsgKeys, uint32_t aNumKeys,
                                        bool isMove, nsIMsgFolder *aDstFolder,
                                        nsIUrlListener *aUrlListener, nsIMsgWindow *aWindow)
{
  nsresult rv;

  nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aDstFolder);
  if (imapFolder)
  {
    nsImapMailFolder *destImapFolder = static_cast<nsImapMailFolder*>(aDstFolder);
    nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
    nsCOMPtr<nsIMsgDatabase> dstFolderDB;
    aDstFolder->GetMsgDatabase(getter_AddRefs(dstFolderDB));
    if (dstFolderDB)
    {
      // find the fake header in the destination db, and use that to
      // set the pending attributes on the real headers. To do this,
      // we need to iterate over the offline ops in the destination db,
      // looking for ones with matching keys and source folder uri.
      // If we find that offline op, its "key" will be the key of the fake
      // header, so we just need to get the header for that key
      // from the dest db.
      nsTArray<nsMsgKey> offlineOps;
      if (NS_SUCCEEDED(dstFolderDB->ListAllOfflineOpIds(&offlineOps)))
      {
        nsCString srcFolderUri;
        GetURI(srcFolderUri);
        nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
        for (uint32_t opIndex = 0; opIndex < offlineOps.Length(); opIndex++)
        {
          dstFolderDB->GetOfflineOpForKey(offlineOps[opIndex], false,
                                          getter_AddRefs(currentOp));
          if (currentOp)
          {
            nsCString opSrcUri;
            currentOp->GetSourceFolderURI(getter_Copies(opSrcUri));
            if (opSrcUri.Equals(srcFolderUri))
            {
              nsMsgKey srcMessageKey;
              currentOp->GetSrcMessageKey(&srcMessageKey);
              for (uint32_t msgIndex = 0; msgIndex < aNumKeys; msgIndex++)
              {
                if (srcMessageKey == aMsgKeys[msgIndex])
                {
                  nsCOMPtr<nsIMsgDBHdr> fakeDestHdr;
                  dstFolderDB->GetMsgHdrForKey(offlineOps[opIndex],
                    getter_AddRefs(fakeDestHdr));
                  if (fakeDestHdr)
                    messages->AppendElement(fakeDestHdr);
                  break;
                }
              }
            }
          }
        }
        // 3rd parameter: Set offline flag.
        destImapFolder->SetPendingAttributes(messages, isMove, true);
      }
    }
    // if we can't get the dst folder db, we should still try to playback
    // the offline move/copy.
  }

  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr <nsIURI> resultUrl;
  nsAutoCString uids;
  AllocateUidStringFromKeys(aMsgKeys, aNumKeys, uids);
  rv = imapService->OnlineMessageCopy(this, uids, aDstFolder,
                                      true, isMove, aUrlListener,
                                      getter_AddRefs(resultUrl), nullptr, aWindow);
  if (resultUrl)
  {
    nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(resultUrl, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr <nsIUrlListener> folderListener = do_QueryInterface(aDstFolder);
    if (folderListener)
      mailnewsUrl->RegisterListener(folderListener);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::AddMoveResultPseudoKey(nsMsgKey aMsgKey)
{
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIMsgDBHdr> pseudoHdr;
  rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(pseudoHdr));
  NS_ENSURE_SUCCESS(rv, rv);
  nsCString messageId;
  pseudoHdr->GetMessageId(getter_Copies(messageId));
  // err on the side of caution and ignore messages w/o messageid.
  if (messageId.IsEmpty())
    return NS_OK;
  m_pseudoHdrs.Put(messageId, aMsgKey);
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::StoreImapFlags(int32_t flags, bool addFlags,
                                               nsMsgKey *keys, uint32_t numKeys,
                                               nsIUrlListener *aUrlListener)
{
  nsresult rv;
  if (!WeAreOffline())
  {
    nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsAutoCString msgIds;
    AllocateUidStringFromKeys(keys, numKeys, msgIds);
    if (addFlags)
      imapService->AddMessageFlags(this, aUrlListener ? aUrlListener : this,
                                   nullptr, msgIds, flags, true);
    else
      imapService->SubtractMessageFlags(this, aUrlListener ? aUrlListener : this,
                                        nullptr, msgIds, flags, true);
  }
  else
  {
    rv = GetDatabase();
    if (NS_SUCCEEDED(rv) && mDatabase)
    {
      uint32_t total = numKeys;
      for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
      {
        nsCOMPtr <nsIMsgOfflineImapOperation> op;
        rv = mDatabase->GetOfflineOpForKey(keys[keyIndex], true, getter_AddRefs(op));
        SetFlag(nsMsgFolderFlags::OfflineEvents);
        if (NS_SUCCEEDED(rv) && op)
        {
          imapMessageFlagsType newFlags;
          op->GetNewFlags(&newFlags);
          op->SetFlagOperation(addFlags ? newFlags | flags : newFlags & ~flags);
        }
      }
      mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline flags
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::LiteSelect(nsIUrlListener *aUrlListener,
                                           nsIMsgWindow *aMsgWindow)
{
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  return imapService->LiteSelectFolder(this, aUrlListener,
                                       aMsgWindow, nullptr);
}

nsresult nsImapMailFolder::GetFolderOwnerUserName(nsACString& userName)
{
  if ((mFlags & nsMsgFolderFlags::ImapPersonal) ||
    !(mFlags & (nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::ImapOtherUser)))
  {
    // this is one of our personal mail folders
    // return our username on this host
    nsCOMPtr<nsIMsgIncomingServer> server;
    nsresult rv = GetServer(getter_AddRefs(server));
    return NS_FAILED(rv) ? rv : server->GetUsername(userName);
  }

  // the only other type of owner is if it's in the other users' namespace
  if (!(mFlags & nsMsgFolderFlags::ImapOtherUser))
    return NS_OK;

  if (m_ownerUserName.IsEmpty())
  {
    nsCString onlineName;
    GetOnlineName(onlineName);
    m_ownerUserName = nsIMAPNamespaceList::GetFolderOwnerNameFromPath(GetNamespaceForFolder(), onlineName.get());
  }
  userName = m_ownerUserName;
  return NS_OK;
}

nsIMAPNamespace *nsImapMailFolder::GetNamespaceForFolder()
{
  if (!m_namespace)
  {
#ifdef DEBUG_bienvenu
    // Make sure this isn't causing us to open the database
    NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown, "haven't set hierarchy delimiter");
#endif
    nsCString serverKey;
    nsCString onlineName;
    GetServerKey(serverKey);
    GetOnlineName(onlineName);
    char hierarchyDelimiter;
    GetHierarchyDelimiter(&hierarchyDelimiter);

    m_namespace = nsIMAPNamespaceList::GetNamespaceForFolder(
                    serverKey.get(), onlineName.get(), hierarchyDelimiter);
    NS_ASSERTION(m_namespace, "didn't get namespace for folder");
    if (m_namespace)
    {
      nsIMAPNamespaceList::SuggestHierarchySeparatorForNamespace(m_namespace, hierarchyDelimiter);
      m_folderIsNamespace = nsIMAPNamespaceList::GetFolderIsNamespace(
                              serverKey.get(), onlineName.get(),
                              hierarchyDelimiter, m_namespace);
    }
  }
  return m_namespace;
}

void nsImapMailFolder::SetNamespaceForFolder(nsIMAPNamespace *ns)
{
#ifdef DEBUG_bienvenu
  NS_ASSERTION(ns, "null namespace");
#endif
  m_namespace = ns;
}

NS_IMETHODIMP nsImapMailFolder::FolderPrivileges(nsIMsgWindow *window)
{
  NS_ENSURE_ARG_POINTER(window);
  nsresult rv = NS_OK;  // if no window...
  if (!m_adminUrl.IsEmpty())
  {
    nsCOMPtr<nsIExternalProtocolService> extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
    if (extProtService)
    {
      nsAutoCString scheme;
      nsCOMPtr<nsIURI> uri;
      if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), m_adminUrl.get())))
        return rv;
      uri->GetScheme(scheme);
      if (!scheme.IsEmpty())
      {
        // if the URL scheme does not correspond to an exposed protocol, then we
        // need to hand this link click over to the external protocol handler.
        bool isExposed;
        rv = extProtService->IsExposedProtocol(scheme.get(), &isExposed);
        if (NS_SUCCEEDED(rv) && !isExposed)
          return extProtService->LoadURI(uri, nullptr);
      }
    }
  }
  else
  {
    nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv,rv);
    rv = imapService->GetFolderAdminUrl(this, window, this, nullptr);
    if (NS_SUCCEEDED(rv))
      m_urlRunning = true;
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetHasAdminUrl(bool *aBool)
{
  NS_ENSURE_ARG_POINTER(aBool);
  nsCOMPtr<nsIImapIncomingServer> imapServer;
  nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
  nsCString manageMailAccountUrl;
  if (NS_SUCCEEDED(rv) && imapServer)
    rv = imapServer->GetManageMailAccountUrl(manageMailAccountUrl);
  *aBool = (NS_SUCCEEDED(rv) && !manageMailAccountUrl.IsEmpty());
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetAdminUrl(nsACString& aResult)
{
  aResult = m_adminUrl;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetAdminUrl(const nsACString& adminUrl)
{
  m_adminUrl = adminUrl;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetHdrParser(nsIMsgParseMailMsgState **aHdrParser)
{
  NS_ENSURE_ARG_POINTER(aHdrParser);
  NS_IF_ADDREF(*aHdrParser = m_msgParser);
  return NS_OK;
}

  // this is used to issue an arbitrary imap command on the passed in msgs.
  // It assumes the command needs to be run in the selected state.
NS_IMETHODIMP nsImapMailFolder::IssueCommandOnMsgs(const nsACString& command, const char *uids, nsIMsgWindow *aWindow, nsIURI **url)
{
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);
  return imapService->IssueCommandOnMsgs(this, aWindow, command, nsDependentCString(uids), url);
}

NS_IMETHODIMP nsImapMailFolder::FetchCustomMsgAttribute(const nsACString& attribute, const char *uids, nsIMsgWindow *aWindow, nsIURI **url)
{
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  return imapService->FetchCustomMsgAttribute(this, aWindow, attribute, nsDependentCString(uids), url);
}

nsresult nsImapMailFolder::MoveIncorporatedMessage(nsIMsgDBHdr *mailHdr,
                                                   nsIMsgDatabase *sourceDB,
                                                   const nsACString& destFolderUri,
                                                   nsIMsgFilter *filter,
                                                   nsIMsgWindow *msgWindow)
{
  nsresult rv;
  if (m_moveCoalescer)
  {
    nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIRDFResource> res;
    rv = rdf->GetResource(destFolderUri, getter_AddRefs(res));
    if (NS_FAILED(rv))
      return rv;

    nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &rv));
    if (NS_FAILED(rv))
      return rv;

    if (destIFolder)
    {
      // check if the destination is a real folder (by checking for null parent)
      // and if it can file messages (e.g., servers or news folders can't file messages).
      // Or read only imap folders...
      bool canFileMessages = true;
      nsCOMPtr<nsIMsgFolder> parentFolder;
      destIFolder->GetParent(getter_AddRefs(parentFolder));
      if (parentFolder)
        destIFolder->GetCanFileMessages(&canFileMessages);
      if (filter && (!parentFolder || !canFileMessages))
      {
        filter->SetEnabled(false);
        m_filterList->SaveToDefaultFile();
        destIFolder->ThrowAlertMsg("filterDisabled",msgWindow);
        return NS_MSG_NOT_A_MAIL_FOLDER;
      }
      // put the header into the source db, since it needs to be there when we copy it
      // and we need a valid header to pass to StartAsyncCopyMessagesInto
      nsMsgKey keyToFilter;
      mailHdr->GetMessageKey(&keyToFilter);

      if (sourceDB && destIFolder)
      {
        bool imapDeleteIsMoveToTrash = DeleteIsMoveToTrash();
        m_moveCoalescer->AddMove (destIFolder, keyToFilter);
        // For each folder, we need to keep track of the ids we want to move to that
        // folder - we used to store them in the MSG_FolderInfo and then when we'd finished
        // downloading headers, we'd iterate through all the folders looking for the ones
        // that needed messages moved into them - perhaps instead we could
        // keep track of nsIMsgFolder, nsTArray<nsMsgKey> pairs here in the imap code.
        // nsTArray<nsMsgKey> *idsToMoveFromInbox = msgFolder->GetImapIdsToMoveFromInbox();
        // idsToMoveFromInbox->AppendElement(keyToFilter);
        if (imapDeleteIsMoveToTrash)
        {
        }
        bool isRead = false;
        mailHdr->GetIsRead(&isRead);
        if (imapDeleteIsMoveToTrash)
          rv = NS_OK;
      }
    }
  } else
    rv = NS_ERROR_UNEXPECTED;

  // we have to return an error because we do not actually move the message
  // it is done async and that can fail
  return rv;
}

/**
 * This method assumes that key arrays and flag states are sorted by increasing key.
 */
void nsImapMailFolder::FindKeysToDelete(const nsTArray<nsMsgKey> &existingKeys,
                                        nsTArray<nsMsgKey> &keysToDelete,
                                        nsIImapFlagAndUidState *flagState,
                                        uint32_t boxFlags)
{
  bool showDeletedMessages = ShowDeletedMessages();
  int32_t numMessageInFlagState;
  bool partialUIDFetch;
  uint32_t uidOfMessage;
  imapMessageFlagsType flags;

  flagState->GetNumberOfMessages(&numMessageInFlagState);
  flagState->GetPartialUIDFetch(&partialUIDFetch);

  // if we're doing a partialUIDFetch, just delete the keys from the db
  // that have the deleted flag set (if not using imap delete model)
  // and return.
  if (partialUIDFetch)
  {
    if (!showDeletedMessages)
    {
      for (uint32_t i = 0; (int32_t) i < numMessageInFlagState; i++)
      {
        flagState->GetUidOfMessage(i, &uidOfMessage);
        // flag state will be zero filled up to first real uid, so ignore those.
        if (uidOfMessage)
        {
          flagState->GetMessageFlags(i, &flags);
          if (flags & kImapMsgDeletedFlag)
            keysToDelete.AppendElement(uidOfMessage);
        }
      }
    }
    else if (boxFlags & kJustExpunged)
    {
      // we've just issued an expunge with a partial flag state. We should
      // delete headers with the imap deleted flag set, because we can't
      // tell from the expunge response which messages were deleted.
      nsCOMPtr <nsISimpleEnumerator> hdrs;
      nsresult rv = GetMessages(getter_AddRefs(hdrs));
      NS_ENSURE_SUCCESS_VOID(rv);
      bool hasMore = false;
      nsCOMPtr <nsIMsgDBHdr> pHeader;
      while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
      {
        nsCOMPtr <nsISupports> supports;
        rv = hdrs->GetNext(getter_AddRefs(supports));
        NS_ENSURE_SUCCESS_VOID(rv);
        pHeader = do_QueryInterface(supports, &rv);
        NS_ENSURE_SUCCESS_VOID(rv);
        uint32_t msgFlags;
        pHeader->GetFlags(&msgFlags);
        if (msgFlags & nsMsgMessageFlags::IMAPDeleted)
        {
          nsMsgKey msgKey;
          pHeader->GetMessageKey(&msgKey);
          keysToDelete.AppendElement(msgKey);
        }
      }
    }
    return;
  }
  // otherwise, we have a complete set of uid's and flags, so we delete
  // anything that's in existingKeys but not in the flag state, as well
  // as messages with the deleted flag set.
  uint32_t total = existingKeys.Length();
  int onlineIndex = 0; // current index into flagState
  for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
  {

    while ((onlineIndex < numMessageInFlagState) &&
           NS_SUCCEEDED(flagState->GetUidOfMessage(onlineIndex, &uidOfMessage)) &&
           (existingKeys[keyIndex] > uidOfMessage))
      onlineIndex++;

    flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
    flagState->GetMessageFlags(onlineIndex, &flags);
    // delete this key if it is not there or marked deleted
    if ( (onlineIndex >= numMessageInFlagState ) ||
       (existingKeys[keyIndex] != uidOfMessage) ||
       ((flags & kImapMsgDeletedFlag) && !showDeletedMessages) )
    {
      nsMsgKey doomedKey = existingKeys[keyIndex];
      if ((int32_t) doomedKey <= 0 && doomedKey != nsMsgKey_None)
        continue;

      keysToDelete.AppendElement(existingKeys[keyIndex]);
    }

    flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
    if (existingKeys[keyIndex] == uidOfMessage)
      onlineIndex++;
  }
}

void nsImapMailFolder::FindKeysToAdd(const nsTArray<nsMsgKey> &existingKeys, nsTArray<nsMsgKey> &keysToFetch, uint32_t &numNewUnread, nsIImapFlagAndUidState *flagState)
{
  bool showDeletedMessages = ShowDeletedMessages();
  int dbIndex=0; // current index into existingKeys
  int32_t existTotal, numberOfKnownKeys;
  int32_t messageIndex;

  numNewUnread = 0;
  existTotal = numberOfKnownKeys = existingKeys.Length();
  flagState->GetNumberOfMessages(&messageIndex);
  bool partialUIDFetch;
  flagState->GetPartialUIDFetch(&partialUIDFetch);

  for (int32_t flagIndex=0; flagIndex < messageIndex; flagIndex++)
  {
    uint32_t uidOfMessage;
    flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
    while ( (flagIndex < numberOfKnownKeys) && (dbIndex < existTotal) &&
        existingKeys[dbIndex] < uidOfMessage)
      dbIndex++;

    if ( (flagIndex >= numberOfKnownKeys)  ||
       (dbIndex >= existTotal) ||
       (existingKeys[dbIndex] != uidOfMessage ) )
    {
      numberOfKnownKeys++;

      imapMessageFlagsType flags;
      flagState->GetMessageFlags(flagIndex, &flags);
      NS_ASSERTION(uidOfMessage != nsMsgKey_None, "got invalid msg key");
      if (uidOfMessage && uidOfMessage != nsMsgKey_None && (showDeletedMessages || ! (flags & kImapMsgDeletedFlag)))
      {
        if (mDatabase)
        {
          bool dbContainsKey;
          if (NS_SUCCEEDED(mDatabase->ContainsKey(uidOfMessage, &dbContainsKey)) &&
              dbContainsKey)
          {
            // this is expected in the partial uid fetch case because the
            // flag state does not contain all messages, so the db has
            // messages the flag state doesn't know about.
            if (!partialUIDFetch)
              NS_ERROR("db has key - flagState messed up?");
            continue;
          }
        }
        keysToFetch.AppendElement(uidOfMessage);
        if (! (flags & kImapMsgSeenFlag))
          numNewUnread++;
      }
    }
  }
}

NS_IMETHODIMP nsImapMailFolder::GetMsgHdrsToDownload(bool *aMoreToDownload,
                                                     int32_t *aTotalCount,
                                                     uint32_t *aLength,
                                                     nsMsgKey **aKeys)
{
  NS_ENSURE_ARG_POINTER(aMoreToDownload);
  NS_ENSURE_ARG_POINTER(aTotalCount);
  NS_ENSURE_ARG_POINTER(aLength);
  NS_ENSURE_ARG_POINTER(aKeys);

  *aMoreToDownload = false;
  *aTotalCount = m_totalKeysToFetch;
  if (m_keysToFetch.IsEmpty())
  {
    *aLength = 0;
    return NS_OK;
  }

  // if folder isn't open in a window, no reason to limit the number of headers
  // we download.
  nsCOMPtr<nsIMsgMailSession> session = do_GetService(NS_MSGMAILSESSION_CONTRACTID);
  bool folderOpen = false;
  if (session)
    session->IsFolderOpenInWindow(this, &folderOpen);

  int32_t hdrChunkSize = 200;
  if (folderOpen)
  {
    nsresult rv;
    nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    if (prefBranch)
      prefBranch->GetIntPref("mail.imap.hdr_chunk_size", &hdrChunkSize);
  }
  int32_t numKeysToFetch = m_keysToFetch.Length();
  int32_t startIndex = 0;
  if (folderOpen && hdrChunkSize > 0 && (int32_t) m_keysToFetch.Length() > hdrChunkSize)
  {
    numKeysToFetch = hdrChunkSize;
    *aMoreToDownload = true;
    startIndex = m_keysToFetch.Length() - hdrChunkSize;
  }
  *aKeys = (nsMsgKey *) moz_xmemdup(&m_keysToFetch[startIndex],
                                    numKeysToFetch * sizeof(nsMsgKey));
  NS_ENSURE_TRUE(*aKeys, NS_ERROR_OUT_OF_MEMORY);
  // Remove these for the incremental header download case, so that
  // we know we don't have to download them again.
  m_keysToFetch.RemoveElementsAt(startIndex, numKeysToFetch);
  *aLength = numKeysToFetch;

  return NS_OK;
}

void nsImapMailFolder::PrepareToAddHeadersToMailDB(nsIImapProtocol* aProtocol)
{
  // now, tell it we don't need any bodies.
  aProtocol->NotifyBodysToDownload(nullptr, 0);
}

void nsImapMailFolder::TweakHeaderFlags(nsIImapProtocol* aProtocol, nsIMsgDBHdr *tweakMe)
{
  if (mDatabase && aProtocol && tweakMe)
  {
    tweakMe->SetMessageKey(m_curMsgUid);
    tweakMe->SetMessageSize(m_nextMessageByteLength);

    bool foundIt = false;
    imapMessageFlagsType imap_flags;

    nsCString customFlags;
    nsresult rv = aProtocol->GetFlagsForUID(m_curMsgUid, &foundIt, &imap_flags, getter_Copies(customFlags));
    if (NS_SUCCEEDED(rv) && foundIt)
    {
      // make a mask and clear these message flags
      uint32_t mask = nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |
                      nsMsgMessageFlags::Marked | nsMsgMessageFlags::IMAPDeleted |
                      nsMsgMessageFlags::Labels;
      uint32_t dbHdrFlags;

      tweakMe->GetFlags(&dbHdrFlags);
      tweakMe->AndFlags(~mask, &dbHdrFlags);

      // set the new value for these flags
      uint32_t newFlags = 0;
      if (imap_flags & kImapMsgSeenFlag)
        newFlags |= nsMsgMessageFlags::Read;
      else // if (imap_flags & kImapMsgRecentFlag)
        newFlags |= nsMsgMessageFlags::New;

      // Okay here is the MDN needed logic (if DNT header seen):
      /* if server support user defined flag:
                    MDNSent flag set => clear kMDNNeeded flag
                    MDNSent flag not set => do nothing, leave kMDNNeeded on
                    else if
                    not nsMsgMessageFlags::New => clear kMDNNeeded flag
                   nsMsgMessageFlags::New => do nothing, leave kMDNNeeded on
               */
      uint16_t userFlags;
      rv = aProtocol->GetSupportedUserFlags(&userFlags);
      if (NS_SUCCEEDED(rv) && (userFlags & (kImapMsgSupportUserFlag |
                            kImapMsgSupportMDNSentFlag)))
      {
        if (imap_flags & kImapMsgMDNSentFlag)
        {
          newFlags |= nsMsgMessageFlags::MDNReportSent;
          if (dbHdrFlags & nsMsgMessageFlags::MDNReportNeeded)
            tweakMe->AndFlags(~nsMsgMessageFlags::MDNReportNeeded, &dbHdrFlags);
        }
      }

      if (imap_flags & kImapMsgAnsweredFlag)
        newFlags |= nsMsgMessageFlags::Replied;
      if (imap_flags & kImapMsgFlaggedFlag)
        newFlags |= nsMsgMessageFlags::Marked;
      if (imap_flags & kImapMsgDeletedFlag)
        newFlags |= nsMsgMessageFlags::IMAPDeleted;
      if (imap_flags & kImapMsgForwardedFlag)
        newFlags |= nsMsgMessageFlags::Forwarded;

      // db label flags are 0x0E000000 and imap label flags are 0x0E00
      // so we need to shift 16 bits to the left to convert them.
      if (imap_flags & kImapMsgLabelFlags)
      {
        // we need to set label attribute on header because the dbview code
        // does msgHdr->GetLabel when asked to paint a row
        tweakMe->SetLabel((imap_flags & kImapMsgLabelFlags) >> 9);
        newFlags |= (imap_flags & kImapMsgLabelFlags) << 16;
      }
      if (newFlags)
        tweakMe->OrFlags(newFlags, &dbHdrFlags);
      if (!customFlags.IsEmpty())
        (void) HandleCustomFlags(m_curMsgUid, tweakMe, userFlags, customFlags);
    }
  }
}

NS_IMETHODIMP
nsImapMailFolder::SetupMsgWriteStream(nsIFile * aFile, bool addDummyEnvelope)
{
  nsresult rv;
  aFile->Remove(false);
  rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_tempMessageStream), aFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 00700);
  if (m_tempMessageStream && addDummyEnvelope)
  {
    nsAutoCString result;
    char *ct;
    uint32_t writeCount;
    time_t now = time ((time_t*) 0);
    ct = ctime(&now);
    ct[24] = 0;
    result = "From - ";
    result += ct;
    result += MSG_LINEBREAK;

    m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
    result = "X-Mozilla-Status: 0001";
    result += MSG_LINEBREAK;
    m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
    result =  "X-Mozilla-Status2: 00000000";
    result += MSG_LINEBREAK;
    m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *window)
{
  nsAutoCString messageIds;
  nsTArray<nsMsgKey> srcKeyArray;
  nsresult rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
  if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv;

  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv,rv);

  rv = AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
  if (NS_FAILED(rv))
  {
    ThrowAlertMsg("operationFailedFolderBusy", window);
    return rv;
  }
  return imapService->DownloadMessagesForOffline(messageIds, this, this, window);
}

NS_IMETHODIMP nsImapMailFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow)
{
  nsresult rv;
  nsCOMPtr <nsIURI> runningURI;
  bool noSelect;
  GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);

  if (!noSelect)
  {
    nsAutoCString messageIdsToDownload;
    nsTArray<nsMsgKey> msgsToDownload;

    GetDatabase();
    m_downloadingFolderForOfflineUse = true;

    rv = AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
    if (NS_FAILED(rv))
    {
      m_downloadingFolderForOfflineUse = false;
      ThrowAlertMsg("operationFailedFolderBusy", msgWindow);
      return rv;
    }
    nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    // Selecting the folder with nsIImapUrl::shouldStoreMsgOffline true will
    // cause us to fetch any message bodies we don't have.
    m_urlListener = listener;
    rv = imapService->SelectFolder(this, this, msgWindow,
                                   getter_AddRefs(runningURI));
    if (NS_SUCCEEDED(rv))
    {
      nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(runningURI));
      if (imapUrl)
        imapUrl->SetStoreResultsOffline(true);
      m_urlRunning = true;
    }
  }
  else
    rv = NS_MSG_FOLDER_UNREADABLE;
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::ParseAdoptedMsgLine(const char *adoptedMessageLine,
                                      nsMsgKey uidOfMessage,
                                      nsIImapUrl *aImapUrl)
{
  NS_ENSURE_ARG_POINTER(aImapUrl);
  uint32_t count = 0;
  nsresult rv;
  // remember the uid of the message we're downloading.
  m_curMsgUid = uidOfMessage;
  if (!m_offlineHeader)
  {
    rv = GetMessageHeader(uidOfMessage, getter_AddRefs(m_offlineHeader));
    if (NS_SUCCEEDED(rv) && !m_offlineHeader)
      rv = NS_ERROR_UNEXPECTED;
    NS_ENSURE_SUCCESS(rv, rv);
    rv = StartNewOfflineMessage();
    NS_ENSURE_SUCCESS(rv, rv);
  }
  // adoptedMessageLine is actually a string with a lot of message lines, separated by native line terminators
  // we need to count the number of MSG_LINEBREAK's to determine how much to increment m_numOfflineMsgLines by.
  const char *nextLine = adoptedMessageLine;
  do
  {
    m_numOfflineMsgLines++;
    nextLine = PL_strstr(nextLine, MSG_LINEBREAK);
    if (nextLine)
      nextLine += MSG_LINEBREAK_LEN;
  }
  while (nextLine && *nextLine);

  if (m_tempMessageStream)
  {
    nsCOMPtr <nsISeekableStream> seekable (do_QueryInterface(m_tempMessageStream));
    if (seekable)
      seekable->Seek(PR_SEEK_END, 0);
    rv = m_tempMessageStream->Write(adoptedMessageLine,
                PL_strlen(adoptedMessageLine), &count);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

void nsImapMailFolder::EndOfflineDownload()
{
  if (m_tempMessageStream)
  {
    m_tempMessageStream->Close();
    m_tempMessageStream = nullptr;
    ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
    if (mDatabase)
      mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  m_offlineHeader = nullptr;
}

NS_IMETHODIMP
nsImapMailFolder::NormalEndMsgWriteStream(nsMsgKey uidOfMessage,
                                          bool markRead,
                                          nsIImapUrl *imapUrl,
                                          int32_t updatedMessageSize)
{
  if (updatedMessageSize != -1) {
    // retrieve the message header to update size, if we don't already have it
    nsCOMPtr<nsIMsgDBHdr> msgHeader = m_offlineHeader;
    if (!msgHeader)
      GetMessageHeader(uidOfMessage, getter_AddRefs(msgHeader));
    if (msgHeader) {
      uint32_t msgSize;
      msgHeader->GetMessageSize(&msgSize);
      MOZ_LOG(IMAP, mozilla::LogLevel::Debug, ("Updating stored message size from %u, new size %d",
                                  msgSize, updatedMessageSize));
      msgHeader->SetMessageSize(updatedMessageSize);
      // only commit here if this isn't an offline message
      // offline header gets committed in EndNewOfflineMessage() called below
      if (mDatabase && !m_offlineHeader)
        mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
    }
    else
      NS_WARNING("Failed to get message header when trying to update message size");
  }

  if (m_offlineHeader)
    EndNewOfflineMessage();

  m_curMsgUid = uidOfMessage;

  // Apply filter now if it needed a body
  if (m_filterListRequiresBody)
  {
    if (m_filterList)
    {
      nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
      GetMessageHeader(uidOfMessage, getter_AddRefs(newMsgHdr));
      GetMoveCoalescer();
      nsCOMPtr<nsIMsgWindow> msgWindow;
      if (imapUrl)
      {
        nsresult rv;
        nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
        msgUrl = do_QueryInterface(imapUrl, &rv);
        if (msgUrl && NS_SUCCEEDED(rv))
          msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
      }
      m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
                                      this, mDatabase, nullptr, 0, this,
                                      msgWindow);
      NotifyFolderEvent(kFiltersApplied);
    }
    // Process filter plugins and other items normally done at the end of
    // HeaderFetchCompleted.
    bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
    PlaybackCoalescedOperations();

    bool filtersRun;
    CallFilterPlugins(nullptr, &filtersRun);
    int32_t numNewBiffMsgs = 0;
    if (m_performingBiff)
      GetNumNewMessages(false, &numNewBiffMsgs);

    if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
        (!pendingMoves || !ShowPreviewText()))
    {
      // If we are performing biff for this folder, tell the
      // stand-alone biff about the new high water mark
      // We must ensure that the server knows that we are performing biff.
      // Otherwise the stand-alone biff won't fire.
      nsCOMPtr<nsIMsgIncomingServer> server;
      if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
        server->SetPerformingBiff(true);

      SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
      if (server)
        server->SetPerformingBiff(false);
      m_performingBiff = false;
    }

    if (m_filterList)
      (void)m_filterList->FlushLogIfNecessary();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::AbortMsgWriteStream()
{
  m_offlineHeader = nullptr;
  return NS_ERROR_FAILURE;
}

    // message move/copy related methods
NS_IMETHODIMP
nsImapMailFolder::OnlineCopyCompleted(nsIImapProtocol *aProtocol, ImapOnlineCopyState aCopyState)
{
  NS_ENSURE_ARG_POINTER(aProtocol);

  nsresult rv;
  if (aCopyState == ImapOnlineCopyStateType::kSuccessfulCopy)
  {
    nsCOMPtr <nsIImapUrl> imapUrl;
    rv = aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
    if (NS_FAILED(rv) || !imapUrl) return NS_ERROR_FAILURE;
    nsImapAction action;
    rv = imapUrl->GetImapAction(&action);
    if (NS_FAILED(rv)) return rv;
    if (action != nsIImapUrl::nsImapOnlineToOfflineMove)
      return NS_ERROR_FAILURE; // don't assert here...
    nsCString messageIds;
    rv = imapUrl->GetListOfMessageIds(messageIds);
    if (NS_FAILED(rv)) return rv;
    nsCOMPtr<nsIImapService> imapService =  do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv,rv);
    return imapService->AddMessageFlags(this, nullptr, nullptr,
                                      messageIds,
                                      kImapMsgDeletedFlag,
                                      true);
  }
  /* unhandled copystate */
  if (m_copyState) // whoops, this is the wrong folder - should use the source folder
  {
    nsCOMPtr<nsIMsgFolder> srcFolder;
    srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv);
    if (srcFolder)
      srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
  }
  else
    rv = NS_ERROR_FAILURE;

  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::CloseMockChannel(nsIImapMockChannel * aChannel)
{
  aChannel->Close();
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::ReleaseUrlCacheEntry(nsIMsgMailNewsUrl *aUrl)
{
  NS_ENSURE_ARG_POINTER(aUrl);
  return aUrl->SetMemCacheEntry(nullptr);
}

NS_IMETHODIMP
nsImapMailFolder::BeginMessageUpload()
{
  return NS_ERROR_FAILURE;
}

nsresult nsImapMailFolder::HandleCustomFlags(nsMsgKey uidOfMessage,
                                             nsIMsgDBHdr *dbHdr,
                                             uint16_t userFlags,
                                             nsCString &keywords)
{
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);

  ToLowerCase(keywords);
  bool messageClassified = true;
  // Mac Mail uses "NotJunk"
  if (keywords.Find("NonJunk", /* ignoreCase = */ true) != kNotFound ||
      keywords.Find("NotJunk", /* ignoreCase = */ true) != kNotFound)
  {
    nsAutoCString msgJunkScore;
    msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
    mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
  }
  // ### TODO: we really should parse the keywords into space delimited keywords before checking
  else if (keywords.Find("Junk", /* ignoreCase = */ true) != kNotFound)
  {
    uint32_t newFlags;
    dbHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
    nsAutoCString msgJunkScore;
    msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_SPAM_SCORE);
    mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
  }
  else
    messageClassified = false;
  if (messageClassified)
  {
    // only set the junkscore origin if it wasn't set before.
    nsCString existingProperty;
    dbHdr->GetStringProperty("junkscoreorigin", getter_Copies(existingProperty));
    if (existingProperty.IsEmpty())
      dbHdr->SetStringProperty("junkscoreorigin", "imapflag");
  }
  return (userFlags & kImapMsgSupportUserFlag) ?
          dbHdr->SetStringProperty("keywords", keywords.get()) : NS_OK;
}

// synchronize the message flags in the database with the server flags
nsresult nsImapMailFolder::SyncFlags(nsIImapFlagAndUidState *flagState)
{
  nsresult rv = GetDatabase(); // we need a database for this
  NS_ENSURE_SUCCESS(rv, rv);
  bool partialUIDFetch;
  flagState->GetPartialUIDFetch(&partialUIDFetch);

  // update all of the database flags
  int32_t messageIndex;
  uint32_t messageSize;

  // Take this opportunity to recalculate the folder size, if we're not a
  // partial (condstore) fetch.
  int64_t newFolderSize = 0;

  flagState->GetNumberOfMessages(&messageIndex);

  uint16_t supportedUserFlags;
  flagState->GetSupportedUserFlags(&supportedUserFlags);

  for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++)
  {
    uint32_t uidOfMessage;
    flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
    imapMessageFlagsType flags;
    flagState->GetMessageFlags(flagIndex, &flags);
    nsCOMPtr<nsIMsgDBHdr> dbHdr;
    bool containsKey;
    rv = mDatabase->ContainsKey(uidOfMessage , &containsKey);
    // if we don't have the header, don't diddle the flags.
    // GetMsgHdrForKey will create the header if it doesn't exist.
    if (NS_FAILED(rv) || !containsKey)
      continue;

    rv = mDatabase->GetMsgHdrForKey(uidOfMessage, getter_AddRefs(dbHdr));
    if (NS_SUCCEEDED(dbHdr->GetMessageSize(&messageSize)))
      newFolderSize += messageSize;

    nsCString keywords;
    if (NS_SUCCEEDED(flagState->GetCustomFlags(uidOfMessage, getter_Copies(keywords))))
        HandleCustomFlags(uidOfMessage, dbHdr, supportedUserFlags, keywords);

    NotifyMessageFlagsFromHdr(dbHdr, uidOfMessage, flags);
  }
  if (!partialUIDFetch && newFolderSize != mFolderSize)
  {
    int64_t oldFolderSize = mFolderSize;
    mFolderSize = newFolderSize;
    NotifyIntPropertyChanged(kFolderSize, oldFolderSize, mFolderSize);
  }

  return NS_OK;
}

// helper routine to sync the flags on a given header
nsresult
nsImapMailFolder::NotifyMessageFlagsFromHdr(nsIMsgDBHdr *dbHdr,
                                            nsMsgKey msgKey, uint32_t flags)
{
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);

  // Although it may seem strange to keep a local reference of mDatabase here,
  // the current lifetime management of databases requires that methods sometimes
  // null the database when they think they opened it. Unfortunately experience
  // shows this happens when we don't expect, so for crash protection best
  // practice with the current flawed database management is to keep a local
  // reference when there will be complex calls in a method. See bug 1312254.
  nsCOMPtr<nsIMsgDatabase> database(mDatabase);
  NS_ENSURE_STATE(database);

  database->MarkHdrRead(dbHdr, (flags & kImapMsgSeenFlag) != 0, nullptr);
  database->MarkHdrReplied(dbHdr, (flags & kImapMsgAnsweredFlag) != 0, nullptr);
  database->MarkHdrMarked(dbHdr, (flags & kImapMsgFlaggedFlag) != 0, nullptr);
  database->MarkImapDeleted(msgKey, (flags & kImapMsgDeletedFlag) != 0, nullptr);

  uint32_t supportedFlags;
  GetSupportedUserFlags(&supportedFlags);
  if (supportedFlags & kImapMsgSupportForwardedFlag)
    database->MarkForwarded(msgKey, (flags & kImapMsgForwardedFlag) != 0, nullptr);
  // this turns on labels, but it doesn't handle the case where the user
  // unlabels a message on one machine, and expects it to be unlabeled
  // on their other machines. If I turn that on, I'll be removing all the labels
  // that were assigned before we started storing them on the server, which will
  // make some people very unhappy.
  if (flags & kImapMsgLabelFlags)
    database->SetLabel(msgKey, (flags & kImapMsgLabelFlags) >> 9);
  else
  {
    if (supportedFlags & kImapMsgLabelFlags)
      database->SetLabel(msgKey, 0);
  }
  if (supportedFlags & kImapMsgSupportMDNSentFlag)
    database->MarkMDNSent(msgKey, (flags & kImapMsgMDNSentFlag) != 0, nullptr);

  return NS_OK;
}

// message flags operation - this is called from the imap protocol,
// proxied over from the imap thread to the ui thread, when a flag changes
NS_IMETHODIMP
nsImapMailFolder::NotifyMessageFlags(uint32_t aFlags,
                                     const nsACString &aKeywords,
                                     nsMsgKey aMsgKey, uint64_t aHighestModSeq)
{
  if (NS_SUCCEEDED(GetDatabase()) && mDatabase)
  {
    bool msgDeleted = aFlags & kImapMsgDeletedFlag;
    if (aHighestModSeq || msgDeleted)
    {
      nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
      mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
      if (dbFolderInfo)
      {
        if (aHighestModSeq)
        {
          char intStrBuf[40];
          PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu",  aHighestModSeq);
          MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
                  ("NotifyMessageFlags(): Store highest MODSEQ=%" PRIu64 " for folder=%s",
                   aHighestModSeq, m_onlineFolderName.get()));
          dbFolderInfo->SetCharProperty(kModSeqPropertyName, nsDependentCString(intStrBuf));
        }
        if (msgDeleted)
        {
          uint32_t oldDeletedCount;
          dbFolderInfo->GetUint32Property(kDeletedHdrCountPropertyName, 0, &oldDeletedCount);
          dbFolderInfo->SetUint32Property(kDeletedHdrCountPropertyName, oldDeletedCount + 1);
        }
      }
    }
    nsCOMPtr<nsIMsgDBHdr> dbHdr;
    bool containsKey;
    nsresult rv = mDatabase->ContainsKey(aMsgKey , &containsKey);
    // if we don't have the header, don't diddle the flags.
    // GetMsgHdrForKey will create the header if it doesn't exist.
    if (NS_FAILED(rv) || !containsKey)
      return rv;
    rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(dbHdr));
    if (NS_SUCCEEDED(rv) && dbHdr)
    {
      uint32_t supportedUserFlags;
      GetSupportedUserFlags(&supportedUserFlags);
      NotifyMessageFlagsFromHdr(dbHdr, aMsgKey, aFlags);
      nsCString keywords(aKeywords);
      HandleCustomFlags(aMsgKey, dbHdr, supportedUserFlags, keywords);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::NotifyMessageDeleted(const char * onlineFolderName, bool deleteAllMsgs, const char * msgIdString)
{
  if (deleteAllMsgs)
    return NS_OK;

  if (!msgIdString)
    return NS_OK;

  nsTArray<nsMsgKey> affectedMessages;
  ParseUidString(msgIdString, affectedMessages);

  if (!ShowDeletedMessages())
  {
    GetDatabase();
    NS_ENSURE_TRUE(mDatabase, NS_OK);
    if (!ShowDeletedMessages())
    {
      if (!affectedMessages.IsEmpty()) // perhaps Search deleted these messages
      {
        DeleteStoreMessages(affectedMessages);
        mDatabase->DeleteMessages(affectedMessages.Length(), affectedMessages.Elements(), nullptr);
      }
    }
    else // && !imapDeleteIsMoveToTrash // TODO: can this ever be executed?
      SetIMAPDeletedFlag(mDatabase, affectedMessages, false);
  }
  return NS_OK;
}

bool nsImapMailFolder::ShowDeletedMessages()
{
  nsresult rv;
  nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionList, &rv);
  NS_ENSURE_SUCCESS(rv, false);

  bool showDeleted = false;
  nsCString serverKey;
  GetServerKey(serverKey);
  hostSession->GetShowDeletedMessagesForHost(serverKey.get(), showDeleted);

  return showDeleted;
}

bool nsImapMailFolder::DeleteIsMoveToTrash()
{
  nsresult err;
  nsCOMPtr<nsIImapHostSessionList> hostSession = do_GetService(kCImapHostSessionList, &err);
  NS_ENSURE_SUCCESS(err, true);
  bool rv = true;

  nsCString serverKey;
  GetServerKey(serverKey);
  hostSession->GetDeleteIsMoveToTrashForHost(serverKey.get(), rv);
  return rv;
}

nsresult nsImapMailFolder::GetTrashFolder(nsIMsgFolder **pTrashFolder)
{
  NS_ENSURE_ARG_POINTER(pTrashFolder);
  nsCOMPtr<nsIMsgFolder> rootFolder;
  nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
  if(NS_SUCCEEDED(rv) && rootFolder)
  {
    rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder);
    if (!*pTrashFolder)
      rv = NS_ERROR_FAILURE;
  }
  return rv;
}


// store nsMsgMessageFlags::IMAPDeleted in the specified mailhdr records
void nsImapMailFolder::SetIMAPDeletedFlag(nsIMsgDatabase *mailDB, const nsTArray<nsMsgKey> &msgids, bool markDeleted)
{
  nsresult markStatus = NS_OK;
  uint32_t total = msgids.Length();

  for (uint32_t msgIndex=0; NS_SUCCEEDED(markStatus) && (msgIndex < total); msgIndex++)
    markStatus = mailDB->MarkImapDeleted(msgids[msgIndex], markDeleted, nullptr);
}

NS_IMETHODIMP
nsImapMailFolder::GetMessageSizeFromDB(const char * id, uint32_t *size)
{
  NS_ENSURE_ARG_POINTER(size);

  *size = 0;
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);
  if (id)
  {
    nsMsgKey key = msgKeyFromInt(ParseUint64Str(id));
    nsCOMPtr<nsIMsgDBHdr> mailHdr;
    rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(mailHdr));
    if (NS_SUCCEEDED(rv) && mailHdr)
      rv = mailHdr->GetMessageSize(size);
  }
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::SetContentModified(nsIImapUrl *aImapUrl, nsImapContentModifiedType modified)
{
  return aImapUrl->SetContentModified(modified);
}

NS_IMETHODIMP
nsImapMailFolder::GetCurMoveCopyMessageInfo(nsIImapUrl *runningUrl,
                                            PRTime *aDate,
                                            nsACString& aKeywords,
                                            uint32_t* aResult)
{
  nsCOMPtr <nsISupports> copyState;
  runningUrl->GetCopyState(getter_AddRefs(copyState));
  if (copyState)
  {
    nsCOMPtr<nsImapMailCopyState> mailCopyState = do_QueryInterface(copyState);
    uint32_t supportedFlags = 0;
    GetSupportedUserFlags(&supportedFlags);
    if (mailCopyState && mailCopyState->m_message)
    {
      nsMsgLabelValue label;
      mailCopyState->m_message->GetFlags(aResult);
      if (supportedFlags & (kImapMsgSupportUserFlag | kImapMsgLabelFlags))
      {
        mailCopyState->m_message->GetLabel(&label);
        if (label != 0)
          *aResult |= label << 25;
      }
      if (aDate)
        mailCopyState->m_message->GetDate(aDate);
      if (supportedFlags & kImapMsgSupportUserFlag)
      {
        // setup the custom imap keywords, which includes the message keywords
        // plus any junk status
        nsCString junkscore;
        mailCopyState->m_message->GetStringProperty("junkscore",
                                                    getter_Copies(junkscore));
        bool isJunk = false, isNotJunk = false;
        if (!junkscore.IsEmpty())
        {
          if (junkscore.EqualsLiteral("0"))
            isNotJunk = true;
          else
            isJunk = true;
        }

        nsCString keywords; // MsgFindKeyword can't use nsACString
        mailCopyState->m_message->GetStringProperty("keywords",
                                                    getter_Copies(keywords));
        int32_t start;
        int32_t length;
        bool hasJunk = MsgFindKeyword(NS_LITERAL_CSTRING("junk"),
                                        keywords, &start, &length);
        if (hasJunk && !isJunk)
          keywords.Cut(start, length);
        else if (!hasJunk && isJunk)
          keywords.AppendLiteral(" Junk");
        bool hasNonJunk = MsgFindKeyword(NS_LITERAL_CSTRING("nonjunk"),
                                           keywords, &start, &length);
        if (!hasNonJunk)
          hasNonJunk = MsgFindKeyword(NS_LITERAL_CSTRING("notjunk"),
                                      keywords, &start, &length);
        if (hasNonJunk && !isNotJunk)
          keywords.Cut(start, length);
        else if (!hasNonJunk && isNotJunk)
          keywords.AppendLiteral(" NonJunk");

        // Cleanup extra spaces
        while (!keywords.IsEmpty() && keywords.First() == ' ')
          keywords.Cut(0, 1);
        while (!keywords.IsEmpty() && keywords.Last() == ' ')
          keywords.Cut(keywords.Length() - 1, 1);
        while (!keywords.IsEmpty() &&
               (start = keywords.Find(NS_LITERAL_CSTRING("  "))) >= 0)
          keywords.Cut(start, 1);
        aKeywords.Assign(keywords);
      }
    }
    // if we don't have a source header, and it's not the drafts folder,
    // then mark the message read, since it must be an append to the
    // fcc or templates folder.
    else if (mailCopyState)
    {
      *aResult = mailCopyState->m_newMsgFlags;
      if (supportedFlags & kImapMsgSupportUserFlag)
        aKeywords.Assign(mailCopyState->m_newMsgKeywords);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::OnStartRunningUrl(nsIURI *aUrl)
{
  NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url");
  nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
  if (mailUrl)
  {
    bool updatingFolder;
    mailUrl->GetUpdatingFolder(&updatingFolder);
    m_updatingFolder = updatingFolder;
  }
  m_urlRunning = true;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
{
  nsresult rv;
  bool endedOfflineDownload = false;
  nsImapAction imapAction = nsIImapUrl::nsImapTest;
  m_urlRunning = false;
  m_updatingFolder = false;
  nsCOMPtr<nsIMsgMailSession> session =
    do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  if (aUrl)
  {
  nsCOMPtr <nsIImapUrl> imapUrl = do_QueryInterface(aUrl, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  bool downloadingForOfflineUse;
  imapUrl->GetStoreResultsOffline(&downloadingForOfflineUse);
    bool hasSemaphore = false;
    // if we have the folder locked, clear it.
    TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore);
    if (hasSemaphore)
      ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
  if (downloadingForOfflineUse)
  {
    endedOfflineDownload = true;
    EndOfflineDownload();
  }
    nsCOMPtr<nsIMsgWindow> msgWindow;
    nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
    bool folderOpen = false;
    if (mailUrl)
      mailUrl->GetMsgWindow(getter_AddRefs(msgWindow));
    if (session)
      session->IsFolderOpenInWindow(this, &folderOpen);
#ifdef DEBUG_bienvenu
    printf("stop running url %s\n", aUrl->GetSpecOrDefault().get());
#endif

   if (imapUrl)
   {
      DisplayStatusMsg(imapUrl, EmptyString());
      imapUrl->GetImapAction(&imapAction);
      if (imapAction == nsIImapUrl::nsImapMsgFetch || imapAction == nsIImapUrl::nsImapMsgDownloadForOffline)
      {
        ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
        if (!endedOfflineDownload)
          EndOfflineDownload();
      }

      // Notify move, copy or delete (online operations)
      // Not sure whether nsImapDeleteMsg is even used, deletes in all three models use nsImapAddMsgFlags.
      nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
      if (notifier && m_copyState)
      {
        if (imapAction == nsIImapUrl::nsImapOnlineMove)
          notifier->NotifyMsgsMoveCopyCompleted(true, m_copyState->m_messages, this, nullptr);
        else if (imapAction == nsIImapUrl::nsImapOnlineCopy)
          notifier->NotifyMsgsMoveCopyCompleted(false, m_copyState->m_messages, this, nullptr);
        else if (imapAction == nsIImapUrl::nsImapDeleteMsg)
          notifier->NotifyMsgsDeleted(m_copyState->m_messages);
      }

      switch(imapAction)
      {
      case nsIImapUrl::nsImapDeleteMsg:
      case nsIImapUrl::nsImapOnlineMove:
      case nsIImapUrl::nsImapOnlineCopy:
        if (NS_SUCCEEDED(aExitCode))
        {
          if (folderOpen)
            UpdateFolder(msgWindow);
          else
            UpdatePendingCounts();
        }

        if (m_copyState)
        {
          nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv);
          if (m_copyState->m_isMove && !m_copyState->m_isCrossServerOp)
          {
            if (NS_SUCCEEDED(aExitCode))
            {
              nsCOMPtr<nsIMsgDatabase> srcDB;
              if (srcFolder)
                  rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
              if (NS_SUCCEEDED(rv) && srcDB)
              {
                RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
                nsTArray<nsMsgKey> srcKeyArray;
                if (m_copyState->m_allowUndo)
                {
                  msgTxn = m_copyState->m_undoMsgTxn;
                  if (msgTxn)
                    msgTxn->GetSrcKeyArray(srcKeyArray);
                }
                else
                {
                  nsAutoCString messageIds;
                  rv = BuildIdsAndKeyArray(m_copyState->m_messages, messageIds, srcKeyArray);
                  NS_ENSURE_SUCCESS(rv,rv);
                }

                if (!ShowDeletedMessages())
                {
                  // We only reach here for same-server operations
                  // (!m_copyState->m_isCrossServerOp in if above), so we can
                  // assume that the src is also imap that uses offline storage.
                  DeleteStoreMessages(srcKeyArray, srcFolder);
                  srcDB->DeleteMessages(srcKeyArray.Length(), srcKeyArray.Elements(), nullptr);
                }
                else
                  MarkMessagesImapDeleted(&srcKeyArray, true, srcDB);
              }
              srcFolder->EnableNotifications(allMessageCountNotifications, true);
              // even if we're showing deleted messages,
              // we still need to notify FE so it will show the imap deleted flag
              srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
              // is there a way to see that we think we have new msgs?
              nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
              if (NS_SUCCEEDED(rv))
              {
                bool showPreviewText;
                prefBranch->GetBoolPref("mail.biff.alert.show_preview", &showPreviewText);
                // if we're showing preview text, update ourselves if we got a new unread
                // message copied so that we can download the new headers and have a chance
                // to preview the msg bodies.
                if (!folderOpen && showPreviewText && m_copyState->m_unreadCount > 0
                    && ! (mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
                  UpdateFolder(msgWindow);
              }
            }
            else
            {
              srcFolder->EnableNotifications(allMessageCountNotifications, true);
              srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
            }

          }
          if (m_copyState->m_msgWindow &&
              m_copyState->m_undoMsgTxn &&  // may be null from filters
              NS_SUCCEEDED(aExitCode))      //we should do this only if move/copy succeeds
          {
            nsCOMPtr<nsITransactionManager> txnMgr;
            m_copyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
            if (txnMgr)
            {
              mozilla::DebugOnly<nsresult> rv2 = txnMgr->DoTransaction(m_copyState->m_undoMsgTxn);
              NS_ASSERTION(NS_SUCCEEDED(rv2), "doing transaction failed");
            }
          }
          (void) OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
        }

        // we're the dest folder of a move/copy - if we're not open in the ui,
        // then we should clear our nsMsgDatabase pointer. Otherwise, the db would
        // be open until the user selected it and then selected another folder.
        // but don't do this for the trash or inbox - we'll leave them open
        if (!folderOpen && ! (mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
          SetMsgDatabase(nullptr);
        break;
      case nsIImapUrl::nsImapSubtractMsgFlags:
        {
        // this isn't really right - we'd like to know we were
        // deleting a message to start with, but it probably
        // won't do any harm.
          imapMessageFlagsType flags = 0;
          imapUrl->GetMsgFlags(&flags);
          //we need to subtract the delete flag in db only in case when we show deleted msgs
          if (flags & kImapMsgDeletedFlag && ShowDeletedMessages())
          {
            nsCOMPtr<nsIMsgDatabase> db;
            rv = GetMsgDatabase(getter_AddRefs(db));
            if (NS_SUCCEEDED(rv) && db)
            {
              nsTArray<nsMsgKey> keyArray;
              nsCString keyString;
              imapUrl->GetListOfMessageIds(keyString);
              ParseUidString(keyString.get(), keyArray);
              MarkMessagesImapDeleted(&keyArray, false, db);
              db->Commit(nsMsgDBCommitType::kLargeCommit);
            }
          }
        }
        break;
      case nsIImapUrl::nsImapAddMsgFlags:
        {
          imapMessageFlagsType flags = 0;
          imapUrl->GetMsgFlags(&flags);
          if (flags & kImapMsgDeletedFlag)
          {
            // we need to delete headers from db only when we don't show deleted msgs
            if (!ShowDeletedMessages())
            {
              nsCOMPtr<nsIMsgDatabase> db;
              rv = GetMsgDatabase(getter_AddRefs(db));
              if (NS_SUCCEEDED(rv) && db)
              {
                nsTArray<nsMsgKey> keyArray;
                nsCString keyString;
                imapUrl->GetListOfMessageIds(keyString);
                ParseUidString(keyString.get(), keyArray);

                // For pluggable stores that do not support compaction, we need
                // to delete the messages now.
                bool supportsCompaction = false;
                uint32_t numHdrs = 0;
                nsCOMPtr<nsIMsgPluggableStore> offlineStore;
                (void) GetMsgStore(getter_AddRefs(offlineStore));
                if (offlineStore)
                  offlineStore->GetSupportsCompaction(&supportsCompaction);

                nsCOMPtr<nsIMutableArray> msgHdrs;
                if (notifier || !supportsCompaction)
                {
                  msgHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID);
                  NS_ENSURE_STATE(msgHdrs);
                  MsgGetHeadersFromKeys(db, keyArray, msgHdrs);
                  msgHdrs->GetLength(&numHdrs);
                }

                // Notify listeners of delete.
                if (notifier && numHdrs)
                {
                  // XXX Currently, the DeleteMessages below gets executed twice on deletes.
                  // Once in DeleteMessages, once here. The second time, it silently fails
                  // to delete. This is why we're also checking whether the array is empty.
                  notifier->NotifyMsgsDeleted(msgHdrs);
                }

                if (!supportsCompaction && numHdrs)
                  DeleteStoreMessages(msgHdrs);

                db->DeleteMessages(keyArray.Length(), keyArray.Elements(), nullptr);
                db->SetSummaryValid(true);
                db->Commit(nsMsgDBCommitType::kLargeCommit);
              }
            }
          }
        }
        break;
      case nsIImapUrl::nsImapAppendMsgFromFile:
      case nsIImapUrl::nsImapAppendDraftFromFile:
          if (m_copyState)
          {
            if (NS_SUCCEEDED(aExitCode))
            {
              UpdatePendingCounts();

              m_copyState->m_curIndex++;
              if (m_copyState->m_curIndex >= m_copyState->m_totalCount)
              {
                nsCOMPtr<nsIUrlListener> saveUrlListener = m_urlListener;
                if (folderOpen)
                {
                  // This gives a way for the caller to get notified
                  // when the UpdateFolder url is done.
                  if (m_copyState->m_listener)
                    m_urlListener = do_QueryInterface(m_copyState->m_listener);
                }
                if (m_copyState->m_msgWindow && m_copyState->m_undoMsgTxn)
                {
                  nsCOMPtr<nsITransactionManager> txnMgr;
                  m_copyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
                  if (txnMgr)
                    txnMgr->DoTransaction(m_copyState->m_undoMsgTxn);
                }
                (void) OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
                if (folderOpen ||
                    imapAction == nsIImapUrl::nsImapAppendDraftFromFile)
                {
                  UpdateFolderWithListener(msgWindow, m_urlListener);
                  m_urlListener = saveUrlListener;
                }
              }
            }
            else
              //clear the copyState if copy has failed
              (void) OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
          }
          break;
      case nsIImapUrl::nsImapMoveFolderHierarchy:
        if (m_copyState) // delete folder gets here, but w/o an m_copyState
        {
          nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
          NS_ENSURE_SUCCESS(rv, rv);
          nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(m_copyState->m_srcSupport);
          if (srcFolder)
          {
            copyService->NotifyCompletion(m_copyState->m_srcSupport, this, aExitCode);
          }
          m_copyState = nullptr;
        }
        break;
      case nsIImapUrl::nsImapRenameFolder:
        if (NS_FAILED(aExitCode))
        {
          NotifyFolderEvent(kRenameCompleted);
        }
        break;
      case nsIImapUrl::nsImapDeleteAllMsgs:
          if (NS_SUCCEEDED(aExitCode))
          {
            if (folderOpen)
              UpdateFolder(msgWindow);
            else
            {
              ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
              ChangeNumPendingUnread(-mNumPendingUnreadMessages);
              m_numServerUnseenMessages = 0;
            }

          }
          break;
      case nsIImapUrl::nsImapListFolder:
          if (NS_SUCCEEDED(aExitCode))
          {
            // listing folder will open db; don't leave the db open.
            SetMsgDatabase(nullptr);
            if (!m_verifiedAsOnlineFolder)
            {
              // If folder is not verified, we remove it.
              nsCOMPtr<nsIMsgFolder> parent;
              rv = GetParent(getter_AddRefs(parent));
              if (NS_SUCCEEDED(rv) && parent)
              {
                nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
                if (imapParent)
                  imapParent->RemoveSubFolder(this);
              }
            }
          }
        break;
      case nsIImapUrl::nsImapRefreshFolderUrls:
        // we finished getting an admin url for the folder.
          if (!m_adminUrl.IsEmpty())
            FolderPrivileges(msgWindow);
          break;
      case nsIImapUrl::nsImapCreateFolder:
        if (NS_FAILED(aExitCode))  //if success notification already done
        {
          NotifyFolderEvent(kFolderCreateFailed);
        }
        break;
      case nsIImapUrl::nsImapSubscribe:
        if (NS_SUCCEEDED(aExitCode) && msgWindow)
        {
          nsCString canonicalFolderName;
          imapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(canonicalFolderName));
          nsCOMPtr <nsIMsgFolder> rootFolder;
          nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
          if(NS_SUCCEEDED(rv) && rootFolder)
          {
            nsCOMPtr <nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
            if (imapRoot)
            {
              nsCOMPtr <nsIMsgImapMailFolder> foundFolder;
              rv = imapRoot->FindOnlineSubFolder(canonicalFolderName, getter_AddRefs(foundFolder));
              if (NS_SUCCEEDED(rv) && foundFolder)
              {
                nsCString uri;
                nsCOMPtr <nsIMsgFolder> msgFolder = do_QueryInterface(foundFolder);
                if (msgFolder)
                {
                  msgFolder->GetURI(uri);
                  nsCOMPtr<nsIMsgWindowCommands> windowCommands;
                  msgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
                  if (windowCommands)
                    windowCommands->SelectFolder(uri);
                }
              }
            }
          }
        }
        break;
      case nsIImapUrl::nsImapExpungeFolder:
        m_expunging = false;
        break;
      default:
          break;
      }
    }
    // give base class a chance to send folder loaded notification...
    rv = nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
  }
  // if we're not running a url, we must not be getting new mail.
  SetGettingNewMessages(false);
  // don't send OnStopRunning notification if still compacting offline store.
  if (m_urlListener && (imapAction != nsIImapUrl::nsImapExpungeFolder ||
                        !m_compactingOfflineStore))
  {
    nsCOMPtr<nsIUrlListener> saveListener = m_urlListener;
    m_urlListener = nullptr;
    saveListener->OnStopRunningUrl(aUrl, aExitCode);
  }
  return rv;
}

void nsImapMailFolder::UpdatePendingCounts()
{
  if (m_copyState)
  {
    ChangePendingTotal(m_copyState->m_isCrossServerOp ? 1 : m_copyState->m_totalCount);

    // count the moves that were unread
    int numUnread = m_copyState->m_unreadCount;
    if (numUnread)
    {
      m_numServerUnseenMessages += numUnread; // adjust last status count by this delta.
      ChangeNumPendingUnread(numUnread);
    }
    SummaryChanged();
  }
}

NS_IMETHODIMP
nsImapMailFolder::ClearFolderRights()
{
  SetFolderNeedsACLListed(false);
  delete m_folderACL;
  m_folderACL = new nsMsgIMAPFolderACL(this);
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::AddFolderRights(const nsACString& userName, const nsACString& rights)
{
  SetFolderNeedsACLListed(false);
  GetFolderACL()->SetFolderRightsForUser(userName, rights);
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::RefreshFolderRights()
{
  if (GetFolderACL()->GetIsFolderShared())
    SetFlag(nsMsgFolderFlags::PersonalShared);
  else
    ClearFlag(nsMsgFolderFlags::PersonalShared);
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::SetCopyResponseUid(const char* msgIdString,
                                     nsIImapUrl * aUrl)
{   // CopyMessages() only
  nsresult rv = NS_OK;
  RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
  nsCOMPtr<nsISupports> copyState;

  if (aUrl)
    aUrl->GetCopyState(getter_AddRefs(copyState));

  if (copyState)
  {
    nsCOMPtr<nsImapMailCopyState> mailCopyState =
        do_QueryInterface(copyState, &rv);
    if (NS_FAILED(rv)) return rv;
    if (mailCopyState->m_undoMsgTxn)
      msgTxn = mailCopyState->m_undoMsgTxn;
  }
  else if (aUrl && m_pendingOfflineMoves.Length())
  {
    nsCString urlSourceMsgIds, undoTxnSourceMsgIds;
    aUrl->GetListOfMessageIds(urlSourceMsgIds);
    RefPtr<nsImapMoveCopyMsgTxn> imapUndo = m_pendingOfflineMoves[0];
    if (imapUndo)
    {
      imapUndo->GetSrcMsgIds(undoTxnSourceMsgIds);
      if (undoTxnSourceMsgIds.Equals(urlSourceMsgIds))
        msgTxn = imapUndo;
      // ### we should handle batched moves, but lets keep it simple for a2.
      m_pendingOfflineMoves.Clear();
    }
  }
  if (msgTxn)
    msgTxn->SetCopyResponseUid(msgIdString);
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::StartMessage(nsIMsgMailNewsUrl * aUrl)
{
  nsCOMPtr<nsIImapUrl> imapUrl (do_QueryInterface(aUrl));
  nsCOMPtr<nsISupports> copyState;
  NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);

  imapUrl->GetCopyState(getter_AddRefs(copyState));
  if (copyState)
  {
    nsCOMPtr <nsICopyMessageStreamListener> listener = do_QueryInterface(copyState);
    if (listener)
      listener->StartMessage();
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::EndMessage(nsIMsgMailNewsUrl * aUrl, nsMsgKey uidOfMessage)
{
  nsCOMPtr<nsIImapUrl> imapUrl (do_QueryInterface(aUrl));
  nsCOMPtr<nsISupports> copyState;
  NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
  imapUrl->GetCopyState(getter_AddRefs(copyState));
  if (copyState)
  {
    nsCOMPtr <nsICopyMessageStreamListener> listener = do_QueryInterface(copyState);
    if (listener)
      listener->EndMessage(uidOfMessage);
  }
  return NS_OK;
}

#define WHITESPACE " \015\012"     // token delimiter

NS_IMETHODIMP
nsImapMailFolder::NotifySearchHit(nsIMsgMailNewsUrl * aUrl,
                                  const char* searchHitLine)
{
  NS_ENSURE_ARG_POINTER(aUrl);
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);

  // expect search results in the form of "* SEARCH <hit> <hit> ..."
  // expect search results in the form of "* SEARCH <hit> <hit> ..."
  nsCString tokenString(searchHitLine);
  char *currentPosition = PL_strcasestr(tokenString.get(), "SEARCH");
  if (currentPosition)
  {
    currentPosition += strlen("SEARCH");
    bool shownUpdateAlert = false;
    char *hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
    while (hitUidToken)
    {
      long naturalLong; // %l is 64 bits on OSF1
      sscanf(hitUidToken, "%ld", &naturalLong);
      nsMsgKey hitUid = (nsMsgKey) naturalLong;

      nsCOMPtr <nsIMsgDBHdr> hitHeader;