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 else
status.setText(getString(R.string.not_inst)); status.setText(getString(R.string.not_inst));
TextView size = (TextView) v.findViewById(R.id.size); TextView size = (TextView) v.findViewById(R.id.size);
if (apk.size == 0) { if (apk.detail_size == 0) {
size.setText(""); size.setText("");
} else { } else {
size.setText(getFriendlySize(apk.size)); size.setText(getFriendlySize(apk.detail_size));
} }
TextView buildtype = (TextView) v.findViewById(R.id.buildtype); TextView buildtype = (TextView) v.findViewById(R.id.buildtype);
if (apk.srcname != null) { if (apk.srcname != null) {
@ -277,6 +277,16 @@ public class AppDetails extends ListActivity {
finish(); finish();
return; 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(); DB.Apk curver = app.getCurrentVersion();
app_currentvercode = curver == null ? 0 : curver.vercode; 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), tv.setText(String.format(getString(R.string.details_installed),
app.installedVersion)); app.installedVersion));
tv = (TextView) findViewById(R.id.description); tv = (TextView) findViewById(R.id.description);
tv.setText(app.description); tv.setText(app.detail_description);
if (pref_expert && mInstalledSignature != null) { if (pref_expert && mInstalledSignature != null) {
tv = (TextView) findViewById(R.id.signature); tv = (TextView) findViewById(R.id.signature);
tv.setText("Signed: " + mInstalledSigID); tv.setText("Signed: " + mInstalledSigID);
@ -370,21 +380,21 @@ public class AppDetails extends ListActivity {
menu.add(Menu.NONE, UNINSTALL, 1, R.string.menu_uninstall).setIcon( menu.add(Menu.NONE, UNINSTALL, 1, R.string.menu_uninstall).setIcon(
android.R.drawable.ic_menu_delete); 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( menu.add(Menu.NONE, WEBSITE, 2, R.string.menu_website).setIcon(
android.R.drawable.ic_menu_view); 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( menu.add(Menu.NONE, ISSUES, 3, R.string.menu_issues).setIcon(
android.R.drawable.ic_menu_view); 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( menu.add(Menu.NONE, SOURCE, 4, R.string.menu_source).setIcon(
android.R.drawable.ic_menu_view); android.R.drawable.ic_menu_view);
} }
menu.add(Menu.NONE, MARKET, 5, R.string.menu_market).setIcon( menu.add(Menu.NONE, MARKET, 5, R.string.menu_market).setIcon(
android.R.drawable.ic_menu_view); 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( menu.add(Menu.NONE, DONATE, 6, R.string.menu_donate).setIcon(
android.R.drawable.ic_menu_view); android.R.drawable.ic_menu_view);
} }
@ -409,17 +419,17 @@ public class AppDetails extends ListActivity {
return true; return true;
case WEBSITE: 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; return true;
case ISSUES: case ISSUES:
startActivity(new Intent(Intent.ACTION_VIEW, startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.trackerURL))); Uri.parse(app.detail_trackerURL)));
return true; return true;
case SOURCE: case SOURCE:
startActivity(new Intent(Intent.ACTION_VIEW, startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.sourceURL))); Uri.parse(app.detail_sourceURL)));
return true; return true;
case MARKET: case MARKET:
@ -429,7 +439,7 @@ public class AppDetails extends ListActivity {
case DONATE: case DONATE:
startActivity(new Intent(Intent.ACTION_VIEW, startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(app.donateURL))); Uri.parse(app.detail_donateURL)));
return true; return true;
} }

View File

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

View File

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

View File

