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