/* -*- 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 "netCore.h"
#include "nsNetUtil.h"
#include "nsImapOfflineSync.h"
#include "nsImapMailFolder.h"
#include "nsMsgFolderFlags.h"
#include "nsMsgMessageFlags.h"
#include "nsIMsgMailNewsUrl.h"
#include "nsIMsgAccountManager.h"
#include "nsINntpIncomingServer.h"
#include "nsDirectoryServiceDefs.h"
#include "nsISeekableStream.h"
#include "nsIMsgCopyService.h"
#include "nsImapProtocol.h"
#include "nsMsgUtils.h"
#include "nsIAutoSyncManager.h"
#include "mozilla/Unused.h"

NS_IMPL_ISUPPORTS(nsImapOfflineSync, nsIUrlListener, nsIMsgCopyServiceListener,
                  nsIDBChangeListener, nsIImapOfflineSync)

nsImapOfflineSync::nsImapOfflineSync() {
  m_singleFolderToUpdate = nullptr;
  m_window = nullptr;
  mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kFlagsChanged;
  m_mailboxupdatesStarted = false;
  m_mailboxupdatesFinished = false;
  m_createdOfflineFolders = false;
  m_pseudoOffline = false;
  m_KeyIndex = 0;
  mCurrentUIDValidity = nsMsgKey_None;
  m_listener = nullptr;
}

NS_IMETHODIMP
nsImapOfflineSync::Init(nsIMsgWindow* window, nsIUrlListener* listener,
                        nsIMsgFolder* singleFolderOnly, bool isPseudoOffline) {
  m_window = window;
  m_listener = listener;
  m_singleFolderToUpdate = singleFolderOnly;
  m_pseudoOffline = isPseudoOffline;

  // not the perfect place for this, but I think it will work.
  if (m_window) m_window->SetStopped(false);

  return NS_OK;
}

nsImapOfflineSync::~nsImapOfflineSync() {}

void nsImapOfflineSync::SetWindow(nsIMsgWindow* window) { m_window = window; }

NS_IMETHODIMP nsImapOfflineSync::OnStartRunningUrl(nsIURI* url) {
  return NS_OK;
}

NS_IMETHODIMP
nsImapOfflineSync::OnStopRunningUrl(nsIURI* url, nsresult exitCode) {
  nsresult rv = exitCode;

  // where do we make sure this gets cleared when we start running urls?
  bool stopped = false;
  if (m_window) m_window->GetStopped(&stopped);

  if (m_curTempFile) {
    m_curTempFile->Remove(false);
    m_curTempFile = nullptr;
  }
  // NS_BINDING_ABORTED is used for the user pressing stop, which
  // should cause us to abort the offline process. Other errors
  // should allow us to continue.
  if (stopped) {
    if (m_listener) m_listener->OnStopRunningUrl(url, NS_BINDING_ABORTED);
    return NS_OK;
  }
  nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);

  if (imapUrl)
    nsImapProtocol::LogImapUrl(NS_SUCCEEDED(rv) ? "offline imap url succeeded "
                                                : "offline imap url failed ",
                               imapUrl);

  // If we succeeded, or it was an imap move/copy that timed out, clear the
  // operation.
  bool moveCopy =
      mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgCopy ||
      mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgMoved;
  if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_ERROR_IMAP_COMMAND_FAILED ||
      (moveCopy && exitCode == NS_ERROR_NET_TIMEOUT)) {
    ClearCurrentOps();
    rv = ProcessNextOperation();
  }
  // else if it's a non-stop error, and we're doing multiple folders,
  // go to the next folder.
  else if (!m_singleFolderToUpdate) {
    if (AdvanceToNextFolder())
      rv = ProcessNextOperation();
    else if (m_listener)
      m_listener->OnStopRunningUrl(url, rv);
  }

  return rv;
}

/**
 * Leaves m_currentServer at the next imap or local mail "server" that
 * might have offline events to playback, and m_folderQueue holding
 * a (reversed) list of all the folders to consider for that server.
 * If no more servers, m_currentServer will be left at nullptr and the
 * function returns false.
 */
bool nsImapOfflineSync::AdvanceToNextServer() {
  nsresult rv = NS_OK;

  if (m_allServers.IsEmpty()) {
    NS_ASSERTION(!m_currentServer, "this shouldn't be set");
    m_currentServer = nullptr;
    nsCOMPtr<nsIMsgAccountManager> accountManager =
        do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
    NS_ASSERTION(accountManager && NS_SUCCEEDED(rv),
                 "couldn't get account mgr");
    if (!accountManager || NS_FAILED(rv)) return false;

    rv = accountManager->GetAllServers(m_allServers);
    NS_ENSURE_SUCCESS(rv, false);
  }
  size_t serverIndex = 0;
  if (m_currentServer) {
    serverIndex = m_allServers.IndexOf(m_currentServer);
    if (serverIndex == m_allServers.NoIndex) {
      serverIndex = 0;
    } else {
      // Move to the next server
      ++serverIndex;
    }
  }
  m_currentServer = nullptr;
  nsCOMPtr<nsIMsgFolder> rootFolder;

  while (serverIndex < m_allServers.Length()) {
    nsCOMPtr<nsIMsgIncomingServer> server(m_allServers[serverIndex]);
    serverIndex++;

    nsCOMPtr<nsINntpIncomingServer> newsServer = do_QueryInterface(server);
    if (newsServer)  // news servers aren't involved in offline imap
      continue;

    if (server) {
      m_currentServer = server;
      server->GetRootFolder(getter_AddRefs(rootFolder));
      if (rootFolder) {
        rv = rootFolder->GetDescendants(m_folderQueue);
        if (NS_SUCCEEDED(rv)) {
          if (!m_folderQueue.IsEmpty()) {
            // We'll be popping folders off the end as they are processed.
            m_folderQueue.Reverse();
            return true;
          }
        }
      }
    }
  }
  return false;
}

