WIP: Do repo update database work in temp table for apks, then copy at end.
At the start of a repo update, it will create a copy of the apk table. Throughout the update, it will query the original apk table for info. All inserts and updates happen to the temp table. After the repo has been verified as trusted, the original apk table is emptied, and all apks are copied from the temp table to the real one. I realise that the work done to query the apk table for info during the update could happen against the temp table, but it was not neccesary to move all of the queries that are required for this task to the temp apk provider.
This commit is contained in:
parent
b989ef3ecc
commit
9a2d390279
@ -83,6 +83,11 @@
|
|||||||
android:name="org.fdroid.fdroid.data.ApkProvider"
|
android:name="org.fdroid.fdroid.data.ApkProvider"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:authorities="org.fdroid.fdroid.data.TempApkProvider"
|
||||||
|
android:name="org.fdroid.fdroid.data.TempApkProvider"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:authorities="org.fdroid.fdroid.data.InstalledAppProvider"
|
android:authorities="org.fdroid.fdroid.data.InstalledAppProvider"
|
||||||
android:name="org.fdroid.fdroid.data.InstalledAppProvider"
|
android:name="org.fdroid.fdroid.data.InstalledAppProvider"
|
||||||
|
@ -17,6 +17,7 @@ import org.fdroid.fdroid.data.App;
|
|||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
|
import org.fdroid.fdroid.data.TempApkProvider;
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
import org.fdroid.fdroid.net.DownloaderFactory;
|
import org.fdroid.fdroid.net.DownloaderFactory;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
@ -225,7 +226,7 @@ public class RepoUpdater {
|
|||||||
apkOperations.addAll(insertOrUpdateApks(apksToSaveList));
|
apkOperations.addAll(insertOrUpdateApks(apksToSaveList));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.getContentResolver().applyBatch(ApkProvider.getAuthority(), apkOperations);
|
context.getContentResolver().applyBatch(TempApkProvider.getAuthority(), apkOperations);
|
||||||
} catch (RemoteException | OperationApplicationException e) {
|
} catch (RemoteException | OperationApplicationException e) {
|
||||||
throw new UpdateException(repo, "An internal error occured while updating the database", e);
|
throw new UpdateException(repo, "An internal error occured while updating the database", e);
|
||||||
}
|
}
|
||||||
@ -325,7 +326,7 @@ public class RepoUpdater {
|
|||||||
* <strong>Does not do any checks to see if the apk already exists or not.</strong>
|
* <strong>Does not do any checks to see if the apk already exists or not.</strong>
|
||||||
*/
|
*/
|
||||||
private ContentProviderOperation updateExistingApk(final Apk apk) {
|
private ContentProviderOperation updateExistingApk(final Apk apk) {
|
||||||
Uri uri = ApkProvider.getContentUri(apk);
|
Uri uri = TempApkProvider.getApkUri(apk);
|
||||||
ContentValues values = apk.toContentValues();
|
ContentValues values = apk.toContentValues();
|
||||||
return ContentProviderOperation.newUpdate(uri).withValues(values).build();
|
return ContentProviderOperation.newUpdate(uri).withValues(values).build();
|
||||||
}
|
}
|
||||||
@ -336,7 +337,7 @@ public class RepoUpdater {
|
|||||||
*/
|
*/
|
||||||
private ContentProviderOperation insertNewApk(final Apk apk) {
|
private ContentProviderOperation insertNewApk(final Apk apk) {
|
||||||
ContentValues values = apk.toContentValues();
|
ContentValues values = apk.toContentValues();
|
||||||
Uri uri = ApkProvider.getContentUri();
|
Uri uri = TempApkProvider.getContentUri();
|
||||||
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
return ContentProviderOperation.newInsert(uri).withValues(values).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +377,7 @@ public class RepoUpdater {
|
|||||||
|
|
||||||
// TODO: Deal with more than MAX_QUERY_PARAMS...
|
// TODO: Deal with more than MAX_QUERY_PARAMS...
|
||||||
if (toDelete.size() > 0) {
|
if (toDelete.size() > 0) {
|
||||||
Uri uri = ApkProvider.getContentUriForApks(repo, toDelete);
|
Uri uri = TempApkProvider.getApksUri(repo, toDelete);
|
||||||
return ContentProviderOperation.newDelete(uri).build();
|
return ContentProviderOperation.newDelete(uri).build();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@ -409,6 +410,13 @@ public class RepoUpdater {
|
|||||||
if (downloadedFile == null || !downloadedFile.exists())
|
if (downloadedFile == null || !downloadedFile.exists())
|
||||||
throw new UpdateException(repo, downloadedFile + " does not exist!");
|
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
|
// 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
|
// breakage when verifying the signature of the downloaded .jar. For more
|
||||||
// details, check out https://gitlab.com/fdroid/fdroidclient/issues/111.
|
// details, check out https://gitlab.com/fdroid/fdroidclient/issues/111.
|
||||||
@ -419,9 +427,6 @@ public class RepoUpdater {
|
|||||||
indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
|
indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
|
||||||
progressListener, repo, (int) indexEntry.getSize());
|
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...
|
// Process the index...
|
||||||
final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
|
final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
|
||||||
final XMLReader reader = parser.getXMLReader();
|
final XMLReader reader = parser.getXMLReader();
|
||||||
@ -430,7 +435,11 @@ public class RepoUpdater {
|
|||||||
reader.parse(new InputSource(indexInputStream));
|
reader.parse(new InputSource(indexInputStream));
|
||||||
signingCertFromJar = getSigningCertFromJar(indexEntry);
|
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();
|
assertSigningCertFromXmlCorrect();
|
||||||
|
|
||||||
|
TempApkProvider.Helper.commit(context);
|
||||||
RepoProvider.Helper.update(context, repo, repoDetailsToSave);
|
RepoProvider.Helper.update(context, repo, repoDetailsToSave);
|
||||||
|
|
||||||
} catch (SAXException | ParserConfigurationException | IOException e) {
|
} catch (SAXException | ParserConfigurationException | IOException e) {
|
||||||
|
@ -227,15 +227,15 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
private static final int CODE_REPO = CODE_APP + 1;
|
private static final int CODE_REPO = CODE_APP + 1;
|
||||||
private static final int CODE_APKS = CODE_REPO + 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_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 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_APKS = "apks";
|
||||||
private static final String PATH_APP = "app";
|
private static final String PATH_APP = "app";
|
||||||
private static final String PATH_REPO = "repo";
|
private static final String PATH_REPO = "repo";
|
||||||
private static final String PATH_REPO_APPS = "repo-apps";
|
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);
|
private static final UriMatcher matcher = new UriMatcher(-1);
|
||||||
|
|
||||||
@ -322,7 +322,7 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildApkString(List<Apk> apks) {
|
protected static String buildApkString(List<Apk> apks) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
for (int i = 0; i < apks.size(); i++) {
|
for (int i = 0; i < apks.size(); i++) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
|
139
F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java
Normal file
139
F-Droid/src/org/fdroid/fdroid/data/TempApkProvider.java
Normal file
@ -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<Apk> 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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user