diff options
Diffstat (limited to 'comm/mailnews/imap/src/nsAutoSyncManager.cpp')
-rw-r--r-- | comm/mailnews/imap/src/nsAutoSyncManager.cpp | 1372 |
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(¬LoggedIn); + 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) |