Split net.Downloader into abstract Downloader and concrete HttpDownloader.

This will allow the more general, non HTTP related stuff (progress events,
stream copying) to occur in a separate base class. HTTP specific stuff
(HTTP status codes, etag cache checking) is done in the HTTPDownloader
base class.
This commit is contained in:
Peter Serwylo 2014-02-23 18:08:24 +11:00
parent d850ae2307
commit 2c80f1a758
3 changed files with 142 additions and 91 deletions

View File

@ -5,24 +5,18 @@ import java.net.*;
import android.content.*;
import org.fdroid.fdroid.*;
public class Downloader {
public abstract class Downloader {
private static final String HEADER_IF_NONE_MATCH = "If-None-Match";
private static final String HEADER_FIELD_ETAG = "ETag";
private URL sourceUrl;
private OutputStream outputStream;
private ProgressListener progressListener = null;
private ProgressListener.Event progressEvent = null;
private String eTag = null;
private final File outputFile;
private HttpURLConnection connection;
private int statusCode = -1;
public abstract InputStream inputStream() throws IOException;
// The context is required for opening the file to write to.
public Downloader(String source, String destFile, Context ctx)
public Downloader(String destFile, Context ctx)
throws FileNotFoundException, MalformedURLException {
sourceUrl = new URL(source);
outputStream = ctx.openFileOutput(destFile, Context.MODE_PRIVATE);
outputFile = new File(ctx.getFilesDir() + File.separator + destFile);
}
@ -32,16 +26,14 @@ public class Downloader {
* you are done*.
* @see org.fdroid.fdroid.net.Downloader#getFile()
*/
public Downloader(String source, Context ctx) throws IOException {
public Downloader(Context ctx) throws IOException {
// http://developer.android.com/guide/topics/data/data-storage.html#InternalCache
outputFile = File.createTempFile("dl-", "", ctx.getCacheDir());
outputStream = new FileOutputStream(outputFile);
sourceUrl = new URL(source);
}
public Downloader(String source, OutputStream output)
public Downloader(OutputStream output)
throws MalformedURLException {
sourceUrl = new URL(source);
outputStream = output;
outputFile = null;
}
@ -61,82 +53,25 @@ public class Downloader {
return outputFile;
}
/**
* Only available after downloading a file.
*/
public int getStatusCode() {
return statusCode;
}
/**
* If you ask for the eTag before calling download(), you will get the
* same one you passed in (if any). If you call it after download(), you
* will get the new eTag from the server, or null if there was none.
*/
public String getETag() {
return eTag;
}
/**
* If this eTag matches that returned by the server, then no download will
* take place, and a status code of 304 will be returned by download().
*/
public void setETag(String eTag) {
this.eTag = eTag;
}
// Get a remote file. Returns the HTTP response code.
// If 'etag' is not null, it's passed to the server as an If-None-Match
// header, in which case expect a 304 response if nothing changed.
// In the event of a 200 response ONLY, 'retag' (which should be passed
// empty) may contain an etag value for the response, or it may be left
// empty if none was available.
public int download() throws IOException {
connection = (HttpURLConnection)sourceUrl.openConnection();
setupCacheCheck();
statusCode = connection.getResponseCode();
if (statusCode == 200) {
setupProgressListener();
InputStream input = null;
try {
input = connection.getInputStream();
Utils.copy(input, outputStream,
progressListener, progressEvent);
} finally {
Utils.closeQuietly(outputStream);
Utils.closeQuietly(input);
}
updateCacheCheck();
}
return statusCode;
}
protected void setupCacheCheck() {
if (eTag != null) {
connection.setRequestProperty(HEADER_IF_NONE_MATCH, eTag);
}
}
protected void updateCacheCheck() {
eTag = connection.getHeaderField(HEADER_FIELD_ETAG);
}
protected void setupProgressListener() {
private void setupProgressListener() {
if (progressListener != null && progressEvent != null) {
// Testing in the emulator for me, showed that figuring out the
// filesize took about 1 to 1.5 seconds.
// To put this in context, downloading a repo of:
// - 400k takes ~6 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
// become more worth it.
progressEvent.total = connection.getContentLength();
progressEvent.total = totalDownloadSize();
}
}
public boolean hasChanged() {
return this.statusCode == 200;
protected abstract int totalDownloadSize();
public void download() throws IOException {
setupProgressListener();
InputStream input = null;
try {
input = inputStream();
Utils.copy(input, outputStream,
progressListener, progressEvent);
} finally {
Utils.closeQuietly(outputStream);
Utils.closeQuietly(input);
}
}
}

View File

@ -0,0 +1,115 @@
package org.fdroid.fdroid.net;
import android.content.Context;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class HttpDownloader extends Downloader {
private static final String HEADER_IF_NONE_MATCH = "If-None-Match";
private static final String HEADER_FIELD_ETAG = "ETag";
private URL sourceUrl;
private String eTag = null;
private HttpURLConnection connection;
private int statusCode = -1;
// The context is required for opening the file to write to.
public HttpDownloader(String source, String destFile, Context ctx)
throws FileNotFoundException, MalformedURLException {
super(destFile, ctx);
sourceUrl = new URL(source);
}
/**
* Downloads to a temporary file, which *you must delete yourself when
* you are done*.
* @see org.fdroid.fdroid.net.HttpDownloader#getFile()
*/
public HttpDownloader(String source, Context ctx) throws IOException {
super(ctx);
sourceUrl = new URL(source);
}
public HttpDownloader(String source, OutputStream output)
throws MalformedURLException {
super(output);
sourceUrl = new URL(source);
}
public InputStream inputStream() throws IOException {
return connection.getInputStream();
}
// Get a remote file. Returns the HTTP response code.
// If 'etag' is not null, it's passed to the server as an If-None-Match
// header, in which case expect a 304 response if nothing changed.
// In the event of a 200 response ONLY, 'retag' (which should be passed
// empty) may contain an etag value for the response, or it may be left
// empty if none was available.
public int downloadHttpFile() throws IOException {
connection = (HttpURLConnection)sourceUrl.openConnection();
setupCacheCheck();
statusCode = connection.getResponseCode();
if (statusCode == 200) {
download();
updateCacheCheck();
}
return statusCode;
}
/**
* Only available after downloading a file.
*/
public int getStatusCode() {
return statusCode;
}
/**
* If you ask for the eTag before calling download(), you will get the
* same one you passed in (if any). If you call it after download(), you
* will get the new eTag from the server, or null if there was none.
*/
public String getETag() {
return eTag;
}
/**
* If this eTag matches that returned by the server, then no download will
* take place, and a status code of 304 will be returned by download().
*/
public void setETag(String eTag) {
this.eTag = eTag;
}
protected void setupCacheCheck() {
if (eTag != null) {
connection.setRequestProperty(HEADER_IF_NONE_MATCH, eTag);
}
}
protected void updateCacheCheck() {
eTag = connection.getHeaderField(HEADER_FIELD_ETAG);
}
// Testing in the emulator for me, showed that figuring out the
// filesize took about 1 to 1.5 seconds.
// To put this in context, downloading a repo of:
// - 400k takes ~6 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
// become more worth it.
protected int totalDownloadSize() {
return connection.getContentLength();
}
public boolean hasChanged() {
return this.statusCode == 200;
}
}

View File

@ -12,6 +12,7 @@ import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.HttpDownloader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
@ -84,11 +85,11 @@ abstract public class RepoUpdater {
protected abstract String getIndexAddress();
protected Downloader downloadIndex() throws UpdateException {
protected HttpDownloader downloadIndex() throws UpdateException {
Bundle progressData = createProgressData(repo.address);
Downloader downloader = null;
HttpDownloader downloader = null;
try {
downloader = new Downloader(getIndexAddress(), context);
downloader = new HttpDownloader(getIndexAddress(), context);
downloader.setETag(repo.lastetag);
if (isInteractive()) {
@ -98,7 +99,7 @@ abstract public class RepoUpdater {
downloader.setProgressListener(progressListener, event);
}
int status = downloader.download();
int status = downloader.downloadHttpFile();
if (status == 304) {
// The index is unchanged since we last read it. We just mark
@ -169,7 +170,7 @@ abstract public class RepoUpdater {
File indexFile = null;
try {
Downloader downloader = downloadIndex();
HttpDownloader downloader = downloadIndex();
hasChanged = downloader.hasChanged();
if (hasChanged) {