From f40274d214fb93260e7bfd21fa5ab34f8d89d760 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 27 Jan 2022 14:15:09 -0300 Subject: [PATCH] Allow DownloadRequests to define a mirror that should be tried first --- .../java/org/fdroid/fdroid/IndexUpdater.java | 2 +- .../org/fdroid/fdroid/IndexV1Updater.java | 2 +- .../fdroid/fdroid/net/DownloaderFactory.java | 22 +++++++++++-------- .../org/fdroid/download/DownloadRequest.kt | 14 +++++++++++- .../org/fdroid/download/MirrorChooser.kt | 7 +++++- .../org/fdroid/download/MirrorChooserTest.kt | 13 +++++++++++ 6 files changed, 47 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/IndexUpdater.java b/app/src/main/java/org/fdroid/fdroid/IndexUpdater.java index 85ce21782..32d8f956a 100644 --- a/app/src/main/java/org/fdroid/fdroid/IndexUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/IndexUpdater.java @@ -134,7 +134,7 @@ public class IndexUpdater { try { destFile = File.createTempFile("dl-", "", context.getCacheDir()); destFile.deleteOnExit(); // this probably does nothing, but maybe... - downloader = DownloaderFactory.createWithoutMirrors(repo, Uri.parse(indexUrl), destFile); + downloader = DownloaderFactory.createWithTryFirstMirror(repo, Uri.parse(indexUrl), destFile); downloader.setCacheTag(repo.lastetag); downloader.setListener(downloadListener); downloader.download(); diff --git a/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java b/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java index bdc016366..fd3c47563 100644 --- a/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java +++ b/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java @@ -115,7 +115,7 @@ public class IndexV1Updater extends IndexUpdater { destFile = File.createTempFile("dl-", "", context.getCacheDir()); destFile.deleteOnExit(); // this probably does nothing, but maybe... // read file name from file - downloader = DownloaderFactory.createWithoutMirrors(repo, Uri.parse(indexUrl), destFile); + downloader = DownloaderFactory.createWithTryFirstMirror(repo, Uri.parse(indexUrl), destFile); downloader.setCacheTag(repo.lastetag); downloader.setListener(downloadListener); downloader.download(); diff --git a/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java b/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java index 27a1a6ab4..949b59e18 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java +++ b/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java @@ -3,6 +3,8 @@ package org.fdroid.fdroid.net; import android.content.ContentResolver; import android.net.Uri; +import androidx.annotation.Nullable; + import org.fdroid.download.DownloadRequest; import org.fdroid.download.Downloader; import org.fdroid.download.HttpDownloader; @@ -15,7 +17,6 @@ import org.fdroid.fdroid.data.Repo; import java.io.File; import java.io.IOException; import java.net.Proxy; -import java.util.Collections; import java.util.List; import info.guardianproject.netcipher.NetCipher; @@ -28,22 +29,24 @@ public class DownloaderFactory { new HttpManager(Utils.getUserAgent(), FDroidApp.queryString, NetCipher.getProxy()); /** - * Same as {@link #create(Repo, Uri, File)}, but not using mirrors for download. - * + * Same as {@link #create(Repo, Uri, File)}, but trying canonical address first. + *

* See https://gitlab.com/fdroid/fdroidclient/-/issues/1708 for why this is still needed. */ - public static Downloader createWithoutMirrors(Repo repo, Uri uri, File destFile) + public static Downloader createWithTryFirstMirror(Repo repo, Uri uri, File destFile) throws IOException { - List mirrors = Collections.singletonList(new Mirror(repo.address)); - return create(repo, mirrors, uri, destFile); + Mirror tryFirst = new Mirror(repo.address); + List mirrors = Mirror.fromStrings(repo.getMirrorList()); + return create(repo, mirrors, uri, destFile, tryFirst); } public static Downloader create(Repo repo, Uri uri, File destFile) throws IOException { List mirrors = Mirror.fromStrings(repo.getMirrorList()); - return create(repo, mirrors, uri, destFile); + return create(repo, mirrors, uri, destFile, null); } - private static Downloader create(Repo repo, List mirrors, Uri uri, File destFile) throws IOException { + private static Downloader create(Repo repo, List mirrors, Uri uri, File destFile, + @Nullable Mirror tryFirst) throws IOException { Downloader downloader; String scheme = uri.getScheme(); @@ -57,7 +60,8 @@ public class DownloaderFactory { String path = uri.toString().replace(repo.address, ""); Utils.debugLog(TAG, "Using suffix " + path + " with mirrors " + mirrors); Proxy proxy = NetCipher.getProxy(); - DownloadRequest request = new DownloadRequest(path, mirrors, proxy, repo.username, repo.password); + DownloadRequest request = + new DownloadRequest(path, mirrors, proxy, repo.username, repo.password, tryFirst); downloader = new HttpDownloader(HTTP_MANAGER, request, destFile); } return downloader; diff --git a/download/src/commonMain/kotlin/org/fdroid/download/DownloadRequest.kt b/download/src/commonMain/kotlin/org/fdroid/download/DownloadRequest.kt index 457beb336..d8467aabf 100644 --- a/download/src/commonMain/kotlin/org/fdroid/download/DownloadRequest.kt +++ b/download/src/commonMain/kotlin/org/fdroid/download/DownloadRequest.kt @@ -9,4 +9,16 @@ public data class DownloadRequest @JvmOverloads constructor( val proxy: ProxyConfig? = null, val username: String? = null, val password: String? = null, -) + /** + * Signals the [MirrorChooser] that this mirror should be tried before all other mirrors. + * This could be useful for index updates for repositories with mirrors that update infrequently, + * so that the official repository can be tried first to get updates fast. + */ + val tryFirstMirror: Mirror? = null, +) { + init { + require(tryFirstMirror == null || mirrors.contains(tryFirstMirror)) { + "$tryFirstMirror not in mirrors." + } + } +} diff --git a/download/src/commonMain/kotlin/org/fdroid/download/MirrorChooser.kt b/download/src/commonMain/kotlin/org/fdroid/download/MirrorChooser.kt index 568224dfc..1ea1b4674 100644 --- a/download/src/commonMain/kotlin/org/fdroid/download/MirrorChooser.kt +++ b/download/src/commonMain/kotlin/org/fdroid/download/MirrorChooser.kt @@ -58,7 +58,12 @@ internal class MirrorChooserRandom : MirrorChooserImpl() { */ override fun orderMirrors(downloadRequest: DownloadRequest): List { // simple random selection for now - return downloadRequest.mirrors.toMutableList().apply { shuffle() } + return downloadRequest.mirrors.toMutableList().apply { shuffle() }.also { mirrors -> + // respect the mirror to try first, if set + if (downloadRequest.tryFirstMirror != null) { + mirrors.sortBy { if (it == downloadRequest.tryFirstMirror) 0 else 1 } + } + } } } diff --git a/download/src/commonTest/kotlin/org/fdroid/download/MirrorChooserTest.kt b/download/src/commonTest/kotlin/org/fdroid/download/MirrorChooserTest.kt index b4e12699e..a0364b48d 100644 --- a/download/src/commonTest/kotlin/org/fdroid/download/MirrorChooserTest.kt +++ b/download/src/commonTest/kotlin/org/fdroid/download/MirrorChooserTest.kt @@ -34,4 +34,17 @@ class MirrorChooserTest { assertEquals(mirrors.toSet(), orderedMirrors.toSet()) } + @Test + fun testMirrorChooserRandomRespectsTryFirstMirror() { + val mirrorChooser = MirrorChooserRandom() + + val tryFirstRequest = downloadRequest.copy(tryFirstMirror = Mirror("42")) + val orderedMirrors = mirrorChooser.orderMirrors(tryFirstRequest) + + // try-first mirror is first in list + assertEquals(tryFirstRequest.tryFirstMirror, orderedMirrors[0]) + // set of input mirrors is equal to set of output mirrors + assertEquals(mirrors.toSet(), orderedMirrors.toSet()) + } + }