Make progress more multi-repo aware.

Changed strings.xml to reflect the multi-repo nature of updating.

Also refactored progress events to make them more generic and
easier to nest deeply down the call stack. The ProgressListener
now just expects a ProgressListener.Event, which in addition to
statically typed type and progress info, also has an associated
Bundle which can store arbitrary data.
This commit is contained in:
Peter Serwylo 2013-04-16 12:45:51 +10:00
parent 4bcf4bf60d
commit 841ec9d289
5 changed files with 126 additions and 62 deletions

View File

@ -153,14 +153,15 @@
<string name="category_recentlyupdated">Recently Updated</string> <string name="category_recentlyupdated">Recently Updated</string>
<!-- <!--
status_download takes three parameters: status_download takes four parameters:
- Repository (url)
- Downloaded size (human readable) - Downloaded size (human readable)
- Total size (human readable) - Total size (human readable)
- Percentage complete (int between 0-100) - Percentage complete (int between 0-100)
--> -->
<string name="status_download">Downloading %1$s / %2$s (%3$d%%)</string> <string name="status_download">Downloading\n%2$s / %3$s (%4$d%%) from\n%1$s</string>
<string name="status_processing_xml">Processing application %1$d of %2$d</string> <string name="status_processing_xml">Processing application\n%2$d of %3$d from\n%1$s</string>
<string name="status_connecting_to_repo">Connecting to repository:\n%1$s</string> <string name="status_connecting_to_repo">Connecting to\n%1$s</string>
<string name="status_checking_compatibility">Checking apps compatibility with your device…</string> <string name="status_checking_compatibility">Checking all apps compatibility with your device…</string>
</resources> </resources>

View File

@ -1,7 +1,54 @@
package org.fdroid.fdroid; package org.fdroid.fdroid;
import android.os.Bundle;
public interface ProgressListener { public interface ProgressListener {
public void onProgress(int type, int progress, int total); public void onProgress(Event event);
// I went a bit overboard with the overloaded constructors, but they all
// seemed potentially useful and unambiguous, so I just put them in there
// while I'm here.
public static class Event {
public static final int NO_VALUE = Integer.MIN_VALUE;
public final int type;
public final Bundle data;
// These two are not final, so that you can create a template Event,
// pass it into a function which performs something over time, and
// that function can initialize "total" and progressively
// update "progress"
public int progress;
public int total;
public Event(int type) {
this(type, NO_VALUE, NO_VALUE, null);
}
public Event(int type, Bundle data) {
this(type, NO_VALUE, NO_VALUE, data);
}
public Event(int type, int progress) {
this(type, progress, NO_VALUE, null);
}
public Event(int type, int progress, Bundle data) {
this(type, NO_VALUE, NO_VALUE, data);
}
public Event(int type, int progress, int total) {
this(type, progress, total, null);
}
public Event(int type, int progress, int total, Bundle data) {
this.type = type;
this.progress = progress;
this.total = total;
this.data = data == null ? new Bundle() : data;
}
}
} }

View File

