diff --git a/app/src/main/java/org/fdroid/fdroid/net/BluetoothDownloader.java b/app/src/main/java/org/fdroid/fdroid/net/BluetoothDownloader.java index 63c125f44..c782d3ee6 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/BluetoothDownloader.java +++ b/app/src/main/java/org/fdroid/fdroid/net/BluetoothDownloader.java @@ -80,7 +80,7 @@ public class BluetoothDownloader extends Downloader { @Override public void download() throws IOException, InterruptedException { - downloadFromStream(1024); + downloadFromStream(1024, false); connection.closeQuietly(); } diff --git a/app/src/main/java/org/fdroid/fdroid/net/Downloader.java b/app/src/main/java/org/fdroid/fdroid/net/Downloader.java index 8be443376..ab5474ffb 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/Downloader.java +++ b/app/src/main/java/org/fdroid/fdroid/net/Downloader.java @@ -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(); } /** diff --git a/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java b/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java index 20dc237f2..f6a41c4fd 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java +++ b/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java @@ -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(); + } } } diff --git a/app/src/main/java/org/fdroid/fdroid/net/LocalFileDownloader.java b/app/src/main/java/org/fdroid/fdroid/net/LocalFileDownloader.java index 7e249040e..2207a689f 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/LocalFileDownloader.java +++ b/app/src/main/java/org/fdroid/fdroid/net/LocalFileDownloader.java @@ -43,7 +43,7 @@ public class LocalFileDownloader extends Downloader { @Override public void download() throws IOException, InterruptedException { - downloadFromStream(1024 * 50); + downloadFromStream(1024 * 50, false); } @Override