Copy message sync code to ImapMessageStore/ImapSync
This will allow us to modify the sync implementation for IMAP without having to worry about supporting the other protocols with the same code.
This commit is contained in:
parent
6bb42191ac
commit
87eb9398ae
6 changed files with 1082 additions and 41 deletions
|
@ -59,6 +59,7 @@ import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead;
|
|||
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy;
|
||||
import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag;
|
||||
import com.fsck.k9.controller.ProgressBodyFactory.ProgressListener;
|
||||
import com.fsck.k9.controller.imap.ImapMessageStore;
|
||||
import com.fsck.k9.helper.Contacts;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.AuthenticationFailedException;
|
||||
|
@ -121,7 +122,7 @@ import static com.fsck.k9.mail.Flag.X_REMOTE_COPY_STARTED;
|
|||
public class MessagingController {
|
||||
public static final long INVALID_MESSAGE_ID = -1;
|
||||
|
||||
private static final Set<Flag> SYNC_FLAGS = EnumSet.of(Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED);
|
||||
public static final Set<Flag> SYNC_FLAGS = EnumSet.of(Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED);
|
||||
|
||||
|
||||
private static MessagingController inst = null;
|
||||
|
@ -141,6 +142,8 @@ public class MessagingController {
|
|||
private final MemorizingMessagingListener memorizingMessagingListener = new MemorizingMessagingListener();
|
||||
private final TransportProvider transportProvider;
|
||||
|
||||
private ImapMessageStore imapMessageStore;
|
||||
|
||||
|
||||
private MessagingListener checkMailListener = null;
|
||||
private volatile boolean stopped = false;
|
||||
|
@ -254,6 +257,19 @@ public class MessagingController {
|
|||
throw new Error(e);
|
||||
}
|
||||
|
||||
private RemoteMessageStore getRemoteMessageStore(Account account) {
|
||||
return account.getStoreUri().startsWith("imap") ? getImapMessageStore() : null;
|
||||
}
|
||||
|
||||
private ImapMessageStore getImapMessageStore() {
|
||||
if (imapMessageStore == null) {
|
||||
imapMessageStore = new ImapMessageStore(notificationController, this, context);
|
||||
}
|
||||
|
||||
return imapMessageStore;
|
||||
}
|
||||
|
||||
|
||||
public void addListener(MessagingListener listener) {
|
||||
listeners.add(listener);
|
||||
refreshListener(listener);
|
||||
|
@ -296,7 +312,7 @@ public class MessagingController {
|
|||
cache.unhideMessages(messages);
|
||||
}
|
||||
|
||||
private boolean isMessageSuppressed(LocalMessage message) {
|
||||
public boolean isMessageSuppressed(LocalMessage message) {
|
||||
long messageId = message.getDatabaseId();
|
||||
long folderId = message.getFolder().getDatabaseId();
|
||||
|
||||
|
@ -721,6 +737,17 @@ public class MessagingController {
|
|||
@VisibleForTesting
|
||||
void synchronizeMailboxSynchronous(final Account account, final String folder, final MessagingListener listener,
|
||||
Folder providedRemoteFolder) {
|
||||
RemoteMessageStore remoteMessageStore = getRemoteMessageStore(account);
|
||||
if (remoteMessageStore != null) {
|
||||
remoteMessageStore.sync(account, folder, listener, providedRemoteFolder);
|
||||
} else {
|
||||
synchronizeMailboxSynchronousLegacy(account, folder, listener, providedRemoteFolder);
|
||||
}
|
||||
}
|
||||
|
||||
void synchronizeMailboxSynchronousLegacy(final Account account, final String folder, final MessagingListener listener,
|
||||
Folder providedRemoteFolder) {
|
||||
|
||||
Folder remoteFolder = null;
|
||||
LocalFolder tLocalFolder = null;
|
||||
|
||||
|
@ -984,11 +1011,11 @@ public class MessagingController {
|
|||
|
||||
}
|
||||
|
||||
void handleAuthenticationFailure(Account account, boolean incoming) {
|
||||
public void handleAuthenticationFailure(Account account, boolean incoming) {
|
||||
notificationController.showAuthenticationErrorNotification(account, incoming);
|
||||
}
|
||||
|
||||
private void updateMoreMessages(Folder remoteFolder, LocalFolder localFolder, Date earliestDate, int remoteStart)
|
||||
public void updateMoreMessages(Folder remoteFolder, LocalFolder localFolder, Date earliestDate, int remoteStart)
|
||||
throws MessagingException, IOException {
|
||||
|
||||
if (remoteStart == 1) {
|
||||
|
@ -1653,7 +1680,7 @@ public class MessagingController {
|
|||
});
|
||||
}
|
||||
|
||||
private void processPendingCommandsSynchronous(Account account) throws MessagingException {
|
||||
public void processPendingCommandsSynchronous(Account account) throws MessagingException {
|
||||
LocalStore localStore = account.getLocalStore();
|
||||
List<PendingCommand> commands = localStore.getPendingCommands();
|
||||
|
||||
|
@ -3709,7 +3736,7 @@ public class MessagingController {
|
|||
}
|
||||
|
||||
|
||||
private boolean shouldNotifyForMessage(Account account, LocalFolder localFolder, Message message) {
|
||||
public boolean shouldNotifyForMessage(Account account, LocalFolder localFolder, Message message) {
|
||||
// If we don't even have an account name, don't show the notification.
|
||||
// (This happens during initial account setup)
|
||||
if (account.getName() == null) {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.fsck.k9.controller;
|
||||
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.mail.Folder;
|
||||
|
||||
|
||||
public interface RemoteMessageStore {
|
||||
// TODO: Nicer interface
|
||||
// Instead of using Account pass in "remote store config", "sync config", "local mail store" (like LocalStore
|
||||
// only with an interface/implementation optimized for sync; eventually this can replace LocalStore which does
|
||||
// many things we don't need and does badly some of the things we do need), "folder id", "sync listener"
|
||||
// TODO: Add a way to cancel the sync process
|
||||
void sync(Account account, String folder, MessagingListener listener, Folder providedRemoteFolder);
|
||||
}
|
|
@ -6,7 +6,7 @@ import java.util.Comparator;
|
|||
import com.fsck.k9.mail.Message;
|
||||
|
||||
|
||||
class UidReverseComparator implements Comparator<Message> {
|
||||
public class UidReverseComparator implements Comparator<Message> {
|
||||
@Override
|
||||
public int compare(Message messageLeft, Message messageRight) {
|
||||
Long uidLeft = getUidForMessage(messageLeft);
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package com.fsck.k9.controller.imap;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.controller.MessagingController;
|
||||
import com.fsck.k9.controller.MessagingListener;
|
||||
import com.fsck.k9.controller.RemoteMessageStore;
|
||||
import com.fsck.k9.mail.Folder;
|
||||
import com.fsck.k9.notification.NotificationController;
|
||||
|
||||
|
||||
public class ImapMessageStore implements RemoteMessageStore {
|
||||
private final ImapSync imapSync;
|
||||
|
||||
|
||||
public ImapMessageStore(NotificationController notificationController, MessagingController controller,
|
||||
Context context) {
|
||||
this.imapSync = new ImapSync(notificationController, controller, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sync(Account account, String folder, MessagingListener listener, Folder providedRemoteFolder) {
|
||||
imapSync.sync(account, folder, listener, providedRemoteFolder);
|
||||
}
|
||||
}
|
971
k9mail/src/main/java/com/fsck/k9/controller/imap/ImapSync.java
Normal file
971
k9mail/src/main/java/com/fsck/k9/controller/imap/ImapSync.java
Normal file
|
@ -0,0 +1,971 @@
|
|||
package com.fsck.k9.controller.imap;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.Account.Expunge;
|
||||
import com.fsck.k9.AccountStats;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.activity.MessageReference;
|
||||
import com.fsck.k9.controller.MessagingController;
|
||||
import com.fsck.k9.controller.MessagingListener;
|
||||
import com.fsck.k9.controller.UidReverseComparator;
|
||||
import com.fsck.k9.mail.AuthenticationFailedException;
|
||||
import com.fsck.k9.mail.BodyFactory;
|
||||
import com.fsck.k9.mail.DefaultBodyFactory;
|
||||
import com.fsck.k9.mail.FetchProfile;
|
||||
import com.fsck.k9.mail.Flag;
|
||||
import com.fsck.k9.mail.Folder;
|
||||
import com.fsck.k9.mail.Folder.FolderType;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.MessageRetrievalListener;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.Part;
|
||||
import com.fsck.k9.mail.Store;
|
||||
import com.fsck.k9.mail.internet.MessageExtractor;
|
||||
import com.fsck.k9.mailstore.LocalFolder;
|
||||
import com.fsck.k9.mailstore.LocalFolder.MoreMessages;
|
||||
import com.fsck.k9.mailstore.LocalMessage;
|
||||
import com.fsck.k9.mailstore.LocalStore;
|
||||
import com.fsck.k9.mailstore.MessageRemovalListener;
|
||||
import com.fsck.k9.notification.NotificationController;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static com.fsck.k9.helper.ExceptionHelper.getRootCauseMessage;
|
||||
|
||||
|
||||
class ImapSync {
|
||||
private final NotificationController notificationController;
|
||||
private final MessagingController controller;
|
||||
private final Context context;
|
||||
|
||||
|
||||
// TODO: Replace all of these dependencies with one or more interfaces
|
||||
ImapSync(NotificationController notificationController, MessagingController controller, Context context) {
|
||||
this.notificationController = notificationController;
|
||||
this.controller = controller;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void sync(Account account, String folder, MessagingListener listener, Folder providedRemoteFolder) {
|
||||
synchronizeMailboxSynchronous(account, folder, listener, providedRemoteFolder);
|
||||
}
|
||||
|
||||
void synchronizeMailboxSynchronous(final Account account, final String folder, final MessagingListener listener,
|
||||
Folder providedRemoteFolder) {
|
||||
Folder remoteFolder = null;
|
||||
LocalFolder tLocalFolder = null;
|
||||
|
||||
Timber.i("Synchronizing folder %s:%s", account.getDescription(), folder);
|
||||
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxStarted(account, folder);
|
||||
}
|
||||
/*
|
||||
* We don't ever sync the Outbox
|
||||
*/
|
||||
if (folder.equals(account.getOutboxFolderName())) {
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxFinished(account, folder, 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the message list from the local store and create an index of
|
||||
* the uids within the list.
|
||||
*/
|
||||
Timber.v("SYNC: About to get local folder %s", folder);
|
||||
|
||||
final LocalStore localStore = account.getLocalStore();
|
||||
tLocalFolder = localStore.getFolder(folder);
|
||||
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", folder);
|
||||
remoteFolder = providedRemoteFolder;
|
||||
} else {
|
||||
Store remoteStore = account.getRemoteStore();
|
||||
|
||||
Timber.v("SYNC: About to get remote folder %s", folder);
|
||||
remoteFolder = remoteStore.getFolder(folder);
|
||||
|
||||
if (!verifyOrCreateRemoteSpecialFolder(account, folder, remoteFolder, listener)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Synchronization process:
|
||||
*
|
||||
Open the folder
|
||||
Upload any local messages that are marked as PENDING_UPLOAD (Drafts, Sent, Trash)
|
||||
Get the message count
|
||||
Get the list of the newest K9.DEFAULT_VISIBLE_LIMIT messages
|
||||
getMessages(messageCount - K9.DEFAULT_VISIBLE_LIMIT, messageCount)
|
||||
See if we have each message locally, if not fetch it's flags and envelope
|
||||
Get and update the unread count for the folder
|
||||
Update the remote flags of any messages we have locally with an internal date newer than the remote message.
|
||||
Get the current flags for any messages we have locally but did not just download
|
||||
Update local flags
|
||||
For any message we have locally but not remotely, delete the local message to keep cache clean.
|
||||
Download larger parts of any new messages.
|
||||
(Optional) Download small attachments in the background.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Open the remote folder. This pre-loads certain metadata like message count.
|
||||
*/
|
||||
Timber.v("SYNC: About to open remote folder %s", folder);
|
||||
|
||||
if (Expunge.EXPUNGE_ON_POLL == account.getExpungePolicy()) {
|
||||
Timber.d("SYNC: Expunging folder %s:%s", account.getDescription(), folder);
|
||||
remoteFolder.expunge();
|
||||
}
|
||||
remoteFolder.open(Folder.OPEN_MODE_RO);
|
||||
|
||||
}
|
||||
|
||||
notificationController.clearAuthenticationErrorNotification(account, true);
|
||||
|
||||
/*
|
||||
* Get the remote message count.
|
||||
*/
|
||||
int remoteMessageCount = remoteFolder.getMessageCount();
|
||||
|
||||
int visibleLimit = localFolder.getVisibleLimit();
|
||||
|
||||
if (visibleLimit < 0) {
|
||||
visibleLimit = K9.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", folder, remoteMessageCount);
|
||||
|
||||
final Date earliestDate = account.getEarliestPollDate();
|
||||
long earliestTimestamp = earliestDate != null ? earliestDate.getTime() : 0L;
|
||||
|
||||
|
||||
int remoteStart = 1;
|
||||
if (remoteMessageCount > 0) {
|
||||
/* Message numbers start at 1. */
|
||||
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, folder);
|
||||
|
||||
final AtomicInteger headerProgress = new AtomicInteger(0);
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxHeadersStarted(account, folder);
|
||||
}
|
||||
|
||||
|
||||
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, folder, 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(), folder);
|
||||
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxHeadersFinished(account, folder, headerProgress.get(), remoteUidMap.size());
|
||||
}
|
||||
|
||||
} else if (remoteMessageCount < 0) {
|
||||
throw new Exception("Message count " + remoteMessageCount + " for folder " + folder);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove any messages that are in the local store but no longer on the remote store or are too old
|
||||
*/
|
||||
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, folder, destroyMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// noinspection UnusedAssignment, free memory early? (better break up the method!)
|
||||
localUidMap = null;
|
||||
|
||||
if (moreMessages == MoreMessages.UNKNOWN) {
|
||||
updateMoreMessages(remoteFolder, localFolder, earliestDate, remoteStart);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now we download the actual content of messages.
|
||||
*/
|
||||
int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages, false, true);
|
||||
|
||||
int unreadMessageCount = localFolder.getUnreadMessageCount();
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.folderStatusChanged(account, folder, unreadMessageCount);
|
||||
}
|
||||
|
||||
/* Notify listeners that we're finally done. */
|
||||
|
||||
localFolder.setLastChecked(System.currentTimeMillis());
|
||||
localFolder.setStatus(null);
|
||||
|
||||
Timber.d("Done synchronizing folder %s:%s @ %tc with %d new messages",
|
||||
account.getDescription(),
|
||||
folder,
|
||||
System.currentTimeMillis(),
|
||||
newMessages);
|
||||
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxFinished(account, folder, remoteMessageCount, newMessages);
|
||||
}
|
||||
|
||||
|
||||
if (commandException != null) {
|
||||
String rootMessage = getRootCauseMessage(commandException);
|
||||
Timber.e("Root cause failure in %s:%s was '%s'",
|
||||
account.getDescription(), tLocalFolder.getName(), rootMessage);
|
||||
localFolder.setStatus(rootMessage);
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxFailed(account, folder, rootMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Timber.i("Done synchronizing folder %s:%s", account.getDescription(), folder);
|
||||
|
||||
} catch (AuthenticationFailedException e) {
|
||||
handleAuthenticationFailure(account, true);
|
||||
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxFailed(account, folder, "Authentication failure");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Timber.e(e, "synchronizeMailbox");
|
||||
// If we don't set the last checked, it can try too often during
|
||||
// failure conditions
|
||||
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.getName());
|
||||
}
|
||||
}
|
||||
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxFailed(account, folder, rootMessage);
|
||||
}
|
||||
notifyUserIfCertificateProblem(account, e, true);
|
||||
Timber.e("Failed synchronizing folder %s:%s @ %tc", account.getDescription(), folder,
|
||||
System.currentTimeMillis());
|
||||
|
||||
} finally {
|
||||
if (providedRemoteFolder == null) {
|
||||
closeFolder(remoteFolder);
|
||||
}
|
||||
|
||||
closeFolder(tLocalFolder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* If the folder is a "special" folder we need to see if it exists
|
||||
* on the remote server. It if does not exist we'll try to create it. If we
|
||||
* can't create we'll abort. This will happen on every single Pop3 folder as
|
||||
* designed and on Imap folders during error conditions. This allows us
|
||||
* to treat Pop3 and Imap the same in this code.
|
||||
*/
|
||||
private boolean verifyOrCreateRemoteSpecialFolder(Account account, String folder, Folder remoteFolder,
|
||||
MessagingListener listener) throws MessagingException {
|
||||
if (folder.equals(account.getTrashFolderName()) ||
|
||||
folder.equals(account.getSentFolderName()) ||
|
||||
folder.equals(account.getDraftsFolderName())) {
|
||||
if (!remoteFolder.exists()) {
|
||||
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.synchronizeMailboxFinished(account, folder, 0, 0);
|
||||
}
|
||||
|
||||
Timber.i("Done synchronizing folder %s", folder);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the messages described by inputMessages from the remote store and writes them to
|
||||
* local storage.
|
||||
*
|
||||
* @param account
|
||||
* The account the remote store belongs to.
|
||||
* @param remoteFolder
|
||||
* The remote folder to download messages from.
|
||||
* @param localFolder
|
||||
* The {@link LocalFolder} instance corresponding to the remote folder.
|
||||
* @param inputMessages
|
||||
* A list of messages objects that store the UIDs of which messages to download.
|
||||
* @param flagSyncOnly
|
||||
* Only flags will be fetched from the remote store if this is {@code true}.
|
||||
* @param purgeToVisibleLimit
|
||||
* If true, local messages will be purged down to the limit of visible messages.
|
||||
*
|
||||
* @return The number of downloaded messages that are not flagged as {@link Flag#SEEN}.
|
||||
*
|
||||
* @throws MessagingException
|
||||
*/
|
||||
int downloadMessages(final Account account, final Folder remoteFolder,
|
||||
final LocalFolder localFolder, List<Message> inputMessages,
|
||||
boolean flagSyncOnly, boolean purgeToVisibleLimit) throws MessagingException {
|
||||
|
||||
final Date earliestDate = account.getEarliestPollDate();
|
||||
Date downloadStarted = new Date(); // now
|
||||
|
||||
if (earliestDate != null) {
|
||||
Timber.d("Only syncing messages after %s", earliestDate);
|
||||
}
|
||||
final String folder = remoteFolder.getName();
|
||||
|
||||
int unreadBeforeStart = 0;
|
||||
try {
|
||||
AccountStats stats = account.getStats(context);
|
||||
unreadBeforeStart = stats.unreadMessageCount;
|
||||
|
||||
} catch (MessagingException e) {
|
||||
Timber.e(e, "Unable to getUnreadMessageCount for account: %s", account);
|
||||
}
|
||||
|
||||
List<Message> syncFlagMessages = new ArrayList<>();
|
||||
List<Message> unsyncedMessages = new ArrayList<>();
|
||||
final AtomicInteger newMessages = new AtomicInteger(0);
|
||||
|
||||
List<Message> messages = new ArrayList<>(inputMessages);
|
||||
|
||||
for (Message message : messages) {
|
||||
evaluateMessageForDownload(message, folder, localFolder, remoteFolder, account, unsyncedMessages,
|
||||
syncFlagMessages, flagSyncOnly);
|
||||
}
|
||||
|
||||
final AtomicInteger progress = new AtomicInteger(0);
|
||||
final int todo = unsyncedMessages.size() + syncFlagMessages.size();
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.synchronizeMailboxProgress(account, folder, progress.get(), todo);
|
||||
}
|
||||
|
||||
Timber.d("SYNC: Have %d unsynced messages", unsyncedMessages.size());
|
||||
|
||||
messages.clear();
|
||||
final List<Message> largeMessages = new ArrayList<>();
|
||||
final List<Message> smallMessages = new ArrayList<>();
|
||||
if (!unsyncedMessages.isEmpty()) {
|
||||
|
||||
/*
|
||||
* Reverse the order of the messages. Depending on the server this may get us
|
||||
* fetch results for newest to oldest. If not, no harm done.
|
||||
*/
|
||||
Collections.sort(unsyncedMessages, new UidReverseComparator());
|
||||
int visibleLimit = localFolder.getVisibleLimit();
|
||||
int listSize = unsyncedMessages.size();
|
||||
|
||||
if ((visibleLimit > 0) && (listSize > visibleLimit)) {
|
||||
unsyncedMessages = unsyncedMessages.subList(0, visibleLimit);
|
||||
}
|
||||
|
||||
FetchProfile fp = new FetchProfile();
|
||||
if (remoteFolder.supportsFetchingFlags()) {
|
||||
fp.add(FetchProfile.Item.FLAGS);
|
||||
}
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
|
||||
Timber.d("SYNC: About to fetch %d unsynced messages for folder %s", unsyncedMessages.size(), folder);
|
||||
|
||||
fetchUnsyncedMessages(account, remoteFolder, unsyncedMessages, smallMessages, largeMessages, progress, todo,
|
||||
fp);
|
||||
|
||||
String updatedPushState = localFolder.getPushState();
|
||||
for (Message message : unsyncedMessages) {
|
||||
String newPushState = remoteFolder.getNewPushState(updatedPushState, message);
|
||||
if (newPushState != null) {
|
||||
updatedPushState = newPushState;
|
||||
}
|
||||
}
|
||||
localFolder.setPushState(updatedPushState);
|
||||
|
||||
Timber.d("SYNC: Synced unsynced messages for folder %s", folder);
|
||||
}
|
||||
|
||||
Timber.d("SYNC: Have %d large messages and %d small messages out of %d unsynced messages",
|
||||
largeMessages.size(), smallMessages.size(), unsyncedMessages.size());
|
||||
|
||||
unsyncedMessages.clear();
|
||||
/*
|
||||
* Grab the content of the small messages first. This is going to
|
||||
* be very fast and at very worst will be a single up of a few bytes and a single
|
||||
* download of 625k.
|
||||
*/
|
||||
FetchProfile fp = new FetchProfile();
|
||||
//TODO: Only fetch small and large messages if we have some
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
// fp.add(FetchProfile.Item.FLAGS);
|
||||
// fp.add(FetchProfile.Item.ENVELOPE);
|
||||
downloadSmallMessages(account, remoteFolder, localFolder, smallMessages, progress, unreadBeforeStart,
|
||||
newMessages, todo, fp);
|
||||
smallMessages.clear();
|
||||
/*
|
||||
* Now do the large messages that require more round trips.
|
||||
*/
|
||||
fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.STRUCTURE);
|
||||
downloadLargeMessages(account, remoteFolder, localFolder, largeMessages, progress, unreadBeforeStart,
|
||||
newMessages, todo, fp);
|
||||
largeMessages.clear();
|
||||
|
||||
/*
|
||||
* Refresh the flags for any messages in the local store that we didn't just
|
||||
* download.
|
||||
*/
|
||||
|
||||
refreshLocalMessageFlags(account, remoteFolder, localFolder, syncFlagMessages, progress, todo);
|
||||
|
||||
Timber.d("SYNC: Synced remote messages for folder %s, %d new messages", folder, newMessages.get());
|
||||
|
||||
if (purgeToVisibleLimit) {
|
||||
localFolder.purgeToVisibleLimit(new MessageRemovalListener() {
|
||||
@Override
|
||||
public void messageRemoved(Message message) {
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.synchronizeMailboxRemovedMessage(account, folder, message);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// If the oldest message seen on this sync is newer than
|
||||
// the oldest message seen on the previous sync, then
|
||||
// we want to move our high-water mark forward
|
||||
// this is all here just for pop which only syncs inbox
|
||||
// this would be a little wrong for IMAP (we'd want a folder-level pref, not an account level pref.)
|
||||
// fortunately, we just don't care.
|
||||
Long oldestMessageTime = localFolder.getOldestMessageDate();
|
||||
|
||||
if (oldestMessageTime != null) {
|
||||
Date oldestExtantMessage = new Date(oldestMessageTime);
|
||||
if (oldestExtantMessage.before(downloadStarted) &&
|
||||
oldestExtantMessage.after(new Date(account.getLatestOldMessageSeenTime()))) {
|
||||
account.setLatestOldMessageSeenTime(oldestExtantMessage.getTime());
|
||||
account.save(Preferences.getPreferences(context));
|
||||
}
|
||||
|
||||
}
|
||||
return newMessages.get();
|
||||
}
|
||||
|
||||
private void evaluateMessageForDownload(final Message message, final String folder,
|
||||
final LocalFolder localFolder,
|
||||
final Folder remoteFolder,
|
||||
final Account account,
|
||||
final List<Message> unsyncedMessages,
|
||||
final List<Message> syncFlagMessages,
|
||||
boolean flagSyncOnly) throws MessagingException {
|
||||
if (message.isSet(Flag.DELETED)) {
|
||||
Timber.v("Message with uid %s is marked as deleted", message.getUid());
|
||||
|
||||
syncFlagMessages.add(message);
|
||||
return;
|
||||
}
|
||||
|
||||
Message localMessage = localFolder.getMessage(message.getUid());
|
||||
|
||||
if (localMessage == null) {
|
||||
if (!flagSyncOnly) {
|
||||
if (!message.isSet(Flag.X_DOWNLOADED_FULL) && !message.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
|
||||
Timber.v("Message with uid %s has not yet been downloaded", message.getUid());
|
||||
|
||||
unsyncedMessages.add(message);
|
||||
} else {
|
||||
Timber.v("Message with uid %s is partially or fully downloaded", message.getUid());
|
||||
|
||||
// Store the updated message locally
|
||||
localFolder.appendMessages(Collections.singletonList(message));
|
||||
|
||||
localMessage = localFolder.getMessage(message.getUid());
|
||||
|
||||
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, message.isSet(Flag.X_DOWNLOADED_FULL));
|
||||
localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, message.isSet(Flag.X_DOWNLOADED_PARTIAL));
|
||||
|
||||
for (MessagingListener l : getListeners()) {
|
||||
if (!localMessage.isSet(Flag.SEEN)) {
|
||||
l.synchronizeMailboxNewMessage(account, folder, localMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!localMessage.isSet(Flag.DELETED)) {
|
||||
Timber.v("Message with uid %s is present in the local store", message.getUid());
|
||||
|
||||
if (!localMessage.isSet(Flag.X_DOWNLOADED_FULL) && !localMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
|
||||
Timber.v("Message with uid %s is not downloaded, even partially; trying again", message.getUid());
|
||||
|
||||
unsyncedMessages.add(message);
|
||||
} else {
|
||||
String newPushState = remoteFolder.getNewPushState(localFolder.getPushState(), message);
|
||||
if (newPushState != null) {
|
||||
localFolder.setPushState(newPushState);
|
||||
}
|
||||
syncFlagMessages.add(message);
|
||||
}
|
||||
} else {
|
||||
Timber.v("Local copy of message with uid %s is marked as deleted", message.getUid());
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Message> void fetchUnsyncedMessages(final Account account, final Folder<T> remoteFolder,
|
||||
List<T> unsyncedMessages,
|
||||
final List<Message> smallMessages,
|
||||
final List<Message> largeMessages,
|
||||
final AtomicInteger progress,
|
||||
final int todo,
|
||||
FetchProfile fp) throws MessagingException {
|
||||
final String folder = remoteFolder.getName();
|
||||
|
||||
final Date earliestDate = account.getEarliestPollDate();
|
||||
remoteFolder.fetch(unsyncedMessages, fp,
|
||||
new MessageRetrievalListener<T>() {
|
||||
@Override
|
||||
public void messageFinished(T message, int number, int ofTotal) {
|
||||
try {
|
||||
if (message.isSet(Flag.DELETED) || message.olderThan(earliestDate)) {
|
||||
if (K9.isDebug()) {
|
||||
if (message.isSet(Flag.DELETED)) {
|
||||
Timber.v("Newly downloaded message %s:%s:%s was marked deleted on server, " +
|
||||
"skipping", account, folder, message.getUid());
|
||||
} else {
|
||||
Timber.d("Newly downloaded message %s is older than %s, skipping",
|
||||
message.getUid(), earliestDate);
|
||||
}
|
||||
}
|
||||
progress.incrementAndGet();
|
||||
for (MessagingListener l : getListeners()) {
|
||||
//TODO: This might be the source of poll count errors in the UI. Is todo always the same as ofTotal
|
||||
l.synchronizeMailboxProgress(account, folder, progress.get(), todo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (account.getMaximumAutoDownloadMessageSize() > 0 &&
|
||||
message.getSize() > account.getMaximumAutoDownloadMessageSize()) {
|
||||
largeMessages.add(message);
|
||||
} else {
|
||||
smallMessages.add(message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Timber.e(e, "Error while storing downloaded message.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageStarted(String uid, int number, int ofTotal) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messagesFinished(int total) {
|
||||
// FIXME this method is almost never invoked by various Stores! Don't rely on it unless fixed!!
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private <T extends Message> void downloadSmallMessages(final Account account, final Folder<T> remoteFolder,
|
||||
final LocalFolder localFolder,
|
||||
List<T> smallMessages,
|
||||
final AtomicInteger progress,
|
||||
final int unreadBeforeStart,
|
||||
final AtomicInteger newMessages,
|
||||
final int todo,
|
||||
FetchProfile fp) throws MessagingException {
|
||||
final String folder = remoteFolder.getName();
|
||||
|
||||
final Date earliestDate = account.getEarliestPollDate();
|
||||
|
||||
Timber.d("SYNC: Fetching %d small messages for folder %s", smallMessages.size(), folder);
|
||||
|
||||
remoteFolder.fetch(smallMessages,
|
||||
fp, new MessageRetrievalListener<T>() {
|
||||
@Override
|
||||
public void messageFinished(final T message, int number, int ofTotal) {
|
||||
try {
|
||||
|
||||
if (!shouldImportMessage(account, message, earliestDate)) {
|
||||
progress.incrementAndGet();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the updated message locally
|
||||
final LocalMessage localMessage = localFolder.storeSmallMessage(message, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
progress.incrementAndGet();
|
||||
}
|
||||
});
|
||||
|
||||
// Increment the number of "new messages" if the newly downloaded message is
|
||||
// not marked as read.
|
||||
if (!localMessage.isSet(Flag.SEEN)) {
|
||||
newMessages.incrementAndGet();
|
||||
}
|
||||
|
||||
Timber.v("About to notify listeners that we got a new small message %s:%s:%s",
|
||||
account, folder, message.getUid());
|
||||
|
||||
// Update the listener with what we've found
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.synchronizeMailboxProgress(account, folder, progress.get(), todo);
|
||||
if (!localMessage.isSet(Flag.SEEN)) {
|
||||
l.synchronizeMailboxNewMessage(account, folder, localMessage);
|
||||
}
|
||||
}
|
||||
// Send a notification of this message
|
||||
|
||||
if (shouldNotifyForMessage(account, localFolder, message)) {
|
||||
// Notify with the localMessage so that we don't have to recalculate the content preview.
|
||||
notificationController.addNewMailNotification(account, localMessage, unreadBeforeStart);
|
||||
}
|
||||
|
||||
} catch (MessagingException me) {
|
||||
Timber.e(me, "SYNC: fetch small messages");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageStarted(String uid, int number, int ofTotal) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messagesFinished(int total) {
|
||||
}
|
||||
});
|
||||
|
||||
Timber.d("SYNC: Done fetching small messages for folder %s", folder);
|
||||
}
|
||||
|
||||
private <T extends Message> void downloadLargeMessages(final Account account, final Folder<T> remoteFolder,
|
||||
final LocalFolder localFolder,
|
||||
List<T> largeMessages,
|
||||
final AtomicInteger progress,
|
||||
final int unreadBeforeStart,
|
||||
final AtomicInteger newMessages,
|
||||
final int todo,
|
||||
FetchProfile fp) throws MessagingException {
|
||||
final String folder = remoteFolder.getName();
|
||||
final Date earliestDate = account.getEarliestPollDate();
|
||||
|
||||
Timber.d("SYNC: Fetching large messages for folder %s", folder);
|
||||
|
||||
remoteFolder.fetch(largeMessages, fp, null);
|
||||
for (T message : largeMessages) {
|
||||
|
||||
if (!shouldImportMessage(account, message, earliestDate)) {
|
||||
progress.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.getBody() == null) {
|
||||
downloadSaneBody(account, remoteFolder, localFolder, message);
|
||||
} else {
|
||||
downloadPartial(remoteFolder, localFolder, message);
|
||||
}
|
||||
|
||||
Timber.v("About to notify listeners that we got a new large message %s:%s:%s",
|
||||
account, folder, message.getUid());
|
||||
|
||||
// Update the listener with what we've found
|
||||
progress.incrementAndGet();
|
||||
// TODO do we need to re-fetch this here?
|
||||
LocalMessage localMessage = localFolder.getMessage(message.getUid());
|
||||
// Increment the number of "new messages" if the newly downloaded message is
|
||||
// not marked as read.
|
||||
if (!localMessage.isSet(Flag.SEEN)) {
|
||||
newMessages.incrementAndGet();
|
||||
}
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.synchronizeMailboxProgress(account, folder, progress.get(), todo);
|
||||
if (!localMessage.isSet(Flag.SEEN)) {
|
||||
l.synchronizeMailboxNewMessage(account, folder, localMessage);
|
||||
}
|
||||
}
|
||||
// Send a notification of this message
|
||||
if (shouldNotifyForMessage(account, localFolder, message)) {
|
||||
// Notify with the localMessage so that we don't have to recalculate the content preview.
|
||||
notificationController.addNewMailNotification(account, localMessage, unreadBeforeStart);
|
||||
}
|
||||
}
|
||||
|
||||
Timber.d("SYNC: Done fetching large messages for folder %s", folder);
|
||||
}
|
||||
|
||||
private void refreshLocalMessageFlags(final Account account, final Folder remoteFolder,
|
||||
final LocalFolder localFolder,
|
||||
List<Message> syncFlagMessages,
|
||||
final AtomicInteger progress,
|
||||
final int todo
|
||||
) throws MessagingException {
|
||||
|
||||
final String folder = remoteFolder.getName();
|
||||
if (remoteFolder.supportsFetchingFlags()) {
|
||||
Timber.d("SYNC: About to sync flags for %d remote messages for folder %s", syncFlagMessages.size(), folder);
|
||||
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.FLAGS);
|
||||
|
||||
List<Message> undeletedMessages = new LinkedList<>();
|
||||
for (Message message : syncFlagMessages) {
|
||||
if (!message.isSet(Flag.DELETED)) {
|
||||
undeletedMessages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
remoteFolder.fetch(undeletedMessages, fp, null);
|
||||
for (Message remoteMessage : syncFlagMessages) {
|
||||
LocalMessage localMessage = localFolder.getMessage(remoteMessage.getUid());
|
||||
boolean messageChanged = syncFlags(localMessage, remoteMessage);
|
||||
if (messageChanged) {
|
||||
boolean shouldBeNotifiedOf = false;
|
||||
if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(localMessage)) {
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.synchronizeMailboxRemovedMessage(account, folder, localMessage);
|
||||
}
|
||||
} else {
|
||||
if (shouldNotifyForMessage(account, localFolder, localMessage)) {
|
||||
shouldBeNotifiedOf = true;
|
||||
}
|
||||
}
|
||||
|
||||
// we're only interested in messages that need removing
|
||||
if (!shouldBeNotifiedOf) {
|
||||
MessageReference messageReference = localMessage.makeMessageReference();
|
||||
notificationController.removeNewMailNotification(account, messageReference);
|
||||
}
|
||||
}
|
||||
progress.incrementAndGet();
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.synchronizeMailboxProgress(account, folder, progress.get(), todo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadSaneBody(Account account, Folder remoteFolder, LocalFolder localFolder, Message message)
|
||||
throws MessagingException {
|
||||
/*
|
||||
* The provider was unable to get the structure of the message, so
|
||||
* we'll download a reasonable portion of the messge and mark it as
|
||||
* incomplete so the entire thing can be downloaded later if the user
|
||||
* wishes to download it.
|
||||
*/
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.BODY_SANE);
|
||||
/*
|
||||
* TODO a good optimization here would be to make sure that all Stores set
|
||||
* the proper size after this fetch and compare the before and after size. If
|
||||
* they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED
|
||||
*/
|
||||
|
||||
remoteFolder.fetch(Collections.singletonList(message), fp, null);
|
||||
|
||||
// Store the updated message locally
|
||||
localFolder.appendMessages(Collections.singletonList(message));
|
||||
|
||||
Message localMessage = localFolder.getMessage(message.getUid());
|
||||
|
||||
|
||||
// Certain (POP3) servers give you the whole message even when you ask for only the first x Kb
|
||||
if (!message.isSet(Flag.X_DOWNLOADED_FULL)) {
|
||||
/*
|
||||
* Mark the message as fully downloaded if the message size is smaller than
|
||||
* the account's autodownload size limit, otherwise mark as only a partial
|
||||
* download. This will prevent the system from downloading the same message
|
||||
* twice.
|
||||
*
|
||||
* If there is no limit on autodownload size, that's the same as the message
|
||||
* being smaller than the max size
|
||||
*/
|
||||
if (account.getMaximumAutoDownloadMessageSize() == 0
|
||||
|| message.getSize() < account.getMaximumAutoDownloadMessageSize()) {
|
||||
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||
} else {
|
||||
// Set a flag indicating that the message has been partially downloaded and
|
||||
// is ready for view.
|
||||
localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadPartial(Folder remoteFolder, LocalFolder localFolder, Message message)
|
||||
throws MessagingException {
|
||||
/*
|
||||
* We have a structure to deal with, from which
|
||||
* we can pull down the parts we want to actually store.
|
||||
* Build a list of parts we are interested in. Text parts will be downloaded
|
||||
* right now, attachments will be left for later.
|
||||
*/
|
||||
|
||||
Set<Part> viewables = MessageExtractor.collectTextParts(message);
|
||||
|
||||
/*
|
||||
* Now download the parts we're interested in storing.
|
||||
*/
|
||||
BodyFactory bodyFactory = new DefaultBodyFactory();
|
||||
for (Part part : viewables) {
|
||||
remoteFolder.fetchPart(message, part, null, bodyFactory);
|
||||
}
|
||||
// Store the updated message locally
|
||||
localFolder.appendMessages(Collections.singletonList(message));
|
||||
|
||||
Message localMessage = localFolder.getMessage(message.getUid());
|
||||
|
||||
// Set a flag indicating this message has been fully downloaded and can be
|
||||
// viewed.
|
||||
localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, true);
|
||||
}
|
||||
|
||||
private boolean syncFlags(LocalMessage localMessage, Message remoteMessage) throws MessagingException {
|
||||
boolean messageChanged = false;
|
||||
if (localMessage == null || localMessage.isSet(Flag.DELETED)) {
|
||||
return false;
|
||||
}
|
||||
if (remoteMessage.isSet(Flag.DELETED)) {
|
||||
if (localMessage.getFolder().syncRemoteDeletions()) {
|
||||
localMessage.setFlag(Flag.DELETED, true);
|
||||
messageChanged = true;
|
||||
}
|
||||
} else {
|
||||
for (Flag flag : MessagingController.SYNC_FLAGS) {
|
||||
if (remoteMessage.isSet(flag) != localMessage.isSet(flag)) {
|
||||
localMessage.setFlag(flag, remoteMessage.isSet(flag));
|
||||
messageChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return messageChanged;
|
||||
}
|
||||
|
||||
private boolean shouldImportMessage(final Account account, final Message message,
|
||||
final Date earliestDate) {
|
||||
|
||||
if (account.isSearchByDateCapable() && message.olderThan(earliestDate)) {
|
||||
Timber.d("Message %s is older than %s, hence not saving", message.getUid(), earliestDate);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void closeFolder(Folder folder) {
|
||||
if (folder != null) {
|
||||
folder.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Methods calling back to MessagingController
|
||||
*
|
||||
* TODO: Move all of these to an interface so we don't have to depend on MessagingController directly
|
||||
*/
|
||||
|
||||
private void processPendingCommandsSynchronous(Account account) throws MessagingException {
|
||||
controller.processPendingCommandsSynchronous(account);
|
||||
}
|
||||
|
||||
private Set<MessagingListener> getListeners() {
|
||||
return controller.getListeners();
|
||||
}
|
||||
|
||||
private Set<MessagingListener> getListeners(MessagingListener listener) {
|
||||
return controller.getListeners(listener);
|
||||
}
|
||||
|
||||
private void updateMoreMessages(Folder remoteFolder, LocalFolder localFolder, Date earliestDate, int remoteStart)
|
||||
throws IOException, MessagingException {
|
||||
controller.updateMoreMessages(remoteFolder, localFolder, earliestDate, remoteStart);
|
||||
}
|
||||
|
||||
private void handleAuthenticationFailure(Account account, boolean incoming) {
|
||||
controller.handleAuthenticationFailure(account, incoming);
|
||||
}
|
||||
|
||||
private void notifyUserIfCertificateProblem(Account account, Exception exception, boolean incoming) {
|
||||
controller.notifyUserIfCertificateProblem(account, exception, incoming);
|
||||
}
|
||||
|
||||
private boolean shouldNotifyForMessage(Account account, LocalFolder localFolder, Message message) {
|
||||
return controller.shouldNotifyForMessage(account, localFolder, message);
|
||||
}
|
||||
|
||||
private boolean isMessageSuppressed(LocalMessage message) {
|
||||
return controller.isMessageSuppressed(message);
|
||||
}
|
||||
}
|
|
@ -614,101 +614,102 @@ public class MessagingControllerTest {
|
|||
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withOneMessageInRemoteFolder_shouldFinishWithoutError()
|
||||
public void synchronizeMailboxSynchronousLegacy_withOneMessageInRemoteFolder_shouldFinishWithoutError()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(listener).synchronizeMailboxFinished(account, FOLDER_NAME, 1, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withEmptyRemoteFolder_shouldFinishWithoutError()
|
||||
public void synchronizeMailboxSynchronousLegacy_withEmptyRemoteFolder_shouldFinishWithoutError()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(0);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(listener).synchronizeMailboxFinished(account, FOLDER_NAME, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withNegativeMessageCountInRemoteFolder_shouldFinishWithError()
|
||||
public void synchronizeMailboxSynchronousLegacy_withNegativeMessageCountInRemoteFolder_shouldFinishWithError()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(-1);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(listener).synchronizeMailboxFailed(account, FOLDER_NAME,
|
||||
"Exception: Message count -1 for folder Folder");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withRemoteFolderProvided_shouldNotOpenRemoteFolder() throws Exception {
|
||||
public void synchronizeMailboxSynchronousLegacy_withRemoteFolderProvided_shouldNotOpenRemoteFolder()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(remoteFolder, never()).open(Folder.OPEN_MODE_RW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withNoRemoteFolderProvided_shouldOpenRemoteFolderFromStore()
|
||||
public void synchronizeMailboxSynchronousLegacy_withNoRemoteFolderProvided_shouldOpenRemoteFolderFromStore()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
configureRemoteStoreWithFolder();
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, null);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, null);
|
||||
|
||||
verify(remoteFolder).open(Folder.OPEN_MODE_RO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withRemoteFolderProvided_shouldNotCloseRemoteFolder() throws Exception {
|
||||
public void synchronizeMailboxSynchronousLegacy_withRemoteFolderProvided_shouldNotCloseRemoteFolder() throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(remoteFolder, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withNoRemoteFolderProvided_shouldCloseRemoteFolderFromStore()
|
||||
public void synchronizeMailboxSynchronousLegacy_withNoRemoteFolderProvided_shouldCloseRemoteFolderFromStore()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
configureRemoteStoreWithFolder();
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, null);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, null);
|
||||
|
||||
verify(remoteFolder).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withAccountPolicySetToExpungeOnPoll_shouldExpungeRemoteFolder()
|
||||
public void synchronizeMailboxSynchronousLegacy_withAccountPolicySetToExpungeOnPoll_shouldExpungeRemoteFolder()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
when(account.getExpungePolicy()).thenReturn(Account.Expunge.EXPUNGE_ON_POLL);
|
||||
configureRemoteStoreWithFolder();
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, null);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, null);
|
||||
|
||||
verify(remoteFolder).expunge();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withAccountPolicySetToExpungeManually_shouldNotExpungeRemoteFolder()
|
||||
public void synchronizeMailboxSynchronousLegacy_withAccountPolicySetToExpungeManually_shouldNotExpungeRemoteFolder()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
when(account.getExpungePolicy()).thenReturn(Account.Expunge.EXPUNGE_MANUALLY);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, null);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, null);
|
||||
|
||||
verify(remoteFolder, never()).expunge();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withAccountSetToSyncRemoteDeletions_shouldDeleteLocalCopiesOfDeletedMessages()
|
||||
public void synchronizeMailboxSynchronousLegacy_withAccountSetToSyncRemoteDeletions_shouldDeleteLocalCopiesOfDeletedMessages()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(0);
|
||||
LocalMessage localCopyOfRemoteDeletedMessage = mock(LocalMessage.class);
|
||||
|
@ -717,14 +718,14 @@ public class MessagingControllerTest {
|
|||
when(localFolder.getMessagesByUids(any(List.class)))
|
||||
.thenReturn(Collections.singletonList(localCopyOfRemoteDeletedMessage));
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(localFolder).destroyMessages(messageListCaptor.capture());
|
||||
assertEquals(localCopyOfRemoteDeletedMessage, messageListCaptor.getValue().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withAccountSetToSyncRemoteDeletions_shouldNotDeleteLocalCopiesOfExistingMessagesAfterEarliestPollDate()
|
||||
public void synchronizeMailboxSynchronousLegacy_withAccountSetToSyncRemoteDeletions_shouldNotDeleteLocalCopiesOfExistingMessagesAfterEarliestPollDate()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
Date dateOfEarliestPoll = new Date();
|
||||
|
@ -734,13 +735,13 @@ public class MessagingControllerTest {
|
|||
when(localMessage.olderThan(dateOfEarliestPoll)).thenReturn(false);
|
||||
when(localFolder.getMessages(null)).thenReturn(Collections.singletonList(localMessage));
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(localFolder, never()).destroyMessages(messageListCaptor.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withAccountSetToSyncRemoteDeletions_shouldDeleteLocalCopiesOfExistingMessagesBeforeEarliestPollDate()
|
||||
public void synchronizeMailboxSynchronousLegacy_withAccountSetToSyncRemoteDeletions_shouldDeleteLocalCopiesOfExistingMessagesBeforeEarliestPollDate()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
LocalMessage localMessage = localMessageWithCopyOnServer();
|
||||
|
@ -751,33 +752,33 @@ public class MessagingControllerTest {
|
|||
when(localFolder.getAllMessagesAndEffectiveDates()).thenReturn(Collections.singletonMap(MESSAGE_UID1, 0L));
|
||||
when(localFolder.getMessagesByUids(any(List.class))).thenReturn(Collections.singletonList(localMessage));
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(localFolder).destroyMessages(messageListCaptor.capture());
|
||||
assertEquals(localMessage, messageListCaptor.getValue().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withAccountSetNotToSyncRemoteDeletions_shouldNotDeleteLocalCopiesOfMessages()
|
||||
public void synchronizeMailboxSynchronousLegacy_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_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(localFolder, never()).destroyMessages(messageListCaptor.capture());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withAccountSupportingFetchingFlags_shouldFetchUnsychronizedMessagesListAndFlags()
|
||||
public void synchronizeMailboxSynchronousLegacy_withAccountSupportingFetchingFlags_shouldFetchUnsychronizedMessagesListAndFlags()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
hasUnsyncedRemoteMessage();
|
||||
when(remoteFolder.supportsFetchingFlags()).thenReturn(true);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(remoteFolder, atLeastOnce()).fetch(any(List.class), fetchProfileCaptor.capture(),
|
||||
any(MessageRetrievalListener.class));
|
||||
|
@ -787,13 +788,13 @@ public class MessagingControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withAccountNotSupportingFetchingFlags_shouldFetchUnsychronizedMessages()
|
||||
public void synchronizeMailboxSynchronousLegacy_withAccountNotSupportingFetchingFlags_shouldFetchUnsychronizedMessages()
|
||||
throws Exception {
|
||||
messageCountInRemoteFolder(1);
|
||||
hasUnsyncedRemoteMessage();
|
||||
when(remoteFolder.supportsFetchingFlags()).thenReturn(false);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(remoteFolder, atLeastOnce()).fetch(any(List.class), fetchProfileCaptor.capture(),
|
||||
any(MessageRetrievalListener.class));
|
||||
|
@ -802,7 +803,7 @@ public class MessagingControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withUnsyncedNewSmallMessage_shouldFetchBodyOfSmallMessage()
|
||||
public void synchronizeMailboxSynchronousLegacy_withUnsyncedNewSmallMessage_shouldFetchBodyOfSmallMessage()
|
||||
throws Exception {
|
||||
Message smallMessage = buildSmallNewMessage();
|
||||
messageCountInRemoteFolder(1);
|
||||
|
@ -810,7 +811,7 @@ public class MessagingControllerTest {
|
|||
when(remoteFolder.supportsFetchingFlags()).thenReturn(false);
|
||||
respondToFetchEnvelopesWithMessage(smallMessage);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
verify(remoteFolder, atLeast(2)).fetch(any(List.class), fetchProfileCaptor.capture(),
|
||||
any(MessageRetrievalListener.class));
|
||||
|
@ -819,7 +820,7 @@ public class MessagingControllerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void synchronizeMailboxSynchronous_withUnsyncedNewSmallMessage_shouldFetchStructureAndLimitedBodyOfLargeMessage()
|
||||
public void synchronizeMailboxSynchronousLegacy_withUnsyncedNewSmallMessage_shouldFetchStructureAndLimitedBodyOfLargeMessage()
|
||||
throws Exception {
|
||||
Message largeMessage = buildLargeNewMessage();
|
||||
messageCountInRemoteFolder(1);
|
||||
|
@ -827,7 +828,7 @@ public class MessagingControllerTest {
|
|||
when(remoteFolder.supportsFetchingFlags()).thenReturn(false);
|
||||
respondToFetchEnvelopesWithMessage(largeMessage);
|
||||
|
||||
controller.synchronizeMailboxSynchronous(account, FOLDER_NAME, listener, remoteFolder);
|
||||
controller.synchronizeMailboxSynchronousLegacy(account, FOLDER_NAME, listener, remoteFolder);
|
||||
|
||||
//TODO: Don't bother fetching messages of a size we don't have
|
||||
verify(remoteFolder, atLeast(4)).fetch(any(List.class), fetchProfileCaptor.capture(),
|
||||
|
|
Loading…
Reference in a new issue