diff --git a/res/values/arrays.xml b/res/values/arrays.xml index a265e7398..a76c3c918 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -66,6 +66,36 @@ 1000 + + @string/account_settings_message_age_any + @string/account_settings_message_age_0 + @string/account_settings_message_age_1 + @string/account_settings_message_age_2 + @string/account_settings_message_age_7 + @string/account_settings_message_age_14 + @string/account_settings_message_age_21 + @string/account_settings_message_age_1_month + @string/account_settings_message_age_2_months + @string/account_settings_message_age_3_months + @string/account_settings_message_age_6_months + @string/account_settings_message_age_1_year + + + + -1 + 0 + 1 + 2 + 7 + 14 + 21 + 28 + 56 + 84 + 168 + 365 + + @string/account_settings_folder_display_mode_all @string/account_settings_folder_display_mode_first_class diff --git a/res/values/strings.xml b/res/values/strings.xml index 857d109d1..37ede45c3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -446,6 +446,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin 500 messages 1000 messages + Cannot copy or move a message that is not synchronized with the server Setup could not finish @@ -492,6 +493,21 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Choose the color your your phone LED should blink for this account Number of messages to display + + Synchronize messages sent + At any time + Today + since yesterday + in the last 2 days + in the last week + in the last 2 weeks + in the last 3 weeks + in the last month + in the last 2 months + in the last 3 months + in the last 6 months + in the last year + Folders to display All diff --git a/res/xml/account_settings_preferences.xml b/res/xml/account_settings_preferences.xml index 0b52e8b5e..4b8d79f3b 100644 --- a/res/xml/account_settings_preferences.xml +++ b/res/xml/account_settings_preferences.xml @@ -39,7 +39,8 @@ android:title="@string/account_settings_mail_display_count_label" android:entries="@array/account_settings_display_count_entries" android:entryValues="@array/account_settings_display_count_values" - android:dialogTitle="@string/account_settings_mail_display_count_label" /> + android:dialogTitle="@string/account_settings_mail_display_count_label" /> + + + compressionMap = new ConcurrentHashMap(); private Searchable searchableFolders; private boolean subscribedFoldersOnly; + private int maximumPolledMessageAge; // Tracks if we have sent a notification for this account for // current set of fetched messages private boolean mRingNotified; @@ -142,6 +145,7 @@ public class Account implements BaseAccount mLedColor = mChipColor; goToUnreadMessageSearch = false; subscribedFoldersOnly = false; + maximumPolledMessageAge = 10; searchableFolders = Searchable.ALL; @@ -180,7 +184,7 @@ public class Account implements BaseAccount + ".saveAllHeaders", false); mPushPollOnConnect = preferences.getPreferences().getBoolean(mUuid + ".pushPollOnConnect", true); - mDisplayCount = preferences.getPreferences().getInt(mUuid + ".displayCount", -1); + mDisplayCount = preferences.getPreferences().getInt(mUuid + ".displayCount", K9.DEFAULT_VISIBLE_LIMIT); mLastAutomaticCheckTime = preferences.getPreferences().getLong(mUuid + ".lastAutomaticCheckTime", 0); mNotifyNewMail = preferences.getPreferences().getBoolean(mUuid + ".notifyNewMail", @@ -205,6 +209,8 @@ public class Account implements BaseAccount false); subscribedFoldersOnly = preferences.getPreferences().getBoolean(mUuid + ".subscribedFoldersOnly", false); + maximumPolledMessageAge = preferences.getPreferences().getInt(mUuid + + ".maximumPolledMessageAge", -1); for (String type : networkTypes) { Boolean useCompression = preferences.getPreferences().getBoolean(mUuid + ".useCompression." + type, @@ -375,6 +381,7 @@ public class Account implements BaseAccount editor.remove(mUuid + ".ledColor"); editor.remove(mUuid + ".goToUnreadMessageSearch"); editor.remove(mUuid + ".subscribedFoldersOnly"); + editor.remove(mUuid + ".maximumPolledMessageAge"); for (String type : networkTypes) { editor.remove(mUuid + ".useCompression." + type); @@ -459,7 +466,8 @@ public class Account implements BaseAccount editor.putInt(mUuid + ".ledColor", mLedColor); editor.putBoolean(mUuid + ".goToUnreadMessageSearch", goToUnreadMessageSearch); editor.putBoolean(mUuid + ".subscribedFoldersOnly", subscribedFoldersOnly); - + editor.putInt(mUuid + ".maximumPolledMessageAge", maximumPolledMessageAge); + for (String type : networkTypes) { Boolean useCompression = compressionMap.get(type); @@ -718,10 +726,6 @@ public class Account implements BaseAccount public synchronized int getDisplayCount() { - if (mDisplayCount == -1) - { - this.mDisplayCount = K9.DEFAULT_VISIBLE_LIMIT; - } return mDisplayCount; } @@ -1218,4 +1222,54 @@ public class Account implements BaseAccount { this.subscribedFoldersOnly = subscribedFoldersOnly; } + + public int getMaximumPolledMessageAge() + { + return maximumPolledMessageAge; + } + + public void setMaximumPolledMessageAge(int maximumPolledMessageAge) + { + this.maximumPolledMessageAge = maximumPolledMessageAge; + } + public Date getEarliestPollDate() + { + int age = getMaximumPolledMessageAge(); + if (age < 0 == false) + { + Calendar now = Calendar.getInstance(); + now.set(Calendar.HOUR_OF_DAY, 0); + now.set(Calendar.MINUTE, 0); + now.set(Calendar.SECOND, 0); + now.set(Calendar.MILLISECOND, 0); + if (age < 28) + { + now.add(Calendar.DATE, age * -1); + } + else switch (age) + { + case 28: + now.add(Calendar.MONTH, -1); + break; + case 56: + now.add(Calendar.MONTH, -2); + break; + case 84: + now.add(Calendar.MONTH, -3); + break; + case 168: + now.add(Calendar.MONTH, -6); + break; + case 365: + now.add(Calendar.YEAR, -1); + break; + } + + return now.getTime(); + } + else + { + return null; + } + } } diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java index f3a37c6c2..ff53b276b 100644 --- a/src/com/fsck/k9/activity/setup/AccountSettings.java +++ b/src/com/fsck/k9/activity/setup/AccountSettings.java @@ -52,6 +52,7 @@ public class AccountSettings extends K9PreferenceActivity private static final String PREFERENCE_CHIP_COLOR = "chip_color"; private static final String PREFERENCE_LED_COLOR = "led_color"; private static final String PREFERENCE_NOTIFICATION_OPENS_UNREAD = "notification_opens_unread"; + private static final String PREFERENCE_MESSAGE_AGE = "account_message_age"; @@ -60,6 +61,7 @@ public class AccountSettings extends K9PreferenceActivity private EditTextPreference mAccountDescription; private ListPreference mCheckFrequency; private ListPreference mDisplayCount; + private ListPreference mMessageAge; private CheckBoxPreference mAccountDefault; private CheckBoxPreference mAccountNotify; private CheckBoxPreference mAccountNotifySelf; @@ -267,6 +269,21 @@ public class AccountSettings extends K9PreferenceActivity return false; } }); + + mMessageAge = (ListPreference) findPreference(PREFERENCE_MESSAGE_AGE); + mMessageAge.setValue(String.valueOf(mAccount.getMaximumPolledMessageAge())); + mMessageAge.setSummary(mMessageAge.getEntry()); + mMessageAge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() + { + public boolean onPreferenceChange(Preference preference, Object newValue) + { + final String summary = newValue.toString(); + int index = mMessageAge.findIndexOfValue(summary); + mMessageAge.setSummary(mMessageAge.getEntries()[index]); + mMessageAge.setValue(summary); + return false; + } + }); mAccountDefault = (CheckBoxPreference) findPreference(PREFERENCE_DEFAULT); mAccountDefault.setChecked( @@ -417,6 +434,7 @@ public class AccountSettings extends K9PreferenceActivity mAccount.setNotifySelfNewMail(mAccountNotifySelf.isChecked()); mAccount.setShowOngoing(mAccountNotifySync.isChecked()); mAccount.setDisplayCount(Integer.parseInt(mDisplayCount.getValue())); + mAccount.setMaximumPolledMessageAge(Integer.parseInt(mMessageAge.getValue())); mAccount.setVibrate(mAccountVibrate.isChecked()); mAccount.setGoToUnreadMessageSearch(mNotificationOpensUnread.isChecked()); mAccount.setFolderTargetMode(Account.FolderMode.valueOf(mTargetMode.getValue())); diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 6e2e2178f..016e951eb 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -4,6 +4,7 @@ package com.fsck.k9.controller; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -1139,16 +1140,17 @@ public class MessagingController implements Runnable Log.v(K9.LOG_TAG, "SYNC: About to open remote folder " + folder); remoteFolder.open(OpenMode.READ_WRITE); + if (Account.EXPUNGE_ON_POLL.equals(account.getExpungePolicy())) + { + if (K9.DEBUG) + Log.d(K9.LOG_TAG, "SYNC: Expunging folder " + account.getDescription() + ":" + folder); + + + remoteFolder.expunge(); + } + } - if (Account.EXPUNGE_ON_POLL.equals(account.getExpungePolicy())) - { - if (K9.DEBUG) - Log.d(K9.LOG_TAG, "SYNC: Expunging folder " + account.getDescription() + ":" + folder); - - remoteFolder.expunge(); - } - - + /* * Get the remote message count. */ @@ -1163,7 +1165,7 @@ public class MessagingController implements Runnable if (K9.DEBUG) Log.v(K9.LOG_TAG, "SYNC: Remote message count for folder " + folder + " is " + remoteMessageCount); - + final Date earliestDate = account.getEarliestPollDate(); if (remoteMessageCount > 0) { /* @@ -1175,11 +1177,15 @@ public class MessagingController implements Runnable if (K9.DEBUG) Log.v(K9.LOG_TAG, "SYNC: About to get messages " + remoteStart + " through " + remoteEnd + " for folder " + folder); - remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteEnd, null); + remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteEnd, earliestDate, null); for (Message thisMess : remoteMessageArray) { - remoteMessages.add(thisMess); - remoteUidMap.put(thisMess.getUid(), thisMess); + Message localMessage = localUidMap.get(thisMess.getUid()); + if (localMessage == null || localMessage.olderThan(earliestDate) == false) + { + remoteMessages.add(thisMess); + remoteUidMap.put(thisMess.getUid(), thisMess); + } } if (K9.DEBUG) Log.v(K9.LOG_TAG, "SYNC: Got " + remoteUidMap.size() + " messages for folder " + folder); @@ -1193,8 +1199,9 @@ public class MessagingController implements Runnable } /* - * Remove any messages that are in the local store but no longer on the remote store. + * Remove any messages that are in the local store but no longer on the remote store or are too old */ + for (Message localMessage : localMessages) { if (remoteUidMap.get(localMessage.getUid()) == null && !localMessage.isSet(Flag.DELETED)) @@ -1370,6 +1377,14 @@ public class MessagingController implements Runnable private int downloadMessages(final Account account, final Folder remoteFolder, final LocalFolder localFolder, List inputMessages, boolean flagSyncOnly) throws MessagingException { + final Date earliestDate = account.getEarliestPollDate(); + if (earliestDate != null) + { + if (K9.DEBUG) + { + Log.d(K9.LOG_TAG, "Only syncing messages after " + earliestDate); + } + } final String folder = remoteFolder.getName(); ArrayList syncFlagMessages = new ArrayList(); @@ -1500,14 +1515,31 @@ public class MessagingController implements Runnable { localFolder.setPushState(newPushState); } - if (message.isSet(Flag.DELETED)) + if (message.isSet(Flag.DELETED) || message.olderThan(earliestDate)) { + if (K9.DEBUG) - Log.v(K9.LOG_TAG, "Newly downloaded message " + account + ":" + folder + ":" + message.getUid() - + " was already deleted on server, skipping"); + { + if (message.isSet(Flag.DELETED)) + { + Log.v(K9.LOG_TAG, "Newly downloaded message " + account + ":" + folder + ":" + message.getUid() + + " was already deleted on server, skipping"); + } + else + { + Log.d(K9.LOG_TAG, "Newly downloaded message " + message.getUid() + " is older than " + + earliestDate + ", skipping"); + } + } + progress.incrementAndGet(); + for (MessagingListener l : getListeners()) + { + l.synchronizeMailboxProgress(account, folder, progress.get(), todo); + } return; } - + + // Store the new message locally localFolder.appendMessages(new Message[] { @@ -1611,10 +1643,23 @@ public class MessagingController implements Runnable { try { + if (message.olderThan(earliestDate)) + { + if (K9.DEBUG) + { + Log.d(K9.LOG_TAG, "Message " + message.getUid() + " is older than " + + earliestDate + ", hence not saving"); + } + progress.incrementAndGet(); + + return; + } + // Store the updated message locally localFolder.appendMessages(new Message[] { message }); Message localMessage = localFolder.getMessage(message.getUid()); + progress.incrementAndGet(); // Set a flag indicating this message has now be fully downloaded localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); @@ -1622,8 +1667,6 @@ public class MessagingController implements Runnable Log.v(K9.LOG_TAG, "About to notify listeners that we got a new small message " + account + ":" + folder + ":" + message.getUid()); - progress.incrementAndGet(); - // Update the listener with what we've found for (MessagingListener l : getListeners()) { @@ -1677,6 +1720,17 @@ public class MessagingController implements Runnable remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), fp, null); for (Message message : largeMessages) { + if (message.olderThan(earliestDate)) + { + if (K9.DEBUG) + { + Log.d(K9.LOG_TAG, "Message " + message.getUid() + " is older than " + + earliestDate + ", hence not saving"); + } + progress.incrementAndGet(); + + continue; + } if (message.getBody() == null) { /* @@ -1694,6 +1748,7 @@ public class MessagingController implements Runnable */ remoteFolder.fetch(new Message[] { message }, fp, null); + // Store the updated message locally localFolder.appendMessages(new Message[] { message }); diff --git a/src/com/fsck/k9/mail/Folder.java b/src/com/fsck/k9/mail/Folder.java index fc7a57ce5..ce2aa9f1c 100644 --- a/src/com/fsck/k9/mail/Folder.java +++ b/src/com/fsck/k9/mail/Folder.java @@ -1,5 +1,7 @@ package com.fsck.k9.mail; +import java.util.Date; + import com.fsck.k9.Account; import com.fsck.k9.Preferences; import com.fsck.k9.controller.MessageRetrievalListener; @@ -82,7 +84,7 @@ public abstract class Folder public abstract Message getMessage(String uid) throws MessagingException; - public abstract Message[] getMessages(int start, int end, MessageRetrievalListener listener) + public abstract Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException; /** diff --git a/src/com/fsck/k9/mail/Message.java b/src/com/fsck/k9/mail/Message.java index fa9ea2981..435ff511c 100644 --- a/src/com/fsck/k9/mail/Message.java +++ b/src/com/fsck/k9/mail/Message.java @@ -5,6 +5,9 @@ import java.util.Date; import java.util.HashSet; import java.util.Set; +import android.util.Log; + +import com.fsck.k9.K9; import com.fsck.k9.activity.MessageReference; public abstract class Message implements Part, Body @@ -23,7 +26,28 @@ public abstract class Message implements Part, Body protected Date mInternalDate; protected Folder mFolder; - + + public boolean olderThan(Date earliestDate) + { + if (earliestDate == null) + { + return false; + } + Date myDate = getSentDate(); + if (myDate == null) + { + myDate = getInternalDate(); + } + if (myDate == null) + { + myDate = getReceivedDate(); + } + if (myDate != null) + { + return myDate.before(earliestDate); + } + return false; + } @Override public boolean equals(Object o) { @@ -79,9 +103,9 @@ public abstract class Message implements Part, Body this.mInternalDate = internalDate; } - public abstract Date getReceivedDate() throws MessagingException; + public abstract Date getReceivedDate(); - public abstract Date getSentDate() throws MessagingException; + public abstract Date getSentDate(); public abstract void setSentDate(Date sentDate) throws MessagingException; diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java index f2e0858ea..8c59b7a3d 100644 --- a/src/com/fsck/k9/mail/internet/MimeMessage.java +++ b/src/com/fsck/k9/mail/internet/MimeMessage.java @@ -76,13 +76,13 @@ public class MimeMessage extends Message } @Override - public Date getReceivedDate() throws MessagingException + public Date getReceivedDate() { return null; } @Override - public Date getSentDate() throws MessagingException + public Date getSentDate() { if (mSentDate == null) { diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index c48f6c048..262e95be2 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -43,6 +43,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.Security; +import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -91,6 +92,8 @@ public class ImapStore extends Store private volatile String mPathPrefix; private volatile String mCombinedPrefix = null; private volatile String mPathDelimeter = null; + + private static final SimpleDateFormat RFC3501_DATE = new SimpleDateFormat("dd-MMM-yyyy"); private LinkedList mConnections = new LinkedList(); @@ -1000,13 +1003,13 @@ public class ImapStore extends Store @Override - public Message[] getMessages(int start, int end, MessageRetrievalListener listener) + public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException { - return getMessages(start, end, false, listener); + return getMessages(start, end, earliestDate, false, listener); } - protected Message[] getMessages(final int start, final int end, final boolean includeDeleted, final MessageRetrievalListener listener) + protected Message[] getMessages(final int start, final int end, Date earliestDate, final boolean includeDeleted, final MessageRetrievalListener listener) throws MessagingException { if (start < 1 || end < 1 || end < start) @@ -1015,11 +1018,22 @@ public class ImapStore extends Store String.format("Invalid message set %d %d", start, end)); } + final StringBuilder dateSearchString = new StringBuilder(); + if (earliestDate != null) + { + dateSearchString.append("SINCE "); + synchronized(RFC3501_DATE) + { + dateSearchString.append(RFC3501_DATE.format(earliestDate)); + } + } + + ImapSearcher searcher = new ImapSearcher() { public List search() throws IOException, MessagingException { - return executeSimpleCommand(String.format("UID SEARCH %d:%d" + (includeDeleted ? "" : " NOT DELETED"), start, end)); + return executeSimpleCommand(String.format("UID SEARCH %d:%d %s " + (includeDeleted ? "" : " NOT DELETED"), start, end, dateSearchString)); } }; return search(searcher, listener); @@ -2008,7 +2022,10 @@ public class ImapStore extends Store throws MessagingException { Log.e(K9.LOG_TAG, "IOException for " + getLogId(), ioe); - connection.close(); + if (connection != null) + { + connection.close(); + } close(); return new MessagingException("IO Error", ioe); } @@ -3099,7 +3116,7 @@ public class ImapStore extends Store Log.e(K9.LOG_TAG, "Unable to get oldUidNext for " + getLogId(), e); } - Message[] messageArray = getMessages(end, end, true, null); + Message[] messageArray = getMessages(end, end, null, true, null); if (messageArray != null && messageArray.length > 0) { int newUid = Integer.parseInt(messageArray[0].getUid()); diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 887514527..a60539799 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -534,7 +534,7 @@ public class LocalStore extends Store implements Serializable public void resetVisibleLimits() { - resetVisibleLimits(K9.DEFAULT_VISIBLE_LIMIT); + resetVisibleLimits(mAccount.getDisplayCount()); } public void resetVisibleLimits(int visibleLimit) @@ -962,7 +962,7 @@ public class LocalStore extends Store implements Serializable mDb.execSQL("INSERT INTO folders (name, visible_limit) VALUES (?, ?)", new Object[] { mName, - K9.DEFAULT_VISIBLE_LIMIT + mAccount.getDisplayCount() }); return true; } @@ -1445,7 +1445,7 @@ public class LocalStore extends Store implements Serializable } @Override - public Message[] getMessages(int start, int end, MessageRetrievalListener listener) + public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException { open(OpenMode.READ_WRITE); diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index fea1c389b..170fc3b40 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -20,6 +20,7 @@ import java.net.*; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Date; import java.util.LinkedList; import java.util.HashMap; import java.util.HashSet; @@ -435,7 +436,7 @@ public class Pop3Store extends Store } @Override - public Message[] getMessages(int start, int end, MessageRetrievalListener listener) + public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException { if (start < 1 || end < 1 || end < start) diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index 54db8526b..cf4db4484 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -1393,7 +1393,7 @@ public class WebDavStore extends Store } @Override - public Message[] getMessages(int start, int end, MessageRetrievalListener listener) + public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException { ArrayList messages = new ArrayList();