am f095e9df
: Merge change Ib53e4411 into eclair-mr2
Merge commit 'f095e9df5a8dbd97dcefecb479fbcce05ddb4d87' into eclair-mr2-plus-aosp * commit 'f095e9df5a8dbd97dcefecb479fbcce05ddb4d87': Clear out orphaned messages in updates/deletes tables
This commit is contained in:
commit
bdaf801ea2
@ -54,15 +54,25 @@ public class EmailProvider extends ContentProvider {
|
||||
|
||||
private static final String TAG = "EmailProvider";
|
||||
|
||||
static final String DATABASE_NAME = "EmailProvider.db";
|
||||
static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
|
||||
private static final String DATABASE_NAME = "EmailProvider.db";
|
||||
private static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
|
||||
|
||||
// Definitions for our queries looking for orphaned messages
|
||||
private static final String[] ORPHANS_PROJECTION
|
||||
= new String[] {MessageColumns.ID, MessageColumns.MAILBOX_KEY};
|
||||
private static final int ORPHANS_ID = 0;
|
||||
private static final int ORPHANS_MAILBOX_KEY = 1;
|
||||
|
||||
private static final String WHERE_ID = EmailContent.RECORD_ID + "=?";
|
||||
|
||||
// Any changes to the database format *must* include update-in-place code.
|
||||
// Original version: 3
|
||||
// Version 4: Database wipe required; changing AccountManager interface w/Exchange
|
||||
// Version 5: Database wipe required; changing AccountManager interface w/Exchange
|
||||
// Version 6: Adding Message.mServerTimeStamp column
|
||||
public static final int DATABASE_VERSION = 6;
|
||||
// Version 7: Replace the mailbox_delete trigger with a version that removes orphaned messages
|
||||
// from the Message_Deletes and Message_Updates tables
|
||||
public static final int DATABASE_VERSION = 7;
|
||||
|
||||
// Any changes to the database format *must* include update-in-place code.
|
||||
// Original version: 2
|
||||
@ -162,6 +172,17 @@ public class EmailProvider extends ContentProvider {
|
||||
|
||||
private static final String ID_EQUALS = EmailContent.RECORD_ID + "=?";
|
||||
|
||||
private static final String TRIGGER_MAILBOX_DELETE =
|
||||
"create trigger mailbox_delete before delete on " + Mailbox.TABLE_NAME +
|
||||
" begin" +
|
||||
" delete from " + Message.TABLE_NAME +
|
||||
" where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
|
||||
"; delete from " + Message.UPDATED_TABLE_NAME +
|
||||
" where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
|
||||
"; delete from " + Message.DELETED_TABLE_NAME +
|
||||
" where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
|
||||
"; end";
|
||||
|
||||
static {
|
||||
// Email URI matching table
|
||||
UriMatcher matcher = sURIMatcher;
|
||||
@ -450,11 +471,8 @@ public class EmailProvider extends ContentProvider {
|
||||
+ " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.SERVER_ID + ")");
|
||||
db.execSQL("create index mailbox_" + MailboxColumns.ACCOUNT_KEY
|
||||
+ " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.ACCOUNT_KEY + ")");
|
||||
// Deleting a Mailbox deletes associated Messages
|
||||
db.execSQL("create trigger mailbox_delete before delete on " + Mailbox.TABLE_NAME +
|
||||
" begin delete from " + Message.TABLE_NAME +
|
||||
" where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
|
||||
"; end");
|
||||
// Deleting a Mailbox deletes associated Messages in all three tables
|
||||
db.execSQL(TRIGGER_MAILBOX_DELETE);
|
||||
}
|
||||
|
||||
static void resetMailboxTable(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
@ -540,9 +558,67 @@ public class EmailProvider extends ContentProvider {
|
||||
mDatabase.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any orphaned Messages in the updated/deleted tables
|
||||
deleteOrphans(mDatabase, Message.UPDATED_TABLE_NAME);
|
||||
deleteOrphans(mDatabase, Message.DELETED_TABLE_NAME);
|
||||
|
||||
return mDatabase;
|
||||
}
|
||||
|
||||
/*package*/ static SQLiteDatabase getReadableDatabase(Context context) {
|
||||
DatabaseHelper helper = new EmailProvider().new DatabaseHelper(context, DATABASE_NAME);
|
||||
return helper.getReadableDatabase();
|
||||
}
|
||||
|
||||
/*package*/ static void deleteOrphans(SQLiteDatabase database, String tableName) {
|
||||
if (database != null) {
|
||||
// We'll look at all of the items in the table; there won't be many typically
|
||||
Cursor c = database.query(tableName, ORPHANS_PROJECTION, null, null, null, null, null);
|
||||
// Usually, there will be nothing in these tables, so make a quick check
|
||||
try {
|
||||
if (c.getCount() == 0) return;
|
||||
ArrayList<Long> foundMailboxes = new ArrayList<Long>();
|
||||
ArrayList<Long> notFoundMailboxes = new ArrayList<Long>();
|
||||
ArrayList<Long> deleteList = new ArrayList<Long>();
|
||||
String[] bindArray = new String[1];
|
||||
while (c.moveToNext()) {
|
||||
// Get the mailbox key and see if we've already found this mailbox
|
||||
// If so, we're fine
|
||||
long mailboxId = c.getLong(ORPHANS_MAILBOX_KEY);
|
||||
// If we already know this mailbox doesn't exist, mark the message for deletion
|
||||
if (notFoundMailboxes.contains(mailboxId)) {
|
||||
deleteList.add(c.getLong(ORPHANS_ID));
|
||||
// If we don't know about this mailbox, we'll try to find it
|
||||
} else if (!foundMailboxes.contains(mailboxId)) {
|
||||
bindArray[0] = Long.toString(mailboxId);
|
||||
Cursor boxCursor = database.query(Mailbox.TABLE_NAME,
|
||||
Mailbox.ID_PROJECTION, WHERE_ID, bindArray, null, null, null);
|
||||
try {
|
||||
// If it exists, we'll add it to the "found" mailboxes
|
||||
if (boxCursor.moveToFirst()) {
|
||||
foundMailboxes.add(mailboxId);
|
||||
// Otherwise, we'll add to "not found" and mark the message for deletion
|
||||
} else {
|
||||
notFoundMailboxes.add(mailboxId);
|
||||
deleteList.add(c.getLong(ORPHANS_ID));
|
||||
}
|
||||
} finally {
|
||||
boxCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now, delete the orphan messages
|
||||
for (long messageId: deleteList) {
|
||||
bindArray[0] = Long.toString(messageId);
|
||||
database.delete(tableName, WHERE_ID, bindArray);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BodyDatabaseHelper extends SQLiteOpenHelper {
|
||||
BodyDatabaseHelper(Context context, String name) {
|
||||
super(context, name, null, BODY_DATABASE_VERSION);
|
||||
@ -614,6 +690,12 @@ public class EmailProvider extends ContentProvider {
|
||||
}
|
||||
oldVersion = 6;
|
||||
}
|
||||
if (oldVersion == 6) {
|
||||
// Use the newer mailbox_delete trigger
|
||||
db.execSQL("delete trigger mailbox_delete;");
|
||||
db.execSQL(TRIGGER_MAILBOX_DELETE);
|
||||
oldVersion = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -779,6 +861,8 @@ public class EmailProvider extends ContentProvider {
|
||||
Uri resultUri = null;
|
||||
|
||||
switch (match) {
|
||||
case UPDATED_MESSAGE:
|
||||
case DELETED_MESSAGE:
|
||||
case BODY:
|
||||
case MESSAGE:
|
||||
case ATTACHMENT:
|
||||
@ -787,6 +871,13 @@ public class EmailProvider extends ContentProvider {
|
||||
case HOSTAUTH:
|
||||
id = db.insert(TABLE_NAMES[table], "foo", values);
|
||||
resultUri = ContentUris.withAppendedId(uri, id);
|
||||
// Clients shouldn't normally be adding rows to these tables, as they are
|
||||
// maintained by triggers. However, we need to be able to do this for unit
|
||||
// testing, so we allow the insert and then throw the same exception that we
|
||||
// would if this weren't allowed.
|
||||
if (match == UPDATED_MESSAGE || match == DELETED_MESSAGE) {
|
||||
throw new IllegalArgumentException("Unknown URL " + uri);
|
||||
}
|
||||
break;
|
||||
case MAILBOX_ID:
|
||||
// This implies adding a message to a mailbox
|
||||
|
@ -142,7 +142,7 @@ public class SyncManager extends Service implements Runnable {
|
||||
protected static final String WHERE_IN_ACCOUNT_AND_PUSHABLE =
|
||||
MailboxColumns.ACCOUNT_KEY + "=? and type in (" + Mailbox.TYPE_INBOX + ','
|
||||
+ Mailbox.TYPE_EAS_ACCOUNT_MAILBOX + ',' + Mailbox.TYPE_CONTACTS + ')';
|
||||
private static final String WHERE_MAILBOX_KEY = EmailContent.RECORD_ID + "=?";
|
||||
private static final String WHERE_MAILBOX_KEY = Message.MAILBOX_KEY + "=?";
|
||||
private static final String WHERE_PROTOCOL_EAS = HostAuthColumns.PROTOCOL + "=\"" +
|
||||
AbstractSyncService.EAS_PROTOCOL + "\"";
|
||||
private static final String WHERE_NOT_INTERVAL_NEVER_AND_ACCOUNT_KEY_IN =
|
||||
@ -1147,6 +1147,10 @@ public class SyncManager extends Service implements Runnable {
|
||||
// We ignore drafts completely (doesn't sync). Changes in Outbox are handled
|
||||
// in the checkMailboxes loop, so we can ignore these pings.
|
||||
if (m.mType == Mailbox.TYPE_DRAFTS || m.mType == Mailbox.TYPE_OUTBOX) {
|
||||
String[] args = new String[] {Long.toString(m.mId)};
|
||||
ContentResolver resolver = INSTANCE.mResolver;
|
||||
resolver.delete(Message.DELETED_CONTENT_URI, WHERE_MAILBOX_KEY, args);
|
||||
resolver.delete(Message.UPDATED_CONTENT_URI, WHERE_MAILBOX_KEY, args);
|
||||
return;
|
||||
}
|
||||
service.mAccount = Account.restoreAccountWithId(INSTANCE, m.mAccountKey);
|
||||
|
@ -169,6 +169,7 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
/**
|
||||
* Test the various combinations of SSL, TLS, and trust-certificates encoded as Uris
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testHostAuthSecurityUri() {
|
||||
HostAuth ha = ProviderTestUtils.setupHostAuth("uri-security", 1, false, mMockContext);
|
||||
|
||||
@ -209,6 +210,7 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
/**
|
||||
* Test port assignments made from Uris
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testHostAuthPortAssignments() {
|
||||
HostAuth ha = ProviderTestUtils.setupHostAuth("uri-port", 1, false, mMockContext);
|
||||
|
||||
@ -656,7 +658,7 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
public void testDeleteOrphanBodies() {
|
||||
final ContentResolver resolver = mMockContext.getContentResolver();
|
||||
|
||||
// Create account and twa mailboxes
|
||||
// Create account and two mailboxes
|
||||
Account account1 = ProviderTestUtils.setupAccount("orphaned body", true, mMockContext);
|
||||
long account1Id = account1.mId;
|
||||
Mailbox box1 = ProviderTestUtils.setupMailbox("box1", account1Id, true, mMockContext);
|
||||
@ -686,6 +688,130 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
assertNotNull(loadBodyForMessageId(message2Id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete orphan messages
|
||||
* 1. create message without body (message id 1)
|
||||
* 2. create message with body (message id 2. Body has _id 1 and messageKey 2).
|
||||
* 3. delete first message.
|
||||
* 4. delete some other mailbox -- this triggers delete orphan bodies.
|
||||
* 5. verify that body for message 2 has not been deleted.
|
||||
*/
|
||||
public void testDeleteOrphanMessages() {
|
||||
final ContentResolver resolver = mMockContext.getContentResolver();
|
||||
final Context context = mMockContext;
|
||||
|
||||
// Create account and two mailboxes
|
||||
Account acct = ProviderTestUtils.setupAccount("orphaned body", true, context);
|
||||
Mailbox box1 = ProviderTestUtils.setupMailbox("box1", acct.mId, true, context);
|
||||
Mailbox box2 = ProviderTestUtils.setupMailbox("box2", acct.mId, true, context);
|
||||
|
||||
// Create 4 messages in box1
|
||||
Message msg1_1 =
|
||||
ProviderTestUtils.setupMessage("message1", acct.mId, box1.mId, false, true, context);
|
||||
Message msg1_2 =
|
||||
ProviderTestUtils.setupMessage("message2", acct.mId, box1.mId, false, true, context);
|
||||
Message msg1_3 =
|
||||
ProviderTestUtils.setupMessage("message3", acct.mId, box1.mId, false, true, context);
|
||||
Message msg1_4 =
|
||||
ProviderTestUtils.setupMessage("message4", acct.mId, box1.mId, false, true, context);
|
||||
|
||||
// Create 4 messages in box2
|
||||
Message msg2_1 =
|
||||
ProviderTestUtils.setupMessage("message1", acct.mId, box2.mId, false, true, context);
|
||||
Message msg2_2 =
|
||||
ProviderTestUtils.setupMessage("message2", acct.mId, box2.mId, false, true, context);
|
||||
Message msg2_3 =
|
||||
ProviderTestUtils.setupMessage("message3", acct.mId, box2.mId, false, true, context);
|
||||
Message msg2_4 =
|
||||
ProviderTestUtils.setupMessage("message4", acct.mId, box2.mId, false, true, context);
|
||||
|
||||
// Delete 2 from each mailbox
|
||||
resolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg1_1.mId),
|
||||
null, null);
|
||||
resolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg1_2.mId),
|
||||
null, null);
|
||||
resolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg2_1.mId),
|
||||
null, null);
|
||||
resolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg2_2.mId),
|
||||
null, null);
|
||||
|
||||
// There should be 4 items in the deleted item table
|
||||
assertEquals(4, EmailContent.count(context, Message.DELETED_CONTENT_URI, null, null));
|
||||
|
||||
// Update 2 from each mailbox
|
||||
ContentValues v = new ContentValues();
|
||||
v.put(MessageColumns.DISPLAY_NAME, "--updated--");
|
||||
resolver.update(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg1_3.mId),
|
||||
v, null, null);
|
||||
resolver.update(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg1_4.mId),
|
||||
v, null, null);
|
||||
resolver.update(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg2_3.mId),
|
||||
v, null, null);
|
||||
resolver.update(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg2_4.mId),
|
||||
v, null, null);
|
||||
|
||||
// There should be 4 items in the updated item table
|
||||
assertEquals(4, EmailContent.count(context, Message.UPDATED_CONTENT_URI, null, null));
|
||||
|
||||
// Manually add 2 messages from a "deleted" mailbox to deleted and updated tables
|
||||
// Use a value > 2 for the deleted box id
|
||||
long delBoxId = 10;
|
||||
// Create 4 messages in the "deleted" mailbox
|
||||
Message msgX_A =
|
||||
ProviderTestUtils.setupMessage("messageA", acct.mId, delBoxId, false, false, context);
|
||||
Message msgX_B =
|
||||
ProviderTestUtils.setupMessage("messageB", acct.mId, delBoxId, false, false, context);
|
||||
Message msgX_C =
|
||||
ProviderTestUtils.setupMessage("messageC", acct.mId, delBoxId, false, false, context);
|
||||
Message msgX_D =
|
||||
ProviderTestUtils.setupMessage("messageD", acct.mId, delBoxId, false, false, context);
|
||||
|
||||
ContentValues cv;
|
||||
// We have to assign id's manually because there are no autoincrement id's for these tables
|
||||
// Start with an id that won't exist, since id's in these tables must be unique
|
||||
long msgId = 10;
|
||||
// It's illegal to manually insert these, so we need to catch the exception
|
||||
// NOTE: The insert succeeds, and then throws the exception
|
||||
try {
|
||||
cv = msgX_A.toContentValues();
|
||||
cv.put(EmailContent.RECORD_ID, msgId++);
|
||||
resolver.insert(Message.DELETED_CONTENT_URI, cv);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
try {
|
||||
cv = msgX_B.toContentValues();
|
||||
cv.put(EmailContent.RECORD_ID, msgId++);
|
||||
resolver.insert(Message.DELETED_CONTENT_URI, cv);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
try {
|
||||
cv = msgX_C.toContentValues();
|
||||
cv.put(EmailContent.RECORD_ID, msgId++);
|
||||
resolver.insert(Message.UPDATED_CONTENT_URI, cv);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
try {
|
||||
cv = msgX_D.toContentValues();
|
||||
cv.put(EmailContent.RECORD_ID, msgId++);
|
||||
resolver.insert(Message.UPDATED_CONTENT_URI, cv);
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
|
||||
// There should be 6 items in the deleted and updated tables
|
||||
assertEquals(6, EmailContent.count(context, Message.UPDATED_CONTENT_URI, null, null));
|
||||
assertEquals(6, EmailContent.count(context, Message.DELETED_CONTENT_URI, null, null));
|
||||
|
||||
// Delete the orphans
|
||||
EmailProvider.deleteOrphans(EmailProvider.getReadableDatabase(context),
|
||||
Message.DELETED_TABLE_NAME);
|
||||
EmailProvider.deleteOrphans(EmailProvider.getReadableDatabase(context),
|
||||
Message.UPDATED_TABLE_NAME);
|
||||
|
||||
// There should now be 4 messages in each of the deleted and updated tables again
|
||||
assertEquals(4, EmailContent.count(context, Message.UPDATED_CONTENT_URI, null, null));
|
||||
assertEquals(4, EmailContent.count(context, Message.DELETED_CONTENT_URI, null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete mailbox
|
||||
*/
|
||||
@ -961,10 +1087,16 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
long account1Id = account1.mId;
|
||||
Mailbox box1 = ProviderTestUtils.setupMailbox("box1", account1Id, true, mMockContext);
|
||||
long box1Id = box1.mId;
|
||||
/* Message message1 = */ ProviderTestUtils.setupMessage("message1", account1Id, box1Id,
|
||||
Message message1 = ProviderTestUtils.setupMessage("message1", account1Id, box1Id,
|
||||
false, true, mMockContext);
|
||||
/* Message message2 = */ ProviderTestUtils.setupMessage("message2", account1Id, box1Id,
|
||||
Message message2 = ProviderTestUtils.setupMessage("message2", account1Id, box1Id,
|
||||
false, true, mMockContext);
|
||||
Message message3 = ProviderTestUtils.setupMessage("message3", account1Id, box1Id,
|
||||
false, true, mMockContext);
|
||||
Message message4 = ProviderTestUtils.setupMessage("message4", account1Id, box1Id,
|
||||
false, true, mMockContext);
|
||||
ProviderTestUtils.setupMessage("message5", account1Id, box1Id, false, true, mMockContext);
|
||||
ProviderTestUtils.setupMessage("message6", account1Id, box1Id, false, true, mMockContext);
|
||||
|
||||
String selection = EmailContent.MessageColumns.ACCOUNT_KEY + "=? AND " +
|
||||
EmailContent.MessageColumns.MAILBOX_KEY + "=?";
|
||||
@ -972,15 +1104,46 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
|
||||
// make sure there are two messages
|
||||
int numMessages = EmailContent.count(mMockContext, Message.CONTENT_URI, selection, selArgs);
|
||||
assertEquals(6, numMessages);
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(Message.SERVER_ID, "SERVER_ID");
|
||||
ContentResolver resolver = mMockContext.getContentResolver();
|
||||
|
||||
// Update two messages
|
||||
resolver.update(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, message1.mId),
|
||||
cv, null, null);
|
||||
resolver.update(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, message2.mId),
|
||||
cv, null, null);
|
||||
// Delete two messages
|
||||
resolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, message3.mId),
|
||||
null, null);
|
||||
resolver.delete(ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, message4.mId),
|
||||
null, null);
|
||||
|
||||
// There should now be two messages in updated/deleted, and 4 in messages
|
||||
numMessages = EmailContent.count(mMockContext, Message.CONTENT_URI, selection, selArgs);
|
||||
assertEquals(4, numMessages);
|
||||
numMessages = EmailContent.count(mMockContext, Message.DELETED_CONTENT_URI, selection,
|
||||
selArgs);
|
||||
assertEquals(2, numMessages);
|
||||
numMessages = EmailContent.count(mMockContext, Message.UPDATED_CONTENT_URI, selection,
|
||||
selArgs);
|
||||
assertEquals(2, numMessages);
|
||||
|
||||
// now delete the mailbox
|
||||
Uri uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, box1Id);
|
||||
mMockContext.getContentResolver().delete(uri, null, null);
|
||||
resolver.delete(uri, null, null);
|
||||
|
||||
// there should now be zero messages
|
||||
// there should now be zero messages in all three tables
|
||||
numMessages = EmailContent.count(mMockContext, Message.CONTENT_URI, selection, selArgs);
|
||||
assertEquals(0, numMessages);
|
||||
numMessages = EmailContent.count(mMockContext, Message.DELETED_CONTENT_URI, selection,
|
||||
selArgs);
|
||||
assertEquals(0, numMessages);
|
||||
numMessages = EmailContent.count(mMockContext, Message.UPDATED_CONTENT_URI, selection,
|
||||
selArgs);
|
||||
assertEquals(0, numMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user