implement resumable APK downloads for HttpDownloader
Moving towards having the Downloader always download directly into the cache means its really easy to support resuming downloads. This should also work for updates and icons, since it fetches the Content Length via HTTP first. The icon and update downloading code needs to be adjusted to support that. For APKs, it probably makes sense to include the file size from the index db to save the query over the net. That would be probably more helpful on Bluetooth.
This commit is contained in:
parent
fa766180bf
commit
3265c8634c
@ -80,7 +80,7 @@ public class BluetoothDownloader extends Downloader {
|
||||
|
||||
@Override
|
||||
public void download() throws IOException, InterruptedException {
|
||||
downloadFromStream(1024);
|
||||
downloadFromStream(1024, false);
|
||||
connection.closeQuietly();
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,6 @@ public abstract class Downloader {
|
||||
private volatile int totalBytes;
|
||||
private Timer timer;
|
||||
|
||||
private final OutputStream outputStream;
|
||||
|
||||
public final File outputFile;
|
||||
|
||||
protected final URL sourceUrl;
|
||||
@ -60,7 +58,6 @@ public abstract class Downloader {
|
||||
throws FileNotFoundException, MalformedURLException {
|
||||
this.sourceUrl = url;
|
||||
outputFile = destFile;
|
||||
outputStream = new FileOutputStream(outputFile);
|
||||
}
|
||||
|
||||
public final InputStream getInputStream() throws IOException {
|
||||
@ -100,9 +97,10 @@ public abstract class Downloader {
|
||||
|
||||
public abstract boolean isCached();
|
||||
|
||||
protected void downloadFromStream(int bufferSize) throws IOException, InterruptedException {
|
||||
protected void downloadFromStream(int bufferSize, boolean resumable) throws IOException, InterruptedException {
|
||||
Utils.debugLog(TAG, "Downloading from stream");
|
||||
InputStream input = null;
|
||||
OutputStream outputStream = new FileOutputStream(outputFile, resumable);
|
||||
try {
|
||||
input = getInputStream();
|
||||
|
||||
@ -110,7 +108,7 @@ public abstract class Downloader {
|
||||
// we were interrupted before proceeding to the download.
|
||||
throwExceptionIfInterrupted();
|
||||
|
||||
copyInputToOutputStream(input, bufferSize);
|
||||
copyInputToOutputStream(input, bufferSize, outputStream);
|
||||
} finally {
|
||||
Utils.closeQuietly(outputStream);
|
||||
Utils.closeQuietly(input);
|
||||
@ -126,6 +124,7 @@ public abstract class Downloader {
|
||||
* interrupt occured during that blocking operation. The goal is to ensure we
|
||||
* don't move onto another slow, network operation if we have cancelled the
|
||||
* download.
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private void throwExceptionIfInterrupted() throws InterruptedException {
|
||||
@ -150,7 +149,7 @@ public abstract class Downloader {
|
||||
* keeping track of the number of bytes that have flowed through for the
|
||||
* progress counter.
|
||||
*/
|
||||
private void copyInputToOutputStream(InputStream input, int bufferSize) throws IOException, InterruptedException {
|
||||
private void copyInputToOutputStream(InputStream input, int bufferSize, OutputStream output) throws IOException, InterruptedException {
|
||||
bytesRead = 0;
|
||||
totalBytes = totalDownloadSize();
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
@ -178,14 +177,13 @@ public abstract class Downloader {
|
||||
Utils.debugLog(TAG, "Finished downloading from stream");
|
||||
break;
|
||||
}
|
||||
|
||||
bytesRead += count;
|
||||
outputStream.write(buffer, 0, count);
|
||||
output.write(buffer, 0, count);
|
||||
}
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
output.flush();
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@ package org.fdroid.fdroid.net;
|
||||
|
||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.spongycastle.util.encoders.Base64;
|
||||
@ -65,7 +66,7 @@ public class HttpDownloader extends Downloader {
|
||||
*/
|
||||
@Override
|
||||
protected InputStream getDownloadersInputStream() throws IOException {
|
||||
setupConnection();
|
||||
setupConnection(false);
|
||||
return new BufferedInputStream(connection.getInputStream());
|
||||
}
|
||||
|
||||
@ -79,8 +80,25 @@ public class HttpDownloader extends Downloader {
|
||||
*/
|
||||
@Override
|
||||
public void download() throws IOException, InterruptedException {
|
||||
setupConnection();
|
||||
doDownload();
|
||||
boolean resumable = false;
|
||||
long fileLength = outputFile.length();
|
||||
|
||||
// get the file size from the server
|
||||
HttpURLConnection tmpConn = getConnection();
|
||||
int contentLength = -1;
|
||||
if (tmpConn.getResponseCode() == 200) {
|
||||
contentLength = tmpConn.getContentLength();
|
||||
}
|
||||
tmpConn.disconnect();
|
||||
if (fileLength > contentLength) {
|
||||
FileUtils.deleteQuietly(outputFile);
|
||||
} else if (fileLength == contentLength && outputFile.isFile()) {
|
||||
return; // already have it!
|
||||
} else if (fileLength > 0) {
|
||||
resumable = true;
|
||||
}
|
||||
setupConnection(resumable);
|
||||
doDownload(resumable);
|
||||
}
|
||||
|
||||
private boolean isSwapUrl() {
|
||||
@ -90,10 +108,8 @@ public class HttpDownloader extends Downloader {
|
||||
&& FDroidApp.subnetInfo.isInRange(host); // on the same subnet as we are
|
||||
}
|
||||
|
||||
protected void setupConnection() throws IOException {
|
||||
if (connection != null) {
|
||||
return;
|
||||
}
|
||||
private HttpURLConnection getConnection() throws IOException {
|
||||
HttpURLConnection connection;
|
||||
if (isSwapUrl()) {
|
||||
// swap never works with a proxy, its unrouted IP on the same subnet
|
||||
connection = (HttpURLConnection) sourceUrl.openConnection();
|
||||
@ -113,9 +129,25 @@ public class HttpDownloader extends Downloader {
|
||||
String authString = username + ":" + password;
|
||||
connection.setRequestProperty("Authorization", "Basic " + Base64.toBase64String(authString.getBytes()));
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
protected void doDownload() throws IOException, InterruptedException {
|
||||
/**
|
||||
* @return Whether the connection is resumable or not
|
||||
*/
|
||||
protected void setupConnection(boolean resumable) throws IOException {
|
||||
if (connection != null) {
|
||||
return;
|
||||
}
|
||||
connection = getConnection();
|
||||
|
||||
if (resumable) {
|
||||
// partial file exists, resume the download
|
||||
connection.setRequestProperty("Range", "bytes=" + outputFile.length() + "-");
|
||||
}
|
||||
}
|
||||
|
||||
protected void doDownload(boolean resumable) throws IOException, InterruptedException {
|
||||
if (wantToCheckCache()) {
|
||||
setupCacheCheck();
|
||||
Utils.debugLog(TAG, "Checking cached status of " + sourceUrl);
|
||||
@ -125,8 +157,8 @@ public class HttpDownloader extends Downloader {
|
||||
if (isCached()) {
|
||||
Utils.debugLog(TAG, sourceUrl + " is cached, so not downloading (HTTP " + statusCode + ")");
|
||||
} else {
|
||||
Utils.debugLog(TAG, "Downloading from " + sourceUrl);
|
||||
downloadFromStream(4096);
|
||||
Utils.debugLog(TAG, "doDownload for " + sourceUrl + " " + resumable);
|
||||
downloadFromStream(8192, resumable);
|
||||
updateCacheCheck();
|
||||
}
|
||||
}
|
||||
@ -166,6 +198,8 @@ public class HttpDownloader extends Downloader {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
connection.disconnect();
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class LocalFileDownloader extends Downloader {
|
||||
|
||||
@Override
|
||||
public void download() throws IOException, InterruptedException {
|
||||
downloadFromStream(1024 * 50);
|
||||
downloadFromStream(1024 * 50, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user