/**
 * Sets m_currentFolder to the next folder to process.
 *
 * @return  True if next folder to process was found, otherwise false.
 */
bool nsImapOfflineSync::AdvanceToNextFolder() {
  // we always start by changing flags
  mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kFlagsChanged;

  if (m_currentFolder) {
    m_currentFolder->SetMsgDatabase(nullptr);
    m_currentFolder = nullptr;
  }

  bool hasMore = false;
  if (m_currentServer) {
    hasMore = !m_folderQueue.IsEmpty();
  }
  if (!hasMore) {
    hasMore = AdvanceToNextServer();
  }
  if (hasMore) {
    m_currentFolder = m_folderQueue.PopLastElement();
  }
  ClearDB();
  return m_currentFolder;
}

void nsImapOfflineSync::AdvanceToFirstIMAPFolder() {
  m_currentServer = nullptr;
  nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
  while (!imapFolder && AdvanceToNextFolder()) {
    imapFolder = do_QueryInterface(m_currentFolder);
  }
}

void nsImapOfflineSync::ProcessFlagOperation(nsIMsgOfflineImapOperation* op) {
  nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = op;
  nsTArray<nsMsgKey> matchingFlagKeys;
  uint32_t currentKeyIndex = m_KeyIndex;

  imapMessageFlagsType matchingFlags;
  currentOp->GetNewFlags(&matchingFlags);
  bool flagsMatch = true;
  do {  // loop for all messages with the same flags
    if (flagsMatch) {
      nsMsgKey curKey;
      currentOp->GetMessageKey(&curKey);
      matchingFlagKeys.AppendElement(curKey);
      currentOp->SetPlayingBack(true);
      m_currentOpsToClear.AppendObject(currentOp);
    }
    currentOp = nullptr;
    imapMessageFlagsType newFlags = kNoImapMsgFlag;
    imapMessageFlagsType flagOperation = kNoImapMsgFlag;
    if (++currentKeyIndex < m_CurrentKeys.Length())
      m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex], false,
                                      getter_AddRefs(currentOp));
    if (currentOp) {
      currentOp->GetFlagOperation(&flagOperation);
      currentOp->GetNewFlags(&newFlags);
    }
    flagsMatch = (flagOperation & nsIMsgOfflineImapOperation::kFlagsChanged) &&
                 (newFlags == matchingFlags);
  } while (currentOp);

  if (!matchingFlagKeys.IsEmpty()) {
    nsAutoCString uids;
    nsImapMailFolder::AllocateUidStringFromKeys(matchingFlagKeys, uids);
    uint32_t curFolderFlags;
    m_currentFolder->GetFlags(&curFolderFlags);

    if (uids.get() && (curFolderFlags & nsMsgFolderFlags::ImapBox)) {
      nsresult rv = NS_OK;
      nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
          do_QueryInterface(m_currentFolder);
      nsCOMPtr<nsIURI> uriToSetFlags;
      if (imapFolder) {
        rv = imapFolder->SetImapFlags(uids.get(), matchingFlags,
                                      getter_AddRefs(uriToSetFlags));
        if (NS_SUCCEEDED(rv) && uriToSetFlags) {
          nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
              do_QueryInterface(uriToSetFlags);
          if (mailnewsUrl) mailnewsUrl->RegisterListener(this);
        }
      }
    }
  } else
    ProcessNextOperation();
}

void nsImapOfflineSync::ProcessKeywordOperation(
    nsIMsgOfflineImapOperation* op) {
  nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = op;
  nsTArray<nsMsgKey> matchingKeywordKeys;
  uint32_t currentKeyIndex = m_KeyIndex;

  nsAutoCString keywords;
  if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
    currentOp->GetKeywordsToAdd(getter_Copies(keywords));
  else
    currentOp->GetKeywordsToRemove(getter_Copies(keywords));
  bool keywordsMatch = true;
  do {  // loop for all messages with the same keywords
    if (keywordsMatch) {
      nsMsgKey curKey;
      currentOp->GetMessageKey(&curKey);
      matchingKeywordKeys.AppendElement(curKey);
      currentOp->SetPlayingBack(true);
      m_currentOpsToClear.AppendObject(currentOp);
    }
    currentOp = nullptr;
    if (++currentKeyIndex < m_CurrentKeys.Length())
      m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex], false,
                                      getter_AddRefs(currentOp));
    if (currentOp) {
      nsAutoCString curOpKeywords;
      nsOfflineImapOperationType operation;
      currentOp->GetOperation(&operation);
      if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
        currentOp->GetKeywordsToAdd(getter_Copies(curOpKeywords));
      else
        currentOp->GetKeywordsToRemove(getter_Copies(curOpKeywords));
      keywordsMatch = (operation & mCurrentPlaybackOpType) &&
                      (curOpKeywords.Equals(keywords));
    }
  } while (currentOp);

  if (!matchingKeywordKeys.IsEmpty()) {
    uint32_t curFolderFlags;
    m_currentFolder->GetFlags(&curFolderFlags);

    if (curFolderFlags & nsMsgFolderFlags::ImapBox) {
      nsresult rv = NS_OK;
      nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
          do_QueryInterface(m_currentFolder);
      nsCOMPtr<nsIURI> uriToStoreCustomKeywords;
      if (imapFolder) {
        rv = imapFolder->StoreCustomKeywords(
            m_window,
            (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
                ? keywords
                : EmptyCString(),
            (mCurrentPlaybackOpType ==
             nsIMsgOfflineImapOperation::kRemoveKeywords)
                ? keywords
                : EmptyCString(),
            matchingKeywordKeys, getter_AddRefs(uriToStoreCustomKeywords));
        if (NS_SUCCEEDED(rv) && uriToStoreCustomKeywords) {
          nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
              do_QueryInterface(uriToStoreCustomKeywords);
          if (mailnewsUrl) mailnewsUrl->RegisterListener(this);
        }
      }
    }
  } else
    ProcessNextOperation();
}

