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 {
|
public final class Policy extends EmailContent implements EmailContent.PolicyColumns, Parcelable {
|
||||||
// STOPSHIP Change to false after a few days of debugging
|
// 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 TAG = "Email/Policy";
|
||||||
|
|
||||||
public static final String TABLE_NAME = "Policy";
|
public static final String TABLE_NAME = "Policy";
|
||||||
|
@ -786,6 +786,25 @@ public class EmailProvider extends ContentProvider {
|
|||||||
private SQLiteDatabase mDatabase;
|
private SQLiteDatabase mDatabase;
|
||||||
private SQLiteDatabase mBodyDatabase;
|
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
|
@VisibleForTesting
|
||||||
synchronized SQLiteDatabase getDatabase(Context context) {
|
synchronized SQLiteDatabase getDatabase(Context context) {
|
||||||
// Always return the cached database, if we've got one
|
// 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...
|
// Restore accounts if the database is corrupted...
|
||||||
restoreIfNeeded(context, mDatabase);
|
restoreIfNeeded(context, mDatabase);
|
||||||
|
|
||||||
|
if (Email.DEBUG) {
|
||||||
|
Log.d(TAG, "Deleting orphans...");
|
||||||
|
}
|
||||||
// Check for any orphaned Messages in the updated/deleted tables
|
// Check for any orphaned Messages in the updated/deleted tables
|
||||||
deleteOrphans(mDatabase, Message.UPDATED_TABLE_NAME);
|
deleteMessageOrphans(mDatabase, Message.UPDATED_TABLE_NAME);
|
||||||
deleteOrphans(mDatabase, Message.DELETED_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) {
|
if (Email.DEBUG) {
|
||||||
Log.d(TAG, "EmailProvider pre-caching...");
|
Log.d(TAG, "EmailProvider pre-caching...");
|
||||||
}
|
}
|
||||||
preCacheData();
|
preCacheData();
|
||||||
if (Email.DEBUG) {
|
if (Email.DEBUG) {
|
||||||
Log.d(TAG, "Pre-caching finished.");
|
Log.d(TAG, "EmailProvider ready.");
|
||||||
}
|
}
|
||||||
return mDatabase;
|
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) {
|
if (database != null) {
|
||||||
// We'll look at all of the items in the table; there won't be many typically
|
// 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);
|
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),
|
c = db.query(tableName, projection, whereWithId(id, selection),
|
||||||
selectionArgs, null, null, sortOrder, limit);
|
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) {
|
if (cache != null) {
|
||||||
c = cache.putCursor(c, id, projection, token);
|
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.MailboxColumns;
|
||||||
import com.android.emailcommon.provider.EmailContent.Message;
|
import com.android.emailcommon.provider.EmailContent.Message;
|
||||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
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.HostAuth;
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
import com.android.emailcommon.provider.Policy;
|
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));
|
assertEquals(6, EmailContent.count(context, Message.DELETED_CONTENT_URI, null, null));
|
||||||
|
|
||||||
// Delete the orphans
|
// Delete the orphans
|
||||||
EmailProvider.deleteOrphans(EmailProvider.getReadableDatabase(context),
|
EmailProvider.deleteMessageOrphans(EmailProvider.getReadableDatabase(context),
|
||||||
Message.DELETED_TABLE_NAME);
|
Message.DELETED_TABLE_NAME);
|
||||||
EmailProvider.deleteOrphans(EmailProvider.getReadableDatabase(context),
|
EmailProvider.deleteMessageOrphans(EmailProvider.getReadableDatabase(context),
|
||||||
Message.UPDATED_TABLE_NAME);
|
Message.UPDATED_TABLE_NAME);
|
||||||
|
|
||||||
// There should now be 4 messages in each of the deleted and updated tables again
|
// 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);
|
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