Merge "Delete orphaned mailboxes/messages/policies at provider startup"
This commit is contained in:
commit
983fd116b1
@ -40,7 +40,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public final class Policy extends EmailContent implements EmailContent.PolicyColumns, Parcelable {
|
||||
// STOPSHIP Change to false after a few days of debugging
|
||||
public static final boolean DEBUG_POLICY = true; // DO NOT SUBMIT WITH THIS SET TO FALSE
|
||||
public static final boolean DEBUG_POLICY = false; // DO NOT SUBMIT WITH THIS SET TO TRUE
|
||||
public static final String TAG = "Email/Policy";
|
||||
|
||||
public static final String TABLE_NAME = "Policy";
|
||||
|
@ -786,6 +786,25 @@ public class EmailProvider extends ContentProvider {
|
||||
private SQLiteDatabase mDatabase;
|
||||
private SQLiteDatabase mBodyDatabase;
|
||||
|
||||
/**
|
||||
* Orphan record deletion utility. Generates a sqlite statement like:
|
||||
* delete from <table> where <column> not in (select <foreignColumn> from <foreignTable>)
|
||||
* @param db the EmailProvider database
|
||||
* @param table the table whose orphans are to be removed
|
||||
* @param column the column deletion will be based on
|
||||
* @param foreignColumn the column in the foreign table whose absence will trigger the deletion
|
||||
* @param foreignTable the foreign table
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void deleteUnlinked(SQLiteDatabase db, String table, String column, String foreignColumn,
|
||||
String foreignTable) {
|
||||
int count = db.delete(table, column + " not in (select " + foreignColumn + " from " +
|
||||
foreignTable + ")", null);
|
||||
if (count > 0) {
|
||||
Log.w(TAG, "Found " + count + " orphaned row(s) in " + table);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
synchronized SQLiteDatabase getDatabase(Context context) {
|
||||
// Always return the cached database, if we've got one
|
||||
@ -811,15 +830,26 @@ public class EmailProvider extends ContentProvider {
|
||||
// Restore accounts if the database is corrupted...
|
||||
restoreIfNeeded(context, mDatabase);
|
||||
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "Deleting orphans...");
|
||||
}
|
||||
// Check for any orphaned Messages in the updated/deleted tables
|
||||
deleteOrphans(mDatabase, Message.UPDATED_TABLE_NAME);
|
||||
deleteOrphans(mDatabase, Message.DELETED_TABLE_NAME);
|
||||
deleteMessageOrphans(mDatabase, Message.UPDATED_TABLE_NAME);
|
||||
deleteMessageOrphans(mDatabase, Message.DELETED_TABLE_NAME);
|
||||
// Delete orphaned mailboxes/messages/policies (account no longer exists)
|
||||
deleteUnlinked(mDatabase, Mailbox.TABLE_NAME, MailboxColumns.ACCOUNT_KEY, AccountColumns.ID,
|
||||
Account.TABLE_NAME);
|
||||
deleteUnlinked(mDatabase, Message.TABLE_NAME, MessageColumns.ACCOUNT_KEY, AccountColumns.ID,
|
||||
Account.TABLE_NAME);
|
||||
deleteUnlinked(mDatabase, Policy.TABLE_NAME, PolicyColumns.ID, AccountColumns.POLICY_KEY,
|
||||
Account.TABLE_NAME);
|
||||
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "EmailProvider pre-caching...");
|
||||
}
|
||||
preCacheData();
|
||||
if (Email.DEBUG) {
|
||||
Log.d(TAG, "Pre-caching finished.");
|
||||
Log.d(TAG, "EmailProvider ready.");
|
||||
}
|
||||
return mDatabase;
|
||||
}
|
||||
@ -959,7 +989,7 @@ public class EmailProvider extends ContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ static void deleteOrphans(SQLiteDatabase database, String tableName) {
|
||||
/*package*/ static void deleteMessageOrphans(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);
|
||||
@ -1808,17 +1838,6 @@ public class EmailProvider extends ContentProvider {
|
||||
}
|
||||
c = db.query(tableName, projection, whereWithId(id, selection),
|
||||
selectionArgs, null, null, sortOrder, limit);
|
||||
if (Email.DEBUG) {
|
||||
switch(match) {
|
||||
case ACCOUNT_ID:
|
||||
case HOSTAUTH_ID:
|
||||
case POLICY_ID:
|
||||
case MAILBOX_ID:
|
||||
Log.w(Logging.LOG_TAG,
|
||||
"==== UNCACHED read of " + tableName + ", id = " + id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cache != null) {
|
||||
c = cache.putCursor(c, id, projection, token);
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import com.android.emailcommon.provider.EmailContent.BodyColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.Message;
|
||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.PolicyColumns;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.provider.Policy;
|
||||
@ -953,9 +954,9 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
assertEquals(6, EmailContent.count(context, Message.DELETED_CONTENT_URI, null, null));
|
||||
|
||||
// Delete the orphans
|
||||
EmailProvider.deleteOrphans(EmailProvider.getReadableDatabase(context),
|
||||
EmailProvider.deleteMessageOrphans(EmailProvider.getReadableDatabase(context),
|
||||
Message.DELETED_TABLE_NAME);
|
||||
EmailProvider.deleteOrphans(EmailProvider.getReadableDatabase(context),
|
||||
EmailProvider.deleteMessageOrphans(EmailProvider.getReadableDatabase(context),
|
||||
Message.UPDATED_TABLE_NAME);
|
||||
|
||||
// There should now be 4 messages in each of the deleted and updated tables again
|
||||
@ -2459,4 +2460,69 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
|
||||
cleanupTestAccountManagerAccounts(accountManager);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCleanupOrphans() {
|
||||
EmailProvider ep = getProvider();
|
||||
SQLiteDatabase db = ep.getDatabase(mMockContext);
|
||||
|
||||
Account a = ProviderTestUtils.setupAccount("account1", true, mMockContext);
|
||||
// Mailbox a1 and a3 won't have a valid account
|
||||
Mailbox a1 = createTypeMailbox(mMockContext, -1, Mailbox.TYPE_INBOX);
|
||||
Mailbox a2 = createTypeMailbox(mMockContext, a.mId, Mailbox.TYPE_MAIL);
|
||||
Mailbox a3 = createTypeMailbox(mMockContext, -1, Mailbox.TYPE_DRAFTS);
|
||||
Mailbox a4 = createTypeMailbox(mMockContext, a.mId, Mailbox.TYPE_SENT);
|
||||
Mailbox a5 = createTypeMailbox(mMockContext, a.mId, Mailbox.TYPE_TRASH);
|
||||
// Mailbox ax isn't even saved; use an obviously invalid id
|
||||
Mailbox ax = new Mailbox();
|
||||
ax.mId = 69105;
|
||||
|
||||
// Message mt2 is an orphan, as is mt4
|
||||
Message m1 = createMessage(mMockContext, a1, true, false, Message.FLAG_LOADED_COMPLETE);
|
||||
Message m2 = createMessage(mMockContext, a2, true, false, Message.FLAG_LOADED_COMPLETE);
|
||||
Message m3 = createMessage(mMockContext, a3, true, false, Message.FLAG_LOADED_COMPLETE);
|
||||
Message m4 = createMessage(mMockContext, a4, true, false, Message.FLAG_LOADED_COMPLETE);
|
||||
Message m5 = createMessage(mMockContext, a5, true, false, Message.FLAG_LOADED_COMPLETE);
|
||||
Message mx = createMessage(mMockContext, ax, true, false, Message.FLAG_LOADED_COMPLETE);
|
||||
|
||||
// Two orphan policies
|
||||
Policy p1 = new Policy();
|
||||
p1.save(mMockContext);
|
||||
Policy p2 = new Policy();
|
||||
p2.save(mMockContext);
|
||||
Policy p3 = new Policy();
|
||||
Policy.setAccountPolicy(mMockContext, a.mId, p3, "0");
|
||||
|
||||
// We don't want anything cached or the tests below won't work. Note that
|
||||
// deleteUnlinked is only called by EmailProvider when the caches are empty
|
||||
ContentCache.invalidateAllCaches();
|
||||
// Delete orphaned mailboxes/messages/policies
|
||||
ep.deleteUnlinked(db, Mailbox.TABLE_NAME, MailboxColumns.ACCOUNT_KEY, AccountColumns.ID,
|
||||
Account.TABLE_NAME);
|
||||
ep.deleteUnlinked(db, Message.TABLE_NAME, MessageColumns.ACCOUNT_KEY, AccountColumns.ID,
|
||||
Account.TABLE_NAME);
|
||||
ep.deleteUnlinked(db, Policy.TABLE_NAME, PolicyColumns.ID, AccountColumns.POLICY_KEY,
|
||||
Account.TABLE_NAME);
|
||||
|
||||
// Make sure the orphaned mailboxes are gone
|
||||
assertNull(Mailbox.restoreMailboxWithId(mMockContext, a1.mId));
|
||||
assertNotNull(Mailbox.restoreMailboxWithId(mMockContext, a2.mId));
|
||||
assertNull(Mailbox.restoreMailboxWithId(mMockContext, a3.mId));
|
||||
assertNotNull(Mailbox.restoreMailboxWithId(mMockContext, a4.mId));
|
||||
assertNotNull(Mailbox.restoreMailboxWithId(mMockContext, a5.mId));
|
||||
assertNull(Mailbox.restoreMailboxWithId(mMockContext, ax.mId));
|
||||
|
||||
// Make sure orphaned messages are gone
|
||||
assertNull(Message.restoreMessageWithId(mMockContext, m1.mId));
|
||||
assertNotNull(Message.restoreMessageWithId(mMockContext, m2.mId));
|
||||
assertNull(Message.restoreMessageWithId(mMockContext, m3.mId));
|
||||
assertNotNull(Message.restoreMessageWithId(mMockContext, m4.mId));
|
||||
assertNotNull(Message.restoreMessageWithId(mMockContext, m5.mId));
|
||||
assertNull(Message.restoreMessageWithId(mMockContext, mx.mId));
|
||||
|
||||
// Make sure orphaned policies are gone
|
||||
assertNull(Policy.restorePolicyWithId(mMockContext, p1.mId));
|
||||
assertNull(Policy.restorePolicyWithId(mMockContext, p2.mId));
|
||||
a = Account.restoreAccountWithId(mMockContext, a.mId);
|
||||
assertNotNull(Policy.restorePolicyWithId(mMockContext, a.mPolicyKey));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user