@ -18,6 +18,9 @@
package org.fdroid.fdroid; package org.fdroid.fdroid;
import java.io.File;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
@ -35,12 +38,33 @@ public class Preferences extends PreferenceActivity {
r.setOnPreferenceClickListener(new OnPreferenceClickListener() { r.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
DB.delete(getApplicationContext());
// TODO: Clear cached apks and icons too. // TODO: Progress dialog + thread is needed, it can take a
Toast // while to delete all the icons and cached apks in a long
.makeText(getBaseContext(), // standing install!
"Local cached data has been cleared", Toast.makeText(getBaseContext(),
Toast.LENGTH_LONG).show(); "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; 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! // going to be stupid if the list gets very big!
boolean merged = false; boolean merged = false;
for (DB.App app : apps) { for (DB.App app : apps) {
if (app.id == curapp.id) { if (app.id.equals(curapp.id)) {
app.apks.addAll(curapp.apks); app.apks.addAll(curapp.apks);
merged = true;
break; break;
} }
} }
@ -123,19 +124,19 @@ public class RepoXMLHandler extends DefaultHandler {
} }
} else if (curel.equals("size")) { } else if (curel.equals("size")) {
try { try {
curapk.size = Integer.parseInt(str); curapk.detail_size = Integer.parseInt(str);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
curapk.size = 0; curapk.detail_size = 0;
} }
} else if (curel.equals("hash")) { } else if (curel.equals("hash")) {
if (hashType == null || hashType.equals("md5")) { if (hashType == null || hashType.equals("md5")) {
if (curapk.hash == null) { if (curapk.detail_hash == null) {
curapk.hash = str; curapk.detail_hash = str;
curapk.hashType = "MD5"; curapk.detail_hashType = "MD5";
} }
} else if (hashType.equals("sha256")) { } else if (hashType.equals("sha256")) {
curapk.hash = str; curapk.detail_hash = str;
curapk.hashType = "SHA-256"; curapk.detail_hashType = "SHA-256";
} }
} else if (curel.equals("sig")) { } else if (curel.equals("sig")) {
curapk.sig = str; curapk.sig = str;
@ -159,7 +160,7 @@ public class RepoXMLHandler extends DefaultHandler {
curapk.added = null; curapk.added = null;
} }
} else if (curel.equals("permissions")) { } else if (curel.equals("permissions")) {
curapk.permissions = DB.CommaSeparatedList.make(str); curapk.detail_permissions = DB.CommaSeparatedList.make(str);
} else if (curel.equals("features")) { } else if (curel.equals("features")) {
curapk.features = DB.CommaSeparatedList.make(str); curapk.features = DB.CommaSeparatedList.make(str);
} }
@ -171,7 +172,7 @@ public class RepoXMLHandler extends DefaultHandler {
} else if (curel.equals("icon")) { } else if (curel.equals("icon")) {
curapp.icon = str; curapp.icon = str;
} else if (curel.equals("description")) { } else if (curel.equals("description")) {
curapp.description = str; curapp.detail_description = str;
} else if (curel.equals("summary")) { } else if (curel.equals("summary")) {
curapp.summary = str; curapp.summary = str;
} else if (curel.equals("license")) { } else if (curel.equals("license")) {
@ -179,13 +180,13 @@ public class RepoXMLHandler extends DefaultHandler {
} else if (curel.equals("category")) { } else if (curel.equals("category")) {
curapp.category = str; curapp.category = str;
} else if (curel.equals("source")) { } else if (curel.equals("source")) {
curapp.sourceURL = str; curapp.detail_sourceURL = str;
} else if (curel.equals("donate")) { } else if (curel.equals("donate")) {
curapp.donateURL = str; curapp.detail_donateURL = str;
} else if (curel.equals("web")) { } else if (curel.equals("web")) {
curapp.webURL = str; curapp.detail_webURL = str;
} else if (curel.equals("tracker")) { } else if (curel.equals("tracker")) {
curapp.trackerURL = str; curapp.detail_trackerURL = str;
} else if (curel.equals("added")) { } else if (curel.equals("added")) {
try { try {
curapp.added = str.length() == 0 ? null : mXMLDateFormat curapp.added = str.length() == 0 ? null : mXMLDateFormat
@ -228,10 +229,11 @@ public class RepoXMLHandler extends DefaultHandler {
pubkey = pk; pubkey = pk;
} else if (localName == "application" && curapp == null) { } else if (localName == "application" && curapp == null) {
curapp = new DB.App(); curapp = new DB.App();
curapp.detail_Populated = true;
} else if (localName == "package" && curapp != null && curapk == null) { } else if (localName == "package" && curapp != null && curapk == null) {
curapk = new DB.Apk(); curapk = new DB.Apk();
curapk.id = curapp.id; curapk.id = curapp.id;
curapk.server = server; curapk.detail_server = server;
hashType = null; hashType = null;
} else if (localName == "hash" && curapk != null) { } else if (localName == "hash" && curapk != null) {
hashType = attributes.getValue("", "type"); hashType = attributes.getValue("", "type");

View File

@ -32,7 +32,7 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
public class SearchResults extends ListActivity { public class SearchResults extends ListActivity {
private static final int REQUEST_APPDETAILS = 0; private static final int REQUEST_APPDETAILS = 0;
private static final int SEARCH = Menu.FIRST; private static final int SEARCH = Menu.FIRST;
@ -40,7 +40,7 @@ public class SearchResults extends ListActivity {
private AppListAdapter applist = new AppListAdapter(this); private AppListAdapter applist = new AppListAdapter(this);
private String mQuery; private String mQuery;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -68,37 +68,55 @@ public class SearchResults extends ListActivity {
private void updateView() { 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); AppFilter appfilter = new AppFilter(this);
String mq = mQuery.toLowerCase();
Vector<DB.App> tapps = ((FDroidApp) getApplication()).getApps(); Vector<DB.App> tapps = ((FDroidApp) getApplication()).getApps();
for(DB.App tapp : tapps) { for (DB.App tapp : tapps) {
if(tapp.name.toLowerCase().contains(mq) || tapp.description.toLowerCase().contains(mq)) { boolean include = false;
if(!appfilter.filter(tapp)) for (String tid : matchingids) {
apps.add(tapp); if (tid.equals(tapp.id)) {
include = true;
break;
}
} }
if (include && !appfilter.filter(tapp))
apps.add(tapp);
} }
TextView tv = (TextView) findViewById(R.id.description); TextView tv = (TextView) findViewById(R.id.description);
String headertext; String headertext;
if(apps.size()==0) if (apps.size() == 0)
headertext = String.format(getString(R.string.searchres_noapps),mQuery); headertext = String.format(getString(R.string.searchres_noapps),
else if(apps.size()==1) mQuery);
headertext = String.format(getString(R.string.searchres_oneapp),mQuery); else if (apps.size() == 1)
headertext = String.format(getString(R.string.searchres_oneapp),
mQuery);
else 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); tv.setText(headertext);
Log.d("FDroid", "Search for '" + mQuery + "' returned " Log.d("FDroid", "Search for '" + mQuery + "' returned " + apps.size()
+ apps.size() + " results"); + " results");
applist.clear(); applist.clear();
for (DB.App app : apps) { for (DB.App app : apps) {
applist.addItem(app); applist.addItem(app);
} }
applist.notifyDataSetChanged(); applist.notifyDataSetChanged();
setListAdapter(applist); setListAdapter(applist);
} }
@Override @Override
protected void onListItemClick(ListView l, View v, int position, long id) { protected void onListItemClick(ListView l, View v, int position, long id) {
final DB.App app; final DB.App app;
@ -127,10 +145,9 @@ public class SearchResults extends ListActivity {
case SEARCH: case SEARCH:
onSearchRequested(); onSearchRequested();
return true; return true;
} }
return super.onOptionsItemSelected(item); 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... // See if it's time to actually do anything yet...
if (receiver == null) { if (receiver == null) {
long lastUpdate = prefs.getLong("lastUpdateCheck", 0); long lastUpdate = prefs.getLong("lastUpdateCheck",
System.currentTimeMillis());
String sint = prefs.getString("updateInterval", "0"); String sint = prefs.getString("updateInterval", "0");
int interval = Integer.parseInt(sint); int interval = Integer.parseInt(sint);
if (interval == 0) if (interval == 0)
@ -127,7 +128,8 @@ public class UpdateService extends IntentService {
} }
if (success) { if (success) {
Vector<DB.App> prevapps = ((FDroidApp)getApplication()).getApps(); Vector<DB.App> prevapps = ((FDroidApp) getApplication())
.getApps();
db = DB.getDB(); db = DB.getDB();
try { try {
prevUpdates = db.beginUpdate(prevapps); prevUpdates = db.beginUpdate(prevapps);
@ -135,7 +137,7 @@ public class UpdateService extends IntentService {
db.updateApplication(app); db.updateApplication(app);
} }
db.endUpdate(); db.endUpdate();
((FDroidApp)getApplication()).invalidateApps(); ((FDroidApp) getApplication()).invalidateApps();
if (notify) if (notify)
newUpdates = db.getNumUpdates(); newUpdates = db.getNumUpdates();
} catch (Exception ex) { } catch (Exception ex) {