diff options
Diffstat (limited to 'comm/mailnews/imap/src/nsImapOfflineSync.cpp')
-rw-r--r-- | comm/mailnews/imap/src/nsImapOfflineSync.cpp | 1175 |
1 files changed, 1175 insertions, 0 deletions
diff --git a/comm/mailnews/imap/src/nsImapOfflineSync.cpp b/comm/mailnews/imap/src/nsImapOfflineSync.cpp new file mode 100644 index 0000000000..6b43a106bb --- /dev/null +++ b/comm/mailnews/imap/src/nsImapOfflineSync.cpp @@ -0,0 +1,1175 @@ +/* -*- 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; +} |