// XXX This should not be void but return an error to indicate which low
// level routine failed.
void nsImapOfflineSync::ProcessAppendMsgOperation(
    nsIMsgOfflineImapOperation* currentOp, int32_t opType) {
  nsMsgKey msgKey;
  currentOp->GetMessageKey(&msgKey);
  nsCOMPtr<nsIMsgDBHdr> mailHdr;
  nsresult rv = m_currentDB->GetMsgHdrForKey(msgKey, getter_AddRefs(mailHdr));
  if (NS_FAILED(rv) || !mailHdr) {
    m_currentDB->RemoveOfflineOp(currentOp);
    ProcessNextOperation();
    return;
  }

  uint64_t messageOffset;
  uint32_t messageSize;
  mailHdr->GetMessageOffset(&messageOffset);
  mailHdr->GetOfflineMessageSize(&messageSize);
  nsCOMPtr<nsIFile> tmpFile;

  if (NS_WARN_IF(NS_FAILED(GetSpecialDirectoryWithFileName(
          NS_OS_TEMP_DIR, "nscpmsg.txt", getter_AddRefs(tmpFile)))))
    return;

  if (NS_WARN_IF(
          NS_FAILED(tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600))))
    return;

  nsCOMPtr<nsIOutputStream> outputStream;
  rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), tmpFile,
                                      PR_WRONLY | PR_CREATE_FILE, 00600);
  if (NS_WARN_IF(NS_FAILED(rv) || !outputStream)) return;

  // We break out of the loop to get to the clean-up code.
  bool setPlayingBack = false;
  do {
    nsCString moveDestination;
    currentOp->GetDestinationFolderURI(moveDestination);

    nsCOMPtr<nsIMsgFolder> destFolder;
    rv = GetOrCreateFolder(moveDestination, getter_AddRefs(destFolder));
    if (NS_WARN_IF(NS_FAILED(rv))) break;

    nsCOMPtr<nsIInputStream> offlineStoreInputStream;
    rv = destFolder->GetMsgInputStream(mailHdr,
                                       getter_AddRefs(offlineStoreInputStream));
    if (NS_WARN_IF((NS_FAILED(rv) || !offlineStoreInputStream))) break;

    nsCOMPtr<nsISeekableStream> seekStream =
        do_QueryInterface(offlineStoreInputStream);
    MOZ_ASSERT(seekStream, "non seekable stream - can't read from offline msg");
    if (!seekStream) break;

    // From this point onwards, we need to set "playing back".
    setPlayingBack = true;

    rv = seekStream->Seek(PR_SEEK_SET, messageOffset);
    if (NS_WARN_IF(NS_FAILED(rv))) break;

    // Copy the dest folder offline store msg to the temp file.
    int32_t inputBufferSize = FILE_IO_BUFFER_SIZE;
    char* inputBuffer = (char*)PR_Malloc(inputBufferSize);
    int32_t bytesLeft;
    uint32_t bytesRead, bytesWritten;

    bytesLeft = messageSize;
    rv = inputBuffer ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
    while (bytesLeft > 0 && NS_SUCCEEDED(rv)) {
      int32_t bytesToRead = std::min(inputBufferSize, bytesLeft);
      rv = offlineStoreInputStream->Read(inputBuffer, bytesToRead, &bytesRead);
      if (NS_WARN_IF(NS_FAILED(rv)) || bytesRead == 0) break;
      rv = outputStream->Write(inputBuffer, bytesRead, &bytesWritten);
      if (NS_WARN_IF(NS_FAILED(rv))) break;
      MOZ_ASSERT(bytesWritten == bytesRead,
                 "wrote out incorrect number of bytes");
      bytesLeft -= bytesRead;
    }
    PR_FREEIF(inputBuffer);

    // rv could have an error from Read/Write.
    nsresult rv2 = outputStream->Close();
    if (NS_FAILED(rv2)) {
      NS_WARNING("ouputStream->Close() failed");
    }
    outputStream = nullptr;  // Don't try to close it again below.

    // rv: Read/Write, rv2: Close
    if (NS_FAILED(rv) || NS_FAILED(rv2)) {
      // This Remove() will fail under Windows if the output stream
      // fails to close above.
      mozilla::Unused << NS_WARN_IF(NS_FAILED(tmpFile->Remove(false)));
      break;
    }

    nsCOMPtr<nsIFile> cloneTmpFile;
    // clone the tmp file to defeat nsIFile's stat/size caching.
    tmpFile->Clone(getter_AddRefs(cloneTmpFile));
    m_curTempFile = cloneTmpFile;
    nsCOMPtr<nsIMsgCopyService> copyService =
        do_GetService("@mozilla.org/messenger/messagecopyservice;1");

    // CopyFileMessage returns error async to this->OnStopCopy
    // if copyService is null, let's crash here and now.
    rv = copyService->CopyFileMessage(cloneTmpFile, destFolder,
                                      nullptr,  // nsIMsgDBHdr* msgToReplace
                                      true,     // isDraftOrTemplate
                                      0,        // new msg flags
                                      EmptyCString(), this, m_window);
    MOZ_ASSERT(NS_SUCCEEDED(rv),
               "CopyFileMessage() failed. Fatal. Error in call setup?");
  } while (false);

  if (setPlayingBack) {
    currentOp->SetPlayingBack(true);
    m_currentOpsToClear.AppendObject(currentOp);
    m_currentDB->DeleteHeader(mailHdr, nullptr, true, true);
  }

  // Close the output stream if it's not already closed.
  if (outputStream)
    mozilla::Unused << NS_WARN_IF(NS_FAILED(outputStream->Close()));
}

