Clarify some of the database stuff around database providers.

Includes:
 * One of the functions querying for apps did not correctly specify
   the repository the repos came from.
 * Fix deletion code which refered to incorrect field.
 * Cleanup code style in some places.
This commit is contained in:
Peter Serwylo 2016-10-06 01:44:08 +11:00
parent f2a58ad67f
commit 01b8f7f4bf
12 changed files with 100 additions and 45 deletions

View File

@ -424,7 +424,6 @@ public class ApkProvider extends FDroidProvider {
"You tried to query " + apkDetails.length);
}
String alias = includeAlias ? "apk." : "";
String metadataAlias = includeAlias ? "app." : "";
final String[] args = new String[apkDetails.length * 2];
StringBuilder sb = new StringBuilder();
for (int i = 0; i < apkDetails.length; i++) {
@ -436,12 +435,24 @@ public class ApkProvider extends FDroidProvider {
if (i != 0) {
sb.append(" OR ");
}
sb.append(" ( ")
.append(metadataAlias)
.append(AppMetadataTable.Cols.PACKAGE_ID)
.append(" = (")
.append(getPackageIdFromPackageNameQuery())
.append(") AND ")
sb.append(" ( ");
if (includeAlias) {
// This is the simpler way to figure out the package name of a row in the apk table.
// It requires slightly less work for sqlite3 than the alternative below.
sb.append("app.")
.append(AppMetadataTable.Cols.PACKAGE_ID)
.append(" = (")
.append(getPackageIdFromPackageNameQuery())
.append(") ");
} else {
sb.append(Cols.APP_ID)
.append(" IN (")
.append(getMetadataIdFromPackageNameQuery())
.append(")");
}
sb.append(" AND ")
.append(alias)
.append(Cols.VERSION_CODE)
.append(" = ? ) ");

View File

@ -155,6 +155,10 @@ public class AppProvider extends FDroidProvider {
return cursorToApp(resolver.query(uri, projection, null, null, null));
}
public static App findSpecificApp(ContentResolver resolver, String packageName, long repoId) {
return findSpecificApp(resolver, packageName, repoId, Cols.ALL);
}
private static App cursorToApp(Cursor cursor) {
App app = null;
if (cursor != null) {
@ -434,7 +438,7 @@ public class AppProvider extends FDroidProvider {
private static final String PATH_SEARCH_REPO = "searchRepo";
private static final String PATH_NO_APKS = "noApks";
protected static final String PATH_APPS = "apps";
protected static final String PATH_APP = "app";
protected static final String PATH_SPECIFIC_APP = "app";
private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
private static final String PATH_NEWLY_ADDED = "newlyAdded";
private static final String PATH_CATEGORY = "category";
@ -471,7 +475,7 @@ public class AppProvider extends FDroidProvider {
MATCHER.addURI(getAuthority(), PATH_INSTALLED, INSTALLED);
MATCHER.addURI(getAuthority(), PATH_NO_APKS, NO_APKS);
MATCHER.addURI(getAuthority(), PATH_HIGHEST_PRIORITY + "/*", HIGHEST_PRIORITY);
MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE);
MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
}
public static Uri getContentUri() {
@ -528,7 +532,7 @@ public class AppProvider extends FDroidProvider {
public static Uri getSpecificAppUri(String packageName, long repoId) {
return getContentUri()
.buildUpon()
.appendPath(PATH_APP)
.appendPath(PATH_SPECIFIC_APP)
.appendPath(Long.toString(repoId))
.appendPath(packageName)
.build();

View File

@ -158,7 +158,11 @@ public abstract class FDroidProvider extends ContentProvider {
}
}
protected String getPackageIdFromPackageNameQuery() {
/**
* Helper function to be used when you need to know the primary key from the package table
* when all you have is the package name.
*/
protected static String getPackageIdFromPackageNameQuery() {
return "SELECT " + Schema.PackageTable.Cols.ROW_ID + " FROM " + Schema.PackageTable.NAME + " WHERE " + Schema.PackageTable.Cols.PACKAGE_NAME + " = ?";
}
}

View File

@ -5,15 +5,12 @@ import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import org.fdroid.fdroid.data.Schema.PackageTable;
import org.fdroid.fdroid.data.Schema.PackageTable.Cols;
public class PackageProvider extends FDroidProvider {
private static final String TAG = "PackageProvider";
public static final class Helper {
private Helper() { }
@ -126,18 +123,13 @@ public class PackageProvider extends FDroidProvider {
@Override
public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) {
QuerySelection selection = new QuerySelection(customSelection, selectionArgs);
switch (MATCHER.match(uri)) {
case CODE_SINGLE:
selection = selection.add(querySingle(uri.getLastPathSegment()));
break;
default:
Log.e(TAG, "Invalid URI for content provider: " + uri);
throw new UnsupportedOperationException("Invalid URI for content provider: " + uri);
if (MATCHER.match(uri) != CODE_SINGLE) {
throw new UnsupportedOperationException("Invalid URI for content provider: " + uri);
}
QuerySelection selection = new QuerySelection(customSelection, selectionArgs)
.add(querySingle(uri.getLastPathSegment()));
Query query = new Query();
query.addSelection(selection);
query.addFields(projection);
@ -152,7 +144,7 @@ public class PackageProvider extends FDroidProvider {
* Deleting of packages is not required.
* It doesn't matter if we have a package name in the database after the package is no longer
* present in the repo any more. They wont take up much space, and it is the presence of rows
* in the {@link Schema.MetadataTable} which decides whether something is available in the
* in the {@link Schema.AppMetadataTable} which decides whether something is available in the
* F-Droid client or not.
*/
@Override
@ -167,6 +159,10 @@ public class PackageProvider extends FDroidProvider {
return getPackageIdUri(rowId);
}
/**
* Package names never change. If a package name has changed, then that means that it is a
* new app all together as far as Android is concerned.
*/
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
throw new UnsupportedOperationException("Update not supported for " + uri + ".");

View File

@ -144,7 +144,7 @@ public class RepoPersister {
packageNames.add(app.packageName);
}
String[] projection = {Schema.AppMetadataTable.Cols.ROW_ID, Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME};
List<App> fromDb = TempAppProvider.Helper.findByPackageNames(context, packageNames, projection);
List<App> fromDb = TempAppProvider.Helper.findByPackageNames(context, packageNames, repo.id, projection);
Map<String, Long> ids = new HashMap<>(fromDb.size());
for (App app : fromDb) {
@ -205,7 +205,7 @@ public class RepoPersister {
* <strong>Does not do any checks to see if the app already exists or not.</strong>
*/
private ContentProviderOperation updateExistingApp(App app) {
Uri uri = TempAppProvider.getAppUri(app.packageName, app.repoId);
Uri uri = TempAppProvider.getSpecificTempAppUri(app.packageName, app.repoId);
return ContentProviderOperation.newUpdate(uri).withValues(app.toContentValues()).build();
}

View File

@ -13,6 +13,7 @@ import java.util.List;
import org.fdroid.fdroid.data.Schema.ApkTable;
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.fdroid.fdroid.data.Schema.PackageTable;
/**
@ -41,8 +42,8 @@ public class TempAppProvider extends AppProvider {
static {
MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT);
MATCHER.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT);
MATCHER.addURI(getAuthority(), PATH_APPS + "/*", APPS);
MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE);
MATCHER.addURI(getAuthority(), PATH_APPS + "/#/*", APPS);
MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
}
@Override
@ -58,24 +59,36 @@ public class TempAppProvider extends AppProvider {
return Uri.parse("content://" + getAuthority());
}
public static Uri getAppUri(String packageName, long repoId) {
/**
* Same as {@link AppProvider#getSpecificAppUri(String, long)}, except loads data from the temp
* table being used during a repo update rather than the persistent table.
*/
public static Uri getSpecificTempAppUri(String packageName, long repoId) {
return getContentUri()
.buildUpon()
.appendPath(PATH_APP)
.appendPath(PATH_SPECIFIC_APP)
.appendPath(Long.toString(repoId))
.appendPath(packageName)
.build();
}
public static Uri getAppsUri(List<String> apps) {
public static Uri getAppsUri(List<String> apps, long repoId) {
return getContentUri().buildUpon()
.appendPath(PATH_APPS)
.appendPath(Long.toString(repoId))
.appendPath(TextUtils.join(",", apps))
.build();
}
private AppQuerySelection queryApps(String packageNames) {
return queryPackageNames(packageNames, PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
private AppQuerySelection queryRepoApps(long repoId, String packageNames) {
return queryPackageNames(packageNames, PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME)
.add(queryRepo(repoId));
}
private AppQuerySelection queryRepo(long repoId) {
String[] args = new String[] {Long.toString(repoId)};
String selection = getTableName() + "." + Cols.REPO_ID + " = ? ";
return new AppQuerySelection(selection, args);
}
public static class Helper {
@ -90,8 +103,8 @@ public class TempAppProvider extends AppProvider {
TempApkProvider.Helper.init(context);
}
public static List<App> findByPackageNames(Context context, List<String> packageNames, String[] projection) {
Uri uri = getAppsUri(packageNames);
public static List<App> findByPackageNames(Context context, List<String> packageNames, long repoId, String[] projection) {
Uri uri = getAppsUri(packageNames, repoId);
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
return AppProvider.Helper.cursorToList(cursor);
}
@ -138,7 +151,7 @@ public class TempAppProvider extends AppProvider {
QuerySelection query = new QuerySelection(where, whereArgs).add(querySingleForUpdate(packageName, repoId));
// Package names for apps cannot change...
values.remove(AppMetadataTable.Cols.Package.PACKAGE_NAME);
values.remove(Cols.Package.PACKAGE_NAME);
int count = db().update(getTableName(), values, query.getSelection(), query.getArgs());
if (!isApplyingBatch()) {
@ -152,7 +165,8 @@ public class TempAppProvider extends AppProvider {
AppQuerySelection selection = new AppQuerySelection(customSelection, selectionArgs);
switch (MATCHER.match(uri)) {
case APPS:
selection = selection.add(queryApps(uri.getLastPathSegment()));
List<String> segments = uri.getPathSegments();
selection = selection.add(queryRepoApps(Long.parseLong(segments.get(1)), segments.get(2)));
break;
}

View File

@ -24,6 +24,7 @@ import org.fdroid.fdroid.compat.PackageManagerCompat;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.DownloaderService;
@ -259,7 +260,7 @@ public class InstallManagerService extends Service {
App app = getAppFromActive(downloadUrl);
if (app == null) {
ContentResolver resolver = context.getContentResolver();
app = AppProvider.Helper.findByPackageName(resolver, apk.packageName, apk.repo);
app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repo);
}
// show notification if app details is not visible
if (app != null && AppDetails.isAppVisible(app.packageName)) {
@ -346,7 +347,8 @@ public class InstallManagerService extends Service {
String name = getAppName(apk);
if (TextUtils.isEmpty(name) || name.equals(new App().name)) {
ContentResolver resolver = getContentResolver();
App app = AppProvider.Helper.findByPackageName(resolver, apk.packageName, apk.repo);
App app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repo,
new String[] {Schema.AppMetadataTable.Cols.NAME});
if (app == null || TextUtils.isEmpty(app.name)) {
return; // do not have a name to display, so leave notification as is
}

View File

@ -191,7 +191,7 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel
intent = getIntent();
Uri uri = intent.getData();
Apk apk = ApkProvider.Helper.findByUri(this, uri, Schema.ApkTable.Cols.ALL);
app = AppProvider.Helper.findByPackageName(getContentResolver(), apk.packageName, apk.repo, Schema.AppMetadataTable.Cols.ALL);
app = AppProvider.Helper.findSpecificApp(getContentResolver(), apk.packageName, apk.repo, Schema.AppMetadataTable.Cols.ALL);
appDiff = new AppDiff(getPackageManager(), apk);

View File

@ -289,7 +289,7 @@ public class SwapAppsView extends ListView implements
public void onChange(boolean selfChange) {
Activity activity = getActivity();
if (activity != null) {
app = AppProvider.Helper.findByPackageName(getActivity().getContentResolver(),
app = AppProvider.Helper.findSpecificApp(getActivity().getContentResolver(),
app.packageName, app.repoId, AppMetadataTable.Cols.ALL);
resetView();
}

View File

@ -112,7 +112,7 @@ public class ProviderUriTests {
packageNames.add("org.fdroid.fdroid");
packageNames.add("com.example.com");
assertValidUri(resolver, TempAppProvider.getAppsUri(packageNames), "content://org.fdroid.fdroid.data.TempAppProvider/apps/org.fdroid.fdroid%2Ccom.example.com", projection);
assertValidUri(resolver, TempAppProvider.getAppsUri(packageNames, 1), "content://org.fdroid.fdroid.data.TempAppProvider/apps/1/org.fdroid.fdroid%2Ccom.example.com", projection);
assertValidUri(resolver, TempAppProvider.getContentUri(), "content://org.fdroid.fdroid.data.TempAppProvider", projection);
}

View File

@ -9,7 +9,7 @@ import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.RepoUpdater.UpdateException;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.data.Schema.RepoTable.Cols;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
@ -147,7 +147,7 @@ public class AcceptableMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
private void disableRepo(Repo repo) {
ContentValues values = new ContentValues(1);
values.put(Schema.RepoTable.Cols.IN_USE, 0);
values.put(Cols.IN_USE, 0);
RepoProvider.Helper.update(context, repo, values);
}

View File

@ -40,6 +40,30 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
@StringDef({"Conflicting", "Normal"})
public @interface RepoIdentifier {}
/*
*This test fails due to issue #568 (https://gitlab.com/fdroid/fdroidclient/issues/568).
@Test
public void appsRemovedFromRepo() throws RepoUpdater.UpdateException {
assertEquals(0, AppProvider.Helper.all(context.getContentResolver()).size());
updateMain();
Repo repo = RepoProvider.Helper.findByAddress(context, REPO_MAIN_URI);
assertEquals(3, AppProvider.Helper.all(context.getContentResolver()).size());
assertEquals(6, ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL).size());
assertEquals(3, ApkProvider.Helper.findByPackageName(context, "org.adaway").size());
assertEquals(2, ApkProvider.Helper.findByPackageName(context, "com.uberspot.a2048").size());
assertEquals(1, ApkProvider.Helper.findByPackageName(context, "siir.es.adbWireless").size());
RepoUpdater updater = new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address));
updateRepo(updater, "multiRepo.conflicting.jar");
assertEquals(2, AppProvider.Helper.all(context.getContentResolver()).size());
assertEquals(6, ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL).size());
assertEquals(4, ApkProvider.Helper.findByPackageName(context, "org.adaway").size());
assertEquals(2, ApkProvider.Helper.findByPackageName(context, "org.dgtale.icsimport").size());
}*/
@Test
public void mainRepo() throws RepoUpdater.UpdateException {
assertEmpty();