diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java index 7fa0885eb..7e5d29fdb 100644 --- a/src/com/fsck/k9/K9.java +++ b/src/com/fsck/k9/K9.java @@ -294,6 +294,8 @@ public class K9 extends Application { public static final String IDENTITY_HEADER = "X-K9mail-Identity"; + public static final String SPAM_FLAG_HEADER = "X-Spam-Flag"; + /** * Specifies how many messages will be shown in a folder by default. This number is set * on each new folder and can be incremented with "Load more messages..." by the diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 50c0e868f..6b335f996 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -1256,6 +1256,7 @@ public class MessagingController implements Runnable { messages.clear(); final ArrayList largeMessages = new ArrayList(); final ArrayList smallMessages = new ArrayList(); + final Map spamMessages = new HashMap(); if (!unsyncedMessages.isEmpty()) { /* @@ -1280,7 +1281,33 @@ public class MessagingController implements Runnable { Log.d(K9.LOG_TAG, "SYNC: About to fetch " + unsyncedMessages.size() + " unsynced messages for folder " + folder); - fetchUnsyncedMessages(account, remoteFolder, localFolder, unsyncedMessages, smallMessages, largeMessages, progress, todo, fp); + fetchUnsyncedMessages(account, remoteFolder, localFolder, unsyncedMessages, smallMessages, largeMessages, spamMessages, progress, todo, fp); + + if (spamMessages.size() > 0) { + if (K9.DEBUG) { + Log.d(K9.LOG_TAG, "Moving " + spamMessages.size() + + " messages to the Spam folder."); + } + + queueMoveOrCopy(account, localFolder.getName(), account.getSpamFolderName(), false, + spamMessages.keySet().toArray(EMPTY_STRING_ARRAY), spamMessages); + spamMessages.clear(); + + try { + processPendingCommandsSynchronous(account); + } catch (Exception e) { + addErrorMessage(account, null, e); + Log.e(K9.LOG_TAG, "Failure processing command, but continuing message sync attempt", e); + } + + // FIXME: This is bad! + // ImapStore is using a cache for ImapFolder instances which causes our remoteFolder + // object to be used and later closed by processPendingMoveOrCopy(). So we have to + // re-open it here. + if (!remoteFolder.isOpen()) { + remoteFolder.open(OpenMode.READ_WRITE); + } + } // If a message didn't exist, messageFinished won't be called, but we shouldn't try again // If we got here, nothing failed @@ -1433,6 +1460,7 @@ public class MessagingController implements Runnable { List unsyncedMessages, final ArrayList smallMessages, final ArrayList largeMessages, + final Map spamMessages, final AtomicInteger progress, final int todo, FetchProfile fp) throws MessagingException { @@ -1445,6 +1473,11 @@ public class MessagingController implements Runnable { */ final List chunk = new ArrayList(UNSYNC_CHUNK_SIZE); + final String spamFolderName = account.getSpamFolderName(); + final LocalFolder spamFolder = account.getLocalStore().getFolder(spamFolderName); + final boolean isSpamFilterEnabled = (account.isSpamAssassinFilterEnabled() && + !spamFolder.equals(localFolder)); + remoteFolder.fetch(unsyncedMessages.toArray(EMPTY_MESSAGE_ARRAY), fp, new MessageRetrievalListener() { @Override @@ -1472,6 +1505,15 @@ public class MessagingController implements Runnable { return; } + if (isSpamFilterEnabled && isSpam(message)) { + // Write message to the local Spam folder + Map uidMap = spamFolder.insertAsLocalMessages( + new Message[] { message }); + + spamMessages.putAll(uidMap); + return; + } + if (account.getMaximumAutoDownloadMessageSize() > 0 && message.getSize() > account.getMaximumAutoDownloadMessageSize()) { largeMessages.add(message); @@ -1518,6 +1560,11 @@ public class MessagingController implements Runnable { } } + private boolean isSpam(Message message) throws MessagingException { + String[] headers = message.getHeader(K9.SPAM_FLAG_HEADER); + return (headers != null && headers.length > 0 && headers[0].equalsIgnoreCase("YES")); + } + /** * Actual storing of messages * diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 9bca9990e..1cc63786c 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -1492,7 +1492,8 @@ public class ImapStore extends Store { fetchFields.add("INTERNALDATE"); fetchFields.add("RFC822.SIZE"); fetchFields.add("BODY.PEEK[HEADER.FIELDS (date subject from content-type to cc " + - "reply-to message-id references in-reply-to " + K9.IDENTITY_HEADER + ")]"); + "reply-to message-id references in-reply-to " + K9.SPAM_FLAG_HEADER + + " " + K9.IDENTITY_HEADER + ")]"); } if (fp.contains(FetchProfile.Item.STRUCTURE)) { fetchFields.add("BODYSTRUCTURE"); diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 91d115aec..a991e0228 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -2130,7 +2130,7 @@ public class LocalStore extends Store implements Serializable { if (!(folder instanceof LocalFolder)) { throw new MessagingException("copyMessages called with incorrect Folder"); } - return ((LocalFolder) folder).appendMessages(msgs, true); + return ((LocalFolder) folder).appendMessages(msgs, true, false); } @Override @@ -2299,7 +2299,7 @@ public class LocalStore extends Store implements Serializable { */ @Override public Map appendMessages(Message[] messages) throws MessagingException { - return appendMessages(messages, false); + return appendMessages(messages, false, false); } public void destroyMessages(final Message[] messages) { @@ -2389,6 +2389,11 @@ public class LocalStore extends Store implements Serializable { return null; } + public Map insertAsLocalMessages(Message[] messages) + throws MessagingException { + return appendMessages(messages, false, true); + } + /** * The method differs slightly from the contract; If an incoming message already has a uid * assigned and it matches the uid of an existing message then this message will replace @@ -2401,9 +2406,14 @@ public class LocalStore extends Store implements Serializable { * message, retrieve the appropriate local message instance first (if it already exists). * @param messages * @param copy + * @param createAsLocal + * If {@code true} the message will get a local UID. The mapping is added to the map + * that is returned by this method. + * * @return Map uidMap of srcUids -> destUids */ - private Map appendMessages(final Message[] messages, final boolean copy) throws MessagingException { + private Map appendMessages(final Message[] messages, final boolean copy, + final boolean createAsLocal) throws MessagingException { open(OpenMode.READ_WRITE); try { final Map uidMap = new HashMap(); @@ -2418,14 +2428,14 @@ public class LocalStore extends Store implements Serializable { long oldMessageId = -1; String uid = message.getUid(); - if (uid == null || copy) { + if (uid == null || copy || createAsLocal) { /* * Create a new message in the database */ String randomLocalUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); - if (copy) { + if (copy || createAsLocal) { // Save mapping: source UID -> target UID uidMap.put(uid, randomLocalUid); } else {