summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/imap/src/nsAutoSyncManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/imap/src/nsAutoSyncManager.cpp')
-rw-r--r--comm/mailnews/imap/src/nsAutoSyncManager.cpp1372
1 files changed, 1372 insertions, 0 deletions
diff --git a/comm/mailnews/imap/src/nsAutoSyncManager.cpp b/comm/mailnews/imap/src/nsAutoSyncManager.cpp
new file mode 100644
index 0000000000..e0b9611dc0
--- /dev/null
+++ b/comm/mailnews/imap/src/nsAutoSyncManager.cpp
@@ -0,0 +1,1372 @@
+/* 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 "nsAutoSyncManager.h"
+#include "nsAutoSyncState.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIObserverService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgUtils.h"
+#include "nsIIOService.h"
+#include "nsITimer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsDefaultAutoSyncMsgStrategy, nsIAutoSyncMsgStrategy)
+
+const char* kAppIdleNotification = "mail:appIdle";
+const char* kStartupDoneNotification = "mail-startup-done";
+LazyLogModule gAutoSyncLog("IMAPAutoSync");
+
+// recommended size of each group of messages per download
+static const uint32_t kDefaultGroupSize = 50U * 1024U /* 50K */;
+
+nsDefaultAutoSyncMsgStrategy::nsDefaultAutoSyncMsgStrategy() {}
+
+nsDefaultAutoSyncMsgStrategy::~nsDefaultAutoSyncMsgStrategy() {}
+
+NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::Sort(
+ nsIMsgFolder* aFolder, nsIMsgDBHdr* aMsgHdr1, nsIMsgDBHdr* aMsgHdr2,
+ nsAutoSyncStrategyDecisionType* aDecision) {
+ NS_ENSURE_ARG_POINTER(aDecision);
+
+ uint32_t msgSize1 = 0, msgSize2 = 0;
+ PRTime msgDate1 = 0, msgDate2 = 0;
+
+ if (!aMsgHdr1 || !aMsgHdr2) {
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ return NS_OK;
+ }
+
+ aMsgHdr1->GetMessageSize(&msgSize1);
+ aMsgHdr1->GetDate(&msgDate1);
+
+ aMsgHdr2->GetMessageSize(&msgSize2);
+ aMsgHdr2->GetDate(&msgDate2);
+
+ // Special case: if message size is larger than a
+ // certain size, then place it to the bottom of the q
+ if (msgSize2 > kFirstPassMessageSize && msgSize1 > kFirstPassMessageSize)
+ *aDecision = msgSize2 > msgSize1 ? nsAutoSyncStrategyDecisions::Lower
+ : nsAutoSyncStrategyDecisions::Higher;
+ else if (msgSize2 > kFirstPassMessageSize)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else if (msgSize1 > kFirstPassMessageSize)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else {
+ // Most recent and smallest first
+ if (msgDate1 < msgDate2)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (msgDate1 > msgDate2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else {
+ if (msgSize1 > msgSize2)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (msgSize1 < msgSize2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::IsExcluded(nsIMsgFolder* aFolder,
+ nsIMsgDBHdr* aMsgHdr,
+ bool* aDecision) {
+ NS_ENSURE_ARG_POINTER(aDecision);
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsresult rv = aFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer(do_QueryInterface(server, &rv));
+ int32_t offlineMsgAgeLimit = -1;
+ imapServer->GetAutoSyncMaxAgeDays(&offlineMsgAgeLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ PRTime msgDate;
+ aMsgHdr->GetDate(&msgDate);
+ *aDecision = offlineMsgAgeLimit > 0 &&
+ msgDate < MsgConvertAgeInDaysToCutoffDate(offlineMsgAgeLimit);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDefaultAutoSyncFolderStrategy, nsIAutoSyncFolderStrategy)
+
+nsDefaultAutoSyncFolderStrategy::nsDefaultAutoSyncFolderStrategy() {}
+
+nsDefaultAutoSyncFolderStrategy::~nsDefaultAutoSyncFolderStrategy() {}
+
+NS_IMETHODIMP nsDefaultAutoSyncFolderStrategy::Sort(
+ nsIMsgFolder* aFolderA, nsIMsgFolder* aFolderB,
+ nsAutoSyncStrategyDecisionType* aDecision) {
+ NS_ENSURE_ARG_POINTER(aDecision);
+
+ if (!aFolderA || !aFolderB) {
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ return NS_OK;
+ }
+
+ bool isInbox1, isInbox2, isDrafts1, isDrafts2, isTrash1, isTrash2;
+ aFolderA->GetFlag(nsMsgFolderFlags::Inbox, &isInbox1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Inbox, &isInbox2);
+ //
+ aFolderA->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts2);
+ //
+ aFolderA->GetFlag(nsMsgFolderFlags::Trash, &isTrash1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Trash, &isTrash2);
+
+ // Follow this order;
+ // INBOX > DRAFTS > SUBFOLDERS > TRASH
+
+ // test whether the folder is opened by the user.
+ // we give high priority to the folders explicitly opened by
+ // the user.
+ nsresult rv;
+ bool folderAOpen = false;
+ bool folderBOpen = false;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ if (NS_SUCCEEDED(rv) && session) {
+ session->IsFolderOpenInWindow(aFolderA, &folderAOpen);
+ session->IsFolderOpenInWindow(aFolderB, &folderBOpen);
+ }
+
+ if (folderAOpen == folderBOpen) {
+ // if both of them or none of them are opened by the user
+ // make your decision based on the folder type
+ if (isInbox2 || (isDrafts2 && !isInbox1) || isTrash1)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (isInbox1 || (isDrafts1 && !isDrafts2) || isTrash2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ } else {
+ // otherwise give higher priority to opened one
+ *aDecision = folderBOpen ? nsAutoSyncStrategyDecisions::Higher
+ : nsAutoSyncStrategyDecisions::Lower;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultAutoSyncFolderStrategy::IsExcluded(nsIMsgFolder* aFolder,
+ bool* aDecision) {
+ NS_ENSURE_ARG_POINTER(aDecision);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ uint32_t folderFlags;
+ aFolder->GetFlags(&folderFlags);
+ // exclude saved search
+ *aDecision = (folderFlags & nsMsgFolderFlags::Virtual);
+ if (!*aDecision) {
+ // Exclude orphans
+ nsCOMPtr<nsIMsgFolder> parent;
+ aFolder->GetParent(getter_AddRefs(parent));
+ if (!parent) *aDecision = true;
+ }
+ return NS_OK;
+}
+
+#define NOTIFY_LISTENERS_STATIC(obj_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIAutoSyncMgrListener>>::ForwardIterator iter( \
+ obj_->mListeners); \
+ nsCOMPtr<nsIAutoSyncMgrListener> listener; \
+ while (iter.HasMore()) { \
+ listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+ NOTIFY_LISTENERS_STATIC(this, propertyfunc_, params_)
+
+nsAutoSyncManager::nsAutoSyncManager() {
+ mGroupSize = kDefaultGroupSize;
+
+ mIdleState = notIdle;
+ mStartupDone = false;
+ mDownloadModel = dmChained;
+ mUpdateInProgress = false;
+ mPaused = false;
+
+ nsresult rv;
+ mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1", &rv);
+ if (mIdleService) mIdleService->AddIdleObserver(this, kIdleTimeInSec);
+
+ // Observe xpcom-shutdown event and app-idle changes
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ observerService->AddObserver(this, kAppIdleNotification, false);
+ observerService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
+ observerService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, false);
+ observerService->AddObserver(this, kStartupDoneNotification, false);
+}
+
+nsAutoSyncManager::~nsAutoSyncManager() {}
+
+void nsAutoSyncManager::InitTimer() {
+ if (!mTimer) {
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTimer), TimerCallback, (void*)this, kTimerIntervalInMs,
+ nsITimer::TYPE_REPEATING_SLACK, "nsAutoSyncManager::TimerCallback",
+ nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start nsAutoSyncManager timer");
+ }
+ }
+}
+
+void nsAutoSyncManager::StopTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void nsAutoSyncManager::StartTimerIfNeeded() {
+ if ((mUpdateQ.Count() > 0 || mDiscoveryQ.Count() > 0) && !mTimer) InitTimer();
+}
+
+void nsAutoSyncManager::TimerCallback(nsITimer* aTimer, void* aClosure) {
+ if (!aClosure) return;
+
+ nsAutoSyncManager* autoSyncMgr = static_cast<nsAutoSyncManager*>(aClosure);
+ if (autoSyncMgr->GetIdleState() == notIdle ||
+ (autoSyncMgr->mDiscoveryQ.Count() <= 0 &&
+ autoSyncMgr->mUpdateQ.Count() <= 0)) {
+ // Idle will create a new timer automatically if discovery Q or update Q is
+ // not empty
+ autoSyncMgr->StopTimer();
+ }
+
+ // process a folder in the discovery queue
+ if (autoSyncMgr->mDiscoveryQ.Count() > 0) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mDiscoveryQ[0]);
+ if (autoSyncStateObj) {
+ uint32_t leftToProcess;
+ nsresult rv = autoSyncStateObj->ProcessExistingHeaders(
+ kNumberOfHeadersToProcess, &leftToProcess);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(
+ autoSyncMgr, OnDiscoveryQProcessed,
+ (folder, kNumberOfHeadersToProcess, leftToProcess));
+
+ if (NS_SUCCEEDED(rv) && 0 == leftToProcess) {
+ autoSyncMgr->mDiscoveryQ.RemoveObjectAt(0);
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(
+ autoSyncMgr, OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
+ }
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: processed discovery q for folder=%s, "
+ "msgs left to process in folder=%d",
+ __func__, folderName.get(), leftToProcess));
+ }
+ }
+ }
+
+ if (autoSyncMgr->mUpdateQ.Count() > 0) {
+ if (!autoSyncMgr->mUpdateInProgress) // Avoids possible overlap of updates
+ {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mUpdateQ[0]);
+ if (autoSyncStateObj) {
+ int32_t state;
+ nsresult rv = autoSyncStateObj->GetState(&state);
+ if (NS_SUCCEEDED(rv) && (state == nsAutoSyncState::stCompletedIdle ||
+ state == nsAutoSyncState::stUpdateNeeded)) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = imapFolder->InitiateAutoSync(autoSyncMgr);
+ if (NS_SUCCEEDED(rv)) {
+ autoSyncMgr->mUpdateInProgress = true;
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnAutoSyncInitiated,
+ (folder));
+ }
+ }
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: process update q for folder=%s", __func__,
+ folderName.get()));
+ }
+ }
+ }
+ }
+ // if initiation is not successful for some reason, or
+ // if there is an on going download for this folder,
+ // remove it from q and continue with the next one
+ if (!autoSyncMgr->mUpdateInProgress) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncMgr->mUpdateQ[0]->GetOwnerFolder(getter_AddRefs(folder));
+
+ autoSyncMgr->mUpdateQ.RemoveObjectAt(0);
+
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Error)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Error,
+ ("%s: update q init failed for folder=%s", __func__,
+ folderName.get()));
+ }
+ }
+
+ } // endif
+}
+
+/**
+ * Populates aChainedQ with the auto-sync state objects that are not owned by
+ * the same imap server.
+ * Assumes that aChainedQ initially empty.
+ */
+void nsAutoSyncManager::ChainFoldersInQ(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsCOMArray<nsIAutoSyncState>& aChainedQ) {
+ if (aQueue.Count() > 0) aChainedQ.AppendObject(aQueue[0]);
+
+ int32_t pqElemCount = aQueue.Count();
+ for (int32_t pqidx = 1; pqidx < pqElemCount; pqidx++) {
+ bool chained = false;
+ int32_t needToBeReplacedWith = -1;
+ int32_t elemCount = aChainedQ.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ bool isSibling;
+ nsresult rv = aChainedQ[idx]->IsSibling(aQueue[pqidx], &isSibling);
+
+ if (NS_SUCCEEDED(rv) && isSibling) {
+ // this prevent us to overwrite a lower priority sibling in
+ // download-in-progress state with a higher priority one.
+ // we have to wait until its download is completed before
+ // switching to new one.
+ int32_t state;
+ aQueue[pqidx]->GetState(&state);
+ if (aQueue[pqidx] != aChainedQ[idx] &&
+ state == nsAutoSyncState::stDownloadInProgress)
+ needToBeReplacedWith = idx;
+ else
+ chained = true;
+
+ break;
+ }
+ } // endfor
+
+ if (needToBeReplacedWith > -1)
+ aChainedQ.ReplaceObjectAt(aQueue[pqidx], needToBeReplacedWith);
+ else if (!chained)
+ aChainedQ.AppendObject(aQueue[pqidx]);
+
+ } // endfor
+}
+
+/**
+ * Searches the given queue for another folder owned by the same imap server.
+ */
+nsIAutoSyncState* nsAutoSyncManager::SearchQForSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t aStartIdx, int32_t* aIndex) {
+ if (aIndex) *aIndex = -1;
+
+ if (aAutoSyncStateObj) {
+ bool isSibling;
+ int32_t elemCount = aQueue.Count();
+ for (int32_t idx = aStartIdx; idx < elemCount; idx++) {
+ nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
+
+ if (NS_SUCCEEDED(rv) && isSibling && aAutoSyncStateObj != aQueue[idx]) {
+ if (aIndex) *aIndex = idx;
+
+ return aQueue[idx];
+ }
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Searches for the next folder owned by the same imap server in the given
+ * queue, starting from the index of the given folder.
+ */
+nsIAutoSyncState* nsAutoSyncManager::GetNextSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t* aIndex) {
+ if (aIndex) *aIndex = -1;
+
+ if (aAutoSyncStateObj) {
+ bool located = false;
+ bool isSibling;
+ int32_t elemCount = aQueue.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ if (!located) {
+ located = (aAutoSyncStateObj == aQueue[idx]);
+ continue;
+ }
+
+ nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
+ if (NS_SUCCEEDED(rv) && isSibling) {
+ if (aIndex) *aIndex = idx;
+
+ return aQueue[idx];
+ }
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Checks whether there is another folder in the given q that is owned
+ * by the same imap server or not.
+ *
+ * @param aQueue the queue that will be searched for a sibling
+ * @param aAutoSyncStateObj the auto-sync state object that we are looking
+ * a sibling for
+ * @param aState the state of the sibling. -1 means "any state"
+ * @param aIndex [out] the index of the found sibling, if it is provided by the
+ * caller (not null)
+ * @return true if found, false otherwise
+ */
+bool nsAutoSyncManager::DoesQContainAnySiblingOf(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, const int32_t aState,
+ int32_t* aIndex) {
+ if (aState == -1)
+ return (nullptr != SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex));
+
+ int32_t offset = 0;
+ nsIAutoSyncState* autoSyncState;
+ while ((autoSyncState =
+ SearchQForSibling(aQueue, aAutoSyncStateObj, offset, &offset))) {
+ int32_t state;
+ nsresult rv = autoSyncState->GetState(&state);
+ if (NS_SUCCEEDED(rv) && aState == state) break;
+
+ offset++;
+ }
+ if (aIndex) *aIndex = offset;
+
+ return (nullptr != autoSyncState);
+}
+
+/**
+ * Searches the given queue for the highest priority folder owned by the
+ * same imap server.
+ */
+nsIAutoSyncState* nsAutoSyncManager::GetHighestPrioSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t* aIndex) {
+ return SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex);
+}
+
+// to chain update folder actions
+NS_IMETHODIMP nsAutoSyncManager::OnStartRunningUrl(nsIURI* aUrl) {
+ return NS_OK;
+}
+
+/**
+ * This is called when an update folder URL finishes. It is also called by
+ * nsAutoSyncState::OnStopRunningUrl when a folder status URL finishes.
+ */
+NS_IMETHODIMP nsAutoSyncManager::OnStopRunningUrl(nsIURI* aUrl,
+ nsresult aExitCode) {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString uri;
+ if (aUrl) uri = aUrl->GetSpecOrDefault();
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("nsAutoSyncManager::%s, count=%d, url=%s", __func__,
+ mUpdateQ.Count(), uri.get()));
+ }
+ mUpdateInProgress = false; // Set false to allow next folder to update
+ if (mUpdateQ.Count() > 0) mUpdateQ.RemoveObjectAt(0);
+
+ return aExitCode;
+}
+
+/**
+ * This occurs on system sleep, hibernate or when TB is set offline or shutdown.
+ */
+NS_IMETHODIMP nsAutoSyncManager::Pause() {
+ StopTimer();
+ mPaused = true;
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("autosync paused"));
+ return NS_OK;
+}
+
+/**
+ * This occurs on wakeup from sleep or hibernate and when TB is returned online.
+ */
+NS_IMETHODIMP nsAutoSyncManager::Resume() {
+ mPaused = false;
+ StartTimerIfNeeded();
+ // If mUpdateInProgress was true on resume it needs to be reset back to false
+ // to avoid inhibiting autosync until a restart. OnStopRunningUrl(), where it
+ // is normally reset, may not occur depending on timing and autosync will
+ // never be initiated in TimerCallback() for any folder.
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("autosync resumed, mUpdateInProgress=%d(bool)", mUpdateInProgress));
+ mUpdateInProgress = false; // May already be false, that's OK
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::Observe(nsISupports*, const char* aTopic,
+ const char16_t* aSomeData) {
+ if (!PL_strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this, kAppIdleNotification);
+ observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ observerService->RemoveObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC);
+ observerService->RemoveObserver(this, kStartupDoneNotification);
+ }
+
+ // cancel and release the timer
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ // unsubscribe from idle service
+ if (mIdleService) mIdleService->RemoveIdleObserver(this, kIdleTimeInSec);
+
+ return NS_OK;
+ }
+
+ if (!PL_strcmp(aTopic, kStartupDoneNotification)) {
+ mStartupDone = true;
+ } else if (!PL_strcmp(aTopic, kAppIdleNotification)) {
+ if (nsDependentString(aSomeData).EqualsLiteral("idle")) {
+ IdleState prevIdleState = GetIdleState();
+
+ // we were already idle (either system or app), so
+ // just remember that we're app idle and return.
+ SetIdleState(appIdle);
+ if (prevIdleState != notIdle) return NS_OK;
+
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("%s: in app idle", __func__));
+ return StartIdleProcessing();
+ }
+
+ // we're back from appIdle - if already notIdle, just return;
+ if (GetIdleState() == notIdle) return NS_OK;
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("%s: out of app idle", __func__));
+
+ SetIdleState(notIdle);
+ NOTIFY_LISTENERS(OnStateChanged, (false));
+ return NS_OK;
+ } else if (!PL_strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
+ if (nsDependentString(aSomeData).EqualsLiteral(NS_IOSERVICE_ONLINE))
+ Resume();
+ } else if (!PL_strcmp(aTopic, NS_IOSERVICE_GOING_OFFLINE_TOPIC)) {
+ Pause();
+ }
+ // we're back from system idle
+ else if (!PL_strcmp(aTopic, "back")) {
+ // if we're app idle when we get back from system idle, we ignore
+ // it, since we'll keep doing our idle stuff.
+ if (GetIdleState() != appIdle) {
+ SetIdleState(notIdle);
+ NOTIFY_LISTENERS(OnStateChanged, (false));
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("%s: out of idle", __func__));
+ }
+ return NS_OK;
+ } else // we've gone system idle
+ {
+ // Check if we were already idle. We may have gotten
+ // multiple system idle notificatons. In that case,
+ // just remember that we're systemIdle and return;
+ if (GetIdleState() != notIdle) return NS_OK;
+
+ // we might want to remember if we were app idle, because
+ // coming back from system idle while app idle shouldn't stop
+ // app indexing. But I think it's OK for now just leave ourselves
+ // in appIdle state.
+ if (GetIdleState() != appIdle) SetIdleState(systemIdle);
+ if (WeAreOffline()) return NS_OK;
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("%s: in sys idle", __func__));
+ return StartIdleProcessing();
+ }
+ return NS_OK;
+}
+
+nsresult nsAutoSyncManager::StartIdleProcessing() {
+ if (mPaused) return NS_OK;
+
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("enter %s", __func__));
+ StartTimerIfNeeded();
+
+ // Ignore idle events sent during the startup
+ if (!mStartupDone) return NS_OK;
+
+ // notify listeners that auto-sync is running
+ NOTIFY_LISTENERS(OnStateChanged, (true));
+
+ nsCOMArray<nsIAutoSyncState> chainedQ;
+ nsCOMArray<nsIAutoSyncState>* queue = &mPriorityQ;
+ if (mDownloadModel == dmChained) {
+ ChainFoldersInQ(mPriorityQ, chainedQ);
+ queue = &chainedQ;
+ }
+
+ // to store the folders that should be removed from the priority
+ // queue at the end of the iteration.
+ nsCOMArray<nsIAutoSyncState> foldersToBeRemoved;
+
+ // process folders in the priority queue
+ int32_t elemCount = queue->Count();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj((*queue)[idx]);
+ if (!autoSyncStateObj) continue;
+
+ int32_t state;
+ autoSyncStateObj->GetState(&state);
+
+ // TODO: Test cached-connection availability in parallel mode
+ // and do not exceed (cached-connection count - 1)
+
+ if (state != nsAutoSyncState::stReadyToDownload) continue;
+
+ nsresult rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_FAILED(rv)) {
+ // special case: this folder does not have any message to download
+ // (see bug 457342), remove it explicitly from the queue when iteration
+ // is over.
+ // Note that in normal execution flow, folders are removed from priority
+ // queue only in OnDownloadCompleted when all messages are downloaded
+ // successfully. This is the only place we change this flow.
+ if (NS_ERROR_NOT_AVAILABLE == rv)
+ foldersToBeRemoved.AppendObject(autoSyncStateObj);
+
+ HandleDownloadErrorFor(autoSyncStateObj, rv);
+ } // endif
+ } // endfor
+
+ // remove folders with no pending messages from the priority queue
+ elemCount = foldersToBeRemoved.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(foldersToBeRemoved[idx]);
+ if (!autoSyncStateObj) continue;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
+
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder=%s has no pending msgs, "
+ "remove from priority q",
+ __func__, folderName.get()));
+ }
+ autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
+
+ if (mPriorityQ.RemoveObject(autoSyncStateObj))
+ NOTIFY_LISTENERS(OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folder));
+ }
+
+ return AutoUpdateFolders();
+}
+
+/**
+ * Updates offline imap folders that are not synchronized recently. This is
+ * called whenever we're idle.
+ */
+nsresult nsAutoSyncManager::AutoUpdateFolders() {
+ nsresult rv;
+
+ // iterate through each imap account and update offline folders automatically
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("enter %s", __func__));
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgAccount>> accounts;
+ rv = accountManager->GetAccounts(accounts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto account : accounts) {
+ if (!account) continue;
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = account->GetIncomingServer(getter_AddRefs(incomingServer));
+ if (!incomingServer) continue;
+
+ nsCString type;
+ rv = incomingServer->GetType(type);
+
+ if (!type.EqualsLiteral("imap")) continue;
+
+ // If we haven't logged onto this server yet during this session or if the
+ // password has been removed from cache (see
+ // nsImapIncomingServer::ForgetSessionPassword) then skip autosync for
+ // this account.
+ bool notLoggedIn;
+ incomingServer->GetServerRequiresPasswordForBiff(&notLoggedIn);
+ if (notLoggedIn) {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString serverName;
+ incomingServer->GetHostName(serverName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: server |%s| don't autosync; not yet logged in", __func__,
+ serverName.get()));
+ }
+ continue;
+ }
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+
+ rv = incomingServer->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder) {
+ if (NS_FAILED(rv)) continue;
+
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rv = rootFolder->GetDescendants(allDescendants);
+
+ // Get the update time in minutes for each folder of this account/server.
+ // It will be the user configured biff time for server even if user has
+ // disabled "Check for new messages every X minutes" for the account.
+ // Update time will default to 10 minutes if an invalid value is set or
+ // if there are errors obtaining it.
+ // Specifically, the value used here is mail.server.serverX.check_time
+ // or the default mail.server.default.check_time.
+ int32_t updateMinutes = -1;
+ rv = incomingServer->GetBiffMinutes(&updateMinutes);
+ if (NS_FAILED(rv) || updateMinutes < 1)
+ updateMinutes = kDefaultUpdateInterval;
+ PRTime span = updateMinutes * (PR_USEC_PER_SEC * 60UL);
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString serverName;
+ incomingServer->GetHostName(serverName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: Update time set to |%d| minutes for "
+ "folders in account |%s|",
+ __func__, updateMinutes, serverName.get()));
+ }
+
+ for (auto folder : allDescendants) {
+ uint32_t folderFlags;
+ rv = folder->GetFlags(&folderFlags);
+ // Skip this folder if not offline or is a saved search or is no select.
+ if (NS_FAILED(rv) || !(folderFlags & nsMsgFolderFlags::Offline) ||
+ folderFlags &
+ (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect))
+ continue;
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(folder, &rv);
+ if (NS_FAILED(rv)) continue;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = imapFolder->GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer) {
+ bool autoSyncOfflineStores = false;
+ rv = imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
+
+ // skip if AutoSyncOfflineStores pref is not set for this folder
+ if (NS_FAILED(rv) || !autoSyncOfflineStores) continue;
+ }
+
+ nsCOMPtr<nsIAutoSyncState> autoSyncState;
+ rv = imapFolder->GetAutoSyncStateObj(getter_AddRefs(autoSyncState));
+ NS_ASSERTION(
+ autoSyncState,
+ "*** nsAutoSyncState shouldn't be NULL, check owner folder");
+
+ // shouldn't happen but let's be defensive here
+ if (!autoSyncState) continue;
+
+ int32_t state;
+ rv = autoSyncState->GetState(&state);
+ nsCString folderName;
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ folder->GetURI(folderName);
+ MOZ_LOG(
+ gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder=%s, state=%d", __func__, folderName.get(), state));
+ }
+ if (state == nsAutoSyncState::stCompletedIdle ||
+ state == nsAutoSyncState::stUpdateNeeded ||
+ state == nsAutoSyncState::stUpdateIssued) {
+ // Ensure that we wait for at least the "span" time set above before
+ // queuing an update of the same folder.
+ PRTime lastUpdateTime;
+ rv = autoSyncState->GetLastUpdateTime(&lastUpdateTime);
+ if (NS_SUCCEEDED(rv) && ((lastUpdateTime + span) < PR_Now())) {
+ int32_t idx = mUpdateQ.IndexOf(autoSyncState);
+ if (state == nsAutoSyncState::stUpdateIssued) {
+ // Handle the case where an update is triggered but nothing is
+ // found to download. This can happen after messages are copied
+ // or moved between offline folders of the same server or if imap
+ // "folderstatus" URL triggers an update but no new messages
+ // are detected.
+ bool downloadQEmpty;
+ autoSyncState->IsDownloadQEmpty(&downloadQEmpty);
+ if (downloadQEmpty) {
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: nothing to download for folder %s, "
+ "set state to stCompletedIdle, updateQ idx=%d",
+ __func__, folderName.get(), idx));
+ autoSyncState->SetState(nsAutoSyncState::stCompletedIdle);
+
+ // This should already be done by
+ // nsAutoSyncManager::OnStopRunningUrl() but set update state to
+ // completed and remove folder state object from update queue in
+ // case OnStopRunningUrl never occurred.
+ mUpdateInProgress = false;
+ if (idx > -1) {
+ mUpdateQ.RemoveObjectAt(idx);
+ idx = -1; // re-q below
+ }
+ } else {
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: downloadQ not empty. Why? updateQ idx=%d",
+ __func__, idx));
+ if (idx > -1) {
+ // Download q not empty and folder still on update q, maybe it
+ // just needs more time so leave update q as it is to update
+ // on next "span" interval. (Never seen this happen.)
+ idx = 0;
+ }
+ }
+ }
+ // Now q or re-q the update for this folder unless it's still q'd.
+ if (idx < 0) {
+ mUpdateQ.AppendObject(autoSyncState);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder=%s added to update q", __func__,
+ folderName.get()));
+ if (folder)
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+ }
+ }
+
+ // Check if time to add folder to discovery q on kAutoSyncFreq (1 hour)
+ // time base.
+ PRTime lastSyncTime;
+ rv = autoSyncState->GetLastSyncTime(&lastSyncTime);
+ if (NS_SUCCEEDED(rv) && ((lastSyncTime + kAutoSyncFreq) < PR_Now())) {
+ // add this folder into discovery queue to process existing headers
+ // and discover messages not downloaded yet
+ if (mDiscoveryQ.IndexOf(autoSyncState) == -1) {
+ mDiscoveryQ.AppendObject(autoSyncState);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder=%s added to discovery q", __func__,
+ folderName.get()));
+ if (folder)
+ NOTIFY_LISTENERS(
+ OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
+ }
+ }
+ } // endfor
+ } // endif
+ } // endfor
+
+ // lazily create the timer if there is something to process in the queue
+ // when timer is done, it will self destruct
+ StartTimerIfNeeded();
+
+ return rv;
+}
+
+/**
+ * Places the given folder into the priority queue based on active
+ * strategy function.
+ */
+void nsAutoSyncManager::ScheduleFolderForOfflineDownload(
+ nsIAutoSyncState* aAutoSyncStateObj) {
+ if (aAutoSyncStateObj && (mPriorityQ.IndexOf(aAutoSyncStateObj) == -1)) {
+ nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
+ GetFolderStrategy(getter_AddRefs(folStrategy));
+
+ if (mPriorityQ.Count() <= 0) {
+ // make sure that we don't insert a folder excluded by the given strategy
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) {
+ bool excluded = false;
+ if (folStrategy) folStrategy->IsExcluded(folder, &excluded);
+
+ if (!excluded) {
+ mPriorityQ.AppendObject(
+ aAutoSyncStateObj); // insert into the first spot
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folder));
+ }
+ }
+ } else {
+ // find the right spot for the given folder
+ uint32_t qidx = mPriorityQ.Count();
+ while (qidx > 0) {
+ --qidx;
+
+ nsCOMPtr<nsIMsgFolder> folderA, folderB;
+ mPriorityQ[qidx]->GetOwnerFolder(getter_AddRefs(folderA));
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folderB));
+
+ bool excluded = false;
+ if (folderB && folStrategy) folStrategy->IsExcluded(folderB, &excluded);
+
+ if (excluded) break;
+
+ nsAutoSyncStrategyDecisionType decision =
+ nsAutoSyncStrategyDecisions::Same;
+ if (folderA && folderB && folStrategy)
+ folStrategy->Sort(folderA, folderB, &decision);
+
+ if (decision == nsAutoSyncStrategyDecisions::Higher && 0 == qidx)
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, 0);
+ else if (decision == nsAutoSyncStrategyDecisions::Higher)
+ continue;
+ else if (decision == nsAutoSyncStrategyDecisions::Lower)
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx + 1);
+ else // decision == nsAutoSyncStrategyDecisions::Same
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx);
+
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folderB));
+ break;
+ } // end while
+ }
+ } // endif
+}
+
+/**
+ * Zero aSizeLimit means no limit
+ */
+nsresult nsAutoSyncManager::DownloadMessagesForOffline(
+ nsIAutoSyncState* aAutoSyncStateObj, uint32_t aSizeLimit) {
+ if (!aAutoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ int32_t count;
+ nsresult rv = aAutoSyncStateObj->GetPendingMessageCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // special case: no more message to download for this folder:
+ // see HandleDownloadErrorFor for recovery policy
+ if (!count) return NS_ERROR_NOT_AVAILABLE;
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> messagesToDownload;
+ uint32_t totalSize = 0;
+ rv = aAutoSyncStateObj->GetNextGroupOfMessages(mGroupSize, &totalSize,
+ messagesToDownload);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // there are pending messages but the cumulative size is zero:
+ // treat as special case.
+ // Note that although it shouldn't happen, we know that sometimes
+ // imap servers manifest messages as zero length. By returning
+ // NS_ERROR_NOT_AVAILABLE we cause this folder to be removed from
+ // the priority queue temporarily (until the next idle or next update)
+ // in an effort to prevent it blocking other folders of the same account
+ // being synced.
+ if (!totalSize) return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure that we don't exceed the given size limit for this particular group
+ if (aSizeLimit && aSizeLimit < totalSize) return NS_ERROR_FAILURE;
+
+ if (!messagesToDownload.IsEmpty()) {
+ rv = aAutoSyncStateObj->DownloadMessagesForOffline(messagesToDownload);
+
+ int32_t totalCount;
+ (void)aAutoSyncStateObj->GetTotalMessageCount(&totalCount);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ NOTIFY_LISTENERS(OnDownloadStarted,
+ (folder, messagesToDownload.Length(), totalCount));
+ }
+
+ return rv;
+}
+
+// clang-format off
+/**
+ * Assuming that the download operation on the given folder has been failed at
+ * least once, execute these steps:
+ * - put the auto-sync state into ready-to-download mode
+ * - rollback the message offset so we can try the same group again (unless the
+ * retry count is reached to the given limit)
+ * - if parallel model is active, wait to be resumed by the next idle
+ * - if chained model is active, search the priority queue to find a sibling to
+ * continue with.
+ */
+// clang-format on
+nsresult nsAutoSyncManager::HandleDownloadErrorFor(
+ nsIAutoSyncState* aAutoSyncStateObj, const nsresult error) {
+ if (!aAutoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ // ensure that an error occurred
+ if (NS_SUCCEEDED(error)) return NS_OK;
+
+ // NS_ERROR_NOT_AVAILABLE is a special case/error happens when the queued
+ // folder doesn't have any message to download (see bug 457342). In such case
+ // we shouldn't retry the current message group, nor notify listeners. Simply
+ // continuing with the next sibling in the priority queue would suffice.
+
+ if (NS_ERROR_NOT_AVAILABLE != error) {
+ // force the auto-sync state to try downloading the same group at least
+ // kGroupRetryCount times before it moves to the next one
+ aAutoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) NOTIFY_LISTENERS(OnDownloadError, (folder));
+ }
+
+ // if parallel model, don't do anything else
+
+ if (mDownloadModel == dmChained) {
+ // switch to the next folder in the chain and continue downloading
+ nsIAutoSyncState* autoSyncStateObj = aAutoSyncStateObj;
+ nsIAutoSyncState* nextAutoSyncStateObj = nullptr;
+ while (
+ (nextAutoSyncStateObj = GetNextSibling(mPriorityQ, autoSyncStateObj))) {
+ autoSyncStateObj = nextAutoSyncStateObj;
+ nsresult rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_SUCCEEDED(rv)) break;
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ // next folder in the chain also doesn't have any message to download
+ // switch to next one if any
+ continue;
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetGroupSize(uint32_t* aGroupSize) {
+ NS_ENSURE_ARG_POINTER(aGroupSize);
+ *aGroupSize = mGroupSize;
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetGroupSize(uint32_t aGroupSize) {
+ mGroupSize = aGroupSize ? aGroupSize : kDefaultGroupSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetMsgStrategy(
+ nsIAutoSyncMsgStrategy** aMsgStrategy) {
+ NS_ENSURE_ARG_POINTER(aMsgStrategy);
+
+ // lazily create if it is not done already
+ if (!mMsgStrategyImpl) {
+ mMsgStrategyImpl = new nsDefaultAutoSyncMsgStrategy;
+ if (!mMsgStrategyImpl) return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_IF_ADDREF(*aMsgStrategy = mMsgStrategyImpl);
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetMsgStrategy(
+ nsIAutoSyncMsgStrategy* aMsgStrategy) {
+ mMsgStrategyImpl = aMsgStrategy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetFolderStrategy(
+ nsIAutoSyncFolderStrategy** aFolderStrategy) {
+ NS_ENSURE_ARG_POINTER(aFolderStrategy);
+
+ // lazily create if it is not done already
+ if (!mFolderStrategyImpl) {
+ mFolderStrategyImpl = new nsDefaultAutoSyncFolderStrategy;
+ if (!mFolderStrategyImpl) return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_IF_ADDREF(*aFolderStrategy = mFolderStrategyImpl);
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetFolderStrategy(
+ nsIAutoSyncFolderStrategy* aFolderStrategy) {
+ mFolderStrategyImpl = aFolderStrategy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::DoesMsgFitDownloadCriteria(nsIMsgDBHdr* aMsgHdr,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ uint32_t msgFlags = 0;
+ aMsgHdr->GetFlags(&msgFlags);
+
+ // check whether this message is marked imap deleted or not
+ *aResult = !(msgFlags & nsMsgMessageFlags::IMAPDeleted);
+ if (!(*aResult)) return NS_OK;
+
+ bool shouldStoreMsgOffline = true;
+ nsCOMPtr<nsIMsgFolder> folder;
+ aMsgHdr->GetFolder(getter_AddRefs(folder));
+ if (folder) {
+ nsMsgKey msgKey;
+ nsresult rv = aMsgHdr->GetMessageKey(&msgKey);
+ // a cheap way to get the size limit for this folder and make
+ // sure that we don't have this message offline already
+ if (NS_SUCCEEDED(rv))
+ folder->ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
+ }
+
+ *aResult &= shouldStoreMsgOffline;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::OnDownloadQChanged(
+ nsIAutoSyncState* aAutoSyncStateObj) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ if (mPaused) return NS_OK;
+ // We want to start downloading immediately unless the folder is excluded.
+ bool excluded = false;
+ nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ GetFolderStrategy(getter_AddRefs(folStrategy));
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+
+ if (folder && folStrategy) folStrategy->IsExcluded(folder, &excluded);
+
+ nsresult rv = NS_OK;
+
+ if (!excluded) {
+ // Add this folder into the priority queue.
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+ ScheduleFolderForOfflineDownload(autoSyncStateObj);
+
+ // If we operate in parallel mode or if there is no sibling downloading
+ // messages at the moment, we can download the first group of the messages
+ // for this folder
+ if (mDownloadModel == dmParallel ||
+ !DoesQContainAnySiblingOf(mPriorityQ, autoSyncStateObj,
+ nsAutoSyncState::stDownloadInProgress)) {
+ // this will download the first group of messages immediately;
+ // to ensure that we don't end up downloading a large single message in
+ // not-idle time, we enforce a limit. If there is no message fits into
+ // this limit we postpone the download until the next idle.
+ if (GetIdleState() == notIdle)
+ rv = DownloadMessagesForOffline(autoSyncStateObj, kFirstGroupSizeLimit);
+ else
+ rv = DownloadMessagesForOffline(autoSyncStateObj);
+
+ if (NS_FAILED(rv))
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnDownloadStarted(nsIAutoSyncState* aAutoSyncStateObj,
+ nsresult aStartCode) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ // resume downloads during next idle time
+ if (NS_FAILED(aStartCode))
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+
+ return aStartCode;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnDownloadCompleted(nsIAutoSyncState* aAutoSyncStateObj,
+ nsresult aExitCode) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = aExitCode;
+
+ if (NS_FAILED(aExitCode)) {
+ // retry the same group kGroupRetryCount times
+ // try again if TB still idle, otherwise wait for the next idle time
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ if (GetIdleState() != notIdle) {
+ rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_FAILED(rv)) rv = HandleDownloadErrorFor(autoSyncStateObj, rv);
+ }
+ return rv;
+ }
+
+ // download is successful, reset the retry counter of the folder
+ autoSyncStateObj->ResetRetryCounter();
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
+
+ int32_t count;
+ rv = autoSyncStateObj->GetPendingMessageCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIAutoSyncState* nextFolderToDownload = nullptr;
+ if (count > 0) {
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+
+ // in parallel model, we continue downloading the same folder as long as it
+ // has more pending messages
+ nextFolderToDownload = autoSyncStateObj;
+
+ // in chained model, ensure that we are always downloading the highest
+ // priority folder first
+ if (mDownloadModel == dmChained) {
+ // switch to higher priority folder and continue to download,
+ // if any added recently
+ int32_t myIndex = mPriorityQ.IndexOf(autoSyncStateObj);
+
+ int32_t siblingIndex;
+ nsIAutoSyncState* sibling =
+ GetHighestPrioSibling(mPriorityQ, autoSyncStateObj, &siblingIndex);
+
+ // lesser index = higher priority
+ if (sibling && myIndex > -1 && siblingIndex < myIndex)
+ nextFolderToDownload = sibling;
+ }
+ } else {
+ autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+
+ if (NS_SUCCEEDED(rv) && mPriorityQ.RemoveObject(autoSyncStateObj))
+ NOTIFY_LISTENERS(OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folder));
+
+ // find the next folder owned by the same server in the queue and continue
+ // downloading
+ if (mDownloadModel == dmChained)
+ nextFolderToDownload =
+ GetHighestPrioSibling(mPriorityQ, autoSyncStateObj);
+
+ } // endif
+
+ // continue downloading if TB is still in idle state
+ if (nextFolderToDownload && GetIdleState() != notIdle) {
+ rv = DownloadMessagesForOffline(nextFolderToDownload);
+ if (NS_FAILED(rv)) rv = HandleDownloadErrorFor(nextFolderToDownload, rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetDownloadModel(int32_t* aDownloadModel) {
+ NS_ENSURE_ARG_POINTER(aDownloadModel);
+ *aDownloadModel = mDownloadModel;
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetDownloadModel(int32_t aDownloadModel) {
+ mDownloadModel = aDownloadModel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::AddListener(
+ nsIAutoSyncMgrListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListeners.AppendElementUnlessExists(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::RemoveListener(
+ nsIAutoSyncMgrListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long discoveryQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetDiscoveryQLength(
+ uint32_t* aDiscoveryQLength) {
+ NS_ENSURE_ARG_POINTER(aDiscoveryQLength);
+ *aDiscoveryQLength = mDiscoveryQ.Count();
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long uploadQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetUpdateQLength(uint32_t* aUpdateQLength) {
+ NS_ENSURE_ARG_POINTER(aUpdateQLength);
+ *aUpdateQLength = mUpdateQ.Count();
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long downloadQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetDownloadQLength(
+ uint32_t* aDownloadQLength) {
+ NS_ENSURE_ARG_POINTER(aDownloadQLength);
+ *aDownloadQLength = mPriorityQ.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnFolderHasPendingMsgs(nsIAutoSyncState* aAutoSyncStateObj) {
+ NS_ENSURE_ARG_POINTER(aAutoSyncStateObj);
+ if (mUpdateQ.IndexOf(aAutoSyncStateObj) == -1) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ // If this folder isn't the trash, add it to the update q.
+ if (folder) {
+ bool isTrash;
+ folder->GetFlag(nsMsgFolderFlags::Trash, &isTrash);
+ if (!isTrash) {
+ bool isSentOrArchive;
+ folder->IsSpecialFolder(
+ nsMsgFolderFlags::SentMail | nsMsgFolderFlags::Archive, true,
+ &isSentOrArchive);
+ // Sent or archive folders go to the q front, the rest to the end.
+ if (isSentOrArchive)
+ mUpdateQ.InsertObjectAt(aAutoSyncStateObj, 0);
+ else
+ mUpdateQ.AppendObject(aAutoSyncStateObj);
+ aAutoSyncStateObj->SetState(nsAutoSyncState::stUpdateNeeded);
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void nsAutoSyncManager::SetIdleState(IdleState st) { mIdleState = st; }
+
+nsAutoSyncManager::IdleState nsAutoSyncManager::GetIdleState() const {
+ return mIdleState;
+}
+
+NS_IMPL_ISUPPORTS(nsAutoSyncManager, nsIObserver, nsIUrlListener,
+ nsIAutoSyncManager)