Major revamp of database and app index handling

This commit is contained in:
Ciaran Gultnieks 2012-09-14 22:05:55 +01:00
parent c40572ca92
commit a14a926264
8 changed files with 328 additions and 171 deletions

View File

@ -111,10 +111,10 @@ public class AppDetails extends ListActivity {
else
status.setText(getString(R.string.not_inst));
TextView size = (TextView) v.findViewById(R.id.size);
if (apk.size == 0) {
if (apk.detail_size == 0) {
size.setText("");
} else {
size.setText(getFriendlySize(apk.size));
size.setText(getFriendlySize(apk.detail_size));
}
TextView buildtype = (TextView) v.findViewById(R.id.buildtype);
if (apk.srcname != null) {
@ -277,6 +277,16 @@ public class AppDetails extends ListActivity {
finish();
return;
}
// Make sure the app is populated.
try {
DB db = DB.getDB();
db.populateDetails(app);
} catch (Exception ex) {
Log.d("FDroid", "Failed to populate app - " + ex.getMessage());
} finally {
DB.releaseDB();
}
DB.Apk curver = app.getCurrentVersion();
app_currentvercode = curver == null ? 0 : curver.vercode;
@ -333,7 +343,7 @@ public class AppDetails extends ListActivity {
tv.setText(String.format(getString(R.string.details_installed),
app.installedVersion));
tv = (TextView) findViewById(R.id.description);
tv.setText(app.description);
tv.setText(app.detail_description);
if (pref_expert && mInstalledSignature != null) {
tv = (TextView) findViewById(R.id.signature);
tv.setText("Signed: " + mInstalledSigID);
@ -370,21 +380,21 @@ public class AppDetails extends ListActivity {
menu.add(Menu.NONE, UNINSTALL, 1, R.string.menu_uninstall).setIcon(
android.R.drawable.ic_menu_delete);
}
if (app.webURL.length() > 0) {
if (app.detail_webURL.length() > 0) {
menu.add(Menu.NONE, WEBSITE, 2, R.string.menu_website).setIcon(
android.R.drawable.ic_menu_view);
}
if (app.trackerURL.length() > 0) {
if (app.detail_trackerURL.length() > 0) {
menu.add(Menu.NONE, ISSUES, 3, R.string.menu_issues).setIcon(
android.R.drawable.ic_menu_view);
}
if (app.sourceURL.length() > 0) {
if (app.detail_sourceURL.length() > 0) {
menu.add(Menu.NONE, SOURCE, 4, R.string.menu_source).setIcon(
android.R.drawable.ic_menu_view);
}
menu.add(Menu.NONE, MARKET, 5, R.string.menu_market).setIcon(
android.R.drawable.ic_menu_view);
if (app.donateURL != null) {
if (app.detail_donateURL != null) {
menu.add(Menu.NONE, DONATE, 6, R.string.menu_donate).setIcon(
android.R.drawable.ic_menu_view);
}
@ -409,17 +419,17 @@ public class AppDetails extends ListActivity {
return true;
case WEBSITE:
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(app.webURL)));
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(app.detail_webURL)));
return true;
case ISSUES:
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.trackerURL)));
Uri.parse(app.detail_trackerURL)));
return true;
case SOURCE:
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.sourceURL)));
Uri.parse(app.detail_sourceURL)));
return true;
case MARKET:
@ -429,7 +439,7 @@ public class AppDetails extends ListActivity {
case DONATE:
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.donateURL)));
Uri.parse(app.detail_donateURL)));
return true;
}

View File