void nsImapOfflineSync::ClearCurrentOps() {
  int32_t opCount = m_currentOpsToClear.Count();
  for (int32_t i = opCount - 1; i >= 0; i--) {
    m_currentOpsToClear[i]->SetPlayingBack(false);
    m_currentOpsToClear[i]->ClearOperation(mCurrentPlaybackOpType);
    m_currentOpsToClear.RemoveObjectAt(i);
  }
}

void nsImapOfflineSync::ProcessMoveOperation(nsIMsgOfflineImapOperation* op) {
  nsTArray<nsMsgKey> matchingFlagKeys;
  uint32_t currentKeyIndex = m_KeyIndex;
  nsCString moveDestination;
  op->GetDestinationFolderURI(moveDestination);
  bool moveMatches = true;
  nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = op;
  do {  // loop for all messages with the same destination
    if (moveMatches) {
      nsMsgKey curKey;
      currentOp->GetMessageKey(&curKey);
      matchingFlagKeys.AppendElement(curKey);
      currentOp->SetPlayingBack(true);
      m_currentOpsToClear.AppendObject(currentOp);
    }
    currentOp = nullptr;

    if (++currentKeyIndex < m_CurrentKeys.Length()) {
      nsCString nextDestination;
      nsresult rv = m_currentDB->GetOfflineOpForKey(
          m_CurrentKeys[currentKeyIndex], false, getter_AddRefs(currentOp));
      moveMatches = false;
      if (NS_SUCCEEDED(rv) && currentOp) {
        nsOfflineImapOperationType opType;
        currentOp->GetOperation(&opType);
        if (opType & nsIMsgOfflineImapOperation::kMsgMoved) {
          currentOp->GetDestinationFolderURI(nextDestination);
          moveMatches = moveDestination.Equals(nextDestination);
        }
      }
    }
  } while (currentOp);

  nsCOMPtr<nsIMsgFolder> destFolder;
  FindFolder(moveDestination, getter_AddRefs(destFolder));
  // if the dest folder doesn't really exist, these operations are
  // going to fail, so clear them out and move on.
  if (!destFolder) {
    NS_WARNING("trying to playing back move to non-existent folder");
    ClearCurrentOps();
    ProcessNextOperation();
    return;
  }
  nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
      do_QueryInterface(m_currentFolder);
  if (imapFolder && DestFolderOnSameServer(destFolder)) {
    uint32_t curFolderFlags;
    m_currentFolder->GetFlags(&curFolderFlags);
    bool curFolderOffline = curFolderFlags & nsMsgFolderFlags::Offline;
    imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys, true, destFolder, this,
                                      m_window, curFolderOffline);
  } else {
    nsresult rv;
    nsTArray<RefPtr<nsIMsgDBHdr>> messages;
    for (uint32_t keyIndex = 0; keyIndex < matchingFlagKeys.Length();
         keyIndex++) {
      nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr;
      rv = m_currentFolder->GetMessageHeader(
          matchingFlagKeys.ElementAt(keyIndex), getter_AddRefs(mailHdr));
      if (NS_SUCCEEDED(rv) && mailHdr) {
        uint32_t msgSize;
        // in case of a move, the header has already been deleted,
        // so we've really got a fake header. We need to get its flags and
        // size from the offline op to have any chance of doing the move.
        mailHdr->GetMessageSize(&msgSize);
        if (!msgSize) {
          imapMessageFlagsType newImapFlags;
          uint32_t msgFlags = 0;
          op->GetMsgSize(&msgSize);
          op->GetNewFlags(&newImapFlags);
          // first three bits are the same
          msgFlags |= (newImapFlags & 0x07);
          if (newImapFlags & kImapMsgForwardedFlag)
            msgFlags |= nsMsgMessageFlags::Forwarded;
          mailHdr->SetFlags(msgFlags);
          mailHdr->SetMessageSize(msgSize);
        }
        messages.AppendElement(mailHdr);
      }
    }
    nsCOMPtr<nsIMsgCopyService> copyService =
        do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
    if (copyService) {
      copyService->CopyMessages(m_currentFolder, messages, destFolder, true,
                                this, m_window, false);
    }
  }
}

// I'm tempted to make this a method on nsIMsgFolder, but that interface
// is already so huge, and there are only a few places in the code that do this.
// If there end up to be more places that need this, then we can reconsider.
bool nsImapOfflineSync::DestFolderOnSameServer(nsIMsgFolder* destFolder) {
  nsCOMPtr<nsIMsgIncomingServer> srcServer;
  nsCOMPtr<nsIMsgIncomingServer> dstServer;

  bool sameServer = false;
  if (NS_SUCCEEDED(m_currentFolder->GetServer(getter_AddRefs(srcServer))) &&
      NS_SUCCEEDED(destFolder->GetServer(getter_AddRefs(dstServer))))
    dstServer->Equals(srcServer, &sameServer);
  return sameServer;
}

