Improved update handling:

Database is locked for much less time - only briefly before starting an
update sequence, and again after all downloads and parsing are complete.
Also, when the same app is defined in multiple repos, the apks are
merged rather than just having one take priority.
This commit is contained in:
Ciaran Gultnieks 2012-09-10 22:46:45 +01:00
parent 0623801474
commit 2d397c8611
2 changed files with 217 additions and 175 deletions

View File

@ -54,9 +54,8 @@ import android.util.Log;
public class RepoXMLHandler extends DefaultHandler {
String mserver;
private DB db;
String server;
private Vector<DB.App> apps;
private DB.App curapp = null;
private DB.Apk curapk = null;
@ -68,9 +67,9 @@ 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, DB db) {
mserver = srv;
this.db = db;
public RepoXMLHandler(String srv, Vector<DB.App> apps) {
this.server = srv;
this.apps = apps;
pubkey = null;
}
@ -99,12 +98,25 @@ public class RepoXMLHandler extends DefaultHandler {
}
if (curel.equals("application") && curapp != null) {
// Log.d("FDroid", "Repo: Updating application " + curapp.id);
db.updateApplication(curapp);
getIcon(curapp);
// If we already have this application (must be from scanning a
// different repo) then just merge in the apks.
// TODO: Scanning the whole app list like this every time is
// going to be stupid if the list gets very big!
boolean merged = false;
for (DB.App app : apps) {
if (app.id == curapp.id) {
app.apks.addAll(curapp.apks);
break;
}
}
if (!merged)
apps.add(curapp);
curapp = null;
} else if (curel.equals("package") && curapk != null && curapp != null) {
// Log.d("FDroid", "Repo: Package added (" + curapk.version + ")");
curapp.apks.add(curapk);
curapk = null;
} else if (curapk != null && str != null) {
@ -160,7 +172,6 @@ public class RepoXMLHandler extends DefaultHandler {
}
} else if (curapp != null && str != null) {
if (curel.equals("id")) {
// Log.d("FDroid", "App id is " + str);
curapp.id = str;
} else if (curel.equals("name")) {
curapp.name = str;
@ -223,13 +234,11 @@ public class RepoXMLHandler extends DefaultHandler {
if (pk != null)
pubkey = pk;
} else if (localName == "application" && curapp == null) {
// Log.d("FDroid", "Repo: Found application at " + mserver);
curapp = new DB.App();
} else if (localName == "package" && curapp != null && curapk == null) {
// Log.d("FDroid", "Repo: Found package for " + curapp.id);
curapk = new DB.Apk();
curapk.id = curapp.id;
curapk.server = mserver;
curapk.server = server;
hashType = null;
} else if (localName == "hash" && curapk != null) {
hashType = attributes.getValue("", "type");
@ -245,7 +254,7 @@ public class RepoXMLHandler extends DefaultHandler {
if (f.exists())
return;
URL u = new URL(mserver + "/icons/" + app.icon);
URL u = new URL(server + "/icons/" + app.icon);
HttpURLConnection uc = (HttpURLConnection) u.openConnection();
if (uc.getResponseCode() == 200) {
BufferedInputStream getit = new BufferedInputStream(
@ -289,26 +298,22 @@ public class RepoXMLHandler extends DefaultHandler {
}
public static boolean doUpdates(Context ctx, DB db) {
long startTime = System.currentTimeMillis();
db.beginUpdate();
Vector<DB.Repo> repos = db.getRepos();
for (DB.Repo repo : repos) {
if (repo.inuse) {
// Do an update from the given repo. All applications found, and their
// APKs, are added to 'apps'. (If 'apps' already contains an app, its
// APKs are merged into the existing one)
public static boolean doUpdate(Context ctx, DB.Repo repo,
Vector<DB.App> apps) {
try {
if (repo.pubkey != null) {
// This is a signed repo - we download the jar file,
// check the signature, and extract the index...
Log.d("FDroid", "Getting signed index from "
+ repo.address);
Log.d("FDroid", "Getting signed index from " + repo.address);
String address = repo.address + "/index.jar";
PackageManager pm = ctx.getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo(
ctx.getPackageName(), 0);
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), 0);
address += "?" + pi.versionName;
} catch (Exception e) {
}
@ -319,8 +324,7 @@ public class RepoXMLHandler extends DefaultHandler {
try {
jar = new JarFile(jarpath, true);
je = (JarEntry) jar.getEntry("index.xml");
File efile = new File(ctx.getFilesDir(),
"/tempindex.xml");
File efile = new File(ctx.getFilesDir(), "/tempindex.xml");
InputStream in = new BufferedInputStream(
jar.getInputStream(je), 8192);
OutputStream out = new BufferedOutputStream(
@ -345,8 +349,7 @@ public class RepoXMLHandler extends DefaultHandler {
Log.d("FDroid", "No signature found in index");
return false;
}
Log.d("FDroid", "Index has " + certs.length
+ " signature"
Log.d("FDroid", "Index has " + certs.length + " signature"
+ (certs.length > 1 ? "s." : "."));
boolean match = false;
@ -365,22 +368,19 @@ public class RepoXMLHandler extends DefaultHandler {
} else {
// It's an old-fashioned unsigned repo...
Log.d("FDroid", "Getting unsigned index from "
+ repo.address);
getRemoteFile(ctx, repo.address + "/index.xml",
"tempindex.xml");
Log.d("FDroid", "Getting unsigned index from " + repo.address);
getRemoteFile(ctx, repo.address + "/index.xml", "tempindex.xml");
}
// Process the index...
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
RepoXMLHandler handler = new RepoXMLHandler(repo.address,
db);
RepoXMLHandler handler = new RepoXMLHandler(repo.address, apps);
xr.setContentHandler(handler);
InputStreamReader isr = new FileReader(new File(
ctx.getFilesDir() + "/tempindex.xml"));
InputStreamReader isr = new FileReader(new File(ctx.getFilesDir()
+ "/tempindex.xml"));
InputSource is = new InputSource(isr);
xr.parse(is);
@ -390,25 +390,23 @@ public class RepoXMLHandler extends DefaultHandler {
Log.d("FDroid",
"Public key found - switching to signed repo for future updates");
repo.pubkey = handler.pubkey;
DB db = DB.getDB();
try {
db.updateRepoByAddress(repo);
} finally {
DB.releaseDB();
}
}
} catch (Exception e) {
Log.e("FDroid", "Exception updating from " + repo.address
+ ":\n" + Log.getStackTraceString(e));
db.cancelUpdate();
Log.e("FDroid", "Exception updating from " + repo.address + ":\n"
+ Log.getStackTraceString(e));
return false;
} finally {
ctx.deleteFile("tempindex.xml");
ctx.deleteFile("tempindex.jar");
}
}
}
db.endUpdate();
Log.d("FDroid", "Update completed in "
+ ((System.currentTimeMillis() - startTime) / 1000)
+ " seconds.");
return true;
}

View File

@ -18,6 +18,8 @@
package org.fdroid.fdroid;
import java.util.Vector;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.Notification;
@ -38,7 +40,6 @@ public class UpdateService extends IntentService {
super("UpdateService");
}
// Schedule (or cancel schedule for) this service, according to the
// current preferences. Should be called a) at boot, or b) if the preference
// is changed.
@ -63,14 +64,18 @@ public class UpdateService extends IntentService {
}
}
protected void onHandleIntent(Intent intent) {
// We might be doing a scheduled run, or we might have been launched by
// the app in response to a user's request. If we get this receiver, it's
// the app in response to a user's request. If we get this receiver,
// it's
// the latter...
ResultReceiver receiver = intent.getParcelableExtra("receiver");
long startTime = System.currentTimeMillis();
try {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
@ -86,42 +91,80 @@ public class UpdateService extends IntentService {
return;
}
// Do the update...
DB db = null;
try {
db = DB.getDB();
boolean notify = prefs.getBoolean("updateNotify", false);
// Grab some preliminary information, then we can release the
// database
// while we do all the downloading, etc...
DB db = DB.getDB();
int prevUpdates = 0;
int newUpdates = 0;
Vector<DB.Repo> repos;
try {
// Get the number of updates available before we
// start, so we can notify if there are new ones.
// (But avoid doing it if the user doesn't want
// notifications, since it may be time consuming)
int prevUpdates = 0;
if (notify)
prevUpdates = db.getNumUpdates();
boolean success = RepoXMLHandler.doUpdates(
getBaseContext(), db);
repos = db.getRepos();
} finally {
DB.releaseDB();
}
// Process each repo...
Vector<DB.App> apps = new Vector<DB.App>();
boolean success = true;
for (DB.Repo repo : repos) {
if (repo.inuse) {
if (!RepoXMLHandler.doUpdate(getBaseContext(), repo, apps)) {
Log.d("FDroid", "Update failed for repo "
+ repo.address);
success = false;
}
}
}
if (success) {
db = DB.getDB();
try {
db.beginUpdate();
for (DB.App app : apps) {
db.updateApplication(app);
}
db.endUpdate();
if (notify)
newUpdates = db.getNumUpdates();
} catch (Exception ex) {
db.cancelUpdate();
Log.e("FDroid", "Exception during update processing:\n"
+ Log.getStackTraceString(ex));
success = false;
} finally {
DB.releaseDB();
}
}
if (success && notify) {
int newUpdates = db.getNumUpdates();
Log.d("FDroid", "Updates before:" + prevUpdates + ", after: " +newUpdates);
Log.d("FDroid", "Updates before:" + prevUpdates + ", after: "
+ newUpdates);
if (newUpdates > prevUpdates) {
NotificationManager n = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(
R.drawable.icon,
"FDroid Updates Available", System
.currentTimeMillis());
R.drawable.icon, "F-Droid Updates Available",
System.currentTimeMillis());
Context context = getApplicationContext();
CharSequence contentTitle = "FDroid";
CharSequence contentTitle = "F-Droid";
CharSequence contentText = "Updates are available.";
Intent notificationIntent = new Intent(
UpdateService.this, FDroid.class);
PendingIntent contentIntent = PendingIntent
.getActivity(UpdateService.this, 0,
notificationIntent, 0);
notification.setLatestEventInfo(context,
contentTitle, contentText, contentIntent);
Intent notificationIntent = new Intent(UpdateService.this,
FDroid.class);
PendingIntent contentIntent = PendingIntent.getActivity(
UpdateService.this, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, contentTitle,
contentText, contentIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
n.notify(1, notification);
}
@ -133,17 +176,18 @@ public class UpdateService extends IntentService {
}
} catch (Exception e) {
Log.e("FDroid", "Exception during handleCommand():\n"
Log.e("FDroid",
"Exception during update processing:\n"
+ Log.getStackTraceString(e));
if (receiver != null) {
Bundle resultData = new Bundle();
receiver.send(1, resultData);
}
} finally {
if (db != null)
DB.releaseDB();
Log.d("FDroid", "Update took "
+ ((System.currentTimeMillis() - startTime) / 1000)
+ " seconds.");
}
}
}