diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d59e431..f60125789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ### Upcoming release +* Significant performance improvements when updating repositories + * Show what repository each apk comes from * Better support for Android 6.0 diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 96475ea42..f67803ecf 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -1057,20 +1057,7 @@ public class AppProvider extends FDroidProvider { " JOIN " + repo + " ON (" + repo + "._id = " + apk + ".repo) " + " WHERE " + app + ".id = " + apk + ".id AND " + - apk + ".vercode = ( " + - - // We only want the latest apk here. Ideally, we should - // instead join onto apk.suggestedVercode, but as per - // https://gitlab.com/fdroid/fdroidclient/issues/1 there - // may be some situations where suggestedVercode isn't - // set. - // TODO: If we can guarantee that suggestedVercode is set, - // then join onto that instead. This will save from doing - // a futher sub query for each app. - " SELECT MAX(inner_apk.vercode) " + - " FROM " + apk + " as inner_apk " + - " WHERE inner_apk.id = " + apk + ".id ) " + - " AND " + apk + ".repo = fdroid_repo._id "; + apk + ".vercode = " + app + ".suggestedVercode "; return " UPDATE " + app + " SET " + diff --git a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java index 2138f8575..8f5d2e037 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java +++ b/app/src/main/java/org/fdroid/fdroid/data/RepoPersister.java @@ -91,9 +91,8 @@ public class RepoPersister { // end of the process. This is due to the fact that we can't verify the cert // the index was signed with until we've finished reading it - and we don't // want to put stuff in the real database until we are sure it is from a - // trusted source. + // trusted source. It also helps performance as it is done via an in-memory database. TempAppProvider.Helper.init(context); - TempApkProvider.Helper.init(context); hasBeenInitialized = true; } diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java index 831b231ba..19b4d277d 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempApkProvider.java @@ -68,12 +68,16 @@ public class TempApkProvider extends ApkProvider { /** * Deletes the old temporary table (if it exists). Then creates a new temporary apk provider * table and populates it with all the data from the real apk provider table. + * + * This is package local because it must be invoked after + * {@link org.fdroid.fdroid.data.TempAppProvider.Helper#init(Context)}. Due to this + * dependence, that method invokes this one itself, rather than leaving it to the + * {@link RepoPersister}. */ - public static void init(Context context) { + static void init(Context context) { Uri uri = Uri.withAppendedPath(getContentUri(), PATH_INIT); context.getContentResolver().insert(uri, new ContentValues()); } - } @Override @@ -123,11 +127,11 @@ public class TempApkProvider extends ApkProvider { private void initTable() { final SQLiteDatabase db = db(); - db.execSQL("DROP TABLE IF EXISTS " + getTableName()); - db.execSQL("CREATE TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APK); - db.execSQL("CREATE INDEX IF NOT EXISTS apk_vercode on " + getTableName() + " (vercode);"); - db.execSQL("CREATE INDEX IF NOT EXISTS apk_id on " + getTableName() + " (id);"); - db.execSQL("CREATE INDEX IF NOT EXISTS apk_compatible ON " + getTableName() + " (compatible);"); + final String memoryDbName = TempAppProvider.DB; + db.execSQL("CREATE TABLE " + memoryDbName + "." + getTableName() + " AS SELECT * FROM main." + DBHelper.TABLE_APK); + db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_vercode on " + getTableName() + " (vercode);"); + db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_id on " + getTableName() + " (id);"); + db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_compatible ON " + getTableName() + " (compatible);"); } } diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java index 81a0737ff..d2ee5aa6e 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java @@ -4,17 +4,18 @@ import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; import android.net.Uri; -import android.util.Log; - -import org.fdroid.fdroid.Utils; /** * This class does all of its operations in a temporary sqlite table. */ public class TempAppProvider extends AppProvider { - private static final String TAG = "TempAppProvider"; + /** + * The name of the in memory database used for updating. + */ + static final String DB = "temp_update_db"; private static final String PROVIDER_NAME = "TempAppProvider"; @@ -60,6 +61,7 @@ public class TempAppProvider extends AppProvider { public static void init(Context context) { Uri uri = Uri.withAppendedPath(getContentUri(), PATH_INIT); context.getContentResolver().insert(uri, new ContentValues()); + TempApkProvider.Helper.init(context); } /** @@ -111,13 +113,24 @@ public class TempAppProvider extends AppProvider { return count; } + private void ensureTempTableDetached(SQLiteDatabase db) { + try { + db.execSQL("DETACH DATABASE " + DB); + } catch (SQLiteException e) { + // We expect that most of the time the database will not exist unless an error occurred + // midway through the last update, The resulting exception is: + // android.database.sqlite.SQLiteException: no such database: temp_update_db (code 1) + } + } + private void initTable() { final SQLiteDatabase db = db(); - db.execSQL("DROP TABLE IF EXISTS " + getTableName()); - db.execSQL("CREATE TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APP); - db.execSQL("CREATE INDEX IF NOT EXISTS app_id ON " + getTableName() + " (id);"); - db.execSQL("CREATE INDEX IF NOT EXISTS app_upstreamVercode ON " + getTableName() + " (upstreamVercode);"); - db.execSQL("CREATE INDEX IF NOT EXISTS app_compatible ON " + getTableName() + " (compatible);"); + ensureTempTableDetached(db); + db.execSQL("ATTACH DATABASE ':memory:' AS " + DB); + db.execSQL("CREATE TABLE " + DB + "." + getTableName() + " AS SELECT * FROM main." + DBHelper.TABLE_APP); + db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_id ON " + getTableName() + " (id);"); + db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_upstreamVercode ON " + getTableName() + " (upstreamVercode);"); + db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_compatible ON " + getTableName() + " (compatible);"); } private void commitTable() { @@ -125,21 +138,22 @@ public class TempAppProvider extends AppProvider { try { db.beginTransaction(); - Log.i(TAG, "Renaming " + TABLE_TEMP_APP + " to " + DBHelper.TABLE_APP); - db.execSQL("DROP TABLE " + DBHelper.TABLE_APP); - db.execSQL("ALTER TABLE " + TABLE_TEMP_APP + " RENAME TO " + DBHelper.TABLE_APP); + final String tempApp = DB + "." + TempAppProvider.TABLE_TEMP_APP; + final String tempApk = DB + "." + TempApkProvider.TABLE_TEMP_APK; - Log.i(TAG, "Renaming " + TempApkProvider.TABLE_TEMP_APK + " to " + DBHelper.TABLE_APK); - db.execSQL("DROP TABLE " + DBHelper.TABLE_APK); - db.execSQL("ALTER TABLE " + TempApkProvider.TABLE_TEMP_APK + " RENAME TO " + DBHelper.TABLE_APK); + db.execSQL("DELETE FROM " + DBHelper.TABLE_APP + " WHERE 1"); + db.execSQL("INSERT INTO " + DBHelper.TABLE_APP + " SELECT * FROM " + tempApp); + + db.execSQL("DELETE FROM " + DBHelper.TABLE_APK + " WHERE 1"); + db.execSQL("INSERT INTO " + DBHelper.TABLE_APK + " SELECT * FROM " + tempApk); - Utils.debugLog(TAG, "Successfully renamed both tables, will commit transaction"); db.setTransactionSuccessful(); getContext().getContentResolver().notifyChange(AppProvider.getContentUri(), null); getContext().getContentResolver().notifyChange(ApkProvider.getContentUri(), null); } finally { db.endTransaction(); + db.execSQL("DETACH DATABASE " + DB); // Can't be done in a transaction. } } }