Add support for duplicate links
Support duplicate links [1] to handle better temporarily unavailable mirrors. [1] https://tools.ietf.org/html/rfc6249 Change-Id: If78fb4a90da68ef221294eed2c59063a14cf1f43
This commit is contained in:
@@ -375,6 +375,7 @@ public class UpdaterController implements Controller {
|
|||||||
.setDestination(update.getFile())
|
.setDestination(update.getFile())
|
||||||
.setDownloadCallback(getDownloadCallback(downloadId))
|
.setDownloadCallback(getDownloadCallback(downloadId))
|
||||||
.setProgressListener(getProgressListener(downloadId))
|
.setProgressListener(getProgressListener(downloadId))
|
||||||
|
.setUseDuplicateLinks(true)
|
||||||
.build();
|
.build();
|
||||||
} catch (IOException exception) {
|
} catch (IOException exception) {
|
||||||
Log.e(TAG, "Could not build download client");
|
Log.e(TAG, "Could not build download client");
|
||||||
@@ -417,6 +418,7 @@ public class UpdaterController implements Controller {
|
|||||||
.setDestination(update.getFile())
|
.setDestination(update.getFile())
|
||||||
.setDownloadCallback(getDownloadCallback(downloadId))
|
.setDownloadCallback(getDownloadCallback(downloadId))
|
||||||
.setProgressListener(getProgressListener(downloadId))
|
.setProgressListener(getProgressListener(downloadId))
|
||||||
|
.setUseDuplicateLinks(true)
|
||||||
.build();
|
.build();
|
||||||
} catch (IOException exception) {
|
} catch (IOException exception) {
|
||||||
Log.e(TAG, "Could not build download client");
|
Log.e(TAG, "Could not build download client");
|
||||||
|
@@ -63,6 +63,7 @@ public interface DownloadClient {
|
|||||||
private File mDestination;
|
private File mDestination;
|
||||||
private DownloadClient.DownloadCallback mCallback;
|
private DownloadClient.DownloadCallback mCallback;
|
||||||
private DownloadClient.ProgressListener mProgressListener;
|
private DownloadClient.ProgressListener mProgressListener;
|
||||||
|
private boolean mUseDuplicateLinks;
|
||||||
|
|
||||||
public DownloadClient build() throws IOException {
|
public DownloadClient build() throws IOException {
|
||||||
if (mUrl == null) {
|
if (mUrl == null) {
|
||||||
@@ -72,7 +73,8 @@ public interface DownloadClient {
|
|||||||
} else if (mCallback == null) {
|
} else if (mCallback == null) {
|
||||||
throw new IllegalStateException("No download callback defined");
|
throw new IllegalStateException("No download callback defined");
|
||||||
}
|
}
|
||||||
return new HttpURLConnectionClient(mUrl, mDestination, mProgressListener, mCallback);
|
return new HttpURLConnectionClient(mUrl, mDestination, mProgressListener, mCallback,
|
||||||
|
mUseDuplicateLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setUrl(String url) {
|
public Builder setUrl(String url) {
|
||||||
@@ -94,5 +96,10 @@ public interface DownloadClient {
|
|||||||
mProgressListener = progressListener;
|
mProgressListener = progressListener;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder setUseDuplicateLinks(boolean useDuplicateLinks) {
|
||||||
|
mUseDuplicateLinks = useDuplicateLinks;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,18 +25,23 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class HttpURLConnectionClient implements DownloadClient {
|
public class HttpURLConnectionClient implements DownloadClient {
|
||||||
|
|
||||||
private final static String TAG = "HttpURLConnectionClient";
|
private final static String TAG = "HttpURLConnectionClient";
|
||||||
|
|
||||||
private final HttpURLConnection mClient;
|
private HttpURLConnection mClient;
|
||||||
|
|
||||||
private final File mDestination;
|
private final File mDestination;
|
||||||
private final DownloadClient.ProgressListener mProgressListener;
|
private final DownloadClient.ProgressListener mProgressListener;
|
||||||
private final DownloadClient.DownloadCallback mCallback;
|
private final DownloadClient.DownloadCallback mCallback;
|
||||||
|
private final boolean mUseDuplicateLinks;
|
||||||
|
|
||||||
private DownloadThread mDownloadThread;
|
private DownloadThread mDownloadThread;
|
||||||
|
|
||||||
@@ -54,11 +59,13 @@ public class HttpURLConnectionClient implements DownloadClient {
|
|||||||
|
|
||||||
HttpURLConnectionClient(String url, File destination,
|
HttpURLConnectionClient(String url, File destination,
|
||||||
DownloadClient.ProgressListener progressListener,
|
DownloadClient.ProgressListener progressListener,
|
||||||
DownloadClient.DownloadCallback callback) throws IOException {
|
DownloadClient.DownloadCallback callback,
|
||||||
|
boolean useDuplicateLinks) throws IOException {
|
||||||
mClient = (HttpURLConnection) new URL(url).openConnection();
|
mClient = (HttpURLConnection) new URL(url).openConnection();
|
||||||
mDestination = destination;
|
mDestination = destination;
|
||||||
mProgressListener = progressListener;
|
mProgressListener = progressListener;
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
|
mUseDuplicateLinks = useDuplicateLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -113,6 +120,10 @@ public class HttpURLConnectionClient implements DownloadClient {
|
|||||||
return (statusCode / 100) == 2;
|
return (statusCode / 100) == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isRedirectCode(int statusCode) {
|
||||||
|
return (statusCode / 100) == 3;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isPartialContentCode(int statusCode) {
|
private static boolean isPartialContentCode(int statusCode) {
|
||||||
return statusCode == 206;
|
return statusCode == 206;
|
||||||
}
|
}
|
||||||
@@ -155,11 +166,100 @@ public class HttpURLConnectionClient implements DownloadClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void changeClientUrl(URL newUrl) throws IOException {
|
||||||
|
String range = mClient.getRequestProperty("Range");
|
||||||
|
mClient.disconnect();
|
||||||
|
mClient = (HttpURLConnection) newUrl.openConnection();
|
||||||
|
if (range != null) {
|
||||||
|
mClient.setRequestProperty("Range", range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDuplicateLinks() throws IOException {
|
||||||
|
String protocol = mClient.getURL().getProtocol();
|
||||||
|
|
||||||
|
class DuplicateLink {
|
||||||
|
private String mUrl;
|
||||||
|
private int mPriority;
|
||||||
|
private DuplicateLink(String url, int priority) {
|
||||||
|
mUrl = url;
|
||||||
|
mPriority = priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PriorityQueue<DuplicateLink> duplicates = null;
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<String>> entry : mClient.getHeaderFields().entrySet()) {
|
||||||
|
if ("Link".equalsIgnoreCase((entry.getKey()))) {
|
||||||
|
duplicates = new PriorityQueue<>(entry.getValue().size(),
|
||||||
|
new Comparator<DuplicateLink>() {
|
||||||
|
@Override
|
||||||
|
public int compare(DuplicateLink d1, DuplicateLink d2) {
|
||||||
|
return Integer.compare(d1.mPriority, d2.mPriority);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc6249
|
||||||
|
// https://tools.ietf.org/html/rfc5988#section-5
|
||||||
|
String regex = "(?i)<(.+)>\\s*;\\s*rel=duplicate(?:.*pri=([0-9]+).*|.*)?";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
for (String field : entry.getValue()) {
|
||||||
|
Matcher matcher = pattern.matcher(field);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
String url = matcher.group(1);
|
||||||
|
String pri = matcher.group(2);
|
||||||
|
int priority = pri != null ? Integer.parseInt(pri) : 999999;
|
||||||
|
duplicates.add(new DuplicateLink(url, priority));
|
||||||
|
Log.d(TAG, "Adding duplicate link " + url);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Ignoring link " + field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String newUrl = mClient.getHeaderField("Location");
|
||||||
|
for (;;) {
|
||||||
|
try {
|
||||||
|
URL url = new URL(newUrl);
|
||||||
|
if (!url.getProtocol().equals(protocol)) {
|
||||||
|
// If we hadn't handled duplicate links, we wouldn't have
|
||||||
|
// used this url.
|
||||||
|
throw new IOException("Protocol changes are not allowed");
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Downloading from " + newUrl);
|
||||||
|
changeClientUrl(url);
|
||||||
|
mClient.setConnectTimeout(5000);
|
||||||
|
mClient.connect();
|
||||||
|
if (!isSuccessCode(mClient.getResponseCode())) {
|
||||||
|
throw new IOException("Server replied with " + mClient.getResponseCode());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (duplicates != null && !duplicates.isEmpty()) {
|
||||||
|
DuplicateLink link = duplicates.poll();
|
||||||
|
duplicates.remove(link);
|
||||||
|
newUrl = link.mUrl;
|
||||||
|
Log.e(TAG, "Using duplicate link " + link.mUrl, e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
mClient.setInstanceFollowRedirects(!mUseDuplicateLinks);
|
||||||
mClient.connect();
|
mClient.connect();
|
||||||
int responseCode = mClient.getResponseCode();
|
int responseCode = mClient.getResponseCode();
|
||||||
|
|
||||||
|
if (mUseDuplicateLinks && isRedirectCode(responseCode)) {
|
||||||
|
handleDuplicateLinks();
|
||||||
|
responseCode = mClient.getResponseCode();
|
||||||
|
}
|
||||||
|
|
||||||
mCallback.onResponse(responseCode, mClient.getURL().toString(), new Headers());
|
mCallback.onResponse(responseCode, mClient.getURL().toString(), new Headers());
|
||||||
|
|
||||||
if (mResume && isPartialContentCode(responseCode)) {
|
if (mResume && isPartialContentCode(responseCode)) {
|
||||||
|
Reference in New Issue
Block a user