mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-24 02:12:15 -05:00
Use separate table to store the thread structure
This commit is contained in:
parent
3f84bb54f2
commit
1df88ea153
@ -711,8 +711,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
|
|||||||
public void showThread(Account account, String folderName, long threadRootId) {
|
public void showThread(Account account, String folderName, long threadRootId) {
|
||||||
LocalSearch tmpSearch = new LocalSearch();
|
LocalSearch tmpSearch = new LocalSearch();
|
||||||
tmpSearch.addAccountUuid(account.getUuid());
|
tmpSearch.addAccountUuid(account.getUuid());
|
||||||
tmpSearch.and(Searchfield.THREAD_ROOT, String.valueOf(threadRootId), Attribute.EQUALS);
|
tmpSearch.and(Searchfield.THREAD_ID, String.valueOf(threadRootId), Attribute.EQUALS);
|
||||||
tmpSearch.or(new SearchCondition(Searchfield.ID, Attribute.EQUALS, String.valueOf(threadRootId)));
|
|
||||||
|
|
||||||
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, true, false);
|
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, true, false);
|
||||||
addMessageListFragment(fragment, true);
|
addMessageListFragment(fragment, true);
|
||||||
|
@ -3758,8 +3758,9 @@ public class MessagingController implements Runnable {
|
|||||||
|
|
||||||
List<Message> messagesInThreads = new ArrayList<Message>();
|
List<Message> messagesInThreads = new ArrayList<Message>();
|
||||||
for (Message message : messages) {
|
for (Message message : messages) {
|
||||||
long rootId = ((LocalMessage) message).getRootId();
|
LocalMessage localMessage = (LocalMessage) message;
|
||||||
long threadId = (rootId == -1) ? message.getId() : rootId;
|
long rootId = localMessage.getRootId();
|
||||||
|
long threadId = (rootId == -1) ? localMessage.getThreadId() : rootId;
|
||||||
|
|
||||||
Message[] messagesInThread = localStore.getMessagesInThread(threadId);
|
Message[] messagesInThread = localStore.getMessagesInThread(threadId);
|
||||||
Collections.addAll(messagesInThreads, messagesInThread);
|
Collections.addAll(messagesInThreads, messagesInThread);
|
||||||
|
@ -91,8 +91,12 @@ import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
|||||||
import com.fsck.k9.provider.EmailProvider;
|
import com.fsck.k9.provider.EmailProvider;
|
||||||
import com.fsck.k9.provider.EmailProvider.MessageColumns;
|
import com.fsck.k9.provider.EmailProvider.MessageColumns;
|
||||||
import com.fsck.k9.provider.EmailProvider.SpecialColumns;
|
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.LocalSearch;
|
||||||
import com.fsck.k9.search.SearchSpecification;
|
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.fsck.k9.search.SqlQueryBuilder;
|
||||||
import com.handmark.pulltorefresh.library.PullToRefreshBase;
|
import com.handmark.pulltorefresh.library.PullToRefreshBase;
|
||||||
import com.handmark.pulltorefresh.library.PullToRefreshListView;
|
import com.handmark.pulltorefresh.library.PullToRefreshListView;
|
||||||
@ -117,11 +121,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
MessageColumns.ATTACHMENT_COUNT,
|
MessageColumns.ATTACHMENT_COUNT,
|
||||||
MessageColumns.FOLDER_ID,
|
MessageColumns.FOLDER_ID,
|
||||||
MessageColumns.PREVIEW,
|
MessageColumns.PREVIEW,
|
||||||
MessageColumns.THREAD_ROOT,
|
ThreadColumns.ROOT,
|
||||||
SpecialColumns.ACCOUNT_UUID,
|
SpecialColumns.ACCOUNT_UUID,
|
||||||
SpecialColumns.FOLDER_NAME,
|
SpecialColumns.FOLDER_NAME,
|
||||||
|
|
||||||
MessageColumns.THREAD_COUNT,
|
SpecialColumns.THREAD_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final int ID_COLUMN = 0;
|
private static final int ID_COLUMN = 0;
|
||||||
@ -2083,15 +2087,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
accountMapping.put(account, messageIdList);
|
accountMapping.put(account, messageIdList);
|
||||||
}
|
}
|
||||||
|
|
||||||
long selectionId;
|
messageIdList.add(cursor.getLong(ID_COLUMN));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2888,19 +2884,30 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
String accountUuid = mAccountUuids[id];
|
String accountUuid = mAccountUuids[id];
|
||||||
Account account = mPreferences.getAccount(accountUuid);
|
Account account = mPreferences.getAccount(accountUuid);
|
||||||
|
|
||||||
|
String threadId = getThreadId(mSearch);
|
||||||
|
|
||||||
Uri uri;
|
Uri uri;
|
||||||
String[] projection;
|
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");
|
uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages/threaded");
|
||||||
projection = THREADED_PROJECTION;
|
projection = THREADED_PROJECTION;
|
||||||
|
needConditions = true;
|
||||||
} else {
|
} else {
|
||||||
uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages");
|
uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages");
|
||||||
projection = PROJECTION;
|
projection = PROJECTION;
|
||||||
|
needConditions = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder query = new StringBuilder();
|
StringBuilder query = new StringBuilder();
|
||||||
List<String> queryArgs = new ArrayList<String>();
|
List<String> queryArgs = new ArrayList<String>();
|
||||||
|
if (needConditions) {
|
||||||
SqlQueryBuilder.buildWhereClause(account, mSearch.getConditions(), query, queryArgs);
|
SqlQueryBuilder.buildWhereClause(account, mSearch.getConditions(), query, queryArgs);
|
||||||
|
}
|
||||||
|
|
||||||
String selection = query.toString();
|
String selection = query.toString();
|
||||||
String[] selectionArgs = queryArgs.toArray(new String[0]);
|
String[] selectionArgs = queryArgs.toArray(new String[0]);
|
||||||
@ -2911,6 +2918,17 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
|
|||||||
sortOrder);
|
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() {
|
private String buildSortOrder() {
|
||||||
String sortColumn = MessageColumns.ID;
|
String sortColumn = MessageColumns.ID;
|
||||||
switch (mSortType) {
|
switch (mSortType) {
|
||||||
|
@ -74,7 +74,6 @@ import com.fsck.k9.provider.AttachmentProvider;
|
|||||||
import com.fsck.k9.provider.EmailProvider;
|
import com.fsck.k9.provider.EmailProvider;
|
||||||
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.SearchCondition;
|
|
||||||
import com.fsck.k9.search.SearchSpecification.Searchfield;
|
import com.fsck.k9.search.SearchSpecification.Searchfield;
|
||||||
import com.fsck.k9.search.SqlQueryBuilder;
|
import com.fsck.k9.search.SqlQueryBuilder;
|
||||||
|
|
||||||
@ -96,8 +95,10 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
* in the correct order.
|
* in the correct order.
|
||||||
*/
|
*/
|
||||||
static private String GET_MESSAGES_COLS =
|
static private String GET_MESSAGES_COLS =
|
||||||
"subject, sender_list, date, uid, flags, messages.id, to_list, cc_list, "
|
"subject, sender_list, date, uid, flags, messages.id, to_list, cc_list, " +
|
||||||
+ "bcc_list, reply_to_list, attachment_count, internal_date, message_id, folder_id, preview, thread_root, thread_parent, deleted, read, flagged, answered, forwarded ";
|
"bcc_list, reply_to_list, attachment_count, internal_date, messages.message_id, " +
|
||||||
|
"folder_id, preview, threads.id, threads.root, deleted, read, flagged, answered, " +
|
||||||
|
"forwarded ";
|
||||||
|
|
||||||
static private String GET_FOLDER_COLS = "folders.id, name, SUM(read=0), visible_limit, last_updated, status, push_state, last_pushed, SUM(flagged), integrate, top_group, poll_class, push_class, display_class";
|
static private String GET_FOLDER_COLS = "folders.id, name, SUM(read=0), visible_limit, last_updated, status, push_state, last_pushed, SUM(flagged), integrate, top_group, poll_class, push_class, display_class";
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private static final int FLAG_UPDATE_BATCH_SIZE = 500;
|
private static final int FLAG_UPDATE_BATCH_SIZE = 500;
|
||||||
|
|
||||||
public static final int DB_VERSION = 46;
|
public static final int DB_VERSION = 47;
|
||||||
|
|
||||||
protected String uUid = null;
|
protected String uUid = null;
|
||||||
|
|
||||||
@ -204,8 +205,6 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
"message_id TEXT, " +
|
"message_id TEXT, " +
|
||||||
"preview TEXT, " +
|
"preview TEXT, " +
|
||||||
"mime_type TEXT, "+
|
"mime_type TEXT, "+
|
||||||
"thread_root INTEGER, " +
|
|
||||||
"thread_parent INTEGER, " +
|
|
||||||
"normalized_subject_hash INTEGER, " +
|
"normalized_subject_hash INTEGER, " +
|
||||||
"empty INTEGER, " +
|
"empty INTEGER, " +
|
||||||
"read INTEGER default 0, " +
|
"read INTEGER default 0, " +
|
||||||
@ -226,18 +225,30 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
db.execSQL("DROP INDEX IF EXISTS msg_empty");
|
db.execSQL("DROP INDEX IF EXISTS msg_empty");
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
|
db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
|
||||||
|
|
||||||
db.execSQL("DROP INDEX IF EXISTS msg_thread_root");
|
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_root ON messages (thread_root)");
|
|
||||||
|
|
||||||
db.execSQL("DROP INDEX IF EXISTS msg_thread_parent");
|
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_parent ON messages (thread_parent)");
|
|
||||||
|
|
||||||
db.execSQL("DROP INDEX IF EXISTS msg_read");
|
db.execSQL("DROP INDEX IF EXISTS msg_read");
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
|
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
|
||||||
|
|
||||||
db.execSQL("DROP INDEX IF EXISTS msg_flagged");
|
db.execSQL("DROP INDEX IF EXISTS msg_flagged");
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
|
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
|
||||||
|
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS threads");
|
||||||
|
db.execSQL("CREATE TABLE threads (" +
|
||||||
|
"id INTEGER PRIMARY KEY, " +
|
||||||
|
"message_id INTEGER, " +
|
||||||
|
"root INTEGER, " +
|
||||||
|
"parent INTEGER" +
|
||||||
|
")");
|
||||||
|
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS threads_message_id");
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id)");
|
||||||
|
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS threads_root");
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS threads_root ON threads (root)");
|
||||||
|
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS threads_parent");
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
|
||||||
|
|
||||||
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS attachments");
|
db.execSQL("DROP TABLE IF EXISTS attachments");
|
||||||
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
|
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
|
||||||
+ "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
|
+ "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
|
||||||
@ -539,6 +550,76 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
|
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
|
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (db.getVersion() < 47) {
|
||||||
|
// Create new 'threads' table
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS threads");
|
||||||
|
db.execSQL("CREATE TABLE threads (" +
|
||||||
|
"id INTEGER PRIMARY KEY, " +
|
||||||
|
"message_id INTEGER, " +
|
||||||
|
"root INTEGER, " +
|
||||||
|
"parent INTEGER" +
|
||||||
|
")");
|
||||||
|
|
||||||
|
// Create indices for new table
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS threads_message_id");
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id)");
|
||||||
|
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS threads_root");
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS threads_root ON threads (root)");
|
||||||
|
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS threads_parent");
|
||||||
|
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
|
||||||
|
|
||||||
|
// Create entries for all messages in 'threads' table
|
||||||
|
db.execSQL("INSERT INTO threads (message_id) SELECT id FROM messages");
|
||||||
|
|
||||||
|
// Copy thread structure from 'messages' table to 'threads'
|
||||||
|
Cursor cursor = db.query("messages",
|
||||||
|
new String[] { "id", "thread_root", "thread_parent" },
|
||||||
|
null, null, null, null, null);
|
||||||
|
try {
|
||||||
|
ContentValues cv = new ContentValues();
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
cv.clear();
|
||||||
|
long messageId = cursor.getLong(0);
|
||||||
|
|
||||||
|
if (!cursor.isNull(1)) {
|
||||||
|
long threadRootMessageId = cursor.getLong(1);
|
||||||
|
db.execSQL("UPDATE threads SET root = (SELECT t.id FROM " +
|
||||||
|
"threads t WHERE t.message_id = ?) " +
|
||||||
|
"WHERE message_id = ?",
|
||||||
|
new String[] {
|
||||||
|
Long.toString(threadRootMessageId),
|
||||||
|
Long.toString(messageId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cursor.isNull(2)) {
|
||||||
|
long threadParentMessageId = cursor.getLong(2);
|
||||||
|
db.execSQL("UPDATE threads SET parent = (SELECT t.id FROM " +
|
||||||
|
"threads t WHERE t.message_id = ?) " +
|
||||||
|
"WHERE message_id = ?",
|
||||||
|
new String[] {
|
||||||
|
Long.toString(threadParentMessageId),
|
||||||
|
Long.toString(messageId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove indices for old thread-related columns in 'messages' table
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS msg_thread_root");
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS msg_thread_parent");
|
||||||
|
|
||||||
|
// Clear out old thread-related columns in 'messages'
|
||||||
|
ContentValues cv = new ContentValues();
|
||||||
|
cv.putNull("thread_root");
|
||||||
|
cv.putNull("thread_parent");
|
||||||
|
db.update("messages", cv, null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException e) {
|
||||||
Log.e(K9.LOG_TAG, "Exception while upgrading database. Resetting the DB to v0");
|
Log.e(K9.LOG_TAG, "Exception while upgrading database. Resetting the DB to v0");
|
||||||
@ -671,8 +752,21 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
database.execute(false, new DbCallback<Void>() {
|
database.execute(false, new DbCallback<Void>() {
|
||||||
@Override
|
@Override
|
||||||
public Void doDbWork(final SQLiteDatabase db) {
|
public Void doDbWork(final SQLiteDatabase db) {
|
||||||
db.execSQL("DELETE FROM messages WHERE deleted = 0 and uid not like 'Local%'");
|
// Delete entries from 'threads' table
|
||||||
db.execSQL("update folders set flagged_count = 0, unread_count = 0");
|
db.execSQL("DELETE FROM threads WHERE message_id IN " +
|
||||||
|
"(SELECT id FROM messages WHERE deleted = 0 AND uid NOT LIKE 'Local%')");
|
||||||
|
|
||||||
|
// Set 'root' and 'parent' of remaining entries in 'thread' table to 'NULL' to make
|
||||||
|
// sure the thread structure is in a valid state (this may destroy existing valid
|
||||||
|
// thread trees, but is much faster than adjusting the tree by removing messages
|
||||||
|
// one by one).
|
||||||
|
ContentValues cv = new ContentValues();
|
||||||
|
cv.putNull("root");
|
||||||
|
cv.putNull("parent");
|
||||||
|
db.update("threads", cv, null, null);
|
||||||
|
|
||||||
|
// Delete entries from 'messages' table
|
||||||
|
db.execSQL("DELETE FROM messages WHERE deleted = 0 AND uid NOT LIKE 'Local%'");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -979,6 +1073,7 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
String[] selectionArgs = queryArgs.toArray(EMPTY_STRING_ARRAY);
|
String[] selectionArgs = queryArgs.toArray(EMPTY_STRING_ARRAY);
|
||||||
|
|
||||||
String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages " +
|
String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages " +
|
||||||
|
"JOIN threads ON (threads.message_id = messages.id) " +
|
||||||
"LEFT JOIN folders ON (folders.id = messages.folder_id) WHERE " +
|
"LEFT JOIN folders ON (folders.id = messages.folder_id) WHERE " +
|
||||||
"((empty IS NULL OR empty != 1) AND deleted = 0)" +
|
"((empty IS NULL OR empty != 1) AND deleted = 0)" +
|
||||||
((!StringUtils.isNullOrEmpty(where)) ? " AND (" + where + ")" : "") +
|
((!StringUtils.isNullOrEmpty(where)) ? " AND (" + where + ")" : "") +
|
||||||
@ -1052,8 +1147,7 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
String rootIdString = Long.toString(rootId);
|
String rootIdString = Long.toString(rootId);
|
||||||
|
|
||||||
LocalSearch search = new LocalSearch();
|
LocalSearch search = new LocalSearch();
|
||||||
search.and(Searchfield.THREAD_ROOT, rootIdString, Attribute.EQUALS);
|
search.and(Searchfield.THREAD_ID, rootIdString, Attribute.EQUALS);
|
||||||
search.or(new SearchCondition(Searchfield.ID, Attribute.EQUALS, rootIdString));
|
|
||||||
|
|
||||||
return searchForMessages(null, search);
|
return searchForMessages(null, search);
|
||||||
}
|
}
|
||||||
@ -1947,9 +2041,11 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
cursor = db.rawQuery(
|
cursor = db.rawQuery(
|
||||||
"SELECT "
|
"SELECT " +
|
||||||
+ GET_MESSAGES_COLS
|
GET_MESSAGES_COLS +
|
||||||
+ "FROM messages WHERE uid = ? AND folder_id = ?",
|
"FROM messages " +
|
||||||
|
"JOIN threads ON (threads.message_id = messages.id) " +
|
||||||
|
"WHERE uid = ? AND folder_id = ?",
|
||||||
new String[] {
|
new String[] {
|
||||||
message.getUid(), Long.toString(mFolderId)
|
message.getUid(), Long.toString(mFolderId)
|
||||||
});
|
});
|
||||||
@ -1987,13 +2083,13 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
return LocalStore.this.getMessages(
|
return LocalStore.this.getMessages(
|
||||||
listener,
|
listener,
|
||||||
LocalFolder.this,
|
LocalFolder.this,
|
||||||
"SELECT " + GET_MESSAGES_COLS
|
"SELECT " + GET_MESSAGES_COLS +
|
||||||
+ "FROM messages WHERE (empty IS NULL OR empty != 1) AND "
|
"FROM messages " +
|
||||||
+ (includeDeleted ? "" : "deleted = 0 AND ")
|
"JOIN threads ON (threads.message_id = messages.id) " +
|
||||||
+ " folder_id = ? ORDER BY date DESC"
|
"WHERE (empty IS NULL OR empty != 1) AND " +
|
||||||
, new String[] {
|
(includeDeleted ? "" : "deleted = 0 AND ") +
|
||||||
Long.toString(mFolderId)
|
"folder_id = ? ORDER BY date DESC",
|
||||||
}
|
new String[] { Long.toString(mFolderId) }
|
||||||
);
|
);
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
throw new WrappedException(e);
|
throw new WrappedException(e);
|
||||||
@ -2065,36 +2161,34 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
ThreadInfo threadInfo = lDestFolder.doMessageThreading(db, message);
|
ThreadInfo threadInfo = lDestFolder.doMessageThreading(db, message);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "Move" the message into the new folder and thread structure
|
* "Move" the message into the new folder
|
||||||
*/
|
*/
|
||||||
long id = lMessage.getId();
|
long msgId = lMessage.getId();
|
||||||
String[] idArg = new String[] { Long.toString(id) };
|
String[] idArg = new String[] { Long.toString(msgId) };
|
||||||
|
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put("folder_id", lDestFolder.getId());
|
cv.put("folder_id", lDestFolder.getId());
|
||||||
cv.put("uid", newUid);
|
cv.put("uid", newUid);
|
||||||
|
|
||||||
|
db.update("messages", cv, "id = ?", idArg);
|
||||||
|
|
||||||
|
// Create/update entry in 'threads' table for the message in the
|
||||||
|
// target folder
|
||||||
|
cv.clear();
|
||||||
|
cv.put("message_id", msgId);
|
||||||
|
if (threadInfo.threadId == -1) {
|
||||||
if (threadInfo.rootId != -1) {
|
if (threadInfo.rootId != -1) {
|
||||||
cv.put("thread_root", threadInfo.rootId);
|
cv.put("root", threadInfo.rootId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threadInfo.parentId != -1) {
|
if (threadInfo.parentId != -1) {
|
||||||
cv.put("thread_parent", threadInfo.parentId);
|
cv.put("parent", threadInfo.parentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
db.update("messages", cv, "id = ?", idArg);
|
db.insert("threads", null, cv);
|
||||||
|
} else {
|
||||||
if (threadInfo.id != -1) {
|
db.update("threads", cv, "id = ?",
|
||||||
String[] oldIdArg =
|
new String[] { Long.toString(threadInfo.threadId) });
|
||||||
new String[] { Long.toString(threadInfo.id) };
|
|
||||||
|
|
||||||
cv.clear();
|
|
||||||
cv.put("thread_root", id);
|
|
||||||
db.update("messages", cv, "thread_root = ?", oldIdArg);
|
|
||||||
|
|
||||||
cv.clear();
|
|
||||||
cv.put("thread_parent", id);
|
|
||||||
db.update("messages", cv, "thread_parent = ?", oldIdArg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2112,29 +2206,19 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
cv.put("read", 1);
|
cv.put("read", 1);
|
||||||
cv.put("deleted", 1);
|
cv.put("deleted", 1);
|
||||||
cv.put("folder_id", mFolderId);
|
cv.put("folder_id", mFolderId);
|
||||||
|
cv.put("empty", 0);
|
||||||
|
|
||||||
String messageId = message.getMessageId();
|
String messageId = message.getMessageId();
|
||||||
if (messageId != null) {
|
if (messageId != null) {
|
||||||
cv.put("message_id", messageId);
|
cv.put("message_id", messageId);
|
||||||
cv.put("empty", 1);
|
|
||||||
|
|
||||||
long rootId = lMessage.getRootId();
|
|
||||||
if (rootId != -1) {
|
|
||||||
cv.put("thread_root", rootId);
|
|
||||||
}
|
|
||||||
|
|
||||||
long parentId = lMessage.getParentId();
|
|
||||||
if (parentId != -1) {
|
|
||||||
cv.put("thread_parent", parentId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final long newId;
|
final long newId;
|
||||||
if (threadInfo.id != -1) {
|
if (threadInfo.msgId != -1) {
|
||||||
// There already existed an empty message in the target folder.
|
// There already existed an empty message in the target folder.
|
||||||
// Let's use it as placeholder.
|
// Let's use it as placeholder.
|
||||||
|
|
||||||
newId = threadInfo.id;
|
newId = threadInfo.msgId;
|
||||||
|
|
||||||
db.update("messages", cv, "id = ?",
|
db.update("messages", cv, "id = ?",
|
||||||
new String[] { Long.toString(newId) });
|
new String[] { Long.toString(newId) });
|
||||||
@ -2143,27 +2227,14 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Replace all "thread links" to the original message with links to
|
* Update old entry in 'threads' table to point to the newly
|
||||||
* the placeholder message.
|
* created placeholder.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
String[] whereArgs = new String[] {
|
|
||||||
Long.toString(mFolderId),
|
|
||||||
Long.toString(id) };
|
|
||||||
|
|
||||||
// Note: If there was an empty message in the target folder we
|
|
||||||
// already reconnected some messages to point to 'id'. We don't
|
|
||||||
// want to change those links again, so we limit the update
|
|
||||||
// statements below to the source folder.
|
|
||||||
cv.clear();
|
cv.clear();
|
||||||
cv.put("thread_root", newId);
|
cv.put("message_id", newId);
|
||||||
db.update("messages", cv, "folder_id = ? AND thread_root = ?",
|
db.update("threads", cv, "id = ?",
|
||||||
whereArgs);
|
new String[] { Long.toString(lMessage.getThreadId()) });
|
||||||
|
|
||||||
cv.clear();
|
|
||||||
cv.put("thread_parent", newId);
|
|
||||||
db.update("messages", cv, "folder_id = ? AND thread_parent = ?",
|
|
||||||
whereArgs);
|
|
||||||
}
|
}
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
throw new WrappedException(e);
|
throw new WrappedException(e);
|
||||||
@ -2285,21 +2356,23 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ThreadInfo getThreadInfo(SQLiteDatabase db, String messageId) {
|
private ThreadInfo getThreadInfo(SQLiteDatabase db, String messageId) {
|
||||||
Cursor cursor = db.query("messages",
|
String sql = "SELECT t.id, t.message_id, t.root, t.parent " +
|
||||||
new String[] { "id", "thread_root", "thread_parent" },
|
"FROM threads t " +
|
||||||
"folder_id=? AND message_id=?",
|
"JOIN messages m ON (t.message_id = m.id) " +
|
||||||
new String[] { Long.toString(mFolderId), messageId },
|
"WHERE m.folder_id = ? AND m.message_id = ?";
|
||||||
null, null, null);
|
String[] selectionArgs = { Long.toString(mFolderId), messageId };
|
||||||
|
Cursor cursor = db.rawQuery(sql, selectionArgs);
|
||||||
|
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
try {
|
try {
|
||||||
if (cursor.getCount() == 1) {
|
if (cursor.getCount() == 1) {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
long id = cursor.getLong(0);
|
long threadId = cursor.getLong(0);
|
||||||
long rootId = (cursor.isNull(1)) ? -1 : cursor.getLong(1);
|
long msgId = cursor.getLong(1);
|
||||||
long parentId = (cursor.isNull(2)) ? -1 : cursor.getLong(2);
|
long rootId = (cursor.isNull(2)) ? -1 : cursor.getLong(2);
|
||||||
|
long parentId = (cursor.isNull(3)) ? -1 : cursor.getLong(3);
|
||||||
|
|
||||||
return new ThreadInfo(id, messageId, rootId, parentId);
|
return new ThreadInfo(threadId, msgId, messageId, rootId, parentId);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
@ -2374,7 +2447,7 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
if (oldMessageId == -1) {
|
if (oldMessageId == -1) {
|
||||||
// This is a new message. Do the message threading.
|
// This is a new message. Do the message threading.
|
||||||
ThreadInfo threadInfo = doMessageThreading(db, message);
|
ThreadInfo threadInfo = doMessageThreading(db, message);
|
||||||
oldMessageId = threadInfo.id;
|
oldMessageId = threadInfo.msgId;
|
||||||
rootId = threadInfo.rootId;
|
rootId = threadInfo.rootId;
|
||||||
parentId = threadInfo.parentId;
|
parentId = threadInfo.parentId;
|
||||||
}
|
}
|
||||||
@ -2437,25 +2510,32 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
cv.put("message_id", messageId);
|
cv.put("message_id", messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rootId != -1) {
|
long msgId;
|
||||||
cv.put("thread_root", rootId);
|
|
||||||
}
|
|
||||||
if (parentId != -1) {
|
|
||||||
cv.put("thread_parent", parentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
long messageUid;
|
|
||||||
|
|
||||||
if (oldMessageId == -1) {
|
if (oldMessageId == -1) {
|
||||||
messageUid = db.insert("messages", "uid", cv);
|
msgId = db.insert("messages", "uid", cv);
|
||||||
|
|
||||||
|
// Create entry in 'threads' table
|
||||||
|
cv.clear();
|
||||||
|
cv.put("message_id", msgId);
|
||||||
|
|
||||||
|
if (rootId != -1) {
|
||||||
|
cv.put("root", rootId);
|
||||||
|
}
|
||||||
|
if (parentId != -1) {
|
||||||
|
cv.put("parent", parentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.insert("threads", null, cv);
|
||||||
} else {
|
} else {
|
||||||
db.update("messages", cv, "id = ?", new String[] { Long.toString(oldMessageId) });
|
db.update("messages", cv, "id = ?", new String[] { Long.toString(oldMessageId) });
|
||||||
messageUid = oldMessageId;
|
msgId = oldMessageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Part attachment : attachments) {
|
for (Part attachment : attachments) {
|
||||||
saveAttachment(messageUid, attachment, copy);
|
saveAttachment(msgId, attachment, copy);
|
||||||
}
|
}
|
||||||
saveHeaders(messageUid, (MimeMessage)message);
|
saveHeaders(msgId, (MimeMessage)message);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new MessagingException("Error appending message", e);
|
throw new MessagingException("Error appending message", e);
|
||||||
}
|
}
|
||||||
@ -2852,47 +2932,69 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
throw new MessagingException("Cannot call getUidFromMessageId on LocalFolder");
|
throw new MessagingException("Cannot call getUidFromMessageId on LocalFolder");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearMessagesWhere(final String whereClause, final String[] params) throws MessagingException {
|
public void clearMessagesOlderThan(long cutoff) throws MessagingException {
|
||||||
open(OpenMode.READ_ONLY);
|
open(OpenMode.READ_ONLY);
|
||||||
|
|
||||||
Message[] messages = LocalStore.this.getMessages(
|
Message[] messages = LocalStore.this.getMessages(
|
||||||
null,
|
null,
|
||||||
this,
|
this,
|
||||||
"SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE (empty IS NULL OR empty != 1) AND (" + whereClause + ")",
|
"SELECT " + GET_MESSAGES_COLS +
|
||||||
params);
|
"FROM messages " +
|
||||||
|
"JOIN threads ON (threads.message_id = messages.id) " +
|
||||||
|
"WHERE (empty IS NULL OR empty != 1) AND " +
|
||||||
|
"(folder_id = ? and date < ?)",
|
||||||
|
new String[] {
|
||||||
|
Long.toString(mFolderId), Long.toString(cutoff)
|
||||||
|
});
|
||||||
|
|
||||||
for (Message message : messages) {
|
for (Message message : messages) {
|
||||||
deleteAttachments(message.getUid());
|
message.destroy();
|
||||||
}
|
}
|
||||||
database.execute(false, new DbCallback<Void>() {
|
|
||||||
@Override
|
|
||||||
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
|
|
||||||
db.execSQL("DELETE FROM messages WHERE " + whereClause, params);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
notifyChange();
|
notifyChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearMessagesOlderThan(long cutoff) throws MessagingException {
|
public void clearAllMessages() throws MessagingException {
|
||||||
final String where = "folder_id = ? and date < ?";
|
final String[] folderIdArg = new String[] { Long.toString(mFolderId) };
|
||||||
final String[] params = new String[] {
|
|
||||||
Long.toString(mFolderId), Long.toString(cutoff)
|
|
||||||
};
|
|
||||||
|
|
||||||
clearMessagesWhere(where, params);
|
open(OpenMode.READ_ONLY);
|
||||||
|
|
||||||
|
try {
|
||||||
|
database.execute(false, new DbCallback<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
|
||||||
|
try {
|
||||||
|
// Get UIDs for all messages to delete
|
||||||
|
Cursor cursor = db.query("messages", new String[] { "uid" },
|
||||||
|
"folder_id = ? AND (empty IS NULL OR empty != 1)",
|
||||||
|
folderIdArg, null, null, null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Delete attachments of these messages
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
deleteAttachments(cursor.getString(0));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete entries in 'threads' and 'messages'
|
||||||
|
db.execSQL("DELETE FROM threads WHERE message_id IN " +
|
||||||
|
"(SELECT id FROM messages WHERE folder_id = ?)", folderIdArg);
|
||||||
|
db.execSQL("DELETE FROM messages WHERE folder_id = ?", folderIdArg);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
throw new WrappedException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (WrappedException e) {
|
||||||
|
throw(MessagingException) e.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
public void clearAllMessages() throws MessagingException {
|
notifyChange();
|
||||||
final String where = "folder_id = ?";
|
|
||||||
final String[] params = new String[] {
|
|
||||||
Long.toString(mFolderId)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
clearMessagesWhere(where, params);
|
|
||||||
setPushState(null);
|
setPushState(null);
|
||||||
setLastPush(0);
|
setLastPush(0);
|
||||||
setLastChecked(0);
|
setLastChecked(0);
|
||||||
@ -3142,7 +3244,7 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
String messageId = message.getMessageId();
|
String messageId = message.getMessageId();
|
||||||
|
|
||||||
// If there's already an empty message in the database, update that
|
// If there's already an empty message in the database, update that
|
||||||
long id = getDatabaseIdByMessageId(db, messageId, true);
|
ThreadInfo msgThreadInfo = getThreadInfo(db, messageId);
|
||||||
|
|
||||||
// Get the message IDs from the "References" header line
|
// Get the message IDs from the "References" header line
|
||||||
String[] referencesArray = message.getHeader("References");
|
String[] referencesArray = message.getHeader("References");
|
||||||
@ -3168,56 +3270,73 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
|
|
||||||
if (messageIds == null) {
|
if (messageIds == null) {
|
||||||
// This is not a reply, nothing to do for us.
|
// This is not a reply, nothing to do for us.
|
||||||
return new ThreadInfo(id, messageId, -1, -1);
|
return (msgThreadInfo != null) ?
|
||||||
|
msgThreadInfo : new ThreadInfo(-1, -1, messageId, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String reference : messageIds) {
|
for (String reference : messageIds) {
|
||||||
ThreadInfo threadInfo = getThreadInfo(db, reference);
|
ThreadInfo threadInfo = getThreadInfo(db, reference);
|
||||||
|
|
||||||
if (threadInfo == null) {
|
if (threadInfo == null) {
|
||||||
// Create placeholder message
|
// Create placeholder message in 'messages' table
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put("message_id", reference);
|
cv.put("message_id", reference);
|
||||||
cv.put("folder_id", mFolderId);
|
cv.put("folder_id", mFolderId);
|
||||||
cv.put("empty", 1);
|
cv.put("empty", 1);
|
||||||
|
|
||||||
|
long newMsgId = db.insert("messages", null, cv);
|
||||||
|
|
||||||
|
// Create entry in 'threads' table
|
||||||
|
cv.clear();
|
||||||
|
cv.put("message_id", newMsgId);
|
||||||
if (rootId != -1) {
|
if (rootId != -1) {
|
||||||
cv.put("thread_root", rootId);
|
cv.put("root", rootId);
|
||||||
}
|
}
|
||||||
if (parentId != -1) {
|
if (parentId != -1) {
|
||||||
cv.put("thread_parent", parentId);
|
cv.put("parent", parentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
parentId = db.insert("messages", null, cv);
|
parentId = db.insert("threads", null, cv);
|
||||||
if (rootId == -1) {
|
if (rootId == -1) {
|
||||||
rootId = parentId;
|
rootId = parentId;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (rootId != -1 && threadInfo.rootId == -1 && rootId != threadInfo.id) {
|
if (rootId != -1 && threadInfo.rootId == -1 && rootId != threadInfo.threadId) {
|
||||||
// We found an existing root container that is not
|
// We found an existing root container that is not
|
||||||
// the root of our current path (References).
|
// the root of our current path (References).
|
||||||
// Connect it to the current parent.
|
// Connect it to the current parent.
|
||||||
|
|
||||||
// Let all children know who's the new root
|
// Let all children know who's the new root
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put("thread_root", rootId);
|
cv.put("root", rootId);
|
||||||
db.update("messages", cv, "thread_root=?",
|
db.update("threads", cv, "root = ?",
|
||||||
new String[] { Long.toString(threadInfo.id) });
|
new String[] { Long.toString(threadInfo.threadId) });
|
||||||
|
|
||||||
// Connect the message to the current parent
|
// Connect the message to the current parent
|
||||||
cv.put("thread_parent", parentId);
|
cv.put("parent", parentId);
|
||||||
db.update("messages", cv, "id=?",
|
db.update("threads", cv, "id = ?",
|
||||||
new String[] { Long.toString(threadInfo.id) });
|
new String[] { Long.toString(threadInfo.threadId) });
|
||||||
} else {
|
} else {
|
||||||
rootId = (threadInfo.rootId == -1) ? threadInfo.id : threadInfo.rootId;
|
rootId = (threadInfo.rootId == -1) ?
|
||||||
|
threadInfo.threadId : threadInfo.rootId;
|
||||||
}
|
}
|
||||||
parentId = threadInfo.id;
|
parentId = threadInfo.threadId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: set in-reply-to "link" even if one already exists
|
//TODO: set in-reply-to "link" even if one already exists
|
||||||
|
|
||||||
return new ThreadInfo(id, messageId, rootId, parentId);
|
long threadId;
|
||||||
|
long msgId;
|
||||||
|
if (msgThreadInfo != null) {
|
||||||
|
threadId = msgThreadInfo.threadId;
|
||||||
|
msgId = msgThreadInfo.msgId;
|
||||||
|
} else {
|
||||||
|
threadId = -1;
|
||||||
|
msgId = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ThreadInfo(threadId, msgId, messageId, rootId, parentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Message> extractNewMessages(final List<Message> messages)
|
public List<Message> extractNewMessages(final List<Message> messages)
|
||||||
@ -3328,8 +3447,8 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
private boolean mHeadersLoaded = false;
|
private boolean mHeadersLoaded = false;
|
||||||
private boolean mMessageDirty = false;
|
private boolean mMessageDirty = false;
|
||||||
|
|
||||||
|
private long mThreadId;
|
||||||
private long mRootId;
|
private long mRootId;
|
||||||
private long mParentId;
|
|
||||||
|
|
||||||
public LocalMessage() {
|
public LocalMessage() {
|
||||||
}
|
}
|
||||||
@ -3385,8 +3504,8 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
this.mFolder = f;
|
this.mFolder = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
mRootId = (cursor.isNull(15)) ? -1 : cursor.getLong(15);
|
mThreadId = (cursor.isNull(15)) ? -1 : cursor.getLong(15);
|
||||||
mParentId = (cursor.isNull(16)) ? -1 : cursor.getLong(16);
|
mRootId = (cursor.isNull(16)) ? -1 : cursor.getLong(16);
|
||||||
|
|
||||||
boolean deleted = (cursor.getInt(17) == 1);
|
boolean deleted = (cursor.getInt(17) == 1);
|
||||||
boolean read = (cursor.getInt(18) == 1);
|
boolean read = (cursor.getInt(18) == 1);
|
||||||
@ -3656,17 +3775,9 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
|
|
||||||
localFolder.deleteAttachments(mId);
|
localFolder.deleteAttachments(mId);
|
||||||
|
|
||||||
String id = Long.toString(mId);
|
if (hasThreadChildren(db, mId)) {
|
||||||
|
// This message has children in the thread structure so we need to
|
||||||
// Check if this message has children in the thread hierarchy
|
// make it an empty message.
|
||||||
Cursor cursor = db.query("messages", new String[] { "COUNT(id)" },
|
|
||||||
"thread_root = ? OR thread_parent = ?",
|
|
||||||
new String[] {id, id},
|
|
||||||
null, null, null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (cursor.moveToFirst() && cursor.getLong(0) > 0) {
|
|
||||||
// Make the message an empty message
|
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put("id", mId);
|
cv.put("id", mId);
|
||||||
cv.put("folder_id", localFolder.getId());
|
cv.put("folder_id", localFolder.getId());
|
||||||
@ -3674,79 +3785,37 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
cv.put("message_id", getMessageId());
|
cv.put("message_id", getMessageId());
|
||||||
cv.put("empty", 1);
|
cv.put("empty", 1);
|
||||||
|
|
||||||
if (getRootId() != -1) {
|
|
||||||
cv.put("thread_root", getRootId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getParentId() != -1) {
|
|
||||||
cv.put("thread_parent", getParentId());
|
|
||||||
}
|
|
||||||
|
|
||||||
db.replace("messages", null, cv);
|
db.replace("messages", null, cv);
|
||||||
|
|
||||||
// Nothing else to do
|
// Nothing else to do
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
long parentId = getParentId();
|
// Get the message ID of the parent message if it's empty
|
||||||
|
long currentId = getEmptyThreadParent(db, mId);
|
||||||
|
|
||||||
// Check if 'parentId' is empty
|
// Delete the placeholder message
|
||||||
cursor = db.query("messages", new String[] { "id" },
|
deleteMessageRow(db, mId);
|
||||||
"id = ? AND empty = 1",
|
|
||||||
new String[] { Long.toString(parentId) },
|
|
||||||
null, null, null);
|
|
||||||
|
|
||||||
try {
|
/*
|
||||||
if (cursor.getCount() == 0) {
|
* Walk the thread tree to delete all empty parents without children
|
||||||
// If the message isn't empty we skip the loop below
|
*/
|
||||||
parentId = -1;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (parentId != -1) {
|
while (currentId != -1) {
|
||||||
String parentIdString = Long.toString(parentId);
|
if (hasThreadChildren(db, currentId)) {
|
||||||
|
// We made sure there are no empty leaf nodes and can stop now.
|
||||||
// Get the parent of the message 'parentId'
|
|
||||||
cursor = db.query("messages", new String[] { "thread_parent" },
|
|
||||||
"id = ? AND empty = 1",
|
|
||||||
new String[] { parentIdString },
|
|
||||||
null, null, null);
|
|
||||||
try {
|
|
||||||
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
|
||||||
parentId = cursor.getLong(0);
|
|
||||||
} else {
|
|
||||||
parentId = -1;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if (the old) 'parentId' has any children
|
|
||||||
cursor = db.query("messages", new String[] { "COUNT(id)" },
|
|
||||||
"thread_parent = ? AND id != ?",
|
|
||||||
new String[] { parentIdString, id },
|
|
||||||
null, null, null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (cursor.moveToFirst() && cursor.getLong(0) == 0) {
|
|
||||||
// If it has no children we can remove it
|
|
||||||
db.delete("messages", "id = ?",
|
|
||||||
new String[] { parentIdString });
|
|
||||||
} else {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
cursor.close();
|
// Get ID of the (empty) parent for the next iteration
|
||||||
}
|
long newId = getEmptyThreadParent(db, currentId);
|
||||||
|
|
||||||
|
// Delete the empty message
|
||||||
|
deleteMessageRow(db, currentId);
|
||||||
|
|
||||||
|
currentId = newId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the placeholder message
|
|
||||||
db.delete("messages", "id = ?", new String[] { id });
|
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
throw new WrappedException(e);
|
throw new WrappedException(e);
|
||||||
}
|
}
|
||||||
@ -3760,6 +3829,77 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
notifyChange();
|
notifyChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ID of the the given message's parent if the parent is an empty message.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* {@link SQLiteDatabase} instance to access the database.
|
||||||
|
* @param messageId
|
||||||
|
* The database ID of the message to get the parent for.
|
||||||
|
*
|
||||||
|
* @return Message ID of the parent message if there exists a parent and it is empty.
|
||||||
|
* Otherwise {@code -1}.
|
||||||
|
*/
|
||||||
|
private long getEmptyThreadParent(SQLiteDatabase db, long messageId) {
|
||||||
|
Cursor cursor = db.rawQuery(
|
||||||
|
"SELECT m.id " +
|
||||||
|
"FROM threads t1 " +
|
||||||
|
"JOIN threads t2 ON (t1.parent = t2.id) " +
|
||||||
|
"JOIN messages m ON (t2.message_id = m.id) " +
|
||||||
|
"WHERE t1.message_id = ? AND m.empty = 1",
|
||||||
|
new String[] { Long.toString(messageId) });
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (cursor.moveToFirst() && !cursor.isNull(0)) ? cursor.getLong(0) : -1;
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether or not a message has child messages in the thread structure.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* {@link SQLiteDatabase} instance to access the database.
|
||||||
|
* @param messageId
|
||||||
|
* The database ID of the message to get the children for.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the message has children. {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
private boolean hasThreadChildren(SQLiteDatabase db, long messageId) {
|
||||||
|
Cursor cursor = db.rawQuery(
|
||||||
|
"SELECT COUNT(t2.id) " +
|
||||||
|
"FROM threads t1 " +
|
||||||
|
"JOIN threads t2 ON (t2.parent = t1.id) " +
|
||||||
|
"WHERE t1.message_id = ?",
|
||||||
|
new String[] { Long.toString(messageId) });
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (cursor.moveToFirst() && !cursor.isNull(0) && cursor.getLong(0) > 0L);
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a message from the 'messages' and 'threads' tables.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* {@link SQLiteDatabase} instance to access the database.
|
||||||
|
* @param messageId
|
||||||
|
* The database ID of the message to delete.
|
||||||
|
*/
|
||||||
|
private void deleteMessageRow(SQLiteDatabase db, long messageId) {
|
||||||
|
String[] idArg = { Long.toString(messageId) };
|
||||||
|
|
||||||
|
// Delete the message
|
||||||
|
db.delete("messages", "id = ?", idArg);
|
||||||
|
|
||||||
|
// Delete row in 'threads' table
|
||||||
|
// TODO: create trigger for 'messages' table to get rid of the row in 'threads' table
|
||||||
|
db.delete("threads", "message_id = ?", idArg);
|
||||||
|
}
|
||||||
|
|
||||||
private void loadHeaders() throws UnavailableStorageException {
|
private void loadHeaders() throws UnavailableStorageException {
|
||||||
ArrayList<LocalMessage> messages = new ArrayList<LocalMessage>();
|
ArrayList<LocalMessage> messages = new ArrayList<LocalMessage>();
|
||||||
messages.add(this);
|
messages.add(this);
|
||||||
@ -3818,12 +3958,12 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getRootId() {
|
public long getThreadId() {
|
||||||
return mRootId;
|
return mThreadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getParentId() {
|
public long getRootId() {
|
||||||
return mParentId;
|
return mRootId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3893,13 +4033,15 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class ThreadInfo {
|
static class ThreadInfo {
|
||||||
public final long id;
|
public final long threadId;
|
||||||
|
public final long msgId;
|
||||||
public final String messageId;
|
public final String messageId;
|
||||||
public final long rootId;
|
public final long rootId;
|
||||||
public final long parentId;
|
public final long parentId;
|
||||||
|
|
||||||
public ThreadInfo(long id, String messageId, long rootId, long parentId) {
|
public ThreadInfo(long threadId, long msgId, String messageId, long rootId, long parentId) {
|
||||||
this.id = id;
|
this.threadId = threadId;
|
||||||
|
this.msgId = msgId;
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.rootId = rootId;
|
this.rootId = rootId;
|
||||||
this.parentId = parentId;
|
this.parentId = parentId;
|
||||||
@ -4045,8 +4187,18 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
*
|
*
|
||||||
* @throws MessagingException
|
* @throws MessagingException
|
||||||
*/
|
*/
|
||||||
public void setFlag(final List<Long> messageIds, final Flag flag,
|
public void setFlag(List<Long> messageIds, Flag flag, boolean newState, boolean threadRootIds)
|
||||||
final boolean newState, final boolean threadRootIds) throws MessagingException {
|
throws MessagingException {
|
||||||
|
|
||||||
|
if (threadRootIds) {
|
||||||
|
setFlagForThreads(messageIds, flag, newState);
|
||||||
|
} else {
|
||||||
|
setFlag(messageIds, flag, newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlag(final List<Long> messageIds, final Flag flag, final boolean newState)
|
||||||
|
throws MessagingException {
|
||||||
|
|
||||||
final ContentValues cv = new ContentValues();
|
final ContentValues cv = new ContentValues();
|
||||||
|
|
||||||
@ -4090,11 +4242,67 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
|
|
||||||
db.update("messages", cv, "(empty IS NULL OR empty != 1) AND id" + selectionSet,
|
db.update("messages", cv, "(empty IS NULL OR empty != 1) AND id" + selectionSet,
|
||||||
selectionArgs);
|
selectionArgs);
|
||||||
|
|
||||||
if (threadRootIds) {
|
|
||||||
db.update("messages", cv, "(empty IS NULL OR empty != 1) AND thread_root" +
|
|
||||||
selectionSet, selectionArgs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postDbWork() {
|
||||||
|
notifyChange();
|
||||||
|
}
|
||||||
|
}, FLAG_UPDATE_BATCH_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlagForThreads(final List<Long> messageIds, Flag flag, final boolean newState)
|
||||||
|
throws MessagingException {
|
||||||
|
|
||||||
|
final String flagColumn;
|
||||||
|
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() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getListSize() {
|
||||||
|
return messageIds.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getListItem(int index) {
|
||||||
|
return Long.toString(messageIds.get(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
|
||||||
|
throws UnavailableStorageException {
|
||||||
|
|
||||||
|
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) " +
|
||||||
|
"WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
|
||||||
|
"AND h.id" + selectionSet + ")",
|
||||||
|
selectionArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -4140,6 +4348,20 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
|
public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
|
||||||
throws UnavailableStorageException {
|
throws UnavailableStorageException {
|
||||||
|
|
||||||
|
if (threadedList) {
|
||||||
|
String sql = "SELECT m.uid, f.name " +
|
||||||
|
"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) " +
|
||||||
|
"JOIN folders f ON (m.folder_id = f.id) " +
|
||||||
|
"WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
|
||||||
|
"AND h.id" + selectionSet;
|
||||||
|
|
||||||
|
getDataFromCursor(db.rawQuery(sql, selectionArgs));
|
||||||
|
|
||||||
|
} else {
|
||||||
String sqlPrefix =
|
String sqlPrefix =
|
||||||
"SELECT m.uid, f.name " +
|
"SELECT m.uid, f.name " +
|
||||||
"FROM messages m " +
|
"FROM messages m " +
|
||||||
@ -4148,10 +4370,6 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
|
|
||||||
String sql = sqlPrefix + "m.id" + selectionSet;
|
String sql = sqlPrefix + "m.id" + selectionSet;
|
||||||
getDataFromCursor(db.rawQuery(sql, selectionArgs));
|
getDataFromCursor(db.rawQuery(sql, selectionArgs));
|
||||||
|
|
||||||
if (threadedList) {
|
|
||||||
String threadSql = sqlPrefix + "m.thread_root" + selectionSet;
|
|
||||||
getDataFromCursor(db.rawQuery(threadSql, selectionArgs));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ public class EmailProvider extends ContentProvider {
|
|||||||
private static final int MESSAGE_BASE = 0;
|
private static final int MESSAGE_BASE = 0;
|
||||||
private static final int MESSAGES = MESSAGE_BASE;
|
private static final int MESSAGES = MESSAGE_BASE;
|
||||||
private static final int MESSAGES_THREADED = MESSAGE_BASE + 1;
|
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_BASE = 100;
|
||||||
private static final int STATS = STATS_BASE;
|
private static final int STATS = STATS_BASE;
|
||||||
@ -78,8 +78,6 @@ public class EmailProvider extends ContentProvider {
|
|||||||
MessageColumns.ATTACHMENT_COUNT,
|
MessageColumns.ATTACHMENT_COUNT,
|
||||||
MessageColumns.FOLDER_ID,
|
MessageColumns.FOLDER_ID,
|
||||||
MessageColumns.PREVIEW,
|
MessageColumns.PREVIEW,
|
||||||
MessageColumns.THREAD_ROOT,
|
|
||||||
MessageColumns.THREAD_PARENT,
|
|
||||||
MessageColumns.READ,
|
MessageColumns.READ,
|
||||||
MessageColumns.FLAGGED,
|
MessageColumns.FLAGGED,
|
||||||
MessageColumns.ANSWERED,
|
MessageColumns.ANSWERED,
|
||||||
@ -95,6 +93,8 @@ public class EmailProvider extends ContentProvider {
|
|||||||
MessageColumns.ID
|
MessageColumns.ID
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static final String FOLDERS_TABLE = "folders";
|
||||||
|
|
||||||
private static final String[] FOLDERS_COLUMNS = {
|
private static final String[] FOLDERS_COLUMNS = {
|
||||||
FolderColumns.ID,
|
FolderColumns.ID,
|
||||||
FolderColumns.NAME,
|
FolderColumns.NAME,
|
||||||
@ -112,12 +112,21 @@ public class EmailProvider extends ContentProvider {
|
|||||||
FolderColumns.DISPLAY_CLASS
|
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 {
|
static {
|
||||||
UriMatcher matcher = sUriMatcher;
|
UriMatcher matcher = sUriMatcher;
|
||||||
|
|
||||||
matcher.addURI(AUTHORITY, "account/*/messages", MESSAGES);
|
matcher.addURI(AUTHORITY, "account/*/messages", MESSAGES);
|
||||||
matcher.addURI(AUTHORITY, "account/*/messages/threaded", MESSAGES_THREADED);
|
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);
|
matcher.addURI(AUTHORITY, "account/*/stats", STATS);
|
||||||
}
|
}
|
||||||
@ -125,6 +134,8 @@ public class EmailProvider extends ContentProvider {
|
|||||||
public interface SpecialColumns {
|
public interface SpecialColumns {
|
||||||
public static final String ACCOUNT_UUID = "account_uuid";
|
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 FOLDER_NAME = "name";
|
||||||
public static final String INTEGRATE = "integrate";
|
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 ATTACHMENT_COUNT = "attachment_count";
|
||||||
public static final String FOLDER_ID = "folder_id";
|
public static final String FOLDER_ID = "folder_id";
|
||||||
public static final String PREVIEW = "preview";
|
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 READ = "read";
|
||||||
public static final String FLAGGED = "flagged";
|
public static final String FLAGGED = "flagged";
|
||||||
public static final String ANSWERED = "answered";
|
public static final String ANSWERED = "answered";
|
||||||
@ -179,6 +187,13 @@ public class EmailProvider extends ContentProvider {
|
|||||||
public static final String DISPLAY_CLASS = "display_class";
|
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 interface StatsColumns {
|
||||||
public static final String UNREAD_COUNT = "unread_count";
|
public static final String UNREAD_COUNT = "unread_count";
|
||||||
public static final String FLAGGED_COUNT = "flagged_count";
|
public static final String FLAGGED_COUNT = "flagged_count";
|
||||||
@ -216,7 +231,8 @@ public class EmailProvider extends ContentProvider {
|
|||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
switch (match) {
|
switch (match) {
|
||||||
case MESSAGES:
|
case MESSAGES:
|
||||||
case MESSAGES_THREADED: {
|
case MESSAGES_THREADED:
|
||||||
|
case MESSAGES_THREAD: {
|
||||||
List<String> segments = uri.getPathSegments();
|
List<String> segments = uri.getPathSegments();
|
||||||
String accountUuid = segments.get(1);
|
String accountUuid = segments.get(1);
|
||||||
|
|
||||||
@ -238,11 +254,16 @@ public class EmailProvider extends ContentProvider {
|
|||||||
} else if (match == MESSAGES_THREADED) {
|
} else if (match == MESSAGES_THREADED) {
|
||||||
cursor = getThreadedMessages(accountUuid, dbProjection, selection,
|
cursor = getThreadedMessages(accountUuid, dbProjection, selection,
|
||||||
selectionArgs, sortOrder);
|
selectionArgs, sortOrder);
|
||||||
|
} else if (match == MESSAGES_THREAD) {
|
||||||
|
String threadId = segments.get(3);
|
||||||
|
cursor = getThread(accountUuid, dbProjection, threadId, sortOrder);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Not implemented");
|
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,
|
cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection,
|
||||||
specialColumns);
|
specialColumns);
|
||||||
@ -328,6 +349,7 @@ public class EmailProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query.append(" FROM messages m " +
|
query.append(" FROM messages m " +
|
||||||
|
"JOIN threads t ON (t.message_id = m.id) " +
|
||||||
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
|
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
|
||||||
"WHERE ");
|
"WHERE ");
|
||||||
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS,
|
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS,
|
||||||
@ -374,14 +396,20 @@ public class EmailProvider extends ContentProvider {
|
|||||||
|
|
||||||
if (MessageColumns.DATE.equals(columnName)) {
|
if (MessageColumns.DATE.equals(columnName)) {
|
||||||
query.append("MAX(m.date) AS " + MessageColumns.DATE);
|
query.append("MAX(m.date) AS " + MessageColumns.DATE);
|
||||||
} else if (MessageColumns.THREAD_COUNT.equals(columnName)) {
|
} else if (SpecialColumns.THREAD_COUNT.equals(columnName)) {
|
||||||
query.append("COUNT(h.id) AS " + MessageColumns.THREAD_COUNT);
|
query.append("COUNT(h.id) AS " + SpecialColumns.THREAD_COUNT);
|
||||||
} else if (SpecialColumns.FOLDER_NAME.equals(columnName)) {
|
} else if (SpecialColumns.FOLDER_NAME.equals(columnName)) {
|
||||||
query.append("f." + SpecialColumns.FOLDER_NAME + " AS " +
|
query.append("f." + SpecialColumns.FOLDER_NAME + " AS " +
|
||||||
SpecialColumns.FOLDER_NAME);
|
SpecialColumns.FOLDER_NAME);
|
||||||
} else if (SpecialColumns.INTEGRATE.equals(columnName)) {
|
} else if (SpecialColumns.INTEGRATE.equals(columnName)) {
|
||||||
query.append("f." + SpecialColumns.INTEGRATE + " AS " +
|
query.append("f." + SpecialColumns.INTEGRATE + " AS " +
|
||||||
SpecialColumns.INTEGRATE);
|
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 {
|
} else {
|
||||||
query.append("m.");
|
query.append("m.");
|
||||||
query.append(columnName);
|
query.append(columnName);
|
||||||
@ -391,8 +419,10 @@ public class EmailProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query.append(
|
query.append(
|
||||||
" FROM messages h JOIN messages m " +
|
" FROM messages h " +
|
||||||
"ON (h.id = m.thread_root OR h.id = m.id) ");
|
"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)) {
|
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
|
||||||
query.append("LEFT JOIN folders f ON (m.folder_id = f.id) ");
|
query.append("LEFT JOIN folders f ON (m.folder_id = f.id) ");
|
||||||
@ -400,9 +430,9 @@ public class EmailProvider extends ContentProvider {
|
|||||||
|
|
||||||
query.append(
|
query.append(
|
||||||
"WHERE " +
|
"WHERE " +
|
||||||
"(m.deleted = 0 AND " +
|
"(t1.root IS NULL AND " +
|
||||||
"(m.empty IS NULL OR m.empty != 1) AND " +
|
"m.deleted = 0 AND " +
|
||||||
"h.thread_root IS NULL) ");
|
"(m.empty IS NULL OR m.empty != 1)) ");
|
||||||
|
|
||||||
if (!StringUtils.isNullOrEmpty(selection)) {
|
if (!StringUtils.isNullOrEmpty(selection)) {
|
||||||
query.append("AND (");
|
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,
|
private Cursor getAccountStats(String accountUuid, String[] columns,
|
||||||
final String selection, final String[] selectionArgs) {
|
final String selection, final String[] selectionArgs) {
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ public interface SearchSpecification extends Parcelable {
|
|||||||
MESSAGE_CONTENTS,
|
MESSAGE_CONTENTS,
|
||||||
ATTACHMENT_COUNT,
|
ATTACHMENT_COUNT,
|
||||||
DELETED,
|
DELETED,
|
||||||
THREAD_ROOT,
|
THREAD_ID,
|
||||||
ID,
|
ID,
|
||||||
INTEGRATE,
|
INTEGRATE,
|
||||||
READ,
|
READ,
|
||||||
|
@ -66,6 +66,12 @@ public class SqlQueryBuilder {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case THREAD_ID: {
|
||||||
|
query.append("threads.id = ? OR threads.root = ?");
|
||||||
|
selectionArgs.add(condition.value);
|
||||||
|
selectionArgs.add(condition.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
appendCondition(condition, query, selectionArgs);
|
appendCondition(condition, query, selectionArgs);
|
||||||
}
|
}
|
||||||
@ -149,10 +155,6 @@ public class SqlQueryBuilder {
|
|||||||
columnName = "subject";
|
columnName = "subject";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case THREAD_ROOT: {
|
|
||||||
columnName = "thread_root";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case TO: {
|
case TO: {
|
||||||
columnName = "to_list";
|
columnName = "to_list";
|
||||||
break;
|
break;
|
||||||
@ -177,6 +179,7 @@ public class SqlQueryBuilder {
|
|||||||
columnName = "display_class";
|
columnName = "display_class";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case THREAD_ID:
|
||||||
case FOLDER:
|
case FOLDER:
|
||||||
case SEARCHABLE: {
|
case SEARCHABLE: {
|
||||||
// Special cases handled in buildWhereClauseInternal()
|
// Special cases handled in buildWhereClauseInternal()
|
||||||
@ -258,7 +261,7 @@ public class SqlQueryBuilder {
|
|||||||
case FOLDER:
|
case FOLDER:
|
||||||
case ID:
|
case ID:
|
||||||
case INTEGRATE:
|
case INTEGRATE:
|
||||||
case THREAD_ROOT:
|
case THREAD_ID:
|
||||||
case READ:
|
case READ:
|
||||||
case FLAGGED: {
|
case FLAGGED: {
|
||||||
return true;
|
return true;
|
||||||
@ -272,7 +275,7 @@ public class SqlQueryBuilder {
|
|||||||
public static String addPrefixToSelection(String[] columnNames, String prefix, String selection) {
|
public static String addPrefixToSelection(String[] columnNames, String prefix, String selection) {
|
||||||
String result = selection;
|
String result = selection;
|
||||||
for (String columnName : columnNames) {
|
for (String columnName : columnNames) {
|
||||||
result = result.replaceAll("\\b" + columnName + "\\b", prefix + columnName);
|
result = result.replaceAll("(?<=^|[^\\.])\\b" + columnName + "\\b", prefix + columnName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
Loading…
Reference in New Issue
Block a user