@Test public void synchronizeMailboxSynchronous_withAccountSetNotToSyncRemoteDeletions_shouldNotDeleteLocalCopiesOfMessages() throws Exception { messageCountInRemoteFolder(0); LocalMessage remoteDeletedMessage = mock(LocalMessage.class); when(account.syncRemoteDeletions()).thenReturn(false); when(localFolder.getMessages(null)).thenReturn(Collections.singletonList(remoteDeletedMessage)); controller.synchronizeMailboxSynchronous(account, FOLDER_ID, FOLDER_NAME, listener, remoteFolder); verify(localFolder, never()).destroyMessages(messageListCaptor.capture()); }
@VisibleForTesting void synchronizeMailboxSynchronous(final Account account, final String folderId, final String folderName, final MessagingListener listener, Folder providedRemoteFolder) { Folder remoteFolder = null; LocalFolder tLocalFolder = null; Timber.i("Synchronizing folder %s:%s", account.getDescription(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxStarted(account, folderId, folderName); } if (folderId.equals(account.getOutboxFolderId())) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, 0, 0); } return; } Exception commandException = null; try { Timber.d("SYNC: About to process pending commands for account %s", account.getDescription()); try { processPendingCommandsSynchronous(account); } catch (Exception e) { Timber.e(e, "Failure processing command, but allow message sync attempt"); commandException = e; } Timber.v("SYNC: About to get local folder %s", folderId); final LocalStore localStore = account.getLocalStore(); tLocalFolder = localStore.getFolder(folderId); final LocalFolder localFolder = tLocalFolder; localFolder.open(Folder.OPEN_MODE_RW); localFolder.updateLastUid(); Map<String, Long> localUidMap = localFolder.getAllMessagesAndEffectiveDates(); if (providedRemoteFolder != null) { Timber.v("SYNC: using providedRemoteFolder %s", folderId); remoteFolder = providedRemoteFolder; } else { Store remoteStore = account.getRemoteStore(); Timber.v("SYNC: About to get remote folder %s", folderId); remoteFolder = remoteStore.getFolder(folderId); if (!verifyOrCreateRemoteSpecialFolder(account, folderId, folderName, remoteFolder, listener)) { return; } Timber.v("SYNC: About to open remote folder %s", folderId); if (Expunge.EXPUNGE_ON_POLL == account.getExpungePolicy()) { Timber.d("SYNC: Expunging folder %s:%s", account.getDescription(), folderId); remoteFolder.expunge(); } remoteFolder.open(Folder.OPEN_MODE_RO); } notificationController.clearAuthenticationErrorNotification(account, true); int remoteMessageCount = remoteFolder.getMessageCount(); int visibleLimit = localFolder.getVisibleLimit(); if (visibleLimit < 0) { visibleLimit = QMail.DEFAULT_VISIBLE_LIMIT; } final List<Message> remoteMessages = new ArrayList<>(); Map<String, Message> remoteUidMap = new HashMap<>(); Timber.v("SYNC: Remote message count for folder %s is %d", folderId, remoteMessageCount); final Date earliestDate = account.getEarliestPollDate(); long earliestTimestamp = earliestDate != null ? earliestDate.getTime() : 0L; int remoteStart = 1; if (remoteMessageCount > 0) { if (visibleLimit > 0) { remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1; } else { remoteStart = 1; } Timber.v("SYNC: About to get messages %d through %d for folder %s", remoteStart, remoteMessageCount, folderId); final AtomicInteger headerProgress = new AtomicInteger(0); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersStarted(account, folderId, folderName); } List<? extends Message> remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteMessageCount, earliestDate, null); int messageCount = remoteMessageArray.size(); for (Message thisMess : remoteMessageArray) { headerProgress.incrementAndGet(); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersProgress(account, folderId, folderName, headerProgress.get(), messageCount); } Long localMessageTimestamp = localUidMap.get(thisMess.getUid()); if (localMessageTimestamp == null || localMessageTimestamp >= earliestTimestamp) { remoteMessages.add(thisMess); remoteUidMap.put(thisMess.getUid(), thisMess); } } Timber.v("SYNC: Got %d messages for folder %s", remoteUidMap.size(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersFinished(account, folderId, folderName, headerProgress.get(), remoteUidMap.size()); } } else if (remoteMessageCount < 0) { throw new Exception("Message count " + remoteMessageCount + " for folder " + folderId); } MoreMessages moreMessages = localFolder.getMoreMessages(); if (account.syncRemoteDeletions()) { List<String> destroyMessageUids = new ArrayList<>(); for (String localMessageUid : localUidMap.keySet()) { if (remoteUidMap.get(localMessageUid) == null) { destroyMessageUids.add(localMessageUid); } } List<LocalMessage> destroyMessages = localFolder.getMessagesByUids(destroyMessageUids); if (!destroyMessageUids.isEmpty()) { moreMessages = MoreMessages.UNKNOWN; localFolder.destroyMessages(destroyMessages); for (Message destroyMessage : destroyMessages) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxRemovedMessage(account, folderId, folderName, destroyMessage); } } } } localUidMap = null; if (moreMessages == MoreMessages.UNKNOWN) { updateMoreMessages(remoteFolder, localFolder, earliestDate, remoteStart); } int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages, false, true); int unreadMessageCount = localFolder.getUnreadMessageCount(); for (MessagingListener l : getListeners()) { l.folderStatusChanged(account, folderId, folderName, unreadMessageCount); } localFolder.setLastChecked(System.currentTimeMillis()); localFolder.setStatus(null); Timber.d("Done synchronizing folder %s:%s @ %tc with %d new messages", account.getDescription(), folderId, System.currentTimeMillis(), newMessages); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, remoteMessageCount, newMessages); } if (commandException != null) { String rootMessage = getRootCauseMessage(commandException); Timber.e("Root cause failure in %s:%s was '%s'", account.getDescription(), tLocalFolder.getId(), rootMessage); localFolder.setStatus(rootMessage); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } } Timber.i("Done synchronizing folder %s:%s", account.getDescription(), folderId); } catch (AuthenticationFailedException e) { handleAuthenticationFailure(account, true); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, "Authentication failure"); } } catch (Exception e) { Timber.e(e, "synchronizeMailbox"); String rootMessage = getRootCauseMessage(e); if (tLocalFolder != null) { try { tLocalFolder.setStatus(rootMessage); tLocalFolder.setLastChecked(System.currentTimeMillis()); } catch (MessagingException me) { Timber.e(e, "Could not set last checked on folder %s:%s", account.getDescription(), tLocalFolder.getId()); } } for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } notifyUserIfCertificateProblem(account, e, true); Timber.e("Failed synchronizing folder %s:%s @ %tc", account.getDescription(), folderId, System.currentTimeMillis()); } finally { if (providedRemoteFolder == null) { closeFolder(remoteFolder); } closeFolder(tLocalFolder); } }
MessagingController { @VisibleForTesting void synchronizeMailboxSynchronous(final Account account, final String folderId, final String folderName, final MessagingListener listener, Folder providedRemoteFolder) { Folder remoteFolder = null; LocalFolder tLocalFolder = null; Timber.i("Synchronizing folder %s:%s", account.getDescription(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxStarted(account, folderId, folderName); } if (folderId.equals(account.getOutboxFolderId())) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, 0, 0); } return; } Exception commandException = null; try { Timber.d("SYNC: About to process pending commands for account %s", account.getDescription()); try { processPendingCommandsSynchronous(account); } catch (Exception e) { Timber.e(e, "Failure processing command, but allow message sync attempt"); commandException = e; } Timber.v("SYNC: About to get local folder %s", folderId); final LocalStore localStore = account.getLocalStore(); tLocalFolder = localStore.getFolder(folderId); final LocalFolder localFolder = tLocalFolder; localFolder.open(Folder.OPEN_MODE_RW); localFolder.updateLastUid(); Map<String, Long> localUidMap = localFolder.getAllMessagesAndEffectiveDates(); if (providedRemoteFolder != null) { Timber.v("SYNC: using providedRemoteFolder %s", folderId); remoteFolder = providedRemoteFolder; } else { Store remoteStore = account.getRemoteStore(); Timber.v("SYNC: About to get remote folder %s", folderId); remoteFolder = remoteStore.getFolder(folderId); if (!verifyOrCreateRemoteSpecialFolder(account, folderId, folderName, remoteFolder, listener)) { return; } Timber.v("SYNC: About to open remote folder %s", folderId); if (Expunge.EXPUNGE_ON_POLL == account.getExpungePolicy()) { Timber.d("SYNC: Expunging folder %s:%s", account.getDescription(), folderId); remoteFolder.expunge(); } remoteFolder.open(Folder.OPEN_MODE_RO); } notificationController.clearAuthenticationErrorNotification(account, true); int remoteMessageCount = remoteFolder.getMessageCount(); int visibleLimit = localFolder.getVisibleLimit(); if (visibleLimit < 0) { visibleLimit = QMail.DEFAULT_VISIBLE_LIMIT; } final List<Message> remoteMessages = new ArrayList<>(); Map<String, Message> remoteUidMap = new HashMap<>(); Timber.v("SYNC: Remote message count for folder %s is %d", folderId, remoteMessageCount); final Date earliestDate = account.getEarliestPollDate(); long earliestTimestamp = earliestDate != null ? earliestDate.getTime() : 0L; int remoteStart = 1; if (remoteMessageCount > 0) { if (visibleLimit > 0) { remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1; } else { remoteStart = 1; } Timber.v("SYNC: About to get messages %d through %d for folder %s", remoteStart, remoteMessageCount, folderId); final AtomicInteger headerProgress = new AtomicInteger(0); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersStarted(account, folderId, folderName); } List<? extends Message> remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteMessageCount, earliestDate, null); int messageCount = remoteMessageArray.size(); for (Message thisMess : remoteMessageArray) { headerProgress.incrementAndGet(); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersProgress(account, folderId, folderName, headerProgress.get(), messageCount); } Long localMessageTimestamp = localUidMap.get(thisMess.getUid()); if (localMessageTimestamp == null || localMessageTimestamp >= earliestTimestamp) { remoteMessages.add(thisMess); remoteUidMap.put(thisMess.getUid(), thisMess); } } Timber.v("SYNC: Got %d messages for folder %s", remoteUidMap.size(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersFinished(account, folderId, folderName, headerProgress.get(), remoteUidMap.size()); } } else if (remoteMessageCount < 0) { throw new Exception("Message count " + remoteMessageCount + " for folder " + folderId); } MoreMessages moreMessages = localFolder.getMoreMessages(); if (account.syncRemoteDeletions()) { List<String> destroyMessageUids = new ArrayList<>(); for (String localMessageUid : localUidMap.keySet()) { if (remoteUidMap.get(localMessageUid) == null) { destroyMessageUids.add(localMessageUid); } } List<LocalMessage> destroyMessages = localFolder.getMessagesByUids(destroyMessageUids); if (!destroyMessageUids.isEmpty()) { moreMessages = MoreMessages.UNKNOWN; localFolder.destroyMessages(destroyMessages); for (Message destroyMessage : destroyMessages) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxRemovedMessage(account, folderId, folderName, destroyMessage); } } } } localUidMap = null; if (moreMessages == MoreMessages.UNKNOWN) { updateMoreMessages(remoteFolder, localFolder, earliestDate, remoteStart); } int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages, false, true); int unreadMessageCount = localFolder.getUnreadMessageCount(); for (MessagingListener l : getListeners()) { l.folderStatusChanged(account, folderId, folderName, unreadMessageCount); } localFolder.setLastChecked(System.currentTimeMillis()); localFolder.setStatus(null); Timber.d("Done synchronizing folder %s:%s @ %tc with %d new messages", account.getDescription(), folderId, System.currentTimeMillis(), newMessages); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, remoteMessageCount, newMessages); } if (commandException != null) { String rootMessage = getRootCauseMessage(commandException); Timber.e("Root cause failure in %s:%s was '%s'", account.getDescription(), tLocalFolder.getId(), rootMessage); localFolder.setStatus(rootMessage); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } } Timber.i("Done synchronizing folder %s:%s", account.getDescription(), folderId); } catch (AuthenticationFailedException e) { handleAuthenticationFailure(account, true); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, "Authentication failure"); } } catch (Exception e) { Timber.e(e, "synchronizeMailbox"); String rootMessage = getRootCauseMessage(e); if (tLocalFolder != null) { try { tLocalFolder.setStatus(rootMessage); tLocalFolder.setLastChecked(System.currentTimeMillis()); } catch (MessagingException me) { Timber.e(e, "Could not set last checked on folder %s:%s", account.getDescription(), tLocalFolder.getId()); } } for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } notifyUserIfCertificateProblem(account, e, true); Timber.e("Failed synchronizing folder %s:%s @ %tc", account.getDescription(), folderId, System.currentTimeMillis()); } finally { if (providedRemoteFolder == null) { closeFolder(remoteFolder); } closeFolder(tLocalFolder); } } }
MessagingController { @VisibleForTesting void synchronizeMailboxSynchronous(final Account account, final String folderId, final String folderName, final MessagingListener listener, Folder providedRemoteFolder) { Folder remoteFolder = null; LocalFolder tLocalFolder = null; Timber.i("Synchronizing folder %s:%s", account.getDescription(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxStarted(account, folderId, folderName); } if (folderId.equals(account.getOutboxFolderId())) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, 0, 0); } return; } Exception commandException = null; try { Timber.d("SYNC: About to process pending commands for account %s", account.getDescription()); try { processPendingCommandsSynchronous(account); } catch (Exception e) { Timber.e(e, "Failure processing command, but allow message sync attempt"); commandException = e; } Timber.v("SYNC: About to get local folder %s", folderId); final LocalStore localStore = account.getLocalStore(); tLocalFolder = localStore.getFolder(folderId); final LocalFolder localFolder = tLocalFolder; localFolder.open(Folder.OPEN_MODE_RW); localFolder.updateLastUid(); Map<String, Long> localUidMap = localFolder.getAllMessagesAndEffectiveDates(); if (providedRemoteFolder != null) { Timber.v("SYNC: using providedRemoteFolder %s", folderId); remoteFolder = providedRemoteFolder; } else { Store remoteStore = account.getRemoteStore(); Timber.v("SYNC: About to get remote folder %s", folderId); remoteFolder = remoteStore.getFolder(folderId); if (!verifyOrCreateRemoteSpecialFolder(account, folderId, folderName, remoteFolder, listener)) { return; } Timber.v("SYNC: About to open remote folder %s", folderId); if (Expunge.EXPUNGE_ON_POLL == account.getExpungePolicy()) { Timber.d("SYNC: Expunging folder %s:%s", account.getDescription(), folderId); remoteFolder.expunge(); } remoteFolder.open(Folder.OPEN_MODE_RO); } notificationController.clearAuthenticationErrorNotification(account, true); int remoteMessageCount = remoteFolder.getMessageCount(); int visibleLimit = localFolder.getVisibleLimit(); if (visibleLimit < 0) { visibleLimit = QMail.DEFAULT_VISIBLE_LIMIT; } final List<Message> remoteMessages = new ArrayList<>(); Map<String, Message> remoteUidMap = new HashMap<>(); Timber.v("SYNC: Remote message count for folder %s is %d", folderId, remoteMessageCount); final Date earliestDate = account.getEarliestPollDate(); long earliestTimestamp = earliestDate != null ? earliestDate.getTime() : 0L; int remoteStart = 1; if (remoteMessageCount > 0) { if (visibleLimit > 0) { remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1; } else { remoteStart = 1; } Timber.v("SYNC: About to get messages %d through %d for folder %s", remoteStart, remoteMessageCount, folderId); final AtomicInteger headerProgress = new AtomicInteger(0); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersStarted(account, folderId, folderName); } List<? extends Message> remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteMessageCount, earliestDate, null); int messageCount = remoteMessageArray.size(); for (Message thisMess : remoteMessageArray) { headerProgress.incrementAndGet(); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersProgress(account, folderId, folderName, headerProgress.get(), messageCount); } Long localMessageTimestamp = localUidMap.get(thisMess.getUid()); if (localMessageTimestamp == null || localMessageTimestamp >= earliestTimestamp) { remoteMessages.add(thisMess); remoteUidMap.put(thisMess.getUid(), thisMess); } } Timber.v("SYNC: Got %d messages for folder %s", remoteUidMap.size(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersFinished(account, folderId, folderName, headerProgress.get(), remoteUidMap.size()); } } else if (remoteMessageCount < 0) { throw new Exception("Message count " + remoteMessageCount + " for folder " + folderId); } MoreMessages moreMessages = localFolder.getMoreMessages(); if (account.syncRemoteDeletions()) { List<String> destroyMessageUids = new ArrayList<>(); for (String localMessageUid : localUidMap.keySet()) { if (remoteUidMap.get(localMessageUid) == null) { destroyMessageUids.add(localMessageUid); } } List<LocalMessage> destroyMessages = localFolder.getMessagesByUids(destroyMessageUids); if (!destroyMessageUids.isEmpty()) { moreMessages = MoreMessages.UNKNOWN; localFolder.destroyMessages(destroyMessages); for (Message destroyMessage : destroyMessages) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxRemovedMessage(account, folderId, folderName, destroyMessage); } } } } localUidMap = null; if (moreMessages == MoreMessages.UNKNOWN) { updateMoreMessages(remoteFolder, localFolder, earliestDate, remoteStart); } int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages, false, true); int unreadMessageCount = localFolder.getUnreadMessageCount(); for (MessagingListener l : getListeners()) { l.folderStatusChanged(account, folderId, folderName, unreadMessageCount); } localFolder.setLastChecked(System.currentTimeMillis()); localFolder.setStatus(null); Timber.d("Done synchronizing folder %s:%s @ %tc with %d new messages", account.getDescription(), folderId, System.currentTimeMillis(), newMessages); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, remoteMessageCount, newMessages); } if (commandException != null) { String rootMessage = getRootCauseMessage(commandException); Timber.e("Root cause failure in %s:%s was '%s'", account.getDescription(), tLocalFolder.getId(), rootMessage); localFolder.setStatus(rootMessage); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } } Timber.i("Done synchronizing folder %s:%s", account.getDescription(), folderId); } catch (AuthenticationFailedException e) { handleAuthenticationFailure(account, true); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, "Authentication failure"); } } catch (Exception e) { Timber.e(e, "synchronizeMailbox"); String rootMessage = getRootCauseMessage(e); if (tLocalFolder != null) { try { tLocalFolder.setStatus(rootMessage); tLocalFolder.setLastChecked(System.currentTimeMillis()); } catch (MessagingException me) { Timber.e(e, "Could not set last checked on folder %s:%s", account.getDescription(), tLocalFolder.getId()); } } for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } notifyUserIfCertificateProblem(account, e, true); Timber.e("Failed synchronizing folder %s:%s @ %tc", account.getDescription(), folderId, System.currentTimeMillis()); } finally { if (providedRemoteFolder == null) { closeFolder(remoteFolder); } closeFolder(tLocalFolder); } } @VisibleForTesting  MessagingController(Context context, NotificationController notificationController,
            Contacts contacts, TransportProvider transportProvider); }