void nsImapOfflineSync::ProcessCopyOperation(
    nsIMsgOfflineImapOperation* aCurrentOp) {
  nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = aCurrentOp;

  nsTArray<nsMsgKey> matchingFlagKeys;
  uint32_t currentKeyIndex = m_KeyIndex;
  nsCString copyDestination;
  currentOp->GetCopyDestination(0, getter_Copies(copyDestination));
  bool copyMatches = true;
  nsresult rv;

  do {  // loop for all messages with the same destination
    if (copyMatches) {
      nsMsgKey curKey;
      currentOp->GetMessageKey(&curKey);
      matchingFlagKeys.AppendElement(curKey);
      currentOp->SetPlayingBack(true);
      m_currentOpsToClear.AppendObject(currentOp);
    }
    currentOp = nullptr;

    if (++currentKeyIndex < m_CurrentKeys.Length()) {
      nsCString nextDestination;
      rv = m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex],
                                           false, getter_AddRefs(currentOp));
      copyMatches = false;
      if (NS_SUCCEEDED(rv) && currentOp) {
        nsOfflineImapOperationType opType;
        currentOp->GetOperation(&opType);
        if (opType & nsIMsgOfflineImapOperation::kMsgCopy) {
          currentOp->GetCopyDestination(0, getter_Copies(nextDestination));
          copyMatches = copyDestination.Equals(nextDestination);
        }
      }
    }
  } while (currentOp);

  nsAutoCString uids;
  nsCOMPtr<nsIMsgFolder> destFolder;
  FindFolder(copyDestination, getter_AddRefs(destFolder));
  // if the dest folder doesn't really exist, these operations are
  // going to fail, so clear them out and move on.
  if (!destFolder) {
    NS_ERROR("trying to playing back copy to non-existent folder");
    ClearCurrentOps();
    ProcessNextOperation();
    return;
  }
  nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
      do_QueryInterface(m_currentFolder);
  if (imapFolder && DestFolderOnSameServer(destFolder)) {
    uint32_t curFolderFlags;
    m_currentFolder->GetFlags(&curFolderFlags);
    bool curFolderOffline = curFolderFlags & nsMsgFolderFlags::Offline;
    rv = imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys, false, destFolder,
                                           this, m_window, curFolderOffline);
  } else {
    nsTArray<RefPtr<nsIMsgDBHdr>> messages;
    for (uint32_t keyIndex = 0; keyIndex < matchingFlagKeys.Length();
         keyIndex++) {
      nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr;
      rv = m_currentFolder->GetMessageHeader(
          matchingFlagKeys.ElementAt(keyIndex), getter_AddRefs(mailHdr));
      if (NS_SUCCEEDED(rv) && mailHdr) messages.AppendElement(mailHdr);
    }
    nsCOMPtr<nsIMsgCopyService> copyService =
        do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
    if (copyService)
      copyService->CopyMessages(m_currentFolder, messages, destFolder, false,
                                this, m_window, false);
  }
}

void nsImapOfflineSync::ProcessEmptyTrash() {
  m_currentFolder->EmptyTrash(this);
  ClearDB();  // EmptyTrash closes and deletes the trash db.
}

// returns true if we found a folder to create, false if we're done creating
// folders.
bool nsImapOfflineSync::CreateOfflineFolders() {
  while (m_currentFolder) {
    uint32_t flags;
    m_currentFolder->GetFlags(&flags);
    bool offlineCreate = (flags & nsMsgFolderFlags::CreatedOffline) != 0;
    if (offlineCreate) {
      if (CreateOfflineFolder(m_currentFolder)) return true;
    }
    AdvanceToNextFolder();
  }
  return false;
}

bool nsImapOfflineSync::CreateOfflineFolder(nsIMsgFolder* folder) {
  nsCOMPtr<nsIMsgFolder> parent;
  folder->GetParent(getter_AddRefs(parent));

  nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(parent);
  nsCOMPtr<nsIURI> createFolderURI;
  nsCString onlineName;
  imapFolder->GetOnlineName(onlineName);

  NS_ConvertASCIItoUTF16 folderName(onlineName);
  nsresult rv = imapFolder->PlaybackOfflineFolderCreate(
      folderName, nullptr, getter_AddRefs(createFolderURI));
  if (createFolderURI && NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
        do_QueryInterface(createFolderURI);
    if (mailnewsUrl) mailnewsUrl->RegisterListener(this);
  }
  return NS_SUCCEEDED(rv) ? true
                          : false;  // this is asynch, we have to return and be
                                    // called again by the OfflineOpExitFunction
}

int32_t nsImapOfflineSync::GetCurrentUIDValidity() {
  if (m_currentFolder) {
    nsCOMPtr<nsIImapMailFolderSink> imapFolderSink =
        do_QueryInterface(m_currentFolder);
    if (imapFolderSink) imapFolderSink->GetUidValidity(&mCurrentUIDValidity);
  }
  return mCurrentUIDValidity;
}

/**
 * Playing back offline operations is one giant state machine that runs through
 * ProcessNextOperation.
 * The first state is creating online any folders created offline (we do this
 * first, so we can play back any operations in them in the next pass)
 */
