mirror of
https://github.com/moparisthebest/k-9
synced 2025-02-17 07:30:16 -05:00
Add a caching layer to EmailProvider
Database updates can be surprisingly slow. This lead to slow updates of the user interface which in turn made working with K-9 Mail not as fun as it should be. This commit hopefully changes that.
This commit is contained in:
parent
9287d97626
commit
50fcff76fd
179
src/com/fsck/k9/cache/EmailProviderCache.java
vendored
Normal file
179
src/com/fsck/k9/cache/EmailProviderCache.java
vendored
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package com.fsck.k9.cache;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import com.fsck.k9.fragment.MessageListFragment;
|
||||||
|
import com.fsck.k9.mail.Message;
|
||||||
|
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||||
|
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
||||||
|
import com.fsck.k9.provider.EmailProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache to bridge the time needed to write (user-initiated) changes to the database.
|
||||||
|
*/
|
||||||
|
public class EmailProviderCache {
|
||||||
|
public static final String ACTION_CACHE_UPDATED = "EmailProviderCache.ACTION_CACHE_UPDATED";
|
||||||
|
|
||||||
|
private static Context sContext;
|
||||||
|
private static Map<String, EmailProviderCache> sInstances =
|
||||||
|
new HashMap<String, EmailProviderCache>();
|
||||||
|
|
||||||
|
public static synchronized EmailProviderCache getCache(String accountUuid, Context context) {
|
||||||
|
|
||||||
|
if (sContext == null) {
|
||||||
|
sContext = context.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmailProviderCache instance = sInstances.get(accountUuid);
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new EmailProviderCache(accountUuid);
|
||||||
|
sInstances.put(accountUuid, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String mAccountUuid;
|
||||||
|
private Map<Long, Map<String, String>> mMessageCache = new HashMap<Long, Map<String, String>>();
|
||||||
|
private Map<Long, Map<String, String>> mThreadCache = new HashMap<Long, Map<String, String>>();
|
||||||
|
private Map<Long, Long> mHiddenMessageCache = new HashMap<Long, Long>();
|
||||||
|
|
||||||
|
|
||||||
|
private EmailProviderCache(String accountUuid) {
|
||||||
|
mAccountUuid = accountUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValueForMessage(Long messageId, String columnName) {
|
||||||
|
synchronized (mMessageCache) {
|
||||||
|
Map<String, String> map = mMessageCache.get(messageId);
|
||||||
|
return (map == null) ? null : map.get(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValueForThread(Long threadRootId, String columnName) {
|
||||||
|
synchronized (mThreadCache) {
|
||||||
|
Map<String, String> map = mThreadCache.get(threadRootId);
|
||||||
|
return (map == null) ? null : map.get(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueForMessages(List<Long> messageIds, String columnName, String value) {
|
||||||
|
synchronized (mMessageCache) {
|
||||||
|
for (Long messageId : messageIds) {
|
||||||
|
Map<String, String> map = mMessageCache.get(messageId);
|
||||||
|
if (map == null) {
|
||||||
|
map = new HashMap<String, String>();
|
||||||
|
mMessageCache.put(messageId, map);
|
||||||
|
}
|
||||||
|
map.put(columnName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueForThreads(List<Long> threadRootIds, String columnName, String value) {
|
||||||
|
synchronized (mThreadCache) {
|
||||||
|
for (Long threadRootId : threadRootIds) {
|
||||||
|
Map<String, String> map = mThreadCache.get(threadRootId);
|
||||||
|
if (map == null) {
|
||||||
|
map = new HashMap<String, String>();
|
||||||
|
mThreadCache.put(threadRootId, map);
|
||||||
|
}
|
||||||
|
map.put(columnName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeValueForMessages(List<Long> messageIds, String columnName) {
|
||||||
|
synchronized (mMessageCache) {
|
||||||
|
for (Long messageId : messageIds) {
|
||||||
|
Map<String, String> map = mMessageCache.get(messageId);
|
||||||
|
if (map != null) {
|
||||||
|
map.remove(columnName);
|
||||||
|
if (map.size() == 0) {
|
||||||
|
mMessageCache.remove(messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeValueForThreads(List<Long> threadRootIds, String columnName) {
|
||||||
|
synchronized (mThreadCache) {
|
||||||
|
for (Long threadRootId : threadRootIds) {
|
||||||
|
Map<String, String> map = mThreadCache.get(threadRootId);
|
||||||
|
if (map != null) {
|
||||||
|
map.remove(columnName);
|
||||||
|
if (map.size() == 0) {
|
||||||
|
mThreadCache.remove(threadRootId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideMessages(List<Message> messages) {
|
||||||
|
synchronized (mHiddenMessageCache) {
|
||||||
|
for (Message message : messages) {
|
||||||
|
LocalMessage localMessage = (LocalMessage) message;
|
||||||
|
long messageId = localMessage.getId();
|
||||||
|
long folderId = ((LocalFolder) localMessage.getFolder()).getId();
|
||||||
|
mHiddenMessageCache.put(messageId, folderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMessageHidden(Long messageId, long folderId) {
|
||||||
|
synchronized (mHiddenMessageCache) {
|
||||||
|
Long hiddenInFolder = mHiddenMessageCache.get(messageId);
|
||||||
|
return (hiddenInFolder != null && hiddenInFolder.longValue() == folderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unhideMessages(Message[] messages) {
|
||||||
|
synchronized (mHiddenMessageCache) {
|
||||||
|
for (Message message : messages) {
|
||||||
|
LocalMessage localMessage = (LocalMessage) message;
|
||||||
|
long messageId = localMessage.getId();
|
||||||
|
long folderId = ((LocalFolder) localMessage.getFolder()).getId();
|
||||||
|
Long hiddenInFolder = mHiddenMessageCache.get(messageId);
|
||||||
|
|
||||||
|
if (hiddenInFolder != null && hiddenInFolder.longValue() == folderId) {
|
||||||
|
mHiddenMessageCache.remove(messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify all concerned parties that the message list has changed.
|
||||||
|
*
|
||||||
|
* <p><strong>Note:</strong>
|
||||||
|
* Notifying the content resolver of the change will cause the {@code CursorLoader} in
|
||||||
|
* {@link MessageListFragment} to reload the cursor. But especially with flag changes this will
|
||||||
|
* block because of the DB write operation to update the flags. So additionally we use
|
||||||
|
* {@link LocalBroadcastManager} to send a {@link #ACTION_CACHE_UPDATED} broadcast. This way
|
||||||
|
* {@code MessageListFragment} can update the view without reloading the cursor.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private void notifyChange() {
|
||||||
|
LocalBroadcastManager.getInstance(sContext).sendBroadcast(new Intent(ACTION_CACHE_UPDATED));
|
||||||
|
|
||||||
|
Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + mAccountUuid +
|
||||||
|
"/messages");
|
||||||
|
sContext.getContentResolver().notifyChange(uri, null);
|
||||||
|
}
|
||||||
|
}
|
129
src/com/fsck/k9/cache/EmailProviderCacheCursor.java
vendored
Normal file
129
src/com/fsck/k9/cache/EmailProviderCacheCursor.java
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package com.fsck.k9.cache;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fsck.k9.provider.EmailProvider.MessageColumns;
|
||||||
|
import com.fsck.k9.provider.EmailProvider.ThreadColumns;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.CursorWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link CursorWrapper} that utilizes {@link EmailProviderCache}.
|
||||||
|
*/
|
||||||
|
public class EmailProviderCacheCursor extends CursorWrapper {
|
||||||
|
private EmailProviderCache mCache;
|
||||||
|
private List<Integer> mHiddenRows = new ArrayList<Integer>();
|
||||||
|
private int mMessageIdColumn;
|
||||||
|
private int mFolderIdColumn;
|
||||||
|
private int mThreadRootColumn;
|
||||||
|
private int mPosition;
|
||||||
|
|
||||||
|
|
||||||
|
public EmailProviderCacheCursor(String accountUuid, Cursor cursor, Context context) {
|
||||||
|
super(cursor);
|
||||||
|
|
||||||
|
mCache = EmailProviderCache.getCache(accountUuid, context);
|
||||||
|
|
||||||
|
mMessageIdColumn = cursor.getColumnIndex(MessageColumns.ID);
|
||||||
|
mFolderIdColumn = cursor.getColumnIndex(MessageColumns.FOLDER_ID);
|
||||||
|
mThreadRootColumn = cursor.getColumnIndex(ThreadColumns.ROOT);
|
||||||
|
|
||||||
|
if (mMessageIdColumn == -1 || mFolderIdColumn == -1 || mThreadRootColumn == -1) {
|
||||||
|
throw new IllegalArgumentException("The supplied cursor needs to contain the " +
|
||||||
|
"following columns: " + MessageColumns.ID + ", " + MessageColumns.FOLDER_ID +
|
||||||
|
", " + ThreadColumns.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
long messageId = cursor.getLong(mMessageIdColumn);
|
||||||
|
long folderId = cursor.getLong(mFolderIdColumn);
|
||||||
|
if (mCache.isMessageHidden(messageId, folderId)) {
|
||||||
|
mHiddenRows.add(cursor.getPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the cursor position
|
||||||
|
cursor.moveToFirst();
|
||||||
|
cursor.moveToPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(int columnIndex) {
|
||||||
|
long messageId = getLong(mMessageIdColumn);
|
||||||
|
long threadRootId = getLong(mThreadRootColumn);
|
||||||
|
|
||||||
|
String columnName = getColumnName(columnIndex);
|
||||||
|
String value = mCache.getValueForMessage(messageId, columnName);
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
return Integer.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = mCache.getValueForThread(threadRootId, columnName);
|
||||||
|
if (value != null) {
|
||||||
|
return Integer.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getInt(columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return super.getCount() - mHiddenRows.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToFirst() {
|
||||||
|
return moveToPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToLast() {
|
||||||
|
return moveToPosition(getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToNext() {
|
||||||
|
return moveToPosition(getPosition() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToPrevious() {
|
||||||
|
return moveToPosition(getPosition() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean move(int offset) {
|
||||||
|
return moveToPosition(getPosition() + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean moveToPosition(int position) {
|
||||||
|
if (mHiddenRows.size() == 0) {
|
||||||
|
return super.moveToPosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
mPosition = position;
|
||||||
|
int newPosition = position;
|
||||||
|
for (int hiddenRow : mHiddenRows) {
|
||||||
|
if (hiddenRow > newPosition) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
newPosition++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.moveToPosition(newPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPosition() {
|
||||||
|
if (mHiddenRows.size() == 0) {
|
||||||
|
return super.getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPosition;
|
||||||
|
}
|
||||||
|
}
|
@ -53,6 +53,7 @@ import com.fsck.k9.activity.MessageReference;
|
|||||||
import com.fsck.k9.activity.NotificationDeleteConfirmation;
|
import com.fsck.k9.activity.NotificationDeleteConfirmation;
|
||||||
import com.fsck.k9.activity.setup.AccountSetupIncoming;
|
import com.fsck.k9.activity.setup.AccountSetupIncoming;
|
||||||
import com.fsck.k9.activity.setup.AccountSetupOutgoing;
|
import com.fsck.k9.activity.setup.AccountSetupOutgoing;
|
||||||
|
import com.fsck.k9.cache.EmailProviderCache;
|
||||||
import com.fsck.k9.helper.Contacts;
|
import com.fsck.k9.helper.Contacts;
|
||||||
import com.fsck.k9.helper.power.TracingPowerManager;
|
import com.fsck.k9.helper.power.TracingPowerManager;
|
||||||
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
|
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
|
||||||
@ -199,9 +200,6 @@ public class MessagingController implements Runnable {
|
|||||||
*/
|
*/
|
||||||
private Application mApplication;
|
private Application mApplication;
|
||||||
|
|
||||||
// Key is accountUuid:folderName:messageUid , value is unimportant
|
|
||||||
private ConcurrentHashMap<String, String> deletedUids = new ConcurrentHashMap<String, String>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A holder class for pending notification data
|
* A holder class for pending notification data
|
||||||
*
|
*
|
||||||
@ -320,45 +318,69 @@ public class MessagingController implements Runnable {
|
|||||||
|
|
||||||
private static final Flag[] SYNC_FLAGS = new Flag[] { Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED };
|
private static final Flag[] SYNC_FLAGS = new Flag[] { Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED };
|
||||||
|
|
||||||
private String createMessageKey(Account account, String folder, Message message) {
|
|
||||||
return createMessageKey(account, folder, message.getUid());
|
private void suppressMessages(Account account, List<Message> messages) {
|
||||||
|
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
|
||||||
|
mApplication.getApplicationContext());
|
||||||
|
cache.hideMessages(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createMessageKey(Account account, String folder, String uid) {
|
private void unsuppressMessages(Account account, Message[] messages) {
|
||||||
return account.getUuid() + ":" + folder + ":" + uid;
|
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
|
||||||
|
mApplication.getApplicationContext());
|
||||||
|
cache.unhideMessages(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void suppressMessage(Account account, String folder, Message message) {
|
private boolean isMessageSuppressed(Account account, Message message) {
|
||||||
|
LocalMessage localMessage = (LocalMessage) message;
|
||||||
|
String accountUuid = account.getUuid();
|
||||||
|
long messageId = localMessage.getId();
|
||||||
|
long folderId = ((LocalFolder) localMessage.getFolder()).getId();
|
||||||
|
|
||||||
if (account == null || folder == null || message == null) {
|
EmailProviderCache cache = EmailProviderCache.getCache(accountUuid,
|
||||||
return;
|
mApplication.getApplicationContext());
|
||||||
}
|
return cache.isMessageHidden(messageId, folderId);
|
||||||
String messKey = createMessageKey(account, folder, message);
|
|
||||||
deletedUids.put(messKey, "true");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unsuppressMessage(Account account, String folder, String uid) {
|
private void setFlagInCache(final Account account, final List<Long> messageIds,
|
||||||
if (account == null || folder == null || uid == null) {
|
final Flag flag, final boolean newState) {
|
||||||
return;
|
|
||||||
}
|
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
|
||||||
String messKey = createMessageKey(account, folder, uid);
|
mApplication.getApplicationContext());
|
||||||
deletedUids.remove(messKey);
|
String columnName = LocalStore.getColumnNameForFlag(flag);
|
||||||
|
String value = Integer.toString((newState) ? 1 : 0);
|
||||||
|
cache.setValueForMessages(messageIds, columnName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeFlagFromCache(final Account account, final List<Long> messageIds,
|
||||||
|
final Flag flag) {
|
||||||
|
|
||||||
private boolean isMessageSuppressed(Account account, String folder, Message message) {
|
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
|
||||||
if (account == null || folder == null || message == null) {
|
mApplication.getApplicationContext());
|
||||||
return false;
|
String columnName = LocalStore.getColumnNameForFlag(flag);
|
||||||
}
|
cache.removeValueForMessages(messageIds, columnName);
|
||||||
String messKey = createMessageKey(account, folder, message);
|
|
||||||
|
|
||||||
if (deletedUids.containsKey(messKey)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setFlagForThreadsInCache(final Account account, final List<Long> threadRootIds,
|
||||||
|
final Flag flag, final boolean newState) {
|
||||||
|
|
||||||
|
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
|
||||||
|
mApplication.getApplicationContext());
|
||||||
|
String columnName = LocalStore.getColumnNameForFlag(flag);
|
||||||
|
String value = Integer.toString((newState) ? 1 : 0);
|
||||||
|
cache.setValueForThreads(threadRootIds, columnName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFlagForThreadsFromCache(final Account account, final List<Long> messageIds,
|
||||||
|
final Flag flag) {
|
||||||
|
|
||||||
|
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
|
||||||
|
mApplication.getApplicationContext());
|
||||||
|
String columnName = LocalStore.getColumnNameForFlag(flag);
|
||||||
|
cache.removeValueForThreads(messageIds, columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param application {@link K9}
|
* @param application {@link K9}
|
||||||
*/
|
*/
|
||||||
@ -677,7 +699,7 @@ public class MessagingController implements Runnable {
|
|||||||
public void messagesFinished(int number) {}
|
public void messagesFinished(int number) {}
|
||||||
@Override
|
@Override
|
||||||
public void messageFinished(Message message, int number, int ofTotal) {
|
public void messageFinished(Message message, int number, int ofTotal) {
|
||||||
if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) {
|
if (!isMessageSuppressed(message.getFolder().getAccount(), message)) {
|
||||||
List<Message> messages = new ArrayList<Message>();
|
List<Message> messages = new ArrayList<Message>();
|
||||||
|
|
||||||
messages.add(message);
|
messages.add(message);
|
||||||
@ -1386,8 +1408,6 @@ public class MessagingController implements Runnable {
|
|||||||
if (message.isSet(Flag.DELETED)) {
|
if (message.isSet(Flag.DELETED)) {
|
||||||
syncFlagMessages.add(message);
|
syncFlagMessages.add(message);
|
||||||
return;
|
return;
|
||||||
} else if (isMessageSuppressed(account, folder, message)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Message localMessage = localFolder.getMessage(message.getUid());
|
Message localMessage = localFolder.getMessage(message.getUid());
|
||||||
@ -1498,14 +1518,13 @@ public class MessagingController implements Runnable {
|
|||||||
* (POP) may not be able to give us headers for
|
* (POP) may not be able to give us headers for
|
||||||
* ENVELOPE, only size.
|
* ENVELOPE, only size.
|
||||||
*/
|
*/
|
||||||
if (!isMessageSuppressed(account, folder, message)) {
|
|
||||||
// keep message for delayed storing
|
|
||||||
chunk.add(message);
|
|
||||||
|
|
||||||
if (chunk.size() >= UNSYNC_CHUNK_SIZE) {
|
// keep message for delayed storing
|
||||||
writeUnsyncedMessages(chunk, localFolder, account, folder);
|
chunk.add(message);
|
||||||
chunk.clear();
|
|
||||||
}
|
if (chunk.size() >= UNSYNC_CHUNK_SIZE) {
|
||||||
|
writeUnsyncedMessages(chunk, localFolder, account, folder);
|
||||||
|
chunk.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -1567,15 +1586,6 @@ public class MessagingController implements Runnable {
|
|||||||
|
|
||||||
private boolean shouldImportMessage(final Account account, final String folder, final Message message, final AtomicInteger progress, final Date earliestDate) {
|
private boolean shouldImportMessage(final Account account, final String folder, final Message message, final AtomicInteger progress, final Date earliestDate) {
|
||||||
|
|
||||||
if (isMessageSuppressed(account, folder, message)) {
|
|
||||||
if (K9.DEBUG) {
|
|
||||||
Log.d(K9.LOG_TAG, "Message " + message.getUid() + " was suppressed " +
|
|
||||||
"but just downloaded. " +
|
|
||||||
"The race condition means we wasted some bandwidth. Oh well.");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (account.isSearchByDateCapable() && message.olderThan(earliestDate)) {
|
if (account.isSearchByDateCapable() && message.olderThan(earliestDate)) {
|
||||||
if (K9.DEBUG) {
|
if (K9.DEBUG) {
|
||||||
Log.d(K9.LOG_TAG, "Message " + message.getUid() + " is older than "
|
Log.d(K9.LOG_TAG, "Message " + message.getUid() + " is older than "
|
||||||
@ -1819,7 +1829,7 @@ public class MessagingController implements Runnable {
|
|||||||
boolean messageChanged = syncFlags(localMessage, remoteMessage);
|
boolean messageChanged = syncFlags(localMessage, remoteMessage);
|
||||||
if (messageChanged) {
|
if (messageChanged) {
|
||||||
boolean shouldBeNotifiedOf = false;
|
boolean shouldBeNotifiedOf = false;
|
||||||
if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(account, folder, localMessage)) {
|
if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(account, localMessage)) {
|
||||||
for (MessagingListener l : getListeners()) {
|
for (MessagingListener l : getListeners()) {
|
||||||
l.synchronizeMailboxRemovedMessage(account, folder, localMessage);
|
l.synchronizeMailboxRemovedMessage(account, folder, localMessage);
|
||||||
}
|
}
|
||||||
@ -2748,6 +2758,8 @@ public class MessagingController implements Runnable {
|
|||||||
public void setFlag(final Account account, final List<Long> messageIds, final Flag flag,
|
public void setFlag(final Account account, final List<Long> messageIds, final Flag flag,
|
||||||
final boolean newState) {
|
final boolean newState) {
|
||||||
|
|
||||||
|
setFlagInCache(account, messageIds, flag, newState);
|
||||||
|
|
||||||
threadPool.execute(new Runnable() {
|
threadPool.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -2759,6 +2771,8 @@ public class MessagingController implements Runnable {
|
|||||||
public void setFlagForThreads(final Account account, final List<Long> threadRootIds,
|
public void setFlagForThreads(final Account account, final List<Long> threadRootIds,
|
||||||
final Flag flag, final boolean newState) {
|
final Flag flag, final boolean newState) {
|
||||||
|
|
||||||
|
setFlagForThreadsInCache(account, threadRootIds, flag, newState);
|
||||||
|
|
||||||
threadPool.execute(new Runnable() {
|
threadPool.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -2783,8 +2797,10 @@ public class MessagingController implements Runnable {
|
|||||||
try {
|
try {
|
||||||
if (threadedList) {
|
if (threadedList) {
|
||||||
localStore.setFlagForThreads(ids, flag, newState);
|
localStore.setFlagForThreads(ids, flag, newState);
|
||||||
|
removeFlagFromCache(account, ids, flag);
|
||||||
} else {
|
} else {
|
||||||
localStore.setFlag(ids, flag, newState);
|
localStore.setFlag(ids, flag, newState);
|
||||||
|
removeFlagForThreadsFromCache(account, ids, flag);
|
||||||
}
|
}
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
Log.e(K9.LOG_TAG, "Couldn't set flags in local database", e);
|
Log.e(K9.LOG_TAG, "Couldn't set flags in local database", e);
|
||||||
@ -3763,9 +3779,7 @@ public class MessagingController implements Runnable {
|
|||||||
final List<Message> messages, final String destFolder,
|
final List<Message> messages, final String destFolder,
|
||||||
final MessagingListener listener) {
|
final MessagingListener listener) {
|
||||||
|
|
||||||
for (Message message : messages) {
|
suppressMessages(account, messages);
|
||||||
suppressMessage(account, srcFolder, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
putBackground("moveMessages", null, new Runnable() {
|
putBackground("moveMessages", null, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -3779,9 +3793,7 @@ public class MessagingController implements Runnable {
|
|||||||
public void moveMessagesInThread(final Account account, final String srcFolder,
|
public void moveMessagesInThread(final Account account, final String srcFolder,
|
||||||
final List<Message> messages, final String destFolder) {
|
final List<Message> messages, final String destFolder) {
|
||||||
|
|
||||||
for (Message message : messages) {
|
suppressMessages(account, messages);
|
||||||
suppressMessage(account, srcFolder, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
putBackground("moveMessagesInThread", null, new Runnable() {
|
putBackground("moveMessagesInThread", null, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -3905,8 +3917,8 @@ public class MessagingController implements Runnable {
|
|||||||
for (MessagingListener l : getListeners()) {
|
for (MessagingListener l : getListeners()) {
|
||||||
l.messageUidChanged(account, srcFolder, origUid, message.getUid());
|
l.messageUidChanged(account, srcFolder, origUid, message.getUid());
|
||||||
}
|
}
|
||||||
unsuppressMessage(account, srcFolder, origUid);
|
|
||||||
}
|
}
|
||||||
|
unsuppressMessages(account, messages);
|
||||||
|
|
||||||
if (unreadCountAffected) {
|
if (unreadCountAffected) {
|
||||||
// If this move operation changes the unread count, notify the listeners
|
// If this move operation changes the unread count, notify the listeners
|
||||||
@ -3970,9 +3982,7 @@ public class MessagingController implements Runnable {
|
|||||||
public void act(final Account account, final Folder folder,
|
public void act(final Account account, final Folder folder,
|
||||||
final List<Message> accountMessages) {
|
final List<Message> accountMessages) {
|
||||||
|
|
||||||
for (Message message : accountMessages) {
|
suppressMessages(account, messages);
|
||||||
suppressMessage(account, folder.getName(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
putBackground("deleteThreads", null, new Runnable() {
|
putBackground("deleteThreads", null, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -4021,9 +4031,7 @@ public class MessagingController implements Runnable {
|
|||||||
@Override
|
@Override
|
||||||
public void act(final Account account, final Folder folder,
|
public void act(final Account account, final Folder folder,
|
||||||
final List<Message> accountMessages) {
|
final List<Message> accountMessages) {
|
||||||
for (Message message : accountMessages) {
|
suppressMessages(account, messages);
|
||||||
suppressMessage(account, folder.getName(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
putBackground("deleteMessages", null, new Runnable() {
|
putBackground("deleteMessages", null, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -4111,9 +4119,8 @@ public class MessagingController implements Runnable {
|
|||||||
if (K9.DEBUG)
|
if (K9.DEBUG)
|
||||||
Log.d(K9.LOG_TAG, "Delete policy " + account.getDeletePolicy() + " prevents delete from server");
|
Log.d(K9.LOG_TAG, "Delete policy " + account.getDeletePolicy() + " prevents delete from server");
|
||||||
}
|
}
|
||||||
for (String uid : uids) {
|
|
||||||
unsuppressMessage(account, folder, uid);
|
unsuppressMessages(account, messages);
|
||||||
}
|
|
||||||
} catch (UnavailableStorageException e) {
|
} catch (UnavailableStorageException e) {
|
||||||
Log.i(K9.LOG_TAG, "Failed to delete message because storage is not available - trying again later.");
|
Log.i(K9.LOG_TAG, "Failed to delete message because storage is not available - trying again later.");
|
||||||
throw new UnavailableAccountException(e);
|
throw new UnavailableAccountException(e);
|
||||||
|
@ -13,8 +13,10 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@ -32,6 +34,7 @@ import android.support.v4.app.LoaderManager;
|
|||||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
@ -75,19 +78,19 @@ import com.fsck.k9.activity.ChooseFolder;
|
|||||||
import com.fsck.k9.activity.FolderInfoHolder;
|
import com.fsck.k9.activity.FolderInfoHolder;
|
||||||
import com.fsck.k9.activity.MessageReference;
|
import com.fsck.k9.activity.MessageReference;
|
||||||
import com.fsck.k9.activity.misc.ContactPictureLoader;
|
import com.fsck.k9.activity.misc.ContactPictureLoader;
|
||||||
|
import com.fsck.k9.cache.EmailProviderCache;
|
||||||
import com.fsck.k9.controller.MessagingController;
|
import com.fsck.k9.controller.MessagingController;
|
||||||
import com.fsck.k9.fragment.ConfirmationDialogFragment;
|
|
||||||
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
|
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
|
||||||
import com.fsck.k9.helper.MessageHelper;
|
|
||||||
import com.fsck.k9.helper.MergeCursorWithUniqueId;
|
import com.fsck.k9.helper.MergeCursorWithUniqueId;
|
||||||
|
import com.fsck.k9.helper.MessageHelper;
|
||||||
import com.fsck.k9.helper.StringUtils;
|
import com.fsck.k9.helper.StringUtils;
|
||||||
import com.fsck.k9.helper.Utility;
|
import com.fsck.k9.helper.Utility;
|
||||||
import com.fsck.k9.mail.Address;
|
import com.fsck.k9.mail.Address;
|
||||||
import com.fsck.k9.mail.Flag;
|
import com.fsck.k9.mail.Flag;
|
||||||
import com.fsck.k9.mail.Folder;
|
import com.fsck.k9.mail.Folder;
|
||||||
|
import com.fsck.k9.mail.Folder.OpenMode;
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.Folder.OpenMode;
|
|
||||||
import com.fsck.k9.mail.store.LocalStore;
|
import com.fsck.k9.mail.store.LocalStore;
|
||||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||||
import com.fsck.k9.provider.EmailProvider;
|
import com.fsck.k9.provider.EmailProvider;
|
||||||
@ -428,6 +431,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
|
|
||||||
private ContactPictureLoader mContactsPictureLoader;
|
private ContactPictureLoader mContactsPictureLoader;
|
||||||
|
|
||||||
|
private LocalBroadcastManager mLocalBroadcastManager;
|
||||||
|
private BroadcastReceiver mCacheBroadcastReceiver;
|
||||||
|
private IntentFilter mCacheIntentFilter;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to run operations that modify UI elements in the UI thread.
|
* This class is used to run operations that modify UI elements in the UI thread.
|
||||||
*
|
*
|
||||||
@ -728,7 +736,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mPreferences = Preferences.getPreferences(getActivity().getApplicationContext());
|
Context appContext = getActivity().getApplicationContext();
|
||||||
|
|
||||||
|
mPreferences = Preferences.getPreferences(appContext);
|
||||||
mController = MessagingController.getInstance(getActivity().getApplication());
|
mController = MessagingController.getInstance(getActivity().getApplication());
|
||||||
|
|
||||||
mPreviewLines = K9.messageListPreviewLines();
|
mPreviewLines = K9.messageListPreviewLines();
|
||||||
@ -742,6 +752,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
restoreInstanceState(savedInstanceState);
|
restoreInstanceState(savedInstanceState);
|
||||||
decodeArguments();
|
decodeArguments();
|
||||||
|
|
||||||
|
createCacheBroadcastReceiver(appContext);
|
||||||
|
|
||||||
mInitialized = true;
|
mInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -916,6 +928,19 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
mListView.setAdapter(mAdapter);
|
mListView.setAdapter(mAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createCacheBroadcastReceiver(Context appContext) {
|
||||||
|
mLocalBroadcastManager = LocalBroadcastManager.getInstance(appContext);
|
||||||
|
|
||||||
|
mCacheBroadcastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mCacheIntentFilter = new IntentFilter(EmailProviderCache.ACTION_CACHE_UPDATED);
|
||||||
|
}
|
||||||
|
|
||||||
private FolderInfoHolder getFolder(String folder, Account account) {
|
private FolderInfoHolder getFolder(String folder, Account account) {
|
||||||
LocalFolder local_folder = null;
|
LocalFolder local_folder = null;
|
||||||
try {
|
try {
|
||||||
@ -960,6 +985,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
|
||||||
|
mLocalBroadcastManager.unregisterReceiver(mCacheBroadcastReceiver);
|
||||||
mListener.onPause(getActivity());
|
mListener.onPause(getActivity());
|
||||||
mController.removeListener(mListener);
|
mController.removeListener(mListener);
|
||||||
}
|
}
|
||||||
@ -1001,6 +1028,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLocalBroadcastManager.registerReceiver(mCacheBroadcastReceiver, mCacheIntentFilter);
|
||||||
mListener.onResume(getActivity());
|
mListener.onResume(getActivity());
|
||||||
mController.addListener(mListener);
|
mController.addListener(mListener);
|
||||||
|
|
||||||
|
@ -72,6 +72,7 @@ import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
|
|||||||
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
|
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
|
||||||
import com.fsck.k9.provider.AttachmentProvider;
|
import com.fsck.k9.provider.AttachmentProvider;
|
||||||
import com.fsck.k9.provider.EmailProvider;
|
import com.fsck.k9.provider.EmailProvider;
|
||||||
|
import com.fsck.k9.provider.EmailProvider.MessageColumns;
|
||||||
import com.fsck.k9.search.LocalSearch;
|
import com.fsck.k9.search.LocalSearch;
|
||||||
import com.fsck.k9.search.SearchSpecification.Attribute;
|
import com.fsck.k9.search.SearchSpecification.Attribute;
|
||||||
import com.fsck.k9.search.SearchSpecification.Searchfield;
|
import com.fsck.k9.search.SearchSpecification.Searchfield;
|
||||||
@ -127,6 +128,28 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
|
|
||||||
public static final int DB_VERSION = 47;
|
public static final int DB_VERSION = 47;
|
||||||
|
|
||||||
|
|
||||||
|
public static String getColumnNameForFlag(Flag flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case SEEN: {
|
||||||
|
return MessageColumns.READ;
|
||||||
|
}
|
||||||
|
case FLAGGED: {
|
||||||
|
return MessageColumns.FLAGGED;
|
||||||
|
}
|
||||||
|
case ANSWERED: {
|
||||||
|
return MessageColumns.ANSWERED;
|
||||||
|
}
|
||||||
|
case FORWARDED: {
|
||||||
|
return MessageColumns.FORWARDED;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException("Flag must be a special column flag");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String uUid = null;
|
protected String uUid = null;
|
||||||
|
|
||||||
private final Application mApplication;
|
private final Application mApplication;
|
||||||
@ -4104,28 +4127,7 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
|
|
||||||
final ContentValues cv = new ContentValues();
|
final ContentValues cv = new ContentValues();
|
||||||
|
cv.put(getColumnNameForFlag(flag), newState);
|
||||||
switch (flag) {
|
|
||||||
case SEEN: {
|
|
||||||
cv.put("read", newState);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FLAGGED: {
|
|
||||||
cv.put("flagged", newState);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ANSWERED: {
|
|
||||||
cv.put("answered", newState);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FORWARDED: {
|
|
||||||
cv.put("forwarded", newState);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new IllegalArgumentException("Flag must be a special column flag");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doBatchSetSelection(new BatchSetSelection() {
|
doBatchSetSelection(new BatchSetSelection() {
|
||||||
|
|
||||||
@ -4173,28 +4175,7 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
public void setFlagForThreads(final List<Long> threadRootIds, Flag flag, final boolean newState)
|
public void setFlagForThreads(final List<Long> threadRootIds, Flag flag, final boolean newState)
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
|
|
||||||
final String flagColumn;
|
final String flagColumn = getColumnNameForFlag(flag);
|
||||||
switch (flag) {
|
|
||||||
case SEEN: {
|
|
||||||
flagColumn = "read";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FLAGGED: {
|
|
||||||
flagColumn = "flagged";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ANSWERED: {
|
|
||||||
flagColumn = "answered";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FORWARDED: {
|
|
||||||
flagColumn = "forwarded";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new IllegalArgumentException("Flag must be a special column flag");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doBatchSetSelection(new BatchSetSelection() {
|
doBatchSetSelection(new BatchSetSelection() {
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.Preferences;
|
import com.fsck.k9.Preferences;
|
||||||
|
import com.fsck.k9.cache.EmailProviderCacheCursor;
|
||||||
import com.fsck.k9.helper.StringUtils;
|
import com.fsck.k9.helper.StringUtils;
|
||||||
import com.fsck.k9.helper.Utility;
|
import com.fsck.k9.helper.Utility;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
@ -271,6 +272,7 @@ public class EmailProvider extends ContentProvider {
|
|||||||
cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection,
|
cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection,
|
||||||
specialColumns);
|
specialColumns);
|
||||||
|
|
||||||
|
cursor = new EmailProviderCacheCursor(accountUuid, cursor, getContext());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case STATS: {
|
case STATS: {
|
||||||
|
Loading…
Reference in New Issue
Block a user