Use separate table to store the thread structure

This commit is contained in:
cketti 2013-01-11 03:40:35 +01:00
parent 3f84bb54f2
commit 1df88ea153
7 changed files with 617 additions and 291 deletions

View File

@ -711,8 +711,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
public void showThread(Account account, String folderName, long threadRootId) {
LocalSearch tmpSearch = new LocalSearch();
tmpSearch.addAccountUuid(account.getUuid());
tmpSearch.and(Searchfield.THREAD_ROOT, String.valueOf(threadRootId), Attribute.EQUALS);
tmpSearch.or(new SearchCondition(Searchfield.ID, Attribute.EQUALS, String.valueOf(threadRootId)));
tmpSearch.and(Searchfield.THREAD_ID, String.valueOf(threadRootId), Attribute.EQUALS);
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, true, false);
addMessageListFragment(fragment, true);

View File

@ -3758,8 +3758,9 @@ public class MessagingController implements Runnable {
List<Message> messagesInThreads = new ArrayList<Message>();
for (Message message : messages) {
long rootId = ((LocalMessage) message).getRootId();
long threadId = (rootId == -1) ? message.getId() : rootId;
LocalMessage localMessage = (LocalMessage) message;
long rootId = localMessage.getRootId();
long threadId = (rootId == -1) ? localMessage.getThreadId() : rootId;
Message[] messagesInThread = localStore.getMessagesInThread(threadId);
Collections.addAll(messagesInThreads, messagesInThread);

View File

@ -91,8 +91,12 @@ import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.SpecialColumns;
import com.fsck.k9.provider.EmailProvider.ThreadColumns;
import com.fsck.k9.search.ConditionsTreeNode;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield;
import com.fsck.k9.search.SqlQueryBuilder;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
@ -117,11 +121,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
MessageColumns.ATTACHMENT_COUNT,
MessageColumns.FOLDER_ID,
MessageColumns.PREVIEW,
MessageColumns.THREAD_ROOT,
ThreadColumns.ROOT,
SpecialColumns.ACCOUNT_UUID,
SpecialColumns.FOLDER_NAME,
MessageColumns.THREAD_COUNT,
SpecialColumns.THREAD_COUNT,
};
private static final int ID_COLUMN = 0;
@ -2083,15 +2087,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
accountMapping.put(account, messageIdList);
}
long selectionId;
if (mThreadedList) {
selectionId = (cursor.isNull(THREAD_ROOT_COLUMN)) ?
cursor.getLong(ID_COLUMN) : cursor.getLong(THREAD_ROOT_COLUMN);
} else {
selectionId = cursor.getLong(ID_COLUMN);
}
messageIdList.add(selectionId);
messageIdList.add(cursor.getLong(ID_COLUMN));
}
}
@ -2888,19 +2884,30 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
String accountUuid = mAccountUuids[id];
Account account = mPreferences.getAccount(accountUuid);
String threadId = getThreadId(mSearch);
Uri uri;
String[] projection;
if (mThreadedList) {
boolean needConditions;
if (threadId != null) {
uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/thread/" + threadId);
projection = PROJECTION;
needConditions = false;
} else if (mThreadedList) {
uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages/threaded");
projection = THREADED_PROJECTION;
needConditions = true;
} else {
uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages");
projection = PROJECTION;
needConditions = true;
}
StringBuilder query = new StringBuilder();
List<String> queryArgs = new ArrayList<String>();
SqlQueryBuilder.buildWhereClause(account, mSearch.getConditions(), query, queryArgs);
if (needConditions) {
SqlQueryBuilder.buildWhereClause(account, mSearch.getConditions(), query, queryArgs);
}
String selection = query.toString();
String[] selectionArgs = queryArgs.toArray(new String[0]);
@ -2911,6 +2918,17 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
sortOrder);
}
private String getThreadId(LocalSearch search) {
for (ConditionsTreeNode node : search.getLeafSet()) {
SearchCondition condition = node.mCondition;
if (condition.field == Searchfield.THREAD_ID) {
return condition.value;
}
}
return null;
}
private String buildSortOrder() {
String sortColumn = MessageColumns.ID;
switch (mSortType) {

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,7 @@ public class EmailProvider extends ContentProvider {
private static final int MESSAGE_BASE = 0;
private static final int MESSAGES = MESSAGE_BASE;
private static final int MESSAGES_THREADED = MESSAGE_BASE + 1;
//private static final int MESSAGES_THREAD = MESSAGE_BASE + 2;
private static final int MESSAGES_THREAD = MESSAGE_BASE + 2;
private static final int STATS_BASE = 100;
private static final int STATS = STATS_BASE;
@ -78,8 +78,6 @@ public class EmailProvider extends ContentProvider {
MessageColumns.ATTACHMENT_COUNT,
MessageColumns.FOLDER_ID,
MessageColumns.PREVIEW,
MessageColumns.THREAD_ROOT,
MessageColumns.THREAD_PARENT,
MessageColumns.READ,
MessageColumns.FLAGGED,
MessageColumns.ANSWERED,
@ -95,6 +93,8 @@ public class EmailProvider extends ContentProvider {
MessageColumns.ID
};
private static final String FOLDERS_TABLE = "folders";
private static final String[] FOLDERS_COLUMNS = {
FolderColumns.ID,
FolderColumns.NAME,
@ -112,12 +112,21 @@ public class EmailProvider extends ContentProvider {
FolderColumns.DISPLAY_CLASS
};
private static final String THREADS_TABLE = "threads";
private static final String[] THREADS_COLUMNS = {
ThreadColumns.ID,
ThreadColumns.MESSAGE_ID,
ThreadColumns.ROOT,
ThreadColumns.PARENT
};
static {
UriMatcher matcher = sUriMatcher;
matcher.addURI(AUTHORITY, "account/*/messages", MESSAGES);
matcher.addURI(AUTHORITY, "account/*/messages/threaded", MESSAGES_THREADED);
//matcher.addURI(AUTHORITY, "account/*/thread/#", MESSAGES_THREAD);
matcher.addURI(AUTHORITY, "account/*/thread/#", MESSAGES_THREAD);
matcher.addURI(AUTHORITY, "account/*/stats", STATS);
}
@ -125,6 +134,8 @@ public class EmailProvider extends ContentProvider {
public interface SpecialColumns {
public static final String ACCOUNT_UUID = "account_uuid";
public static final String THREAD_COUNT = "thread_count";
public static final String FOLDER_NAME = "name";
public static final String INTEGRATE = "integrate";
}
@ -145,9 +156,6 @@ public class EmailProvider extends ContentProvider {
public static final String ATTACHMENT_COUNT = "attachment_count";
public static final String FOLDER_ID = "folder_id";
public static final String PREVIEW = "preview";
public static final String THREAD_ROOT = "thread_root";
public static final String THREAD_PARENT = "thread_parent";
public static final String THREAD_COUNT = "thread_count";
public static final String READ = "read";
public static final String FLAGGED = "flagged";
public static final String ANSWERED = "answered";
@ -179,6 +187,13 @@ public class EmailProvider extends ContentProvider {
public static final String DISPLAY_CLASS = "display_class";
}
public interface ThreadColumns {
public static final String ID = "id";
public static final String MESSAGE_ID = "message_id";
public static final String ROOT = "root";
public static final String PARENT = "parent";
}
public interface StatsColumns {
public static final String UNREAD_COUNT = "unread_count";
public static final String FLAGGED_COUNT = "flagged_count";
@ -216,7 +231,8 @@ public class EmailProvider extends ContentProvider {
Cursor cursor = null;
switch (match) {
case MESSAGES:
case MESSAGES_THREADED: {
case MESSAGES_THREADED:
case MESSAGES_THREAD: {
List<String> segments = uri.getPathSegments();
String accountUuid = segments.get(1);
@ -238,11 +254,16 @@ public class EmailProvider extends ContentProvider {
} else if (match == MESSAGES_THREADED) {
cursor = getThreadedMessages(accountUuid, dbProjection, selection,
selectionArgs, sortOrder);
} else if (match == MESSAGES_THREAD) {
String threadId = segments.get(3);
cursor = getThread(accountUuid, dbProjection, threadId, sortOrder);
} else {
throw new RuntimeException("Not implemented");
}
cursor.setNotificationUri(contentResolver, uri);
Uri notificationUri = Uri.withAppendedPath(CONTENT_URI, "account/" + accountUuid +
"/messages");
cursor.setNotificationUri(contentResolver, notificationUri);
cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection,
specialColumns);
@ -328,6 +349,7 @@ public class EmailProvider extends ContentProvider {
}
query.append(" FROM messages m " +
"JOIN threads t ON (t.message_id = m.id) " +
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
"WHERE ");
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS,
@ -374,14 +396,20 @@ public class EmailProvider extends ContentProvider {
if (MessageColumns.DATE.equals(columnName)) {
query.append("MAX(m.date) AS " + MessageColumns.DATE);
} else if (MessageColumns.THREAD_COUNT.equals(columnName)) {
query.append("COUNT(h.id) AS " + MessageColumns.THREAD_COUNT);
} else if (SpecialColumns.THREAD_COUNT.equals(columnName)) {
query.append("COUNT(h.id) AS " + SpecialColumns.THREAD_COUNT);
} else if (SpecialColumns.FOLDER_NAME.equals(columnName)) {
query.append("f." + SpecialColumns.FOLDER_NAME + " AS " +
SpecialColumns.FOLDER_NAME);
} else if (SpecialColumns.INTEGRATE.equals(columnName)) {
query.append("f." + SpecialColumns.INTEGRATE + " AS " +
SpecialColumns.INTEGRATE);
} else if (ThreadColumns.ROOT.equals(columnName)) {
// Always return the thread ID of the root message (even for the root
// message itself)
query.append("CASE WHEN t2." + ThreadColumns.ROOT + " IS NULL THEN " +
"t2." + ThreadColumns.ID + " ELSE t2." + ThreadColumns.ROOT +
" END AS " + ThreadColumns.ROOT);
} else {
query.append("m.");
query.append(columnName);
@ -391,8 +419,10 @@ public class EmailProvider extends ContentProvider {
}
query.append(
" FROM messages h JOIN messages m " +
"ON (h.id = m.thread_root OR h.id = m.id) ");
" FROM messages h " +
"LEFT JOIN threads t1 ON (t1.message_id = h.id) " +
"LEFT JOIN threads t2 ON (t1.id = t2.id OR t1.id = t2.root) " +
"LEFT JOIN messages m ON (m.id = t2.message_id) ");
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
query.append("LEFT JOIN folders f ON (m.folder_id = f.id) ");
@ -400,9 +430,9 @@ public class EmailProvider extends ContentProvider {
query.append(
"WHERE " +
"(m.deleted = 0 AND " +
"(m.empty IS NULL OR m.empty != 1) AND " +
"h.thread_root IS NULL) ");
"(t1.root IS NULL AND " +
"m.deleted = 0 AND " +
"(m.empty IS NULL OR m.empty != 1)) ");
if (!StringUtils.isNullOrEmpty(selection)) {
query.append("AND (");
@ -427,6 +457,63 @@ public class EmailProvider extends ContentProvider {
}
}
protected Cursor getThread(String accountUuid, final String[] projection, final String threadId,
final String sortOrder) {
Account account = getAccount(accountUuid);
LockableDatabase database = getDatabase(account);
try {
return database.execute(false, new DbCallback<Cursor>() {
@Override
public Cursor doDbWork(SQLiteDatabase db) throws WrappedException,
UnavailableStorageException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
boolean first = true;
for (String columnName : projection) {
if (!first) {
query.append(",");
} else {
first = false;
}
if (MessageColumns.ID.equals(columnName)) {
query.append("m." + MessageColumns.ID + " AS " + MessageColumns.ID);
} else {
query.append(columnName);
}
}
query.append(" FROM " + THREADS_TABLE + " t JOIN " + MESSAGES_TABLE + " m " +
"ON (m." + MessageColumns.ID + " = t." + ThreadColumns.MESSAGE_ID +
") ");
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
query.append("LEFT JOIN " + FOLDERS_TABLE + " f " +
"ON (m." + MessageColumns.FOLDER_ID + " = f." + FolderColumns.ID +
") ");
}
query.append("WHERE (t." + ThreadColumns.ID + " = ? OR " +
ThreadColumns.ROOT + " = ?) AND " +
InternalMessageColumns.DELETED + " = 0 AND (" +
InternalMessageColumns.EMPTY + " IS NULL OR " +
InternalMessageColumns.EMPTY + " != 1)");
query.append(" ORDER BY ");
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS,
"m.", sortOrder));
return db.rawQuery(query.toString(), new String[] { threadId, threadId });
}
});
} catch (UnavailableStorageException e) {
throw new RuntimeException("Storage not available", e);
}
}
private Cursor getAccountStats(String accountUuid, String[] columns,
final String selection, final String[] selectionArgs) {

View File

@ -76,7 +76,7 @@ public interface SearchSpecification extends Parcelable {
MESSAGE_CONTENTS,
ATTACHMENT_COUNT,
DELETED,
THREAD_ROOT,
THREAD_ID,
ID,
INTEGRATE,
READ,

View File

@ -66,6 +66,12 @@ public class SqlQueryBuilder {
}
break;
}
case THREAD_ID: {
query.append("threads.id = ? OR threads.root = ?");
selectionArgs.add(condition.value);
selectionArgs.add(condition.value);
break;
}
default: {
appendCondition(condition, query, selectionArgs);
}
@ -149,10 +155,6 @@ public class SqlQueryBuilder {
columnName = "subject";
break;
}
case THREAD_ROOT: {
columnName = "thread_root";
break;
}
case TO: {
columnName = "to_list";
break;
@ -177,6 +179,7 @@ public class SqlQueryBuilder {
columnName = "display_class";
break;
}
case THREAD_ID:
case FOLDER:
case SEARCHABLE: {
// Special cases handled in buildWhereClauseInternal()
@ -258,7 +261,7 @@ public class SqlQueryBuilder {
case FOLDER:
case ID:
case INTEGRATE:
case THREAD_ROOT:
case THREAD_ID:
case READ:
case FLAGGED: {
return true;
@ -272,7 +275,7 @@ public class SqlQueryBuilder {
public static String addPrefixToSelection(String[] columnNames, String prefix, String selection) {
String result = selection;
for (String columnName : columnNames) {
result = result.replaceAll("\\b" + columnName + "\\b", prefix + columnName);
result = result.replaceAll("(?<=^|[^\\.])\\b" + columnName + "\\b", prefix + columnName);
}
return result;