Better internal handling of repos

This commit is contained in:
Ciaran Gultnieks 2012-09-26 21:31:03 +01:00
parent 8415cc2e0a
commit c8aeb08dfa
6 changed files with 134 additions and 74 deletions

View File

@ -307,7 +307,7 @@ public class AppDetails extends ListActivity {
// Make sure the app is populated. // Make sure the app is populated.
try { try {
DB db = DB.getDB(); DB db = DB.getDB();
db.populateDetails(app, null); db.populateDetails(app, 0);
} catch (Exception ex) { } catch (Exception ex) {
Log.d("FDroid", "Failed to populate app - " + ex.getMessage()); Log.d("FDroid", "Failed to populate app - " + ex.getMessage());
} finally { } finally {
@ -536,14 +536,31 @@ public class AppDetails extends ListActivity {
// Install the version of this app denoted by 'curapk'. // Install the version of this app denoted by 'curapk'.
private void install() { 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); AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
ask_alrt.setMessage(getString(R.string.installIncompatible)); ask_alrt.setMessage(getString(R.string.installIncompatible));
ask_alrt.setPositiveButton(getString(R.string.yes), ask_alrt.setPositiveButton(getString(R.string.yes),
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, public void onClick(DialogInterface dialog,
int whichButton) { int whichButton) {
downloadHandler = new DownloadHandler(curapk); downloadHandler = new DownloadHandler(curapk,
repoaddress);
} }
}); });
ask_alrt.setNegativeButton(getString(R.string.no), ask_alrt.setNegativeButton(getString(R.string.no),
@ -571,7 +588,7 @@ public class AppDetails extends ListActivity {
alert.show(); alert.show();
return; return;
} }
downloadHandler = new DownloadHandler(curapk); downloadHandler = new DownloadHandler(curapk, repoaddress);
} }
private void removeApk(String id) { private void removeApk(String id) {
@ -628,8 +645,8 @@ public class AppDetails extends ListActivity {
private boolean updating; private boolean updating;
private File localFile; private File localFile;
public DownloadHandler(DB.Apk apk) { public DownloadHandler(DB.Apk apk, String repoaddress) {
download = new Downloader(apk); download = new Downloader(apk, repoaddress);
download.start(); download.start();
startUpdates(); startUpdates();
} }

View File

@ -224,22 +224,20 @@ public class DB {
private static final String TABLE_APK = "fdroid_apk"; private static final String TABLE_APK = "fdroid_apk";
private static final String CREATE_TABLE_APK = "create table " + TABLE_APK private static final String CREATE_TABLE_APK = "create table " + TABLE_APK
+ " ( " + "id text not null, " + "version text not null, " + " ( " + "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, " + "vercode int not null," + "apkName text not null, "
+ "size int not null," + "apkSource text," + "sig string," + "size int not null," + "sig string," + "srcname string,"
+ "srcname string," + "minSdkVersion integer," + "minSdkVersion integer," + "permissions string,"
+ "permissions string," + "features string," + "hashType string," + "features string," + "hashType string," + "added string,"
+ "added string," + "compatible int not null," + "compatible int not null," + "primary key(id,vercode));";
+ "primary key(id,vercode));";
public static class Apk { public static class Apk {
public Apk() { public Apk() {
updated = false; updated = false;
detail_size = 0; detail_size = 0;
apkSource = null;
added = null; added = null;
server = null; repo = 0;
detail_hash = null; detail_hash = null;
detail_hashType = null; detail_hashType = null;
detail_permissions = null; detail_permissions = null;
@ -250,7 +248,7 @@ public class DB {
public String version; public String version;
public int vercode; public int vercode;
public int detail_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 int repo; // ID of the repo it comes from
public String detail_hash; public String detail_hash;
public String detail_hashType; public String detail_hashType;
public int minSdkVersion; // 0 if unknown public int minSdkVersion; // 0 if unknown
@ -268,10 +266,6 @@ public class DB {
public String apkName; 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 // If not null, this is the name of the source tarball for the
// application. Null indicates that it's a developer's binary // application. Null indicates that it's a developer's binary
// build - otherwise it's built from source. // build - otherwise it's built from source.
@ -280,11 +274,6 @@ public class DB {
// Used internally for tracking during repo updates. // Used internally for tracking during repo updates.
public boolean updated; public boolean updated;
public String getURL() {
String path = apkName.replace(" ", "%20");
return server + "/" + path;
}
// Call isCompatible(apk) on an instance of this class to // Call isCompatible(apk) on an instance of this class to
// check if an APK is compatible with the user's device. // check if an APK is compatible with the user's device.
public static abstract class CompatibilityChecker { public static abstract class CompatibilityChecker {
@ -362,11 +351,12 @@ public class DB {
// The TABLE_REPO table stores the details of the repositories in use. // The TABLE_REPO table stores the details of the repositories in use.
private static final String TABLE_REPO = "fdroid_repo"; private static final String TABLE_REPO = "fdroid_repo";
private static final String CREATE_TABLE_REPO = "create table " 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," + "inuse integer not null, " + "priority integer not null,"
+ "pubkey text, lastetag text);"; + "pubkey text, lastetag text);";
public static class Repo { public static class Repo {
public int id;
public String address; public String address;
public boolean inuse; public boolean inuse;
public int priority; public int priority;
@ -374,7 +364,7 @@ public class DB {
public String lastetag; // last etag we updated from, null forces update 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) { private static void createAppApk(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_APP); db.execSQL(CREATE_TABLE_APP);
@ -417,10 +407,36 @@ public class DB {
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
resetTransient(db); resetTransient(db);
if (oldVersion < 7)
db.execSQL("alter table " + TABLE_REPO + " add pubkey string"); // Migrate repo list to new structure. (No way to change primary
if (oldVersion < 19) // key in sqlite - table must be recreated)
db.execSQL("alter table " + TABLE_REPO + " add lastetag string"); if (oldVersion < 20) {
Vector<Repo> oldrepos = new Vector<Repo>();
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. // 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) // 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) if (app.detail_Populated)
return; return;
Cursor c = null; Cursor c = null;
@ -547,7 +563,7 @@ public class DB {
cols = new String[] { "hash", "hashType", "size", "permissions" }; cols = new String[] { "hash", "hashType", "size", "permissions" };
for (Apk apk : app.apks) { 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 = " c = db.query(TABLE_APK, cols, "id = ? and vercode = "
+ Integer.toString(apk.vercode), + Integer.toString(apk.vercode),
new String[] { apk.id }, null, null, null, null); new String[] { apk.id }, null, null, null, null);
@ -639,8 +655,8 @@ public class DB {
+ (System.currentTimeMillis() - startTime) + " ms)"); + (System.currentTimeMillis() - startTime) + " ms)");
cols = new String[] { "id", "version", "vercode", "sig", "srcname", cols = new String[] { "id", "version", "vercode", "sig", "srcname",
"apkName", "apkSource", "minSdkVersion", "added", "apkName", "minSdkVersion", "added", "features",
"features", "compatible", "server" }; "compatible", "repo" };
c = db.query(TABLE_APK, cols, null, null, null, null, c = db.query(TABLE_APK, cols, null, null, null, null,
"vercode desc"); "vercode desc");
c.moveToFirst(); c.moveToFirst();
@ -652,14 +668,13 @@ public class DB {
apk.sig = c.getString(3); apk.sig = c.getString(3);
apk.srcname = c.getString(4); apk.srcname = c.getString(4);
apk.apkName = c.getString(5); apk.apkName = c.getString(5);
apk.apkSource = c.getString(6); apk.minSdkVersion = c.getInt(6);
apk.minSdkVersion = c.getInt(7); String sApkAdded = c.getString(7);
String sApkAdded = c.getString(8);
apk.added = (sApkAdded == null || sApkAdded.length() == 0) ? null apk.added = (sApkAdded == null || sApkAdded.length() == 0) ? null
: mDateFormat.parse(sApkAdded); : mDateFormat.parse(sApkAdded);
apk.features = CommaSeparatedList.make(c.getString(9)); apk.features = CommaSeparatedList.make(c.getString(8));
apk.compatible = c.getInt(10) == 1; apk.compatible = c.getInt(9) == 1;
apk.server = c.getString(11); apk.repo = c.getInt(10);
apps.get(apk.id).apks.add(apk); apps.get(apk.id).apks.add(apk);
c.moveToNext(); c.moveToNext();
} }
@ -950,14 +965,13 @@ 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("repo", upapk.repo);
values.put("hash", upapk.detail_hash); values.put("hash", upapk.detail_hash);
values.put("hashType", upapk.detail_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.detail_size); values.put("size", upapk.detail_size);
values.put("apkName", upapk.apkName); values.put("apkName", upapk.apkName);
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));
@ -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. // Get a list of the configured repositories.
public Vector<Repo> getRepos() { public Vector<Repo> getRepos() {
Vector<Repo> repos = new Vector<Repo>(); Vector<Repo> repos = new Vector<Repo>();
Cursor c = null; Cursor c = null;
try { try {
c = db.rawQuery( c = db.rawQuery(
"select address, inuse, priority, pubkey, lastetag from " "select id, address, inuse, priority, pubkey, lastetag from "
+ TABLE_REPO + " order by priority", null); + TABLE_REPO + " order by priority", null);
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
Repo repo = new Repo(); Repo repo = new Repo();
repo.address = c.getString(0); repo.id = c.getInt(0);
repo.inuse = (c.getInt(1) == 1); repo.address = c.getString(1);
repo.priority = c.getInt(2); repo.inuse = (c.getInt(2) == 1);
repo.pubkey = c.getString(3); repo.priority = c.getInt(3);
repo.lastetag = c.getString(4); repo.pubkey = c.getString(4);
repo.lastetag = c.getString(5);
repos.add(repo); repos.add(repo);
c.moveToNext(); c.moveToNext();
} }
@ -1025,10 +1064,11 @@ public class DB {
new String[] { repo.address }); 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(); ContentValues values = new ContentValues();
values.put("address", address); values.put("address", address);
values.put("inuse", 1); values.put("inuse", inuse ? 1 : 0);
values.put("priority", priority); values.put("priority", priority);
values.put("pubkey", pubkey); values.put("pubkey", pubkey);
values.put("lastetag", (String) null); values.put("lastetag", (String) null);

View File

@ -30,6 +30,7 @@ import java.net.URL;
public class Downloader extends Thread { public class Downloader extends Thread {
private DB.Apk curapk; private DB.Apk curapk;
private String repoaddress;
private String filename; private String filename;
private File localfile; private File localfile;
@ -49,8 +50,9 @@ public class Downloader extends Thread {
// Constructor - creates a Downloader to download the given Apk, // Constructor - creates a Downloader to download the given Apk,
// which must have its detail populated. // which must have its detail populated.
Downloader(DB.Apk apk) { Downloader(DB.Apk apk, String repoaddress) {
curapk = apk; curapk = apk;
this.repoaddress = repoaddress;
} }
public synchronized Status getStatus() { 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... // If we haven't got the apk locally, we'll have to download it...
String remotefile; String remotefile;
if (curapk.apkSource == null) { remotefile = repoaddress + "/" + apkname.replace(" ", "%20");
remotefile = curapk.server + "/" + apkname.replace(" ", "%20");
} else {
remotefile = curapk.apkSource;
}
Log.d("FDroid", "Downloading apk from " + remotefile); Log.d("FDroid", "Downloading apk from " + remotefile);
synchronized (this) { synchronized (this) {
filename = remotefile; filename = remotefile;

View File

@ -158,7 +158,7 @@ public class ManageRepo extends ListActivity {
String uri_str = uri.getText().toString(); String uri_str = uri.getText().toString();
try { try {
DB db = DB.getDB(); DB db = DB.getDB();
db.addServer(uri_str, 10, null); db.addRepo(uri_str, 10, null, true);
} finally { } finally {
DB.releaseDB(); DB.releaseDB();
} }

View File

@ -55,7 +55,9 @@ import android.util.Log;
public class RepoXMLHandler extends DefaultHandler { public class RepoXMLHandler extends DefaultHandler {
String server; // The ID of the repo we're processing.
private int repo;
private Vector<DB.App> apps; private Vector<DB.App> apps;
private DB.App curapp = null; private DB.App curapp = null;
@ -68,8 +70,8 @@ public class RepoXMLHandler extends DefaultHandler {
// The date format used in the repo XML file. // The date format used in the repo XML file.
private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd"); private SimpleDateFormat mXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd");
public RepoXMLHandler(String srv, Vector<DB.App> apps) { public RepoXMLHandler(int repo, Vector<DB.App> apps) {
this.server = srv; this.repo = repo;
this.apps = apps; this.apps = apps;
pubkey = null; pubkey = null;
} }
@ -143,8 +145,6 @@ public class RepoXMLHandler extends DefaultHandler {
curapk.srcname = str; curapk.srcname = str;
} else if (curel.equals("apkname")) { } else if (curel.equals("apkname")) {
curapk.apkName = str; curapk.apkName = str;
} else if (curel.equals("apksource")) {
curapk.apkSource = str;
} else if (curel.equals("sdkver")) { } else if (curel.equals("sdkver")) {
try { try {
curapk.minSdkVersion = Integer.parseInt(str); curapk.minSdkVersion = Integer.parseInt(str);
@ -238,7 +238,7 @@ public class RepoXMLHandler extends DefaultHandler {
} 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.repo = repo;
hashType = null; hashType = null;
} else if (localName == "hash" && curapk != null) { } else if (localName == "hash" && curapk != null) {
hashType = attributes.getValue("", "type"); 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 // value for the index that was successfully processed, or it may contain
// null if none was available. // null if none was available.
public static String doUpdate(Context ctx, DB.Repo repo, public static String doUpdate(Context ctx, DB.Repo repo,
Vector<DB.App> apps, StringBuilder newetag, Vector<String> keeprepos) { Vector<DB.App> apps, StringBuilder newetag, Vector<Integer> keeprepos) {
try { try {
int code = 0; int code = 0;
@ -377,7 +377,7 @@ public class RepoXMLHandler extends DefaultHandler {
SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser(); SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader(); XMLReader xr = sp.getXMLReader();
RepoXMLHandler handler = new RepoXMLHandler(repo.address, apps); RepoXMLHandler handler = new RepoXMLHandler(repo.id, apps);
xr.setContentHandler(handler); xr.setContentHandler(handler);
InputStreamReader isr = new FileReader(new File( InputStreamReader isr = new FileReader(new File(
@ -404,7 +404,7 @@ public class RepoXMLHandler extends DefaultHandler {
// everything that came from this repo as being updated. // everything that came from this repo as being updated.
Log.d("FDroid", "Repo index for " + repo.address Log.d("FDroid", "Repo index for " + repo.address
+ " is up to date (by etag)"); + " 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 // Make sure we give back the same etag. (The 200 route will
// have supplied a new one. // have supplied a new one.
newetag.append(repo.lastetag); newetag.append(repo.lastetag);

View File

@ -119,7 +119,7 @@ public class UpdateService extends IntentService {
// Process each repo... // Process each repo...
Vector<DB.App> apps = new Vector<DB.App>(); Vector<DB.App> apps = new Vector<DB.App>();
Vector<String> keeprepos = new Vector<String>(); Vector<Integer> keeprepos = new Vector<Integer>();
boolean success = true; boolean success = true;
for (DB.Repo repo : repos) { for (DB.Repo repo : repos) {
if (repo.inuse) { if (repo.inuse) {
@ -151,11 +151,11 @@ public class UpdateService extends IntentService {
// Need to flag things we're keeping despite having received // Need to flag things we're keeping despite having received
// no data about during the update. (i.e. stuff from a repo // no data about during the update. (i.e. stuff from a repo
// that we know is unchanged due to the etag) // that we know is unchanged due to the etag)
for (String keep : keeprepos) { for (int keep : keeprepos) {
for (DB.App app : prevapps) { for (DB.App app : prevapps) {
boolean keepapp = false; boolean keepapp = false;
for (DB.Apk apk : app.apks) { for (DB.Apk apk : app.apks) {
if (apk.server.equals(keep)) { if (apk.repo == keep) {
keepapp = true; keepapp = true;
break; break;
} }
@ -177,7 +177,7 @@ public class UpdateService extends IntentService {
db.populateDetails(app_k, keep); db.populateDetails(app_k, keep);
} }
for (DB.Apk apk : app.apks) for (DB.Apk apk : app.apks)
if (apk.server.equals(keep)) if (apk.repo == keep)
apk.updated = true; apk.updated = true;
} }
} }
@ -204,7 +204,7 @@ public class UpdateService extends IntentService {
} }
if (success) { if (success) {
for (DB.App app : acceptedapps) for (DB.App app : acceptedapps)
getIcon(app); getIcon(app, repos);
((FDroidApp) getApplication()).invalidateApps(); ((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<DB.Repo> repos) {
try { try {
File f = new File(DB.getIconsPath(), app.icon); File f = new File(DB.getIconsPath(), app.icon);
@ -273,7 +273,12 @@ public class UpdateService extends IntentService {
if (app.apks.size() == 0) if (app.apks.size() == 0)
return; 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); URL u = new URL(server + "/icons/" + app.icon);
HttpURLConnection uc = (HttpURLConnection) u.openConnection(); HttpURLConnection uc = (HttpURLConnection) u.openConnection();
if (uc.getResponseCode() == 200) { if (uc.getResponseCode() == 200) {