AI 147401: Redo the implementation of store-specific flags to allow
deeper database-style operations with them. This enables two new LocalStore APIs to be provided: A new version of GetMessages() that can retrieve only flagged (or un-flagged) messages, and a new method to set flags for an entire set of messages, in a single SQL transaction. BUG=1786939 Automated import of CL 147401
This commit is contained in:
parent
7e6df9c6aa
commit
29bbfd0b97
|
@ -20,6 +20,9 @@ package com.android.email.mail;
|
|||
* Flags that can be applied to Messages.
|
||||
*/
|
||||
public enum Flag {
|
||||
|
||||
// If adding new flags: ALL FLAGS MUST BE UPPER CASE.
|
||||
|
||||
DELETED,
|
||||
SEEN,
|
||||
ANSWERED,
|
||||
|
@ -73,16 +76,4 @@ public enum Flag {
|
|||
*/
|
||||
X_STORE_2,
|
||||
|
||||
/**
|
||||
* General purpose flag that can be used by any remote store. The flag will be
|
||||
* saved and restored by the LocalStore.
|
||||
*/
|
||||
X_STORE_3,
|
||||
|
||||
/**
|
||||
* General purpose flag that can be used by any remote store. The flag will be
|
||||
* saved and restored by the LocalStore.
|
||||
*/
|
||||
X_STORE_4,
|
||||
|
||||
}
|
||||
|
|
|
@ -104,6 +104,21 @@ public abstract class Folder {
|
|||
|
||||
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
||||
throws MessagingException;
|
||||
|
||||
/**
|
||||
* Return a set of messages based on the state of the flags.
|
||||
* Note: Not typically implemented in remote stores, so not abstract.
|
||||
*
|
||||
* @param setFlags The flags that should be set for a message to be selected (can be null)
|
||||
* @param clearFlags The flags that should be clear for a message to be selected (can be null)
|
||||
* @param listener
|
||||
* @return A list of messages matching the desired flag states.
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public Message[] getMessages(Flag[] setFlags, Flag[] clearFlags,
|
||||
MessageRetrievalListener listener) throws MessagingException {
|
||||
throw new MessagingException("Not implemented");
|
||||
}
|
||||
|
||||
public abstract void appendMessages(Message[] messages) throws MessagingException;
|
||||
|
||||
|
@ -157,6 +172,18 @@ public abstract class Folder {
|
|||
* @return the data saved by the Folder, or defaultValue if never set.
|
||||
*/
|
||||
public String getPersistentString(String key, String defaultValue);
|
||||
|
||||
/**
|
||||
* In a single transaction: Set a key/value pair for the folder, and bulk set or clear
|
||||
* message flags. Typically used at the beginning or conclusion of a bulk sync operation.
|
||||
*
|
||||
* @param key if non-null, the transaction will set this folder persistent value
|
||||
* @param value the value that will be stored for the key
|
||||
* @param setFlags if non-null, flag(s) will be set for all messages in the folder
|
||||
* @param clearFlags if non-null, flag(s) will be cleared for all messages in the folder
|
||||
*/
|
||||
public void setPersistentStringAndMessageFlags(String key, String value,
|
||||
Flag[] setFlags, Flag[] clearFlags) throws MessagingException;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -130,4 +130,9 @@ public abstract class Message implements Part, Body {
|
|||
}
|
||||
|
||||
public abstract void saveChanges() throws MessagingException;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ':' + mUid;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,9 +78,10 @@ public class LocalStore extends Store {
|
|||
* 19 - Added message_id column to messages table.
|
||||
* 20 1.5 Added content_id column to attachments table.
|
||||
* 21 - Added remote_store_data table
|
||||
* 22 - Added store_flag_1 and store_flag_2 columns to messages table.
|
||||
*/
|
||||
|
||||
private static final int DB_VERSION = 21;
|
||||
private static final int DB_VERSION = 22;
|
||||
|
||||
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN };
|
||||
|
||||
|
@ -142,7 +143,8 @@ public class LocalStore extends Store {
|
|||
"uid TEXT, subject TEXT, date INTEGER, flags TEXT, sender_list TEXT, " +
|
||||
"to_list TEXT, cc_list TEXT, bcc_list TEXT, reply_to_list TEXT, " +
|
||||
"html_content TEXT, text_content TEXT, attachment_count INTEGER, " +
|
||||
"internal_date INTEGER, message_id TEXT)");
|
||||
"internal_date INTEGER, message_id TEXT, store_flag_1 INTEGER, " +
|
||||
"store_flag_2 INTEGER)");
|
||||
|
||||
mDb.execSQL("DROP TABLE IF EXISTS attachments");
|
||||
mDb.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
|
||||
|
@ -184,6 +186,14 @@ public class LocalStore extends Store {
|
|||
addFolderDeleteTrigger();
|
||||
mDb.setVersion(21);
|
||||
}
|
||||
if (oldVersion < 22) {
|
||||
/**
|
||||
* Upgrade 21 to 22: add store_flag_1 and store_flag_2 to messages table
|
||||
*/
|
||||
mDb.execSQL("ALTER TABLE messages ADD COLUMN store_flag_1 INTEGER;");
|
||||
mDb.execSQL("ALTER TABLE messages ADD COLUMN store_flag_2 INTEGER;");
|
||||
mDb.setVersion(22);
|
||||
}
|
||||
}
|
||||
|
||||
if (mDb.getVersion() != DB_VERSION) {
|
||||
|
@ -201,9 +211,10 @@ public class LocalStore extends Store {
|
|||
*/
|
||||
private void addRemoteStoreDataTable() {
|
||||
mDb.execSQL("DROP TABLE IF EXISTS remote_store_data");
|
||||
mDb.execSQL("CREATE TABLE remote_store_data "
|
||||
+ "(id INTEGER PRIMARY KEY, folder_id INTEGER, "
|
||||
+ "data_key TEXT, data TEXT)");
|
||||
mDb.execSQL("CREATE TABLE remote_store_data (" +
|
||||
"id INTEGER PRIMARY KEY, folder_id INTEGER, data_key TEXT, data TEXT, " +
|
||||
"UNIQUE (folder_id, data_key) ON CONFLICT REPLACE" +
|
||||
")");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -648,13 +659,21 @@ public class LocalStore extends Store {
|
|||
}
|
||||
|
||||
/**
|
||||
* Populate a message from a cursor with the following colummns:
|
||||
* The columns to select when calling populateMessageFromGetMessageCursor()
|
||||
*/
|
||||
private final String POPULATE_MESSAGE_SELECT_COLUMNS =
|
||||
"subject, sender_list, date, uid, flags, id, to_list, cc_list, " +
|
||||
"bcc_list, reply_to_list, attachment_count, internal_date, message_id, " +
|
||||
"store_flag_1, store_flag_2";
|
||||
|
||||
/**
|
||||
* Populate a message from a cursor with the following columns:
|
||||
*
|
||||
* 0 subject
|
||||
* 1 from address
|
||||
* 2 date (long)
|
||||
* 3 uid
|
||||
* 4 flag list
|
||||
* 4 flag list (older flags - comma-separated string)
|
||||
* 5 local id (long)
|
||||
* 6 to addresses
|
||||
* 7 cc addresses
|
||||
|
@ -663,6 +682,8 @@ public class LocalStore extends Store {
|
|||
* 10 attachment count (int)
|
||||
* 11 internal date (long)
|
||||
* 12 message id (from Mime headers)
|
||||
* 13 store flag 1
|
||||
* 14 store flag 2
|
||||
*/
|
||||
private void populateMessageFromGetMessageCursor(LocalMessage message, Cursor cursor)
|
||||
throws MessagingException{
|
||||
|
@ -691,6 +712,8 @@ public class LocalStore extends Store {
|
|||
message.mAttachmentCount = cursor.getInt(10);
|
||||
message.setInternalDate(new Date(cursor.getLong(11)));
|
||||
message.setMessageId(cursor.getString(12));
|
||||
message.setFlagInternal(Flag.X_STORE_1, (0 != cursor.getInt(13)));
|
||||
message.setFlagInternal(Flag.X_STORE_2, (0 != cursor.getInt(14)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -708,9 +731,9 @@ public class LocalStore extends Store {
|
|||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = mDb.rawQuery(
|
||||
"SELECT subject, sender_list, date, uid, flags, id, to_list, cc_list, "
|
||||
+ "bcc_list, reply_to_list, attachment_count, internal_date, message_id "
|
||||
+ "FROM messages " + "WHERE uid = ? " + "AND folder_id = ?",
|
||||
"SELECT " + POPULATE_MESSAGE_SELECT_COLUMNS +
|
||||
" FROM messages" +
|
||||
" WHERE uid = ? AND folder_id = ?",
|
||||
new String[] {
|
||||
message.getUid(), Long.toString(mFolderId)
|
||||
});
|
||||
|
@ -734,10 +757,11 @@ public class LocalStore extends Store {
|
|||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = mDb.rawQuery(
|
||||
"SELECT subject, sender_list, date, uid, flags, id, to_list, cc_list, "
|
||||
+ "bcc_list, reply_to_list, attachment_count, internal_date, message_id "
|
||||
+ "FROM messages " + "WHERE folder_id = ?", new String[] {
|
||||
Long.toString(mFolderId)
|
||||
"SELECT " + POPULATE_MESSAGE_SELECT_COLUMNS +
|
||||
" FROM messages" +
|
||||
" WHERE folder_id = ?",
|
||||
new String[] {
|
||||
Long.toString(mFolderId)
|
||||
});
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
|
@ -768,6 +792,73 @@ public class LocalStore extends Store {
|
|||
}
|
||||
return messages.toArray(new Message[] {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a set of messages based on the state of the flags.
|
||||
*
|
||||
* @param setFlags The flags that should be set for a message to be selected (null ok)
|
||||
* @param clearFlags The flags that should be clear for a message to be selected (null ok)
|
||||
* @param listener
|
||||
* @return A list of messages matching the desired flag states.
|
||||
* @throws MessagingException
|
||||
*/
|
||||
@Override
|
||||
public Message[] getMessages(Flag[] setFlags, Flag[] clearFlags,
|
||||
MessageRetrievalListener listener) throws MessagingException {
|
||||
// Generate WHERE clause based on flags observed
|
||||
StringBuilder sql = new StringBuilder(
|
||||
"SELECT " + POPULATE_MESSAGE_SELECT_COLUMNS +
|
||||
" FROM messages" +
|
||||
" WHERE ");
|
||||
if (setFlags != null) {
|
||||
for (Flag flag : setFlags) {
|
||||
if (flag == Flag.X_STORE_1) {
|
||||
sql.append("store_flag_1 = 1 AND ");
|
||||
} else if (flag == Flag.X_STORE_2) {
|
||||
sql.append("store_flag_2 = 1 AND ");
|
||||
} else {
|
||||
throw new MessagingException("Unsupported flag " + flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clearFlags != null) {
|
||||
for (Flag flag : clearFlags) {
|
||||
if (flag == Flag.X_STORE_1) {
|
||||
sql.append("store_flag_1 = 0 AND ");
|
||||
} else if (flag == Flag.X_STORE_2) {
|
||||
sql.append("store_flag_2 = 0 AND ");
|
||||
} else {
|
||||
throw new MessagingException("Unsupported flag " + flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
sql.append("folder_id = ?");
|
||||
|
||||
open(OpenMode.READ_WRITE);
|
||||
ArrayList<Message> messages = new ArrayList<Message>();
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = mDb.rawQuery(
|
||||
sql.toString(),
|
||||
new String[] {
|
||||
Long.toString(mFolderId)
|
||||
});
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
LocalMessage message = new LocalMessage(null, this);
|
||||
populateMessageFromGetMessageCursor(message, cursor);
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
return messages.toArray(new Message[] {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {
|
||||
|
@ -845,7 +936,7 @@ public class LocalStore extends Store {
|
|||
cv.put("sender_list", Address.pack(message.getFrom()));
|
||||
cv.put("date", message.getSentDate() == null
|
||||
? System.currentTimeMillis() : message.getSentDate().getTime());
|
||||
cv.put("flags", Utility.combine(message.getFlags(), ',').toUpperCase());
|
||||
cv.put("flags", makeFlagsString(message));
|
||||
cv.put("folder_id", mFolderId);
|
||||
cv.put("to_list", Address.pack(message.getRecipients(RecipientType.TO)));
|
||||
cv.put("cc_list", Address.pack(message.getRecipients(RecipientType.CC)));
|
||||
|
@ -857,6 +948,8 @@ public class LocalStore extends Store {
|
|||
cv.put("internal_date", message.getInternalDate() == null
|
||||
? System.currentTimeMillis() : message.getInternalDate().getTime());
|
||||
cv.put("message_id", ((MimeMessage)message).getMessageId());
|
||||
cv.put("store_flag_1", makeFlagNumeric(message, Flag.X_STORE_1));
|
||||
cv.put("store_flag_2", makeFlagNumeric(message, Flag.X_STORE_2));
|
||||
long messageId = mDb.insert("messages", "uid", cv);
|
||||
for (Part attachment : attachments) {
|
||||
saveAttachment(messageId, attachment, copy);
|
||||
|
@ -909,7 +1002,9 @@ public class LocalStore extends Store {
|
|||
+ "uid = ?, subject = ?, sender_list = ?, date = ?, flags = ?, "
|
||||
+ "folder_id = ?, to_list = ?, cc_list = ?, bcc_list = ?, "
|
||||
+ "html_content = ?, text_content = ?, reply_to_list = ?, "
|
||||
+ "attachment_count = ?, message_id = ? WHERE id = ?",
|
||||
+ "attachment_count = ?, message_id = ?, store_flag_1 = ?, "
|
||||
+ "store_flag_2 = ? "
|
||||
+ "WHERE id = ?",
|
||||
new Object[] {
|
||||
message.getUid(),
|
||||
message.getSubject(),
|
||||
|
@ -917,7 +1012,7 @@ public class LocalStore extends Store {
|
|||
message.getSentDate() == null ? System
|
||||
.currentTimeMillis() : message.getSentDate()
|
||||
.getTime(),
|
||||
Utility.combine(message.getFlags(), ',').toUpperCase(),
|
||||
makeFlagsString(message),
|
||||
mFolderId,
|
||||
Address.pack(message
|
||||
.getRecipients(RecipientType.TO)),
|
||||
|
@ -930,6 +1025,9 @@ public class LocalStore extends Store {
|
|||
Address.pack(message.getReplyTo()),
|
||||
attachments.size(),
|
||||
message.getMessageId(),
|
||||
makeFlagNumeric(message, Flag.X_STORE_1),
|
||||
makeFlagNumeric(message, Flag.X_STORE_2),
|
||||
|
||||
message.mId
|
||||
});
|
||||
|
||||
|
@ -1190,30 +1288,64 @@ public class LocalStore extends Store {
|
|||
}
|
||||
|
||||
public void setPersistentString(String key, String value) {
|
||||
// TODO apply sql-foo and replace this with a single statement
|
||||
Cursor cursor = null;
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("folder_id", Long.toString(mFolderId));
|
||||
cv.put("data_key", key);
|
||||
cv.put("data", value);
|
||||
// Note: Table has on-conflict-replace rule
|
||||
mDb.insert("remote_store_data", null, cv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transactionally combine a key/value and a complete message flags flip. Used
|
||||
* for setting sync bits in messages.
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param setFlags
|
||||
* @param clearFlags
|
||||
*/
|
||||
public void setPersistentStringAndMessageFlags(String key, String value,
|
||||
Flag[] setFlags, Flag[] clearFlags) throws MessagingException {
|
||||
mDb.beginTransaction();
|
||||
try {
|
||||
final String where = "folder_id = ? AND data_key = ?";
|
||||
String[] whereArgs = new String[] { Long.toString(mFolderId), key };
|
||||
// take care of folder persistence
|
||||
if (key != null) {
|
||||
setPersistentString(key, value);
|
||||
}
|
||||
|
||||
// take care of flags
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("data", value);
|
||||
cursor = mDb.query("remote_store_data",
|
||||
new String[] { "data" },
|
||||
where, whereArgs,
|
||||
null, null, null);
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
mDb.update("remote_store_data", cv, where, whereArgs);
|
||||
} else {
|
||||
cv.put("folder_id", Long.toString(mFolderId));
|
||||
cv.put("data_key", key);
|
||||
mDb.insert("remote_store_data", null, cv);
|
||||
if (setFlags != null) {
|
||||
for (Flag flag : setFlags) {
|
||||
if (flag == Flag.X_STORE_1) {
|
||||
cv.put("store_flag_1", 1);
|
||||
} else if (flag == Flag.X_STORE_2) {
|
||||
cv.put("store_flag_2", 1);
|
||||
} else {
|
||||
throw new MessagingException("Unsupported flag " + flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
if (clearFlags != null) {
|
||||
for (Flag flag : clearFlags) {
|
||||
if (flag == Flag.X_STORE_1) {
|
||||
cv.put("store_flag_1", 0);
|
||||
} else if (flag == Flag.X_STORE_2) {
|
||||
cv.put("store_flag_2", 0);
|
||||
} else {
|
||||
throw new MessagingException("Unsupported flag " + flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
mDb.update("messages", cv,
|
||||
"folder_id = ?", new String[] { Long.toString(mFolderId) });
|
||||
|
||||
mDb.setTransactionSuccessful();
|
||||
} finally {
|
||||
mDb.endTransaction();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1309,12 +1441,53 @@ public class LocalStore extends Store {
|
|||
/*
|
||||
* Set the flags on the message.
|
||||
*/
|
||||
mDb.execSQL("UPDATE messages " + "SET flags = ? " + "WHERE id = ?", new Object[] {
|
||||
Utility.combine(getFlags(), ',').toUpperCase(), mId
|
||||
mDb.execSQL("UPDATE messages "
|
||||
+ "SET flags = ?, store_flag_1 = ?, store_flag_2 = ? "
|
||||
+ "WHERE id = ?",
|
||||
new Object[] {
|
||||
makeFlagsString(this),
|
||||
makeFlagNumeric(this, Flag.X_STORE_1),
|
||||
makeFlagNumeric(this, Flag.X_STORE_2),
|
||||
mId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert *old* flags to flags string. Some flags are kept in their own columns
|
||||
* (for selecting) and are not included here.
|
||||
* @param message The message containing the flag(s)
|
||||
* @return a comma-separated list of flags, to write into the "flags" column
|
||||
*/
|
||||
/* package */ String makeFlagsString(Message message) {
|
||||
StringBuilder sb = null;
|
||||
boolean nonEmpty = false;
|
||||
for (Flag flag : Flag.values()) {
|
||||
if ((flag != Flag.X_STORE_1 && flag != Flag.X_STORE_2) && message.isSet(flag)) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
if (nonEmpty) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(flag.toString());
|
||||
nonEmpty = true;
|
||||
}
|
||||
}
|
||||
return (sb == null) ? null : sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert flags to numeric form (0 or 1) for database storage.
|
||||
* @param message The message containing the flag of interest
|
||||
* @param flag The flag of interest
|
||||
*
|
||||
*/
|
||||
/* package */ int makeFlagNumeric(Message message, Flag flag) {
|
||||
return message.isSet(flag) ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
public class LocalAttachmentBodyPart extends MimeBodyPart {
|
||||
private long mAttachmentId = -1;
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.mail;
|
||||
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Tests of Flag enum
|
||||
*/
|
||||
@SmallTest
|
||||
public class FlagTests extends TestCase {
|
||||
|
||||
/**
|
||||
* Confirm that all flags are upper-case. This removes the need for wasteful toUpper
|
||||
* conversions in code that uses the flags.
|
||||
*/
|
||||
public void testFlagsUpperCase() {
|
||||
for (Flag flag : Flag.values()) {
|
||||
String name = flag.name();
|
||||
assertEquals(name.toUpperCase(), name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -225,23 +225,16 @@ public class MimeMessageTest extends TestCase {
|
|||
message.setFlag(Flag.X_STORE_1, true);
|
||||
assertTrue(message.isSet(Flag.X_STORE_1));
|
||||
assertFalse(message.isSet(Flag.X_STORE_2));
|
||||
assertFalse(message.isSet(Flag.X_STORE_3));
|
||||
assertFalse(message.isSet(Flag.X_STORE_4));
|
||||
|
||||
// Set another
|
||||
message.setFlag(Flag.X_STORE_2, true);
|
||||
assertTrue(message.isSet(Flag.X_STORE_1));
|
||||
assertTrue(message.isSet(Flag.X_STORE_2));
|
||||
assertFalse(message.isSet(Flag.X_STORE_3));
|
||||
assertFalse(message.isSet(Flag.X_STORE_4));
|
||||
|
||||
// Set some and clear some
|
||||
message.setFlag(Flag.X_STORE_1, false);
|
||||
message.setFlag(Flag.X_STORE_3, true);
|
||||
assertFalse(message.isSet(Flag.X_STORE_1));
|
||||
assertTrue(message.isSet(Flag.X_STORE_2));
|
||||
assertTrue(message.isSet(Flag.X_STORE_3));
|
||||
assertFalse(message.isSet(Flag.X_STORE_4));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
private static final String MESSAGE_ID = "Test-Message-ID";
|
||||
private static final String MESSAGE_ID_2 = "Test-Message-ID-Second";
|
||||
|
||||
private static final int DATABASE_VERSION = 21;
|
||||
private static final int DATABASE_VERSION = 22;
|
||||
|
||||
/* These values are provided by setUp() */
|
||||
private String mLocalStoreUri = null;
|
||||
|
@ -252,6 +252,72 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
assertEquals("value-2-2b", callbacks2.getPersistentString("key2", null)); // changed
|
||||
}
|
||||
|
||||
/**
|
||||
* Test functionality of persistence update with bulk update
|
||||
*/
|
||||
public void testPersistentBulkUpdate() throws MessagingException {
|
||||
mFolder.open(OpenMode.READ_WRITE, null);
|
||||
|
||||
// set up a 2nd folder to confirm independent storage
|
||||
LocalStore.LocalFolder folder2 = (LocalStore.LocalFolder) mStore.getFolder("FOLDER-2");
|
||||
assertFalse(folder2.exists());
|
||||
folder2.create(FolderType.HOLDS_MESSAGES);
|
||||
folder2.open(OpenMode.READ_WRITE, null);
|
||||
|
||||
// use the callbacks, as these are the "official" API
|
||||
Folder.PersistentDataCallbacks callbacks = mFolder.getPersistentCallbacks();
|
||||
Folder.PersistentDataCallbacks callbacks2 = folder2.getPersistentCallbacks();
|
||||
|
||||
// set some values - tests independence & inserts
|
||||
callbacks.setPersistentString("key1", "value-1-1");
|
||||
callbacks.setPersistentString("key2", "value-1-2");
|
||||
callbacks2.setPersistentString("key1", "value-2-1");
|
||||
callbacks2.setPersistentString("key2", "value-2-2");
|
||||
|
||||
final MimeMessage message1 = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message1.setFlag(Flag.X_STORE_1, false);
|
||||
message1.setFlag(Flag.X_STORE_2, false);
|
||||
|
||||
final MimeMessage message2 = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message2.setFlag(Flag.X_STORE_1, true);
|
||||
message2.setFlag(Flag.X_STORE_2, false);
|
||||
|
||||
final MimeMessage message3 = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message3.setFlag(Flag.X_STORE_1, false);
|
||||
message3.setFlag(Flag.X_STORE_2, true);
|
||||
|
||||
final MimeMessage message4 = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message4.setFlag(Flag.X_STORE_1, true);
|
||||
message4.setFlag(Flag.X_STORE_2, true);
|
||||
|
||||
Message[] allOriginals = new Message[]{ message1, message2, message3, message4 };
|
||||
|
||||
mFolder.appendMessages(allOriginals);
|
||||
|
||||
// Now make a bulk update (set)
|
||||
callbacks.setPersistentStringAndMessageFlags("key1", "value-1-1a",
|
||||
new Flag[]{ Flag.X_STORE_1 }, null);
|
||||
// And check all messages for that flag now set, but other flag was not set
|
||||
Message[] messages = mFolder.getMessages(null);
|
||||
for (Message msg : messages) {
|
||||
assertTrue(msg.isSet(Flag.X_STORE_1));
|
||||
if (msg.getUid().equals(message1.getUid())) assertFalse(msg.isSet(Flag.X_STORE_2));
|
||||
if (msg.getUid().equals(message2.getUid())) assertFalse(msg.isSet(Flag.X_STORE_2));
|
||||
}
|
||||
assertEquals("value-1-1a", callbacks.getPersistentString("key1", null));
|
||||
|
||||
// Same test, but clearing
|
||||
callbacks.setPersistentStringAndMessageFlags("key2", "value-1-2a",
|
||||
null, new Flag[]{ Flag.X_STORE_2 });
|
||||
// And check all messages for that flag now set, but other flag was not set
|
||||
messages = mFolder.getMessages(null);
|
||||
for (Message msg : messages) {
|
||||
assertTrue(msg.isSet(Flag.X_STORE_1));
|
||||
assertFalse(msg.isSet(Flag.X_STORE_2));
|
||||
}
|
||||
assertEquals("value-1-2a", callbacks.getPersistentString("key2", null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that messages are being stored with store flags properly persisted.
|
||||
*
|
||||
|
@ -260,8 +326,8 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
public void testStoreFlags() throws MessagingException {
|
||||
final MimeMessage message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message.setMessageId(MESSAGE_ID);
|
||||
message.setFlag(Flag.X_STORE_3, true);
|
||||
message.setFlag(Flag.X_STORE_4, true);
|
||||
message.setFlag(Flag.X_STORE_1, true);
|
||||
message.setFlag(Flag.X_STORE_2, false);
|
||||
|
||||
mFolder.open(OpenMode.READ_WRITE, null);
|
||||
mFolder.appendMessages(new Message[]{ message });
|
||||
|
@ -271,15 +337,13 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
|
||||
MimeMessage retrieved = (MimeMessage) mFolder.getMessage(localUid);
|
||||
assertEquals(MESSAGE_ID, retrieved.getMessageId());
|
||||
assertFalse(message.isSet(Flag.X_STORE_1));
|
||||
assertTrue(message.isSet(Flag.X_STORE_1));
|
||||
assertFalse(message.isSet(Flag.X_STORE_2));
|
||||
assertTrue(message.isSet(Flag.X_STORE_3));
|
||||
assertTrue(message.isSet(Flag.X_STORE_4));
|
||||
|
||||
// Now try to update it using updateMessages()
|
||||
|
||||
retrieved.setFlag(Flag.X_STORE_1, false);
|
||||
retrieved.setFlag(Flag.X_STORE_2, true);
|
||||
retrieved.setFlag(Flag.X_STORE_4, false);
|
||||
mFolder.updateMessage((LocalStore.LocalMessage)retrieved);
|
||||
|
||||
// And read back once more to confirm the change (using getMessages() to confirm "just one")
|
||||
|
@ -290,8 +354,136 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
|
||||
assertFalse(retrievedEntry.isSet(Flag.X_STORE_1));
|
||||
assertTrue(retrievedEntry.isSet(Flag.X_STORE_2));
|
||||
assertTrue(retrievedEntry.isSet(Flag.X_STORE_3));
|
||||
assertFalse(retrievedEntry.isSet(Flag.X_STORE_4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that store flags are separated into separate columns and not replicated in the
|
||||
* (should be deprecated) string flags column.
|
||||
*/
|
||||
public void testStoreFlagStorage() throws MessagingException, URISyntaxException {
|
||||
final MimeMessage message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message.setMessageId(MESSAGE_ID);
|
||||
message.setFlag(Flag.SEEN, true);
|
||||
message.setFlag(Flag.FLAGGED, true);
|
||||
message.setFlag(Flag.X_STORE_1, true);
|
||||
message.setFlag(Flag.X_STORE_2, true);
|
||||
|
||||
mFolder.open(OpenMode.READ_WRITE, null);
|
||||
mFolder.appendMessages(new Message[]{ message });
|
||||
String localUid = message.getUid();
|
||||
long folderId = mFolder.getId();
|
||||
mFolder.close(false);
|
||||
|
||||
// read back using direct db calls, to view columns
|
||||
final URI uri = new URI(mLocalStoreUri);
|
||||
final String dbPath = uri.getPath();
|
||||
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = db.rawQuery(
|
||||
"SELECT flags, store_flag_1, store_flag_2" +
|
||||
" FROM messages" +
|
||||
" WHERE uid = ? AND folder_id = ?",
|
||||
new String[] {
|
||||
localUid, Long.toString(folderId)
|
||||
});
|
||||
if (!cursor.moveToNext()) {
|
||||
fail("appended message not found");
|
||||
}
|
||||
String flagString = cursor.getString(0);
|
||||
String[] flags = flagString.split(",");
|
||||
assertEquals(2, flags.length); // 2 = SEEN & FLAGGED
|
||||
for (String flag : flags) {
|
||||
assertFalse("storeFlag1 in string", flag.equals(Flag.X_STORE_1.toString()));
|
||||
assertFalse("storeFlag2 in string", flag.equals(Flag.X_STORE_2.toString()));
|
||||
}
|
||||
|
||||
int flag1 = cursor.getInt(1); // store flag 1 is set
|
||||
assertEquals(1, flag1);
|
||||
int flag2 = cursor.getInt(2); // store flag 2 is set
|
||||
assertEquals(1, flag2);
|
||||
}
|
||||
finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the new functionality of getting messages from LocalStore based on their flags.
|
||||
*/
|
||||
public void testGetMessagesFlags() throws MessagingException {
|
||||
|
||||
final MimeMessage message1 = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message1.setFlag(Flag.X_STORE_1, false);
|
||||
message1.setFlag(Flag.X_STORE_2, false);
|
||||
|
||||
final MimeMessage message2 = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message2.setFlag(Flag.X_STORE_1, true);
|
||||
message2.setFlag(Flag.X_STORE_2, false);
|
||||
|
||||
final MimeMessage message3 = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message3.setFlag(Flag.X_STORE_1, false);
|
||||
message3.setFlag(Flag.X_STORE_2, true);
|
||||
|
||||
final MimeMessage message4 = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
|
||||
message4.setFlag(Flag.X_STORE_1, true);
|
||||
message4.setFlag(Flag.X_STORE_2, true);
|
||||
|
||||
Message[] allOriginals = new Message[]{ message1, message2, message3, message4 };
|
||||
|
||||
mFolder.open(OpenMode.READ_WRITE, null);
|
||||
mFolder.appendMessages(allOriginals);
|
||||
mFolder.close(false);
|
||||
|
||||
// Now try getting various permutation and see if it works
|
||||
|
||||
// Null lists are the same as empty lists - return all messages
|
||||
mFolder.open(OpenMode.READ_WRITE, null);
|
||||
Message[] getAll1 = mFolder.getMessages(null, null, null);
|
||||
checkGottenMessages("null filters", allOriginals, getAll1);
|
||||
|
||||
Message[] getAll2 = mFolder.getMessages(new Flag[0], new Flag[0], null);
|
||||
checkGottenMessages("empty filters", allOriginals, getAll2);
|
||||
|
||||
// Now try some selections, trying set and clear cases
|
||||
Message[] getSome1 = mFolder.getMessages(new Flag[]{ Flag.X_STORE_1 }, null, null);
|
||||
checkGottenMessages("store_1 set", new Message[]{ message2, message4 }, getSome1);
|
||||
|
||||
Message[] getSome2 = mFolder.getMessages(null, new Flag[]{ Flag.X_STORE_1 }, null);
|
||||
checkGottenMessages("store_1 clear", new Message[]{ message1, message3 }, getSome2);
|
||||
|
||||
Message[] getSome3 = mFolder.getMessages(new Flag[]{ Flag.X_STORE_2 }, null, null);
|
||||
checkGottenMessages("store_2 set", new Message[]{ message3, message4 }, getSome3);
|
||||
|
||||
Message[] getSome4 = mFolder.getMessages(null, new Flag[]{ Flag.X_STORE_2 }, null);
|
||||
checkGottenMessages("store_2 clear", new Message[]{ message1, message2 }, getSome4);
|
||||
|
||||
// Multi-flag selections
|
||||
Message[] getSingle1 = mFolder.getMessages(new Flag[]{ Flag.X_STORE_1, Flag.X_STORE_2 },
|
||||
null, null);
|
||||
checkGottenMessages("both set", new Message[]{ message4 }, getSingle1);
|
||||
|
||||
Message[] getSingle2 = mFolder.getMessages(null,
|
||||
new Flag[]{ Flag.X_STORE_1, Flag.X_STORE_2 }, null);
|
||||
checkGottenMessages("both clear", new Message[]{ message1 }, getSingle2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for matching uid's between two lists of messages
|
||||
*/
|
||||
private void checkGottenMessages(String failMessage, Message[] expected, Message[] actual) {
|
||||
HashSet<String> expectedUids = new HashSet<String>();
|
||||
for (Message message : expected) {
|
||||
expectedUids.add(message.getUid());
|
||||
}
|
||||
HashSet<String> actualUids = new HashSet<String>();
|
||||
for (Message message : actual) {
|
||||
actualUids.add(message.getUid());
|
||||
}
|
||||
assertEquals(failMessage, expectedUids, actualUids);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -585,6 +777,29 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
checkAllTablesFound(db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check upgrade from db version 21 to latest
|
||||
*/
|
||||
public void testDbUpgrade21ToLatest() throws MessagingException, URISyntaxException {
|
||||
final URI uri = new URI(mLocalStoreUri);
|
||||
final String dbPath = uri.getPath();
|
||||
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
|
||||
|
||||
// create sample version 21 db tables
|
||||
createSampleDb(db, 21);
|
||||
db.close();
|
||||
|
||||
// upgrade database 21 to latest
|
||||
LocalStore.newInstance(mLocalStoreUri, getContext(), null);
|
||||
|
||||
// database should be upgraded
|
||||
db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
|
||||
assertEquals("database should be upgraded", DATABASE_VERSION, db.getVersion());
|
||||
|
||||
// check for all "latest version" tables
|
||||
checkAllTablesFound(db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the database to confirm that all tables, with all expected columns are found.
|
||||
*/
|
||||
|
@ -602,7 +817,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
new String[]{ "id", "folder_id", "uid", "subject", "date", "flags", "sender_list",
|
||||
"to_list", "cc_list", "bcc_list", "reply_to_list",
|
||||
"html_content", "text_content", "attachment_count",
|
||||
"internal_date" }
|
||||
"internal_date", "store_flag_1", "store_flag_2" }
|
||||
));
|
||||
assertTrue("messages", foundNames.containsAll(expectedNames));
|
||||
|
||||
|
@ -637,6 +852,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
"html_content TEXT, text_content TEXT, attachment_count INTEGER, " +
|
||||
"internal_date INTEGER" +
|
||||
((version >= 19) ? ", message_id TEXT" : "") +
|
||||
((version >= 22) ? ", store_flag_1 INTEGER, store_flag_2 INTEGER" : "") +
|
||||
")");
|
||||
db.execSQL("DROP TABLE IF EXISTS attachments");
|
||||
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER," +
|
||||
|
|
Loading…
Reference in New Issue