Merge branch 'fix-806--categories-remaining-when-they-shouldnt' into 'master'
Ensure categories are not shown unless there are apps in them **Note: To be hot fixed into 0.102.1 also when merged.** Fixes #806. Also adds tests to hopefully prevent this from regressing in the future. Ensure app-category join table is cleared out properly upon disabling repo. There are certain things we can leave in the database even when they are not being used. The criteria for this is: * Could it be used again in the future? * Can it be excluded from queries easily while it is unused? Examples are entries in the package table, and entries in the category table. This fixes a problem where entries in the category-app join table stayed in the database, causing categories to be considered as "in use" when really there were no apps in those categories. These rows need to be removed, because when new apps are added again in the future, they will have different primary keys. These different primary keys mean that the rows in the category-app table will never be useful again, and thus should be removed. See merge request !418
This commit is contained in:
commit
7f49c82b62
@ -369,7 +369,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
private static final String PATH_SEARCH_INSTALLED = "searchInstalled";
|
private static final String PATH_SEARCH_INSTALLED = "searchInstalled";
|
||||||
private static final String PATH_SEARCH_CAN_UPDATE = "searchCanUpdate";
|
private static final String PATH_SEARCH_CAN_UPDATE = "searchCanUpdate";
|
||||||
private static final String PATH_SEARCH_REPO = "searchRepo";
|
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_APPS = "apps";
|
||||||
protected static final String PATH_SPECIFIC_APP = "app";
|
protected static final String PATH_SPECIFIC_APP = "app";
|
||||||
private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
|
private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
|
||||||
@ -383,8 +382,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
private static final int CAN_UPDATE = CODE_SINGLE + 1;
|
private static final int CAN_UPDATE = CODE_SINGLE + 1;
|
||||||
private static final int INSTALLED = CAN_UPDATE + 1;
|
private static final int INSTALLED = CAN_UPDATE + 1;
|
||||||
private static final int SEARCH = INSTALLED + 1;
|
private static final int SEARCH = INSTALLED + 1;
|
||||||
private static final int NO_APKS = SEARCH + 1;
|
private static final int RECENTLY_UPDATED = SEARCH + 1;
|
||||||
private static final int RECENTLY_UPDATED = NO_APKS + 1;
|
|
||||||
private static final int NEWLY_ADDED = RECENTLY_UPDATED + 1;
|
private static final int NEWLY_ADDED = RECENTLY_UPDATED + 1;
|
||||||
private static final int CATEGORY = NEWLY_ADDED + 1;
|
private static final int CATEGORY = NEWLY_ADDED + 1;
|
||||||
private static final int CALC_SUGGESTED_APKS = CATEGORY + 1;
|
private static final int CALC_SUGGESTED_APKS = CATEGORY + 1;
|
||||||
@ -408,7 +406,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
MATCHER.addURI(getAuthority(), PATH_REPO + "/#", REPO);
|
MATCHER.addURI(getAuthority(), PATH_REPO + "/#", REPO);
|
||||||
MATCHER.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE);
|
MATCHER.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE);
|
||||||
MATCHER.addURI(getAuthority(), PATH_INSTALLED, INSTALLED);
|
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_HIGHEST_PRIORITY + "/*", HIGHEST_PRIORITY);
|
||||||
MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
|
MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
|
||||||
MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA);
|
MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA);
|
||||||
@ -437,10 +434,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri getNoApksUri() {
|
|
||||||
return Uri.withAppendedPath(getContentUri(), PATH_NO_APKS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getInstalledUri() {
|
public static Uri getInstalledUri() {
|
||||||
return Uri.withAppendedPath(getContentUri(), PATH_INSTALLED);
|
return Uri.withAppendedPath(getContentUri(), PATH_INSTALLED);
|
||||||
}
|
}
|
||||||
@ -562,7 +555,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AppQuerySelection queryRepo(long repoId) {
|
private AppQuerySelection queryRepo(long repoId) {
|
||||||
final String selection = getApkTableName() + "." + ApkTable.Cols.REPO_ID + " = ? ";
|
final String selection = getTableName() + "." + Cols.REPO_ID + " = ? ";
|
||||||
final String[] args = {String.valueOf(repoId)};
|
final String[] args = {String.valueOf(repoId)};
|
||||||
return new AppQuerySelection(selection, args);
|
return new AppQuerySelection(selection, args);
|
||||||
}
|
}
|
||||||
@ -688,13 +681,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return new AppQuerySelection(selection, args);
|
return new AppQuerySelection(selection, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppQuerySelection queryNoApks() {
|
|
||||||
final String apk = getApkTableName();
|
|
||||||
final String app = getTableName();
|
|
||||||
String selection = "(SELECT COUNT(*) FROM " + apk + " WHERE " + apk + "." + ApkTable.Cols.APP_ID + " = " + app + "." + Cols.ROW_ID + ") = 0";
|
|
||||||
return new AppQuerySelection(selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppQuerySelection queryPackageNames(String packageNames, String packageNameField) {
|
static AppQuerySelection queryPackageNames(String packageNames, String packageNameField) {
|
||||||
String[] args = packageNames.split(",");
|
String[] args = packageNames.split(",");
|
||||||
String selection = packageNameField + " IN (" + generateQuestionMarksForInClause(args.length) + ")";
|
String selection = packageNameField + " IN (" + generateQuestionMarksForInClause(args.length) + ")";
|
||||||
@ -770,10 +756,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
repoIsKnown = true;
|
repoIsKnown = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NO_APKS:
|
|
||||||
selection = selection.add(queryNoApks());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CATEGORY:
|
case CATEGORY:
|
||||||
selection = selection.add(queryCategory(uri.getLastPathSegment()));
|
selection = selection.add(queryCategory(uri.getLastPathSegment()));
|
||||||
includeSwap = false;
|
includeSwap = false;
|
||||||
@ -833,11 +815,19 @@ public class AppProvider extends FDroidProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int delete(Uri uri, String where, String[] whereArgs) {
|
public int delete(Uri uri, String where, String[] whereArgs) {
|
||||||
if (MATCHER.match(uri) != NO_APKS) {
|
if (MATCHER.match(uri) != REPO) {
|
||||||
throw new UnsupportedOperationException("Delete not supported for " + uri + ".");
|
throw new UnsupportedOperationException("Delete not supported for " + uri + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
AppQuerySelection selection = new AppQuerySelection(where, whereArgs).add(queryNoApks());
|
long repoId = Long.parseLong(uri.getLastPathSegment());
|
||||||
|
|
||||||
|
final String catJoin = getCatJoinTableName();
|
||||||
|
final String app = getTableName();
|
||||||
|
String query = "DELETE FROM " + catJoin + " WHERE " + CatJoinTable.Cols.APP_METADATA_ID + " IN " +
|
||||||
|
"(SELECT " + Cols.ROW_ID + " FROM " + app + " WHERE " + app + "." + Cols.REPO_ID + " = ?)";
|
||||||
|
db().execSQL(query, new String[] {String.valueOf(repoId)});
|
||||||
|
|
||||||
|
AppQuerySelection selection = new AppQuerySelection(where, whereArgs).add(queryRepo(repoId));
|
||||||
return db().delete(getTableName(), selection.getSelection(), selection.getArgs());
|
return db().delete(getTableName(), selection.getSelection(), selection.getArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,9 +229,9 @@ public class RepoProvider extends FDroidProvider {
|
|||||||
int apkCount = resolver.delete(apkUri, null, null);
|
int apkCount = resolver.delete(apkUri, null, null);
|
||||||
Utils.debugLog(TAG, "Removed " + apkCount + " apks from repo " + repo.name);
|
Utils.debugLog(TAG, "Removed " + apkCount + " apks from repo " + repo.name);
|
||||||
|
|
||||||
Uri appUri = AppProvider.getNoApksUri();
|
Uri appUri = AppProvider.getRepoUri(repo);
|
||||||
int appCount = resolver.delete(appUri, null, null);
|
int appCount = resolver.delete(appUri, null, null);
|
||||||
Utils.debugLog(TAG, "Removed " + appCount + " apps with no apks.");
|
Utils.debugLog(TAG, "Removed " + appCount + " apps from repo " + repo.address + ".");
|
||||||
|
|
||||||
AppProvider.Helper.recalculatePreferredMetadata(context);
|
AppProvider.Helper.recalculatePreferredMetadata(context);
|
||||||
}
|
}
|
||||||
|
@ -275,10 +275,14 @@ public class AppProviderTest extends FDroidProviderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static App insertApp(ShadowContentResolver contentResolver, Context context, String id, String name, ContentValues additionalValues) {
|
public static App insertApp(ShadowContentResolver contentResolver, Context context, String id, String name, ContentValues additionalValues) {
|
||||||
|
return insertApp(contentResolver, context, id, name, additionalValues, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static App insertApp(ShadowContentResolver contentResolver, Context context, String id, String name, ContentValues additionalValues, long repoId) {
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(Cols.Package.PACKAGE_NAME, id);
|
values.put(Cols.Package.PACKAGE_NAME, id);
|
||||||
values.put(Cols.REPO_ID, 1);
|
values.put(Cols.REPO_ID, repoId);
|
||||||
values.put(Cols.NAME, name);
|
values.put(Cols.NAME, name);
|
||||||
|
|
||||||
// Required fields (NOT NULL in the database).
|
// Required fields (NOT NULL in the database).
|
||||||
|
@ -8,6 +8,7 @@ import android.net.Uri;
|
|||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
|
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
|
||||||
|
import org.fdroid.fdroid.mock.MockRepo;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -107,9 +108,11 @@ public class CategoryProviderTest extends FDroidProviderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCategoriesMultiple() {
|
public void testCategoriesMultiple() {
|
||||||
insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal");
|
long mainRepo = 1;
|
||||||
insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable");
|
|
||||||
insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable");
|
insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal", mainRepo);
|
||||||
|
insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable", mainRepo);
|
||||||
|
insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable", mainRepo);
|
||||||
|
|
||||||
List<String> categories = CategoryProvider.Helper.categories(context);
|
List<String> categories = CategoryProvider.Helper.categories(context);
|
||||||
String[] expected = new String[] {
|
String[] expected = new String[] {
|
||||||
@ -123,9 +126,11 @@ public class CategoryProviderTest extends FDroidProviderTest {
|
|||||||
};
|
};
|
||||||
assertContainsOnly(categories, expected);
|
assertContainsOnly(categories, expected);
|
||||||
|
|
||||||
|
int additionalRepo = 2;
|
||||||
|
|
||||||
insertAppWithCategory("com.example.game", "Game",
|
insertAppWithCategory("com.example.game", "Game",
|
||||||
"Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," +
|
"Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," +
|
||||||
"The quick brown fox jumps over the lazy dog,With apostrophe's");
|
"The quick brown fox jumps over the lazy dog,With apostrophe's", additionalRepo);
|
||||||
|
|
||||||
List<String> categoriesLonger = CategoryProvider.Helper.categories(context);
|
List<String> categoriesLonger = CategoryProvider.Helper.categories(context);
|
||||||
String[] expectedLonger = new String[] {
|
String[] expectedLonger = new String[] {
|
||||||
@ -150,11 +155,19 @@ public class CategoryProviderTest extends FDroidProviderTest {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assertContainsOnly(categoriesLonger, expectedLonger);
|
assertContainsOnly(categoriesLonger, expectedLonger);
|
||||||
|
|
||||||
|
RepoProvider.Helper.purgeApps(context, new MockRepo(additionalRepo));
|
||||||
|
List<String> categoriesAfterPurge = CategoryProvider.Helper.categories(context);
|
||||||
|
assertContainsOnly(categoriesAfterPurge, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertAppWithCategory(String id, String name, String categories) {
|
private void insertAppWithCategory(String id, String name, String categories) {
|
||||||
|
insertAppWithCategory(id, name, categories, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertAppWithCategory(String id, String name, String categories, long repoId) {
|
||||||
ContentValues values = new ContentValues(1);
|
ContentValues values = new ContentValues(1);
|
||||||
values.put(Cols.ForWriting.Categories.CATEGORIES, categories);
|
values.put(Cols.ForWriting.Categories.CATEGORIES, categories);
|
||||||
AppProviderTest.insertApp(contentResolver, context, id, name, values);
|
AppProviderTest.insertApp(contentResolver, context, id, name, values, repoId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,6 @@ public class ProviderUriTests {
|
|||||||
assertValidUri(resolver, AppProvider.getSearchUri("/"), "content://org.fdroid.fdroid.data.AppProvider/search/%2F", projection);
|
assertValidUri(resolver, AppProvider.getSearchUri("/"), "content://org.fdroid.fdroid.data.AppProvider/search/%2F", projection);
|
||||||
assertValidUri(resolver, AppProvider.getSearchUri(""), "content://org.fdroid.fdroid.data.AppProvider", projection);
|
assertValidUri(resolver, AppProvider.getSearchUri(""), "content://org.fdroid.fdroid.data.AppProvider", projection);
|
||||||
assertValidUri(resolver, AppProvider.getSearchUri(null), "content://org.fdroid.fdroid.data.AppProvider", projection);
|
assertValidUri(resolver, AppProvider.getSearchUri(null), "content://org.fdroid.fdroid.data.AppProvider", projection);
|
||||||
assertValidUri(resolver, AppProvider.getNoApksUri(), "content://org.fdroid.fdroid.data.AppProvider/noApks", projection);
|
|
||||||
assertValidUri(resolver, AppProvider.getInstalledUri(), "content://org.fdroid.fdroid.data.AppProvider/installed", projection);
|
assertValidUri(resolver, AppProvider.getInstalledUri(), "content://org.fdroid.fdroid.data.AppProvider/installed", projection);
|
||||||
assertValidUri(resolver, AppProvider.getCanUpdateUri(), "content://org.fdroid.fdroid.data.AppProvider/canUpdate", projection);
|
assertValidUri(resolver, AppProvider.getCanUpdateUri(), "content://org.fdroid.fdroid.data.AppProvider/canUpdate", projection);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user