diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 327352949..fe2f76597 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2533,17 +2533,28 @@ public class MessagingController implements Runnable { } public void setFlag(final Account account, final List messageIds, final Flag flag, - final boolean newState, final boolean threadedList) { + final boolean newState) { threadPool.execute(new Runnable() { @Override public void run() { - setFlagSynchronous(account, messageIds, flag, newState, threadedList); + setFlagSynchronous(account, messageIds, flag, newState, false); } }); } - private void setFlagSynchronous(final Account account, final List messageIds, + public void setFlagForThreads(final Account account, final List threadRootIds, + final Flag flag, final boolean newState) { + + threadPool.execute(new Runnable() { + @Override + public void run() { + setFlagSynchronous(account, threadRootIds, flag, newState, true); + } + }); + } + + private void setFlagSynchronous(final Account account, final List ids, final Flag flag, final boolean newState, final boolean threadedList) { LocalStore localStore; @@ -2557,7 +2568,11 @@ public class MessagingController implements Runnable { // Update affected messages in the database. This should be as fast as possible so the UI // can be updated with the new state. try { - localStore.setFlag(messageIds, flag, newState, threadedList); + if (threadedList) { + localStore.setFlagForThreads(ids, flag, newState); + } else { + localStore.setFlag(ids, flag, newState); + } } catch (MessagingException e) { Log.e(K9.LOG_TAG, "Couldn't set flags in local database", e); } @@ -2565,7 +2580,7 @@ public class MessagingController implements Runnable { // Read folder name and UID of messages from the database Map> folderMap; try { - folderMap = localStore.getFoldersAndUids(messageIds, threadedList); + folderMap = localStore.getFoldersAndUids(ids, threadedList); } catch (MessagingException e) { Log.e(K9.LOG_TAG, "Couldn't get folder name and UID of messages", e); return; diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java index 02c8d7405..7d11b9946 100644 --- a/src/com/fsck/k9/fragment/MessageListFragment.java +++ b/src/com/fsck/k9/fragment/MessageListFragment.java @@ -2056,10 +2056,16 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick Cursor cursor = (Cursor) mAdapter.getItem(adapterPosition); Account account = mPreferences.getAccount(cursor.getString(ACCOUNT_UUID_COLUMN)); - long id = cursor.getLong(ID_COLUMN); - mController.setFlag(account, Collections.singletonList(Long.valueOf(id)), flag, newState, - mThreadedList); + if (mThreadedList && cursor.getInt(THREAD_COUNT_COLUMN) > 1) { + long threadRootId = cursor.getLong(THREAD_ROOT_COLUMN); + mController.setFlagForThreads(account, + Collections.singletonList(Long.valueOf(threadRootId)), flag, newState); + } else { + long id = cursor.getLong(ID_COLUMN); + mController.setFlag(account, Collections.singletonList(Long.valueOf(id)), flag, + newState); + } computeBatchDirection(); } @@ -2069,7 +2075,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick return; } - Map> accountMapping = new HashMap>(); + Map> messageMap = new HashMap>(); + Map> threadMap = new HashMap>(); Set accounts = new HashSet(); for (int position = 0, end = mAdapter.getCount(); position < end; position++) { @@ -2079,21 +2086,39 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick if (mSelected.contains(uniqueId)) { String uuid = cursor.getString(ACCOUNT_UUID_COLUMN); Account account = mPreferences.getAccount(uuid); - accounts.add(account); - List messageIdList = accountMapping.get(account); - if (messageIdList == null) { - messageIdList = new ArrayList(); - accountMapping.put(account, messageIdList); - } - messageIdList.add(cursor.getLong(ID_COLUMN)); + if (mThreadedList && cursor.getInt(THREAD_COUNT_COLUMN) > 1) { + List threadRootIdList = threadMap.get(account); + if (threadRootIdList == null) { + threadRootIdList = new ArrayList(); + threadMap.put(account, threadRootIdList); + } + + threadRootIdList.add(cursor.getLong(THREAD_ROOT_COLUMN)); + } else { + List messageIdList = messageMap.get(account); + if (messageIdList == null) { + messageIdList = new ArrayList(); + messageMap.put(account, messageIdList); + } + + messageIdList.add(cursor.getLong(ID_COLUMN)); + } } } for (Account account : accounts) { - List messageIds = accountMapping.get(account); - mController.setFlag(account, messageIds, flag, newState, mThreadedList); + List messageIds = messageMap.get(account); + List threadRootIds = threadMap.get(account); + + if (messageIds != null) { + mController.setFlag(account, messageIds, flag, newState); + } + + if (threadRootIds != null) { + mController.setFlagForThreads(account, threadRootIds, flag, newState); + } } computeBatchDirection(); diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 36d8e93b7..9e795bb61 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -118,6 +118,13 @@ public class LocalStore extends Store implements Serializable { */ private static final int FLAG_UPDATE_BATCH_SIZE = 500; + /** + * Number of threads to perform flag updates on at once. + * + * @see #setFlagForThreads(List, Flag, boolean) + */ + private static final int THREAD_FLAG_UPDATE_BATCH_SIZE = 400; + public static final int DB_VERSION = 47; protected String uUid = null; @@ -4169,10 +4176,7 @@ public class LocalStore extends Store implements Serializable { * *

* The goal of this method is to be fast. Currently this means using as few SQL UPDATE - * statements as possible.
- * Current benchmarks show that updating 1000 messages takes about 8 seconds on a Nexus 7. So - * there should be room for further improvement. - *

+ * statements as possible. * * @param messageIds * A list of primary keys in the "messages" table. @@ -4180,23 +4184,9 @@ public class LocalStore extends Store implements Serializable { * The flag to change. This must be a flag with a separate column in the database. * @param newState * {@code true}, if the flag should be set. {@code false}, otherwise. - * @param threadRootIds - * If this is {@code true}, {@code messageIds} contains the IDs of the messages at the - * root of a thread. In that case the flag is changed for all messages in these threads. - * If this is {@code false} only the messages in {@code messageIds} are changed. * * @throws MessagingException */ - public void setFlag(List messageIds, Flag flag, boolean newState, boolean threadRootIds) - throws MessagingException { - - if (threadRootIds) { - setFlagForThreads(messageIds, flag, newState); - } else { - setFlag(messageIds, flag, newState); - } - } - public void setFlag(final List messageIds, final Flag flag, final boolean newState) throws MessagingException { @@ -4251,7 +4241,23 @@ public class LocalStore extends Store implements Serializable { }, FLAG_UPDATE_BATCH_SIZE); } - public void setFlagForThreads(final List messageIds, Flag flag, final boolean newState) + /** + * Change the state of a flag for a list of threads. + * + *

+ * The goal of this method is to be fast. Currently this means using as few SQL UPDATE + * statements as possible. + * + * @param threadRootIds + * A list of root thread IDs. + * @param flag + * The flag to change. This must be a flag with a separate column in the database. + * @param newState + * {@code true}, if the flag should be set. {@code false}, otherwise. + * + * @throws MessagingException + */ + public void setFlagForThreads(final List threadRootIds, Flag flag, final boolean newState) throws MessagingException { final String flagColumn; @@ -4281,35 +4287,37 @@ public class LocalStore extends Store implements Serializable { @Override public int getListSize() { - return messageIds.size(); + return threadRootIds.size(); } @Override public String getListItem(int index) { - return Long.toString(messageIds.get(index)); + return Long.toString(threadRootIds.get(index)); } @Override public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs) throws UnavailableStorageException { + int len = selectionArgs.length; + String[] args = new String[len * 2]; + System.arraycopy(selectionArgs, 0, args, 0, len); + System.arraycopy(selectionArgs, 0, args, len, len); + db.execSQL("UPDATE messages SET " + flagColumn + " = " + ((newState) ? "1" : "0") + " WHERE id IN (" + - "SELECT m.id FROM messages h " + - "JOIN threads t1 ON (t1.message_id = h.id) " + - "JOIN threads t2 ON " + - "(t1.root IN (t2.id, t2.root) OR t1.id IN (t2.id, t2.root)) " + - "JOIN messages m ON (t2.message_id = m.id) " + + "SELECT m.id FROM threads t " + + "LEFT JOIN messages m ON (t.message_id = m.id) " + "WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " + - "AND h.id" + selectionSet + ")", - selectionArgs); + "AND (t.id" + selectionSet + " OR t.root" + selectionSet + "))", + args); } @Override public void postDbWork() { notifyChange(); } - }, FLAG_UPDATE_BATCH_SIZE); + }, THREAD_FLAG_UPDATE_BATCH_SIZE); } /**