summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/imap/src/nsAutoSyncState.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/imap/src/nsAutoSyncState.cpp')
-rw-r--r--comm/mailnews/imap/src/nsAutoSyncState.cpp782
1 files changed, 782 insertions, 0 deletions
diff --git a/comm/mailnews/imap/src/nsAutoSyncState.cpp b/comm/mailnews/imap/src/nsAutoSyncState.cpp
new file mode 100644
index 0000000000..db3216419a
--- /dev/null
+++ b/comm/mailnews/imap/src/nsAutoSyncState.cpp
@@ -0,0 +1,782 @@
+/* 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 "nsAutoSyncState.h"
+#include "nsImapMailFolder.h"
+#include "nsIImapService.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIAutoSyncManager.h"
+#include "nsIAutoSyncMsgStrategy.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+extern LazyLogModule gAutoSyncLog; // defined in nsAutoSyncManager.cpp
+
+MsgStrategyComparatorAdaptor::MsgStrategyComparatorAdaptor(
+ nsIAutoSyncMsgStrategy* aStrategy, nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aDatabase)
+ : mStrategy(aStrategy), mFolder(aFolder), mDatabase(aDatabase) {}
+
+/** @return True if the elements are equals; false otherwise. */
+bool MsgStrategyComparatorAdaptor::Equals(const nsMsgKey& a,
+ const nsMsgKey& b) const {
+ nsCOMPtr<nsIMsgDBHdr> hdrA;
+ nsCOMPtr<nsIMsgDBHdr> hdrB;
+
+ mDatabase->GetMsgHdrForKey(a, getter_AddRefs(hdrA));
+ mDatabase->GetMsgHdrForKey(b, getter_AddRefs(hdrB));
+
+ if (hdrA && hdrB) {
+ nsresult rv = NS_OK;
+ nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+
+ if (mStrategy) rv = mStrategy->Sort(mFolder, hdrA, hdrB, &decision);
+
+ if (NS_SUCCEEDED(rv))
+ return (decision == nsAutoSyncStrategyDecisions::Same);
+ }
+
+ return false;
+}
+
+/** @return True if (a < b); false otherwise. */
+bool MsgStrategyComparatorAdaptor::LessThan(const nsMsgKey& a,
+ const nsMsgKey& b) const {
+ nsCOMPtr<nsIMsgDBHdr> hdrA;
+ nsCOMPtr<nsIMsgDBHdr> hdrB;
+
+ mDatabase->GetMsgHdrForKey(a, getter_AddRefs(hdrA));
+ mDatabase->GetMsgHdrForKey(b, getter_AddRefs(hdrB));
+
+ if (hdrA && hdrB) {
+ nsresult rv = NS_OK;
+ nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+
+ if (mStrategy) rv = mStrategy->Sort(mFolder, hdrA, hdrB, &decision);
+
+ if (NS_SUCCEEDED(rv))
+ return (decision == nsAutoSyncStrategyDecisions::Lower);
+ }
+
+ return false;
+}
+
+nsAutoSyncState::nsAutoSyncState(nsImapMailFolder* aOwnerFolder,
+ PRTime aLastSyncTime)
+ : mSyncState(stCompletedIdle),
+ mOffset(0U),
+ mLastOffset(0U),
+ mLastServerTotal(0),
+ mLastServerRecent(0),
+ mLastServerUnseen(0),
+ mLastNextUID(0),
+ mLastSyncTime(aLastSyncTime),
+ mLastUpdateTime(0UL),
+ mProcessPointer(0U),
+ mIsDownloadQChanged(false),
+ mRetryCounter(0U) {
+ mOwnerFolder =
+ do_GetWeakReference(static_cast<nsIMsgImapMailFolder*>(aOwnerFolder));
+ mHaveAStatusResponse = false;
+}
+
+nsAutoSyncState::~nsAutoSyncState() {}
+
+// TODO:XXXemre should be implemented when we start
+// doing space management
+nsresult nsAutoSyncState::ManageStorageSpace() { return NS_OK; }
+
+nsresult nsAutoSyncState::PlaceIntoDownloadQ(
+ const nsTArray<nsMsgKey>& aMsgKeyList) {
+ nsresult rv = NS_OK;
+ if (!aMsgKeyList.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+
+ // increase the array size
+ mDownloadQ.SetCapacity(mDownloadQ.Length() + aMsgKeyList.Length());
+
+ // remove excluded messages
+ int32_t elemCount = aMsgKeyList.Length();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ bool containsKey;
+ database->ContainsKey(aMsgKeyList[idx], &containsKey);
+ if (!containsKey) continue;
+ rv = database->GetMsgHdrForKey(aMsgKeyList[idx], getter_AddRefs(hdr));
+ if (!hdr)
+ continue; // can't get message header, continue with the next one
+
+ bool doesFit = true;
+ rv = autoSyncMgr->DoesMsgFitDownloadCriteria(hdr, &doesFit);
+ if (NS_SUCCEEDED(rv) && !mDownloadSet.Contains(aMsgKeyList[idx]) &&
+ doesFit) {
+ bool excluded = false;
+ if (msgStrategy) {
+ rv = msgStrategy->IsExcluded(folder, hdr, &excluded);
+
+ if (NS_SUCCEEDED(rv) && !excluded) {
+ mIsDownloadQChanged = true;
+ mDownloadSet.PutEntry(aMsgKeyList[idx]);
+ mDownloadQ.AppendElement(aMsgKeyList[idx]);
+ }
+ }
+ }
+ } // endfor
+
+ if (mIsDownloadQChanged) {
+ LogOwnerFolderName("Download Q is created for ");
+ LogQWithSize(mDownloadQ, 0);
+ rv = autoSyncMgr->OnDownloadQChanged(this);
+ }
+ }
+ return rv;
+}
+
+nsresult nsAutoSyncState::SortQueueBasedOnStrategy(nsTArray<nsMsgKey>& aQueue) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ rv = autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MsgStrategyComparatorAdaptor strategyComp(msgStrategy, folder, database);
+ aQueue.Sort(strategyComp);
+
+ return rv;
+}
+
+// This method is a hack to prioritize newly inserted messages,
+// without changing the size of the queue. It is required since
+// we cannot sort ranges in nsTArray.
+nsresult nsAutoSyncState::SortSubQueueBasedOnStrategy(
+ nsTArray<nsMsgKey>& aQueue, uint32_t aStartingOffset) {
+ NS_ASSERTION(aStartingOffset < aQueue.Length(),
+ "*** Starting offset is out of range");
+
+ // Copy already downloaded messages into a temporary queue,
+ // we want to exclude them from the sort.
+ nsTArray<nsMsgKey> tmpQ;
+ tmpQ.AppendElements(aQueue.Elements(), aStartingOffset);
+
+ // Remove already downloaded messages and sort the resulting queue
+ aQueue.RemoveElementsAt(0, aStartingOffset);
+
+ nsresult rv = SortQueueBasedOnStrategy(aQueue);
+
+ // copy excluded messages back
+ aQueue.InsertElementsAt(0, tmpQ);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetNextGroupOfMessages(
+ uint32_t aSuggestedGroupSizeLimit, uint32_t* aActualGroupSize,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages) {
+ NS_ENSURE_ARG_POINTER(aActualGroupSize);
+
+ aMessages.Clear();
+ *aActualGroupSize = 0;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ folder->GetMsgDatabase(getter_AddRefs(database));
+
+ if (database) {
+ if (!mDownloadQ.IsEmpty()) {
+ // sort the download queue if new items are added since the last time
+ if (mIsDownloadQChanged) {
+ // we want to sort only pending messages. mOffset is
+ // the position of the first pending message in the download queue
+ rv = (mOffset > 0) ? SortSubQueueBasedOnStrategy(mDownloadQ, mOffset)
+ : SortQueueBasedOnStrategy(mDownloadQ);
+
+ if (NS_SUCCEEDED(rv)) mIsDownloadQChanged = false;
+ }
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgCount = mDownloadQ.Length();
+ uint32_t idx = mOffset;
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+
+ for (; idx < msgCount; idx++) {
+ bool containsKey = false;
+ database->ContainsKey(mDownloadQ[idx], &containsKey);
+ if (!containsKey) {
+ mDownloadSet.RemoveEntry(mDownloadQ[idx]);
+ mDownloadQ.RemoveElementAt(idx--);
+ msgCount--;
+ continue;
+ }
+ nsCOMPtr<nsIMsgDBHdr> qhdr;
+ database->GetMsgHdrForKey(mDownloadQ[idx], getter_AddRefs(qhdr));
+ if (!qhdr) continue; // maybe deleted, skip it!
+
+ // ensure that we don't have this message body offline already,
+ // possible if the user explicitly selects this message prior
+ // to auto-sync kicks in
+ bool hasMessageOffline;
+ folder->HasMsgOffline(mDownloadQ[idx], &hasMessageOffline);
+ if (hasMessageOffline) continue;
+
+ // this check point allows msg strategy function
+ // to do last minute decisions based on the current
+ // state of TB such as the size of the message store etc.
+ if (msgStrategy) {
+ bool excluded = false;
+ if (NS_SUCCEEDED(msgStrategy->IsExcluded(folder, qhdr, &excluded)) &&
+ excluded)
+ continue;
+ }
+
+ uint32_t msgSize;
+ qhdr->GetMessageSize(&msgSize);
+ // ignore 0 byte messages; the imap parser asserts when we try
+ // to download them, and there's no point anyway.
+ if (!msgSize) continue;
+
+ if (!*aActualGroupSize && msgSize >= aSuggestedGroupSizeLimit) {
+ *aActualGroupSize = msgSize;
+ aMessages.AppendElement(qhdr);
+ idx++;
+ break;
+ }
+ if ((*aActualGroupSize) + msgSize > aSuggestedGroupSizeLimit) break;
+
+ aMessages.AppendElement(qhdr);
+ *aActualGroupSize += msgSize;
+ } // endfor
+
+ mLastOffset = mOffset;
+ mOffset = idx;
+ }
+
+ LogOwnerFolderName("Next group of messages to be downloaded.");
+ LogQWithSize(aMessages, 0);
+ } // endif
+
+ return NS_OK;
+}
+
+/**
+ * Called by nsAutoSyncManager::TimerCallback to process message headers for a
+ * folder in the discovery queue. The queue is created on the kAutoSyncFreq
+ * time base (1 hour). Headers lacking offline store are placed in download
+ * queue.
+ */
+NS_IMETHODIMP nsAutoSyncState::ProcessExistingHeaders(
+ uint32_t aNumOfHdrsToProcess, uint32_t* aLeftToProcess) {
+ NS_ENSURE_ARG_POINTER(aLeftToProcess);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database) return NS_ERROR_FAILURE;
+
+ // create a queue to process existing headers for the first time
+ if (mExistingHeadersQ.IsEmpty()) {
+ nsTArray<nsMsgKey> keys;
+ rv = database->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ keys.Sort();
+ mExistingHeadersQ.AppendElements(keys);
+ mProcessPointer = 0;
+ }
+
+ // process the existing headers and find the messages not downloaded yet
+ uint32_t lastIdx = mProcessPointer;
+ nsTArray<nsMsgKey> msgKeys;
+ uint32_t keyCount = mExistingHeadersQ.Length();
+ for (; mProcessPointer < (lastIdx + aNumOfHdrsToProcess) &&
+ mProcessPointer < keyCount;
+ mProcessPointer++) {
+ bool hasMessageOffline;
+ folder->HasMsgOffline(mExistingHeadersQ[mProcessPointer],
+ &hasMessageOffline);
+ if (!hasMessageOffline)
+ msgKeys.AppendElement(mExistingHeadersQ[mProcessPointer]);
+ }
+ if (!msgKeys.IsEmpty()) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(
+ gAutoSyncLog, LogLevel::Debug,
+ ("%s: %zu messages will be added into the download q of folder %s\n",
+ __func__, msgKeys.Length(), folderName.get()));
+
+ rv = PlaceIntoDownloadQ(msgKeys);
+ if (NS_FAILED(rv)) mProcessPointer = lastIdx;
+ }
+
+ *aLeftToProcess = keyCount - mProcessPointer;
+
+ // cleanup if we are done processing
+ if (0 == *aLeftToProcess) {
+ mLastSyncTime = PR_Now();
+ mExistingHeadersQ.Clear();
+ mProcessPointer = 0;
+ folder->SetMsgDatabase(nullptr);
+ }
+
+ return rv;
+}
+
+void nsAutoSyncState::OnNewHeaderFetchCompleted(
+ const nsTArray<nsMsgKey>& aMsgKeyList) {
+ SetLastUpdateTime(PR_Now());
+ if (!aMsgKeyList.IsEmpty()) PlaceIntoDownloadQ(aMsgKeyList);
+ MOZ_LOG(
+ gAutoSyncLog, LogLevel::Debug,
+ ("%s: %zu msg keys put into download q", __func__, aMsgKeyList.Length()));
+}
+
+NS_IMETHODIMP nsAutoSyncState::UpdateFolder() {
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> autoSyncMgrListener =
+ do_QueryInterface(autoSyncMgr, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryReferent(mOwnerFolder, &rv);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ return imapFolder->UpdateFolderWithListener(nullptr, autoSyncMgrListener);
+}
+
+NS_IMETHODIMP nsAutoSyncState::OnStartRunningUrl(nsIURI* aUrl) {
+ nsresult rv = NS_OK;
+
+ // if there is a problem to start the download, set rv with the
+ // corresponding error code. In that case, AutoSyncManager is going to
+ // set the autosync state to nsAutoSyncState::stReadyToDownload
+ // to resume downloading another time
+
+ // TODO: is there a way to make sure that download started without
+ // problem through nsIURI interface?
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return autoSyncMgr->OnDownloadStarted(this, rv);
+}
+
+/**
+ * This is called when a folder status URL finishes. It is also called when
+ * needed message downloads (imap fetch) for a folder completes.
+ */
+NS_IMETHODIMP nsAutoSyncState::OnStopRunningUrl(nsIURI* aUrl,
+ nsresult aExitCode) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> autoSyncMgrListener =
+ do_QueryInterface(autoSyncMgr, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSyncState == stStatusIssued) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t serverTotal, serverUnseen, serverRecent, serverNextUID;
+ imapFolder->GetServerTotal(&serverTotal);
+ imapFolder->GetServerUnseen(&serverUnseen);
+ imapFolder->GetServerRecent(&serverRecent);
+ imapFolder->GetServerNextUID(&serverNextUID);
+ // Note: UNSEEN often shows a change when nothing else changes. This is
+ // because UNSEEN produced by SELECT is not the number of unseen messages.
+ // So ignore change to UNSEEN to avoid spurious folder updates. Commented
+ // out below.
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: serverUnseen=%d lastServerUnseen=%d", __func__, serverUnseen,
+ mLastServerUnseen));
+ if (serverNextUID != mLastNextUID || serverTotal != mLastServerTotal ||
+ serverRecent != mLastServerRecent //||
+ /*(serverUnseen != mLastServerUnseen)*/) {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder %s status changed serverNextUID=%d lastNextUID=%d",
+ __func__, folderName.get(), serverNextUID, mLastNextUID));
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: serverTotal = %d lastServerTotal = %d serverRecent = %d "
+ "lastServerRecent = %d\n",
+ __func__, serverTotal, mLastServerTotal, serverRecent,
+ mLastServerRecent));
+ }
+ SetServerCounts(serverTotal, serverRecent, serverUnseen, serverNextUID);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ rv = imapFolder->UpdateFolderWithListener(nullptr, autoSyncMgrListener);
+ } else // folderstatus detected no change
+ {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder %s status or noop issued, no change", __func__,
+ folderName.get()));
+ }
+ // Status detected no change. This may be due to an previously deleted and
+ // now empty database so change compares above could be invalid. If so,
+ // force an update which will re-populate the database (.msf) and download
+ // all the message to mbox/maildir store. This check is only done on the
+ // first imap STATUS response after start-up and if the server response
+ // reports that the folder is not empty.
+ if (!mHaveAStatusResponse && serverTotal != 0) {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+ bool hasHeader = false;
+ if (database) {
+ nsCOMPtr<nsIMsgEnumerator> hdrs;
+ database->EnumerateMessages(getter_AddRefs(hdrs));
+ if (hdrs) hdrs->HasMoreElements(&hasHeader);
+ }
+ if (!hasHeader) {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder %s has empty DB, force an update", __func__,
+ folderName.get()));
+ }
+ SetServerCounts(serverTotal, serverRecent, serverUnseen,
+ serverNextUID);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ rv = imapFolder->UpdateFolderWithListener(nullptr,
+ autoSyncMgrListener);
+ }
+ }
+ if (mSyncState == stStatusIssued) {
+ // Didn't force an update above so transition back to stCompletedIdle
+ ownerFolder->SetMsgDatabase(nullptr);
+ // nothing more to do.
+ SetState(nsAutoSyncState::stCompletedIdle);
+ // autoSyncMgr needs this notification, so manufacture it.
+ rv = autoSyncMgrListener->OnStopRunningUrl(aUrl, NS_OK);
+ }
+ } // end no change detected
+ mHaveAStatusResponse = true;
+ } else // URL not folderstatus but FETCH of message body
+ {
+ // XXXemre how we recover from this error?
+ rv = ownerFolder->ReleaseSemaphore(ownerFolder);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "*** Cannot release folder semaphore");
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl) rv = mailUrl->UnRegisterListener(this);
+
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: URL for FETCH of msg body/bodies complete, folder %s",
+ __func__, folderName.get()));
+ }
+ rv = autoSyncMgr->OnDownloadCompleted(this, aExitCode);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetState(int32_t* aState) {
+ NS_ENSURE_ARG_POINTER(aState);
+ *aState = mSyncState;
+ return NS_OK;
+}
+
+// clang-format off
+const char* stateStrings[] = {
+ "stCompletedIdle:0", // Initial state
+ "stStatusIssued:1", // Imap STATUS or NOOP to occur to detect new msgs
+ "stUpdateNeeded:2", // Imap SELECT to occur due to "pending" msgs
+ "stUpdateIssued:3", // Imap SELECT to occur then fetch new headers
+ "stReadyToDownload:4", // Ready to download a group of new messages
+ "stDownloadInProgress:5" // Download, go to 4 if more msgs then 0 when all done
+};
+// clang-format on
+
+NS_IMETHODIMP nsAutoSyncState::SetState(int32_t aState) {
+ mSyncState = aState;
+ if (aState == stCompletedIdle) {
+ ResetDownloadQ();
+ // tell folder to let go of its cached msg db pointer
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ if (NS_SUCCEEDED(rv) && session) {
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool folderOpen;
+ uint32_t folderFlags;
+ ownerFolder->GetFlags(&folderFlags);
+ session->IsFolderOpenInWindow(ownerFolder, &folderOpen);
+ if (!folderOpen && !(folderFlags & nsMsgFolderFlags::Inbox))
+ ownerFolder->SetMsgDatabase(nullptr);
+ }
+ }
+ nsCString logStr("Sync State set to |");
+ logStr.Append(stateStrings[aState]);
+ logStr.AppendLiteral("| for ");
+ LogOwnerFolderName(logStr.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::TryCurrentGroupAgain(uint32_t aRetryCount) {
+ SetState(stReadyToDownload);
+
+ nsresult rv;
+ if (++mRetryCounter > aRetryCount) {
+ ResetRetryCounter();
+ rv = NS_ERROR_FAILURE;
+ } else
+ rv = Rollback();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::ResetRetryCounter() {
+ mRetryCounter = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetPendingMessageCount(int32_t* aMsgCount) {
+ NS_ENSURE_ARG_POINTER(aMsgCount);
+ *aMsgCount = mDownloadQ.Length() - mOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetTotalMessageCount(int32_t* aMsgCount) {
+ NS_ENSURE_ARG_POINTER(aMsgCount);
+ *aMsgCount = mDownloadQ.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetOwnerFolder(nsIMsgFolder** aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ownerFolder.forget(aFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::Rollback() {
+ mOffset = mLastOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::ResetDownloadQ() {
+ mOffset = mLastOffset = 0;
+ mDownloadSet.Clear();
+ mDownloadQ.Clear();
+ mDownloadQ.Compact();
+
+ return NS_OK;
+}
+
+/**
+ * Tests whether the given folder is owned by the same imap server
+ * or not.
+ */
+NS_IMETHODIMP nsAutoSyncState::IsSibling(nsIAutoSyncState* aAnotherStateObj,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folderA, folderB;
+
+ rv = GetOwnerFolder(getter_AddRefs(folderA));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aAnotherStateObj->GetOwnerFolder(getter_AddRefs(folderB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> serverA, serverB;
+ rv = folderA->GetServer(getter_AddRefs(serverA));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderB->GetServer(getter_AddRefs(serverB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isSibling;
+ rv = serverA->Equals(serverB, &isSibling);
+
+ if (NS_SUCCEEDED(rv)) *aResult = isSibling;
+
+ return rv;
+}
+
+/**
+ * Test whether the download queue is empty.
+ */
+NS_IMETHODIMP nsAutoSyncState::IsDownloadQEmpty(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mDownloadQ.IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::DownloadMessagesForOffline(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> msgKeys;
+
+ rv = nsImapMailFolder::BuildIdsAndKeyArray(messages, messageIds, msgKeys);
+ if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv;
+
+ // acquire semaphore for offline store. If it fails, we won't download
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folder->AcquireSemaphore(folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: downloading UIDs %s for folder %s", __func__,
+ messageIds.get(), folderName.get()));
+ }
+ // start downloading
+ rv = imapService->DownloadMessagesForOffline(messageIds, folder, this,
+ nullptr);
+ if (NS_SUCCEEDED(rv)) SetState(stDownloadInProgress);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetLastSyncTime(PRTime* aLastSyncTime) {
+ NS_ENSURE_ARG_POINTER(aLastSyncTime);
+ *aLastSyncTime = mLastSyncTime;
+ return NS_OK;
+}
+
+void nsAutoSyncState::SetLastSyncTimeInSec(int32_t aLastSyncTime) {
+ mLastSyncTime = ((PRTime)aLastSyncTime * PR_USEC_PER_SEC);
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetLastUpdateTime(PRTime* aLastUpdateTime) {
+ NS_ENSURE_ARG_POINTER(aLastUpdateTime);
+ *aLastUpdateTime = mLastUpdateTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::SetLastUpdateTime(PRTime aLastUpdateTime) {
+ mLastUpdateTime = aLastUpdateTime;
+ return NS_OK;
+}
+
+void nsAutoSyncState::SetServerCounts(int32_t total, int32_t recent,
+ int32_t unseen, int32_t nextUID) {
+ mLastServerTotal = total;
+ mLastServerRecent = recent;
+ mLastServerUnseen = unseen;
+ mLastNextUID = nextUID;
+}
+
+NS_IMPL_ISUPPORTS(nsAutoSyncState, nsIAutoSyncState, nsIUrlListener)
+
+void nsAutoSyncState::LogQWithSize(nsTArray<nsMsgKey>& q, uint32_t toOffset) {
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder) {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+
+ uint32_t x = q.Length();
+ while (x > toOffset && database) {
+ x--;
+ nsCOMPtr<nsIMsgDBHdr> h;
+ database->GetMsgHdrForKey(q[x], getter_AddRefs(h));
+ uint32_t s;
+ if (h) {
+ h->GetMessageSize(&s);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("Elem #%d, size: %u bytes\n", x + 1, s));
+ } else
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("unable to get header for key %ul", q[x]));
+ }
+ }
+}
+
+void nsAutoSyncState::LogQWithSize(nsTArray<RefPtr<nsIMsgDBHdr>> const& q,
+ uint32_t toOffset) {
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder) {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+
+ uint32_t x = q.Length();
+ while (x > toOffset && database) {
+ x--;
+ if (q[x]) {
+ uint32_t s;
+ q[x]->GetMessageSize(&s);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("Elem #%d, size: %u bytes\n", x + 1, s));
+ } else
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("null header in q at index %ul", x));
+ }
+ }
+}
+
+void nsAutoSyncState::LogOwnerFolderName(const char* s) {
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("*** %s Folder: %s ***\n", s, folderName.get()));
+ }
+}