1
0
mirror of https://github.com/SKCraft/Launcher.git synced 2025-01-21 21:31:32 +01:00

Add support for continuing partial downloads

If the server offers byte-range requests & the request fails, we retry
with a Range header to continue the download where it was interrupted.
This commit is contained in:
Henry Le Grys 2021-03-10 00:01:16 +00:00
parent 4da80dfae0
commit ae6fb109e5
2 changed files with 44 additions and 7 deletions

View File

@ -238,6 +238,7 @@ public class HttpDownloader implements Downloader {
int trial = 0;
boolean first = true;
IOException lastException = null;
HttpRequest.PartialDownloadInfo retryDetails = null;
do {
for (URL url : urls) {
@ -249,11 +250,16 @@ public class HttpDownloader implements Downloader {
try {
request = HttpRequest.get(url);
request.execute().expectResponseCode(200).saveContent(file);
request.setResumeInfo(retryDetails).execute().expectResponseCode(200).saveContent(file);
return;
} catch (IOException e) {
lastException = e;
log.log(Level.WARNING, "Failed to download " + url, e);
Optional<HttpRequest.PartialDownloadInfo> byteRangeSupport = request.canRetryPartial();
if (byteRangeSupport.isPresent()) {
retryDetails = byteRangeSupport.get();
}
}
}
} while (++trial < tryCount);
@ -277,5 +283,4 @@ public class HttpDownloader implements Downloader {
}
}
}
}

View File

@ -9,6 +9,7 @@ package com.skcraft.launcher.util;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.skcraft.concurrency.ProgressObservable;
import lombok.Data;
import lombok.Getter;
import lombok.extern.java.Log;
@ -17,10 +18,7 @@ import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
import static org.apache.commons.io.IOUtils.closeQuietly;
@ -46,6 +44,7 @@ public class HttpRequest implements Closeable, ProgressObservable {
private InputStream inputStream;
private int redirectCount;
private PartialDownloadInfo resumeInfo = null;
private long contentLength = -1;
private long readBytes = 0;
@ -143,6 +142,10 @@ public class HttpRequest implements Closeable, ProgressObservable {
conn.setDoInput(true);
}
if (resumeInfo != null) {
conn.setRequestProperty("Range", String.format("bytes=%d-", resumeInfo.currentLength));
}
for (Map.Entry<String, String> entry : headers.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
@ -198,6 +201,11 @@ public class HttpRequest implements Closeable, ProgressObservable {
}
}
if (resumeInfo != null && responseCode == 206) {
// Allow 206 Partial Content for resumed requests
return this;
}
close();
throw new IOException("Did not get expected response code, got " + responseCode + " for " + url);
}
@ -284,7 +292,7 @@ public class HttpRequest implements Closeable, ProgressObservable {
BufferedOutputStream bos = null;
try {
fos = new FileOutputStream(file);
fos = new FileOutputStream(file, resumeInfo != null);
bos = new BufferedOutputStream(fos);
saveContent(bos);
@ -340,6 +348,24 @@ public class HttpRequest implements Closeable, ProgressObservable {
return this;
}
public Optional<PartialDownloadInfo> canRetryPartial() {
if (conn.getHeaderField("Accept-Ranges").equals("bytes")) {
return Optional.of(new PartialDownloadInfo(contentLength, readBytes));
}
return Optional.empty();
}
public HttpRequest setResumeInfo(PartialDownloadInfo info) {
this.resumeInfo = info;
return this;
}
public boolean isResumedRequest() {
return resumeInfo != null;
}
@Override
public double getProgress() {
if (contentLength >= 0) {
@ -594,4 +620,10 @@ public class HttpRequest implements Closeable, ProgressObservable {
}
}
@Data
public static class PartialDownloadInfo {
private final long expectedLength;
private final long currentLength;
}
}