Ensure that the suggestedVersionCode is updated after [un]installing.
Previously, it was only done on repo update. Now it is done whenever an app is installed or unisntalled. The query to update the suggested version for each app is quite slow when run at the end of a repo update. However in this change, we are limiting the query to only update a single app, which means that performance should not be a problem.
This commit is contained in:
parent
677fd3a522
commit
bf4b0d89a1
@ -117,6 +117,11 @@ public class AppProvider extends FDroidProvider {
|
||||
return app;
|
||||
}
|
||||
|
||||
public static void calcSuggestedApk(Context context, String packageName) {
|
||||
Uri uri = Uri.withAppendedPath(calcSuggestedApksUri(), packageName);
|
||||
context.getContentResolver().update(uri, null, null, null);
|
||||
}
|
||||
|
||||
public static void calcSuggestedApks(Context context) {
|
||||
context.getContentResolver().update(calcSuggestedApksUri(), null, null, null);
|
||||
}
|
||||
@ -385,6 +390,7 @@ public class AppProvider extends FDroidProvider {
|
||||
static {
|
||||
MATCHER.addURI(getAuthority(), null, CODE_LIST);
|
||||
MATCHER.addURI(getAuthority(), PATH_CALC_SUGGESTED_APKS, CALC_SUGGESTED_APKS);
|
||||
MATCHER.addURI(getAuthority(), PATH_CALC_SUGGESTED_APKS + "/*", CALC_SUGGESTED_APKS);
|
||||
MATCHER.addURI(getAuthority(), PATH_RECENTLY_UPDATED, RECENTLY_UPDATED);
|
||||
MATCHER.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY);
|
||||
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*/*", SEARCH_TEXT_AND_CATEGORIES);
|
||||
@ -879,7 +885,13 @@ public class AppProvider extends FDroidProvider {
|
||||
throw new UnsupportedOperationException("Update not supported for " + uri + ".");
|
||||
}
|
||||
|
||||
updateSuggestedApks();
|
||||
List<String> segments = uri.getPathSegments();
|
||||
if (segments.size() > 1) {
|
||||
String packageName = segments.get(1);
|
||||
updateSuggestedApk(packageName);
|
||||
} else {
|
||||
updateSuggestedApks();
|
||||
}
|
||||
getContext().getContentResolver().notifyChange(getCanUpdateUri(), null);
|
||||
return 0;
|
||||
}
|
||||
@ -887,8 +899,8 @@ public class AppProvider extends FDroidProvider {
|
||||
protected void updateAllAppDetails() {
|
||||
updatePreferredMetadata();
|
||||
updateCompatibleFlags();
|
||||
updateSuggestedFromUpstream();
|
||||
updateSuggestedFromLatest();
|
||||
updateSuggestedFromUpstream(null);
|
||||
updateSuggestedFromLatest(null);
|
||||
updateIconUrls();
|
||||
}
|
||||
|
||||
@ -909,8 +921,13 @@ public class AppProvider extends FDroidProvider {
|
||||
* {@link android.app.IntentService} as described in https://gitlab.com/fdroid/fdroidclient/issues/520.
|
||||
*/
|
||||
protected void updateSuggestedApks() {
|
||||
updateSuggestedFromUpstream();
|
||||
updateSuggestedFromLatest();
|
||||
updateSuggestedFromUpstream(null);
|
||||
updateSuggestedFromLatest(null);
|
||||
}
|
||||
|
||||
protected void updateSuggestedApk(String packageName) {
|
||||
updateSuggestedFromUpstream(packageName);
|
||||
updateSuggestedFromLatest(packageName);
|
||||
}
|
||||
|
||||
private void updatePreferredMetadata() {
|
||||
@ -964,9 +981,9 @@ public class AppProvider extends FDroidProvider {
|
||||
* If the app is installed, then all apks signed by a different certificate are
|
||||
* ignored for the purpose of this calculation.
|
||||
*
|
||||
* @see #updateSuggestedFromLatest()
|
||||
* @see #updateSuggestedFromLatest(String)
|
||||
*/
|
||||
private void updateSuggestedFromUpstream() {
|
||||
private void updateSuggestedFromUpstream(@Nullable String packageName) {
|
||||
Utils.debugLog(TAG, "Calculating suggested versions for all NON-INSTALLED apps which specify an upstream version code.");
|
||||
|
||||
final String apk = getApkTableName();
|
||||
@ -976,6 +993,14 @@ public class AppProvider extends FDroidProvider {
|
||||
final boolean unstableUpdates = Preferences.get().getUnstableUpdates();
|
||||
String restrictToStable = unstableUpdates ? "" : (apk + "." + ApkTable.Cols.VERSION_CODE + " <= " + app + "." + Cols.UPSTREAM_VERSION_CODE + " AND ");
|
||||
|
||||
String restrictToApp = "";
|
||||
String[] args = null;
|
||||
|
||||
if (packageName != null) {
|
||||
restrictToApp = " AND " + app + "." + Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() + ") ";
|
||||
args = new String[]{packageName};
|
||||
}
|
||||
|
||||
// The join onto `appForThisApk` is to ensure that the MAX(apk.versionCode) is chosen from
|
||||
// all apps regardless of repo. If we joined directly onto the outer `app` table we are
|
||||
// in the process of updating, then it would be limited to only apks from the same repo.
|
||||
@ -1001,9 +1026,9 @@ public class AppProvider extends FDroidProvider {
|
||||
apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " +
|
||||
restrictToStable +
|
||||
" ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + Cols.IS_COMPATIBLE + " = 1 ) ) " +
|
||||
" WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 ";
|
||||
" WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 " + restrictToApp;
|
||||
|
||||
LoggingQuery.execSQL(db(), updateSql);
|
||||
LoggingQuery.execSQL(db(), updateSql, args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1014,15 +1039,28 @@ public class AppProvider extends FDroidProvider {
|
||||
* out from the upstream vercode. In such a case, fall back to the simpler
|
||||
* algorithm as if upstreamVercode was 0.
|
||||
*
|
||||
* @see #updateSuggestedFromUpstream()
|
||||
* @see #updateSuggestedFromUpstream(String)
|
||||
*/
|
||||
private void updateSuggestedFromLatest() {
|
||||
private void updateSuggestedFromLatest(@Nullable String packageName) {
|
||||
Utils.debugLog(TAG, "Calculating suggested versions for all apps which don't specify an upstream version code.");
|
||||
|
||||
final String apk = getApkTableName();
|
||||
final String app = getTableName();
|
||||
final String installed = InstalledAppTable.NAME;
|
||||
|
||||
final String restrictToApps;
|
||||
final String[] args;
|
||||
|
||||
if (packageName == null) {
|
||||
restrictToApps = " COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 OR " + Cols.SUGGESTED_VERSION_CODE + " IS NULL ";
|
||||
args = null;
|
||||
} else {
|
||||
// Don't update an app with an upstream version code, because that would have been updated
|
||||
// by updateSuggestedFromUpdate(packageName).
|
||||
restrictToApps = " COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 AND " + app + "." + Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() + ") ";
|
||||
args = new String[]{packageName};
|
||||
}
|
||||
|
||||
String updateSql =
|
||||
"UPDATE " + app + " SET " + Cols.SUGGESTED_VERSION_CODE + " = ( " +
|
||||
" SELECT MAX( " + apk + "." + ApkTable.Cols.VERSION_CODE + " ) " +
|
||||
@ -1033,9 +1071,9 @@ public class AppProvider extends FDroidProvider {
|
||||
app + "." + Cols.PACKAGE_ID + " = appForThisApk." + Cols.PACKAGE_ID + " AND " +
|
||||
apk + "." + ApkTable.Cols.SIGNATURE + " = COALESCE(" + installed + "." + InstalledAppTable.Cols.SIGNATURE + ", " + apk + "." + ApkTable.Cols.SIGNATURE + ") AND " +
|
||||
" ( " + app + "." + Cols.IS_COMPATIBLE + " = 0 OR " + apk + "." + ApkTable.Cols.IS_COMPATIBLE + " = 1 ) ) " +
|
||||
" WHERE COALESCE(" + Cols.UPSTREAM_VERSION_CODE + ", 0) = 0 OR " + Cols.SUGGESTED_VERSION_CODE + " IS NULL ";
|
||||
" WHERE " + restrictToApps;
|
||||
|
||||
LoggingQuery.execSQL(db(), updateSql);
|
||||
LoggingQuery.execSQL(db(), updateSql, args);
|
||||
}
|
||||
|
||||
private void updateIconUrls() {
|
||||
|
@ -221,10 +221,17 @@ public class InstalledAppProvider extends FDroidProvider {
|
||||
throw new UnsupportedOperationException("Delete not supported for " + uri + ".");
|
||||
}
|
||||
|
||||
String packageName = uri.getLastPathSegment();
|
||||
QuerySelection query = new QuerySelection(where, whereArgs);
|
||||
query = query.add(queryAppSubQuery(uri.getLastPathSegment()));
|
||||
query = query.add(queryAppSubQuery(packageName));
|
||||
|
||||
return db().delete(getTableName(), query.getSelection(), query.getArgs());
|
||||
Utils.debugLog(TAG, "Deleting " + packageName);
|
||||
int count = db().delete(getTableName(), query.getSelection(), query.getArgs());
|
||||
|
||||
Utils.debugLog(TAG, "Requesting the suggested apk get recalculated for " + packageName);
|
||||
AppProvider.Helper.calcSuggestedApk(getContext(), packageName);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -234,15 +241,23 @@ public class InstalledAppProvider extends FDroidProvider {
|
||||
throw new UnsupportedOperationException("Insert not supported for " + uri + ".");
|
||||
}
|
||||
|
||||
if (values.containsKey(Cols.Package.NAME)) {
|
||||
String packageName = values.getAsString(Cols.Package.NAME);
|
||||
long packageId = PackageProvider.Helper.ensureExists(getContext(), packageName);
|
||||
values.remove(Cols.Package.NAME);
|
||||
values.put(Cols.PACKAGE_ID, packageId);
|
||||
if (!values.containsKey(Cols.Package.NAME)) {
|
||||
throw new IllegalStateException("Package name not provided to InstalledAppProvider");
|
||||
}
|
||||
|
||||
String packageName = values.getAsString(Cols.Package.NAME);
|
||||
long packageId = PackageProvider.Helper.ensureExists(getContext(), packageName);
|
||||
values.remove(Cols.Package.NAME);
|
||||
values.put(Cols.PACKAGE_ID, packageId);
|
||||
|
||||
verifyVersionNameNotNull(values);
|
||||
|
||||
Utils.debugLog(TAG, "Inserting/updating " + packageName);
|
||||
db().replaceOrThrow(getTableName(), null, values);
|
||||
|
||||
Utils.debugLog(TAG, "Requesting the suggested apk get recalculated for " + packageName);
|
||||
AppProvider.Helper.calcSuggestedApk(getContext(), packageName);
|
||||
|
||||
return getAppUri(values.getAsString(Cols.Package.NAME));
|
||||
}
|
||||
|
||||
|
@ -69,14 +69,21 @@ final class LoggingQuery {
|
||||
private void execSQLInternal() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
db.execSQL(query);
|
||||
long queryDuration = System.currentTimeMillis() - startTime;
|
||||
|
||||
executeSQLInternal();
|
||||
if (queryDuration >= SLOW_QUERY_DURATION) {
|
||||
logSlowQuery(queryDuration);
|
||||
}
|
||||
} else {
|
||||
executeSQLInternal();
|
||||
}
|
||||
}
|
||||
|
||||
private void executeSQLInternal() {
|
||||
if (queryArgs == null || queryArgs.length == 0) {
|
||||
db.execSQL(query);
|
||||
} else {
|
||||
db.execSQL(query, queryArgs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,7 +138,7 @@ final class LoggingQuery {
|
||||
return new LoggingQuery(db, query, queryBuilderArgs).rawQuery();
|
||||
}
|
||||
|
||||
public static void execSQL(SQLiteDatabase db, String sql) {
|
||||
new LoggingQuery(db, sql, null).execSQLInternal();
|
||||
public static void execSQL(SQLiteDatabase db, String sql, String[] queryArgs) {
|
||||
new LoggingQuery(db, sql, queryArgs).execSQLInternal();
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import android.content.ContextWrapper;
|
||||
import android.content.pm.ProviderInfo;
|
||||
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.data.RepoProviderTest;
|
||||
@ -157,4 +158,15 @@ public class TestUtils {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally apps/apks are only added to the database in response to a repo update.
|
||||
* At the end of a repo update, the {@link AppProvider} updates the suggested apks and
|
||||
* recalculates the preferred metadata for each app. Because we are adding apps/apks
|
||||
* directly to the database, we need to simulate this update after inserting stuff.
|
||||
*/
|
||||
public static void updateDbAfterInserting(Context context) {
|
||||
AppProvider.Helper.calcSuggestedApks(context);
|
||||
AppProvider.Helper.recalculatePreferredMetadata(context);
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,14 @@ public class PreferredSignatureTest extends FDroidProviderTest {
|
||||
public void setup() {
|
||||
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
|
||||
Preferences.setup(context);
|
||||
|
||||
// This is what the FDroidApp does when this preference is changed. Need to also do this under testing.
|
||||
Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
|
||||
@Override
|
||||
public void onPreferenceChange() {
|
||||
AppProvider.Helper.calcSuggestedApks(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@After
|
||||
@ -46,6 +54,8 @@ public class PreferredSignatureTest extends FDroidProviderTest {
|
||||
TestUtils.insertApk(context, app, 2100, TestUtils.UPSTREAM_SIG); // 2.0
|
||||
TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG); // 3.0
|
||||
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@ -70,6 +80,8 @@ public class PreferredSignatureTest extends FDroidProviderTest {
|
||||
TestUtils.insertApk(context, app, 5002, TestUtils.THIRD_PARTY_SIG); // 5.0-rc2
|
||||
TestUtils.insertApk(context, app, 5003, TestUtils.THIRD_PARTY_SIG); // 5.0-rc3
|
||||
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@ -84,6 +96,8 @@ public class PreferredSignatureTest extends FDroidProviderTest {
|
||||
TestUtils.insertApk(context, app, 3100, TestUtils.UPSTREAM_SIG);
|
||||
TestUtils.insertApk(context, app, 4100, TestUtils.UPSTREAM_SIG);
|
||||
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@ -265,9 +279,6 @@ public class PreferredSignatureTest extends FDroidProviderTest {
|
||||
}
|
||||
|
||||
private void assertSuggested(Context context, int suggestedVersion, String suggestedSig) {
|
||||
AppProvider.Helper.calcSuggestedApks(context);
|
||||
AppProvider.Helper.recalculatePreferredMetadata(context);
|
||||
|
||||
App suggestedApp = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), PACKAGE_NAME);
|
||||
assertEquals("Suggested version on App", suggestedVersion, suggestedApp.suggestedVersionCode);
|
||||
|
||||
|
@ -26,6 +26,14 @@ public class SuggestedVersionTest extends FDroidProviderTest {
|
||||
public void setup() {
|
||||
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
|
||||
Preferences.setup(context);
|
||||
|
||||
// This is what the FDroidApp does when this preference is changed. Need to also do this under testing.
|
||||
Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
|
||||
@Override
|
||||
public void onPreferenceChange() {
|
||||
AppProvider.Helper.calcSuggestedApks(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@After
|
||||
@ -40,6 +48,7 @@ public class SuggestedVersionTest extends FDroidProviderTest {
|
||||
TestUtils.insertApk(context, singleApp, 1, TestUtils.FDROID_SIG);
|
||||
TestUtils.insertApk(context, singleApp, 2, TestUtils.FDROID_SIG);
|
||||
TestUtils.insertApk(context, singleApp, 3, TestUtils.FDROID_SIG);
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
assertSuggested("single.app", 2);
|
||||
|
||||
// By enabling unstable updates, the "upstreamVersionCode" should get ignored, and we should
|
||||
@ -59,6 +68,7 @@ public class SuggestedVersionTest extends FDroidProviderTest {
|
||||
TestUtils.insertApk(context, singleApp, 3, TestUtils.FDROID_SIG);
|
||||
TestUtils.insertApk(context, singleApp, 4, TestUtils.UPSTREAM_SIG);
|
||||
TestUtils.insertApk(context, singleApp, 5, TestUtils.UPSTREAM_SIG);
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
|
||||
// Given we aren't installed yet, we don't care which signature.
|
||||
// Just get as close to upstreamVersionCode as possible.
|
||||
@ -72,6 +82,7 @@ public class SuggestedVersionTest extends FDroidProviderTest {
|
||||
// This adds the "upstreamVersionCode" version of the app, but signed by f-droid.
|
||||
TestUtils.insertApk(context, singleApp, 4, TestUtils.FDROID_SIG);
|
||||
TestUtils.insertApk(context, singleApp, 5, TestUtils.FDROID_SIG);
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
assertSuggested("single.app", 4, TestUtils.FDROID_SIG, 1);
|
||||
|
||||
// Version 5 from F-Droid is not the "upstreamVersionCode", but with beta updates it should
|
||||
@ -99,6 +110,7 @@ public class SuggestedVersionTest extends FDroidProviderTest {
|
||||
TestUtils.insertApk(context, thirdPartyApp, 4, TestUtils.THIRD_PARTY_SIG);
|
||||
TestUtils.insertApk(context, thirdPartyApp, 5, TestUtils.THIRD_PARTY_SIG);
|
||||
TestUtils.insertApk(context, thirdPartyApp, 6, TestUtils.THIRD_PARTY_SIG);
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
|
||||
// Given we aren't installed yet, we don't care which signature or even which repo.
|
||||
// Just get as close to upstreamVersionCode as possible.
|
||||
@ -112,6 +124,7 @@ public class SuggestedVersionTest extends FDroidProviderTest {
|
||||
// This adds the "upstreamVersionCode" version of the app, but signed by f-droid.
|
||||
TestUtils.insertApk(context, mainApp, 4, TestUtils.FDROID_SIG);
|
||||
TestUtils.insertApk(context, mainApp, 5, TestUtils.FDROID_SIG);
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
assertSuggested("single.app", 4, TestUtils.FDROID_SIG, 1);
|
||||
|
||||
// Uninstalling the F-Droid build and installing v3 of the third party means we can now go
|
||||
@ -146,6 +159,7 @@ public class SuggestedVersionTest extends FDroidProviderTest {
|
||||
TestUtils.insertApk(context, mainApp, 5, TestUtils.UPSTREAM_SIG);
|
||||
TestUtils.insertApk(context, mainApp, 6, TestUtils.UPSTREAM_SIG);
|
||||
TestUtils.insertApk(context, mainApp, 7, TestUtils.UPSTREAM_SIG);
|
||||
TestUtils.updateDbAfterInserting(context);
|
||||
|
||||
// If the user was to manually install the app, they should be suggested version 7 from upstream...
|
||||
assertSuggested("single.app", 7);
|
||||
@ -180,9 +194,6 @@ public class SuggestedVersionTest extends FDroidProviderTest {
|
||||
* apk is not checked.
|
||||
*/
|
||||
public void assertSuggested(String packageName, int suggestedVersion, String installedSig, int installedVersion) {
|
||||
AppProvider.Helper.calcSuggestedApks(context);
|
||||
AppProvider.Helper.recalculatePreferredMetadata(context);
|
||||
|
||||
App suggestedApp = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), packageName);
|
||||
assertEquals("Suggested version on App", suggestedVersion, suggestedApp.suggestedVersionCode);
|
||||
assertEquals("Installed signature on App", installedSig, suggestedApp.installedSig);
|
||||
|
Loading…
x
Reference in New Issue
Block a user