Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0e79b791 authored by Gabriele M's avatar Gabriele M
Browse files

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
parent 3fc17494
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -375,6 +375,7 @@ public class UpdaterController implements Controller {
                    .setDestination(update.getFile())
                    .setDownloadCallback(getDownloadCallback(downloadId))
                    .setProgressListener(getProgressListener(downloadId))
                    .setUseDuplicateLinks(true)
                    .build();
        } catch (IOException exception) {
            Log.e(TAG, "Could not build download client");
@@ -417,6 +418,7 @@ public class UpdaterController implements Controller {
                        .setDestination(update.getFile())
                        .setDownloadCallback(getDownloadCallback(downloadId))
                        .setProgressListener(getProgressListener(downloadId))
                        .setUseDuplicateLinks(true)
                        .build();
            } catch (IOException exception) {
                Log.e(TAG, "Could not build download client");
+8 −1
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ public interface DownloadClient {
        private File mDestination;
        private DownloadClient.DownloadCallback mCallback;
        private DownloadClient.ProgressListener mProgressListener;
        private boolean mUseDuplicateLinks;

        public DownloadClient build() throws IOException {
            if (mUrl == null) {
@@ -72,7 +73,8 @@ public interface DownloadClient {
            } else if (mCallback == null) {
                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) {
@@ -94,5 +96,10 @@ public interface DownloadClient {
            mProgressListener = progressListener;
            return this;
        }

        public Builder setUseDuplicateLinks(boolean useDuplicateLinks) {
            mUseDuplicateLinks = useDuplicateLinks;
            return this;
        }
    }
}
+102 −2
Original line number Diff line number Diff line
@@ -25,18 +25,23 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class HttpURLConnectionClient implements DownloadClient {

    private final static String TAG = "HttpURLConnectionClient";

    private final HttpURLConnection mClient;
    private HttpURLConnection mClient;

    private final File mDestination;
    private final DownloadClient.ProgressListener mProgressListener;
    private final DownloadClient.DownloadCallback mCallback;
    private final boolean mUseDuplicateLinks;

    private DownloadThread mDownloadThread;

@@ -54,11 +59,13 @@ public class HttpURLConnectionClient implements DownloadClient {

    HttpURLConnectionClient(String url, File destination,
            DownloadClient.ProgressListener progressListener,
            DownloadClient.DownloadCallback callback) throws IOException {
            DownloadClient.DownloadCallback callback,
            boolean useDuplicateLinks) throws IOException {
        mClient = (HttpURLConnection) new URL(url).openConnection();
        mDestination = destination;
        mProgressListener = progressListener;
        mCallback = callback;
        mUseDuplicateLinks = useDuplicateLinks;
    }

    @Override
@@ -113,6 +120,10 @@ public class HttpURLConnectionClient implements DownloadClient {
        return (statusCode / 100) == 2;
    }

    private static boolean isRedirectCode(int statusCode) {
        return (statusCode / 100) == 3;
    }

    private static boolean isPartialContentCode(int statusCode) {
        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
        public void run() {
            try {
                mClient.setInstanceFollowRedirects(!mUseDuplicateLinks);
                mClient.connect();
                int responseCode = mClient.getResponseCode();

                if (mUseDuplicateLinks && isRedirectCode(responseCode)) {
                    handleDuplicateLinks();
                    responseCode = mClient.getResponseCode();
                }

                mCallback.onResponse(responseCode, mClient.getURL().toString(), new Headers());

                if (mResume && isPartialContentCode(responseCode)) {