Save app details to temp table, then flush after update verified.

This commit is contained in:
Peter Serwylo 2015-09-05 10:39:31 +10:00
parent 9a2d390279
commit cc0adcc5ad
4 changed files with 126 additions and 22 deletions

View File

@ -88,6 +88,11 @@
android:name="org.fdroid.fdroid.data.TempApkProvider"
android:exported="false"/>
<provider
android:authorities="org.fdroid.fdroid.data.TempAppProvider"
android:name="org.fdroid.fdroid.data.TempAppProvider"
android:exported="false"/>
<provider
android:authorities="org.fdroid.fdroid.data.InstalledAppProvider"
android:name="org.fdroid.fdroid.data.InstalledAppProvider"

View File

@ -18,6 +18,7 @@ import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.TempApkProvider;
import org.fdroid.fdroid.data.TempAppProvider;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.DownloaderFactory;
import org.xml.sax.InputSource;
@ -203,12 +204,14 @@ public class RepoUpdater {
}
private void flushBufferToDb() throws UpdateException {
Log.d(TAG, "Flushing details of " + MAX_APP_BUFFER + " and their packages to the database.");
if (apksToSave.size() > 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 {
List<Apk> apksToSaveList = new ArrayList<>();
@ -236,7 +239,7 @@ public class RepoUpdater {
ArrayList<ContentProviderOperation> 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 {
* <strong>Does not do any checks to see if the app already exists or not.</strong>
*/
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);

View File

@ -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);
}

View File

@ -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() {
}
}