From a14a9262647655b39f39a96cd12e9b82fe18de96 Mon Sep 17 00:00:00 2001 From: Ciaran Gultnieks Date: Fri, 14 Sep 2012 22:05:55 +0100 Subject: [PATCH] Major revamp of database and app index handling --- src/org/fdroid/fdroid/AppDetails.java | 32 ++- src/org/fdroid/fdroid/DB.java | 295 ++++++++++++++-------- src/org/fdroid/fdroid/Downloader.java | 20 +- src/org/fdroid/fdroid/FDroid.java | 1 + src/org/fdroid/fdroid/Preferences.java | 54 +++- src/org/fdroid/fdroid/RepoXMLHandler.java | 32 +-- src/org/fdroid/fdroid/SearchResults.java | 57 +++-- src/org/fdroid/fdroid/UpdateService.java | 8 +- 8 files changed, 328 insertions(+), 171 deletions(-) diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index dd0ba181f..862bd6227 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -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; } diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index eeb60ce89..840aaee06 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -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 { 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(); + 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 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 doSearch(String query) { + + Vector ids = new Vector(); + 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 { 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); } diff --git a/src/org/fdroid/fdroid/Downloader.java b/src/org/fdroid/fdroid/Downloader.java index 27a4158a6..74503b1ef 100644 --- a/src/org/fdroid/fdroid/Downloader.java +++ b/src/org/fdroid/fdroid/Downloader.java @@ -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(); diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index ea9ef8b9f..6b4950a08 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -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; } diff --git a/src/org/fdroid/fdroid/Preferences.java b/src/org/fdroid/fdroid/Preferences.java index 3f2cb4758..73580a1e4 100644 --- a/src/org/fdroid/fdroid/Preferences.java +++ b/src/org/fdroid/fdroid/Preferences.java @@ -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(); + } + } diff --git a/src/org/fdroid/fdroid/RepoXMLHandler.java b/src/org/fdroid/fdroid/RepoXMLHandler.java index 5eacca815..25d4db507 100644 --- a/src/org/fdroid/fdroid/RepoXMLHandler.java +++ b/src/org/fdroid/fdroid/RepoXMLHandler.java @@ -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"); diff --git a/src/org/fdroid/fdroid/SearchResults.java b/src/org/fdroid/fdroid/SearchResults.java index 82dac1280..7591d882b 100644 --- a/src/org/fdroid/fdroid/SearchResults.java +++ b/src/org/fdroid/fdroid/SearchResults.java @@ -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 apps= new Vector(); + Vector matchingids = new Vector(); + try { + DB db = DB.getDB(); + matchingids = db.doSearch(mQuery); + } catch (Exception ex) { + Log.d("FDroid", "Search failed - " + ex.getMessage()); + } finally { + DB.releaseDB(); + } + + Vector apps = new Vector(); AppFilter appfilter = new AppFilter(this); - String mq = mQuery.toLowerCase(); Vector 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); } - - + } diff --git a/src/org/fdroid/fdroid/UpdateService.java b/src/org/fdroid/fdroid/UpdateService.java index 3c72d0a10..6893d809f 100644 --- a/src/org/fdroid/fdroid/UpdateService.java +++ b/src/org/fdroid/fdroid/UpdateService.java @@ -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 prevapps = ((FDroidApp)getApplication()).getApps(); + Vector 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) {