AI 146134: Add persistence API for remote stores & folders to use while

syncing.  This provides a key-value store, per folder, that
  can be used by network Stores to record persistent data such
  as sync status, server keys, etc.
  Note that, by definition, this only applies to remote folders
  (e.g. IMAP, POP3). You'll see everywhere that LocalFolder is
  passed null, and this is correct - LocalFolder *is* persistent
  storage and does not need external help.
  Note to reviewers:  The core changes are Folder.java,
  LocalStore.java, and LocalStoreUnitTests.java, so please give
  them the bulk of your reviewer attention.  The other files
  are just following along with minor API changes.  Of those,
  the one worth close examination is MessagingController.java,
  which is the only place in the system where remote Folders
  are bonded with Local Folders and thus where this new API
  comes into play.
  Note to jham:  Can you please take a look at
  LocalStore.LocalFolder.setPersistentString() and recommend
  better SQL foo than my primitive test-then-update-or-insert
  logic, which is not transactional or threadsafe.
  BUG=1786939

Automated import of CL 146134
This commit is contained in:
Andy Stadler 2009-04-14 10:15:07 -07:00 committed by The Android Open Source Project
parent cd7e5664f9
commit c6039f7d17
11 changed files with 331 additions and 49 deletions

View File

@ -286,7 +286,7 @@ public class MessagingController implements Runnable {
try {
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null);
Folder localFolder = localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
Message[] localMessages = localFolder.getMessages(null);
ArrayList<Message> messages = new ArrayList<Message>();
for (Message message : localMessages) {
@ -388,7 +388,7 @@ public class MessagingController implements Runnable {
final LocalStore localStore =
(LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication, null);
final LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
Message[] localMessages = localFolder.getMessages(null);
HashMap<String, Message> localUidMap = new HashMap<String, Message>();
for (Message message : localMessages) {
@ -441,7 +441,7 @@ public class MessagingController implements Runnable {
/*
* Open the remote folder. This pre-loads certain metadata like message count.
*/
remoteFolder.open(OpenMode.READ_WRITE);
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
/*
* Trash any remote messages that are marked as trashed locally.
@ -837,7 +837,7 @@ public class MessagingController implements Runnable {
return;
}
}
remoteFolder.open(OpenMode.READ_WRITE);
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
return;
}
@ -914,13 +914,17 @@ public class MessagingController implements Runnable {
String folder = command.arguments[0];
String uid = command.arguments[1];
LocalStore localStore = (LocalStore) Store.getInstance(
account.getLocalStoreUri(), mApplication, null);
LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder);
Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication,
account.getStoreCallbacks());
Folder remoteFolder = remoteStore.getFolder(folder);
if (!remoteFolder.exists()) {
return;
}
remoteFolder.open(OpenMode.READ_WRITE);
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
return;
}
@ -965,13 +969,17 @@ public class MessagingController implements Runnable {
String uid = command.arguments[1];
boolean read = Boolean.parseBoolean(command.arguments[2]);
LocalStore localStore = (LocalStore) Store.getInstance(
account.getLocalStoreUri(), mApplication, null);
LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder);
Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication,
account.getStoreCallbacks());
Folder remoteFolder = remoteStore.getFolder(folder);
if (!remoteFolder.exists()) {
return;
}
remoteFolder.open(OpenMode.READ_WRITE);
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
return;
}
@ -1001,7 +1009,7 @@ public class MessagingController implements Runnable {
try {
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null);
Folder localFolder = localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
Message message = localFolder.getMessage(uid);
message.setFlag(Flag.SEEN, seen);
@ -1024,7 +1032,7 @@ public class MessagingController implements Runnable {
Store localStore = Store.getInstance(
account.getLocalStoreUri(), mApplication, null);
LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
Message message = localFolder.getMessage(uid);
@ -1056,7 +1064,7 @@ public class MessagingController implements Runnable {
Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication,
account.getStoreCallbacks());
Folder remoteFolder = remoteStore.getFolder(folder);
remoteFolder.open(OpenMode.READ_WRITE);
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
// Get the remote message and fully download it
Message remoteMessage = remoteFolder.getMessage(uid);
@ -1103,7 +1111,7 @@ public class MessagingController implements Runnable {
try {
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null);
LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
Message message = localFolder.getMessage(uid);
@ -1207,7 +1215,7 @@ public class MessagingController implements Runnable {
LocalFolder localFolder =
(LocalFolder) localStore.getFolder(message.getFolder().getName());
Folder remoteFolder = remoteStore.getFolder(message.getFolder().getName());
remoteFolder.open(OpenMode.READ_WRITE);
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
FetchProfile fp = new FetchProfile();
fp.add(part);
@ -1244,7 +1252,7 @@ public class MessagingController implements Runnable {
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null);
LocalFolder localFolder =
(LocalFolder) localStore.getFolder(account.getOutboxFolderName());
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
localFolder.appendMessages(new Message[] {
message
});
@ -1287,7 +1295,7 @@ public class MessagingController implements Runnable {
if (!localFolder.exists()) {
return;
}
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
Message[] localMessages = localFolder.getMessages(null);
@ -1393,7 +1401,7 @@ public class MessagingController implements Runnable {
Store localStore = Store.getInstance(
account.getLocalStoreUri(), mApplication, null);
Folder localFolder = localStore.getFolder(account.getTrashFolderName());
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
Message[] messages = localFolder.getMessages(null);
localFolder.setFlags(messages, new Flag[] {
Flag.DELETED
@ -1451,7 +1459,7 @@ public class MessagingController implements Runnable {
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication, null);
LocalFolder localFolder =
(LocalFolder) localStore.getFolder(account.getDraftsFolderName());
localFolder.open(OpenMode.READ_WRITE);
localFolder.open(OpenMode.READ_WRITE, null);
localFolder.appendMessages(new Message[] {
message
});

View File

@ -776,7 +776,7 @@ public class FolderMessageList extends ExpandableListActivity {
holder.messages = new ArrayList<MessageInfoHolder>();
}
try {
folder.open(Folder.OpenMode.READ_WRITE);
folder.open(Folder.OpenMode.READ_WRITE, null);
holder.unreadMessageCount = folder.getUnreadMessageCount();
folder.close(false);
}

View File

@ -31,8 +31,12 @@ public abstract class Folder {
* function returns without doing anything.
*
* @param mode READ_ONLY or READ_WRITE
* @param callbacks Pointer to callbacks class. This may be used by the folder between this
* time and when close() is called. This is only used for remote stores - should be null
* for LocalStore.LocalFolder.
*/
public abstract void open(OpenMode mode) throws MessagingException;
public abstract void open(OpenMode mode, PersistentDataCallbacks callbacks)
throws MessagingException;
/**
* Forces a close of the MailProvider. Any further access will attempt to
@ -105,6 +109,28 @@ public abstract class Folder {
public abstract Flag[] getPermanentFlags() throws MessagingException;
/**
* Callback interface by which a Folder can read and write persistent data.
* TODO This needs to be made more generic & flexible
*/
public interface PersistentDataCallbacks {
/**
* Provides keyed storage of strings. Should be used for per-folder data. Do not use for
* per-message data.
* @param key identifier for the data (e.g. "sync.key" or "folder.id")
* @param value Data to persist. All data must be encoded into a string,
* so use base64 or some other encoding if necessary.
*/
public void setPersistentString(String key, String value);
/**
* @param key identifier for the data of interest
* @return the data saved by the Folder, or defaultValue if never set.
*/
public String getPersistentString(String key, String defaultValue);
}
@Override
public String toString() {
return getName();

View File

@ -33,6 +33,8 @@ public class ExchangeFolderExample extends Folder {
private final ExchangeStoreExample mStore;
private final String mName;
private PersistentDataCallbacks mPersistenceCallbacks;
public ExchangeFolderExample(ExchangeStoreExample store, String name)
throws MessagingException {
mStore = store;
@ -50,6 +52,7 @@ public class ExchangeFolderExample extends Folder {
@Override
public void close(boolean expunge) throws MessagingException {
mPersistenceCallbacks = null;
// TODO Implement this function
}
@ -150,7 +153,8 @@ public class ExchangeFolderExample extends Folder {
}
@Override
public void open(OpenMode mode) throws MessagingException {
public void open(OpenMode mode, PersistentDataCallbacks callbacks) throws MessagingException {
mPersistenceCallbacks = callbacks;
// TODO Implement this function
}

View File

@ -29,6 +29,7 @@ import com.android.email.mail.MessagingException;
import com.android.email.mail.Part;
import com.android.email.mail.Store;
import com.android.email.mail.Transport;
import com.android.email.mail.Folder.PersistentDataCallbacks;
import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeHeader;
import com.android.email.mail.internet.MimeMessage;
@ -325,7 +326,8 @@ public class ImapStore extends Store {
this.mName = name;
}
public void open(OpenMode mode) throws MessagingException {
public void open(OpenMode mode, PersistentDataCallbacks callbacks)
throws MessagingException {
if (isOpen() && mMode == mode) {
// Make sure the connection is valid. If it's not we'll close it down and continue
// on to get a new one.

View File

@ -40,7 +40,6 @@ import com.android.email.provider.AttachmentProvider;
import org.apache.commons.io.IOUtils;
import android.app.Application;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -78,9 +77,10 @@ public class LocalStore extends Store {
* 18 1.0, 1.1 1.0 Release version.
* 19 - Added message_id column to messages table.
* 20 1.5 Added content_id column to attachments table.
* 21 - Added remote_store_data table
*/
private static final int DB_VERSION = 20;
private static final int DB_VERSION = 21;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN };
@ -92,7 +92,8 @@ public class LocalStore extends Store {
/**
* Static named constructor.
*/
public static Store newInstance(String uri, Context context) throws MessagingException {
public static Store newInstance(String uri, Context context, PersistentDataCallbacks callbacks)
throws MessagingException {
return new LocalStore(uri, context);
}
@ -150,9 +151,10 @@ public class LocalStore extends Store {
mDb.execSQL("DROP TABLE IF EXISTS pending_commands");
mDb.execSQL("CREATE TABLE pending_commands " +
"(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)");
addRemoteStoreDataTable();
mDb.execSQL("DROP TRIGGER IF EXISTS delete_folder");
mDb.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;");
addFolderDeleteTrigger();
mDb.execSQL("DROP TRIGGER IF EXISTS delete_message");
mDb.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; END;");
@ -173,6 +175,14 @@ public class LocalStore extends Store {
mDb.execSQL("ALTER TABLE attachments ADD COLUMN content_id TEXT;");
mDb.setVersion(20);
}
if (oldVersion < 21) {
/**
* Upgrade 20 to 21: add remote_store_data and update triggers to match
*/
addRemoteStoreDataTable();
addFolderDeleteTrigger();
mDb.setVersion(21);
}
}
if (mDb.getVersion() != DB_VERSION) {
@ -184,6 +194,29 @@ public class LocalStore extends Store {
mAttachmentsDir.mkdirs();
}
}
/**
* Common code to add the remote_store_data table
*/
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)");
}
/**
* Common code to add folder delete trigger
*/
private void addFolderDeleteTrigger() {
mDb.execSQL("DROP TRIGGER IF EXISTS delete_folder");
mDb.execSQL("CREATE TRIGGER delete_folder "
+ "BEFORE DELETE ON folders "
+ "BEGIN "
+ "DELETE FROM messages WHERE old.id = folder_id; "
+ "DELETE FROM remote_store_data WHERE old.id = folder_id; "
+ "END;");
}
@Override
public Folder getFolder(String name) throws MessagingException {
@ -369,7 +402,7 @@ public class LocalStore extends Store {
}
}
public class LocalFolder extends Folder {
public class LocalFolder extends Folder implements Folder.PersistentDataCallbacks {
private String mName;
private long mFolderId = -1;
private int mUnreadMessageCount = -1;
@ -382,9 +415,17 @@ public class LocalStore extends Store {
public long getId() {
return mFolderId;
}
/**
* This is just used by the internal callers
*/
private void open(OpenMode mode) throws MessagingException {
open(mode, null);
}
@Override
public void open(OpenMode mode) throws MessagingException {
public void open(OpenMode mode, PersistentDataCallbacks callbacks)
throws MessagingException {
if (isOpen()) {
return;
}
@ -1104,6 +1145,65 @@ public class LocalStore extends Store {
}
}
}
/**
* Support for local persistence for our remote stores.
* Will open the folder if necessary.
*/
public Folder.PersistentDataCallbacks getPersistentCallbacks() throws MessagingException {
open(OpenMode.READ_WRITE);
return this;
}
public String getPersistentString(String key, String defaultValue) {
String result = defaultValue;
Cursor cursor = null;
try {
cursor = mDb.query("remote_store_data",
new String[] { "data" },
"folder_id = ? AND data_key = ?",
new String[] { Long.toString(mFolderId), key },
null,
null,
null);
if (cursor != null && cursor.moveToNext()) {
result = cursor.getString(0);
}
}
finally {
if (cursor != null) {
cursor.close();
}
}
return result;
}
public void setPersistentString(String key, String value) {
// TODO apply sql-foo and replace this with a single statement
Cursor cursor = null;
try {
final String where = "folder_id = ? AND data_key = ?";
String[] whereArgs = new String[] { Long.toString(mFolderId), key };
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);
}
}
finally {
if (cursor != null) {
cursor.close();
}
}
}
}
public class LocalMessage extends MimeMessage {

View File

@ -28,6 +28,7 @@ import com.android.email.mail.MessagingException;
import com.android.email.mail.Store;
import com.android.email.mail.Transport;
import com.android.email.mail.Folder.OpenMode;
import com.android.email.mail.Folder.PersistentDataCallbacks;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.transport.LoggingInputStream;
import com.android.email.mail.transport.MailTransport;
@ -179,7 +180,7 @@ public class Pop3Store extends Store {
public void checkSettings() throws MessagingException {
Pop3Folder folder = new Pop3Folder("INBOX");
try {
folder.open(OpenMode.READ_WRITE);
folder.open(OpenMode.READ_WRITE, null);
folder.checkSettings();
} finally {
folder.close(false); // false == don't expunge anything
@ -229,7 +230,8 @@ public class Pop3Store extends Store {
}
@Override
public synchronized void open(OpenMode mode) throws MessagingException {
public synchronized void open(OpenMode mode, PersistentDataCallbacks callbacks)
throws MessagingException {
if (mTransport.isOpen()) {
return;
}
@ -855,7 +857,7 @@ public class Pop3Store extends Store {
*/
private String executeSensitiveCommand(String command, String sensitiveReplacement)
throws IOException, MessagingException {
open(OpenMode.READ_WRITE);
open(OpenMode.READ_WRITE, null);
if (command != null) {
mTransport.writeLine(command, sensitiveReplacement);

View File

@ -156,7 +156,7 @@ public class MessageViewTests
public void testResolveInlineImage() throws MessagingException, IOException {
final MessageView a = getActivity();
final LocalStore store = (LocalStore) LocalStore.newInstance(mAccount.getLocalStoreUri(),
mContext);
mContext, null);
// Single cid case.
final String cid1 = "cid.1@android.com";

View File

@ -66,7 +66,7 @@ public class ImapStoreUnitTests extends AndroidTestCase {
// try to open it
setupOpenFolder(mockTransport);
mFolder.open(OpenMode.READ_WRITE);
mFolder.open(OpenMode.READ_WRITE, null);
// TODO: inject specific facts in the initial folder SELECT and check them here
}

View File

@ -17,8 +17,10 @@
package com.android.email.mail.store;
import com.android.email.mail.Address;
import com.android.email.mail.Folder;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException;
import com.android.email.mail.Folder.FolderType;
import com.android.email.mail.Folder.OpenMode;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.BinaryTempFileBody;
@ -34,6 +36,9 @@ import android.test.suitebuilder.annotation.SmallTest;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
/**
* This is a series of unit tests for the LocalStore class.
@ -50,6 +55,8 @@ 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;
/* These values are provided by setUp() */
private String mLocalStoreUri = null;
private LocalStore mStore = null;
@ -67,7 +74,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
// Create a dummy database (be sure to delete it in tearDown())
mLocalStoreUri = "local://localhost/" + getContext().getDatabasePath(dbName);
mStore = (LocalStore) LocalStore.newInstance(mLocalStoreUri, getContext());
mStore = (LocalStore) LocalStore.newInstance(mLocalStoreUri, getContext(), null);
mFolder = (LocalStore.LocalFolder) mStore.getFolder("TEST");
// This is needed for parsing mime messages
@ -121,7 +128,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
public void testMessageId_1() throws MessagingException {
final MimeMessage message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
message.setMessageId(MESSAGE_ID);
mFolder.open(OpenMode.READ_WRITE);
mFolder.open(OpenMode.READ_WRITE, null);
mFolder.appendMessages(new Message[]{ message });
String localUid = message.getUid();
@ -146,7 +153,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
public void testMessageId_2() throws MessagingException {
final MimeMessage message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
message.setMessageId(MESSAGE_ID);
mFolder.open(OpenMode.READ_WRITE);
mFolder.open(OpenMode.READ_WRITE, null);
mFolder.appendMessages(new Message[]{ message });
String localUid = message.getUid();
@ -202,18 +209,59 @@ public class LocalStoreUnitTests extends AndroidTestCase {
return message;
}
/**
* Test functionality of setting & saving store persistence values
*/
public void testPersistentStorage() 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");
// readback initial values
assertEquals("value-1-1", callbacks.getPersistentString("key1", null));
assertEquals("value-1-2", callbacks.getPersistentString("key2", null));
assertEquals("value-2-1", callbacks2.getPersistentString("key1", null));
assertEquals("value-2-2", callbacks2.getPersistentString("key2", null));
// readback with default values
assertEquals("value-1-3", callbacks.getPersistentString("key3", "value-1-3"));
assertEquals("value-2-3", callbacks2.getPersistentString("key3", "value-2-3"));
// partial updates
callbacks.setPersistentString("key1", "value-1-1b");
callbacks2.setPersistentString("key2", "value-2-2b");
assertEquals("value-1-1b", callbacks.getPersistentString("key1", null)); // changed
assertEquals("value-1-2", callbacks.getPersistentString("key2", null)); // same
assertEquals("value-2-1", callbacks2.getPersistentString("key1", null)); // same
assertEquals("value-2-2b", callbacks2.getPersistentString("key2", null)); // changed
}
/**
* Tests for database version.
*/
public void testDbVersion() throws MessagingException, URISyntaxException {
// build current version database.
LocalStore.newInstance(mLocalStoreUri, getContext());
LocalStore.newInstance(mLocalStoreUri, getContext(), null);
final URI uri = new URI(mLocalStoreUri);
final String dbPath = uri.getPath();
final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
// database version should be latest.
assertEquals("database version should be latest", 20, db.getVersion());
assertEquals("database version should be latest", DATABASE_VERSION, db.getVersion());
db.close();
}
@ -245,9 +293,20 @@ public class LocalStoreUnitTests extends AndroidTestCase {
}
/**
* Tests for database upgrade from version 18 to version 20.
* Helper function to read out Cursor columns
*/
public void testDbUpgrade18To20() throws MessagingException, URISyntaxException {
private HashSet<String> cursorToColumnNames(Cursor c) {
HashSet<String> result = new HashSet<String>();
for (int i = 0, count = c.getColumnCount(); i < count; ++i) {
result.add(c.getColumnName(i));
}
return result;
}
/**
* Tests for database upgrade from version 18 to current version.
*/
public void testDbUpgrade18ToLatest() throws MessagingException, URISyntaxException {
final URI uri = new URI(mLocalStoreUri);
final String dbPath = uri.getPath();
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
@ -270,8 +329,8 @@ public class LocalStoreUnitTests extends AndroidTestCase {
expectedAttachment.put("id", db.insert("attachments", null, initialAttachment));
db.close();
// upgrade database 18 to 20
LocalStore.newInstance(mLocalStoreUri, getContext());
// upgrade database 18 to latest
LocalStore.newInstance(mLocalStoreUri, getContext(), null);
// added message_id column should be initialized as null
expectedMessage.put("message_id", (String) null); // message_id type text == String
@ -280,8 +339,11 @@ public class LocalStoreUnitTests extends AndroidTestCase {
// database should be upgraded
db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
assertEquals("database should be upgraded", 20, db.getVersion());
assertEquals("database should be upgraded", DATABASE_VERSION, db.getVersion());
Cursor c;
// check for all "latest version" tables
checkAllTablesFound(db);
// check message table
c = db.query("messages",
@ -315,14 +377,14 @@ public class LocalStoreUnitTests extends AndroidTestCase {
}
/**
* Tests for database upgrade from version 19 to version 20.
* Tests for database upgrade from version 19 to current version.
*/
public void testDbUpgrade19To20() throws MessagingException, URISyntaxException {
public void testDbUpgrade19ToLatest() throws MessagingException, URISyntaxException {
final URI uri = new URI(mLocalStoreUri);
final String dbPath = uri.getPath();
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
// create minimu version 18 db tables
// create sample version 19 db tables
createSampleDb(db, 19);
// sample message data and expected data
@ -342,17 +404,20 @@ public class LocalStoreUnitTests extends AndroidTestCase {
db.close();
// upgrade database 19 to 20
LocalStore.newInstance(mLocalStoreUri, getContext());
// upgrade database 19 to latest
LocalStore.newInstance(mLocalStoreUri, getContext(), null);
// added content_id column should be initialized as null
expectedAttachment.put("content_id", (String) null); // content_id type text == String
// database should be upgraded
db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
assertEquals(20, db.getVersion());
assertEquals("database should be upgraded", DATABASE_VERSION, db.getVersion());
Cursor c;
// check for all "latest version" tables
checkAllTablesFound(db);
// check message table
c = db.query("messages",
new String[] { "id", "folder_id", "internal_date", "message_id" },
@ -381,6 +446,73 @@ public class LocalStoreUnitTests extends AndroidTestCase {
db.close();
}
/**
* Check upgrade from db version 20 to latest
*/
public void testDbUpgrade20ToLatest() throws MessagingException, URISyntaxException {
final URI uri = new URI(mLocalStoreUri);
final String dbPath = uri.getPath();
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
// create sample version 20 db tables
createSampleDb(db, 20);
db.close();
// upgrade database 20 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.
*/
private void checkAllTablesFound(SQLiteDatabase db) {
Cursor c;
HashSet<String> foundNames;
ArrayList<String> expectedNames;
// check for up-to-date messages table
c = db.query("messages",
null,
null, null, null, null, null);
foundNames = cursorToColumnNames(c);
expectedNames = new ArrayList<String>(Arrays.asList(
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" }
));
assertTrue("messages", foundNames.containsAll(expectedNames));
// check for up-to-date attachments table
c = db.query("attachments",
null,
null, null, null, null, null);
foundNames = cursorToColumnNames(c);
expectedNames = new ArrayList<String>(Arrays.asList(
new String[]{ "id", "message_id",
"store_data", "content_uri", "size", "name",
"mime_type", "content_id" }
));
assertTrue("attachments", foundNames.containsAll(expectedNames));
// check for up-to-date remote_store_data table
c = db.query("remote_store_data",
null,
null, null, null, null, null);
foundNames = cursorToColumnNames(c);
expectedNames = new ArrayList<String>(Arrays.asList(
new String[]{ "id", "folder_id", "data_key", "data" }
));
assertTrue("remote_store_data", foundNames.containsAll(expectedNames));
}
private static void createSampleDb(SQLiteDatabase db, int version) {
db.execSQL("DROP TABLE IF EXISTS messages");
@ -397,6 +529,14 @@ public class LocalStoreUnitTests extends AndroidTestCase {
"mime_type TEXT" +
((version >= 20) ? ", content_id" : "") +
")");
if (version >= 21) {
db.execSQL("DROP TABLE IF EXISTS remote_store_data");
db.execSQL("CREATE TABLE remote_store_data "
+ "(id INTEGER PRIMARY KEY, folder_id INTEGER, "
+ "data_key TEXT, data TEXT)");
}
db.setVersion(version);
}
}

View File

@ -129,7 +129,7 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
// try to open it
setupOpenFolder(mockTransport, 0, null);
mFolder.open(OpenMode.READ_ONLY);
mFolder.open(OpenMode.READ_ONLY, null);
}
/**
@ -569,7 +569,7 @@ public class Pop3StoreUnitTests extends AndroidTestCase {
private void openFolderWithMessage(MockTransport mockTransport) throws MessagingException {
// try to open it
setupOpenFolder(mockTransport, 1, null);
mFolder.open(OpenMode.READ_ONLY);
mFolder.open(OpenMode.READ_ONLY, null);
// check message count
assertEquals(1, mFolder.getMessageCount());