@ -47,6 +47,8 @@ import android.preference.PreferenceManager;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.Log;
public class DB {
private static Semaphore dbSync = new Semaphore(1, true);
@ -91,16 +93,11 @@ public class DB {
+ "summary text not null, " + "icon text, "
+ "description text not null, " + "license text not null, "
+ "webURL text, " + "trackerURL text, " + "sourceURL text, "
+ "curVersion text,"
+ "curVercode integer,"
+ "antiFeatures string,"
+ "donateURL string,"
+ "requirements string,"
+ "category string,"
+ "added string,"
+ "lastUpdated string,"
+ "primary key(id));";
+ "curVersion text," + "curVercode integer,"
+ "antiFeatures string," + "donateURL string,"
+ "requirements string," + "category string," + "added string,"
+ "lastUpdated string," + "primary key(id));";
public static class App implements Comparable<App> {
public App() {
@ -110,10 +107,10 @@ public class DB {
id = "unknown";
license = "Unknown";
category = "Uncategorized";
trackerURL = "";
sourceURL = "";
donateURL = null;
webURL = "";
detail_trackerURL = null;
detail_sourceURL = null;
detail_donateURL = null;
detail_webURL = null;
antiFeatures = null;
requirements = null;
hasUpdates = false;
@ -121,19 +118,36 @@ public class DB {
added = null;
lastUpdated = null;
apks = new Vector<Apk>();
detail_Populated = false;
}
// True when all the detail fields are populated, False otherwise.
public boolean detail_Populated;
public String id;
public String name;
public String summary;
public String icon;
public String description;
// Null when !detail_Populated
public String detail_description;
public String license;
public String category;
public String webURL;
public String trackerURL;
public String sourceURL;
public String donateURL; // Donate link, or null
// Null when !detail_Populated
public String detail_webURL;
// Null when !detail_Populated
public String detail_trackerURL;
// Null when !detail_Populated
public String detail_sourceURL;
// Donate link, or null
// Null when !detail_Populated
public String detail_donateURL;
public String curVersion;
public int curVercode;
public Date added;
@ -163,6 +177,7 @@ public class DB {
// Used internally for tracking during repo updates.
public boolean updated;
// List of apks.
public Vector<Apk> apks;
// Get the current version - this will be one of the Apks from 'apks'.
@ -208,36 +223,35 @@ public class DB {
+ " ( " + "id text not null, " + "version text not null, "
+ "server text not null, " + "hash text not null, "
+ "vercode int not null," + "apkName text not null, "
+ "size int not null,"
+ "apkSource text,"
+ "sig string,"
+ "srcname string,"
+ "minSdkVersion integer,"
+ "permissions string,"
+ "features string,"
+ "hashType string,"
+ "added string,"
+ "primary key(id));";
+ "size int not null," + "apkSource text," + "sig string,"
+ "srcname string," + "minSdkVersion integer,"
+ "permissions string," + "features string," + "hashType string,"
+ "added string," + "primary key(id,vercode));";
public static class Apk {
public Apk() {
updated = false;
size = 0;
detail_size = 0;
apkSource = null;
added = null;
detail_server = null;
detail_hash = null;
detail_hashType = null;
detail_permissions = null;
}
public String id;
public String version;
public int vercode;
public int size; // Size in bytes - 0 means we don't know!
public String server;
public String hash;
public String hashType;
public int detail_size; // Size in bytes - 0 means we don't know!
public String detail_server;
public String detail_hash;
public String detail_hashType;
public int minSdkVersion; // 0 if unknown
public Date added;
public CommaSeparatedList permissions; // null if empty or unknown
public CommaSeparatedList detail_permissions; // null if empty or
// unknown
public CommaSeparatedList features; // null if empty or unknown
// ID (md5 sum of public key) of signature. Might be null, in the
@ -260,7 +274,7 @@ public class DB {
public String getURL() {
String path = apkName.replace(" ", "%20");
return server + "/" + path;
return detail_server + "/" + path;
}
// Call isCompatible(apk) on an instance of this class to
@ -315,7 +329,8 @@ public class DB {
if (apk.features != null) {
for (String feat : apk.features) {
if (!features.contains(feat)) {
Log.d("FDroid", "Incompatible based on lack of "
Log.d("FDroid", apk.id
+ " is incompatible based on lack of "
+ feat);
return false;
}
@ -340,8 +355,21 @@ public class DB {
public String pubkey; // null for an unsigned repo
}
private final int DBVersion = 16;
private final int DBVersion = 17;
private static void createAppApk(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_APP);
db.execSQL("create index app_id on " + TABLE_APP + " (id);");
db.execSQL(CREATE_TABLE_APK);
db.execSQL("create index apk_vercode on " + TABLE_APK
+ " (vercode);");
db.execSQL("create index apk_id on " + TABLE_APK + " (id);");
}
public static void resetTransient(SQLiteDatabase db) {
db.execSQL("drop table " + TABLE_APP);
db.execSQL("drop table " + TABLE_APK);
createAppApk(db);
}
private class DBHelper extends SQLiteOpenHelper {
public DBHelper(Context context) {
@ -350,10 +378,10 @@ public class DB {
@Override
public void onCreate(SQLiteDatabase db) {
createAppApk(db);
db.execSQL(CREATE_TABLE_REPO);
db.execSQL(CREATE_TABLE_APP);
db.execSQL(CREATE_TABLE_APK);
db.execSQL("create index apk_vercode on " + TABLE_APK + " (vercode);");
ContentValues values = new ContentValues();
values.put("address",
mContext.getString(R.string.default_repo_address));
@ -366,14 +394,12 @@ public class DB {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table " + TABLE_APP);
db.execSQL("drop table " + TABLE_APK);
db.execSQL(CREATE_TABLE_APP);
db.execSQL(CREATE_TABLE_APK);
resetTransient(db);
if (oldVersion < 7)
db.execSQL("alter table " + TABLE_REPO + " add pubkey string");
}
}
public static File getDataPath() {
@ -418,6 +444,11 @@ public class DB {
db = null;
}
// Reset the transient data in the database.
public void reset() {
resetTransient(db);
}
// Delete the database, which should cause it to be re-created next time
// it's used.
public static void delete(Context ctx) {
@ -470,6 +501,52 @@ public class DB {
return result;
}
// Populate the details for the given app, if necessary.
public void populateDetails(App app) {
if (app.detail_Populated)
return;
Cursor c = null;
try {
String[] cols = new String[] { "description", "webURL",
"trackerURL", "sourceURL", "donateURL" };
c = db.query(TABLE_APP, cols, "id = ?", new String[] { app.id },
null, null, null, null);
c.moveToFirst();
app.detail_description = c.getString(0);
app.detail_webURL = c.getString(1);
app.detail_trackerURL = c.getString(2);
app.detail_sourceURL = c.getString(3);
app.detail_donateURL = c.getString(4);
c.close();
c = null;
cols = new String[] { "server", "hash", "hashType", "size",
"permissions" };
for (Apk apk : app.apks) {
c = db.query(
TABLE_APK,
cols,
"id = ? and vercode = " + Integer.toString(apk.vercode),
new String[] { apk.id }, null, null, null, null);
c.moveToFirst();
apk.detail_server = c.getString(0);
apk.detail_hash = c.getString(1);
apk.detail_hashType = c.getString(2);
apk.detail_size = c.getInt(3);
apk.detail_permissions = CommaSeparatedList
.make(c.getString(4));
c.close();
c = null;
}
app.detail_Populated = true;
} finally {
if (c != null)
c.close();
}
}
// Return a list of apps matching the given criteria. Filtering is
// also done based on compatibility and anti-features according to
// the user's current preferences.
@ -491,34 +568,28 @@ public class DB {
long startTime = System.currentTimeMillis();
try {
c = db.query(TABLE_APP, null, null, null, null, null, null);
String cols[] = new String[] { "antiFeatures", "requirements",
"id", "name", "summary", "icon", "license", "category",
"curVersion", "curVercode", "added", "lastUpdated" };
c = db.query(TABLE_APP, cols, null, null, null, null, null);
c.moveToFirst();
while (!c.isAfterLast()) {
App app = new App();
app.antiFeatures = DB.CommaSeparatedList.make(c.getString(c
.getColumnIndex("antiFeatures")));
app.requirements = DB.CommaSeparatedList.make(c.getString(c
.getColumnIndex("requirements")));
app.id = c.getString(c.getColumnIndex("id"));
app.name = c.getString(c.getColumnIndex("name"));
app.summary = c.getString(c.getColumnIndex("summary"));
app.icon = c.getString(c.getColumnIndex("icon"));
app.description = c.getString(c.getColumnIndex("description"));
app.license = c.getString(c.getColumnIndex("license"));
app.category = c.getString(c.getColumnIndex("category"));
app.webURL = c.getString(c.getColumnIndex("webURL"));
app.trackerURL = c.getString(c.getColumnIndex("trackerURL"));
app.sourceURL = c.getString(c.getColumnIndex("sourceURL"));
app.donateURL = c.getString(c.getColumnIndex("donateURL"));
app.curVersion = c.getString(c
.getColumnIndex("curVersion"));
app.curVercode = c.getInt(c.getColumnIndex("curVercode"));
String sAdded = c.getString(c.getColumnIndex("added"));
app.antiFeatures = DB.CommaSeparatedList.make(c.getString(0));
app.requirements = DB.CommaSeparatedList.make(c.getString(1));
app.id = c.getString(2);
app.name = c.getString(3);
app.summary = c.getString(4);
app.icon = c.getString(5);
app.license = c.getString(6);
app.category = c.getString(7);
app.curVersion = c.getString(8);
app.curVercode = c.getInt(9);
String sAdded = c.getString(10);
app.added = (sAdded == null || sAdded.length() == 0) ? null
: mDateFormat.parse(sAdded);
String sLastUpdated = c.getString(c
.getColumnIndex("lastUpdated"));
String sLastUpdated = c.getString(11);
app.lastUpdated = (sLastUpdated == null || sLastUpdated
.length() == 0) ? null : mDateFormat
.parse(sLastUpdated);
@ -543,30 +614,26 @@ public class DB {
Log.d("FDroid", "Read app data from database " + " (took "
+ (System.currentTimeMillis() - startTime) + " ms)");
c = db.query(TABLE_APK, null, null, null, null, null,
cols = new String[] { "id", "version", "vercode", "sig", "srcname",
"apkName", "apkSource", "minSdkVersion", "added",
"features" };
c = db.query(TABLE_APK, cols, null, null, null, null,
"vercode desc");
c.moveToFirst();
while (!c.isAfterLast()) {
Apk apk = new Apk();
apk.id = c.getString(c.getColumnIndex("id"));
apk.version = c.getString(c.getColumnIndex("version"));
apk.vercode = c.getInt(c.getColumnIndex("vercode"));
apk.server = c.getString(c.getColumnIndex("server"));
apk.hash = c.getString(c.getColumnIndex("hash"));
apk.hashType = c.getString(c.getColumnIndex("hashType"));
apk.sig = c.getString(c.getColumnIndex("sig"));
apk.srcname = c.getString(c.getColumnIndex("srcname"));
apk.size = c.getInt(c.getColumnIndex("size"));
apk.apkName = c.getString(c.getColumnIndex("apkName"));
apk.apkSource = c.getString(c.getColumnIndex("apkSource"));
apk.minSdkVersion = c.getInt(c.getColumnIndex("minSdkVersion"));
String sApkAdded = c.getString(c.getColumnIndex("added"));
apk.id = c.getString(0);
apk.version = c.getString(1);
apk.vercode = c.getInt(2);
apk.sig = c.getString(3);
apk.srcname = c.getString(4);
apk.apkName = c.getString(5);
apk.apkSource = c.getString(6);
apk.minSdkVersion = c.getInt(7);
String sApkAdded = c.getString(8);
apk.added = (sApkAdded == null || sApkAdded.length() == 0) ? null
: mDateFormat.parse(sApkAdded);
apk.permissions = CommaSeparatedList.make(c.getString(c
.getColumnIndex("permissions")));
apk.features = CommaSeparatedList.make(c.getString(c
.getColumnIndex("features")));
apk.features = CommaSeparatedList.make(c.getString(9));
apps.get(apk.id).apks.add(apk);
c.moveToNext();
}
@ -609,6 +676,27 @@ public class DB {
return result;
}
public Vector<String> doSearch(String query) {
Vector<String> ids = new Vector<String>();
Cursor c = null;
try {
String filter = "%" + query + "%";
c = db.query(TABLE_APP, new String[] { "id" },
"name like ? or description like ?", new String[] { filter,
filter }, null, null, null);
c.moveToFirst();
while (!c.isAfterLast()) {
ids.add(c.getString(0));
c.moveToNext();
}
} finally {
if (c != null)
c.close();
}
return ids;
}
public static class CommaSeparatedList implements Iterable<String> {
private String value;
@ -669,6 +757,7 @@ public class DB {
public void endUpdate() {
if (updateApps == null)
return;
Log.d("FDroid", "Processing endUpdate - " + updateApps.size() + " apps before");
for (App app : updateApps) {
if (!app.updated) {
// The application hasn't been updated, so it's no longer
@ -735,15 +824,13 @@ public class DB {
boolean found = false;
for (App app : updateApps) {
if (app.id.equals(upapp.id)) {
// Log.d("FDroid", "AppUpdate: " + app.id
// + " is already in the database.");
updateApp(app, upapp);
app.updated = true;
found = true;
for (Apk upapk : compatibleapks) {
boolean afound = false;
for (Apk apk : app.apks) {
if (apk.version.equals(upapk.version)) {
if (apk.vercode == upapk.vercode) {
// Log.d("FDroid", "AppUpdate: " + apk.version
// + " is a known version.");
updateApkIfDifferent(apk, upapk);
@ -754,8 +841,6 @@ public class DB {
}
if (!afound) {
// A new version of this application.
// Log.d("FDroid", "AppUpdate: " + upapk.version
// + " is a new version.");
updateApkIfDifferent(null, upapk);
upapk.updated = true;
app.apks.add(upapk);
@ -766,9 +851,6 @@ public class DB {
}
if (!found) {
// It's a brand new application...
// Log
// .d("FDroid", "AppUpdate: " + upapp.id
// + " is a new application.");
updateApp(null, upapp);
for (Apk upapk : compatibleapks) {
updateApkIfDifferent(null, upapk);
@ -791,13 +873,13 @@ public class DB {
values.put("name", upapp.name);
values.put("summary", upapp.summary);
values.put("icon", upapp.icon);
values.put("description", upapp.description);
values.put("description", upapp.detail_description);
values.put("license", upapp.license);
values.put("category", upapp.category);
values.put("webURL", upapp.webURL);
values.put("trackerURL", upapp.trackerURL);
values.put("sourceURL", upapp.sourceURL);
values.put("donateURL", upapp.donateURL);
values.put("webURL", upapp.detail_webURL);
values.put("trackerURL", upapp.detail_trackerURL);
values.put("sourceURL", upapp.detail_sourceURL);
values.put("donateURL", upapp.detail_donateURL);
values.put("added",
upapp.added == null ? "" : mDateFormat.format(upapp.added));
values.put(
@ -826,22 +908,23 @@ public class DB {
values.put("id", upapk.id);
values.put("version", upapk.version);
values.put("vercode", upapk.vercode);
values.put("server", upapk.server);
values.put("hash", upapk.hash);
values.put("hashType", upapk.hashType);
values.put("server", upapk.detail_server);
values.put("hash", upapk.detail_hash);
values.put("hashType", upapk.detail_hashType);
values.put("sig", upapk.sig);
values.put("srcname", upapk.srcname);
values.put("size", upapk.size);
values.put("size", upapk.detail_size);
values.put("apkName", upapk.apkName);
values.put("apkSource", upapk.apkSource);
values.put("minSdkVersion", upapk.minSdkVersion);
values.put("added",
upapk.added == null ? "" : mDateFormat.format(upapk.added));
values.put("permissions", CommaSeparatedList.str(upapk.permissions));
values.put("permissions",
CommaSeparatedList.str(upapk.detail_permissions));
values.put("features", CommaSeparatedList.str(upapk.features));
if (oldapk != null) {
db.update(TABLE_APK, values, "id = ? and version =?", new String[] {
oldapk.id, oldapk.version });
db.update(TABLE_APK, values, "id = ? and vercode = " + Integer.toString(oldapk.vercode),
new String[] { oldapk.id });
} else {
db.insert(TABLE_APK, null, values);
}

View File

@ -47,10 +47,10 @@ public class Downloader extends Thread {
private int max;
private String errorMessage;
// Constructor - creates a Downloader to download the given Apk,
// which must have its detail populated.
Downloader(DB.Apk apk) {
synchronized (this) {
curapk = apk;
}
curapk = apk;
}
public synchronized Status getStatus() {
@ -99,8 +99,8 @@ public class Downloader extends Thread {
// See if we already have this apk cached...
if (localfile.exists()) {
// We do - if its hash matches, we'll use it...
Hasher hash = new Hasher(curapk.hashType, localfile);
if (hash.match(curapk.hash)) {
Hasher hash = new Hasher(curapk.detail_hashType, localfile);
if (hash.match(curapk.detail_hash)) {
Log.d("FDroid", "Using cached apk at " + localfile);
synchronized (this) {
progress = 1;
@ -117,7 +117,7 @@ public class Downloader extends Thread {
// If we haven't got the apk locally, we'll have to download it...
String remotefile;
if (curapk.apkSource == null) {
remotefile = curapk.server + "/" + apkname.replace(" ", "%20");
remotefile = curapk.detail_server + "/" + apkname.replace(" ", "%20");
} else {
remotefile = curapk.apkSource;
}
@ -125,7 +125,7 @@ public class Downloader extends Thread {
synchronized (this) {
filename = remotefile;
progress = 0;
max = curapk.size;
max = curapk.detail_size;
status = Status.RUNNING;
}
@ -161,11 +161,11 @@ public class Downloader extends Thread {
}
return;
}
Hasher hash = new Hasher(curapk.hashType, localfile);
if (!hash.match(curapk.hash)) {
Hasher hash = new Hasher(curapk.detail_hashType, localfile);
if (!hash.match(curapk.detail_hash)) {
synchronized (this) {
Log.d("FDroid", "Downloaded file hash of " + hash.getHash()
+ " did not match repo's " + curapk.hash);
+ " did not match repo's " + curapk.detail_hash);
// No point keeping a bad file, whether we're
// caching or not.
localfile.delete();

View File

@ -256,6 +256,7 @@ public class FDroid extends TabActivity implements OnItemClickListener,
// unschedule) the service accordingly. It's cheap, so no need to
// check if the particular setting has actually been changed.
UpdateService.schedule(getBaseContext());
populateLists();
break;
}

View File

@ -18,6 +18,9 @@
package org.fdroid.fdroid;
import java.io.File;
import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
@ -35,12 +38,33 @@ public class Preferences extends PreferenceActivity {
r.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
DB.delete(getApplicationContext());
// TODO: Clear cached apks and icons too.
Toast
.makeText(getBaseContext(),
"Local cached data has been cleared",
Toast.LENGTH_LONG).show();
// TODO: Progress dialog + thread is needed, it can take a
// while to delete all the icons and cached apks in a long
// standing install!
Toast.makeText(getBaseContext(),
"Hold on...", Toast.LENGTH_SHORT)
.show();
// TODO: This is going to cause problems if there is background
// update in progress at the time!
try {
DB db = DB.getDB();
db.reset();
} finally {
DB.releaseDB();
}
((FDroidApp) getApplication()).invalidateApps();
File dp = DB.getDataPath();
deleteAll(dp);
dp.mkdir();
DB.getIconsPath().mkdir();
Toast.makeText(getBaseContext(),
"Local cached data has been cleared", Toast.LENGTH_LONG)
.show();
return true;
}
@ -48,4 +72,22 @@ public class Preferences extends PreferenceActivity {
}
@Override
public void finish() {
Intent ret = new Intent();
this.setResult(RESULT_OK, ret);
super.finish();
}
private void deleteAll(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (int i = 0; i < children.length; i++) {
deleteAll(new File(dir, children[i]));
}
}
dir.delete();
}
}

View File

@ -99,8 +99,9 @@ public class RepoXMLHandler extends DefaultHandler {
// going to be stupid if the list gets very big!
boolean merged = false;
for (DB.App app : apps) {
if (app.id == curapp.id) {
if (app.id.equals(curapp.id)) {
app.apks.addAll(curapp.apks);
merged = true;
break;
}
}
@ -123,19 +124,19 @@ public class RepoXMLHandler extends DefaultHandler {
}
} else if (curel.equals("size")) {
try {
curapk.size = Integer.parseInt(str);
curapk.detail_size = Integer.parseInt(str);
} catch (NumberFormatException ex) {
curapk.size = 0;
curapk.detail_size = 0;
}
} else if (curel.equals("hash")) {
if (hashType == null || hashType.equals("md5")) {
if (curapk.hash == null) {
curapk.hash = str;
curapk.hashType = "MD5";
if (curapk.detail_hash == null) {
curapk.detail_hash = str;
curapk.detail_hashType = "MD5";
}
} else if (hashType.equals("sha256")) {
curapk.hash = str;
curapk.hashType = "SHA-256";
curapk.detail_hash = str;
curapk.detail_hashType = "SHA-256";
}
} else if (curel.equals("sig")) {
curapk.sig = str;
@ -159,7 +160,7 @@ public class RepoXMLHandler extends DefaultHandler {
curapk.added = null;
}
} else if (curel.equals("permissions")) {
curapk.permissions = DB.CommaSeparatedList.make(str);
curapk.detail_permissions = DB.CommaSeparatedList.make(str);
} else if (curel.equals("features")) {
curapk.features = DB.CommaSeparatedList.make(str);
}
@ -171,7 +172,7 @@ public class RepoXMLHandler extends DefaultHandler {
} else if (curel.equals("icon")) {
curapp.icon = str;
} else if (curel.equals("description")) {
curapp.description = str;
curapp.detail_description = str;
} else if (curel.equals("summary")) {
curapp.summary = str;
} else if (curel.equals("license")) {
@ -179,13 +180,13 @@ public class RepoXMLHandler extends DefaultHandler {
} else if (curel.equals("category")) {
curapp.category = str;
} else if (curel.equals("source")) {
curapp.sourceURL = str;
curapp.detail_sourceURL = str;
} else if (curel.equals("donate")) {
curapp.donateURL = str;
curapp.detail_donateURL = str;
} else if (curel.equals("web")) {
curapp.webURL = str;
curapp.detail_webURL = str;
} else if (curel.equals("tracker")) {
curapp.trackerURL = str;
curapp.detail_trackerURL = str;
} else if (curel.equals("added")) {
try {
curapp.added = str.length() == 0 ? null : mXMLDateFormat
@ -228,10 +229,11 @@ public class RepoXMLHandler extends DefaultHandler {
pubkey = pk;
} else if (localName == "application" && curapp == null) {
curapp = new DB.App();
curapp.detail_Populated = true;
} else if (localName == "package" && curapp != null && curapk == null) {
curapk = new DB.Apk();
curapk.id = curapp.id;
curapk.server = server;
curapk.detail_server = server;
hashType = null;
} else if (localName == "hash" && curapk != null) {
hashType = attributes.getValue("", "type");

View File

@ -32,7 +32,7 @@ import android.widget.ListView;
import android.widget.TextView;
public class SearchResults extends ListActivity {
private static final int REQUEST_APPDETAILS = 0;
private static final int SEARCH = Menu.FIRST;
@ -40,7 +40,7 @@ public class SearchResults extends ListActivity {
private AppListAdapter applist = new AppListAdapter(this);
private String mQuery;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -68,37 +68,55 @@ public class SearchResults extends ListActivity {
private void updateView() {
Vector<DB.App> apps= new Vector<DB.App>();
Vector<String> matchingids = new Vector<String>();
try {
DB db = DB.getDB();
matchingids = db.doSearch(mQuery);
} catch (Exception ex) {
Log.d("FDroid", "Search failed - " + ex.getMessage());
} finally {
DB.releaseDB();
}
Vector<DB.App> apps = new Vector<DB.App>();
AppFilter appfilter = new AppFilter(this);
String mq = mQuery.toLowerCase();
Vector<DB.App> tapps = ((FDroidApp) getApplication()).getApps();
for(DB.App tapp : tapps) {
if(tapp.name.toLowerCase().contains(mq) || tapp.description.toLowerCase().contains(mq)) {
if(!appfilter.filter(tapp))
apps.add(tapp);
for (DB.App tapp : tapps) {
boolean include = false;
for (String tid : matchingids) {
if (tid.equals(tapp.id)) {
include = true;
break;
}
}
if (include && !appfilter.filter(tapp))
apps.add(tapp);
}
TextView tv = (TextView) findViewById(R.id.description);
String headertext;
if(apps.size()==0)
headertext = String.format(getString(R.string.searchres_noapps),mQuery);
else if(apps.size()==1)
headertext = String.format(getString(R.string.searchres_oneapp),mQuery);
if (apps.size() == 0)
headertext = String.format(getString(R.string.searchres_noapps),
mQuery);
else if (apps.size() == 1)
headertext = String.format(getString(R.string.searchres_oneapp),
mQuery);
else
headertext = String.format(getString(R.string.searchres_napps),apps.size(),mQuery);
headertext = String.format(getString(R.string.searchres_napps),
apps.size(), mQuery);
tv.setText(headertext);
Log.d("FDroid", "Search for '" + mQuery + "' returned "
+ apps.size() + " results");
Log.d("FDroid", "Search for '" + mQuery + "' returned " + apps.size()
+ " results");
applist.clear();
for (DB.App app : apps) {
applist.addItem(app);
}
applist.notifyDataSetChanged();
setListAdapter(applist);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
final DB.App app;
@ -127,10 +145,9 @@ public class SearchResults extends ListActivity {
case SEARCH:
onSearchRequested();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -82,7 +82,8 @@ public class UpdateService extends IntentService {
// See if it's time to actually do anything yet...
if (receiver == null) {
long lastUpdate = prefs.getLong("lastUpdateCheck", 0);
long lastUpdate = prefs.getLong("lastUpdateCheck",
System.currentTimeMillis());
String sint = prefs.getString("updateInterval", "0");
int interval = Integer.parseInt(sint);
if (interval == 0)
@ -127,7 +128,8 @@ public class UpdateService extends IntentService {
}
if (success) {
Vector<DB.App> prevapps = ((FDroidApp)getApplication()).getApps();
Vector<DB.App> prevapps = ((FDroidApp) getApplication())
.getApps();
db = DB.getDB();
try {
prevUpdates = db.beginUpdate(prevapps);
@ -135,7 +137,7 @@ public class UpdateService extends IntentService {
db.updateApplication(app);
}
db.endUpdate();
((FDroidApp)getApplication()).invalidateApps();
((FDroidApp) getApplication()).invalidateApps();
if (notify)
newUpdates = db.getNumUpdates();
} catch (Exception ex) {