s/curVersion/upstreamVersion/g, added suggestedVersion. Refactored QueryBuilder.

In order to support suggested version, I didn't want to have both
suggested version + versionCode in the App table. Rather, just the
code, and then use that (and the apps id) to join onto the apk table.
This is something we wanted to do elsewhere, so I refactored the
QueryBuilder class from the ApkProvider so that it can also be used
by the AppProvider.
This commit is contained in:
Peter Serwylo 2014-02-15 20:57:18 +11:00 committed by Peter Serwylo
parent 955c2f5f6c
commit 568224ba78
11 changed files with 272 additions and 147 deletions

View File

@ -3,9 +3,3 @@ These issues are a must-fix before the next stable release:
* Right after updating a repo, `Recently Updated` shows the apps correctly but * Right after updating a repo, `Recently Updated` shows the apps correctly but
the new apks don't show up on App Details until the whole app is restarted the new apks don't show up on App Details until the whole app is restarted
(or until the repos are wiped and re-downloaded) (or until the repos are wiped and re-downloaded)
* `App.curVersion` is now used in some places where before we used
`App.curApk.version`, which means that e.g. app lists now show the current
version at upstream and not the latest stable version in the repository
(highly misleading to users, who might end up looking for versions not in
the repo yet)

View File

@ -128,7 +128,7 @@ public class AppDetails extends ListActivity {
holder.version.setText(getString(R.string.version) holder.version.setText(getString(R.string.version)
+ " " + apk.version + " " + apk.version
+ (apk.vercode == app.curVercode ? "" : "")); + (apk.vercode == app.suggestedVercode ? "" : ""));
if (apk.vercode == app.getInstalledVerCode(getContext()) if (apk.vercode == app.getInstalledVerCode(getContext())
&& mInstalledSigID != null && apk.sig != null && mInstalledSigID != null && apk.sig != null
@ -534,7 +534,7 @@ public class AppDetails extends ListActivity {
Apk curApk = null; Apk curApk = null;
for (int i = 0; i < adapter.getCount(); i ++) { for (int i = 0; i < adapter.getCount(); i ++) {
Apk apk = adapter.getItem(i); Apk apk = adapter.getItem(i);
if (apk.vercode == app.curVercode) { if (apk.vercode == app.suggestedVercode) {
curApk = apk; curApk = apk;
break; break;
} }
@ -678,7 +678,7 @@ public class AppDetails extends ListActivity {
} }
// Check count > 0 due to incompatible apps resulting in an empty list. // Check count > 0 due to incompatible apps resulting in an empty list.
if (app.getInstalledVersion(this) == null && app.curVercode > 0 && if (app.getInstalledVersion(this) == null && app.suggestedVercode > 0 &&
adapter.getCount() > 0) { adapter.getCount() > 0) {
MenuItemCompat.setShowAsAction(menu.add( MenuItemCompat.setShowAsAction(menu.add(
Menu.NONE, INSTALL, 1, R.string.menu_install) Menu.NONE, INSTALL, 1, R.string.menu_install)
@ -716,7 +716,7 @@ public class AppDetails extends ListActivity {
menu.add(Menu.NONE, IGNORETHIS, 2, R.string.menu_ignore_this) menu.add(Menu.NONE, IGNORETHIS, 2, R.string.menu_ignore_this)
.setIcon(android.R.drawable.ic_menu_close_clear_cancel) .setIcon(android.R.drawable.ic_menu_close_clear_cancel)
.setCheckable(true) .setCheckable(true)
.setChecked(app.ignoreThisUpdate >= app.curVercode); .setChecked(app.ignoreThisUpdate >= app.suggestedVercode);
} }
if (app.webURL.length() > 0) { if (app.webURL.length() > 0) {
menu.add(Menu.NONE, WEBSITE, 3, R.string.menu_website).setIcon( menu.add(Menu.NONE, WEBSITE, 3, R.string.menu_website).setIcon(
@ -783,8 +783,8 @@ public class AppDetails extends ListActivity {
case INSTALL: case INSTALL:
// Note that this handles updating as well as installing. // Note that this handles updating as well as installing.
if (app.curVercode > 0) { if (app.suggestedVercode > 0) {
final Apk apkToInstall = ApkProvider.Helper.find(this, app.id, app.curVercode); final Apk apkToInstall = ApkProvider.Helper.find(this, app.id, app.suggestedVercode);
install(apkToInstall); install(apkToInstall);
} }
return true; return true;
@ -799,10 +799,10 @@ public class AppDetails extends ListActivity {
return true; return true;
case IGNORETHIS: case IGNORETHIS:
if (app.ignoreThisUpdate >= app.curVercode) if (app.ignoreThisUpdate >= app.suggestedVercode)
app.ignoreThisUpdate = 0; app.ignoreThisUpdate = 0;
else else
app.ignoreThisUpdate = app.curVercode; app.ignoreThisUpdate = app.suggestedVercode;
item.setChecked(app.ignoreThisUpdate > 0); item.setChecked(app.ignoreThisUpdate > 0);
return true; return true;

View File

@ -226,12 +226,12 @@ public class RepoXMLHandler extends DefaultHandler {
curapp.lastUpdated = null; curapp.lastUpdated = null;
} }
} else if (curel.equals("marketversion")) { } else if (curel.equals("marketversion")) {
curapp.curVersion = str; curapp.upstreamVersion = str;
} else if (curel.equals("marketvercode")) { } else if (curel.equals("marketvercode")) {
try { try {
curapp.curVercode = Integer.parseInt(str); curapp.upstreamVercode = Integer.parseInt(str);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
curapp.curVercode = -1; curapp.upstreamVercode = -1;
} }
} else if (curel.equals("categories")) { } else if (curel.equals("categories")) {
curapp.categories = Utils.CommaSeparatedList.make(str); curapp.categories = Utils.CommaSeparatedList.make(str);

View File

@ -400,19 +400,19 @@ public class UpdateService extends IntentService implements ProgressListener {
private static void calcCurrentApkForApp(App app, List<Apk> apksForApp) { private static void calcCurrentApkForApp(App app, List<Apk> apksForApp) {
Apk latestApk = null; Apk latestApk = null;
// Try and return the real current version first. It will find the // Try and return the real current version first. It will find the
// closest version smaller than the curVercode, being the same // closest version smaller than the upstreamVercode, being the same
// vercode if it exists. // vercode if it exists.
if (app.curVercode > 0) { if (app.upstreamVercode > 0) {
int latestcode = -1; int latestcode = -1;
for (Apk apk : apksForApp) { for (Apk apk : apksForApp) {
if ((!app.compatible || apk.compatible) if ((!app.compatible || apk.compatible)
&& apk.vercode <= app.curVercode && apk.vercode <= app.upstreamVercode
&& apk.vercode > latestcode) { && apk.vercode > latestcode) {
latestApk = apk; latestApk = apk;
latestcode = apk.vercode; latestcode = apk.vercode;
} }
} }
} else if (app.curVercode == -1) { } else if (app.upstreamVercode == -1) {
// If the current version was not set we return the most recent apk. // If the current version was not set we return the most recent apk.
int latestCode = -1; int latestCode = -1;
for (Apk apk : apksForApp) { for (Apk apk : apksForApp) {
@ -425,8 +425,7 @@ public class UpdateService extends IntentService implements ProgressListener {
} }
if (latestApk != null) { if (latestApk != null) {
app.curVercode = latestApk.vercode; app.suggestedVercode = latestApk.vercode;
app.curVersion = latestApk.version;
} }
} }

View File

@ -221,15 +221,16 @@ public class ApkProvider extends FDroidProvider {
return matcher; return matcher;
} }
private static class QueryBuilder { private static class Query extends QueryBuilder {
private StringBuilder fields = new StringBuilder();
private StringBuilder tables = new StringBuilder(DBHelper.TABLE_APK + " AS apk");
private String selection = null;
private String orderBy = null;
private boolean repoTableRequired = false; private boolean repoTableRequired = false;
@Override
protected String getRequiredTables() {
return DBHelper.TABLE_APK + " AS apk";
}
@Override
public void addField(String field) { public void addField(String field) {
if (REPO_FIELDS.containsKey(field)) { if (REPO_FIELDS.containsKey(field)) {
addRepoField(REPO_FIELDS.get(field), field); addRepoField(REPO_FIELDS.get(field), field);
@ -242,63 +243,14 @@ public class ApkProvider extends FDroidProvider {
} }
} }
public void addRepoField(String field, String alias) { private void addRepoField(String field, String alias) {
if (!repoTableRequired) { if (!repoTableRequired) {
repoTableRequired = true; repoTableRequired = true;
tables.append(" LEFT JOIN "); leftJoin(DBHelper.TABLE_REPO, "repo", "apk.repo = repo._id");
tables.append(DBHelper.TABLE_REPO);
tables.append(" AS repo ON (apk.repo = repo._id) ");
} }
appendField(field, "repo", alias); appendField(field, "repo", alias);
} }
private void appendField(String field) {
appendField(field, null, null);
}
private void appendField(String field, String tableAlias) {
appendField(field, tableAlias, null);
}
private void appendField(String field, String tableAlias,
String fieldAlias) {
if (fields.length() != 0) {
fields.append(',');
}
if (tableAlias != null) {
fields.append(tableAlias).append('.');
}
fields.append(field);
if (fieldAlias != null) {
fields.append(" AS ").append(fieldAlias);
}
}
public void addSelection(String selection) {
this.selection = selection;
}
public void addOrderBy(String orderBy) {
this.orderBy = orderBy;
}
@Override
public String toString() {
StringBuilder suffix = new StringBuilder();
if (selection != null) {
suffix.append(" WHERE ").append(selection);
}
if (orderBy != null) {
suffix.append(" ORDER BY ").append(orderBy);
}
return "SELECT " + fields + " FROM " + tables + suffix;
}
} }
private QuerySelection queryApp(String appId) { private QuerySelection queryApp(String appId) {
@ -377,7 +329,7 @@ public class ApkProvider extends FDroidProvider {
throw new UnsupportedOperationException("Invalid URI for apk content provider: " + uri); throw new UnsupportedOperationException("Invalid URI for apk content provider: " + uri);
} }
QueryBuilder queryBuilder = new QueryBuilder(); Query queryBuilder = new Query();
for (String field : projection) { for (String field : projection) {
queryBuilder.addField(field); queryBuilder.addField(field);
} }

View File

@ -41,8 +41,18 @@ public class App extends ValueObject implements Comparable<App> {
public String flattrID; public String flattrID;
public String curVersion; public String upstreamVersion;
public int curVercode; public int upstreamVercode;
/**
* Unlike other public fields, this is only accessible via a getter, to
* emphasise that setting it wont do anything. In order to change this,
* you need to change suggestedVercode to an apk which is in the apk table.
*/
private String suggestedVersion;
public int suggestedVercode;
public Date added; public Date added;
public Date lastUpdated; public Date lastUpdated;
@ -114,10 +124,14 @@ public class App extends ValueObject implements Comparable<App> {
dogecoinAddr = cursor.getString(i); dogecoinAddr = cursor.getString(i);
} else if (column.equals(AppProvider.DataColumns.FLATTR_ID)) { } else if (column.equals(AppProvider.DataColumns.FLATTR_ID)) {
flattrID = cursor.getString(i); flattrID = cursor.getString(i);
} else if (column.equals(AppProvider.DataColumns.CURRENT_VERSION)) { } else if (column.equals(AppProvider.DataColumns.SuggestedApk.VERSION)) {
curVersion = cursor.getString(i); suggestedVersion = cursor.getString(i);
} else if (column.equals(AppProvider.DataColumns.CURRENT_VERSION_CODE)) { } else if (column.equals(AppProvider.DataColumns.SUGGESTED_VERSION_CODE)) {
curVercode = cursor.getInt(i); suggestedVercode = cursor.getInt(i);
} else if (column.equals(AppProvider.DataColumns.UPSTREAM_VERSION_CODE)) {
upstreamVercode = cursor.getInt(i);
} else if (column.equals(AppProvider.DataColumns.UPSTREAM_VERSION)) {
upstreamVersion = cursor.getString(i);
} else if (column.equals(AppProvider.DataColumns.ADDED)) { } else if (column.equals(AppProvider.DataColumns.ADDED)) {
added = ValueObject.toDate(cursor.getString(i)); added = ValueObject.toDate(cursor.getString(i));
} else if (column.equals(AppProvider.DataColumns.LAST_UPDATED)) { } else if (column.equals(AppProvider.DataColumns.LAST_UPDATED)) {
@ -158,8 +172,9 @@ public class App extends ValueObject implements Comparable<App> {
values.put(AppProvider.DataColumns.FLATTR_ID, flattrID); values.put(AppProvider.DataColumns.FLATTR_ID, flattrID);
values.put(AppProvider.DataColumns.ADDED, added == null ? "" : Utils.DATE_FORMAT.format(added)); values.put(AppProvider.DataColumns.ADDED, added == null ? "" : Utils.DATE_FORMAT.format(added));
values.put(AppProvider.DataColumns.LAST_UPDATED, added == null ? "" : Utils.DATE_FORMAT.format(lastUpdated)); values.put(AppProvider.DataColumns.LAST_UPDATED, added == null ? "" : Utils.DATE_FORMAT.format(lastUpdated));
values.put(AppProvider.DataColumns.CURRENT_VERSION, curVersion); values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVercode);
values.put(AppProvider.DataColumns.CURRENT_VERSION_CODE, curVercode); values.put(AppProvider.DataColumns.UPSTREAM_VERSION, upstreamVersion);
values.put(AppProvider.DataColumns.UPSTREAM_VERSION_CODE, upstreamVercode);
values.put(AppProvider.DataColumns.CATEGORIES, Utils.CommaSeparatedList.str(categories)); values.put(AppProvider.DataColumns.CATEGORIES, Utils.CommaSeparatedList.str(categories));
values.put(AppProvider.DataColumns.ANTI_FEATURES, Utils.CommaSeparatedList.str(antiFeatures)); values.put(AppProvider.DataColumns.ANTI_FEATURES, Utils.CommaSeparatedList.str(antiFeatures));
values.put(AppProvider.DataColumns.REQUIREMENTS, Utils.CommaSeparatedList.str(requirements)); values.put(AppProvider.DataColumns.REQUIREMENTS, Utils.CommaSeparatedList.str(requirements));
@ -207,9 +222,9 @@ public class App extends ValueObject implements Comparable<App> {
*/ */
public boolean hasUpdates(Context context) { public boolean hasUpdates(Context context) {
boolean updates = false; boolean updates = false;
if (curVercode > 0) { if (suggestedVercode > 0) {
int installedVerCode = getInstalledVerCode(context); int installedVerCode = getInstalledVerCode(context);
updates = (installedVerCode > 0 && installedVerCode < curVercode); updates = (installedVerCode > 0 && installedVerCode < suggestedVercode);
} }
return updates; return updates;
} }
@ -218,7 +233,7 @@ public class App extends ValueObject implements Comparable<App> {
// to be notified about them // to be notified about them
public boolean canAndWantToUpdate(Context context) { public boolean canAndWantToUpdate(Context context) {
boolean canUpdate = hasUpdates(context); boolean canUpdate = hasUpdates(context);
boolean wantsUpdate = !ignoreAllUpdates && ignoreThisUpdate < curVercode; boolean wantsUpdate = !ignoreAllUpdates && ignoreThisUpdate < suggestedVercode;
return canUpdate && wantsUpdate && !isFiltered(); return canUpdate && wantsUpdate && !isFiltered();
} }
@ -227,4 +242,8 @@ public class App extends ValueObject implements Comparable<App> {
public boolean isFiltered() { public boolean isFiltered() {
return new AppFilter().filter(this); return new AppFilter().filter(this);
} }
public String getSuggestedVersion() {
return suggestedVersion;
}
} }

View File

@ -60,7 +60,7 @@ public class AppProvider extends FDroidProvider {
public static List<String> categories(Context context) { public static List<String> categories(Context context) {
ContentResolver resolver = context.getContentResolver(); ContentResolver resolver = context.getContentResolver();
Uri uri = getContentUri(); Uri uri = getContentUri();
String[] projection = { "DISTINCT " + DataColumns.CATEGORIES }; String[] projection = { DataColumns.CATEGORIES };
Cursor cursor = resolver.query(uri, projection, null, null, null ); Cursor cursor = resolver.query(uri, projection, null, null, null );
Set<String> categorySet = new HashSet<String>(); Set<String> categorySet = new HashSet<String>();
if (cursor != null) { if (cursor != null) {
@ -110,7 +110,7 @@ public class AppProvider extends FDroidProvider {
public interface DataColumns { public interface DataColumns {
public static final String _ID = "rowid as _id"; public static final String _ID = "rowid as _id"; // Required for CursorLoaders
public static final String _COUNT = "_count"; public static final String _COUNT = "_count";
public static final String IS_COMPATIBLE = "compatible"; public static final String IS_COMPATIBLE = "compatible";
public static final String APP_ID = "id"; public static final String APP_ID = "id";
@ -127,8 +127,9 @@ public class AppProvider extends FDroidProvider {
public static final String LITECOIN_ADDR = "litecoinAddr"; public static final String LITECOIN_ADDR = "litecoinAddr";
public static final String DOGECOIN_ADDR = "dogecoinAddr"; public static final String DOGECOIN_ADDR = "dogecoinAddr";
public static final String FLATTR_ID = "flattrID"; public static final String FLATTR_ID = "flattrID";
public static final String CURRENT_VERSION = "curVersion"; public static final String SUGGESTED_VERSION_CODE = "suggestedVercode";
public static final String CURRENT_VERSION_CODE = "curVercode"; public static final String UPSTREAM_VERSION = "upstreamVersion";
public static final String UPSTREAM_VERSION_CODE = "upstreamVercode";
public static final String CURRENT_APK = null; public static final String CURRENT_APK = null;
public static final String ADDED = "added"; public static final String ADDED = "added";
public static final String LAST_UPDATED = "lastUpdated"; public static final String LAST_UPDATED = "lastUpdated";
@ -147,16 +148,73 @@ public class AppProvider extends FDroidProvider {
public static final String UPDATED = null; public static final String UPDATED = null;
public static final String APKS = null; public static final String APKS = null;
public interface SuggestedApk {
public static final String VERSION = "suggestedApkVersion";
}
public static String[] ALL = { public static String[] ALL = {
IS_COMPATIBLE, APP_ID, NAME, SUMMARY, ICON, DESCRIPTION, IS_COMPATIBLE, APP_ID, NAME, SUMMARY, ICON, DESCRIPTION,
LICENSE, WEB_URL, TRACKER_URL, SOURCE_URL, DONATE_URL, LICENSE, WEB_URL, TRACKER_URL, SOURCE_URL, DONATE_URL,
BITCOIN_ADDR, LITECOIN_ADDR, DOGECOIN_ADDR, FLATTR_ID, BITCOIN_ADDR, LITECOIN_ADDR, DOGECOIN_ADDR, FLATTR_ID,
CURRENT_VERSION, CURRENT_VERSION_CODE, ADDED, LAST_UPDATED, UPSTREAM_VERSION, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
CATEGORIES, ANTI_FEATURES, REQUIREMENTS, IGNORE_ALLUPDATES, CATEGORIES, ANTI_FEATURES, REQUIREMENTS, IGNORE_ALLUPDATES,
IGNORE_THISUPDATE, ICON_URL IGNORE_THISUPDATE, ICON_URL, SUGGESTED_VERSION_CODE,
SuggestedApk.VERSION
}; };
} }
private static class Query extends QueryBuilder {
private boolean isSuggestedApkTableAdded = false;
private boolean categoryFieldAdded = false;
@Override
protected String getRequiredTables() {
return DBHelper.TABLE_APP;
}
@Override
protected boolean isDistinct() {
return fieldCount() == 1 && categoryFieldAdded;
}
@Override
public void addField(String field) {
if (field.equals(DataColumns.SuggestedApk.VERSION)) {
addSuggestedApkVersionField();
} else if (field.equals(DataColumns._COUNT)) {
appendCountField();
} else {
if (field.equals(DataColumns.CATEGORIES)) {
categoryFieldAdded = true;
}
appendField(field, "fdroid_app");
}
}
private void appendCountField() {
appendField("COUNT(*) AS " + DataColumns._COUNT);
}
private void addSuggestedApkVersionField() {
addSuggestedApkField(
ApkProvider.DataColumns.VERSION,
DataColumns.SuggestedApk.VERSION);
}
private void addSuggestedApkField(String fieldName, String alias) {
if (!isSuggestedApkTableAdded) {
isSuggestedApkTableAdded = true;
leftJoin(
DBHelper.TABLE_APK,
"suggestedApk",
"fdroid_app.suggestedVercode = suggestedApk.vercode AND fdroid_app.id = suggestedApk.id");
}
appendField(fieldName, "suggestedApk", alias);
}
}
private static final String PROVIDER_NAME = "AppProvider"; private static final String PROVIDER_NAME = "AppProvider";
private static final UriMatcher matcher = new UriMatcher(-1); private static final UriMatcher matcher = new UriMatcher(-1);
@ -274,18 +332,18 @@ public class AppProvider extends FDroidProvider {
private QuerySelection queryCanUpdate() { private QuerySelection queryCanUpdate() {
Map<String, PackageInfo> installedApps = Utils.getInstalledApps(getContext()); Map<String, PackageInfo> installedApps = Utils.getInstalledApps(getContext());
String ignoreCurrent = " ignoreThisUpdate != curVercode "; String ignoreCurrent = " fdroid_app.ignoreThisUpdate != fdroid_app.suggestedVercode ";
String ignoreAll = " ignoreAllUpdates != 1 "; String ignoreAll = " fdroid_app.ignoreAllUpdates != 1 ";
String ignore = " ( " + ignoreCurrent + " AND " + ignoreAll + " ) "; String ignore = " ( " + ignoreCurrent + " AND " + ignoreAll + " ) ";
StringBuilder where = new StringBuilder( ignore + " AND ( 0 "); StringBuilder where = new StringBuilder( ignore + " AND ( 0 ");
String[] selectionArgs = new String[installedApps.size() * 2]; String[] selectionArgs = new String[installedApps.size() * 2];
int i = 0; int i = 0;
for (PackageInfo info : installedApps.values() ) { for (PackageInfo info : installedApps.values() ) {
where.append(" OR ( ") where.append(" OR ( fdroid_app.")
.append(AppProvider.DataColumns.APP_ID) .append(DataColumns.APP_ID)
.append(" = ? AND ") .append(" = ? AND fdroid_app.")
.append(DataColumns.CURRENT_VERSION_CODE) .append(DataColumns.SUGGESTED_VERSION_CODE)
.append(" > ?) "); .append(" > ?) ");
selectionArgs[ i * 2 ] = info.packageName; selectionArgs[ i * 2 ] = info.packageName;
selectionArgs[ i * 2 + 1 ] = Integer.toString(info.versionCode); selectionArgs[ i * 2 + 1 ] = Integer.toString(info.versionCode);
@ -302,7 +360,7 @@ public class AppProvider extends FDroidProvider {
String[] selectionArgs = new String[installedApps.size()]; String[] selectionArgs = new String[installedApps.size()];
int i = 0; int i = 0;
for (Map.Entry<String, PackageInfo> entry : installedApps.entrySet() ) { for (Map.Entry<String, PackageInfo> entry : installedApps.entrySet() ) {
where.append(" OR ") where.append(" OR fdroid_app.")
.append(AppProvider.DataColumns.APP_ID) .append(AppProvider.DataColumns.APP_ID)
.append(" = ? "); .append(" = ? ");
selectionArgs[i] = entry.getKey(); selectionArgs[i] = entry.getKey();
@ -316,27 +374,29 @@ public class AppProvider extends FDroidProvider {
private QuerySelection querySearch(String keywords) { private QuerySelection querySearch(String keywords) {
keywords = "%" + keywords + "%"; keywords = "%" + keywords + "%";
String selection = String selection =
"id like ? OR " + "fdroid_app.id like ? OR " +
"name like ? OR " + "fdroid_app.name like ? OR " +
"summary like ? OR " + "fdroid_app.summary like ? OR " +
"description like ? "; "fdroid_app.description like ? ";
String[] args = new String[] { keywords, keywords, keywords, keywords}; String[] args = new String[] { keywords, keywords, keywords, keywords};
return new QuerySelection(selection, args); return new QuerySelection(selection, args);
} }
private QuerySelection querySingle(String id) {
String selection = "fdroid_app.id = ?";
String[] args = { id };
return new QuerySelection(selection, args);
}
private QuerySelection queryNewlyAdded() { private QuerySelection queryNewlyAdded() {
String selection = "added > ?"; String selection = "fdroid_app.added > ?";
String[] args = new String[] { String[] args = { Utils.DATE_FORMAT.format(Preferences.get().calcMaxHistory()) };
Utils.DATE_FORMAT.format(Preferences.get().calcMaxHistory())
};
return new QuerySelection(selection, args); return new QuerySelection(selection, args);
} }
private QuerySelection queryRecentlyUpdated() { private QuerySelection queryRecentlyUpdated() {
String selection = "added != lastUpdated AND lastUpdated > ?"; String selection = "fdroid_app.added != fdroid_app.lastUpdated AND fdroid_app.lastUpdated > ?";
String[] args = new String[] { String[] args = { Utils.DATE_FORMAT.format(Preferences.get().calcMaxHistory()) };
Utils.DATE_FORMAT.format(Preferences.get().calcMaxHistory())
};
return new QuerySelection(selection, args); return new QuerySelection(selection, args);
} }
@ -344,11 +404,11 @@ public class AppProvider extends FDroidProvider {
// TODO: In the future, add a new table for categories, // TODO: In the future, add a new table for categories,
// so we can join onto it. // so we can join onto it.
String selection = String selection =
" categories = ? OR " + // Only category e.g. "internet" " fdroid_app.categories = ? OR " + // Only category e.g. "internet"
" categories LIKE ? OR " + // First category e.g. "internet,%" " fdroid_app.categories LIKE ? OR " + // First category e.g. "internet,%"
" categories LIKE ? OR " + // Last category e.g. "%,internet" " fdroid_app.categories LIKE ? OR " + // Last category e.g. "%,internet"
" categories LIKE ? "; // One of many categories e.g. "%,internet,%" " fdroid_app.categories LIKE ? "; // One of many categories e.g. "%,internet,%"
String[] args = new String[] { String[] args = {
category, category,
category + ",%", category + ",%",
"%," + category, "%," + category,
@ -364,7 +424,7 @@ public class AppProvider extends FDroidProvider {
private QuerySelection queryApps(String appIds) { private QuerySelection queryApps(String appIds) {
String[] args = appIds.split(","); String[] args = appIds.split(",");
String selection = "id IN (" + generateQuestionMarksForInClause(args.length) + ")"; String selection = "fdroid_app.id IN (" + generateQuestionMarksForInClause(args.length) + ")";
return new QuerySelection(selection, args); return new QuerySelection(selection, args);
} }
@ -376,9 +436,7 @@ public class AppProvider extends FDroidProvider {
break; break;
case CODE_SINGLE: case CODE_SINGLE:
query = query.add( query = query.add(querySingle(uri.getLastPathSegment()));
DataColumns.APP_ID + " = ?",
new String[] { uri.getLastPathSegment() } );
break; break;
case CAN_UPDATE: case CAN_UPDATE:
@ -406,12 +464,12 @@ public class AppProvider extends FDroidProvider {
break; break;
case RECENTLY_UPDATED: case RECENTLY_UPDATED:
sortOrder = DataColumns.LAST_UPDATED + " DESC"; sortOrder = " fdroid_app.lastUpdated DESC";
query = query.add(queryRecentlyUpdated()); query = query.add(queryRecentlyUpdated());
break; break;
case NEWLY_ADDED: case NEWLY_ADDED:
sortOrder = DataColumns.ADDED + " DESC"; sortOrder = " fdroid_app.added DESC";
query = query.add(queryNewlyAdded()); query = query.add(queryNewlyAdded());
break; break;
@ -421,18 +479,15 @@ public class AppProvider extends FDroidProvider {
} }
if (AppProvider.DataColumns.NAME.equals(sortOrder)) { if (AppProvider.DataColumns.NAME.equals(sortOrder)) {
sortOrder = " lower( " + sortOrder + " ) "; sortOrder = " lower( fdroid_app." + sortOrder + " ) ";
} }
for (String field : projection) { Query q = new Query();
if (field.equals(DataColumns._COUNT)) { q.addFields(projection);
projection = new String[] { "COUNT(*) AS " + DataColumns._COUNT }; q.addSelection(query.getSelection());
break; q.addOrderBy(sortOrder);
}
}
Cursor cursor = read().query(getTableName(), projection, query.getSelection(), Cursor cursor = read().rawQuery(q.toString(), query.getArgs());
query.getArgs(), null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri); cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor; return cursor;
} }
@ -472,7 +527,7 @@ public class AppProvider extends FDroidProvider {
switch (matcher.match(uri)) { switch (matcher.match(uri)) {
case CODE_SINGLE: case CODE_SINGLE:
query = query.add(new QuerySelection("id = ?", new String[] { uri.getLastPathSegment()})); query = query.add(querySingle(uri.getLastPathSegment()));
break; break;
default: default:

View File

@ -67,8 +67,9 @@ public class DBHelper extends SQLiteOpenHelper {
+ "webURL text, " + "webURL text, "
+ "trackerURL text, " + "trackerURL text, "
+ "sourceURL text, " + "sourceURL text, "
+ "curVersion text," + "suggestedVercode text,"
+ "curVercode integer," + "upstreamVersion text,"
+ "upstreamVercode integer,"
+ "antiFeatures string," + "antiFeatures string,"
+ "donateURL string," + "donateURL string,"
+ "bitcoinAddr string," + "bitcoinAddr string,"
@ -85,7 +86,7 @@ public class DBHelper extends SQLiteOpenHelper {
+ "iconUrl text, " + "iconUrl text, "
+ "primary key(id));"; + "primary key(id));";
private static final int DB_VERSION = 40; private static final int DB_VERSION = 41;
private Context context; private Context context;

View File

@ -0,0 +1,105 @@
package org.fdroid.fdroid.data;
import java.util.ArrayList;
import java.util.List;
abstract class QueryBuilder {
private List<String> fields = new ArrayList<String>();
private StringBuilder tables = new StringBuilder(getRequiredTables());
private String selection = null;
private String orderBy = null;
protected abstract String getRequiredTables();
public abstract void addField(String field);
protected int fieldCount() {
return fields.size();
}
public void addFields(String[] fields) {
for (String field : fields) {
addField(field);
}
}
protected boolean isDistinct() {
return false;
}
protected void appendField(String field) {
appendField(field, null, null);
}
protected void appendField(String field, String tableAlias) {
appendField(field, tableAlias, null);
}
protected final void appendField(String field, String tableAlias,
String fieldAlias) {
StringBuilder fieldBuilder = new StringBuilder();
if (tableAlias != null) {
fieldBuilder.append(tableAlias).append('.');
}
fieldBuilder.append(field);
if (fieldAlias != null) {
fieldBuilder.append(" AS ").append(fieldAlias);
}
fields.add(fieldBuilder.toString());
}
public void addSelection(String selection) {
this.selection = selection;
}
public void addOrderBy(String orderBy) {
this.orderBy = orderBy;
}
protected final void leftJoin(String table, String alias,
String condition) {
tables.append(" LEFT JOIN ");
tables.append(table);
if (alias != null) {
tables.append(" AS ");
tables.append(alias);
}
tables.append(" ON (");
tables.append(condition);
tables.append(")");
}
private String fieldsSql() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fields.size(); i ++) {
if (i > 0) {
sb.append(',');
}
sb.append(fields.get(i));
}
return sb.toString();
}
private String whereSql() {
return selection != null ? " WHERE " + selection : "";
}
private String orderBySql() {
return orderBy != null ? " ORDER BY " + orderBy : "";
}
private String tablesSql() {
return tables.toString();
}
public String toString() {
String distinct = isDistinct() ? " DISTINCT " : "";
return "SELECT " + distinct + fieldsSql() + " FROM " + tablesSql() + whereSql() + orderBySql();
}
}

View File

@ -130,14 +130,14 @@ abstract public class AppListAdapter extends CursorAdapter {
private String getVersionInfo(App app) { private String getVersionInfo(App app) {
if (app.curVercode <= 0) { if (app.suggestedVercode <= 0) {
return null; return null;
} }
PackageInfo installedInfo = app.getInstalledInfo(mContext); PackageInfo installedInfo = app.getInstalledInfo(mContext);
if (installedInfo == null) { if (installedInfo == null) {
return ellipsize(app.curVersion, 12); return ellipsize(app.getSuggestedVersion(), 12);
} }
String installedVersionString = installedInfo.versionName; String installedVersionString = installedInfo.versionName;
@ -145,7 +145,7 @@ abstract public class AppListAdapter extends CursorAdapter {
if (app.canAndWantToUpdate(mContext) && showStatusUpdate()) { if (app.canAndWantToUpdate(mContext) && showStatusUpdate()) {
return ellipsize(installedVersionString, 8) + return ellipsize(installedVersionString, 8) +
"" + ellipsize(app.curVersion, 8); "" + ellipsize(app.getSuggestedVersion(), 8);
} }
if (installedVersionCode > 0 && showStatusInstalled()) { if (installedVersionCode > 0 && showStatusInstalled()) {

View File

@ -27,7 +27,7 @@ abstract public class AppListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> { LoaderManager.LoaderCallbacks<Cursor> {
public static final String[] APP_PROJECTION = { public static final String[] APP_PROJECTION = {
AppProvider.DataColumns._ID, AppProvider.DataColumns._ID, // Required for cursor loader to work.
AppProvider.DataColumns.APP_ID, AppProvider.DataColumns.APP_ID,
AppProvider.DataColumns.NAME, AppProvider.DataColumns.NAME,
AppProvider.DataColumns.SUMMARY, AppProvider.DataColumns.SUMMARY,
@ -35,8 +35,8 @@ abstract public class AppListFragment extends ListFragment implements
AppProvider.DataColumns.LICENSE, AppProvider.DataColumns.LICENSE,
AppProvider.DataColumns.ICON, AppProvider.DataColumns.ICON,
AppProvider.DataColumns.ICON_URL, AppProvider.DataColumns.ICON_URL,
AppProvider.DataColumns.CURRENT_VERSION, AppProvider.DataColumns.SuggestedApk.VERSION,
AppProvider.DataColumns.CURRENT_VERSION_CODE, AppProvider.DataColumns.SUGGESTED_VERSION_CODE,
AppProvider.DataColumns.REQUIREMENTS, // Needed for filtering apps that require root. AppProvider.DataColumns.REQUIREMENTS, // Needed for filtering apps that require root.
}; };