diff --git a/app/src/org/commcare/android/database/user/models/SessionStateDescriptor.java b/app/src/org/commcare/android/database/user/models/SessionStateDescriptor.java index dafbead72b..f562b74cb7 100755 --- a/app/src/org/commcare/android/database/user/models/SessionStateDescriptor.java +++ b/app/src/org/commcare/android/database/user/models/SessionStateDescriptor.java @@ -61,6 +61,10 @@ public int getFormRecordId() { return formRecordId; } + public void setFormRecordId(int formRecordId) { + this.formRecordId = formRecordId; + } + public SessionStateDescriptor reMapFormRecordId(int idForNewRecord) { SessionStateDescriptor copy = new SessionStateDescriptor(); copy.formRecordId = idForNewRecord; diff --git a/app/src/org/commcare/models/database/user/DatabaseUserOpenHelper.java b/app/src/org/commcare/models/database/user/DatabaseUserOpenHelper.java index d8680ea6fd..99a09ec9e0 100755 --- a/app/src/org/commcare/models/database/user/DatabaseUserOpenHelper.java +++ b/app/src/org/commcare/models/database/user/DatabaseUserOpenHelper.java @@ -62,9 +62,10 @@ public class DatabaseUserOpenHelper extends SQLiteOpenHelper { * V.22 - Add column for appId in entity_cache table * V.23 - Merges InstanceProvider to FormRecord (delete instanceUri, add displayName, filePath and canEditWhenComplete) * v.24 - Adds and indexes column for Case external_id + * v.25 - No DB changes, validates SessionStateDescriptor records corrupted due to an earlier bug in v23 migration (In 2.44 and 2.44.1) */ - private static final int USER_DB_VERSION = 24; + private static final int USER_DB_VERSION = 25; private static final String USER_DB_LOCATOR = "database_sandbox_"; diff --git a/app/src/org/commcare/models/database/user/UserDatabaseUpgrader.java b/app/src/org/commcare/models/database/user/UserDatabaseUpgrader.java index 341eb4cc92..1235d28bd6 100755 --- a/app/src/org/commcare/models/database/user/UserDatabaseUpgrader.java +++ b/app/src/org/commcare/models/database/user/UserDatabaseUpgrader.java @@ -10,6 +10,7 @@ import org.commcare.android.database.user.models.ACasePreV24Model; import org.commcare.android.database.user.models.FormRecordV2; import org.commcare.android.database.user.models.FormRecordV3; +import org.commcare.android.database.user.models.SessionStateDescriptor; import org.commcare.android.logging.ForceCloseLogEntry; import org.commcare.android.javarosa.AndroidLogEntry; import org.commcare.cases.model.Case; @@ -36,6 +37,7 @@ import org.javarosa.core.services.Logger; import org.javarosa.core.services.storage.Persistable; +import java.util.NoSuchElementException; import java.util.Set; import java.util.Vector; @@ -62,6 +64,8 @@ public void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // longer than usual in order to make sure the upgrade has time to finish CommCareApplication.instance().setCustomServiceBindTimeout(5 * 60 * 1000); + int startVersion = oldVersion; + if (oldVersion == 1) { if (upgradeOneTwo(db)) { oldVersion = 2; @@ -191,6 +195,18 @@ public void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) { oldVersion = 24; } } + + if (oldVersion == 24) { + // We are doing migration to v25 because of a bug in migration to v23 earlier, so + // we only want to actually trigger this for apps already on v23 or greater + if (startVersion > 22) { + if (upgradeTwentyFourTwentyFive(db)) { + oldVersion = 25; + } + } else { + oldVersion = 25; + } + } } private boolean upgradeOneTwo(final SQLiteDatabase db) { @@ -648,6 +664,49 @@ private boolean upgradeTwentyThreeTwentyFour(SQLiteDatabase db) { } } + // check for integrity of SSD records and wipes the whole table in case of any discrepancy + private boolean upgradeTwentyFourTwentyFive(SQLiteDatabase db) { + db.beginTransaction(); + try { + boolean strandedRecordObserved = false; + SqlStorage formRecordStorage = UserDbUpgradeUtils.getFormRecordStorage(c, db, FormRecord.class); + SqlStorage ssdStorage = new SqlStorage<>( + SessionStateDescriptor.STORAGE_KEY, + SessionStateDescriptor.class, + new ConcreteAndroidDbHelper(c, db)); + for (SessionStateDescriptor ssd : ssdStorage) { + // we are in invalid state if formRecord with corresponding ssd form id + // either doesn't exist or has status unstarted + try { + FormRecord formRecord = formRecordStorage.read(ssd.getFormRecordId()); + if (formRecord.getStatus().contentEquals(FormRecord.STATUS_UNSTARTED)) { + strandedRecordObserved = true; + break; + } + } catch (NoSuchElementException e) { + strandedRecordObserved = true; + break; + } + } + + if (strandedRecordObserved) { + SqlStorage.wipeTable(db, SessionStateDescriptor.STORAGE_KEY); + + // Since we have wiped out SSD records, we won't be able to resume + // incomplete forms with their earlier session state. Therfore we are + // going to delete all incomplete form records as well + Vector incompleteRecords = formRecordStorage.getRecordsForValue(FormRecord.META_STATUS, FormRecord.STATUS_INCOMPLETE); + for (FormRecord incompleteRecord : incompleteRecords) { + formRecordStorage.remove(incompleteRecord); + } + } + db.setTransactionSuccessful(); + return true; + } finally { + db.endTransaction(); + } + } + private void migrateV2FormRecordsForSingleApp(String appId, SqlStorage oldStorage, Vector upgradedRecords) { diff --git a/app/src/org/commcare/models/database/user/UserDbUpgradeUtils.java b/app/src/org/commcare/models/database/user/UserDbUpgradeUtils.java index b14ef8a068..483e486c86 100644 --- a/app/src/org/commcare/models/database/user/UserDbUpgradeUtils.java +++ b/app/src/org/commcare/models/database/user/UserDbUpgradeUtils.java @@ -30,7 +30,6 @@ import org.javarosa.core.services.storage.Persistable; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -198,7 +197,8 @@ protected static void migrateFormRecordsToV3(Context c, SQLiteDatabase db) { /** * Migrated form records to include data from corresponding Instance from InstanceProvider - * @param c Context + * + * @param c Context * @param db User DB we are migrating * @return a Vector containing Instance Uris corresponding to InstanceProvider entries that got migrated successfully */ @@ -232,6 +232,7 @@ protected static Vector migrateV4FormRecords(Context c, SQLiteDatabase db) cursor.close(); } } + newRecord.setID(oldRecord.getID()); newRecords.add(new Pair<>(newRecord, instanceUri)); } @@ -242,15 +243,34 @@ protected static Vector migrateV4FormRecords(Context c, SQLiteDatabase db) // Write to the new table SqlStorage newStorage = getFormRecordStorage(c, db, FormRecord.class); + SqlStorage ssdStorage = new SqlStorage<>( + SessionStateDescriptor.STORAGE_KEY, + SessionStateDescriptor.class, + new ConcreteAndroidDbHelper(c, db)); for (Pair entry : newRecords) { - newStorage.write(((FormRecord)entry.first)); + FormRecord newRecord = ((FormRecord)entry.first); + int oldId = newRecord.getID(); + + // Since we are writing in new table, reset the id before write + newRecord.setID(-1); + newStorage.write(newRecord); + + // Migrate SSD + try { + SessionStateDescriptor ssd = ssdStorage.getRecordForValue(SessionStateDescriptor.META_FORM_RECORD_ID, oldId); + ssd.setFormRecordId(newRecord.getID()); + ssdStorage.write(ssd); + } catch (Exception e) { + // Ignore failures in SSD Migration + } + migratedInstances.add(((Uri)entry.second)); } return migratedInstances; } - private static SqlStorage getFormRecordStorage(Context c, SQLiteDatabase db, Class formRecordClass) { + public static SqlStorage getFormRecordStorage(Context c, SQLiteDatabase db, Class formRecordClass) { return new SqlStorage<>( FormRecord.STORAGE_KEY, formRecordClass,