Change join from app
to apk
table to auto increment integers rather than Android package name strings.
This is important for the ability to refactor the database for better performance in the future. See #511 for details. For those interested in the details, here is a query plan of selecting the "All" category of apps before this commit: * `SCAN TABLE fdroid_app USING INDEX app_id` * `SEARCH TABLE fdroid_apk USING INDEX apk_id (id=?)` * `SEARCH TABLE fdroid_repo USING INTEGER PRIMARY KEY (rowid=?)` * `SEARCH TABLE fdroid_installedApp AS installed USING INDEX sqlite_autoindex_fdroid_installedApp_1 (appId=?)` * `SEARCH TABLE fdroid_apk AS suggestedApk USING INDEX sqlite_autoindex_fdroid_apk_1 (id=? AND vercode=?)` * `USE TEMP B-TREE FOR ORDER BY` And here is a query plan of afterwards: * `SCAN TABLE fdroid_app` * `SEARCH TABLE fdroid_apk USING INDEX apk_appId (appId=?)` * `SEARCH TABLE fdroid_repo USING INTEGER PRIMARY KEY (rowid=?)` * `SEARCH TABLE fdroid_installedApp AS installed USING INDEX sqlite_autoindex_fdroid_installedApp_1 (appId=?)` * `SEARCH TABLE fdroid_apk AS suggestedApk USING INDEX apk_appId (appId=?)` * `USE TEMP B-TREE FOR ORDER BY` The things of note are: * `SCAN TABLE` doesn't use an index, which means that it is really using the rowid index. Shouldn't behave much differently. * The second item now uses an integer primary key index rather than a package name index. Should increase search speed marginally which was the goal of this commit. As more apks exist, the speed improvement will also increase.
This commit is contained in:
parent
30334c3714
commit
2733081b3a
@ -61,6 +61,11 @@ public class Apk extends ValueObject implements Comparable<Apk> {
|
|||||||
public String repoAddress;
|
public String repoAddress;
|
||||||
public String[] incompatibleReasons;
|
public String[] incompatibleReasons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The numeric primary key of the App table, which is used to join apks.
|
||||||
|
*/
|
||||||
|
public long appId;
|
||||||
|
|
||||||
public Apk() {
|
public Apk() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +79,9 @@ public class Apk extends ValueObject implements Comparable<Apk> {
|
|||||||
|
|
||||||
for (int i = 0; i < cursor.getColumnCount(); i++) {
|
for (int i = 0; i < cursor.getColumnCount(); i++) {
|
||||||
switch (cursor.getColumnName(i)) {
|
switch (cursor.getColumnName(i)) {
|
||||||
|
case Cols.APP_ID:
|
||||||
|
appId = cursor.getLong(i);
|
||||||
|
break;
|
||||||
case Cols.HASH:
|
case Cols.HASH:
|
||||||
hash = cursor.getString(i);
|
hash = cursor.getString(i);
|
||||||
break;
|
break;
|
||||||
@ -131,10 +139,10 @@ public class Apk extends ValueObject implements Comparable<Apk> {
|
|||||||
case Cols.VERSION_CODE:
|
case Cols.VERSION_CODE:
|
||||||
versionCode = cursor.getInt(i);
|
versionCode = cursor.getInt(i);
|
||||||
break;
|
break;
|
||||||
case Cols.REPO_VERSION:
|
case Cols.Repo.VERSION:
|
||||||
repoVersion = cursor.getInt(i);
|
repoVersion = cursor.getInt(i);
|
||||||
break;
|
break;
|
||||||
case Cols.REPO_ADDRESS:
|
case Cols.Repo.ADDRESS:
|
||||||
repoAddress = cursor.getString(i);
|
repoAddress = cursor.getString(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -192,6 +200,7 @@ public class Apk extends ValueObject implements Comparable<Apk> {
|
|||||||
|
|
||||||
public ContentValues toContentValues() {
|
public ContentValues toContentValues() {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(Cols.APP_ID, appId);
|
||||||
values.put(Cols.PACKAGE_NAME, packageName);
|
values.put(Cols.PACKAGE_NAME, packageName);
|
||||||
values.put(Cols.VERSION_NAME, versionName);
|
values.put(Cols.VERSION_NAME, versionName);
|
||||||
values.put(Cols.VERSION_CODE, versionCode);
|
values.put(Cols.VERSION_CODE, versionCode);
|
||||||
|
@ -11,6 +11,7 @@ import android.util.Log;
|
|||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
|
import org.fdroid.fdroid.data.Schema.ApkTable;
|
||||||
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
|
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
|
||||||
|
import org.fdroid.fdroid.data.Schema.AppTable;
|
||||||
import org.fdroid.fdroid.data.Schema.RepoTable;
|
import org.fdroid.fdroid.data.Schema.RepoTable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -135,7 +136,7 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
String packageName, String[] projection) {
|
String packageName, String[] projection) {
|
||||||
ContentResolver resolver = context.getContentResolver();
|
ContentResolver resolver = context.getContentResolver();
|
||||||
final Uri uri = getAppUri(packageName);
|
final Uri uri = getAppUri(packageName);
|
||||||
final String sort = Cols.VERSION_CODE + " DESC";
|
final String sort = "apk." + Cols.VERSION_CODE + " DESC";
|
||||||
Cursor cursor = resolver.query(uri, projection, null, null, sort);
|
Cursor cursor = resolver.query(uri, projection, null, null, sort);
|
||||||
return cursorToList(cursor);
|
return cursorToList(cursor);
|
||||||
}
|
}
|
||||||
@ -213,10 +214,12 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
private static final UriMatcher MATCHER = new UriMatcher(-1);
|
private static final UriMatcher MATCHER = new UriMatcher(-1);
|
||||||
|
|
||||||
private static final Map<String, String> REPO_FIELDS = new HashMap<>();
|
private static final Map<String, String> REPO_FIELDS = new HashMap<>();
|
||||||
|
private static final Map<String, String> APP_FIELDS = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
REPO_FIELDS.put(Cols.REPO_VERSION, RepoTable.Cols.VERSION);
|
REPO_FIELDS.put(Cols.Repo.VERSION, RepoTable.Cols.VERSION);
|
||||||
REPO_FIELDS.put(Cols.REPO_ADDRESS, RepoTable.Cols.ADDRESS);
|
REPO_FIELDS.put(Cols.Repo.ADDRESS, RepoTable.Cols.ADDRESS);
|
||||||
|
APP_FIELDS.put(Cols.App.PACKAGE_NAME, AppTable.Cols.PACKAGE_NAME);
|
||||||
|
|
||||||
MATCHER.addURI(getAuthority(), PATH_REPO + "/#", CODE_REPO);
|
MATCHER.addURI(getAuthority(), PATH_REPO + "/#", CODE_REPO);
|
||||||
MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE);
|
MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE);
|
||||||
@ -323,6 +326,10 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
return ApkTable.NAME;
|
return ApkTable.NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getAppTableName() {
|
||||||
|
return AppTable.NAME;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getProviderName() {
|
protected String getProviderName() {
|
||||||
return PROVIDER_NAME;
|
return PROVIDER_NAME;
|
||||||
@ -333,30 +340,40 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
return MATCHER;
|
return MATCHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Query extends QueryBuilder {
|
private class Query extends QueryBuilder {
|
||||||
|
|
||||||
private boolean repoTableRequired;
|
private boolean repoTableRequired;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getRequiredTables() {
|
protected String getRequiredTables() {
|
||||||
return ApkTable.NAME + " AS apk";
|
final String apk = getTableName();
|
||||||
|
final String app = getAppTableName();
|
||||||
|
|
||||||
|
return apk + " AS apk " +
|
||||||
|
" LEFT JOIN " + app + " AS app ON (app." + AppTable.Cols.ROW_ID + " = apk." + Cols.APP_ID + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addField(String field) {
|
public void addField(String field) {
|
||||||
if (REPO_FIELDS.containsKey(field)) {
|
if (APP_FIELDS.containsKey(field)) {
|
||||||
|
addAppField(APP_FIELDS.get(field), field);
|
||||||
|
} else if (REPO_FIELDS.containsKey(field)) {
|
||||||
addRepoField(REPO_FIELDS.get(field), field);
|
addRepoField(REPO_FIELDS.get(field), field);
|
||||||
} else if (field.equals(Cols._ID)) {
|
} else if (field.equals(Cols._ID)) {
|
||||||
appendField("rowid", "apk", "_id");
|
appendField("rowid", "apk", "_id");
|
||||||
} else if (field.equals(Cols._COUNT)) {
|
} else if (field.equals(Cols._COUNT)) {
|
||||||
appendField("COUNT(*) AS " + Cols._COUNT);
|
appendField("COUNT(*) AS " + Cols._COUNT);
|
||||||
} else if (field.equals(Cols._COUNT_DISTINCT)) {
|
} else if (field.equals(Cols._COUNT_DISTINCT)) {
|
||||||
appendField("COUNT(DISTINCT apk." + Cols.PACKAGE_NAME + ") AS " + Cols._COUNT_DISTINCT);
|
appendField("COUNT(DISTINCT apk." + Cols.APP_ID + ") AS " + Cols._COUNT_DISTINCT);
|
||||||
} else {
|
} else {
|
||||||
appendField(field, "apk");
|
appendField(field, "apk");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addAppField(String field, String alias) {
|
||||||
|
appendField(field, "app", alias);
|
||||||
|
}
|
||||||
|
|
||||||
private void addRepoField(String field, String alias) {
|
private void addRepoField(String field, String alias) {
|
||||||
if (!repoTableRequired) {
|
if (!repoTableRequired) {
|
||||||
repoTableRequired = true;
|
repoTableRequired = true;
|
||||||
@ -368,13 +385,23 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private QuerySelection queryApp(String packageName) {
|
private QuerySelection queryApp(String packageName) {
|
||||||
final String selection = Cols.PACKAGE_NAME + " = ? ";
|
return queryApp(packageName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuerySelection queryApp(String packageName, boolean includeTableAlias) {
|
||||||
|
String alias = includeTableAlias ? "apk." : "";
|
||||||
|
final String selection = alias + Cols.APP_ID + " = (" + getAppIdFromPackageNameQuery() + ")";
|
||||||
final String[] args = {packageName};
|
final String[] args = {packageName};
|
||||||
return new QuerySelection(selection, args);
|
return new QuerySelection(selection, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QuerySelection querySingle(Uri uri) {
|
private QuerySelection querySingle(Uri uri) {
|
||||||
final String selection = Cols.VERSION_CODE + " = ? and " + Cols.PACKAGE_NAME + " = ? ";
|
return querySingle(uri, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuerySelection querySingle(Uri uri, boolean includeAlias) {
|
||||||
|
String alias = includeAlias ? "apk." : "";
|
||||||
|
final String selection = " " + alias + Cols.VERSION_CODE + " = ? and " + alias + Cols.PACKAGE_NAME + " = ? ";
|
||||||
final String[] args = {
|
final String[] args = {
|
||||||
// First (0th) path segment is the word "apk",
|
// First (0th) path segment is the word "apk",
|
||||||
// and we are not interested in it.
|
// and we are not interested in it.
|
||||||
@ -385,22 +412,32 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected QuerySelection queryRepo(long repoId) {
|
protected QuerySelection queryRepo(long repoId) {
|
||||||
final String selection = Cols.REPO_ID + " = ? ";
|
return queryRepo(repoId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected QuerySelection queryRepo(long repoId, boolean includeAlias) {
|
||||||
|
String alias = includeAlias ? "apk." : "";
|
||||||
|
final String selection = alias + Cols.REPO_ID + " = ? ";
|
||||||
final String[] args = {Long.toString(repoId)};
|
final String[] args = {Long.toString(repoId)};
|
||||||
return new QuerySelection(selection, args);
|
return new QuerySelection(selection, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QuerySelection queryRepoApps(long repoId, String packageNames) {
|
private QuerySelection queryRepoApps(long repoId, String packageNames) {
|
||||||
return queryRepo(repoId).add(AppProvider.queryApps(packageNames, Cols.PACKAGE_NAME));
|
return queryRepo(repoId).add(AppProvider.queryApps(packageNames, "app." + AppTable.Cols.PACKAGE_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected QuerySelection queryApks(String apkKeys) {
|
protected QuerySelection queryApks(String apkKeys) {
|
||||||
|
return queryApks(apkKeys, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected QuerySelection queryApks(String apkKeys, boolean includeAlias) {
|
||||||
final String[] apkDetails = apkKeys.split(",");
|
final String[] apkDetails = apkKeys.split(",");
|
||||||
if (apkDetails.length > MAX_APKS_TO_QUERY) {
|
if (apkDetails.length > MAX_APKS_TO_QUERY) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Cannot query more than " + MAX_APKS_TO_QUERY + ". " +
|
"Cannot query more than " + MAX_APKS_TO_QUERY + ". " +
|
||||||
"You tried to query " + apkDetails.length);
|
"You tried to query " + apkDetails.length);
|
||||||
}
|
}
|
||||||
|
String alias = includeAlias ? "apk." : "";
|
||||||
final String[] args = new String[apkDetails.length * 2];
|
final String[] args = new String[apkDetails.length * 2];
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (int i = 0; i < apkDetails.length; i++) {
|
for (int i = 0; i < apkDetails.length; i++) {
|
||||||
@ -412,11 +449,23 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
sb.append(" OR ");
|
sb.append(" OR ");
|
||||||
}
|
}
|
||||||
sb.append(" ( " + Cols.PACKAGE_NAME + " = ? AND " + Cols.VERSION_CODE + " = ? ) ");
|
sb.append(" ( ")
|
||||||
|
.append(alias)
|
||||||
|
.append(Cols.APP_ID)
|
||||||
|
.append(" = (")
|
||||||
|
.append(getAppIdFromPackageNameQuery())
|
||||||
|
.append(") AND ")
|
||||||
|
.append(alias)
|
||||||
|
.append(Cols.VERSION_CODE)
|
||||||
|
.append(" = ? ) ");
|
||||||
}
|
}
|
||||||
return new QuerySelection(sb.toString(), args);
|
return new QuerySelection(sb.toString(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getAppIdFromPackageNameQuery() {
|
||||||
|
return "SELECT " + AppTable.Cols.ROW_ID + " FROM " + getAppTableName() + " WHERE " + AppTable.Cols.PACKAGE_NAME + " = ?";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||||
|
|
||||||
@ -464,13 +513,17 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void removeRepoFields(ContentValues values) {
|
private static void removeFieldsFromOtherTables(ContentValues values) {
|
||||||
for (Map.Entry<String, String> repoField : REPO_FIELDS.entrySet()) {
|
for (Map.Entry<String, String> repoField : REPO_FIELDS.entrySet()) {
|
||||||
final String field = repoField.getKey();
|
final String field = repoField.getKey();
|
||||||
if (values.containsKey(field)) {
|
if (values.containsKey(field)) {
|
||||||
Utils.debugLog(TAG, "Cannot insert/update '" + field + "' field " +
|
values.remove(field);
|
||||||
"on apk table, as it belongs to the repo table. " +
|
}
|
||||||
"This field will be ignored.");
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> appField : APP_FIELDS.entrySet()) {
|
||||||
|
final String field = appField.getKey();
|
||||||
|
if (values.containsKey(field)) {
|
||||||
values.remove(field);
|
values.remove(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -478,7 +531,7 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri insert(Uri uri, ContentValues values) {
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
removeRepoFields(values);
|
removeFieldsFromOtherTables(values);
|
||||||
validateFields(Cols.ALL, values);
|
validateFields(Cols.ALL, values);
|
||||||
db().insertOrThrow(getTableName(), null, values);
|
db().insertOrThrow(getTableName(), null, values);
|
||||||
if (!isApplyingBatch()) {
|
if (!isApplyingBatch()) {
|
||||||
@ -498,15 +551,15 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
switch (MATCHER.match(uri)) {
|
switch (MATCHER.match(uri)) {
|
||||||
|
|
||||||
case CODE_REPO:
|
case CODE_REPO:
|
||||||
query = query.add(queryRepo(Long.parseLong(uri.getLastPathSegment())));
|
query = query.add(queryRepo(Long.parseLong(uri.getLastPathSegment()), false));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CODE_APP:
|
case CODE_APP:
|
||||||
query = query.add(queryApp(uri.getLastPathSegment()));
|
query = query.add(queryApp(uri.getLastPathSegment(), false));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CODE_APKS:
|
case CODE_APKS:
|
||||||
query = query.add(queryApks(uri.getLastPathSegment()));
|
query = query.add(queryApks(uri.getLastPathSegment(), false));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// TODO: Add tests for this.
|
// TODO: Add tests for this.
|
||||||
@ -542,10 +595,10 @@ public class ApkProvider extends FDroidProvider {
|
|||||||
|
|
||||||
protected int performUpdateUnchecked(Uri uri, ContentValues values, String where, String[] whereArgs) {
|
protected int performUpdateUnchecked(Uri uri, ContentValues values, String where, String[] whereArgs) {
|
||||||
validateFields(Cols.ALL, values);
|
validateFields(Cols.ALL, values);
|
||||||
removeRepoFields(values);
|
removeFieldsFromOtherTables(values);
|
||||||
|
|
||||||
QuerySelection query = new QuerySelection(where, whereArgs);
|
QuerySelection query = new QuerySelection(where, whereArgs);
|
||||||
query = query.add(querySingle(uri));
|
query = query.add(querySingle(uri, false));
|
||||||
|
|
||||||
int numRows = db().update(getTableName(), values, query.getSelection(), query.getArgs());
|
int numRows = db().update(getTableName(), values, query.getSelection(), query.getArgs());
|
||||||
if (!isApplyingBatch()) {
|
if (!isApplyingBatch()) {
|
||||||
|
@ -130,6 +130,8 @@ public class App extends ValueObject implements Comparable<App> {
|
|||||||
|
|
||||||
public String installedSig;
|
public String installedSig;
|
||||||
|
|
||||||
|
private long id;
|
||||||
|
|
||||||
public static String getIconName(String packageName, int versionCode) {
|
public static String getIconName(String packageName, int versionCode) {
|
||||||
return packageName + "_" + versionCode + ".png";
|
return packageName + "_" + versionCode + ".png";
|
||||||
}
|
}
|
||||||
@ -153,6 +155,9 @@ public class App extends ValueObject implements Comparable<App> {
|
|||||||
for (int i = 0; i < cursor.getColumnCount(); i++) {
|
for (int i = 0; i < cursor.getColumnCount(); i++) {
|
||||||
String n = cursor.getColumnName(i);
|
String n = cursor.getColumnName(i);
|
||||||
switch (n) {
|
switch (n) {
|
||||||
|
case Cols.ROW_ID:
|
||||||
|
id = cursor.getLong(i);
|
||||||
|
break;
|
||||||
case Cols.IS_COMPATIBLE:
|
case Cols.IS_COMPATIBLE:
|
||||||
compatible = cursor.getInt(i) == 1;
|
compatible = cursor.getInt(i) == 1;
|
||||||
break;
|
break;
|
||||||
@ -440,6 +445,8 @@ public class App extends ValueObject implements Comparable<App> {
|
|||||||
public ContentValues toContentValues() {
|
public ContentValues toContentValues() {
|
||||||
|
|
||||||
final ContentValues values = new ContentValues();
|
final ContentValues values = new ContentValues();
|
||||||
|
// Intentionally don't put "ROW_ID" in here, because we don't ever want to change that
|
||||||
|
// primary key generated by sqlite.
|
||||||
values.put(Cols.PACKAGE_NAME, packageName);
|
values.put(Cols.PACKAGE_NAME, packageName);
|
||||||
values.put(Cols.NAME, name);
|
values.put(Cols.NAME, name);
|
||||||
values.put(Cols.SUMMARY, summary);
|
values.put(Cols.SUMMARY, summary);
|
||||||
@ -549,4 +556,8 @@ public class App extends ValueObject implements Comparable<App> {
|
|||||||
}
|
}
|
||||||
return new int[]{minSdkVersion, targetSdkVersion, maxSdkVersion};
|
return new int[]{minSdkVersion, targetSdkVersion, maxSdkVersion};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return cursorToList(cursor);
|
return cursorToList(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<App> cursorToList(Cursor cursor) {
|
static List<App> cursorToList(Cursor cursor) {
|
||||||
int knownAppCount = cursor != null ? cursor.getCount() : 0;
|
int knownAppCount = cursor != null ? cursor.getCount() : 0;
|
||||||
List<App> apps = new ArrayList<>(knownAppCount);
|
List<App> apps = new ArrayList<>(knownAppCount);
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
@ -153,7 +153,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
final Uri fromUpstream = calcAppDetailsFromIndexUri();
|
final Uri fromUpstream = calcAppDetailsFromIndexUri();
|
||||||
context.getContentResolver().update(fromUpstream, null, null, null);
|
context.getContentResolver().update(fromUpstream, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -184,7 +183,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
* method from this class will return an instance of this class, that is aware of
|
* method from this class will return an instance of this class, that is aware of
|
||||||
* the install apps table.
|
* the install apps table.
|
||||||
*/
|
*/
|
||||||
private static class AppQuerySelection extends QuerySelection {
|
protected static class AppQuerySelection extends QuerySelection {
|
||||||
|
|
||||||
private boolean naturalJoinToInstalled;
|
private boolean naturalJoinToInstalled;
|
||||||
|
|
||||||
@ -247,7 +246,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
final String repo = RepoTable.NAME;
|
final String repo = RepoTable.NAME;
|
||||||
|
|
||||||
return app +
|
return app +
|
||||||
" LEFT JOIN " + apk + " ON (" + apk + "." + ApkTable.Cols.PACKAGE_NAME + " = " + app + "." + Cols.PACKAGE_NAME + ") " +
|
" LEFT JOIN " + apk + " ON (" + apk + "." + ApkTable.Cols.APP_ID + " = " + app + "." + Cols.ROW_ID + ") " +
|
||||||
" LEFT JOIN " + repo + " ON (" + apk + "." + ApkTable.Cols.REPO_ID + " = " + repo + "." + RepoTable.Cols._ID + ") ";
|
" LEFT JOIN " + repo + " ON (" + apk + "." + ApkTable.Cols.REPO_ID + " = " + repo + "." + RepoTable.Cols._ID + ") ";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +258,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
@Override
|
@Override
|
||||||
protected String groupBy() {
|
protected String groupBy() {
|
||||||
// If the count field has been requested, then we want to group all rows together.
|
// If the count field has been requested, then we want to group all rows together.
|
||||||
return countFieldAppended ? null : getTableName() + "." + Cols.PACKAGE_NAME;
|
return countFieldAppended ? null : getTableName() + "." + Cols.ROW_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSelection(AppQuerySelection selection) {
|
public void addSelection(AppQuerySelection selection) {
|
||||||
@ -320,7 +319,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
|
|
||||||
private void appendCountField() {
|
private void appendCountField() {
|
||||||
countFieldAppended = true;
|
countFieldAppended = true;
|
||||||
appendField("COUNT( DISTINCT " + getTableName() + "." + Cols.PACKAGE_NAME + " ) AS " + Cols._COUNT);
|
appendField("COUNT( DISTINCT " + getTableName() + "." + Cols.ROW_ID + " ) AS " + Cols._COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSuggestedApkVersionField() {
|
private void addSuggestedApkVersionField() {
|
||||||
@ -335,7 +334,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
leftJoin(
|
leftJoin(
|
||||||
getApkTableName(),
|
getApkTableName(),
|
||||||
"suggestedApk",
|
"suggestedApk",
|
||||||
getTableName() + "." + Cols.SUGGESTED_VERSION_CODE + " = suggestedApk." + ApkTable.Cols.VERSION_CODE + " AND " + getTableName() + "." + Cols.PACKAGE_NAME + " = suggestedApk." + ApkTable.Cols.PACKAGE_NAME);
|
getTableName() + "." + Cols.SUGGESTED_VERSION_CODE + " = suggestedApk." + ApkTable.Cols.VERSION_CODE + " AND " + getTableName() + "." + Cols.ROW_ID + " = suggestedApk." + ApkTable.Cols.APP_ID);
|
||||||
}
|
}
|
||||||
appendField(fieldName, "suggestedApk", alias);
|
appendField(fieldName, "suggestedApk", alias);
|
||||||
}
|
}
|
||||||
@ -378,7 +377,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
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";
|
private static final String PATH_NO_APKS = "noApks";
|
||||||
private static final String PATH_APPS = "apps";
|
protected static final String PATH_APPS = "apps";
|
||||||
private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
|
private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
|
||||||
private static final String PATH_NEWLY_ADDED = "newlyAdded";
|
private static final String PATH_NEWLY_ADDED = "newlyAdded";
|
||||||
private static final String PATH_CATEGORY = "category";
|
private static final String PATH_CATEGORY = "category";
|
||||||
@ -390,8 +389,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
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 NO_APKS = SEARCH + 1;
|
||||||
private static final int APPS = NO_APKS + 1;
|
private static final int RECENTLY_UPDATED = NO_APKS + 1;
|
||||||
private static final int RECENTLY_UPDATED = APPS + 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 IGNORED = CATEGORY + 1;
|
private static final int IGNORED = CATEGORY + 1;
|
||||||
@ -416,7 +414,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
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_NO_APKS, NO_APKS);
|
||||||
MATCHER.addURI(getAuthority(), PATH_APPS + "/*", APPS);
|
|
||||||
MATCHER.addURI(getAuthority(), "*", CODE_SINGLE);
|
MATCHER.addURI(getAuthority(), "*", CODE_SINGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,20 +463,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri getContentUri(List<App> apps) {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
for (int i = 0; i < apps.size(); i++) {
|
|
||||||
if (i != 0) {
|
|
||||||
builder.append(',');
|
|
||||||
}
|
|
||||||
builder.append(apps.get(i).packageName);
|
|
||||||
}
|
|
||||||
return getContentUri().buildUpon()
|
|
||||||
.appendPath(PATH_APPS)
|
|
||||||
.appendPath(builder.toString())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getContentUri(App app) {
|
public static Uri getContentUri(App app) {
|
||||||
return getContentUri(app.packageName);
|
return getContentUri(app.packageName);
|
||||||
}
|
}
|
||||||
@ -684,10 +667,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return new AppQuerySelection(selection, args);
|
return new AppQuerySelection(selection, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppQuerySelection queryApps(String packageNames) {
|
|
||||||
return queryApps(packageNames, getTableName() + "." + Cols.PACKAGE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) {
|
public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) {
|
||||||
AppQuerySelection selection = new AppQuerySelection(customSelection, selectionArgs);
|
AppQuerySelection selection = new AppQuerySelection(customSelection, selectionArgs);
|
||||||
@ -742,10 +721,6 @@ public class AppProvider extends FDroidProvider {
|
|||||||
selection = selection.add(queryNoApks());
|
selection = selection.add(queryNoApks());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case APPS:
|
|
||||||
selection = selection.add(queryApps(uri.getLastPathSegment()));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IGNORED:
|
case IGNORED:
|
||||||
selection = selection.add(queryIgnored());
|
selection = selection.add(queryIgnored());
|
||||||
break;
|
break;
|
||||||
@ -772,6 +747,14 @@ public class AppProvider extends FDroidProvider {
|
|||||||
throw new UnsupportedOperationException("Invalid URI for app content provider: " + uri);
|
throw new UnsupportedOperationException("Invalid URI for app content provider: " + uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return runQuery(uri, selection, projection, includeSwap, sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
if (!includeSwap) {
|
if (!includeSwap) {
|
||||||
selection = selection.add(queryExcludeSwap());
|
selection = selection.add(queryExcludeSwap());
|
||||||
}
|
}
|
||||||
@ -973,7 +956,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
apk +
|
apk +
|
||||||
" JOIN " + repo + " ON (" + repo + "." + RepoTable.Cols._ID + " = " + apk + "." + ApkTable.Cols.REPO_ID + ") " +
|
" JOIN " + repo + " ON (" + repo + "." + RepoTable.Cols._ID + " = " + apk + "." + ApkTable.Cols.REPO_ID + ") " +
|
||||||
" WHERE " +
|
" WHERE " +
|
||||||
app + "." + Cols.PACKAGE_NAME + " = " + apk + "." + ApkTable.Cols.PACKAGE_NAME + " AND " +
|
app + "." + Cols.ROW_ID + " = " + apk + "." + ApkTable.Cols.APP_ID + " AND " +
|
||||||
apk + "." + ApkTable.Cols.VERSION_CODE + " = " + app + "." + Cols.SUGGESTED_VERSION_CODE;
|
apk + "." + ApkTable.Cols.VERSION_CODE + " = " + app + "." + Cols.SUGGESTED_VERSION_CODE;
|
||||||
|
|
||||||
return "UPDATE " + app + " SET "
|
return "UPDATE " + app + " SET "
|
||||||
|
@ -47,6 +47,7 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
private static final String CREATE_TABLE_APK =
|
private static final String CREATE_TABLE_APK =
|
||||||
"CREATE TABLE " + ApkTable.NAME + " ( "
|
"CREATE TABLE " + ApkTable.NAME + " ( "
|
||||||
+ ApkTable.Cols.PACKAGE_NAME + " text not null, "
|
+ ApkTable.Cols.PACKAGE_NAME + " text not null, "
|
||||||
|
+ ApkTable.Cols.APP_ID + " text not null, "
|
||||||
+ ApkTable.Cols.VERSION_NAME + " text not null, "
|
+ ApkTable.Cols.VERSION_NAME + " text not null, "
|
||||||
+ ApkTable.Cols.REPO_ID + " integer not null, "
|
+ ApkTable.Cols.REPO_ID + " integer not null, "
|
||||||
+ ApkTable.Cols.HASH + " text not null, "
|
+ ApkTable.Cols.HASH + " text not null, "
|
||||||
@ -114,7 +115,7 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
+ " );";
|
+ " );";
|
||||||
private static final String DROP_TABLE_INSTALLED_APP = "DROP TABLE " + InstalledAppTable.NAME + ";";
|
private static final String DROP_TABLE_INSTALLED_APP = "DROP TABLE " + InstalledAppTable.NAME + ";";
|
||||||
|
|
||||||
private static final int DB_VERSION = 57;
|
private static final int DB_VERSION = 58;
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@ -315,8 +316,29 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
requireTimestampInRepos(db, oldVersion);
|
requireTimestampInRepos(db, oldVersion);
|
||||||
recreateInstalledAppTable(db, oldVersion);
|
recreateInstalledAppTable(db, oldVersion);
|
||||||
addTargetSdkVersionToApk(db, oldVersion);
|
addTargetSdkVersionToApk(db, oldVersion);
|
||||||
|
migrateAppPrimaryKeyToRowId(db, oldVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void migrateAppPrimaryKeyToRowId(SQLiteDatabase db, int oldVersion) {
|
||||||
|
if (oldVersion < 58) {
|
||||||
|
final String alter = "ALTER TABLE " + ApkTable.NAME + " ADD COLUMN appId NUMERIC";
|
||||||
|
Log.i(TAG, "Adding appId foreign key to fdroid_apk.");
|
||||||
|
Utils.debugLog(TAG, alter);
|
||||||
|
db.execSQL(alter);
|
||||||
|
|
||||||
|
final String update =
|
||||||
|
"UPDATE " + ApkTable.NAME + " SET appId = ( " +
|
||||||
|
"SELECT app.rowid " +
|
||||||
|
"FROM " + ApkTable.NAME + " AS app " +
|
||||||
|
"WHERE " + ApkTable.NAME + ".id = app.id " +
|
||||||
|
")";
|
||||||
|
Log.i(TAG, "Updating foreign key from fdroid_apk to fdroid_app to use numeric foreign key.");
|
||||||
|
Utils.debugLog(TAG, update);
|
||||||
|
db.execSQL(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrate repo list to new structure. (No way to change primary
|
* Migrate repo list to new structure. (No way to change primary
|
||||||
* key in sqlite - table must be recreated).
|
* key in sqlite - table must be recreated).
|
||||||
@ -570,6 +592,7 @@ class DBHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL("create index app_id on " + AppTable.NAME + " (" + AppTable.Cols.PACKAGE_NAME + ");");
|
db.execSQL("create index app_id on " + AppTable.NAME + " (" + AppTable.Cols.PACKAGE_NAME + ");");
|
||||||
db.execSQL(CREATE_TABLE_APK);
|
db.execSQL(CREATE_TABLE_APK);
|
||||||
db.execSQL("create index apk_vercode on " + ApkTable.NAME + " (" + ApkTable.Cols.VERSION_CODE + ");");
|
db.execSQL("create index apk_vercode on " + ApkTable.NAME + " (" + ApkTable.Cols.VERSION_CODE + ");");
|
||||||
|
db.execSQL("create index apk_appId on " + ApkTable.NAME + " (" + ApkTable.Cols.APP_ID + ");");
|
||||||
db.execSQL("create index apk_id on " + ApkTable.NAME + " (" + AppTable.Cols.PACKAGE_NAME + ");");
|
db.execSQL("create index apk_id on " + ApkTable.NAME + " (" + AppTable.Cols.PACKAGE_NAME + ");");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,16 +98,19 @@ public class RepoPersister {
|
|||||||
|
|
||||||
if (apksToSave.size() > 0 || appsToSave.size() > 0) {
|
if (apksToSave.size() > 0 || appsToSave.size() > 0) {
|
||||||
Utils.debugLog(TAG, "Flushing details of up to " + MAX_APP_BUFFER + " apps and their packages to the database.");
|
Utils.debugLog(TAG, "Flushing details of up to " + MAX_APP_BUFFER + " apps and their packages to the database.");
|
||||||
flushAppsToDbInBatch();
|
Map<String, Long> appIds = flushAppsToDbInBatch();
|
||||||
flushApksToDbInBatch();
|
flushApksToDbInBatch(appIds);
|
||||||
apksToSave.clear();
|
apksToSave.clear();
|
||||||
appsToSave.clear();
|
appsToSave.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushApksToDbInBatch() throws RepoUpdater.UpdateException {
|
private void flushApksToDbInBatch(Map<String, Long> appIds) throws RepoUpdater.UpdateException {
|
||||||
List<Apk> apksToSaveList = new ArrayList<>();
|
List<Apk> apksToSaveList = new ArrayList<>();
|
||||||
for (Map.Entry<String, List<Apk>> entries : apksToSave.entrySet()) {
|
for (Map.Entry<String, List<Apk>> entries : apksToSave.entrySet()) {
|
||||||
|
for (Apk apk : entries.getValue()) {
|
||||||
|
apk.appId = appIds.get(apk.packageName);
|
||||||
|
}
|
||||||
apksToSaveList.addAll(entries.getValue());
|
apksToSaveList.addAll(entries.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,16 +130,43 @@ public class RepoPersister {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushAppsToDbInBatch() throws RepoUpdater.UpdateException {
|
/**
|
||||||
|
* Will first insert new or update existing rows in the database for each {@link RepoPersister#appsToSave}.
|
||||||
|
* Then, will query the database for the ID + packageName for each of these apps, so that they
|
||||||
|
* can be returned and the relevant apks can be joined to the app table correctly.
|
||||||
|
*/
|
||||||
|
private Map<String, Long> flushAppsToDbInBatch() throws RepoUpdater.UpdateException {
|
||||||
ArrayList<ContentProviderOperation> appOperations = insertOrUpdateApps(appsToSave);
|
ArrayList<ContentProviderOperation> appOperations = insertOrUpdateApps(appsToSave);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.getContentResolver().applyBatch(TempAppProvider.getAuthority(), appOperations);
|
context.getContentResolver().applyBatch(TempAppProvider.getAuthority(), appOperations);
|
||||||
|
return getIdsForPackages(appsToSave);
|
||||||
} catch (RemoteException | OperationApplicationException e) {
|
} catch (RemoteException | OperationApplicationException e) {
|
||||||
throw new RepoUpdater.UpdateException(repo, "An internal error occurred while updating the database", e);
|
throw new RepoUpdater.UpdateException(repo, "An internal error occurred while updating the database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Although this might seem counter intuitive - receiving a list of apps, then querying the
|
||||||
|
* database again for info about these apps, it is required because the apps came from the
|
||||||
|
* repo metadata, but we are really interested in their IDs from the database. These IDs only
|
||||||
|
* exist in SQLite and not the repo metadata.
|
||||||
|
*/
|
||||||
|
private Map<String, Long> getIdsForPackages(List<App> apps) {
|
||||||
|
List<String> packageNames = new ArrayList<>(appsToSave.size());
|
||||||
|
for (App app : apps) {
|
||||||
|
packageNames.add(app.packageName);
|
||||||
|
}
|
||||||
|
String[] projection = {Schema.AppTable.Cols.ROW_ID, Schema.AppTable.Cols.PACKAGE_NAME};
|
||||||
|
List<App> fromDb = TempAppProvider.Helper.findByPackageNames(context, packageNames, projection);
|
||||||
|
|
||||||
|
Map<String, Long> ids = new HashMap<>(fromDb.size());
|
||||||
|
for (App app : fromDb) {
|
||||||
|
ids.put(app.packageName, app.getId());
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depending on whether the {@link App}s have been added to the database previously, this
|
* Depending on whether the {@link App}s have been added to the database previously, this
|
||||||
* will queue up an update or an insert {@link ContentProviderOperation} for each app.
|
* will queue up an update or an insert {@link ContentProviderOperation} for each app.
|
||||||
|
@ -14,7 +14,12 @@ public interface Schema {
|
|||||||
String NAME = "fdroid_app";
|
String NAME = "fdroid_app";
|
||||||
|
|
||||||
interface Cols {
|
interface Cols {
|
||||||
String _ID = "rowid as _id"; // Required for CursorLoaders
|
/**
|
||||||
|
* Same as the primary key {@link Cols#ROW_ID}, except aliased as "_id" instead
|
||||||
|
* of "rowid". Required for {@link android.content.CursorLoader}s.
|
||||||
|
*/
|
||||||
|
String _ID = "rowid as _id";
|
||||||
|
String ROW_ID = "rowid";
|
||||||
String _COUNT = "_count";
|
String _COUNT = "_count";
|
||||||
String IS_COMPATIBLE = "compatible";
|
String IS_COMPATIBLE = "compatible";
|
||||||
String PACKAGE_NAME = "id";
|
String PACKAGE_NAME = "id";
|
||||||
@ -57,7 +62,7 @@ public interface Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String[] ALL = {
|
String[] ALL = {
|
||||||
_ID, IS_COMPATIBLE, PACKAGE_NAME, NAME, SUMMARY, ICON, DESCRIPTION,
|
_ID, ROW_ID, IS_COMPATIBLE, PACKAGE_NAME, NAME, SUMMARY, ICON, DESCRIPTION,
|
||||||
LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL,
|
LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL,
|
||||||
CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID,
|
CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID,
|
||||||
UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
|
UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
|
||||||
@ -82,6 +87,10 @@ public interface Schema {
|
|||||||
interface Cols extends BaseColumns {
|
interface Cols extends BaseColumns {
|
||||||
String _COUNT_DISTINCT = "countDistinct";
|
String _COUNT_DISTINCT = "countDistinct";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Foreign key to the {@link AppTable}.
|
||||||
|
*/
|
||||||
|
String APP_ID = "appId";
|
||||||
String PACKAGE_NAME = "id";
|
String PACKAGE_NAME = "id";
|
||||||
String VERSION_NAME = "version";
|
String VERSION_NAME = "version";
|
||||||
String REPO_ID = "repo";
|
String REPO_ID = "repo";
|
||||||
@ -101,14 +110,21 @@ public interface Schema {
|
|||||||
String ADDED_DATE = "added";
|
String ADDED_DATE = "added";
|
||||||
String IS_COMPATIBLE = "compatible";
|
String IS_COMPATIBLE = "compatible";
|
||||||
String INCOMPATIBLE_REASONS = "incompatibleReasons";
|
String INCOMPATIBLE_REASONS = "incompatibleReasons";
|
||||||
String REPO_VERSION = "repoVersion";
|
|
||||||
String REPO_ADDRESS = "repoAddress";
|
interface Repo {
|
||||||
|
String VERSION = "repoVersion";
|
||||||
|
String ADDRESS = "repoAddress";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface App {
|
||||||
|
String PACKAGE_NAME = "appPackageName";
|
||||||
|
}
|
||||||
|
|
||||||
String[] ALL = {
|
String[] ALL = {
|
||||||
_ID, PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME,
|
_ID, APP_ID, PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME,
|
||||||
SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION,
|
SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION,
|
||||||
PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
|
PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
|
||||||
IS_COMPATIBLE, REPO_VERSION, REPO_ADDRESS, INCOMPATIBLE_REASONS,
|
IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,11 @@ public class TempApkProvider extends ApkProvider {
|
|||||||
return TABLE_TEMP_APK;
|
return TABLE_TEMP_APK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppTableName() {
|
||||||
|
return TempAppProvider.TABLE_TEMP_APP;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getAuthority() {
|
public static String getAuthority() {
|
||||||
return AUTHORITY + "." + PROVIDER_NAME;
|
return AUTHORITY + "." + PROVIDER_NAME;
|
||||||
}
|
}
|
||||||
@ -111,7 +116,7 @@ public class TempApkProvider extends ApkProvider {
|
|||||||
switch (MATCHER.match(uri)) {
|
switch (MATCHER.match(uri)) {
|
||||||
case CODE_REPO_APK:
|
case CODE_REPO_APK:
|
||||||
List<String> pathSegments = uri.getPathSegments();
|
List<String> pathSegments = uri.getPathSegments();
|
||||||
query = query.add(queryRepo(Long.parseLong(pathSegments.get(1)))).add(queryApks(pathSegments.get(2)));
|
query = query.add(queryRepo(Long.parseLong(pathSegments.get(1)), false)).add(queryApks(pathSegments.get(2), false));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -3,9 +3,13 @@ package org.fdroid.fdroid.data;
|
|||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.UriMatcher;
|
import android.content.UriMatcher;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
|
import org.fdroid.fdroid.data.Schema.ApkTable;
|
||||||
import org.fdroid.fdroid.data.Schema.AppTable;
|
import org.fdroid.fdroid.data.Schema.AppTable;
|
||||||
@ -22,19 +26,21 @@ public class TempAppProvider extends AppProvider {
|
|||||||
|
|
||||||
private static final String PROVIDER_NAME = "TempAppProvider";
|
private static final String PROVIDER_NAME = "TempAppProvider";
|
||||||
|
|
||||||
private static final String TABLE_TEMP_APP = "temp_" + AppTable.NAME;
|
static final String TABLE_TEMP_APP = "temp_" + AppTable.NAME;
|
||||||
|
|
||||||
private static final String PATH_INIT = "init";
|
private static final String PATH_INIT = "init";
|
||||||
private static final String PATH_COMMIT = "commit";
|
private static final String PATH_COMMIT = "commit";
|
||||||
|
|
||||||
private static final int CODE_INIT = 10000;
|
private static final int CODE_INIT = 10000;
|
||||||
private static final int CODE_COMMIT = CODE_INIT + 1;
|
private static final int CODE_COMMIT = CODE_INIT + 1;
|
||||||
|
private static final int APPS = CODE_COMMIT + 1;
|
||||||
|
|
||||||
private static final UriMatcher MATCHER = new UriMatcher(-1);
|
private static final UriMatcher MATCHER = new UriMatcher(-1);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT);
|
MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT);
|
||||||
MATCHER.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT);
|
MATCHER.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT);
|
||||||
|
MATCHER.addURI(getAuthority(), PATH_APPS + "/*", APPS);
|
||||||
MATCHER.addURI(getAuthority(), "*", CODE_SINGLE);
|
MATCHER.addURI(getAuthority(), "*", CODE_SINGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +61,17 @@ public class TempAppProvider extends AppProvider {
|
|||||||
return Uri.withAppendedPath(getContentUri(), app.packageName);
|
return Uri.withAppendedPath(getContentUri(), app.packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri getAppsUri(List<String> apps) {
|
||||||
|
return getContentUri().buildUpon()
|
||||||
|
.appendPath(PATH_APPS)
|
||||||
|
.appendPath(TextUtils.join(",", apps))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppQuerySelection queryApps(String packageNames) {
|
||||||
|
return queryApps(packageNames, getTableName() + ".id");
|
||||||
|
}
|
||||||
|
|
||||||
public static class Helper {
|
public static class Helper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,6 +84,12 @@ public class TempAppProvider extends AppProvider {
|
|||||||
TempApkProvider.Helper.init(context);
|
TempApkProvider.Helper.init(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<App> findByPackageNames(Context context, List<String> packageNames, String[] projection) {
|
||||||
|
Uri uri = getAppsUri(packageNames);
|
||||||
|
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
|
||||||
|
return AppProvider.Helper.cursorToList(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves data from the temp table to the apk table, by removing _EVERYTHING_ from the real
|
* 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.
|
* apk table and inserting all of the records from here. The temporary table is then removed.
|
||||||
@ -116,6 +139,18 @@ public class TempAppProvider extends AppProvider {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) {
|
||||||
|
AppQuerySelection selection = new AppQuerySelection(customSelection, selectionArgs);
|
||||||
|
switch (MATCHER.match(uri)) {
|
||||||
|
case APPS:
|
||||||
|
selection = selection.add(queryApps(uri.getLastPathSegment()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.runQuery(uri, selection, projection, true, sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureTempTableDetached(SQLiteDatabase db) {
|
private void ensureTempTableDetached(SQLiteDatabase db) {
|
||||||
try {
|
try {
|
||||||
db.execSQL("DETACH DATABASE " + DB);
|
db.execSQL("DETACH DATABASE " + DB);
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import junit.framework.AssertionFailedError;
|
import junit.framework.AssertionFailedError;
|
||||||
|
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
|
import org.fdroid.fdroid.data.Schema.ApkTable;
|
||||||
@ -174,14 +176,14 @@ public class Assert {
|
|||||||
cursor.close();
|
cursor.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void insertApp(ShadowContentResolver resolver, String appId, String name) {
|
public static App insertApp(Context context, String packageName, String name) {
|
||||||
insertApp(resolver, appId, name, new ContentValues());
|
return insertApp(context, packageName, name, new ContentValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void insertApp(ShadowContentResolver resolver, String id, String name, ContentValues additionalValues) {
|
public static App insertApp(Context context, String packageName, String name, ContentValues additionalValues) {
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(AppTable.Cols.PACKAGE_NAME, id);
|
values.put(AppTable.Cols.PACKAGE_NAME, packageName);
|
||||||
values.put(AppTable.Cols.NAME, name);
|
values.put(AppTable.Cols.NAME, name);
|
||||||
|
|
||||||
// Required fields (NOT NULL in the database).
|
// Required fields (NOT NULL in the database).
|
||||||
@ -196,18 +198,38 @@ public class Assert {
|
|||||||
|
|
||||||
Uri uri = AppProvider.getContentUri();
|
Uri uri = AppProvider.getContentUri();
|
||||||
|
|
||||||
resolver.insert(uri, values);
|
context.getContentResolver().insert(uri, values);
|
||||||
|
return AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri insertApk(ShadowContentResolver resolver, String id, int versionCode) {
|
private static App ensureApp(Context context, String packageName) {
|
||||||
return insertApk(resolver, id, versionCode, new ContentValues());
|
App app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName);
|
||||||
|
if (app == null) {
|
||||||
|
insertApp(context, packageName, packageName);
|
||||||
|
app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName);
|
||||||
|
}
|
||||||
|
assertNotNull(app);
|
||||||
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri insertApk(ShadowContentResolver resolver, String id, int versionCode, ContentValues additionalValues) {
|
public static Uri insertApk(Context context, String packageName, int versionCode) {
|
||||||
|
return insertApk(context, ensureApp(context, packageName), versionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri insertApk(Context context, String packageName, int versionCode, ContentValues additionalValues) {
|
||||||
|
return insertApk(context, ensureApp(context, packageName), versionCode, additionalValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri insertApk(Context context, App app, int versionCode) {
|
||||||
|
return insertApk(context, app, versionCode, new ContentValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri insertApk(Context context, App app, int versionCode, ContentValues additionalValues) {
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
|
|
||||||
values.put(ApkTable.Cols.PACKAGE_NAME, id);
|
values.put(ApkTable.Cols.APP_ID, app.getId());
|
||||||
|
values.put(ApkTable.Cols.PACKAGE_NAME, app.packageName);
|
||||||
values.put(ApkTable.Cols.VERSION_CODE, versionCode);
|
values.put(ApkTable.Cols.VERSION_CODE, versionCode);
|
||||||
|
|
||||||
// Required fields (NOT NULL in the database).
|
// Required fields (NOT NULL in the database).
|
||||||
@ -222,7 +244,7 @@ public class Assert {
|
|||||||
|
|
||||||
Uri uri = ApkProvider.getContentUri();
|
Uri uri = ApkProvider.getContentUri();
|
||||||
|
|
||||||
return resolver.insert(uri, values);
|
return context.getContentResolver().insert(uri, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
|
||||||
|
import org.mockito.AdditionalAnswers;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
import org.robolectric.shadows.ShadowContentResolver;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -8,6 +15,7 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
|
||||||
@ -37,4 +45,21 @@ public class TestUtils {
|
|||||||
return tempFile;
|
return tempFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The way that Robolectric has to implement shadows for Android classes such as {@link android.content.ContentProvider}
|
||||||
|
* is by using a special annotation that means the classes will implement the correct methods at runtime.
|
||||||
|
* However this means that the shadow of a content provider does not actually extend
|
||||||
|
* {@link android.content.ContentProvider}. As such, we need to do some special mocking using
|
||||||
|
* Mockito in order to provide a {@link ContextWrapper} which is able to return a proper
|
||||||
|
* content resolver that delegates to the Robolectric shadow object.
|
||||||
|
*/
|
||||||
|
public static ContextWrapper createContextWithContentResolver(ShadowContentResolver contentResolver) {
|
||||||
|
final ContentResolver resolver = mock(ContentResolver.class, AdditionalAnswers.delegatesTo(contentResolver));
|
||||||
|
return new ContextWrapper(RuntimeEnvironment.application.getApplicationContext()) {
|
||||||
|
@Override
|
||||||
|
public ContentResolver getContentResolver() {
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import java.util.List;
|
|||||||
import static org.fdroid.fdroid.Assert.assertCantDelete;
|
import static org.fdroid.fdroid.Assert.assertCantDelete;
|
||||||
import static org.fdroid.fdroid.Assert.assertContainsOnly;
|
import static org.fdroid.fdroid.Assert.assertContainsOnly;
|
||||||
import static org.fdroid.fdroid.Assert.assertResultCount;
|
import static org.fdroid.fdroid.Assert.assertResultCount;
|
||||||
|
import static org.fdroid.fdroid.Assert.insertApp;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
@ -38,9 +39,11 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAppApks() {
|
public void testAppApks() {
|
||||||
|
App fdroidApp = insertApp(context, "org.fdroid.fdroid", "F-Droid");
|
||||||
|
App exampleApp = insertApp(context, "com.example", "Example");
|
||||||
for (int i = 1; i <= 10; i++) {
|
for (int i = 1; i <= 10; i++) {
|
||||||
Assert.insertApk(contentResolver, "org.fdroid.fdroid", i);
|
Assert.insertApk(context, fdroidApp, i);
|
||||||
Assert.insertApk(contentResolver, "com.example", i);
|
Assert.insertApk(context, exampleApp, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTotalApkCount(20);
|
assertTotalApkCount(20);
|
||||||
@ -188,7 +191,7 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
Apk apk = new MockApk("org.fdroid.fdroid", 13);
|
Apk apk = new MockApk("org.fdroid.fdroid", 13);
|
||||||
|
|
||||||
// Insert a new record...
|
// Insert a new record...
|
||||||
Uri newUri = Assert.insertApk(contentResolver, apk.packageName, apk.versionCode);
|
Uri newUri = Assert.insertApk(context, apk.packageName, apk.versionCode);
|
||||||
assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
|
assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
|
||||||
cursor = queryAllApks();
|
cursor = queryAllApks();
|
||||||
assertNotNull(cursor);
|
assertNotNull(cursor);
|
||||||
@ -206,7 +209,7 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testCursorMustMoveToFirst() {
|
public void testCursorMustMoveToFirst() {
|
||||||
Assert.insertApk(contentResolver, "org.example.test", 12);
|
Assert.insertApk(context, "org.example.test", 12);
|
||||||
Cursor cursor = queryAllApks();
|
Cursor cursor = queryAllApks();
|
||||||
new Apk(cursor);
|
new Apk(cursor);
|
||||||
}
|
}
|
||||||
@ -216,7 +219,7 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
String[] projectionCount = new String[] {Cols._COUNT};
|
String[] projectionCount = new String[] {Cols._COUNT};
|
||||||
|
|
||||||
for (int i = 0; i < 13; i++) {
|
for (int i = 0; i < 13; i++) {
|
||||||
Assert.insertApk(contentResolver, "com.example", i);
|
Assert.insertApk(context, "com.example", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri all = ApkProvider.getContentUri();
|
Uri all = ApkProvider.getContentUri();
|
||||||
@ -261,7 +264,7 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
public void assertInvalidExtraField(String field) {
|
public void assertInvalidExtraField(String field) {
|
||||||
ContentValues invalidRepo = new ContentValues();
|
ContentValues invalidRepo = new ContentValues();
|
||||||
invalidRepo.put(field, "Test data");
|
invalidRepo.put(field, "Test data");
|
||||||
Assert.insertApk(contentResolver, "org.fdroid.fdroid", 10, invalidRepo);
|
Assert.insertApk(context, "org.fdroid.fdroid", 10, invalidRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -271,10 +274,10 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
|
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(Cols.REPO_ID, 10);
|
values.put(Cols.REPO_ID, 10);
|
||||||
values.put(Cols.REPO_ADDRESS, "http://example.com");
|
values.put(Cols.Repo.ADDRESS, "http://example.com");
|
||||||
values.put(Cols.REPO_VERSION, 3);
|
values.put(Cols.Repo.VERSION, 3);
|
||||||
values.put(Cols.FEATURES, "Some features");
|
values.put(Cols.FEATURES, "Some features");
|
||||||
Uri uri = Assert.insertApk(contentResolver, "com.example.com", 1, values);
|
Uri uri = Assert.insertApk(context, "com.example.com", 1, values);
|
||||||
|
|
||||||
assertResultCount(1, queryAllApks());
|
assertResultCount(1, queryAllApks());
|
||||||
|
|
||||||
@ -301,18 +304,18 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
public void testKnownApks() {
|
public void testKnownApks() {
|
||||||
|
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < 7; i++) {
|
||||||
Assert.insertApk(contentResolver, "org.fdroid.fdroid", i);
|
Assert.insertApk(context, "org.fdroid.fdroid", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 9; i++) {
|
for (int i = 0; i < 9; i++) {
|
||||||
Assert.insertApk(contentResolver, "org.example", i);
|
Assert.insertApk(context, "org.example", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
Assert.insertApk(contentResolver, "com.example", i);
|
Assert.insertApk(context, "com.example", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.insertApk(contentResolver, "com.apk.thingo", 1);
|
Assert.insertApk(context, "com.apk.thingo", 1);
|
||||||
|
|
||||||
Apk[] known = {
|
Apk[] known = {
|
||||||
new MockApk("org.fdroid.fdroid", 1),
|
new MockApk("org.fdroid.fdroid", 1),
|
||||||
@ -359,18 +362,18 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
public void testFindByApp() {
|
public void testFindByApp() {
|
||||||
|
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < 7; i++) {
|
||||||
Assert.insertApk(contentResolver, "org.fdroid.fdroid", i);
|
Assert.insertApk(context, "org.fdroid.fdroid", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 9; i++) {
|
for (int i = 0; i < 9; i++) {
|
||||||
Assert.insertApk(contentResolver, "org.example", i);
|
Assert.insertApk(context, "org.example", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
Assert.insertApk(contentResolver, "com.example", i);
|
Assert.insertApk(context, "com.example", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.insertApk(contentResolver, "com.apk.thingo", 1);
|
Assert.insertApk(context, "com.apk.thingo", 1);
|
||||||
|
|
||||||
assertTotalApkCount(7 + 9 + 3 + 1);
|
assertTotalApkCount(7 + 9 + 3 + 1);
|
||||||
|
|
||||||
@ -394,7 +397,7 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testUpdate() {
|
public void testUpdate() {
|
||||||
|
|
||||||
Uri apkUri = Assert.insertApk(contentResolver, "com.example", 10);
|
Uri apkUri = Assert.insertApk(context, "com.example", 10);
|
||||||
|
|
||||||
String[] allFields = Cols.ALL;
|
String[] allFields = Cols.ALL;
|
||||||
Cursor cursor = contentResolver.query(apkUri, allFields, null, null, null);
|
Cursor cursor = contentResolver.query(apkUri, allFields, null, null, null);
|
||||||
@ -453,18 +456,20 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
// the Helper.find() method doesn't stumble upon the app we are interested
|
// the Helper.find() method doesn't stumble upon the app we are interested
|
||||||
// in by shear dumb luck...
|
// in by shear dumb luck...
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
Assert.insertApk(contentResolver, "org.fdroid.apk." + i, i);
|
Assert.insertApk(context, "org.fdroid.apk." + i, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
App exampleApp = insertApp(context, "com.example", "Example");
|
||||||
|
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(Cols.VERSION_NAME, "v1.1");
|
values.put(Cols.VERSION_NAME, "v1.1");
|
||||||
values.put(Cols.HASH, "xxxxyyyy");
|
values.put(Cols.HASH, "xxxxyyyy");
|
||||||
values.put(Cols.HASH_TYPE, "a hash type");
|
values.put(Cols.HASH_TYPE, "a hash type");
|
||||||
Assert.insertApk(contentResolver, "com.example", 11, values);
|
Assert.insertApk(context, exampleApp, 11, values);
|
||||||
|
|
||||||
// ...and a few more for good measure...
|
// ...and a few more for good measure...
|
||||||
for (int i = 15; i < 20; i++) {
|
for (int i = 15; i < 20; i++) {
|
||||||
Assert.insertApk(contentResolver, "com.other.thing." + i, i);
|
Assert.insertApk(context, "com.other.thing." + i, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
Apk apk = ApkProvider.Helper.find(context, "com.example", 11);
|
Apk apk = ApkProvider.Helper.find(context, "com.example", 11);
|
||||||
@ -540,7 +545,7 @@ public class ApkProviderTest extends FDroidProviderTest {
|
|||||||
protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
|
protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
|
||||||
ContentValues additionalValues = new ContentValues();
|
ContentValues additionalValues = new ContentValues();
|
||||||
additionalValues.put(Cols.REPO_ID, repoId);
|
additionalValues.put(Cols.REPO_ID, repoId);
|
||||||
Uri uri = Assert.insertApk(contentResolver, id, versionCode, additionalValues);
|
Uri uri = Assert.insertApk(context, id, versionCode, additionalValues);
|
||||||
return ApkProvider.Helper.get(context, uri);
|
return ApkProvider.Helper.get(context, uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package org.fdroid.fdroid.data;
|
|||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContextWrapper;
|
import android.content.ContextWrapper;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.TestUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.mockito.AdditionalAnswers;
|
import org.mockito.AdditionalAnswers;
|
||||||
@ -20,13 +21,7 @@ public abstract class FDroidProviderTest {
|
|||||||
@Before
|
@Before
|
||||||
public final void setupBase() {
|
public final void setupBase() {
|
||||||
contentResolver = Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver());
|
contentResolver = Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver());
|
||||||
final ContentResolver resolver = mock(ContentResolver.class, AdditionalAnswers.delegatesTo(contentResolver));
|
context = TestUtils.createContextWithContentResolver(contentResolver);
|
||||||
context = new ContextWrapper(RuntimeEnvironment.application.getApplicationContext()) {
|
|
||||||
@Override
|
|
||||||
public ContentResolver getContentResolver() {
|
|
||||||
return resolver;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider());
|
ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.fdroid.fdroid.data;
|
package org.fdroid.fdroid.data;
|
||||||
|
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
|
import org.fdroid.fdroid.TestUtils;
|
||||||
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
|
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
|
||||||
import org.fdroid.fdroid.mock.MockApk;
|
import org.fdroid.fdroid.mock.MockApk;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@ -93,14 +94,27 @@ public class ProviderUriTests {
|
|||||||
App app = new App();
|
App app = new App();
|
||||||
app.packageName = "org.fdroid.fdroid";
|
app.packageName = "org.fdroid.fdroid";
|
||||||
|
|
||||||
List<App> apps = new ArrayList<>(1);
|
|
||||||
apps.add(app);
|
|
||||||
|
|
||||||
assertValidUri(resolver, AppProvider.getContentUri(app), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
|
assertValidUri(resolver, AppProvider.getContentUri(app), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
|
||||||
assertValidUri(resolver, AppProvider.getContentUri(apps), "content://org.fdroid.fdroid.data.AppProvider/apps/org.fdroid.fdroid", projection);
|
|
||||||
assertValidUri(resolver, AppProvider.getContentUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
|
assertValidUri(resolver, AppProvider.getContentUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validTempAppProviderUris() {
|
||||||
|
ShadowContentResolver.registerProvider(TempAppProvider.getAuthority(), new TempAppProvider());
|
||||||
|
String[] projection = new String[]{Schema.AppTable.Cols._ID};
|
||||||
|
|
||||||
|
// Required so that the `assertValidUri` calls below will indeed have a real temp_fdroid_app
|
||||||
|
// table to query.
|
||||||
|
TempAppProvider.Helper.init(TestUtils.createContextWithContentResolver(resolver));
|
||||||
|
|
||||||
|
List<String> packageNames = new ArrayList<>(2);
|
||||||
|
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.getContentUri(), "content://org.fdroid.fdroid.data.TempAppProvider", projection);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void invalidApkProviderUris() {
|
public void invalidApkProviderUris() {
|
||||||
ShadowContentResolver.registerProvider(ApkProvider.getAuthority(), new ApkProvider());
|
ShadowContentResolver.registerProvider(ApkProvider.getAuthority(), new ApkProvider());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user