MessagingController { @VisibleForTesting void synchronizeMailboxSynchronous(final Account account, final String folderId, final String folderName, final MessagingListener listener, Folder providedRemoteFolder) { Folder remoteFolder = null; LocalFolder tLocalFolder = null; Timber.i("Synchronizing folder %s:%s", account.getDescription(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxStarted(account, folderId, folderName); } if (folderId.equals(account.getOutboxFolderId())) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, 0, 0); } return; } Exception commandException = null; try { Timber.d("SYNC: About to process pending commands for account %s", account.getDescription()); try { processPendingCommandsSynchronous(account); } catch (Exception e) { Timber.e(e, "Failure processing command, but allow message sync attempt"); commandException = e; } Timber.v("SYNC: About to get local folder %s", folderId); final LocalStore localStore = account.getLocalStore(); tLocalFolder = localStore.getFolder(folderId); final LocalFolder localFolder = tLocalFolder; localFolder.open(Folder.OPEN_MODE_RW); localFolder.updateLastUid(); Map<String, Long> localUidMap = localFolder.getAllMessagesAndEffectiveDates(); if (providedRemoteFolder != null) { Timber.v("SYNC: using providedRemoteFolder %s", folderId); remoteFolder = providedRemoteFolder; } else { Store remoteStore = account.getRemoteStore(); Timber.v("SYNC: About to get remote folder %s", folderId); remoteFolder = remoteStore.getFolder(folderId); if (!verifyOrCreateRemoteSpecialFolder(account, folderId, folderName, remoteFolder, listener)) { return; } Timber.v("SYNC: About to open remote folder %s", folderId); if (Expunge.EXPUNGE_ON_POLL == account.getExpungePolicy()) { Timber.d("SYNC: Expunging folder %s:%s", account.getDescription(), folderId); remoteFolder.expunge(); } remoteFolder.open(Folder.OPEN_MODE_RO); } notificationController.clearAuthenticationErrorNotification(account, true); int remoteMessageCount = remoteFolder.getMessageCount(); int visibleLimit = localFolder.getVisibleLimit(); if (visibleLimit < 0) { visibleLimit = QMail.DEFAULT_VISIBLE_LIMIT; } final List<Message> remoteMessages = new ArrayList<>(); Map<String, Message> remoteUidMap = new HashMap<>(); Timber.v("SYNC: Remote message count for folder %s is %d", folderId, remoteMessageCount); final Date earliestDate = account.getEarliestPollDate(); long earliestTimestamp = earliestDate != null ? earliestDate.getTime() : 0L; int remoteStart = 1; if (remoteMessageCount > 0) { if (visibleLimit > 0) { remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1; } else { remoteStart = 1; } Timber.v("SYNC: About to get messages %d through %d for folder %s", remoteStart, remoteMessageCount, folderId); final AtomicInteger headerProgress = new AtomicInteger(0); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersStarted(account, folderId, folderName); } List<? extends Message> remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteMessageCount, earliestDate, null); int messageCount = remoteMessageArray.size(); for (Message thisMess : remoteMessageArray) { headerProgress.incrementAndGet(); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersProgress(account, folderId, folderName, headerProgress.get(), messageCount); } Long localMessageTimestamp = localUidMap.get(thisMess.getUid()); if (localMessageTimestamp == null || localMessageTimestamp >= earliestTimestamp) { remoteMessages.add(thisMess); remoteUidMap.put(thisMess.getUid(), thisMess); } } Timber.v("SYNC: Got %d messages for folder %s", remoteUidMap.size(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersFinished(account, folderId, folderName, headerProgress.get(), remoteUidMap.size()); } } else if (remoteMessageCount < 0) { throw new Exception("Message count " + remoteMessageCount + " for folder " + folderId); } MoreMessages moreMessages = localFolder.getMoreMessages(); if (account.syncRemoteDeletions()) { List<String> destroyMessageUids = new ArrayList<>(); for (String localMessageUid : localUidMap.keySet()) { if (remoteUidMap.get(localMessageUid) == null) { destroyMessageUids.add(localMessageUid); } } List<LocalMessage> destroyMessages = localFolder.getMessagesByUids(destroyMessageUids); if (!destroyMessageUids.isEmpty()) { moreMessages = MoreMessages.UNKNOWN; localFolder.destroyMessages(destroyMessages); for (Message destroyMessage : destroyMessages) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxRemovedMessage(account, folderId, folderName, destroyMessage); } } } } localUidMap = null; if (moreMessages == MoreMessages.UNKNOWN) { updateMoreMessages(remoteFolder, localFolder, earliestDate, remoteStart); } int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages, false, true); int unreadMessageCount = localFolder.getUnreadMessageCount(); for (MessagingListener l : getListeners()) { l.folderStatusChanged(account, folderId, folderName, unreadMessageCount); } localFolder.setLastChecked(System.currentTimeMillis()); localFolder.setStatus(null); Timber.d("Done synchronizing folder %s:%s @ %tc with %d new messages", account.getDescription(), folderId, System.currentTimeMillis(), newMessages); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, remoteMessageCount, newMessages); } if (commandException != null) { String rootMessage = getRootCauseMessage(commandException); Timber.e("Root cause failure in %s:%s was '%s'", account.getDescription(), tLocalFolder.getId(), rootMessage); localFolder.setStatus(rootMessage); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } } Timber.i("Done synchronizing folder %s:%s", account.getDescription(), folderId); } catch (AuthenticationFailedException e) { handleAuthenticationFailure(account, true); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, "Authentication failure"); } } catch (Exception e) { Timber.e(e, "synchronizeMailbox"); String rootMessage = getRootCauseMessage(e); if (tLocalFolder != null) { try { tLocalFolder.setStatus(rootMessage); tLocalFolder.setLastChecked(System.currentTimeMillis()); } catch (MessagingException me) { Timber.e(e, "Could not set last checked on folder %s:%s", account.getDescription(), tLocalFolder.getId()); } } for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } notifyUserIfCertificateProblem(account, e, true); Timber.e("Failed synchronizing folder %s:%s @ %tc", account.getDescription(), folderId, System.currentTimeMillis()); } finally { if (providedRemoteFolder == null) { closeFolder(remoteFolder); } closeFolder(tLocalFolder); } } @VisibleForTesting  MessagingController(Context context, NotificationController notificationController,
            Contacts contacts, TransportProvider transportProvider); static synchronized MessagingController getInstance(Context context); void addListener(MessagingListener listener); void refreshListener(MessagingListener listener); void removeListener(MessagingListener listener); Set<MessagingListener> getListeners(); Set<MessagingListener> getListeners(MessagingListener listener); void listFolders(final Account account, final boolean refreshRemote, final MessagingListener listener); void listSubFolders(final Account account, final String parentFolderId, final boolean refreshRemote, final MessagingListener listener); void listFoldersSynchronous(final Account account, final boolean refreshRemote,
            final MessagingListener listener); void listSubFoldersSynchronous(final Account account, final String parentFolderId, final boolean refreshRemote,
            final MessagingListener listener); void searchLocalMessages(final LocalSearch search, final MessagingListener listener); Future<?> searchRemoteMessages(final String acctUuid, final String folderId, final String folderName, final String query,
            final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags, final MessagingListener listener); void loadSearchResults(final Account account, final String folderName, final List<Message> messages,
            final MessagingListener listener); void loadMoreMessages(Account account, String folderId, MessagingListener listener); void synchronizeMailbox(final Account account, final String folderId, final String folderName, final MessagingListener listener,
            final Folder providedRemoteFolder); void markAllMessagesRead(final Account account, final String folder); void setFlag(final Account account, final List<Long> messageIds, final Flag flag,
            final boolean newState); void setFlagForThreads(final Account account, final List<Long> threadRootIds,
            final Flag flag, final boolean newState); void setFlag(Account account, String folderId, List<? extends Message> messages, Flag flag,
            boolean newState); void setFlag(Account account, String folderName, String uid, Flag flag,
            boolean newState); void clearAllPending(final Account account); void loadMessageRemotePartial(final Account account, final String folderId,
            final String uid, final MessagingListener listener); void loadMessageRemote(final Account account, final String folderId,
            final String uid, final MessagingListener listener); LocalMessage loadMessage(Account account, String folderId, String uid); void loadAttachment(final Account account, final LocalMessage message, final Part part,
            final MessagingListener listener); void sendMessage(final Account account,
            final Message message,
            MessagingListener listener); void sendPendingMessages(MessagingListener listener); void sendPendingMessages(final Account account,
            MessagingListener listener); void getAccountStats(final Context context, final Account account,
            final MessagingListener listener); void getSearchAccountStats(final SearchAccount searchAccount,
            final MessagingListener listener); AccountStats getSearchAccountStatsSynchronous(final SearchAccount searchAccount,
            final MessagingListener listener); void getFolderUnreadMessageCount(final Account account, final String folderId,
            final MessagingListener l); boolean isMoveCapable(MessageReference messageReference); boolean isCopyCapable(MessageReference message); boolean isMoveCapable(final Account account); boolean isCopyCapable(final Account account); void moveMessages(final Account srcAccount, final String srcFolder,
            List<MessageReference> messageReferences, final String destFolder); void moveMessagesInThread(Account srcAccount, final String srcFolder,
            final List<MessageReference> messageReferences, final String destFolder); void moveMessage(final Account account, final String srcFolder, final MessageReference message,
            final String destFolder); void copyMessages(final Account srcAccount, final String srcFolder,
            final List<MessageReference> messageReferences, final String destFolder); void copyMessagesInThread(Account srcAccount, final String srcFolder,
            final List<MessageReference> messageReferences, final String destFolder); void copyMessage(final Account account, final String srcFolder, final MessageReference message,
            final String destFolder); void expunge(final Account account, final String folder); void deleteDraft(final Account account, long id); void deleteThreads(final List<MessageReference> messages); void deleteMessage(MessageReference message, final MessagingListener listener); void deleteMessages(List<MessageReference> messages, final MessagingListener listener); @SuppressLint("NewApi") // used for debugging only void debugClearMessagesLocally(final List<MessageReference> messages); void emptyTrash(final Account account, MessagingListener listener); void clearFolder(final Account account, final String folderId, final ActivityListener listener); void sendAlternate(Context context, Account account, LocalMessage message); void checkMail(final Context context, final Account account,
            final boolean ignoreLastCheckedTime,
            final boolean useManualWakeLock,
            final MessagingListener listener); void compact(final Account account, final MessagingListener ml); void clear(final Account account, final MessagingListener ml); void recreate(final Account account, final MessagingListener ml); void deleteAccount(Account account); Message saveDraft(final Account account, final Message message, long existingDraftId, boolean saveRemotely); long getId(Message message); MessagingListener getCheckMailListener(); void setCheckMailListener(MessagingListener checkMailListener); Collection<Pusher> getPushers(); boolean setupPushing(final Account account); void stopAllPushing(); void messagesArrived(final Account account, final Folder remoteFolder, final List<Message> messages,
            final boolean flagSyncOnly); void systemStatusChanged(); void cancelNotificationsForAccount(Account account); void cancelNotificationForMessage(Account account, MessageReference messageReference); void clearCertificateErrorNotifications(Account account, CheckDirection direction); void notifyUserIfCertificateProblem(Account account, Exception exception, boolean incoming); }
