1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 11:42:16 -05:00

Do sorting in MergeCursor when merging the query results

Disabled "sort by sender" for now because the database can't sort by
contact names from the contacts database. We probably have to
special-case that and do in-memory sorting.
This commit is contained in:
cketti 2012-10-27 02:15:30 +02:00
parent d74ca8c8ce
commit faa666394c
6 changed files with 270 additions and 155 deletions

View File

@ -29,9 +29,11 @@
<item
android:id="@+id/set_sort_subject"
android:title="@string/sort_by_subject"/>
<!--
<item
android:id="@+id/set_sort_sender"
android:title="@string/sort_by_sender"/>
-->
<item
android:id="@+id/set_sort_flag"
android:title="@string/sort_by_flag"/>

View File

@ -94,7 +94,7 @@ public class Account implements BaseAccount {
SORT_DATE(R.string.sort_earliest_first, R.string.sort_latest_first, false),
SORT_ARRIVAL(R.string.sort_earliest_first, R.string.sort_latest_first, false),
SORT_SUBJECT(R.string.sort_subject_alpha, R.string.sort_subject_re_alpha, true),
SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true),
// SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true),
SORT_UNREAD(R.string.sort_unread_first, R.string.sort_unread_last, true),
SORT_FLAGGED(R.string.sort_flagged_first, R.string.sort_flagged_last, true),
SORT_ATTACHMENT(R.string.sort_attach_first, R.string.sort_unattached_first, true);

View File