NS_IMETHODIMP
nsImapOfflineSync::ProcessNextOperation() {
  nsresult rv = NS_OK;

  // if we haven't created offline folders, and we're updating all folders,
  // first, find offline folders to create.
  if (!m_createdOfflineFolders) {
    if (m_singleFolderToUpdate) {
      if (!m_pseudoOffline) {
        AdvanceToFirstIMAPFolder();
        if (CreateOfflineFolders()) return NS_OK;
      }
    } else {
      if (CreateOfflineFolders()) return NS_OK;
      m_currentServer = nullptr;
      AdvanceToNextFolder();
    }
    m_createdOfflineFolders = true;
  }
  // if updating one folder only, restore m_currentFolder to that folder
  if (m_singleFolderToUpdate) m_currentFolder = m_singleFolderToUpdate;

  uint32_t folderFlags;
  nsCOMPtr<nsIDBFolderInfo> folderInfo;
  while (m_currentFolder && !m_currentDB) {
    m_currentFolder->GetFlags(&folderFlags);
    // need to check if folder has offline events, /* or is configured for
    // offline */ shouldn't need to check if configured for offline use, since
    // any folder with events should have nsMsgFolderFlags::OfflineEvents set.
    if (folderFlags &
        (nsMsgFolderFlags::OfflineEvents /* | nsMsgFolderFlags::Offline */)) {
      nsCOMPtr<nsIMsgDatabase> db;
      m_currentFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
                                            getter_AddRefs(db));
      if (db) {
        m_currentDB = do_QueryInterface(db, &rv);
        m_currentDB->AddListener(this);
      }
    }

    if (m_currentDB) {
      m_CurrentKeys.Clear();
      m_KeyIndex = 0;
      if (NS_FAILED(m_currentDB->ListAllOfflineOpIds(m_CurrentKeys)) ||
          m_CurrentKeys.IsEmpty()) {
        ClearDB();
        folderInfo = nullptr;  // can't hold onto folderInfo longer than db
        m_currentFolder->ClearFlag(nsMsgFolderFlags::OfflineEvents);
      } else {
        // trash any ghost msgs
        bool deletedGhostMsgs = false;
        for (uint32_t fakeIndex = 0; fakeIndex < m_CurrentKeys.Length();
             fakeIndex++) {
          nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
          m_currentDB->GetOfflineOpForKey(m_CurrentKeys[fakeIndex], false,
                                          getter_AddRefs(currentOp));
          if (currentOp) {
            nsOfflineImapOperationType opType;
            currentOp->GetOperation(&opType);

            if (opType == nsIMsgOfflineImapOperation::kMoveResult) {
              nsMsgKey curKey;
              currentOp->GetMessageKey(&curKey);
              m_currentDB->RemoveOfflineOp(currentOp);
              deletedGhostMsgs = true;

              // Remember the pseudo headers before we delete them,
              // and when we download new headers, tell listeners about the
              // message key change between the pseudo headers and the real
              // downloaded headers. Note that we're not currently sending
              // a msgsDeleted notification for these headers, but the
              // db listeners are notified about the deletion.
              // for imap folders, we should adjust the pending counts, because
              // we have a header that we know about, but don't have in the db.
              nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
                  do_QueryInterface(m_currentFolder);
              if (imapFolder) {
                bool hdrIsRead;
                m_currentDB->IsRead(curKey, &hdrIsRead);
                imapFolder->ChangePendingTotal(1);
                if (!hdrIsRead) imapFolder->ChangePendingUnread(1);
                imapFolder->AddMoveResultPseudoKey(curKey);
              }
              m_currentDB->DeleteMessage(curKey, nullptr, false);
            }
          }
        }

        if (deletedGhostMsgs) m_currentFolder->SummaryChanged();

        m_CurrentKeys.Clear();
        if (NS_FAILED(m_currentDB->ListAllOfflineOpIds(m_CurrentKeys)) ||
            m_CurrentKeys.IsEmpty()) {
          ClearDB();
        } else if (folderFlags & nsMsgFolderFlags::ImapBox) {
          // if pseudo offline, falls through to playing ops back.
          if (!m_pseudoOffline) {
            // there are operations to playback so check uid validity
            SetCurrentUIDValidity(0);  // force initial invalid state
            // do a lite select here and hook ourselves up as a listener.
            nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
                do_QueryInterface(m_currentFolder, &rv);
            if (imapFolder) rv = imapFolder->LiteSelect(this, m_window);
            // this is async, we will be called again by OnStopRunningUrl.
            return rv;
          }
        }
      }
    }

    if (!m_currentDB) {
      // only advance if we are doing all folders
      if (!m_singleFolderToUpdate)
        AdvanceToNextFolder();
      else
        m_currentFolder = nullptr;  // force update of this folder now.
    }
  }

  if (m_currentFolder) m_currentFolder->GetFlags(&folderFlags);
  // do the current operation
  if (m_currentDB) {
    bool currentFolderFinished = false;
    if (!folderInfo) m_currentDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
    // user canceled the lite select! if GetCurrentUIDValidity() == 0
    if (folderInfo && (m_KeyIndex < m_CurrentKeys.Length()) &&
        (m_pseudoOffline || (GetCurrentUIDValidity() != 0) ||
         !(folderFlags & nsMsgFolderFlags::ImapBox))) {
      int32_t curFolderUidValidity;
      folderInfo->GetImapUidValidity(&curFolderUidValidity);
      bool uidvalidityChanged =
          (!m_pseudoOffline && folderFlags & nsMsgFolderFlags::ImapBox) &&
          (GetCurrentUIDValidity() != curFolderUidValidity);
      nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
      if (uidvalidityChanged)
        DeleteAllOfflineOpsForCurrentDB();
      else
        m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false,
                                        getter_AddRefs(currentOp));

      if (currentOp) {
        nsOfflineImapOperationType opType;
        currentOp->GetOperation(&opType);
        // loop until we find the next db record that matches the current
        // playback operation
        while (currentOp && !(opType & mCurrentPlaybackOpType)) {
          // remove operations with no type.
          if (!opType) m_currentDB->RemoveOfflineOp(currentOp);
          currentOp = nullptr;
          ++m_KeyIndex;
          if (m_KeyIndex < m_CurrentKeys.Length())
            m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false,
                                            getter_AddRefs(currentOp));
          if (currentOp) currentOp->GetOperation(&opType);
        }
        // if we did not find a db record that matches the current playback
        // operation, then move to the next playback operation and recurse.
        if (!currentOp) {
          // we are done with the current type
          if (mCurrentPlaybackOpType ==
              nsIMsgOfflineImapOperation::kFlagsChanged) {
            mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kAddKeywords;
            // recurse to deal with next type of operation
            m_KeyIndex = 0;
            ProcessNextOperation();
          } else if (mCurrentPlaybackOpType ==
                     nsIMsgOfflineImapOperation::kAddKeywords) {
            mCurrentPlaybackOpType =
                nsIMsgOfflineImapOperation::kRemoveKeywords;
            // recurse to deal with next type of operation
            m_KeyIndex = 0;
            ProcessNextOperation();
          } else if (mCurrentPlaybackOpType ==
                     nsIMsgOfflineImapOperation::kRemoveKeywords) {
            mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kMsgCopy;
            // recurse to deal with next type of operation
            m_KeyIndex = 0;
            ProcessNextOperation();
          } else if (mCurrentPlaybackOpType ==
                     nsIMsgOfflineImapOperation::kMsgCopy) {
            mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kMsgMoved;
            // recurse to deal with next type of operation
            m_KeyIndex = 0;
            ProcessNextOperation();
          } else if (mCurrentPlaybackOpType ==
                     nsIMsgOfflineImapOperation::kMsgMoved) {
            mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kAppendDraft;
            // recurse to deal with next type of operation
            m_KeyIndex = 0;
            ProcessNextOperation();
          } else if (mCurrentPlaybackOpType ==
                     nsIMsgOfflineImapOperation::kAppendDraft) {
            mCurrentPlaybackOpType =
                nsIMsgOfflineImapOperation::kAppendTemplate;
            // recurse to deal with next type of operation
            m_KeyIndex = 0;
            ProcessNextOperation();
          } else if (mCurrentPlaybackOpType ==
                     nsIMsgOfflineImapOperation::kAppendTemplate) {
            mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kDeleteAllMsgs;
            m_KeyIndex = 0;
            ProcessNextOperation();
          } else {
            DeleteAllOfflineOpsForCurrentDB();
            currentFolderFinished = true;
          }

        } else {
          if (mCurrentPlaybackOpType ==
              nsIMsgOfflineImapOperation::kFlagsChanged)
            ProcessFlagOperation(currentOp);
          else if (mCurrentPlaybackOpType ==
                       nsIMsgOfflineImapOperation::kAddKeywords ||
                   mCurrentPlaybackOpType ==
                       nsIMsgOfflineImapOperation::kRemoveKeywords)
            ProcessKeywordOperation(currentOp);
          else if (mCurrentPlaybackOpType ==
                   nsIMsgOfflineImapOperation::kMsgCopy)
            ProcessCopyOperation(currentOp);
          else if (mCurrentPlaybackOpType ==
                   nsIMsgOfflineImapOperation::kMsgMoved)
            ProcessMoveOperation(currentOp);
          else if (mCurrentPlaybackOpType ==
                   nsIMsgOfflineImapOperation::kAppendDraft)
            ProcessAppendMsgOperation(currentOp,
                                      nsIMsgOfflineImapOperation::kAppendDraft);
          else if (mCurrentPlaybackOpType ==
                   nsIMsgOfflineImapOperation::kAppendTemplate)
            ProcessAppendMsgOperation(
                currentOp, nsIMsgOfflineImapOperation::kAppendTemplate);
          else if (mCurrentPlaybackOpType ==
                   nsIMsgOfflineImapOperation::kDeleteAllMsgs) {
            // empty trash is going to delete the db, so we'd better release the
            // reference to the offline operation first.
            currentOp = nullptr;
            ProcessEmptyTrash();
          } else
            NS_WARNING("invalid playback op type");
        }
      } else
        currentFolderFinished = true;
    } else
      currentFolderFinished = true;

    if (currentFolderFinished) {
      ClearDB();
      if (!m_singleFolderToUpdate) {
        AdvanceToNextFolder();
        ProcessNextOperation();
        return NS_OK;
      }
      m_currentFolder = nullptr;
    }
  }

  if (!m_currentFolder && !m_mailboxupdatesStarted) {
    m_mailboxupdatesStarted = true;

    // if we are updating more than one folder then we need the iterator
    if (!m_singleFolderToUpdate) {
      m_currentServer = nullptr;
      AdvanceToNextFolder();
    }
    if (m_singleFolderToUpdate) {
      m_singleFolderToUpdate->ClearFlag(nsMsgFolderFlags::OfflineEvents);
      m_singleFolderToUpdate->UpdateFolder(m_window);
    }
  }
  // if we get here, then I *think* we're done. Not sure, though.