@ -42,6 +42,7 @@ import javax.net.ssl.SSLHandshakeException;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import android.os.Bundle;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -55,8 +56,8 @@ import android.util.Log;
public class RepoXMLHandler extends DefaultHandler { public class RepoXMLHandler extends DefaultHandler {
// The ID of the repo we're processing. // The repo we're processing.
private int repo; private DB.Repo repo;
private Vector<DB.App> apps; private Vector<DB.App> apps;
@ -73,11 +74,13 @@ public class RepoXMLHandler extends DefaultHandler {
public static final int PROGRESS_TYPE_DOWNLOAD = 1; public static final int PROGRESS_TYPE_DOWNLOAD = 1;
public static final int PROGRESS_TYPE_PROCESS_XML = 2; public static final int PROGRESS_TYPE_PROCESS_XML = 2;
public static final String PROGRESS_DATA_REPO = "repo";
// 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");
private int totalAppCount; private int totalAppCount;
public RepoXMLHandler(int repo, Vector<DB.App> apps, ProgressListener listener) { public RepoXMLHandler(DB.Repo repo, Vector<DB.App> apps, ProgressListener listener) {
this.repo = repo; this.repo = repo;
this.apps = apps; this.apps = apps;
pubkey = null; pubkey = null;
@ -228,28 +231,37 @@ public class RepoXMLHandler extends DefaultHandler {
curapp.requirements = DB.CommaSeparatedList.make(str); curapp.requirements = DB.CommaSeparatedList.make(str);
} }
} }
} }
private static Bundle createProgressData(String repoAddress) {
Bundle data = new Bundle();
data.putString(PROGRESS_DATA_REPO, repoAddress);
return data;
}
@Override @Override
public void startElement(String uri, String localName, String qName, public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException { Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes); super.startElement(uri, localName, qName, attributes);
if (localName == "repo") { if (localName.equals("repo")) {
String pk = attributes.getValue("", "pubkey"); String pk = attributes.getValue("", "pubkey");
if (pk != null) if (pk != null)
pubkey = pk; pubkey = pk;
} else if (localName == "application" && curapp == null) { } else if (localName.equals("application") && curapp == null) {
curapp = new DB.App(); curapp = new DB.App();
curapp.detail_Populated = true; curapp.detail_Populated = true;
Bundle progressData = createProgressData(repo.address);
progressCounter ++; progressCounter ++;
progressListener.onProgress(RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML, progressCounter, totalAppCount); progressListener.onProgress(
} else if (localName == "package" && curapp != null && curapk == null) { new ProgressListener.Event(
RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML, progressCounter,
totalAppCount, progressData));
} else if (localName.equals("package") && curapp != null && curapk == null) {
curapk = new DB.Apk(); curapk = new DB.Apk();
curapk.id = curapp.id; curapk.id = curapp.id;
curapk.repo = repo; curapk.repo = repo.id;
hashType = null; hashType = null;
} else if (localName == "hash" && curapk != null) { } else if (localName.equals("hash") && curapk != null) {
hashType = attributes.getValue("", "type"); hashType = attributes.getValue("", "type");
} }
curchars.setLength(0); curchars.setLength(0);
@ -263,7 +275,8 @@ public class RepoXMLHandler extends DefaultHandler {
// empty if none was available. // empty if none was available.
private static int getRemoteFile(Context ctx, String url, String dest, private static int getRemoteFile(Context ctx, String url, String dest,
String etag, StringBuilder retag, String etag, StringBuilder retag,
ProgressListener progressListener ) throws MalformedURLException, ProgressListener progressListener,
ProgressListener.Event progressEvent) throws MalformedURLException,
IOException { IOException {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
@ -280,14 +293,14 @@ public class RepoXMLHandler extends DefaultHandler {
// - 5k takes ~3 seconds // - 5k takes ~3 seconds
// on my connection. I think the 1/1.5 seconds is worth it, because as the repo grows, the tradeoff will // on my connection. I think the 1/1.5 seconds is worth it, because as the repo grows, the tradeoff will
// become more worth it. // become more worth it.
int size = connection.getContentLength(); progressEvent.total = connection.getContentLength();
Log.d("FDroid", "Downloading " + size + " bytes from " + url); Log.d("FDroid", "Downloading " + progressEvent.total + " bytes from " + url);
InputStream input = null; InputStream input = null;
OutputStream output = null; OutputStream output = null;
try { try {
input = connection.getInputStream(); input = connection.getInputStream();
output = ctx.openFileOutput(dest, Context.MODE_PRIVATE); output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
Utils.copy(input, output, size, progressListener, PROGRESS_TYPE_DOWNLOAD); Utils.copy(input, output, progressListener, progressEvent);
} finally { } finally {
Utils.closeQuietly(output); Utils.closeQuietly(output);
Utils.closeQuietly(input); Utils.closeQuietly(input);
@ -329,8 +342,11 @@ public class RepoXMLHandler extends DefaultHandler {
address += "?" + pi.versionName; address += "?" + pi.versionName;
} catch (Exception e) { } catch (Exception e) {
} }
Bundle progressData = createProgressData(repo.address);
ProgressListener.Event event = new ProgressListener.Event(
RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, progressData);
code = getRemoteFile(ctx, address, "tempindex.jar", code = getRemoteFile(ctx, address, "tempindex.jar",
repo.lastetag, newetag, progressListener); repo.lastetag, newetag, progressListener, event );
if (code == 200) { if (code == 200) {
String jarpath = ctx.getFilesDir() + "/tempindex.jar"; String jarpath = ctx.getFilesDir() + "/tempindex.jar";
JarFile jar = null; JarFile jar = null;
@ -385,8 +401,12 @@ public class RepoXMLHandler extends DefaultHandler {
// It's an old-fashioned unsigned repo... // It's an old-fashioned unsigned repo...
Log.d("FDroid", "Getting unsigned index from " + repo.address); Log.d("FDroid", "Getting unsigned index from " + repo.address);
Bundle eventData = createProgressData(repo.address);
ProgressListener.Event event = new ProgressListener.Event(
RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, eventData);
code = getRemoteFile(ctx, repo.address + "/index.xml", code = getRemoteFile(ctx, repo.address + "/index.xml",
"tempindex.xml", repo.lastetag, newetag, progressListener); "tempindex.xml", repo.lastetag, newetag,
progressListener, event);
} }
if (code == 200) { if (code == 200) {
@ -394,7 +414,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.id, apps, progressListener); RepoXMLHandler handler = new RepoXMLHandler(repo, apps, progressListener);
xr.setContentHandler(handler); xr.setContentHandler(handler);
File tempIndex = new File(ctx.getFilesDir() + "/tempindex.xml"); File tempIndex = new File(ctx.getFilesDir() + "/tempindex.xml");

View File

@ -330,16 +330,18 @@ public class UpdateService extends IntentService implements ProgressListener {
* It could be progress downloading from the repo, or perhaps processing the info from the repo. * It could be progress downloading from the repo, or perhaps processing the info from the repo.
*/ */
@Override @Override
public void onProgress(int type, int progress, int total) { public void onProgress(ProgressListener.Event event) {
String message = ""; String message = "";
if (type == RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD) { if (event.type == RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD) {
String downloadedSize = Utils.getFriendlySize( progress ); String repoAddress = event.data.getString(RepoXMLHandler.PROGRESS_DATA_REPO);
String totalSize = Utils.getFriendlySize( total ); String downloadedSize = Utils.getFriendlySize( event.progress );
int percent = (int)((double)progress/total * 100); String totalSize = Utils.getFriendlySize( event.total );
message = getString(R.string.status_download, downloadedSize, totalSize, percent); int percent = (int)((double)event.progress/event.total * 100);
} else if (type == RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML) { message = getString(R.string.status_download, repoAddress, downloadedSize, totalSize, percent);
message = getString(R.string.status_processing_xml, progress, total); } else if (event.type == RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML) {
String repoAddress = event.data.getString(RepoXMLHandler.PROGRESS_DATA_REPO);
message = getString(R.string.status_processing_xml, repoAddress, event.progress, event.total);
} }
sendStatus(STATUS_INFO, message); sendStatus(STATUS_INFO, message);

View File

@ -18,18 +18,9 @@
package org.fdroid.fdroid; package org.fdroid.fdroid;
import java.io.Closeable; import java.io.*;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public final class Utils { public final class Utils {
private Utils() {
}
public static final int BUFFER_SIZE = 4096; public static final int BUFFER_SIZE = 4096;
@ -37,28 +28,31 @@ public final class Utils {
"%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB" }; "%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB" };
public static void copy(InputStream input, OutputStream output) public static void copy(InputStream input, OutputStream output)
throws IOException { throws IOException {
copy(input, output, -1, null, -1); copy(input, output, null, null);
} }
public static void copy(InputStream input, OutputStream output, int totalSize, ProgressListener progressListener, int progressType) public static void copy(InputStream input, OutputStream output,
throws IOException { ProgressListener progressListener,
byte[] buffer = new byte[BUFFER_SIZE]; ProgressListener.Event templateProgressEvent)
int bytesRead = 0; throws IOException {
while (true) { byte[] buffer = new byte[BUFFER_SIZE];
int count = input.read(buffer); int bytesRead = 0;
if (count == -1) { while (true) {
break; int count = input.read(buffer);
} if (count == -1) {
if (progressListener != null) { break;
bytesRead += count; }
progressListener.onProgress(progressType, bytesRead, totalSize); if (progressListener != null) {
} bytesRead += count;
output.write(buffer, 0, count); templateProgressEvent.progress = bytesRead;
} progressListener.onProgress(templateProgressEvent);
output.flush(); }
} output.write(buffer, 0, count);
}
output.flush();
}
public static void closeQuietly(Closeable closeable) { public static void closeQuietly(Closeable closeable) {
if (closeable == null) { if (closeable == null) {