diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index fec90e1c3..b6995b273 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -307,7 +307,7 @@ public class AppDetails extends ListActivity { // Make sure the app is populated. try { DB db = DB.getDB(); - db.populateDetails(app, null); + db.populateDetails(app, 0); } catch (Exception ex) { Log.d("FDroid", "Failed to populate app - " + ex.getMessage()); } finally { @@ -536,14 +536,31 @@ public class AppDetails extends ListActivity { // Install the version of this app denoted by 'curapk'. private void install() { - if(!curapk.compatible) { + + String ra = null; + try { + DB db = DB.getDB(); + DB.Repo repo = db.getRepo(curapk.repo); + if (repo != null) + ra = repo.address; + } catch (Exception ex) { + Log.d("FDroid", "Failed to get repo address - " + ex.getMessage()); + } finally { + DB.releaseDB(); + } + if (ra == null) + return; + final String repoaddress = ra; + + if (!curapk.compatible) { AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this); ask_alrt.setMessage(getString(R.string.installIncompatible)); ask_alrt.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - downloadHandler = new DownloadHandler(curapk); + downloadHandler = new DownloadHandler(curapk, + repoaddress); } }); ask_alrt.setNegativeButton(getString(R.string.no), @@ -571,7 +588,7 @@ public class AppDetails extends ListActivity { alert.show(); return; } - downloadHandler = new DownloadHandler(curapk); + downloadHandler = new DownloadHandler(curapk, repoaddress); } private void removeApk(String id) { @@ -628,8 +645,8 @@ public class AppDetails extends ListActivity { private boolean updating; private File localFile; - public DownloadHandler(DB.Apk apk) { - download = new Downloader(apk); + public DownloadHandler(DB.Apk apk, String repoaddress) { + download = new Downloader(apk, repoaddress); download.start(); startUpdates(); } diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index da3c38f95..4da054fa6 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -224,22 +224,20 @@ public class DB { private static final String TABLE_APK = "fdroid_apk"; private static final String CREATE_TABLE_APK = "create table " + TABLE_APK + " ( " + "id text not null, " + "version text not null, " - + "server text not null, " + "hash text not null, " + + "repo integer 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," + "compatible int not null," - + "primary key(id,vercode));"; + + "size int not null," + "sig string," + "srcname string," + + "minSdkVersion integer," + "permissions string," + + "features string," + "hashType string," + "added string," + + "compatible int not null," + "primary key(id,vercode));"; public static class Apk { public Apk() { updated = false; detail_size = 0; - apkSource = null; added = null; - server = null; + repo = 0; detail_hash = null; detail_hashType = null; detail_permissions = null; @@ -250,7 +248,7 @@ public class DB { public String version; public int vercode; public int detail_size; // Size in bytes - 0 means we don't know! - public String server; + public int repo; // ID of the repo it comes from public String detail_hash; public String detail_hashType; public int minSdkVersion; // 0 if unknown @@ -268,10 +266,6 @@ public class DB { public String apkName; - // If null, the apk comes from the same server as the repo index. - // Otherwise this is the complete URL to download the apk from. - public String apkSource; - // If not null, this is the name of the source tarball for the // application. Null indicates that it's a developer's binary // build - otherwise it's built from source. @@ -280,11 +274,6 @@ public class DB { // Used internally for tracking during repo updates. public boolean updated; - public String getURL() { - String path = apkName.replace(" ", "%20"); - return server + "/" + path; - } - // Call isCompatible(apk) on an instance of this class to // check if an APK is compatible with the user's device. public static abstract class CompatibilityChecker { @@ -362,11 +351,12 @@ public class DB { // The TABLE_REPO table stores the details of the repositories in use. private static final String TABLE_REPO = "fdroid_repo"; private static final String CREATE_TABLE_REPO = "create table " - + TABLE_REPO + " (" + "address text primary key, " + + TABLE_REPO + " (id integer primary key, address text not null, " + "inuse integer not null, " + "priority integer not null," + "pubkey text, lastetag text);"; public static class Repo { + public int id; public String address; public boolean inuse; public int priority; @@ -374,7 +364,7 @@ public class DB { public String lastetag; // last etag we updated from, null forces update } - private final int DBVersion = 19; + private final int DBVersion = 20; private static void createAppApk(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_APP); @@ -417,10 +407,36 @@ public class DB { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { resetTransient(db); - if (oldVersion < 7) - db.execSQL("alter table " + TABLE_REPO + " add pubkey string"); - if (oldVersion < 19) - db.execSQL("alter table " + TABLE_REPO + " add lastetag string"); + + // Migrate repo list to new structure. (No way to change primary + // key in sqlite - table must be recreated) + if (oldVersion < 20) { + Vector oldrepos = new Vector(); + Cursor c = db.rawQuery("select address, inuse, pubkey from " + + TABLE_REPO, null); + c.moveToFirst(); + while (!c.isAfterLast()) { + Repo repo = new Repo(); + repo.address = c.getString(0); + repo.inuse = (c.getInt(1) == 1); + repo.pubkey = c.getString(2); + oldrepos.add(repo); + c.moveToNext(); + } + c.close(); + db.execSQL("drop table " + TABLE_REPO); + db.execSQL(CREATE_TABLE_REPO); + for (Repo repo : oldrepos) { + ContentValues values = new ContentValues(); + values.put("address", repo.address); + values.put("inuse", repo.inuse); + values.put("priority", 10); + values.put("pubkey", repo.pubkey); + values.put("lastetag", (String) null); + db.insert(TABLE_REPO, null, values); + } + } + } } @@ -524,9 +540,9 @@ public class DB { } // Populate the details for the given app, if necessary. - // If 'apkrepo' is not null, only apks from that repo address are + // If 'apkrepo' is non-zero, only apks from that repo are // populated (this is used during the update process) - public void populateDetails(App app, String apkrepo) { + public void populateDetails(App app, int apkrepo) { if (app.detail_Populated) return; Cursor c = null; @@ -547,7 +563,7 @@ public class DB { cols = new String[] { "hash", "hashType", "size", "permissions" }; for (Apk apk : app.apks) { - if (apkrepo == null || apkrepo.equals(apk.server)) { + if (apkrepo == 0 || apkrepo == apk.repo) { c = db.query(TABLE_APK, cols, "id = ? and vercode = " + Integer.toString(apk.vercode), new String[] { apk.id }, null, null, null, null); @@ -639,8 +655,8 @@ public class DB { + (System.currentTimeMillis() - startTime) + " ms)"); cols = new String[] { "id", "version", "vercode", "sig", "srcname", - "apkName", "apkSource", "minSdkVersion", "added", - "features", "compatible", "server" }; + "apkName", "minSdkVersion", "added", "features", + "compatible", "repo" }; c = db.query(TABLE_APK, cols, null, null, null, null, "vercode desc"); c.moveToFirst(); @@ -652,14 +668,13 @@ public class DB { 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.minSdkVersion = c.getInt(6); + String sApkAdded = c.getString(7); apk.added = (sApkAdded == null || sApkAdded.length() == 0) ? null : mDateFormat.parse(sApkAdded); - apk.features = CommaSeparatedList.make(c.getString(9)); - apk.compatible = c.getInt(10) == 1; - apk.server = c.getString(11); + apk.features = CommaSeparatedList.make(c.getString(8)); + apk.compatible = c.getInt(9) == 1; + apk.repo = c.getInt(10); apps.get(apk.id).apks.add(apk); c.moveToNext(); } @@ -950,14 +965,13 @@ 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("repo", upapk.repo); 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.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)); @@ -974,22 +988,47 @@ public class DB { } } + // Get details of a repo, given the ID. Returns null if the repo + // doesn't exist. + public Repo getRepo(int id) { + Cursor c = null; + try { + c = db.query(TABLE_REPO, new String[] { "address, inuse", + "priority", "pubkey", "lastetag" }, + "id = " + Integer.toString(id), null, null, null, null); + if (!c.moveToFirst()) + return null; + Repo repo = new Repo(); + repo.id = id; + repo.address = c.getString(0); + repo.inuse = (c.getInt(1) == 1); + repo.priority = c.getInt(2); + repo.pubkey = c.getString(3); + repo.lastetag = c.getString(4); + return repo; + } finally { + if (c != null) + c.close(); + } + } + // Get a list of the configured repositories. public Vector getRepos() { Vector repos = new Vector(); Cursor c = null; try { c = db.rawQuery( - "select address, inuse, priority, pubkey, lastetag from " + "select id, address, inuse, priority, pubkey, lastetag from " + TABLE_REPO + " order by priority", null); c.moveToFirst(); while (!c.isAfterLast()) { Repo repo = new Repo(); - repo.address = c.getString(0); - repo.inuse = (c.getInt(1) == 1); - repo.priority = c.getInt(2); - repo.pubkey = c.getString(3); - repo.lastetag = c.getString(4); + repo.id = c.getInt(0); + repo.address = c.getString(1); + repo.inuse = (c.getInt(2) == 1); + repo.priority = c.getInt(3); + repo.pubkey = c.getString(4); + repo.lastetag = c.getString(5); repos.add(repo); c.moveToNext(); } @@ -1025,10 +1064,11 @@ public class DB { new String[] { repo.address }); } - public void addServer(String address, int priority, String pubkey) { + public void addRepo(String address, int priority, String pubkey, + boolean inuse) { ContentValues values = new ContentValues(); values.put("address", address); - values.put("inuse", 1); + values.put("inuse", inuse ? 1 : 0); values.put("priority", priority); values.put("pubkey", pubkey); values.put("lastetag", (String) null); diff --git a/src/org/fdroid/fdroid/Downloader.java b/src/org/fdroid/fdroid/Downloader.java index 2be8c9312..e378e4f74 100644 --- a/src/org/fdroid/fdroid/Downloader.java +++ b/src/org/fdroid/fdroid/Downloader.java @@ -30,6 +30,7 @@ import java.net.URL; public class Downloader extends Thread { private DB.Apk curapk; + private String repoaddress; private String filename; private File localfile; @@ -49,8 +50,9 @@ public class Downloader extends Thread { // Constructor - creates a Downloader to download the given Apk, // which must have its detail populated. - Downloader(DB.Apk apk) { + Downloader(DB.Apk apk, String repoaddress) { curapk = apk; + this.repoaddress = repoaddress; } public synchronized Status getStatus() { @@ -116,11 +118,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"); - } else { - remotefile = curapk.apkSource; - } + remotefile = repoaddress + "/" + apkname.replace(" ", "%20"); Log.d("FDroid", "Downloading apk from " + remotefile); synchronized (this) { filename = remotefile; diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index f241a3526..b19012c42 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -158,7 +158,7 @@ public class ManageRepo extends ListActivity { String uri_str = uri.getText().toString(); try { DB db = DB.getDB(); - db.addServer(uri_str, 10, null); + db.addRepo(uri_str, 10, null, true); } finally { DB.releaseDB(); } diff --git a/src/org/fdroid/fdroid/RepoXMLHandler.java b/src/org/fdroid/fdroid/RepoXMLHandler.java index 04a2d92be..307b8c2af 100644 --- a/src/org/fdroid/fdroid/RepoXMLHandler.java +++ b/src/org/fdroid/fdroid/RepoXMLHandler.java @@ -55,7 +55,9 @@ import android.util.Log; public class RepoXMLHandler extends DefaultHandler { - String server; + // The ID of the repo we're processing. + private int repo; + private Vector apps; private DB.App curapp = null; @@ -68,8 +70,8 @@ public class RepoXMLHandler extends DefaultHandler { // The date format used in the repo XML file. private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd"); - public RepoXMLHandler(String srv, Vector apps) { - this.server = srv; + public RepoXMLHandler(int repo, Vector apps) { + this.repo = repo; this.apps = apps; pubkey = null; } @@ -143,8 +145,6 @@ public class RepoXMLHandler extends DefaultHandler { curapk.srcname = str; } else if (curel.equals("apkname")) { curapk.apkName = str; - } else if (curel.equals("apksource")) { - curapk.apkSource = str; } else if (curel.equals("sdkver")) { try { curapk.minSdkVersion = Integer.parseInt(str); @@ -238,7 +238,7 @@ public class RepoXMLHandler extends DefaultHandler { } else if (localName == "package" && curapp != null && curapk == null) { curapk = new DB.Apk(); curapk.id = curapp.id; - curapk.server = server; + curapk.repo = repo; hashType = null; } else if (localName == "hash" && curapk != null) { hashType = attributes.getValue("", "type"); @@ -296,7 +296,7 @@ public class RepoXMLHandler extends DefaultHandler { // value for the index that was successfully processed, or it may contain // null if none was available. public static String doUpdate(Context ctx, DB.Repo repo, - Vector apps, StringBuilder newetag, Vector keeprepos) { + Vector apps, StringBuilder newetag, Vector keeprepos) { try { int code = 0; @@ -377,7 +377,7 @@ public class RepoXMLHandler extends DefaultHandler { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); - RepoXMLHandler handler = new RepoXMLHandler(repo.address, apps); + RepoXMLHandler handler = new RepoXMLHandler(repo.id, apps); xr.setContentHandler(handler); InputStreamReader isr = new FileReader(new File( @@ -404,7 +404,7 @@ public class RepoXMLHandler extends DefaultHandler { // everything that came from this repo as being updated. Log.d("FDroid", "Repo index for " + repo.address + " is up to date (by etag)"); - keeprepos.add(repo.address); + keeprepos.add(repo.id); // Make sure we give back the same etag. (The 200 route will // have supplied a new one. newetag.append(repo.lastetag); diff --git a/src/org/fdroid/fdroid/UpdateService.java b/src/org/fdroid/fdroid/UpdateService.java index 2775e3589..ef0890af9 100644 --- a/src/org/fdroid/fdroid/UpdateService.java +++ b/src/org/fdroid/fdroid/UpdateService.java @@ -119,7 +119,7 @@ public class UpdateService extends IntentService { // Process each repo... Vector apps = new Vector(); - Vector keeprepos = new Vector(); + Vector keeprepos = new Vector(); boolean success = true; for (DB.Repo repo : repos) { if (repo.inuse) { @@ -151,11 +151,11 @@ public class UpdateService extends IntentService { // Need to flag things we're keeping despite having received // no data about during the update. (i.e. stuff from a repo // that we know is unchanged due to the etag) - for (String keep : keeprepos) { + for (int keep : keeprepos) { for (DB.App app : prevapps) { boolean keepapp = false; for (DB.Apk apk : app.apks) { - if (apk.server.equals(keep)) { + if (apk.repo == keep) { keepapp = true; break; } @@ -177,7 +177,7 @@ public class UpdateService extends IntentService { db.populateDetails(app_k, keep); } for (DB.Apk apk : app.apks) - if (apk.server.equals(keep)) + if (apk.repo == keep) apk.updated = true; } } @@ -204,7 +204,7 @@ public class UpdateService extends IntentService { } if (success) { for (DB.App app : acceptedapps) - getIcon(app); + getIcon(app, repos); ((FDroidApp) getApplication()).invalidateApps(); } @@ -264,7 +264,7 @@ public class UpdateService extends IntentService { } - private void getIcon(DB.App app) { + private void getIcon(DB.App app, Vector repos) { try { File f = new File(DB.getIconsPath(), app.icon); @@ -273,7 +273,12 @@ public class UpdateService extends IntentService { if (app.apks.size() == 0) return; - String server = app.apks.get(0).server; + String server = null; + for (DB.Repo repo : repos) + if (repo.id == app.apks.get(0).repo) + server = repo.address; + if (server == null) + return; URL u = new URL(server + "/icons/" + app.icon); HttpURLConnection uc = (HttpURLConnection) u.openConnection(); if (uc.getResponseCode() == 200) {