#ifdef DEBUG_bienvenu
  printf("done with offline imap sync\n");
#endif
  nsCOMPtr<nsIUrlListener> saveListener = m_listener;
  m_listener = nullptr;

  if (saveListener)
    saveListener->OnStopRunningUrl(nullptr /* don't know url */, rv);
  return rv;
}

void nsImapOfflineSync::DeleteAllOfflineOpsForCurrentDB() {
  m_KeyIndex = 0;
  nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
  m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false,
                                  getter_AddRefs(currentOp));
  while (currentOp) {
    // NS_ASSERTION(currentOp->GetOperationFlags() == 0);
    // delete any ops that have already played back
    m_currentDB->RemoveOfflineOp(currentOp);
    currentOp = nullptr;

    if (++m_KeyIndex < m_CurrentKeys.Length())
      m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false,
                                      getter_AddRefs(currentOp));
  }
  m_currentDB->Commit(nsMsgDBCommitType::kLargeCommit);
  // turn off nsMsgFolderFlags::OfflineEvents
  if (m_currentFolder)
    m_currentFolder->ClearFlag(nsMsgFolderFlags::OfflineEvents);
}

nsImapOfflineDownloader::nsImapOfflineDownloader(nsIMsgWindow* aMsgWindow,
                                                 nsIUrlListener* aListener)
    : nsImapOfflineSync() {
  Init(aMsgWindow, aListener, nullptr, false);
  // pause auto-sync service
  nsresult rv;
  nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
      do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
  if (NS_SUCCEEDED(rv)) autoSyncMgr->Pause();
}

nsImapOfflineDownloader::~nsImapOfflineDownloader() {}

