From a8d8e6569849b56e6b7251483edcfaa045521e16 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 20 Oct 2016 09:49:22 +1100 Subject: [PATCH] Added query for 'top X apps in category' and associated test --- .../org/fdroid/fdroid/data/AppProvider.java | 31 ++++++++++++++--- .../org/fdroid/fdroid/data/QueryBuilder.java | 11 +++++- .../fdroid/fdroid/data/TempAppProvider.java | 2 +- .../fdroid/data/CategoryProviderTest.java | 34 +++++++++++++++++++ 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 3b6972915..c8e2581c4 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -378,6 +378,7 @@ public class AppProvider extends FDroidProvider { private static final String PATH_HIGHEST_PRIORITY = "highestPriority"; private static final String PATH_CALC_PREFERRED_METADATA = "calcPreferredMetadata"; private static final String PATH_CALC_SUGGESTED_APKS = "calcNonRepoDetailsFromIndex"; + private static final String PATH_TOP_FROM_CATEGORY = "topFromCategory"; private static final int CAN_UPDATE = CODE_SINGLE + 1; private static final int INSTALLED = CAN_UPDATE + 1; @@ -392,6 +393,7 @@ public class AppProvider extends FDroidProvider { private static final int SEARCH_CAN_UPDATE = SEARCH_INSTALLED + 1; private static final int HIGHEST_PRIORITY = SEARCH_CAN_UPDATE + 1; private static final int CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 1; + private static final int TOP_FROM_CATEGORY = CALC_PREFERRED_METADATA + 1; static { MATCHER.addURI(getAuthority(), null, CODE_LIST); @@ -409,6 +411,7 @@ public class AppProvider extends FDroidProvider { MATCHER.addURI(getAuthority(), PATH_HIGHEST_PRIORITY + "/*", HIGHEST_PRIORITY); MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE); MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA); + MATCHER.addURI(getAuthority(), PATH_TOP_FROM_CATEGORY + "/#/*", TOP_FROM_CATEGORY); } public static Uri getContentUri() { @@ -429,9 +432,17 @@ public class AppProvider extends FDroidProvider { public static Uri getCategoryUri(String category) { return getContentUri().buildUpon() - .appendPath(PATH_CATEGORY) - .appendPath(category) - .build(); + .appendPath(PATH_CATEGORY) + .appendPath(category) + .build(); + } + + public static Uri getTopFromCategoryUri(String category, int limit) { + return getContentUri().buildUpon() + .appendPath(PATH_TOP_FROM_CATEGORY) + .appendPath(Integer.toString(limit)) + .appendPath(category) + .build(); } public static Uri getInstalledUri() { @@ -702,6 +713,8 @@ public class AppProvider extends FDroidProvider { // querying from. boolean repoIsKnown = false; + int limit = 0; + switch (MATCHER.match(uri)) { case CALC_PREFERRED_METADATA: updatePreferredMetadata(); @@ -761,6 +774,13 @@ public class AppProvider extends FDroidProvider { includeSwap = false; break; + case TOP_FROM_CATEGORY: + List parts = uri.getPathSegments(); + selection = selection.add(queryCategory(parts.get(2))); + limit = Integer.parseInt(parts.get(1)); + includeSwap = false; + break; + case RECENTLY_UPDATED: sortOrder = getTableName() + "." + Cols.LAST_UPDATED + " DESC"; selection = selection.add(queryRecentlyUpdated()); @@ -787,14 +807,14 @@ public class AppProvider extends FDroidProvider { selection = selection.add(queryHighestPriority()); } - return runQuery(uri, selection, projection, includeSwap, sortOrder); + return runQuery(uri, selection, projection, includeSwap, sortOrder, limit); } /** * Helper method used by both the genuine {@link AppProvider} and the temporary version used * by the repo updater ({@link TempAppProvider}). */ - protected Cursor runQuery(Uri uri, AppQuerySelection selection, String[] projection, boolean includeSwap, String sortOrder) { + protected Cursor runQuery(Uri uri, AppQuerySelection selection, String[] projection, boolean includeSwap, String sortOrder, int limit) { if (!includeSwap) { selection = selection.add(queryExcludeSwap()); } @@ -807,6 +827,7 @@ public class AppProvider extends FDroidProvider { query.addSelection(selection); query.addFields(projection); // TODO: Make the order of addFields/addSelection not dependent on each other... query.addOrderBy(sortOrder); + query.addLimit(limit); Cursor cursor = LoggingQuery.query(db(), query.toString(), query.getArgs()); cursor.setNotificationUri(getContext().getContentResolver(), uri); diff --git a/app/src/main/java/org/fdroid/fdroid/data/QueryBuilder.java b/app/src/main/java/org/fdroid/fdroid/data/QueryBuilder.java index 86f664fe9..25da47389 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/QueryBuilder.java +++ b/app/src/main/java/org/fdroid/fdroid/data/QueryBuilder.java @@ -14,6 +14,7 @@ abstract class QueryBuilder { private String selection; private String[] selectionArgs; private final List orderBys = new ArrayList<>(); + private int limit = 0; protected abstract String getRequiredTables(); @@ -88,6 +89,10 @@ abstract class QueryBuilder { } } + public void addLimit(int limit) { + this.limit = limit; + } + public String[] getArgs() { List args = new ArrayList<>(); @@ -156,7 +161,11 @@ abstract class QueryBuilder { return tables.toString(); } + private String limitSql() { + return limit > 0 ? " LIMIT " + limit : ""; + } + public String toString() { - return "SELECT " + distinctSql() + fieldsSql() + " FROM " + tablesSql() + whereSql() + groupBySql() + orderBySql(); + return "SELECT " + distinctSql() + fieldsSql() + " FROM " + tablesSql() + whereSql() + groupBySql() + orderBySql() + limitSql(); } } diff --git a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java index 910de6205..a1a339bda 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/TempAppProvider.java @@ -196,7 +196,7 @@ public class TempAppProvider extends AppProvider { break; } - return super.runQuery(uri, selection, projection, true, sortOrder); + return super.runQuery(uri, selection, projection, true, sortOrder, 0); } private void ensureTempTableDetached(SQLiteDatabase db) { diff --git a/app/src/test/java/org/fdroid/fdroid/data/CategoryProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/CategoryProviderTest.java index f093a7c89..1befeab9d 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/CategoryProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/CategoryProviderTest.java @@ -88,6 +88,40 @@ public class CategoryProviderTest extends FDroidProviderTest { AppProviderTest.assertContainsOnlyIds(apps, expectedPackages); } + @Test + public void topAppsFromCategory() { + insertAppWithCategory("com.dog", "Dog", "Animal"); + insertAppWithCategory("com.cat", "Cat", "Animal"); + insertAppWithCategory("com.bird", "Bird", "Animal"); + insertAppWithCategory("com.snake", "Snake", "Animal"); + insertAppWithCategory("com.rat", "Rat", "Animal"); + + insertAppWithCategory("com.rock", "Rock", "Mineral"); + insertAppWithCategory("com.stone", "Stone", "Mineral"); + insertAppWithCategory("com.boulder", "Boulder", "Mineral"); + + insertAppWithCategory("com.banana", "Banana", "Vegetable"); + insertAppWithCategory("com.tomato", "Tomato", "Vegetable"); + + assertContainsOnly(topAppsFromCategory("Animal", 3), new String[] {"com.bird", "com.cat", "com.dog", }); + assertContainsOnly(topAppsFromCategory("Animal", 2), new String[] {"com.bird", "com.cat", }); + assertContainsOnly(topAppsFromCategory("Animal", 1), new String[] {"com.bird", }); + + assertContainsOnly(topAppsFromCategory("Mineral", 2), new String[] {"com.boulder", "com.rock", }); + + assertContainsOnly(topAppsFromCategory("Vegetable", 10), new String[] {"com.banana", "com.tomato", }); + } + + public String[] topAppsFromCategory(String category, int numToGet) { + List apps = AppProvider.Helper.cursorToList(contentResolver.query(AppProvider.getTopFromCategoryUri(category, numToGet), Cols.ALL, null, null, Cols.NAME)); + String[] packageNames = new String[apps.size()]; + for (int i = 0; i < apps.size(); i++) { + packageNames[i] = apps.get(i).packageName; + } + + return packageNames; + } + @Test public void testCategoriesSingle() { insertAppWithCategory("com.dog", "Dog", "Animal");