diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml
index 25a5f24ac..3fffd013c 100644
--- a/F-Droid/AndroidManifest.xml
+++ b/F-Droid/AndroidManifest.xml
@@ -83,6 +83,11 @@
android:name="org.fdroid.fdroid.data.ApkProvider"
android:exported="false"/>
+
+
Does not do any checks to see if the apk already exists or not.
*/
private ContentProviderOperation updateExistingApk(final Apk apk) {
- Uri uri = ApkProvider.getContentUri(apk);
+ Uri uri = TempApkProvider.getApkUri(apk);
ContentValues values = apk.toContentValues();
return ContentProviderOperation.newUpdate(uri).withValues(values).build();
}
@@ -336,7 +337,7 @@ public class RepoUpdater {
*/
private ContentProviderOperation insertNewApk(final Apk apk) {
ContentValues values = apk.toContentValues();
- Uri uri = ApkProvider.getContentUri();
+ Uri uri = TempApkProvider.getContentUri();
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
@@ -376,7 +377,7 @@ public class RepoUpdater {
// TODO: Deal with more than MAX_QUERY_PARAMS...
if (toDelete.size() > 0) {
- Uri uri = ApkProvider.getContentUriForApks(repo, toDelete);
+ Uri uri = TempApkProvider.getApksUri(repo, toDelete);
return ContentProviderOperation.newDelete(uri).build();
} else {
return null;
@@ -409,6 +410,13 @@ public class RepoUpdater {
if (downloadedFile == null || !downloadedFile.exists())
throw new UpdateException(repo, downloadedFile + " does not exist!");
+ // This is where we will store all of the metadata before commiting at the
+ // 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.
+ TempApkProvider.Helper.init(context);
+
// Due to a bug in Android 5.0 Lollipop, the inclusion of spongycastle causes
// breakage when verifying the signature of the downloaded .jar. For more
// details, check out https://gitlab.com/fdroid/fdroidclient/issues/111.
@@ -419,9 +427,6 @@ public class RepoUpdater {
indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
progressListener, repo, (int) indexEntry.getSize());
- /* JarEntry can only read certificates after the file represented by that JarEntry
- * has been read completely, so verification cannot run until now... */
-
// Process the index...
final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
final XMLReader reader = parser.getXMLReader();
@@ -430,7 +435,11 @@ public class RepoUpdater {
reader.parse(new InputSource(indexInputStream));
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();
+
+ TempApkProvider.Helper.commit(context);
RepoProvider.Helper.update(context, repo, repoDetailsToSave);
} catch (SAXException | ParserConfigurationException | IOException e) {
diff --git a/F-Droid/src/org/fdroid/fdroid/data/ApkProvider.java b/F-Droid/src/org/fdroid/fdroid/data/ApkProvider.java
index 495689c17..2a2b4bed1 100644
--- a/F-Droid/src/org/fdroid/fdroid/data/ApkProvider.java
+++ b/F-Droid/src/org/fdroid/fdroid/data/ApkProvider.java
@@ -227,15 +227,15 @@ public class ApkProvider extends FDroidProvider {
private static final int CODE_REPO = CODE_APP + 1;
private static final int CODE_APKS = CODE_REPO + 1;
private static final int CODE_REPO_APPS = CODE_APKS + 1;
- private static final int CODE_REPO_APK = CODE_REPO_APPS + 1;
+ protected static final int CODE_REPO_APK = CODE_REPO_APPS + 1;
private static final String PROVIDER_NAME = "ApkProvider";
- private static final String PATH_APK = "apk";
+ protected static final String PATH_APK = "apk";
private static final String PATH_APKS = "apks";
private static final String PATH_APP = "app";
private static final String PATH_REPO = "repo";
private static final String PATH_REPO_APPS = "repo-apps";
- private static final String PATH_REPO_APK = "repo-apk";
+ protected static final String PATH_REPO_APK = "repo-apk";
private static final UriMatcher matcher = new UriMatcher(-1);
@@ -322,7 +322,7 @@ public class ApkProvider extends FDroidProvider {
.build();
}
- private static String buildApkString(List apks) {
+ protected static String buildApkString(List apks) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < apks.size(); i++) {
if (i != 0) {
diff --git a/F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java b/F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java
new file mode 100644
index 000000000..a8bc1dbbc
--- /dev/null
+++ b/F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java
@@ -0,0 +1,139 @@
+package org.fdroid.fdroid.data;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+import org.fdroid.fdroid.Utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class does all of its operations in a temporary sqlite table.
+ */
+public class TempApkProvider extends ApkProvider {
+
+ private static final String TAG = "TempApkProvider";
+
+ private static final String PROVIDER_NAME = "TempApkProvider";
+
+ 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 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);
+ }
+
+ @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 getApkUri(Apk apk) {
+ return getContentUri()
+ .buildUpon()
+ .appendPath(PATH_APK)
+ .appendPath(Integer.toString(apk.vercode))
+ .appendPath(apk.id)
+ .build();
+ }
+
+ public static Uri getApksUri(Repo repo, List apks) {
+ return getContentUri()
+ .buildUpon()
+ .appendPath(PATH_REPO_APK)
+ .appendPath(Long.toString(repo.id))
+ .appendPath(buildApkString(apks))
+ .build();
+ }
+
+ 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());
+ }
+
+ /**
+ * 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
+ 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 if (code == CODE_ROLLBACK) {
+ removeTable();
+ return null;
+ } else {
+ return super.insert(uri, values);
+ }
+ }
+
+ private void initTable() {
+ removeTable();
+ write().execSQL("CREATE TEMPORARY TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APK);
+ }
+
+ private void commitTable() {
+ Log.d(TAG, "Deleting all apks from " + DBHelper.TABLE_APK + " so they can be copied from " + getTableName());
+ write().execSQL("DELETE FROM " + DBHelper.TABLE_APK);
+ write().execSQL("INSERT INTO " + DBHelper.TABLE_APK + " SELECT * FROM " + getTableName());
+ }
+
+ private void removeTable() {
+ write().execSQL("DROP TABLE IF EXISTS " + getTableName());
+ }
+}