From cc0adcc5ad6e103acbcd57a85978416701140cfd Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sat, 5 Sep 2015 10:39:31 +1000 Subject: [PATCH] Save app details to temp table, then flush after update verified. --- F-Droid/AndroidManifest.xml | 5 + .../src/org/fdroid/fdroid/RepoUpdater.java | 25 +++-- .../fdroid/fdroid/data/TempApkProvider.java | 15 +-- .../fdroid/fdroid/data/TempAppProvider.java | 103 ++++++++++++++++++ 4 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 F-Droid/src/org/fdroid/fdroid/data/TempAppProvider.java diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml index 3fffd013c..b9c86c37b 100644 --- a/F-Droid/AndroidManifest.xml +++ b/F-Droid/AndroidManifest.xml @@ -88,6 +88,11 @@ android:name="org.fdroid.fdroid.data.TempApkProvider" android:exported="false"/> + + 0 || appsToSave.size() > 0) { + Log.d(TAG, "Flushing details of up to " + MAX_APP_BUFFER + " apps and their packages to the database."); + flushAppsToDbInBatch(); + flushApksToDbInBatch(); + apksToSave.clear(); + appsToSave.clear(); + } } private void flushApksToDbInBatch() throws UpdateException { @@ -236,7 +239,7 @@ public class RepoUpdater { ArrayList appOperations = insertOrUpdateApps(appsToSave); try { - context.getContentResolver().applyBatch(AppProvider.getAuthority(), appOperations); + context.getContentResolver().applyBatch(TempAppProvider.getAuthority(), appOperations); } catch (RemoteException|OperationApplicationException e) { Log.e(TAG, "Error updating apps", e); throw new UpdateException(repo, "Error updating apps: " + e.getMessage(), e); @@ -290,7 +293,7 @@ public class RepoUpdater { * Does not do any checks to see if the app already exists or not. */ private ContentProviderOperation updateExistingApp(App app) { - Uri uri = AppProvider.getContentUri(app); + Uri uri = TempAppProvider.getAppUri(app); ContentValues values = app.toContentValues(); for (final String toIgnore : APP_FIELDS_TO_IGNORE) { if (values.containsKey(toIgnore)) { @@ -306,7 +309,7 @@ public class RepoUpdater { */ private ContentProviderOperation insertNewApp(App app) { ContentValues values = app.toContentValues(); - Uri uri = AppProvider.getContentUri(); + Uri uri = TempAppProvider.getContentUri(); return ContentProviderOperation.newInsert(uri).withValues(values).build(); } @@ -415,6 +418,7 @@ public class RepoUpdater { // 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. + TempAppProvider.Helper.init(context); TempApkProvider.Helper.init(context); // Due to a bug in Android 5.0 Lollipop, the inclusion of spongycastle causes @@ -433,12 +437,17 @@ public class RepoUpdater { final RepoXMLHandler repoXMLHandler = new RepoXMLHandler(repo, createIndexReceiver()); reader.setContentHandler(repoXMLHandler); reader.parse(new InputSource(indexInputStream)); + + flushBufferToDb(); + signingCertFromJar = getSigningCertFromJar(indexEntry); // JarEntry can only read certificates after the file represented by that JarEntry // has been read completely, so verification cannot run until now... assertSigningCertFromXmlCorrect(); + Log.i(TAG, "Repo signature verified, saving app metadata to database."); + TempAppProvider.Helper.commit(context); TempApkProvider.Helper.commit(context); RepoProvider.Helper.update(context, repo, repoDetailsToSave); diff --git a/F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java b/F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java index a8bc1dbbc..e2806fd49 100644 --- a/F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java +++ b/F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java @@ -31,14 +31,12 @@ public class TempApkProvider extends ApkProvider { private static final int CODE_INIT = 10000; private static final int CODE_COMMIT = CODE_INIT + 1; - private static final int CODE_ROLLBACK = CODE_INIT + 2; private static final UriMatcher matcher = new UriMatcher(-1); static { matcher.addURI(getAuthority(), PATH_INIT, CODE_INIT); matcher.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT); - matcher.addURI(getAuthority(), PATH_ROLLBACK, CODE_ROLLBACK); matcher.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE); matcher.addURI(getAuthority(), PATH_REPO_APK + "/#/*", CODE_REPO_APK); } @@ -94,14 +92,6 @@ public class TempApkProvider extends ApkProvider { context.getContentResolver().insert(uri, new ContentValues()); } - /** - * Not sure that this is strictly necessary, but this will remove the temp table. - */ - public static void rollback(Context context) { - Uri uri = Uri.withAppendedPath(getContentUri(), PATH_ROLLBACK); - context.getContentResolver().insert(uri, new ContentValues()); - } - } @Override @@ -114,16 +104,13 @@ public class TempApkProvider extends ApkProvider { } else if (code == CODE_COMMIT) { commitTable(); return null; - } else if (code == CODE_ROLLBACK) { - removeTable(); - return null; } else { return super.insert(uri, values); } } private void initTable() { - removeTable(); + write().execSQL("DROP TABLE IF EXISTS " + getTableName()); write().execSQL("CREATE TEMPORARY TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APK); } diff --git a/F-Droid/src/org/fdroid/fdroid/data/TempAppProvider.java b/F-Droid/src/org/fdroid/fdroid/data/TempAppProvider.java new file mode 100644 index 000000000..b4c9fb7eb --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/data/TempAppProvider.java @@ -0,0 +1,103 @@ +package org.fdroid.fdroid.data; + +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.net.Uri; +import android.util.Log; + +import java.util.List; + +/** + * This class does all of its operations in a temporary sqlite table. + */ +public class TempAppProvider extends AppProvider { + + private static final String TAG = "TempAppProvider"; + + private static final String PROVIDER_NAME = "TempAppProvider"; + + private static final String PATH_INIT = "init"; + private static final String PATH_COMMIT = "commit"; + private static final String PATH_ROLLBACK = "rollback"; + + private static final int CODE_INIT = 10000; + private static final int CODE_COMMIT = CODE_INIT + 1; + + private static final UriMatcher matcher = new UriMatcher(-1); + + static { + matcher.addURI(getAuthority(), PATH_INIT, CODE_INIT); + matcher.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT); + matcher.addURI(getAuthority(), "*", CODE_SINGLE); + } + + @Override + protected String getTableName() { + return "temp_" + super.getTableName(); + } + + public static String getAuthority() { + return AUTHORITY + "." + PROVIDER_NAME; + } + + public static Uri getContentUri() { + return Uri.parse("content://" + getAuthority()); + } + + public static Uri getAppUri(App app) { + return Uri.withAppendedPath(getContentUri(), app.id); + } + + public static class Helper { + + /** + * 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. + */ + public static void init(Context context) { + Uri uri = Uri.withAppendedPath(getContentUri(), PATH_INIT); + context.getContentResolver().insert(uri, new ContentValues()); + } + + /** + * Saves data from the temp table to the apk table, by removing _EVERYTHING_ from the real + * apk table and inserting all of the records from here. The temporary table is then removed. + */ + public static void commit(Context context) { + Uri uri = Uri.withAppendedPath(getContentUri(), PATH_COMMIT); + context.getContentResolver().insert(uri, new ContentValues()); + } + + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + int code = matcher.match(uri); + + if (code == CODE_INIT) { + initTable(); + return null; + } else if (code == CODE_COMMIT) { + commitTable(); + return null; + } else { + return super.insert(uri, values); + } + } + + private void initTable() { + write().execSQL("DROP TABLE IF EXISTS " + getTableName()); + write().execSQL("CREATE TEMPORARY TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APP); + } + + private void commitTable() { + Log.d(TAG, "Deleting all apks from " + DBHelper.TABLE_APP + " so they can be copied from " + getTableName()); + write().execSQL("DELETE FROM " + DBHelper.TABLE_APP); + write().execSQL("INSERT INTO " + DBHelper.TABLE_APP + " SELECT * FROM " + getTableName()); + } + + private void removeTable() { + + } +}