@ -374,10 +374,10 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
mMessageListFragment.changeSort(SortType.SORT_SUBJECT);
return true;
}
case R.id.set_sort_sender: {
mMessageListFragment.changeSort(SortType.SORT_SENDER);
return true;
}
// case R.id.set_sort_sender: {
// mMessageListFragment.changeSort(SortType.SORT_SENDER);
// return true;
// }
case R.id.set_sort_flag: {
mMessageListFragment.changeSort(SortType.SORT_FLAGGED);
return true;

View File

@ -7,8 +7,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
@ -69,7 +67,6 @@ import com.fsck.k9.R;
import com.fsck.k9.activity.ActivityListener;
import com.fsck.k9.activity.ChooseFolder;
import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.MessageInfoHolder;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.fragment.ConfirmationDialogFragment;
@ -77,7 +74,6 @@ import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmen
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.MergeCursorWithUniqueId;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
@ -173,7 +169,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
// arg1 & 2 are mixed up, this is done on purpose
return mDelegate.compare(object2, object1);
}
}
/**
@ -182,7 +177,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* @param <T>
*/
public static class ComparatorChain<T> implements Comparator<T> {
private List<Comparator<T>> mChain;
/**
@ -204,89 +198,93 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
return result;
}
}
public static class AttachmentComparator implements Comparator<MessageInfoHolder> {
public static class ReverseIdComparator implements Comparator<Cursor> {
private int mIdColumn = -1;
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return (object1.message.hasAttachments() ? 0 : 1) - (object2.message.hasAttachments() ? 0 : 1);
public int compare(Cursor cursor1, Cursor cursor2) {
if (mIdColumn == -1) {
mIdColumn = cursor1.getColumnIndex("_id");
}
long o1Id = cursor1.getLong(mIdColumn);
long o2Id = cursor2.getLong(mIdColumn);
return (o1Id > o2Id) ? -1 : 1;
}
}
}
public static class FlaggedComparator implements Comparator<MessageInfoHolder> {
public static class AttachmentComparator implements Comparator<Cursor> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return (object1.flagged ? 0 : 1) - (object2.flagged ? 0 : 1);
public int compare(Cursor cursor1, Cursor cursor2) {
int o1HasAttachment = (cursor1.getInt(ATTACHMENT_COUNT_COLUMN) > 0) ? 0 : 1;
int o2HasAttachment = (cursor2.getInt(ATTACHMENT_COUNT_COLUMN) > 0) ? 0 : 1;
return o1HasAttachment - o2HasAttachment;
}
}
}
public static class UnreadComparator implements Comparator<MessageInfoHolder> {
public static class FlaggedComparator implements Comparator<Cursor> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return (object1.read ? 1 : 0) - (object2.read ? 1 : 0);
public int compare(Cursor cursor1, Cursor cursor2) {
int o1IsFlagged = (cursor1.getString(FLAGS_COLUMN).contains("FLAGGED")) ? 0 : 1;
int o2IsFlagged = (cursor2.getString(FLAGS_COLUMN).contains("FLAGGED")) ? 0 : 1;
return o1IsFlagged - o2IsFlagged;
}
}
}
public static class SenderComparator implements Comparator<MessageInfoHolder> {
public static class UnreadComparator implements Comparator<Cursor> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
if (object1.compareCounterparty == null) {
return (object2.compareCounterparty == null ? 0 : 1);
} else if (object2.compareCounterparty == null) {
public int compare(Cursor cursor1, Cursor cursor2) {
int o1IsUnread = (cursor1.getString(FLAGS_COLUMN).contains("SEEN")) ? 1 : 0;
int o2IsUnread = (cursor2.getString(FLAGS_COLUMN).contains("SEEN")) ? 1 : 0;
return o1IsUnread - o2IsUnread;
}
}
public static class DateComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
long o1Date = cursor1.getLong(DATE_COLUMN);
long o2Date = cursor2.getLong(DATE_COLUMN);
if (o1Date < o2Date) {
return -1;
} else if (o1Date == o2Date) {
return 0;
} else {
return 1;
}
}
}
public static class ArrivalComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
long o1Date = cursor1.getLong(INTERNAL_DATE_COLUMN);
long o2Date = cursor2.getLong(INTERNAL_DATE_COLUMN);
if (o1Date == o2Date) {
return 0;
} else if (o1Date < o2Date) {
return -1;
} else {
return object1.compareCounterparty.toLowerCase().compareTo(object2.compareCounterparty.toLowerCase());
return 1;
}
}
}
}
public static class DateComparator implements Comparator<MessageInfoHolder> {
public static class SubjectComparator implements Comparator<Cursor> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
if (object1.compareDate == null) {
return (object2.compareDate == null ? 0 : 1);
} else if (object2.compareDate == null) {
return -1;
} else {
return object1.compareDate.compareTo(object2.compareDate);
}
}
public int compare(Cursor cursor1, Cursor cursor2) {
String subject1 = cursor1.getString(SUBJECT_COLUMN);
String subject2 = cursor2.getString(SUBJECT_COLUMN);
return subject1.compareToIgnoreCase(subject2);
}
public static class ArrivalComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return object1.compareArrival.compareTo(object2.compareArrival);
}
}
public static class SubjectComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder arg0, MessageInfoHolder arg1) {
// XXX doesn't respect the Comparator contract since it alters the compared object
if (arg0.compareSubject == null) {
arg0.compareSubject = Utility.stripSubject(arg0.message.getSubject());
}
if (arg1.compareSubject == null) {
arg1.compareSubject = Utility.stripSubject(arg1.message.getSubject());
}
return arg0.compareSubject.compareToIgnoreCase(arg1.compareSubject);
}
}
@ -301,17 +299,17 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
/**
* Maps a {@link SortType} to a {@link Comparator} implementation.
*/
private static final Map<SortType, Comparator<MessageInfoHolder>> SORT_COMPARATORS;
private static final Map<SortType, Comparator<Cursor>> SORT_COMPARATORS;
static {
// fill the mapping at class time loading
final Map<SortType, Comparator<MessageInfoHolder>> map = new EnumMap<SortType, Comparator<MessageInfoHolder>>(SortType.class);
final Map<SortType, Comparator<Cursor>> map =
new EnumMap<SortType, Comparator<Cursor>>(SortType.class);
map.put(SortType.SORT_ATTACHMENT, new AttachmentComparator());
map.put(SortType.SORT_DATE, new DateComparator());
map.put(SortType.SORT_ARRIVAL, new ArrivalComparator());
map.put(SortType.SORT_FLAGGED, new FlaggedComparator());
map.put(SortType.SORT_SENDER, new SenderComparator());
map.put(SortType.SORT_SUBJECT, new SubjectComparator());
map.put(SortType.SORT_UNREAD, new UnreadComparator());
@ -471,35 +469,33 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* @return The comparator to use to display messages in an ordered
* fashion. Never <code>null</code>.
*/
protected Comparator<MessageInfoHolder> getComparator() {
final List<Comparator<MessageInfoHolder>> chain = new ArrayList<Comparator<MessageInfoHolder>>(2 /* we add 2 comparators at most */);
protected Comparator<Cursor> getComparator() {
final List<Comparator<Cursor>> chain =
new ArrayList<Comparator<Cursor>>(3 /* we add 3 comparators at most */);
{
// add the specified comparator
final Comparator<MessageInfoHolder> comparator = SORT_COMPARATORS.get(mSortType);
// Add the specified comparator
final Comparator<Cursor> comparator = SORT_COMPARATORS.get(mSortType);
if (mSortAscending) {
chain.add(comparator);
} else {
chain.add(new ReverseComparator<MessageInfoHolder>(comparator));
}
chain.add(new ReverseComparator<Cursor>(comparator));
}
{
// add the date comparator if not already specified
// Add the date comparator if not already specified
if (mSortType != SortType.SORT_DATE && mSortType != SortType.SORT_ARRIVAL) {
final Comparator<MessageInfoHolder> comparator = SORT_COMPARATORS.get(SortType.SORT_DATE);
final Comparator<Cursor> dateComparator = SORT_COMPARATORS.get(SortType.SORT_DATE);
if (mSortDateAscending) {
chain.add(comparator);
chain.add(dateComparator);
} else {
chain.add(new ReverseComparator<MessageInfoHolder>(comparator));
}
chain.add(new ReverseComparator<Cursor>(dateComparator));
}
}
// build the comparator chain
final Comparator<MessageInfoHolder> chainComparator = new ComparatorChain<MessageInfoHolder>(chain);
// Add the id comparator
chain.add(new ReverseIdComparator());
return chainComparator;
// Build the comparator chain
return new ComparatorChain<Cursor>(chain);
}
private void folderLoading(String folder, boolean loading) {
@ -699,6 +695,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
initializeMessageList();
// This needs to be done before initializing the cursor loader below
initializeSortSettings();
LoaderManager loaderManager = getLoaderManager();
int len = mAccountUuids.length;
mCursors = new Cursor[len];
@ -707,6 +706,18 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
private void initializeSortSettings() {
if (mSingleAccountMode) {
mSortType = mAccount.getSortType();
mSortAscending = mAccount.isSortAscending(mSortType);
mSortDateAscending = mAccount.isSortAscending(SortType.SORT_DATE);
} else {
mSortType = K9.getSortType();
mSortAscending = K9.isSortAscending(mSortType);
mSortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
}
}
private void decodeArguments() {
Bundle args = getArguments();
@ -781,11 +792,24 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
private String getFolderNameById(Account account, long folderId) {
try {
Folder folder = getFolderById(account, folderId);
if (folder != null) {
return folder.getName();
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "getFolderNameById() failed.", e);
}
return null;
}
private Folder getFolderById(Account account, long folderId) {
try {
LocalStore localStore = account.getLocalStore();
LocalFolder localFolder = localStore.getFolderById(folderId);
localFolder.open(OpenMode.READ_ONLY);
return localFolder.getName();
return localFolder;
} catch (Exception e) {
Log.e(K9.LOG_TAG, "getFolderNameById() failed.", e);
return null;
@ -879,17 +903,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
Account[] accountsWithNotification;
Account account = mAccount;
if (account != null) {
accountsWithNotification = new Account[] { account };
mSortType = account.getSortType();
mSortAscending = account.isSortAscending(mSortType);
mSortDateAscending = account.isSortAscending(SortType.SORT_DATE);
} else {
accountsWithNotification = mPreferences.getAccounts();
mSortType = K9.getSortType();
mSortAscending = K9.isSortAscending(mSortType);
mSortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
}
for (Account accountWithNotification : accountsWithNotification) {
@ -1145,10 +1162,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
changeSort(SortType.SORT_SUBJECT);
return true;
}
case R.id.set_sort_sender: {
changeSort(SortType.SORT_SENDER);
return true;
}
// case R.id.set_sort_sender: {
// changeSort(SortType.SORT_SENDER);
// return true;
// }
case R.id.set_sort_flag: {
changeSort(SortType.SORT_FLAGGED);
return true;
@ -2496,8 +2513,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
Cursor cursor = (Cursor) mAdapter.getItem(adapterPosition);
String uid = cursor.getString(UID_COLUMN);
//TODO: get account and folder from cursor
Folder folder = mCurrentFolder.folder;
Account account = getAccountFromCursor(cursor);
long folderId = cursor.getLong(FOLDER_ID_COLUMN);
Folder folder = getFolderById(account, folderId);
try {
return folder.getMessage(uid);
@ -2668,13 +2686,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
sortColumn = "(" + MessageColumns.FLAGS + " NOT LIKE '%FLAGGED%')";
break;
}
case SORT_SENDER: {
//FIXME
sortColumn = MessageColumns.SENDER_LIST;
break;
}
// case SORT_SENDER: {
// //FIXME
// sortColumn = MessageColumns.SENDER_LIST;
// break;
// }
case SORT_SUBJECT: {
sortColumn = MessageColumns.SUBJECT;
sortColumn = MessageColumns.SUBJECT + " COLLATE NOCASE";
break;
}
case SORT_UNREAD: {
@ -2687,14 +2705,12 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
String sortDirection;
String sortDirection = (mSortAscending) ? " ASC" : " DESC";
String secondarySort;
if (mSortType == SortType.SORT_DATE) {
sortDirection = (mSortDateAscending) ? " ASC" : " DESC";
if (mSortType == SortType.SORT_DATE || mSortType == SortType.SORT_ARRIVAL) {
secondarySort = "";
} else {
sortDirection = (mSortAscending) ? " ASC" : " DESC";
secondarySort = MessageColumns.DATE + " DESC, ";
secondarySort = MessageColumns.DATE + ((mSortDateAscending) ? " ASC, " : " DESC, ");
}
String sortOrder = sortColumn + sortDirection + ", " + secondarySort +
@ -2759,9 +2775,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Cursor cursor;
if (mCursors.length > 1) {
mCursors[loader.getId()] = data;
MergeCursorWithUniqueId cursor = new MergeCursorWithUniqueId(mCursors);
cursor = new MergeCursorWithUniqueId(mCursors, getComparator());
} else {
cursor = data;
}
mSelected = new SparseBooleanArray(cursor.getCount());
//TODO: use the (stable) IDs as index and reuse the old mSelected

View File

@ -17,6 +17,8 @@
package com.fsck.k9.helper;
import java.util.Comparator;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
@ -48,30 +50,53 @@ public class MergeCursor implements Cursor {
*/
protected int mActiveCursorIndex;
/**
* The cursor's current position.
*/
protected int mPosition;
/**
* Used to cache the value of {@link #getCount()}
* Used to cache the value of {@link #getCount()}.
*/
private int mCount = -1;
/**
* The comparator that is used to decide how the individual cursors are merged.
*/
private final Comparator<Cursor> mComparator;
/**
* Constructor
*
* @param cursors
* The list of cursors this {@code MultiCursor} should combine.
* @param comparator
* A comparator that is used to decide in what order the individual cursors are merged.
*/
public MergeCursor(Cursor[] cursors) {
public MergeCursor(Cursor[] cursors, Comparator<Cursor> comparator) {
mCursors = cursors.clone();
mComparator = comparator;
resetCursors();
}
private void resetCursors() {
mActiveCursorIndex = -1;
mActiveCursor = null;
mPosition = -1;
for (int i = 0, len = mCursors.length; i < len; i++) {
if (mCursors[i] != null) {
Cursor cursor = mCursors[i];
if (cursor != null) {
cursor.moveToPosition(-1);
if (mActiveCursor == null) {
mActiveCursorIndex = i;
mActiveCursor = mCursors[mActiveCursorIndex];
}
}
mPosition = -1;
}
}
@Override
@ -255,7 +280,50 @@ public class MergeCursor implements Cursor {
@Override
public boolean moveToNext() {
return moveToPosition(mPosition + 1);
int count = getCount();
if (mPosition == count) {
return false;
}
if (mPosition == count - 1) {
mActiveCursor.moveToNext();
mPosition++;
return false;
}
int smallest = -1;
for (int i = 0, len = mCursors.length; i < len; i++) {
if (mCursors[i] == null || mCursors[i].isLast()) {
continue;
}
if (smallest == -1) {
smallest = i;
mCursors[smallest].moveToNext();
continue;
}
Cursor left = mCursors[smallest];
Cursor right = mCursors[i];
right.moveToNext();
int result = mComparator.compare(left, right);
if (result > 0) {
smallest = i;
left.moveToPrevious();
} else {
right.moveToPrevious();
}
}
mPosition++;
if (smallest != -1) {
mActiveCursorIndex = smallest;
mActiveCursor = mCursors[mActiveCursorIndex];
}
return true;
}
@Override
@ -278,40 +346,63 @@ public class MergeCursor implements Cursor {
return true;
}
/* Find the right cursor */
mActiveCursor = null;
mActiveCursorIndex = -1;
mPosition = -1;
int cursorStartPos = 0;
for (int i = 0, len = mCursors.length; i < len; i++) {
if (mCursors[i] == null) {
continue;
}
if (position < (cursorStartPos + mCursors[i].getCount())) {
mActiveCursorIndex = i;
mActiveCursor = mCursors[mActiveCursorIndex];
break;
}
cursorStartPos += mCursors[i].getCount();
}
/* Move it to the right position */
if (mActiveCursor != null) {
boolean success = mActiveCursor.moveToPosition(position - cursorStartPos);
mPosition = (success) ? position : -1;
return success;
}
if (position > mPosition) {
for (int i = 0, end = position - mPosition; i < end; i++) {
if (!moveToNext()) {
return false;
}
}
} else {
for (int i = 0, end = mPosition - position; i < end; i++) {
if (!moveToPrevious()) {
return false;
}
}
}
return true;
}
@Override
public boolean moveToPrevious() {
return moveToPosition(mPosition - 1);
if (mPosition < 0) {
return false;
}
mActiveCursor.moveToPrevious();
if (mPosition == 0) {
mPosition = -1;
return false;
}
int greatest = -1;
for (int i = 0, len = mCursors.length; i < len; i++) {
if (mCursors[i] == null || mCursors[i].isBeforeFirst()) {
continue;
}
if (greatest == -1) {
greatest = i;
continue;
}
Cursor left = mCursors[greatest];
Cursor right = mCursors[i];
int result = mComparator.compare(left, right);
if (result <= 0) {
greatest = i;
}
}
mPosition--;
if (greatest != -1) {
mActiveCursorIndex = greatest;
mActiveCursor = mCursors[mActiveCursorIndex];
}
return true;
}
@Override

View File

@ -1,5 +1,7 @@
package com.fsck.k9.helper;
import java.util.Comparator;
import android.database.Cursor;
@ -12,8 +14,8 @@ public class MergeCursorWithUniqueId extends MergeCursor {
private int mIdColumnIndex = -1;
public MergeCursorWithUniqueId(Cursor[] cursors) {
super(cursors);
public MergeCursorWithUniqueId(Cursor[] cursors, Comparator<Cursor> comparator) {
super(cursors, comparator);
if (cursors.length > MAX_CURSORS) {
throw new IllegalArgumentException("This class only supports up to " +