package org.commcare.models.database.global; import android.content.Context; import net.sqlcipher.database.SQLiteDatabase; import org.commcare.CommCareApplication; import org.commcare.android.database.global.models.AppAvailableToInstall; import org.commcare.android.logging.ForceCloseLogEntry; import org.commcare.models.database.AndroidTableBuilder; import org.commcare.models.database.ConcreteAndroidDbHelper; import org.commcare.models.database.DbUtil; import org.commcare.models.database.MigrationException; import org.commcare.models.database.SqlStorage; import org.commcare.android.database.global.models.ApplicationRecord; import org.commcare.android.database.global.models.ApplicationRecordV1; import org.commcare.provider.ProviderUtils; import org.javarosa.core.services.storage.Persistable; import java.io.File; /** * @author ctsims */ class GlobalDatabaseUpgrader { private final Context c; public GlobalDatabaseUpgrader(Context c) { this.c = c; } public void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == 1) { if (upgradeOneTwo(db, oldVersion, newVersion)) { oldVersion = 2; } } if (oldVersion == 2) { if (upgradeTwoThree(db)) { oldVersion = 3; } } if (oldVersion == 3) { if (upgradeThreeFour(db)) { oldVersion = 4; } } if (oldVersion == 4) { if (upgradeFourFive(db)) { oldVersion = 5; } } } private boolean upgradeOneTwo(SQLiteDatabase db, int oldVersion, int newVersion) { db.beginTransaction(); try { DbUtil.createNumbersTable(db); db.setTransactionSuccessful(); return true; } finally { db.endTransaction(); } } private boolean upgradeTwoThree(SQLiteDatabase db) { db.beginTransaction(); //First, migrate the old ApplicationRecord in storage to the new version being used for // multiple apps. try { SqlStorage<Persistable> storage = new SqlStorage<Persistable>( ApplicationRecord.STORAGE_KEY, ApplicationRecordV1.class, new ConcreteAndroidDbHelper(c, db)); if (multipleInstalledAppRecords(storage)) { // If a device has multiple installed ApplicationRecords before the multiple apps // db upgrade has occurred, something has definitely gone wrong throw new MigrationException(true); } for (Persistable r : storage) { ApplicationRecordV1 oldRecord = (ApplicationRecordV1) r; ApplicationRecord newRecord = new ApplicationRecord(oldRecord.getApplicationId(), oldRecord.getStatus()); //set this new record to have same ID as the old one newRecord.setID(oldRecord.getID()); //set default values for the new fields newRecord.setResourcesStatus(true); newRecord.setArchiveStatus(false); newRecord.setUniqueId(""); newRecord.setDisplayName(""); newRecord.setVersionNumber(-1); newRecord.setConvertedByDbUpgrader(true); newRecord.setPreMultipleAppsProfile(true); storage.write(newRecord); } // Then migrate the databases for both providers if (upgradeProviderDb(db, ProviderUtils.ProviderType.FORMS) && upgradeProviderDb(db, ProviderUtils.ProviderType.INSTANCES)) { db.setTransactionSuccessful(); return true; } return false; } finally { db.endTransaction(); } } private boolean upgradeThreeFour(SQLiteDatabase db) { return addTableForNewModel(db, ForceCloseLogEntry.STORAGE_KEY, new ForceCloseLogEntry()); } private boolean upgradeFourFive(SQLiteDatabase db) { return addTableForNewModel(db, AppAvailableToInstall.STORAGE_KEY, new AppAvailableToInstall()); } private static boolean addTableForNewModel(SQLiteDatabase db, String storageKey, Persistable modelToAdd) { db.beginTransaction(); try { AndroidTableBuilder builder = new AndroidTableBuilder(storageKey); builder.addData(modelToAdd); db.execSQL(builder.getTableCreateString()); db.setTransactionSuccessful(); return true; } catch (Exception e) { return false; } finally { db.endTransaction(); } } /** * Prior to multiple application seating, the FormsProvider and the InstanceProvider were both * using one global database for all forms/instances. Now that we can have multiple apps * installed at once, we need to have one forms db and one instances db per app. This method * performs the necessary one-time migration from a global db that still exists on a device * to the new per-app system */ private boolean upgradeProviderDb(SQLiteDatabase db, ProviderUtils.ProviderType type) { File oldDbFile = CommCareApplication.instance().getDatabasePath(type.getOldDbName()); if (oldDbFile.exists()) { File newDbFile = CommCareApplication.instance().getDatabasePath( ProviderUtils.getProviderDbName(type, getInstalledAppRecord(c, db).getApplicationId())); if (!oldDbFile.renameTo(newDbFile)) { throw new MigrationException(false); } else { return true; } } return true; } private static boolean multipleInstalledAppRecords(SqlStorage<Persistable> storage) { int count = 0; for (Persistable p : storage) { ApplicationRecordV1 r = (ApplicationRecordV1) p; if (r.getStatus() == ApplicationRecord.STATUS_INSTALLED) { count++; } } return (count > 1); } private static ApplicationRecord getInstalledAppRecord(Context c, SQLiteDatabase db) { SqlStorage<Persistable> storage = new SqlStorage<Persistable>( ApplicationRecord.STORAGE_KEY, ApplicationRecord.class, new ConcreteAndroidDbHelper(c, db)); for (Persistable p : storage) { ApplicationRecord r = (ApplicationRecord) p; if (r.getStatus() == ApplicationRecord.STATUS_INSTALLED) { return r; } } return null; } }