MessagingController { @VisibleForTesting void synchronizeMailboxSynchronous(final Account account, final String folderId, final String folderName, final MessagingListener listener, Folder providedRemoteFolder) { Folder remoteFolder = null; LocalFolder tLocalFolder = null; Timber.i("Synchronizing folder %s:%s", account.getDescription(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxStarted(account, folderId, folderName); } if (folderId.equals(account.getOutboxFolderId())) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, 0, 0); } return; } Exception commandException = null; try { Timber.d("SYNC: About to process pending commands for account %s", account.getDescription()); try { processPendingCommandsSynchronous(account); } catch (Exception e) { Timber.e(e, "Failure processing command, but allow message sync attempt"); commandException = e; } Timber.v("SYNC: About to get local folder %s", folderId); final LocalStore localStore = account.getLocalStore(); tLocalFolder = localStore.getFolder(folderId); final LocalFolder localFolder = tLocalFolder; localFolder.open(Folder.OPEN_MODE_RW); localFolder.updateLastUid(); Map<String, Long> localUidMap = localFolder.getAllMessagesAndEffectiveDates(); if (providedRemoteFolder != null) { Timber.v("SYNC: using providedRemoteFolder %s", folderId); remoteFolder = providedRemoteFolder; } else { Store remoteStore = account.getRemoteStore(); Timber.v("SYNC: About to get remote folder %s", folderId); remoteFolder = remoteStore.getFolder(folderId); if (!verifyOrCreateRemoteSpecialFolder(account, folderId, folderName, remoteFolder, listener)) { return; } Timber.v("SYNC: About to open remote folder %s", folderId); if (Expunge.EXPUNGE_ON_POLL == account.getExpungePolicy()) { Timber.d("SYNC: Expunging folder %s:%s", account.getDescription(), folderId); remoteFolder.expunge(); } remoteFolder.open(Folder.OPEN_MODE_RO); } notificationController.clearAuthenticationErrorNotification(account, true); int remoteMessageCount = remoteFolder.getMessageCount(); int visibleLimit = localFolder.getVisibleLimit(); if (visibleLimit < 0) { visibleLimit = QMail.DEFAULT_VISIBLE_LIMIT; } final List<Message> remoteMessages = new ArrayList<>(); Map<String, Message> remoteUidMap = new HashMap<>(); Timber.v("SYNC: Remote message count for folder %s is %d", folderId, remoteMessageCount); final Date earliestDate = account.getEarliestPollDate(); long earliestTimestamp = earliestDate != null ? earliestDate.getTime() : 0L; int remoteStart = 1; if (remoteMessageCount > 0) { if (visibleLimit > 0) { remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1; } else { remoteStart = 1; } Timber.v("SYNC: About to get messages %d through %d for folder %s", remoteStart, remoteMessageCount, folderId); final AtomicInteger headerProgress = new AtomicInteger(0); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersStarted(account, folderId, folderName); } List<? extends Message> remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteMessageCount, earliestDate, null); int messageCount = remoteMessageArray.size(); for (Message thisMess : remoteMessageArray) { headerProgress.incrementAndGet(); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersProgress(account, folderId, folderName, headerProgress.get(), messageCount); } Long localMessageTimestamp = localUidMap.get(thisMess.getUid()); if (localMessageTimestamp == null || localMessageTimestamp >= earliestTimestamp) { remoteMessages.add(thisMess); remoteUidMap.put(thisMess.getUid(), thisMess); } } Timber.v("SYNC: Got %d messages for folder %s", remoteUidMap.size(), folderId); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxHeadersFinished(account, folderId, folderName, headerProgress.get(), remoteUidMap.size()); } } else if (remoteMessageCount < 0) { throw new Exception("Message count " + remoteMessageCount + " for folder " + folderId); } MoreMessages moreMessages = localFolder.getMoreMessages(); if (account.syncRemoteDeletions()) { List<String> destroyMessageUids = new ArrayList<>(); for (String localMessageUid : localUidMap.keySet()) { if (remoteUidMap.get(localMessageUid) == null) { destroyMessageUids.add(localMessageUid); } } List<LocalMessage> destroyMessages = localFolder.getMessagesByUids(destroyMessageUids); if (!destroyMessageUids.isEmpty()) { moreMessages = MoreMessages.UNKNOWN; localFolder.destroyMessages(destroyMessages); for (Message destroyMessage : destroyMessages) { for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxRemovedMessage(account, folderId, folderName, destroyMessage); } } } } localUidMap = null; if (moreMessages == MoreMessages.UNKNOWN) { updateMoreMessages(remoteFolder, localFolder, earliestDate, remoteStart); } int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages, false, true); int unreadMessageCount = localFolder.getUnreadMessageCount(); for (MessagingListener l : getListeners()) { l.folderStatusChanged(account, folderId, folderName, unreadMessageCount); } localFolder.setLastChecked(System.currentTimeMillis()); localFolder.setStatus(null); Timber.d("Done synchronizing folder %s:%s @ %tc with %d new messages", account.getDescription(), folderId, System.currentTimeMillis(), newMessages); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFinished(account, folderId, folderName, remoteMessageCount, newMessages); } if (commandException != null) { String rootMessage = getRootCauseMessage(commandException); Timber.e("Root cause failure in %s:%s was '%s'", account.getDescription(), tLocalFolder.getId(), rootMessage); localFolder.setStatus(rootMessage); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } } Timber.i("Done synchronizing folder %s:%s", account.getDescription(), folderId); } catch (AuthenticationFailedException e) { handleAuthenticationFailure(account, true); for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, "Authentication failure"); } } catch (Exception e) { Timber.e(e, "synchronizeMailbox"); String rootMessage = getRootCauseMessage(e); if (tLocalFolder != null) { try { tLocalFolder.setStatus(rootMessage); tLocalFolder.setLastChecked(System.currentTimeMillis()); } catch (MessagingException me) { Timber.e(e, "Could not set last checked on folder %s:%s", account.getDescription(), tLocalFolder.getId()); } } for (MessagingListener l : getListeners(listener)) { l.synchronizeMailboxFailed(account, folderId, folderName, rootMessage); } notifyUserIfCertificateProblem(account, e, true); Timber.e("Failed synchronizing folder %s:%s @ %tc", account.getDescription(), folderId, System.currentTimeMillis()); } finally { if (providedRemoteFolder == null) { closeFolder(remoteFolder); } closeFolder(tLocalFolder); } } @VisibleForTesting  MessagingController(Context context, NotificationController notificationController,
            Contacts contacts, TransportProvider transportProvider); static synchronized MessagingController getInstance(Context context); void addListener(MessagingListener listener); void refreshListener(MessagingListener listener); void removeListener(MessagingListener listener); Set<MessagingListener> getListeners(); Set<MessagingListener> getListeners(MessagingListener listener); void listFolders(final Account account, final boolean refreshRemote, final MessagingListener listener); void listSubFolders(final Account account, final String parentFolderId, final boolean refreshRemote, final MessagingListener listener); void listFoldersSynchronous(final Account account, final boolean refreshRemote,
            final MessagingListener listener); void listSubFoldersSynchronous(final Account account, final String parentFolderId, final boolean refreshRemote,
            final MessagingListener listener); void searchLocalMessages(final LocalSearch search, final MessagingListener listener); Future<?> searchRemoteMessages(final String acctUuid, final String folderId, final String folderName, final String query,
            final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags, final MessagingListener listener); void loadSearchResults(final Account account, final String folderName, final List<Message> messages,
            final MessagingListener listener); void loadMoreMessages(Account account, String folderId, MessagingListener listener); void synchronizeMailbox(final Account account, final String folderId, final String folderName, final MessagingListener listener,
            final Folder providedRemoteFolder); void markAllMessagesRead(final Account account, final String folder); void setFlag(final Account account, final List<Long> messageIds, final Flag flag,
            final boolean newState); void setFlagForThreads(final Account account, final List<Long> threadRootIds,
            final Flag flag, final boolean newState); void setFlag(Account account, String folderId, List<? extends Message> messages, Flag flag,
            boolean newState); void setFlag(Account account, String folderName, String uid, Flag flag,
            boolean newState); void clearAllPending(final Account account); void loadMessageRemotePartial(final Account account, final String folderId,
            final String uid, final MessagingListener listener); void loadMessageRemote(final Account account, final String folderId,
            final String uid, final MessagingListener listener); LocalMessage loadMessage(Account account, String folderId, String uid); void loadAttachment(final Account account, final LocalMessage message, final Part part,
            final MessagingListener listener); void sendMessage(final Account account,
            final Message message,
            MessagingListener listener); void sendPendingMessages(MessagingListener listener); void sendPendingMessages(final Account account,
            MessagingListener listener); void getAccountStats(final Context context, final Account account,
            final MessagingListener listener); void getSearchAccountStats(final SearchAccount searchAccount,
            final MessagingListener listener); AccountStats getSearchAccountStatsSynchronous(final SearchAccount searchAccount,
            final MessagingListener listener); void getFolderUnreadMessageCount(final Account account, final String folderId,
            final MessagingListener l); boolean isMoveCapable(MessageReference messageReference); boolean isCopyCapable(MessageReference message); boolean isMoveCapable(final Account account); boolean isCopyCapable(final Account account); void moveMessages(final Account srcAccount, final String srcFolder,
            List<MessageReference> messageReferences, final String destFolder); void moveMessagesInThread(Account srcAccount, final String srcFolder,
            final List<MessageReference> messageReferences, final String destFolder); void moveMessage(final Account account, final String srcFolder, final MessageReference message,
            final String destFolder); void copyMessages(final Account srcAccount, final String srcFolder,
            final List<MessageReference> messageReferences, final String destFolder); void copyMessagesInThread(Account srcAccount, final String srcFolder,
            final List<MessageReference> messageReferences, final String destFolder); void copyMessage(final Account account, final String srcFolder, final MessageReference message,
            final String destFolder); void expunge(final Account account, final String folder); void deleteDraft(final Account account, long id); void deleteThreads(final List<MessageReference> messages); void deleteMessage(MessageReference message, final MessagingListener listener); void deleteMessages(List<MessageReference> messages, final MessagingListener listener); @SuppressLint("NewApi") // used for debugging only void debugClearMessagesLocally(final List<MessageReference> messages); void emptyTrash(final Account account, MessagingListener listener); void clearFolder(final Account account, final String folderId, final ActivityListener listener); void sendAlternate(Context context, Account account, LocalMessage message); void checkMail(final Context context, final Account account,
            final boolean ignoreLastCheckedTime,
            final boolean useManualWakeLock,
            final MessagingListener listener); void compact(final Account account, final MessagingListener ml); void clear(final Account account, final MessagingListener ml); void recreate(final Account account, final MessagingListener ml); void deleteAccount(Account account); Message saveDraft(final Account account, final Message message, long existingDraftId, boolean saveRemotely); long getId(Message message); MessagingListener getCheckMailListener(); void setCheckMailListener(MessagingListener checkMailListener); Collection<Pusher> getPushers(); boolean setupPushing(final Account account); void stopAllPushing(); void messagesArrived(final Account account, final Folder remoteFolder, final List<Message> messages,
            final boolean flagSyncOnly); void systemStatusChanged(); void cancelNotificationsForAccount(Account account); void cancelNotificationForMessage(Account account, MessageReference messageReference); void clearCertificateErrorNotifications(Account account, CheckDirection direction); void notifyUserIfCertificateProblem(Account account, Exception exception, boolean incoming); static final long INVALID_MESSAGE_ID; }