1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "nsAutoSyncManager.h"
6 #include "nsAutoSyncState.h"
7 #include "nsIMsgImapMailFolder.h"
8 #include "nsIMsgHdr.h"
9 #include "nsMsgImapCID.h"
10 #include "nsIObserverService.h"
11 #include "nsIMsgAccountManager.h"
12 #include "nsIMsgIncomingServer.h"
13 #include "nsIImapIncomingServer.h"
14 #include "nsIMsgMailSession.h"
15 #include "nsMsgFolderFlags.h"
16 #include "nsMsgMessageFlags.h"
17 #include "nsMsgUtils.h"
18 #include "nsIIOService.h"
19 #include "nsComponentManagerUtils.h"
20 #include "nsServiceManagerUtils.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/Logging.h"
23 
24 using namespace mozilla;
25 
26 NS_IMPL_ISUPPORTS(nsDefaultAutoSyncMsgStrategy, nsIAutoSyncMsgStrategy)
27 
28 const char* kAppIdleNotification = "mail:appIdle";
29 const char* kStartupDoneNotification = "mail-startup-done";
30 LazyLogModule gAutoSyncLog("IMAPAutoSync");
31 
32 // recommended size of each group of messages per download
33 static const uint32_t kDefaultGroupSize = 50U * 1024U /* 50K */;
34 
nsDefaultAutoSyncMsgStrategy()35 nsDefaultAutoSyncMsgStrategy::nsDefaultAutoSyncMsgStrategy() {}
36 
~nsDefaultAutoSyncMsgStrategy()37 nsDefaultAutoSyncMsgStrategy::~nsDefaultAutoSyncMsgStrategy() {}
38 
Sort(nsIMsgFolder * aFolder,nsIMsgDBHdr * aMsgHdr1,nsIMsgDBHdr * aMsgHdr2,nsAutoSyncStrategyDecisionType * aDecision)39 NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::Sort(
40     nsIMsgFolder* aFolder, nsIMsgDBHdr* aMsgHdr1, nsIMsgDBHdr* aMsgHdr2,
41     nsAutoSyncStrategyDecisionType* aDecision) {
42   NS_ENSURE_ARG_POINTER(aDecision);
43 
44   uint32_t msgSize1 = 0, msgSize2 = 0;
45   PRTime msgDate1 = 0, msgDate2 = 0;
46 
47   if (!aMsgHdr1 || !aMsgHdr2) {
48     *aDecision = nsAutoSyncStrategyDecisions::Same;
49     return NS_OK;
50   }
51 
52   aMsgHdr1->GetMessageSize(&msgSize1);
53   aMsgHdr1->GetDate(&msgDate1);
54 
55   aMsgHdr2->GetMessageSize(&msgSize2);
56   aMsgHdr2->GetDate(&msgDate2);
57 
58   // Special case: if message size is larger than a
59   // certain size, then place it to the bottom of the q
60   if (msgSize2 > kFirstPassMessageSize && msgSize1 > kFirstPassMessageSize)
61     *aDecision = msgSize2 > msgSize1 ? nsAutoSyncStrategyDecisions::Lower
62                                      : nsAutoSyncStrategyDecisions::Higher;
63   else if (msgSize2 > kFirstPassMessageSize)
64     *aDecision = nsAutoSyncStrategyDecisions::Lower;
65   else if (msgSize1 > kFirstPassMessageSize)
66     *aDecision = nsAutoSyncStrategyDecisions::Higher;
67   else {
68     // Most recent and smallest first
69     if (msgDate1 < msgDate2)
70       *aDecision = nsAutoSyncStrategyDecisions::Higher;
71     else if (msgDate1 > msgDate2)
72       *aDecision = nsAutoSyncStrategyDecisions::Lower;
73     else {
74       if (msgSize1 > msgSize2)
75         *aDecision = nsAutoSyncStrategyDecisions::Higher;
76       else if (msgSize1 < msgSize2)
77         *aDecision = nsAutoSyncStrategyDecisions::Lower;
78       else
79         *aDecision = nsAutoSyncStrategyDecisions::Same;
80     }
81   }
82   return NS_OK;
83 }
84 
IsExcluded(nsIMsgFolder * aFolder,nsIMsgDBHdr * aMsgHdr,bool * aDecision)85 NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::IsExcluded(nsIMsgFolder* aFolder,
86                                                        nsIMsgDBHdr* aMsgHdr,
87                                                        bool* aDecision) {
88   NS_ENSURE_ARG_POINTER(aDecision);
89   NS_ENSURE_ARG_POINTER(aMsgHdr);
90   NS_ENSURE_ARG_POINTER(aFolder);
91   nsCOMPtr<nsIMsgIncomingServer> server;
92 
93   nsresult rv = aFolder->GetServer(getter_AddRefs(server));
94   NS_ENSURE_SUCCESS(rv, rv);
95   nsCOMPtr<nsIImapIncomingServer> imapServer(do_QueryInterface(server, &rv));
96   int32_t offlineMsgAgeLimit = -1;
97   imapServer->GetAutoSyncMaxAgeDays(&offlineMsgAgeLimit);
98   NS_ENSURE_SUCCESS(rv, rv);
99   PRTime msgDate;
100   aMsgHdr->GetDate(&msgDate);
101   *aDecision = offlineMsgAgeLimit > 0 &&
102                msgDate < MsgConvertAgeInDaysToCutoffDate(offlineMsgAgeLimit);
103   return NS_OK;
104 }
105 
NS_IMPL_ISUPPORTS(nsDefaultAutoSyncFolderStrategy,nsIAutoSyncFolderStrategy)106 NS_IMPL_ISUPPORTS(nsDefaultAutoSyncFolderStrategy, nsIAutoSyncFolderStrategy)
107 
108 nsDefaultAutoSyncFolderStrategy::nsDefaultAutoSyncFolderStrategy() {}
109 
~nsDefaultAutoSyncFolderStrategy()110 nsDefaultAutoSyncFolderStrategy::~nsDefaultAutoSyncFolderStrategy() {}
111 
Sort(nsIMsgFolder * aFolderA,nsIMsgFolder * aFolderB,nsAutoSyncStrategyDecisionType * aDecision)112 NS_IMETHODIMP nsDefaultAutoSyncFolderStrategy::Sort(
113     nsIMsgFolder* aFolderA, nsIMsgFolder* aFolderB,
114     nsAutoSyncStrategyDecisionType* aDecision) {
115   NS_ENSURE_ARG_POINTER(aDecision);
116 
117   if (!aFolderA || !aFolderB) {
118     *aDecision = nsAutoSyncStrategyDecisions::Same;
119     return NS_OK;
120   }
121 
122   bool isInbox1, isInbox2, isDrafts1, isDrafts2, isTrash1, isTrash2;
123   aFolderA->GetFlag(nsMsgFolderFlags::Inbox, &isInbox1);
124   aFolderB->GetFlag(nsMsgFolderFlags::Inbox, &isInbox2);
125   //
126   aFolderA->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts1);
127   aFolderB->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts2);
128   //
129   aFolderA->GetFlag(nsMsgFolderFlags::Trash, &isTrash1);
130   aFolderB->GetFlag(nsMsgFolderFlags::Trash, &isTrash2);
131 
132   // Follow this order;
133   // INBOX > DRAFTS > SUBFOLDERS > TRASH
134 
135   // test whether the folder is opened by the user.
136   // we give high priority to the folders explicitly opened by
137   // the user.
138   nsresult rv;
139   bool folderAOpen = false;
140   bool folderBOpen = false;
141   nsCOMPtr<nsIMsgMailSession> session =
142       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
143   if (NS_SUCCEEDED(rv) && session) {
144     session->IsFolderOpenInWindow(aFolderA, &folderAOpen);
145     session->IsFolderOpenInWindow(aFolderB, &folderBOpen);
146   }
147 
148   if (folderAOpen == folderBOpen) {
149     // if both of them or none of them are opened by the user
150     // make your decision based on the folder type
151     if (isInbox2 || (isDrafts2 && !isInbox1) || isTrash1)
152       *aDecision = nsAutoSyncStrategyDecisions::Higher;
153     else if (isInbox1 || (isDrafts1 && !isDrafts2) || isTrash2)
154       *aDecision = nsAutoSyncStrategyDecisions::Lower;
155     else
156       *aDecision = nsAutoSyncStrategyDecisions::Same;
157   } else {
158     // otherwise give higher priority to opened one
159     *aDecision = folderBOpen ? nsAutoSyncStrategyDecisions::Higher
160                              : nsAutoSyncStrategyDecisions::Lower;
161   }
162 
163   return NS_OK;
164 }
165 
166 NS_IMETHODIMP
IsExcluded(nsIMsgFolder * aFolder,bool * aDecision)167 nsDefaultAutoSyncFolderStrategy::IsExcluded(nsIMsgFolder* aFolder,
168                                             bool* aDecision) {
169   NS_ENSURE_ARG_POINTER(aDecision);
170   NS_ENSURE_ARG_POINTER(aFolder);
171   uint32_t folderFlags;
172   aFolder->GetFlags(&folderFlags);
173   // exclude saved search
174   *aDecision = (folderFlags & nsMsgFolderFlags::Virtual);
175   if (!*aDecision) {
176     // Exclude orphans
177     nsCOMPtr<nsIMsgFolder> parent;
178     aFolder->GetParent(getter_AddRefs(parent));
179     if (!parent) *aDecision = true;
180   }
181   return NS_OK;
182 }
183 
184 #define NOTIFY_LISTENERS_STATIC(obj_, propertyfunc_, params_)               \
185   PR_BEGIN_MACRO                                                            \
186   nsTObserverArray<nsCOMPtr<nsIAutoSyncMgrListener>>::ForwardIterator iter( \
187       obj_->mListeners);                                                    \
188   nsCOMPtr<nsIAutoSyncMgrListener> listener;                                \
189   while (iter.HasMore()) {                                                  \
190     listener = iter.GetNext();                                              \
191     listener->propertyfunc_ params_;                                        \
192   }                                                                         \
193   PR_END_MACRO
194 
195 #define NOTIFY_LISTENERS(propertyfunc_, params_) \
196   NOTIFY_LISTENERS_STATIC(this, propertyfunc_, params_)
197 
nsAutoSyncManager()198 nsAutoSyncManager::nsAutoSyncManager() {
199   mGroupSize = kDefaultGroupSize;
200 
201   mIdleState = notIdle;
202   mStartupDone = false;
203   mDownloadModel = dmChained;
204   mUpdateState = completed;
205   mPaused = false;
206 
207   nsresult rv;
208   mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1", &rv);
209   if (mIdleService) mIdleService->AddIdleObserver(this, kIdleTimeInSec);
210 
211   // Observe xpcom-shutdown event and app-idle changes
212   nsCOMPtr<nsIObserverService> observerService =
213       mozilla::services::GetObserverService();
214 
215   rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
216   observerService->AddObserver(this, kAppIdleNotification, false);
217   observerService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
218   observerService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, false);
219   observerService->AddObserver(this, kStartupDoneNotification, false);
220 }
221 
~nsAutoSyncManager()222 nsAutoSyncManager::~nsAutoSyncManager() {}
223 
InitTimer()224 void nsAutoSyncManager::InitTimer() {
225   if (!mTimer) {
226     nsresult rv;
227     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
228     NS_ASSERTION(NS_SUCCEEDED(rv),
229                  "failed to create timer in nsAutoSyncManager");
230 
231     mTimer->InitWithNamedFuncCallback(
232         TimerCallback, (void*)this, kTimerIntervalInMs,
233         nsITimer::TYPE_REPEATING_SLACK, "nsAutoSyncManager::TimerCallback");
234   }
235 }
236 
StopTimer()237 void nsAutoSyncManager::StopTimer() {
238   if (mTimer) {
239     mTimer->Cancel();
240     mTimer = nullptr;
241   }
242 }
243 
StartTimerIfNeeded()244 void nsAutoSyncManager::StartTimerIfNeeded() {
245   if ((mUpdateQ.Count() > 0 || mDiscoveryQ.Count() > 0) && !mTimer) InitTimer();
246 }
247 
TimerCallback(nsITimer * aTimer,void * aClosure)248 void nsAutoSyncManager::TimerCallback(nsITimer* aTimer, void* aClosure) {
249   if (!aClosure) return;
250 
251   nsAutoSyncManager* autoSyncMgr = static_cast<nsAutoSyncManager*>(aClosure);
252   if (autoSyncMgr->GetIdleState() == notIdle ||
253       (autoSyncMgr->mDiscoveryQ.Count() <= 0 &&
254        autoSyncMgr->mUpdateQ.Count() <= 0)) {
255     // Idle will create a new timer automatically if discovery Q or update Q is
256     // not empty
257     autoSyncMgr->StopTimer();
258   }
259 
260   // process folders within the discovery queue
261   if (autoSyncMgr->mDiscoveryQ.Count() > 0) {
262     nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mDiscoveryQ[0]);
263     if (autoSyncStateObj) {
264       uint32_t leftToProcess;
265       nsresult rv = autoSyncStateObj->ProcessExistingHeaders(
266           kNumberOfHeadersToProcess, &leftToProcess);
267 
268       nsCOMPtr<nsIMsgFolder> folder;
269       autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
270       if (folder)
271         NOTIFY_LISTENERS_STATIC(
272             autoSyncMgr, OnDiscoveryQProcessed,
273             (folder, kNumberOfHeadersToProcess, leftToProcess));
274 
275       if (NS_SUCCEEDED(rv) && 0 == leftToProcess) {
276         autoSyncMgr->mDiscoveryQ.RemoveObjectAt(0);
277         if (folder)
278           NOTIFY_LISTENERS_STATIC(
279               autoSyncMgr, OnFolderRemovedFromQ,
280               (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
281       }
282     }
283   }
284 
285   if (autoSyncMgr->mUpdateQ.Count() > 0) {
286     if (autoSyncMgr->mUpdateState == completed) {
287       nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mUpdateQ[0]);
288       if (autoSyncStateObj) {
289         int32_t state;
290         nsresult rv = autoSyncStateObj->GetState(&state);
291         if (NS_SUCCEEDED(rv) && (state == nsAutoSyncState::stCompletedIdle ||
292                                  state == nsAutoSyncState::stUpdateNeeded)) {
293           nsCOMPtr<nsIMsgFolder> folder;
294           autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
295           if (folder) {
296             nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
297                 do_QueryInterface(folder, &rv);
298             NS_ENSURE_SUCCESS_VOID(rv);
299             rv = imapFolder->InitiateAutoSync(autoSyncMgr);
300             if (NS_SUCCEEDED(rv)) {
301               autoSyncMgr->mUpdateState = initiated;
302               NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnAutoSyncInitiated,
303                                       (folder));
304             }
305           }
306         }
307       }
308     }
309     // if initiation is not successful for some reason, or
310     // if there is an on going download for this folder,
311     // remove it from q and continue with the next one
312     if (autoSyncMgr->mUpdateState != initiated) {
313       nsCOMPtr<nsIMsgFolder> folder;
314       autoSyncMgr->mUpdateQ[0]->GetOwnerFolder(getter_AddRefs(folder));
315 
316       autoSyncMgr->mUpdateQ.RemoveObjectAt(0);
317 
318       if (folder)
319         NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnFolderRemovedFromQ,
320                                 (nsIAutoSyncMgrListener::UpdateQueue, folder));
321     }
322 
323   }  // endif
324 }
325 
326 /**
327  * Populates aChainedQ with the auto-sync state objects that are not owned by
328  * the same imap server.
329  * Assumes that aChainedQ initially empty.
330  */
ChainFoldersInQ(const nsCOMArray<nsIAutoSyncState> & aQueue,nsCOMArray<nsIAutoSyncState> & aChainedQ)331 void nsAutoSyncManager::ChainFoldersInQ(
332     const nsCOMArray<nsIAutoSyncState>& aQueue,
333     nsCOMArray<nsIAutoSyncState>& aChainedQ) {
334   if (aQueue.Count() > 0) aChainedQ.AppendObject(aQueue[0]);
335 
336   int32_t pqElemCount = aQueue.Count();
337   for (int32_t pqidx = 1; pqidx < pqElemCount; pqidx++) {
338     bool chained = false;
339     int32_t needToBeReplacedWith = -1;
340     int32_t elemCount = aChainedQ.Count();
341     for (int32_t idx = 0; idx < elemCount; idx++) {
342       bool isSibling;
343       nsresult rv = aChainedQ[idx]->IsSibling(aQueue[pqidx], &isSibling);
344 
345       if (NS_SUCCEEDED(rv) && isSibling) {
346         // this prevent us to overwrite a lower priority sibling in
347         // download-in-progress state with a higher priority one.
348         // we have to wait until its download is completed before
349         // switching to new one.
350         int32_t state;
351         aQueue[pqidx]->GetState(&state);
352         if (aQueue[pqidx] != aChainedQ[idx] &&
353             state == nsAutoSyncState::stDownloadInProgress)
354           needToBeReplacedWith = idx;
355         else
356           chained = true;
357 
358         break;
359       }
360     }  // endfor
361 
362     if (needToBeReplacedWith > -1)
363       aChainedQ.ReplaceObjectAt(aQueue[pqidx], needToBeReplacedWith);
364     else if (!chained)
365       aChainedQ.AppendObject(aQueue[pqidx]);
366 
367   }  // endfor
368 }
369 
370 /**
371  * Searches the given queue for another folder owned by the same imap server.
372  */
SearchQForSibling(const nsCOMArray<nsIAutoSyncState> & aQueue,nsIAutoSyncState * aAutoSyncStateObj,int32_t aStartIdx,int32_t * aIndex)373 nsIAutoSyncState* nsAutoSyncManager::SearchQForSibling(
374     const nsCOMArray<nsIAutoSyncState>& aQueue,
375     nsIAutoSyncState* aAutoSyncStateObj, int32_t aStartIdx, int32_t* aIndex) {
376   if (aIndex) *aIndex = -1;
377 
378   if (aAutoSyncStateObj) {
379     bool isSibling;
380     int32_t elemCount = aQueue.Count();
381     for (int32_t idx = aStartIdx; idx < elemCount; idx++) {
382       nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
383 
384       if (NS_SUCCEEDED(rv) && isSibling && aAutoSyncStateObj != aQueue[idx]) {
385         if (aIndex) *aIndex = idx;
386 
387         return aQueue[idx];
388       }
389     }
390   }
391   return nullptr;
392 }
393 
394 /**
395  * Searches for the next folder owned by the same imap server in the given
396  * queue, starting from the index of the given folder.
397  */
GetNextSibling(const nsCOMArray<nsIAutoSyncState> & aQueue,nsIAutoSyncState * aAutoSyncStateObj,int32_t * aIndex)398 nsIAutoSyncState* nsAutoSyncManager::GetNextSibling(
399     const nsCOMArray<nsIAutoSyncState>& aQueue,
400     nsIAutoSyncState* aAutoSyncStateObj, int32_t* aIndex) {
401   if (aIndex) *aIndex = -1;
402 
403   if (aAutoSyncStateObj) {
404     bool located = false;
405     bool isSibling;
406     int32_t elemCount = aQueue.Count();
407     for (int32_t idx = 0; idx < elemCount; idx++) {
408       if (!located) {
409         located = (aAutoSyncStateObj == aQueue[idx]);
410         continue;
411       }
412 
413       nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
414       if (NS_SUCCEEDED(rv) && isSibling) {
415         if (aIndex) *aIndex = idx;
416 
417         return aQueue[idx];
418       }
419     }
420   }
421   return nullptr;
422 }
423 
424 /**
425  * Checks whether there is another folder in the given q that is owned
426  * by the same imap server or not.
427  *
428  * @param aQueue the queue that will be searched for a sibling
429  * @param aAutoSyncStateObj the auto-sync state object that we are looking
430  *                          a sibling for
431  * @param aState the state of the sibling. -1 means "any state"
432  * @param aIndex [out] the index of the found sibling, if it is provided by the
433  *               caller (not null)
434  * @return true if found, false otherwise
435  */
DoesQContainAnySiblingOf(const nsCOMArray<nsIAutoSyncState> & aQueue,nsIAutoSyncState * aAutoSyncStateObj,const int32_t aState,int32_t * aIndex)436 bool nsAutoSyncManager::DoesQContainAnySiblingOf(
437     const nsCOMArray<nsIAutoSyncState>& aQueue,
438     nsIAutoSyncState* aAutoSyncStateObj, const int32_t aState,
439     int32_t* aIndex) {
440   if (aState == -1)
441     return (nullptr != SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex));
442 
443   int32_t offset = 0;
444   nsIAutoSyncState* autoSyncState;
445   while ((autoSyncState =
446               SearchQForSibling(aQueue, aAutoSyncStateObj, offset, &offset))) {
447     int32_t state;
448     nsresult rv = autoSyncState->GetState(&state);
449     if (NS_SUCCEEDED(rv) && aState == state) break;
450 
451     offset++;
452   }
453   if (aIndex) *aIndex = offset;
454 
455   return (nullptr != autoSyncState);
456 }
457 
458 /**
459  * Searches the given queue for the highest priority folder owned by the
460  * same imap server.
461  */
GetHighestPrioSibling(const nsCOMArray<nsIAutoSyncState> & aQueue,nsIAutoSyncState * aAutoSyncStateObj,int32_t * aIndex)462 nsIAutoSyncState* nsAutoSyncManager::GetHighestPrioSibling(
463     const nsCOMArray<nsIAutoSyncState>& aQueue,
464     nsIAutoSyncState* aAutoSyncStateObj, int32_t* aIndex) {
465   return SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex);
466 }
467 
468 // to chain update folder actions
OnStartRunningUrl(nsIURI * aUrl)469 NS_IMETHODIMP nsAutoSyncManager::OnStartRunningUrl(nsIURI* aUrl) {
470   return NS_OK;
471 }
472 
OnStopRunningUrl(nsIURI * aUrl,nsresult aExitCode)473 NS_IMETHODIMP nsAutoSyncManager::OnStopRunningUrl(nsIURI* aUrl,
474                                                   nsresult aExitCode) {
475   mUpdateState = completed;
476   if (mUpdateQ.Count() > 0) mUpdateQ.RemoveObjectAt(0);
477 
478   return aExitCode;
479 }
480 
Pause()481 NS_IMETHODIMP nsAutoSyncManager::Pause() {
482   StopTimer();
483   mPaused = true;
484   MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("autosync paused"));
485   return NS_OK;
486 }
487 
Resume()488 NS_IMETHODIMP nsAutoSyncManager::Resume() {
489   mPaused = false;
490   StartTimerIfNeeded();
491   MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("autosync resumed"));
492   return NS_OK;
493 }
494 
Observe(nsISupports *,const char * aTopic,const char16_t * aSomeData)495 NS_IMETHODIMP nsAutoSyncManager::Observe(nsISupports*, const char* aTopic,
496                                          const char16_t* aSomeData) {
497   if (!PL_strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
498     nsCOMPtr<nsIObserverService> observerService =
499         mozilla::services::GetObserverService();
500     if (observerService) {
501       observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
502       observerService->RemoveObserver(this, kAppIdleNotification);
503       observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
504       observerService->RemoveObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC);
505       observerService->RemoveObserver(this, kStartupDoneNotification);
506     }
507 
508     // cancel and release the timer
509     if (mTimer) {
510       mTimer->Cancel();
511       mTimer = nullptr;
512     }
513     // unsubscribe from idle service
514     if (mIdleService) mIdleService->RemoveIdleObserver(this, kIdleTimeInSec);
515 
516     return NS_OK;
517   }
518 
519   if (!PL_strcmp(aTopic, kStartupDoneNotification)) {
520     mStartupDone = true;
521   } else if (!PL_strcmp(aTopic, kAppIdleNotification)) {
522     if (nsDependentString(aSomeData).EqualsLiteral("idle")) {
523       IdleState prevIdleState = GetIdleState();
524 
525       // we were already idle (either system or app), so
526       // just remember that we're app idle and return.
527       SetIdleState(appIdle);
528       if (prevIdleState != notIdle) return NS_OK;
529 
530       return StartIdleProcessing();
531     }
532 
533     // we're back from appIdle - if already notIdle, just return;
534     if (GetIdleState() == notIdle) return NS_OK;
535 
536     SetIdleState(notIdle);
537     NOTIFY_LISTENERS(OnStateChanged, (false));
538     return NS_OK;
539   } else if (!PL_strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
540     if (nsDependentString(aSomeData).EqualsLiteral(NS_IOSERVICE_ONLINE))
541       Resume();
542   } else if (!PL_strcmp(aTopic, NS_IOSERVICE_GOING_OFFLINE_TOPIC)) {
543     Pause();
544   }
545   // we're back from system idle
546   else if (!PL_strcmp(aTopic, "back")) {
547     // if we're app idle when we get back from system idle, we ignore
548     // it, since we'll keep doing our idle stuff.
549     if (GetIdleState() != appIdle) {
550       SetIdleState(notIdle);
551       NOTIFY_LISTENERS(OnStateChanged, (false));
552     }
553     return NS_OK;
554   } else  // we've gone system idle
555   {
556     // Check if we were already idle. We may have gotten
557     // multiple system idle notificatons. In that case,
558     // just remember that we're systemIdle and return;
559     if (GetIdleState() != notIdle) return NS_OK;
560 
561     // we might want to remember if we were app idle, because
562     // coming back from system idle while app idle shouldn't stop
563     // app indexing. But I think it's OK for now just leave ourselves
564     // in appIdle state.
565     if (GetIdleState() != appIdle) SetIdleState(systemIdle);
566     if (WeAreOffline()) return NS_OK;
567     return StartIdleProcessing();
568   }
569   return NS_OK;
570 }
571 
StartIdleProcessing()572 nsresult nsAutoSyncManager::StartIdleProcessing() {
573   if (mPaused) return NS_OK;
574 
575   StartTimerIfNeeded();
576 
577   // Ignore idle events sent during the startup
578   if (!mStartupDone) return NS_OK;
579 
580   // notify listeners that auto-sync is running
581   NOTIFY_LISTENERS(OnStateChanged, (true));
582 
583   nsCOMArray<nsIAutoSyncState> chainedQ;
584   nsCOMArray<nsIAutoSyncState>* queue = &mPriorityQ;
585   if (mDownloadModel == dmChained) {
586     ChainFoldersInQ(mPriorityQ, chainedQ);
587     queue = &chainedQ;
588   }
589 
590   // to store the folders that should be removed from the priority
591   // queue at the end of the iteration.
592   nsCOMArray<nsIAutoSyncState> foldersToBeRemoved;
593 
594   // process folders in the priority queue
595   int32_t elemCount = queue->Count();
596   for (int32_t idx = 0; idx < elemCount; idx++) {
597     nsCOMPtr<nsIAutoSyncState> autoSyncStateObj((*queue)[idx]);
598     if (!autoSyncStateObj) continue;
599 
600     int32_t state;
601     autoSyncStateObj->GetState(&state);
602 
603     // TODO: Test cached-connection availability in parallel mode
604     // and do not exceed (cached-connection count - 1)
605 
606     if (state != nsAutoSyncState::stReadyToDownload) continue;
607 
608     nsresult rv = DownloadMessagesForOffline(autoSyncStateObj);
609     if (NS_FAILED(rv)) {
610       // special case: this folder does not have any message to download
611       // (see bug 457342), remove it explicitly from the queue when iteration
612       // is over.
613       // Note that in normal execution flow, folders are removed from priority
614       // queue only in OnDownloadCompleted when all messages are downloaded
615       // successfully. This is the only place we change this flow.
616       if (NS_ERROR_NOT_AVAILABLE == rv)
617         foldersToBeRemoved.AppendObject(autoSyncStateObj);
618 
619       HandleDownloadErrorFor(autoSyncStateObj, rv);
620     }  // endif
621   }    // endfor
622 
623   // remove folders with no pending messages from the priority queue
624   elemCount = foldersToBeRemoved.Count();
625   for (int32_t idx = 0; idx < elemCount; idx++) {
626     nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(foldersToBeRemoved[idx]);
627     if (!autoSyncStateObj) continue;
628 
629     nsCOMPtr<nsIMsgFolder> folder;
630     autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
631     if (folder) NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
632 
633     autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
634 
635     if (mPriorityQ.RemoveObject(autoSyncStateObj))
636       NOTIFY_LISTENERS(OnFolderRemovedFromQ,
637                        (nsIAutoSyncMgrListener::PriorityQueue, folder));
638   }
639 
640   return AutoUpdateFolders();
641 }
642 
643 /**
644  * Updates offline imap folders that are not synchronized recently. This is
645  * called whenever we're idle.
646  */
AutoUpdateFolders()647 nsresult nsAutoSyncManager::AutoUpdateFolders() {
648   nsresult rv;
649 
650   // iterate through each imap account and update offline folders automatically
651 
652   nsCOMPtr<nsIMsgAccountManager> accountManager =
653       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
654   NS_ENSURE_SUCCESS(rv, rv);
655 
656   nsTArray<RefPtr<nsIMsgAccount>> accounts;
657   rv = accountManager->GetAccounts(accounts);
658   NS_ENSURE_SUCCESS(rv, rv);
659 
660   for (auto account : accounts) {
661     if (!account) continue;
662 
663     nsCOMPtr<nsIMsgIncomingServer> incomingServer;
664     rv = account->GetIncomingServer(getter_AddRefs(incomingServer));
665     if (!incomingServer) continue;
666 
667     nsCString type;
668     rv = incomingServer->GetType(type);
669 
670     if (!type.EqualsLiteral("imap")) continue;
671 
672     // if we haven't logged onto this server yet, then skip this server.
673     bool passwordRequired;
674     incomingServer->GetServerRequiresPasswordForBiff(&passwordRequired);
675     if (passwordRequired) continue;
676 
677     nsCOMPtr<nsIMsgFolder> rootFolder;
678 
679     rv = incomingServer->GetRootFolder(getter_AddRefs(rootFolder));
680     if (rootFolder) {
681       if (NS_FAILED(rv)) continue;
682 
683       nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
684       rv = rootFolder->GetDescendants(allDescendants);
685 
686       for (auto folder : allDescendants) {
687         uint32_t folderFlags;
688         rv = folder->GetFlags(&folderFlags);
689         // Skip this folder if not offline or is a saved search or is no select.
690         if (NS_FAILED(rv) || !(folderFlags & nsMsgFolderFlags::Offline) ||
691             folderFlags &
692                 (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect))
693           continue;
694 
695         nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
696             do_QueryInterface(folder, &rv);
697         if (NS_FAILED(rv)) continue;
698 
699         nsCOMPtr<nsIImapIncomingServer> imapServer;
700         rv = imapFolder->GetImapIncomingServer(getter_AddRefs(imapServer));
701         if (imapServer) {
702           bool autoSyncOfflineStores = false;
703           rv = imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
704 
705           // skip if AutoSyncOfflineStores pref is not set for this folder
706           if (NS_FAILED(rv) || !autoSyncOfflineStores) continue;
707         }
708 
709         nsCOMPtr<nsIAutoSyncState> autoSyncState;
710         rv = imapFolder->GetAutoSyncStateObj(getter_AddRefs(autoSyncState));
711         NS_ASSERTION(
712             autoSyncState,
713             "*** nsAutoSyncState shouldn't be NULL, check owner folder");
714 
715         // shouldn't happen but let's be defensive here
716         if (!autoSyncState) continue;
717 
718         int32_t state;
719         rv = autoSyncState->GetState(&state);
720 
721         if (NS_SUCCEEDED(rv) && nsAutoSyncState::stCompletedIdle == state) {
722           // ensure that we wait for at least nsMsgIncomingServer::BiffMinutes
723           // between each update of the same folder
724           PRTime lastUpdateTime;
725           rv = autoSyncState->GetLastUpdateTime(&lastUpdateTime);
726           PRTime span =
727               GetUpdateIntervalFor(autoSyncState) * (PR_USEC_PER_SEC * 60UL);
728           if (NS_SUCCEEDED(rv) && ((lastUpdateTime + span) < PR_Now())) {
729             if (mUpdateQ.IndexOf(autoSyncState) == -1) {
730               mUpdateQ.AppendObject(autoSyncState);
731               if (folder)
732                 NOTIFY_LISTENERS(OnFolderAddedIntoQ,
733                                  (nsIAutoSyncMgrListener::UpdateQueue, folder));
734             }
735           }
736         }
737 
738         // check last sync time
739         PRTime lastSyncTime;
740         rv = autoSyncState->GetLastSyncTime(&lastSyncTime);
741         if (NS_SUCCEEDED(rv) && ((lastSyncTime + kAutoSyncFreq) < PR_Now())) {
742           // add this folder into discovery queue to process existing headers
743           // and discover messages not downloaded yet
744           if (mDiscoveryQ.IndexOf(autoSyncState) == -1) {
745             mDiscoveryQ.AppendObject(autoSyncState);
746             if (folder)
747               NOTIFY_LISTENERS(
748                   OnFolderAddedIntoQ,
749                   (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
750           }
751         }
752       }  // endfor
753     }    // endif
754   }      // endfor
755 
756   // lazily create the timer if there is something to process in the queue
757   // when timer is done, it will self destruct
758   StartTimerIfNeeded();
759 
760   return rv;
761 }
762 
763 /**
764  * Places the given folder into the priority queue based on active
765  * strategy function.
766  */
ScheduleFolderForOfflineDownload(nsIAutoSyncState * aAutoSyncStateObj)767 void nsAutoSyncManager::ScheduleFolderForOfflineDownload(
768     nsIAutoSyncState* aAutoSyncStateObj) {
769   if (aAutoSyncStateObj && (mPriorityQ.IndexOf(aAutoSyncStateObj) == -1)) {
770     nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
771     GetFolderStrategy(getter_AddRefs(folStrategy));
772 
773     if (mPriorityQ.Count() <= 0) {
774       // make sure that we don't insert a folder excluded by the given strategy
775       nsCOMPtr<nsIMsgFolder> folder;
776       aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
777       if (folder) {
778         bool excluded = false;
779         if (folStrategy) folStrategy->IsExcluded(folder, &excluded);
780 
781         if (!excluded) {
782           mPriorityQ.AppendObject(
783               aAutoSyncStateObj);  // insert into the first spot
784           NOTIFY_LISTENERS(OnFolderAddedIntoQ,
785                            (nsIAutoSyncMgrListener::PriorityQueue, folder));
786         }
787       }
788     } else {
789       // find the right spot for the given folder
790       uint32_t qidx = mPriorityQ.Count();
791       while (qidx > 0) {
792         --qidx;
793 
794         nsCOMPtr<nsIMsgFolder> folderA, folderB;
795         mPriorityQ[qidx]->GetOwnerFolder(getter_AddRefs(folderA));
796         aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folderB));
797 
798         bool excluded = false;
799         if (folderB && folStrategy) folStrategy->IsExcluded(folderB, &excluded);
800 
801         if (excluded) break;
802 
803         nsAutoSyncStrategyDecisionType decision =
804             nsAutoSyncStrategyDecisions::Same;
805         if (folderA && folderB && folStrategy)
806           folStrategy->Sort(folderA, folderB, &decision);
807 
808         if (decision == nsAutoSyncStrategyDecisions::Higher && 0 == qidx)
809           mPriorityQ.InsertObjectAt(aAutoSyncStateObj, 0);
810         else if (decision == nsAutoSyncStrategyDecisions::Higher)
811           continue;
812         else if (decision == nsAutoSyncStrategyDecisions::Lower)
813           mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx + 1);
814         else  //  decision == nsAutoSyncStrategyDecisions::Same
815           mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx);
816 
817         NOTIFY_LISTENERS(OnFolderAddedIntoQ,
818                          (nsIAutoSyncMgrListener::PriorityQueue, folderB));
819         break;
820       }  // endwhile
821     }
822   }  // endif
823 }
824 
825 /**
826  * Zero aSizeLimit means no limit
827  */
DownloadMessagesForOffline(nsIAutoSyncState * aAutoSyncStateObj,uint32_t aSizeLimit)828 nsresult nsAutoSyncManager::DownloadMessagesForOffline(
829     nsIAutoSyncState* aAutoSyncStateObj, uint32_t aSizeLimit) {
830   if (!aAutoSyncStateObj) return NS_ERROR_INVALID_ARG;
831 
832   int32_t count;
833   nsresult rv = aAutoSyncStateObj->GetPendingMessageCount(&count);
834   NS_ENSURE_SUCCESS(rv, rv);
835 
836   // special case: no more message to download for this folder:
837   // see HandleDownloadErrorFor for recovery policy
838   if (!count) return NS_ERROR_NOT_AVAILABLE;
839 
840   nsTArray<RefPtr<nsIMsgDBHdr>> messagesToDownload;
841   uint32_t totalSize = 0;
842   rv = aAutoSyncStateObj->GetNextGroupOfMessages(mGroupSize, &totalSize,
843                                                  messagesToDownload);
844   NS_ENSURE_SUCCESS(rv, rv);
845 
846   // there are pending messages but the cumulative size is zero:
847   // treat as special case.
848   // Note that although it shouldn't happen, we know that sometimes
849   // imap servers manifest messages as zero length. By returning
850   // NS_ERROR_NOT_AVAILABLE we cause this folder to be removed from
851   // the priority queue temporarily (until the next idle or next update)
852   // in an effort to prevent it blocking other folders of the same account
853   // being synced.
854   if (!totalSize) return NS_ERROR_NOT_AVAILABLE;
855 
856   // ensure that we don't exceed the given size limit for this particular group
857   if (aSizeLimit && aSizeLimit < totalSize) return NS_ERROR_FAILURE;
858 
859   if (!messagesToDownload.IsEmpty()) {
860     rv = aAutoSyncStateObj->DownloadMessagesForOffline(messagesToDownload);
861 
862     int32_t totalCount;
863     (void)aAutoSyncStateObj->GetTotalMessageCount(&totalCount);
864 
865     nsCOMPtr<nsIMsgFolder> folder;
866     aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
867     if (NS_SUCCEEDED(rv) && folder)
868       NOTIFY_LISTENERS(OnDownloadStarted,
869                        (folder, messagesToDownload.Length(), totalCount));
870   }
871 
872   return rv;
873 }
874 
875 // clang-format off
876 /**
877  * Assuming that the download operation on the given folder has been failed at
878  * least once, execute these steps:
879  *  - put the auto-sync state into ready-to-download mode
880  *  - rollback the message offset so we can try the same group again (unless the
881  *    retry count is reached to the given limit)
882  *  - if parallel model is active, wait to be resumed by the next idle
883  *  - if chained model is active, search the priority queue to find a sibling to
884  *    continue with.
885  */
886 // clang-format on
HandleDownloadErrorFor(nsIAutoSyncState * aAutoSyncStateObj,const nsresult error)887 nsresult nsAutoSyncManager::HandleDownloadErrorFor(
888     nsIAutoSyncState* aAutoSyncStateObj, const nsresult error) {
889   if (!aAutoSyncStateObj) return NS_ERROR_INVALID_ARG;
890 
891   // ensure that an error occurred
892   if (NS_SUCCEEDED(error)) return NS_OK;
893 
894   // NS_ERROR_NOT_AVAILABLE is a special case/error happens when the queued
895   // folder doesn't have any message to download (see bug 457342). In such case
896   // we shouldn't retry the current message group, nor notify listeners. Simply
897   // continuing with the next sibling in the priority queue would suffice.
898 
899   if (NS_ERROR_NOT_AVAILABLE != error) {
900     // force the auto-sync state to try downloading the same group at least
901     // kGroupRetryCount times before it moves to the next one
902     aAutoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
903 
904     nsCOMPtr<nsIMsgFolder> folder;
905     aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
906     if (folder) NOTIFY_LISTENERS(OnDownloadError, (folder));
907   }
908 
909   // if parallel model, don't do anything else
910 
911   if (mDownloadModel == dmChained) {
912     // switch to the next folder in the chain and continue downloading
913     nsIAutoSyncState* autoSyncStateObj = aAutoSyncStateObj;
914     nsIAutoSyncState* nextAutoSyncStateObj = nullptr;
915     while (
916         (nextAutoSyncStateObj = GetNextSibling(mPriorityQ, autoSyncStateObj))) {
917       autoSyncStateObj = nextAutoSyncStateObj;
918       nsresult rv = DownloadMessagesForOffline(autoSyncStateObj);
919       if (NS_SUCCEEDED(rv)) break;
920       if (rv == NS_ERROR_NOT_AVAILABLE)
921         // next folder in the chain also doesn't have any message to download
922         // switch to next one if any
923         continue;
924       autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
925     }
926   }
927 
928   return NS_OK;
929 }
930 
GetUpdateIntervalFor(nsIAutoSyncState * aAutoSyncStateObj)931 uint32_t nsAutoSyncManager::GetUpdateIntervalFor(
932     nsIAutoSyncState* aAutoSyncStateObj) {
933   nsCOMPtr<nsIMsgFolder> folder;
934   nsresult rv = aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
935   if (NS_FAILED(rv)) return kDefaultUpdateInterval;
936 
937   nsCOMPtr<nsIMsgIncomingServer> server;
938   rv = folder->GetServer(getter_AddRefs(server));
939   if (NS_FAILED(rv)) return kDefaultUpdateInterval;
940 
941   if (server) {
942     int32_t interval;
943     rv = server->GetBiffMinutes(&interval);
944 
945     if (NS_SUCCEEDED(rv)) return (uint32_t)interval;
946   }
947 
948   return kDefaultUpdateInterval;
949 }
950 
GetGroupSize(uint32_t * aGroupSize)951 NS_IMETHODIMP nsAutoSyncManager::GetGroupSize(uint32_t* aGroupSize) {
952   NS_ENSURE_ARG_POINTER(aGroupSize);
953   *aGroupSize = mGroupSize;
954   return NS_OK;
955 }
SetGroupSize(uint32_t aGroupSize)956 NS_IMETHODIMP nsAutoSyncManager::SetGroupSize(uint32_t aGroupSize) {
957   mGroupSize = aGroupSize ? aGroupSize : kDefaultGroupSize;
958   return NS_OK;
959 }
960 
GetMsgStrategy(nsIAutoSyncMsgStrategy ** aMsgStrategy)961 NS_IMETHODIMP nsAutoSyncManager::GetMsgStrategy(
962     nsIAutoSyncMsgStrategy** aMsgStrategy) {
963   NS_ENSURE_ARG_POINTER(aMsgStrategy);
964 
965   // lazily create if it is not done already
966   if (!mMsgStrategyImpl) {
967     mMsgStrategyImpl = new nsDefaultAutoSyncMsgStrategy;
968     if (!mMsgStrategyImpl) return NS_ERROR_OUT_OF_MEMORY;
969   }
970 
971   NS_IF_ADDREF(*aMsgStrategy = mMsgStrategyImpl);
972   return NS_OK;
973 }
SetMsgStrategy(nsIAutoSyncMsgStrategy * aMsgStrategy)974 NS_IMETHODIMP nsAutoSyncManager::SetMsgStrategy(
975     nsIAutoSyncMsgStrategy* aMsgStrategy) {
976   mMsgStrategyImpl = aMsgStrategy;
977   return NS_OK;
978 }
979 
GetFolderStrategy(nsIAutoSyncFolderStrategy ** aFolderStrategy)980 NS_IMETHODIMP nsAutoSyncManager::GetFolderStrategy(
981     nsIAutoSyncFolderStrategy** aFolderStrategy) {
982   NS_ENSURE_ARG_POINTER(aFolderStrategy);
983 
984   // lazily create if it is not done already
985   if (!mFolderStrategyImpl) {
986     mFolderStrategyImpl = new nsDefaultAutoSyncFolderStrategy;
987     if (!mFolderStrategyImpl) return NS_ERROR_OUT_OF_MEMORY;
988   }
989 
990   NS_IF_ADDREF(*aFolderStrategy = mFolderStrategyImpl);
991   return NS_OK;
992 }
SetFolderStrategy(nsIAutoSyncFolderStrategy * aFolderStrategy)993 NS_IMETHODIMP nsAutoSyncManager::SetFolderStrategy(
994     nsIAutoSyncFolderStrategy* aFolderStrategy) {
995   mFolderStrategyImpl = aFolderStrategy;
996   return NS_OK;
997 }
998 
999 NS_IMETHODIMP
DoesMsgFitDownloadCriteria(nsIMsgDBHdr * aMsgHdr,bool * aResult)1000 nsAutoSyncManager::DoesMsgFitDownloadCriteria(nsIMsgDBHdr* aMsgHdr,
1001                                               bool* aResult) {
1002   NS_ENSURE_ARG_POINTER(aResult);
1003 
1004   uint32_t msgFlags = 0;
1005   aMsgHdr->GetFlags(&msgFlags);
1006 
1007   // check whether this message is marked imap deleted or not
1008   *aResult = !(msgFlags & nsMsgMessageFlags::IMAPDeleted);
1009   if (!(*aResult)) return NS_OK;
1010 
1011   bool shouldStoreMsgOffline = true;
1012   nsCOMPtr<nsIMsgFolder> folder;
1013   aMsgHdr->GetFolder(getter_AddRefs(folder));
1014   if (folder) {
1015     nsMsgKey msgKey;
1016     nsresult rv = aMsgHdr->GetMessageKey(&msgKey);
1017     // a cheap way to get the size limit for this folder and make
1018     // sure that we don't have this message offline already
1019     if (NS_SUCCEEDED(rv))
1020       folder->ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
1021   }
1022 
1023   *aResult &= shouldStoreMsgOffline;
1024 
1025   return NS_OK;
1026 }
1027 
OnDownloadQChanged(nsIAutoSyncState * aAutoSyncStateObj)1028 NS_IMETHODIMP nsAutoSyncManager::OnDownloadQChanged(
1029     nsIAutoSyncState* aAutoSyncStateObj) {
1030   nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
1031   if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
1032 
1033   if (mPaused) return NS_OK;
1034   // We want to start downloading immediately unless the folder is excluded.
1035   bool excluded = false;
1036   nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
1037   nsCOMPtr<nsIMsgFolder> folder;
1038 
1039   GetFolderStrategy(getter_AddRefs(folStrategy));
1040   autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
1041 
1042   if (folder && folStrategy) folStrategy->IsExcluded(folder, &excluded);
1043 
1044   nsresult rv = NS_OK;
1045 
1046   if (!excluded) {
1047     // Add this folder into the priority queue.
1048     autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
1049     ScheduleFolderForOfflineDownload(autoSyncStateObj);
1050 
1051     // If we operate in parallel mode or if there is no sibling downloading
1052     // messages at the moment, we can download the first group of the messages
1053     // for this folder
1054     if (mDownloadModel == dmParallel ||
1055         !DoesQContainAnySiblingOf(mPriorityQ, autoSyncStateObj,
1056                                   nsAutoSyncState::stDownloadInProgress)) {
1057       // this will download the first group of messages immediately;
1058       // to ensure that we don't end up downloading a large single message in
1059       // not-idle time, we enforce a limit. If there is no message fits into
1060       // this limit we postpone the download until the next idle.
1061       if (GetIdleState() == notIdle)
1062         rv = DownloadMessagesForOffline(autoSyncStateObj, kFirstGroupSizeLimit);
1063       else
1064         rv = DownloadMessagesForOffline(autoSyncStateObj);
1065 
1066       if (NS_FAILED(rv))
1067         autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
1068     }
1069   }
1070   return rv;
1071 }
1072 
1073 NS_IMETHODIMP
OnDownloadStarted(nsIAutoSyncState * aAutoSyncStateObj,nsresult aStartCode)1074 nsAutoSyncManager::OnDownloadStarted(nsIAutoSyncState* aAutoSyncStateObj,
1075                                      nsresult aStartCode) {
1076   nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
1077   if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
1078 
1079   // resume downloads during next idle time
1080   if (NS_FAILED(aStartCode))
1081     autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
1082 
1083   return aStartCode;
1084 }
1085 
1086 NS_IMETHODIMP
OnDownloadCompleted(nsIAutoSyncState * aAutoSyncStateObj,nsresult aExitCode)1087 nsAutoSyncManager::OnDownloadCompleted(nsIAutoSyncState* aAutoSyncStateObj,
1088                                        nsresult aExitCode) {
1089   nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
1090   if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
1091 
1092   nsresult rv = aExitCode;
1093 
1094   if (NS_FAILED(aExitCode)) {
1095     // retry the same group kGroupRetryCount times
1096     // try again if TB still idle, otherwise wait for the next idle time
1097     autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
1098     if (GetIdleState() != notIdle) {
1099       rv = DownloadMessagesForOffline(autoSyncStateObj);
1100       if (NS_FAILED(rv)) rv = HandleDownloadErrorFor(autoSyncStateObj, rv);
1101     }
1102     return rv;
1103   }
1104 
1105   // download is successful, reset the retry counter of the folder
1106   autoSyncStateObj->ResetRetryCounter();
1107 
1108   nsCOMPtr<nsIMsgFolder> folder;
1109   aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
1110   if (folder) NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
1111 
1112   int32_t count;
1113   rv = autoSyncStateObj->GetPendingMessageCount(&count);
1114   NS_ENSURE_SUCCESS(rv, rv);
1115 
1116   nsIAutoSyncState* nextFolderToDownload = nullptr;
1117   if (count > 0) {
1118     autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
1119 
1120     // in parallel model, we continue downloading the same folder as long as it
1121     // has more pending messages
1122     nextFolderToDownload = autoSyncStateObj;
1123 
1124     // in chained model, ensure that we are always downloading the highest
1125     // priority folder first
1126     if (mDownloadModel == dmChained) {
1127       // switch to higher priority folder and continue to download,
1128       // if any added recently
1129       int32_t myIndex = mPriorityQ.IndexOf(autoSyncStateObj);
1130 
1131       int32_t siblingIndex;
1132       nsIAutoSyncState* sibling =
1133           GetHighestPrioSibling(mPriorityQ, autoSyncStateObj, &siblingIndex);
1134 
1135       // lesser index = higher priority
1136       if (sibling && myIndex > -1 && siblingIndex < myIndex)
1137         nextFolderToDownload = sibling;
1138     }
1139   } else {
1140     autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
1141 
1142     nsCOMPtr<nsIMsgFolder> folder;
1143     nsresult rv = autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
1144 
1145     if (NS_SUCCEEDED(rv) && mPriorityQ.RemoveObject(autoSyncStateObj))
1146       NOTIFY_LISTENERS(OnFolderRemovedFromQ,
1147                        (nsIAutoSyncMgrListener::PriorityQueue, folder));
1148 
1149     // find the next folder owned by the same server in the queue and continue
1150     // downloading
1151     if (mDownloadModel == dmChained)
1152       nextFolderToDownload =
1153           GetHighestPrioSibling(mPriorityQ, autoSyncStateObj);
1154 
1155   }  // endif
1156 
1157   // continue downloading if TB is still in idle state
1158   if (nextFolderToDownload && GetIdleState() != notIdle) {
1159     rv = DownloadMessagesForOffline(nextFolderToDownload);
1160     if (NS_FAILED(rv)) rv = HandleDownloadErrorFor(nextFolderToDownload, rv);
1161   }
1162 
1163   return rv;
1164 }
1165 
GetDownloadModel(int32_t * aDownloadModel)1166 NS_IMETHODIMP nsAutoSyncManager::GetDownloadModel(int32_t* aDownloadModel) {
1167   NS_ENSURE_ARG_POINTER(aDownloadModel);
1168   *aDownloadModel = mDownloadModel;
1169   return NS_OK;
1170 }
SetDownloadModel(int32_t aDownloadModel)1171 NS_IMETHODIMP nsAutoSyncManager::SetDownloadModel(int32_t aDownloadModel) {
1172   mDownloadModel = aDownloadModel;
1173   return NS_OK;
1174 }
1175 
AddListener(nsIAutoSyncMgrListener * aListener)1176 NS_IMETHODIMP nsAutoSyncManager::AddListener(
1177     nsIAutoSyncMgrListener* aListener) {
1178   NS_ENSURE_ARG_POINTER(aListener);
1179   mListeners.AppendElementUnlessExists(aListener);
1180   return NS_OK;
1181 }
1182 
RemoveListener(nsIAutoSyncMgrListener * aListener)1183 NS_IMETHODIMP nsAutoSyncManager::RemoveListener(
1184     nsIAutoSyncMgrListener* aListener) {
1185   NS_ENSURE_ARG_POINTER(aListener);
1186   mListeners.RemoveElement(aListener);
1187   return NS_OK;
1188 }
1189 
1190 /* readonly attribute unsigned long discoveryQLength; */
GetDiscoveryQLength(uint32_t * aDiscoveryQLength)1191 NS_IMETHODIMP nsAutoSyncManager::GetDiscoveryQLength(
1192     uint32_t* aDiscoveryQLength) {
1193   NS_ENSURE_ARG_POINTER(aDiscoveryQLength);
1194   *aDiscoveryQLength = mDiscoveryQ.Count();
1195   return NS_OK;
1196 }
1197 
1198 /* readonly attribute unsigned long uploadQLength; */
GetUpdateQLength(uint32_t * aUpdateQLength)1199 NS_IMETHODIMP nsAutoSyncManager::GetUpdateQLength(uint32_t* aUpdateQLength) {
1200   NS_ENSURE_ARG_POINTER(aUpdateQLength);
1201   *aUpdateQLength = mUpdateQ.Count();
1202   return NS_OK;
1203 }
1204 
1205 /* readonly attribute unsigned long downloadQLength; */
GetDownloadQLength(uint32_t * aDownloadQLength)1206 NS_IMETHODIMP nsAutoSyncManager::GetDownloadQLength(
1207     uint32_t* aDownloadQLength) {
1208   NS_ENSURE_ARG_POINTER(aDownloadQLength);
1209   *aDownloadQLength = mPriorityQ.Count();
1210   return NS_OK;
1211 }
1212 
1213 NS_IMETHODIMP
OnFolderHasPendingMsgs(nsIAutoSyncState * aAutoSyncStateObj)1214 nsAutoSyncManager::OnFolderHasPendingMsgs(nsIAutoSyncState* aAutoSyncStateObj) {
1215   NS_ENSURE_ARG_POINTER(aAutoSyncStateObj);
1216   if (mUpdateQ.IndexOf(aAutoSyncStateObj) == -1) {
1217     nsCOMPtr<nsIMsgFolder> folder;
1218     aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
1219     // If this folder isn't the trash, add it to the update q.
1220     if (folder) {
1221       bool isTrash;
1222       folder->GetFlag(nsMsgFolderFlags::Trash, &isTrash);
1223       if (!isTrash) {
1224         bool isSentOrArchive;
1225         folder->IsSpecialFolder(
1226             nsMsgFolderFlags::SentMail | nsMsgFolderFlags::Archive, true,
1227             &isSentOrArchive);
1228         // Sent or archive folders go to the q front, the rest to the end.
1229         if (isSentOrArchive)
1230           mUpdateQ.InsertObjectAt(aAutoSyncStateObj, 0);
1231         else
1232           mUpdateQ.AppendObject(aAutoSyncStateObj);
1233         aAutoSyncStateObj->SetState(nsAutoSyncState::stUpdateNeeded);
1234         NOTIFY_LISTENERS(OnFolderAddedIntoQ,
1235                          (nsIAutoSyncMgrListener::UpdateQueue, folder));
1236       }
1237     }
1238   }
1239   return NS_OK;
1240 }
1241 
SetIdleState(IdleState st)1242 void nsAutoSyncManager::SetIdleState(IdleState st) { mIdleState = st; }
1243 
GetIdleState() const1244 nsAutoSyncManager::IdleState nsAutoSyncManager::GetIdleState() const {
1245   return mIdleState;
1246 }
1247 
1248 NS_IMPL_ISUPPORTS(nsAutoSyncManager, nsIObserver, nsIUrlListener,
1249                   nsIAutoSyncManager)
1250