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:
parent
d850ae2307
commit
2c80f1a758
@ -5,24 +5,18 @@ import java.net.*;
|
|||||||
import android.content.*;
|
import android.content.*;
|
||||||
import org.fdroid.fdroid.*;
|
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 OutputStream outputStream;
|
||||||
private ProgressListener progressListener = null;
|
private ProgressListener progressListener = null;
|
||||||
private ProgressListener.Event progressEvent = null;
|
private ProgressListener.Event progressEvent = null;
|
||||||
private String eTag = null;
|
|
||||||
private final File outputFile;
|
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.
|
// 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 {
|
throws FileNotFoundException, MalformedURLException {
|
||||||
sourceUrl = new URL(source);
|
|
||||||
outputStream = ctx.openFileOutput(destFile, Context.MODE_PRIVATE);
|
outputStream = ctx.openFileOutput(destFile, Context.MODE_PRIVATE);
|
||||||
outputFile = new File(ctx.getFilesDir() + File.separator + destFile);
|
outputFile = new File(ctx.getFilesDir() + File.separator + destFile);
|
||||||
}
|
}
|
||||||
@ -32,16 +26,14 @@ public class Downloader {
|
|||||||
* you are done*.
|
* you are done*.
|
||||||
* @see org.fdroid.fdroid.net.Downloader#getFile()
|
* @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
|
// http://developer.android.com/guide/topics/data/data-storage.html#InternalCache
|
||||||
outputFile = File.createTempFile("dl-", "", ctx.getCacheDir());
|
outputFile = File.createTempFile("dl-", "", ctx.getCacheDir());
|
||||||
outputStream = new FileOutputStream(outputFile);
|
outputStream = new FileOutputStream(outputFile);
|
||||||
sourceUrl = new URL(source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Downloader(String source, OutputStream output)
|
public Downloader(OutputStream output)
|
||||||
throws MalformedURLException {
|
throws MalformedURLException {
|
||||||
sourceUrl = new URL(source);
|
|
||||||
outputStream = output;
|
outputStream = output;
|
||||||
outputFile = null;
|
outputFile = null;
|
||||||
}
|
}
|
||||||
@ -61,82 +53,25 @@ public class Downloader {
|
|||||||
return outputFile;
|
return outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setupProgressListener() {
|
||||||
* Only available after downloading a file.
|
if (progressListener != null && progressEvent != null) {
|
||||||
*/
|
progressEvent.total = totalDownloadSize();
|
||||||
public int getStatusCode() {
|
}
|
||||||
return statusCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected abstract int totalDownloadSize();
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void download() throws IOException {
|
||||||
* 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();
|
setupProgressListener();
|
||||||
InputStream input = null;
|
InputStream input = null;
|
||||||
try {
|
try {
|
||||||
input = connection.getInputStream();
|
input = inputStream();
|
||||||
Utils.copy(input, outputStream,
|
Utils.copy(input, outputStream,
|
||||||
progressListener, progressEvent);
|
progressListener, progressEvent);
|
||||||
} finally {
|
} finally {
|
||||||
Utils.closeQuietly(outputStream);
|
Utils.closeQuietly(outputStream);
|
||||||
Utils.closeQuietly(input);
|
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() {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasChanged() {
|
|
||||||
return this.statusCode == 200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
115
src/org/fdroid/fdroid/net/HttpDownloader.java
Normal file
115
src/org/fdroid/fdroid/net/HttpDownloader.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,6 +12,7 @@ import org.fdroid.fdroid.data.App;
|
|||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
|
import org.fdroid.fdroid.net.HttpDownloader;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
import org.xml.sax.XMLReader;
|
import org.xml.sax.XMLReader;
|
||||||
@ -84,11 +85,11 @@ abstract public class RepoUpdater {
|
|||||||
|
|
||||||
protected abstract String getIndexAddress();
|
protected abstract String getIndexAddress();
|
||||||
|
|
||||||
protected Downloader downloadIndex() throws UpdateException {
|
protected HttpDownloader downloadIndex() throws UpdateException {
|
||||||
Bundle progressData = createProgressData(repo.address);
|
Bundle progressData = createProgressData(repo.address);
|
||||||
Downloader downloader = null;
|
HttpDownloader downloader = null;
|
||||||
try {
|
try {
|
||||||
downloader = new Downloader(getIndexAddress(), context);
|
downloader = new HttpDownloader(getIndexAddress(), context);
|
||||||
downloader.setETag(repo.lastetag);
|
downloader.setETag(repo.lastetag);
|
||||||
|
|
||||||
if (isInteractive()) {
|
if (isInteractive()) {
|
||||||
@ -98,7 +99,7 @@ abstract public class RepoUpdater {
|
|||||||
downloader.setProgressListener(progressListener, event);
|
downloader.setProgressListener(progressListener, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
int status = downloader.download();
|
int status = downloader.downloadHttpFile();
|
||||||
|
|
||||||
if (status == 304) {
|
if (status == 304) {
|
||||||
// The index is unchanged since we last read it. We just mark
|
// The index is unchanged since we last read it. We just mark
|
||||||
@ -169,7 +170,7 @@ abstract public class RepoUpdater {
|
|||||||
File indexFile = null;
|
File indexFile = null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
Downloader downloader = downloadIndex();
|
HttpDownloader downloader = downloadIndex();
|
||||||
hasChanged = downloader.hasChanged();
|
hasChanged = downloader.hasChanged();
|
||||||
|
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user