NS_IMETHODIMP
nsImapOfflineDownloader::ProcessNextOperation() {
  nsresult rv = NS_OK;
  m_mailboxupdatesStarted = true;

  if (!m_mailboxupdatesFinished) {
    if (AdvanceToNextServer()) {
      nsCOMPtr<nsIMsgFolder> rootMsgFolder;
      m_currentServer->GetRootFolder(getter_AddRefs(rootMsgFolder));
      nsCOMPtr<nsIMsgFolder> inbox;
      if (rootMsgFolder) {
        // Update the INBOX first so the updates on the remaining
        // folders pickup the results of any filter moves.
        rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
                                          getter_AddRefs(inbox));
        if (inbox) {
          nsCOMPtr<nsIMsgFolder> offlineImapFolder;
          nsCOMPtr<nsIMsgImapMailFolder> imapInbox = do_QueryInterface(inbox);
          if (imapInbox) {
            rootMsgFolder->GetFolderWithFlags(
                nsMsgFolderFlags::Offline, getter_AddRefs(offlineImapFolder));
            if (!offlineImapFolder) {
              // no imap folders configured for offline use - check if the
              // account is set up so that we always download inbox msg bodies
              // for offline use
              nsCOMPtr<nsIImapIncomingServer> imapServer =
                  do_QueryInterface(m_currentServer);
              if (imapServer) {
                bool downloadBodiesOnGetNewMail = false;
                imapServer->GetDownloadBodiesOnGetNewMail(
                    &downloadBodiesOnGetNewMail);
                if (downloadBodiesOnGetNewMail) offlineImapFolder = inbox;
              }
            }
          }
          // if this isn't an imap inbox, or we have an offline imap sub-folder,
          // then update the inbox. otherwise, it's an imap inbox for an account
          // with no folders configured for offline use, so just advance to the
          // next server.
          if (!imapInbox || offlineImapFolder) {
            // here we should check if this a pop3 server/inbox, and the user
            // doesn't want to download pop3 mail for offline use.
            if (!imapInbox) {
            }
            rv = inbox->GetNewMessages(m_window, this);
            if (NS_SUCCEEDED(rv)) return rv;  // otherwise, fall through.
          }
        }
      }
      return ProcessNextOperation();  // recurse and do next server.
    }
    m_allServers.Clear();
    m_mailboxupdatesFinished = true;
  }

  while (AdvanceToNextFolder()) {
    uint32_t folderFlags;

    ClearDB();
    nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
    if (m_currentFolder) imapFolder = do_QueryInterface(m_currentFolder);
    m_currentFolder->GetFlags(&folderFlags);
    // need to check if folder has offline events, or is configured for offline
    if (imapFolder && folderFlags & nsMsgFolderFlags::Offline &&
        !(folderFlags & nsMsgFolderFlags::Virtual)) {
      rv = m_currentFolder->DownloadAllForOffline(this, m_window);
      if (NS_SUCCEEDED(rv) || rv == NS_BINDING_ABORTED) return rv;
      // if this fails and the user didn't cancel/stop, fall through to code
      // that advances to next folder
    }
  }
  if (m_listener) m_listener->OnStopRunningUrl(nullptr, NS_OK);
  return rv;
}

NS_IMETHODIMP nsImapOfflineSync::OnStartCopy() {
  return NS_ERROR_NOT_IMPLEMENTED;
}

/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
NS_IMETHODIMP nsImapOfflineSync::OnProgress(uint32_t aProgress,
                                            uint32_t aProgressMax) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

/* void SetMessageKey (in uint32_t aKey); */
NS_IMETHODIMP nsImapOfflineSync::SetMessageKey(uint32_t aKey) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

/* [noscript] void GetMessageId (in nsCString aMessageId); */
NS_IMETHODIMP nsImapOfflineSync::GetMessageId(nsACString& messageId) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

/* void OnStopCopy (in nsresult aStatus); */
NS_IMETHODIMP nsImapOfflineSync::OnStopCopy(nsresult aStatus) {
  return OnStopRunningUrl(nullptr, aStatus);
}

void nsImapOfflineSync::ClearDB() {
  m_currentOpsToClear.Clear();
  if (m_currentDB) m_currentDB->RemoveListener(this);
  m_currentDB = nullptr;
}

NS_IMETHODIMP
nsImapOfflineSync::OnHdrPropertyChanged(nsIMsgDBHdr* aHdrToChange,
                                        const nsACString& property,
                                        bool aPreChange, uint32_t* aStatus,
                                        nsIDBChangeListener* aInstigator) {
  return NS_OK;
}

NS_IMETHODIMP
nsImapOfflineSync::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged,
                                     uint32_t aOldFlags, uint32_t aNewFlags,
                                     nsIDBChangeListener* aInstigator) {
  return NS_OK;
}

NS_IMETHODIMP
nsImapOfflineSync::OnHdrDeleted(nsIMsgDBHdr* aHdrChanged, nsMsgKey aParentKey,
                                int32_t aFlags,
                                nsIDBChangeListener* aInstigator) {
  return NS_OK;
}

NS_IMETHODIMP
nsImapOfflineSync::OnHdrAdded(nsIMsgDBHdr* aHdrAdded, nsMsgKey aParentKey,
                              int32_t aFlags,
                              nsIDBChangeListener* aInstigator) {
  return NS_OK;
}

/* void OnParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in
 * nsMsgKey newParent, in nsIDBChangeListener aInstigator); */
NS_IMETHODIMP
nsImapOfflineSync::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent,
                                   nsMsgKey newParent,
                                   nsIDBChangeListener* aInstigator) {
  return NS_OK;
}

/* void OnAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */
NS_IMETHODIMP
nsImapOfflineSync::OnAnnouncerGoingAway(nsIDBChangeAnnouncer* instigator) {
  ClearDB();
  return NS_OK;
}

NS_IMETHODIMP nsImapOfflineSync::OnEvent(nsIMsgDatabase* aDB,
                                         const char* aEvent) {
  return NS_OK;
}

/* void OnReadChanged (in nsIDBChangeListener instigator); */
NS_IMETHODIMP
nsImapOfflineSync::OnReadChanged(nsIDBChangeListener* instigator) {
  return NS_OK;
}

/* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */
NS_IMETHODIMP
nsImapOfflineSync::OnJunkScoreChanged(nsIDBChangeListener* instigator) {
  return NS_OK;
}
