Merge branch 'threading_schema_change'

This commit is contained in:
cketti 2013-01-12 23:03:38 +01:00
commit 543fefebaa
7 changed files with 840 additions and 404 deletions

View File

@ -371,16 +371,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
int itemId = item.getItemId();
switch (itemId) {
case android.R.id.home: {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
} else if (mMessageListFragment.isManualSearch()) {
onBackPressed();
} else if (!mSingleFolderMode) {
onAccounts();
} else {
onShowFolderList();
}
goBack();
return true;
}
case R.id.compose: {
@ -719,8 +710,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);
@ -731,4 +721,18 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
// Remove action button for remote search
configureMenu(mMenu);
}
@Override
public void goBack() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
} else if (mMessageListFragment.isManualSearch()) {
onBackPressed();
} else if (!mSingleFolderMode) {
onAccounts();
} else {
onShowFolderList();
}
}
}

View File

@ -2680,17 +2680,28 @@ public class MessagingController implements Runnable {
}
public void setFlag(final Account account, final List<Long> 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<Long> messageIds,
public void setFlagForThreads(final Account account, final List<Long> 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<Long> ids,
final Flag flag, final boolean newState, final boolean threadedList) {
LocalStore localStore;
@ -2704,7 +2715,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);
}
@ -2712,7 +2727,7 @@ public class MessagingController implements Runnable {
// Read folder name and UID of messages from the database
Map<String, List<String>> 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;
@ -3905,8 +3920,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;
@ -425,6 +429,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private static final int ACTION_REFRESH_TITLE = 2;
private static final int ACTION_PROGRESS = 3;
private static final int ACTION_REMOTE_SEARCH_FINISHED = 4;
private static final int ACTION_GO_BACK = 5;
public void folderLoading(String folder, boolean loading) {
@ -458,6 +463,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
});
}
public void goBack() {
android.os.Message msg = android.os.Message.obtain(this, ACTION_GO_BACK);
sendMessage(msg);
}
@Override
public void handleMessage(android.os.Message msg) {
// The following messages don't need an attached activity.
@ -490,6 +500,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
MessageListFragment.this.progress(progress);
break;
}
case ACTION_GO_BACK: {
mFragmentListener.goBack();
break;
}
}
}
}
@ -1195,8 +1209,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mActiveMessages = null; // don't need it any more
final Account account = messages.get(0).getFolder().getAccount();
account.setLastSelectedFolderName(destFolderName);
// We currently only support copy/move in 'single account mode', so it's okay to
// use mAccount.
mAccount.setLastSelectedFolderName(destFolderName);
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE:
@ -2052,10 +2067,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();
}
@ -2065,7 +2086,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return;
}
Map<Account, List<Long>> accountMapping = new HashMap<Account, List<Long>>();
Map<Account, List<Long>> messageMap = new HashMap<Account, List<Long>>();
Map<Account, List<Long>> threadMap = new HashMap<Account, List<Long>>();
Set<Account> accounts = new HashSet<Account>();
for (int position = 0, end = mAdapter.getCount(); position < end; position++) {
@ -2075,29 +2097,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<Long> messageIdList = accountMapping.get(account);
if (messageIdList == null) {
messageIdList = new ArrayList<Long>();
accountMapping.put(account, messageIdList);
}
long selectionId;
if (mThreadedList) {
selectionId = (cursor.isNull(THREAD_ROOT_COLUMN)) ?
cursor.getLong(ID_COLUMN) : cursor.getLong(THREAD_ROOT_COLUMN);
if (mThreadedList && cursor.getInt(THREAD_COUNT_COLUMN) > 1) {
List<Long> threadRootIdList = threadMap.get(account);
if (threadRootIdList == null) {
threadRootIdList = new ArrayList<Long>();
threadMap.put(account, threadRootIdList);
}
threadRootIdList.add(cursor.getLong(THREAD_ROOT_COLUMN));
} else {
selectionId = cursor.getLong(ID_COLUMN);
}
List<Long> messageIdList = messageMap.get(account);
if (messageIdList == null) {
messageIdList = new ArrayList<Long>();
messageMap.put(account, messageIdList);
}
messageIdList.add(selectionId);
messageIdList.add(cursor.getLong(ID_COLUMN));
}
}
}
for (Account account : accounts) {
List<Long> messageIds = accountMapping.get(account);
mController.setFlag(account, messageIds, flag, newState, mThreadedList);
List<Long> messageIds = messageMap.get(account);
List<Long> threadRootIds = threadMap.get(account);
if (messageIds != null) {
mController.setFlag(account, messageIds, flag, newState);
}
if (threadRootIds != null) {
mController.setFlagForThreads(account, threadRootIds, flag, newState);
}
}
computeBatchDirection();
@ -2118,10 +2150,16 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return;
}
final Folder folder = (messages.size() == 1) ?
messages.get(0).getFolder() : mCurrentFolder.folder;
final Folder folder;
if (mIsThreadDisplay) {
folder = messages.get(0).getFolder();
} else if (mSingleFolderMode) {
folder = mCurrentFolder.folder;
} else {
folder = null;
}
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder, messages);
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, mAccount, folder, messages);
}
private void onCopy(Message message) {
@ -2139,10 +2177,16 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return;
}
final Folder folder = (messages.size() == 1) ?
messages.get(0).getFolder() : mCurrentFolder.folder;
final Folder folder;
if (mIsThreadDisplay) {
folder = messages.get(0).getFolder();
} else if (mSingleFolderMode) {
folder = mCurrentFolder.folder;
} else {
folder = null;
}
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder, messages);
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, mAccount, folder, messages);
}
/**
@ -2159,11 +2203,19 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
*
* @see #startActivityForResult(Intent, int)
*/
private void displayFolderChoice(final int requestCode, final Folder folder, final List<Message> messages) {
final Intent intent = new Intent(getActivity(), ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, folder.getAccount().getUuid());
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, folder.getName());
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, folder.getAccount().getLastSelectedFolderName());
private void displayFolderChoice(int requestCode, Account account, Folder folder,
List<Message> messages) {
Intent intent = new Intent(getActivity(), ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, account.getUuid());
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, account.getLastSelectedFolderName());
if (folder == null) {
intent.putExtra(ChooseFolder.EXTRA_SHOW_CURRENT, "yes");
} else {
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, folder.getName());
}
// remember the selected messages for #onActivityResult
mActiveMessages = messages;
startActivityForResult(intent, requestCode);
@ -2318,37 +2370,14 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void copyOrMove(List<Message> messages, final String destination,
final FolderOperation operation) {
if (K9.FOLDER_NONE.equalsIgnoreCase(destination)) {
if (K9.FOLDER_NONE.equalsIgnoreCase(destination) || !mSingleAccountMode) {
return;
}
boolean first = true;
Account account = null;
String folderName = null;
List<Message> outMessages = new ArrayList<Message>();
Account account = mAccount;
Map<String, List<Message>> folderMap = new HashMap<String, List<Message>>();
for (Message message : messages) {
if (first) {
first = false;
folderName = message.getFolder().getName();
account = message.getFolder().getAccount();
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) ||
(operation == FolderOperation.COPY &&
!mController.isCopyCapable(account))) {
// Account is not copy/move capable
return;
}
} else if (!message.getFolder().getAccount().equals(account) ||
!message.getFolder().getName().equals(folderName)) {
// Make sure all messages come from the same account/folder
return;
}
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) ||
(operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
@ -2361,20 +2390,36 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return;
}
String folderName = message.getFolder().getName();
if (folderName.equals(destination)) {
// Skip messages already in the destination folder
continue;
}
List<Message> outMessages = folderMap.get(folderName);
if (outMessages == null) {
outMessages = new ArrayList<Message>();
folderMap.put(folderName, outMessages);
}
outMessages.add(message);
}
if (operation == FolderOperation.MOVE) {
if (mThreadedList) {
mController.moveMessagesInThread(account, folderName, outMessages, destination);
for (String folderName : folderMap.keySet()) {
List<Message> outMessages = folderMap.get(folderName);
if (operation == FolderOperation.MOVE) {
if (mThreadedList) {
mController.moveMessagesInThread(account, folderName, outMessages, destination);
} else {
mController.moveMessages(account, folderName, outMessages, destination, null);
}
} else {
mController.moveMessages(account, folderName, outMessages, destination, null);
}
} else {
if (mThreadedList) {
mController.copyMessagesInThread(account, folderName, outMessages, destination);
} else {
mController.copyMessages(account, folderName, outMessages, destination, null);
if (mThreadedList) {
mController.copyMessagesInThread(account, folderName, outMessages, destination);
} else {
mController.copyMessages(account, folderName, outMessages, destination, null);
}
}
}
}
@ -2713,6 +2758,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
void onCompose(Account account);
boolean startSearch(Account account, String folderName);
void remoteSearchStarted();
void goBack();
}
public void onReverseSort() {
@ -2888,19 +2934,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 +2968,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) {
@ -2960,6 +3028,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (mIsThreadDisplay && data.getCount() == 0) {
mHandler.goBack();
return;
}
// Remove the "Loading..." view
mPullToRefreshView.setEmptyView(null);

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,
@ -363,6 +385,7 @@ public class EmailProvider extends ContentProvider {
UnavailableStorageException {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
boolean first = true;
for (String columnName : projection) {
@ -372,54 +395,144 @@ public class EmailProvider extends ContentProvider {
first = false;
}
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.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);
if (MessageColumns.ID.equals(columnName)) {
query.append("u." + MessageColumns.ID + " AS " + MessageColumns.ID);
} else if (MessageColumns.DATE.equals(columnName)) {
query.append("MAX(date) AS " + MessageColumns.DATE);
} else if (SpecialColumns.THREAD_COUNT.equals(columnName)) {
query.append("COUNT(g) AS " + SpecialColumns.THREAD_COUNT);
} else {
query.append("m.");
query.append(columnName);
query.append(" AS ");
query.append(columnName);
}
}
query.append(
" FROM messages h JOIN messages m " +
"ON (h.id = m.thread_root OR h.id = m.id) ");
query.append(" FROM (");
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
query.append("LEFT JOIN folders f ON (m.folder_id = f.id) ");
}
createThreadedSubQuery(projection, selection, selectionArgs, "t1.id = t2.id", query);
query.append(" UNION ALL ");
createThreadedSubQuery(projection, selection, selectionArgs, "t1.id = t2.root", query);
query.append(
"WHERE " +
"(m.deleted = 0 AND " +
"(m.empty IS NULL OR m.empty != 1) AND " +
"h.thread_root IS NULL) ");
if (!StringUtils.isNullOrEmpty(selection)) {
query.append("AND (");
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
"h.", selection));
query.append(") ");
}
query.append("GROUP BY h.id");
query.append(") u GROUP BY g");
if (!StringUtils.isNullOrEmpty(sortOrder)) {
query.append(" ORDER BY ");
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
"m.", sortOrder));
"u.", sortOrder));
}
return db.rawQuery(query.toString(), selectionArgs);
// We need the selection arguments twice. Once for each sub query.
String[] args = new String[selectionArgs.length * 2];
System.arraycopy(selectionArgs, 0, args, 0, selectionArgs.length);
System.arraycopy(selectionArgs, 0, args, selectionArgs.length, selectionArgs.length);
return db.rawQuery(query.toString(), args);
}
});
} catch (UnavailableStorageException e) {
throw new RuntimeException("Storage not available", e);
}
}
private void createThreadedSubQuery(String[] projection, String selection,
String[] selectionArgs, String join, StringBuilder query) {
query.append("SELECT h." + MessageColumns.ID + " AS g");
for (String columnName : projection) {
if (SpecialColumns.THREAD_COUNT.equals(columnName)) {
// Skip
} else if (SpecialColumns.FOLDER_NAME.equals(columnName) ||
SpecialColumns.INTEGRATE.equals(columnName)) {
query.append("," + columnName);
} 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);
query.append(" AS ");
query.append(columnName);
}
}
query.append(
" FROM messages h " +
"LEFT JOIN threads t1 ON (t1.message_id = h.id) " +
"JOIN threads t2 ON (");
query.append(join);
query.append(") " +
"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) ");
}
query.append(
"WHERE " +
"(t1.root IS NULL AND " +
"m.deleted = 0 AND " +
"(m.empty IS NULL OR m.empty != 1))");
if (!StringUtils.isNullOrEmpty(selection)) {
query.append(" AND (");
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
"h.", selection));
query.append(")");
}
}
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) {

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;