diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java index 9651fcf7f..02c3af7be 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java @@ -26,9 +26,12 @@ import org.jackhuang.hmcl.download.MojangDownloadProvider; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.FXUtils; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ThreadPoolExecutor; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.util.Lang.mapOf; @@ -57,10 +60,22 @@ public final class DownloadProviders { }); } + /** + * Get current primary preferred download provider + */ public static DownloadProvider getDownloadProvider() { return downloadProviderProperty.get(); } + /** + * Preferred download providers have the primary one first, the official one next. + * @return the preferred download providers + */ + public static List getPreferredDownloadProviders() { + DownloadProvider provider = getDownloadProvider(); + return Stream.concat(Stream.of(provider), providersById.values().stream().filter(x -> x != provider)).collect(Collectors.toList()); + } + public static ObservableObjectValue downloadProviderProperty() { return downloadProviderProperty; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java index 39c8fc01f..00a3aa575 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java @@ -162,7 +162,7 @@ public final class Profile implements Observable { } public DefaultDependencyManager getDependency() { - return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY); + return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), DownloadProviders.getPreferredDownloadProviders(), HMCLCacheRepository.REPOSITORY); } public VersionSetting getVersionSetting(String id) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java index 0f393c0cb..c80c7c788 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java @@ -88,7 +88,7 @@ public final class InstallerWizardProvider implements WizardProvider { @Override public Node createPage(WizardController controller, int step, Map settings) { - DownloadProvider provider = profile.getDependency().getDownloadProvider(); + DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider(); switch (step) { case 0: return new AdditionalInstallersPage(this, controller, profile.getRepository(), provider); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java index d757eed39..0b108652e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java @@ -64,7 +64,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider { @Override public Node createPage(WizardController controller, int step, Map settings) { - DownloadProvider provider = profile.getDependency().getDownloadProvider(); + DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider(); switch (step) { case 0: return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, provider, libraryId, () -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java index 5db7d2983..82a6b670f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java @@ -68,7 +68,7 @@ public final class VanillaInstallWizardProvider implements WizardProvider { @Override public Node createPage(WizardController controller, int step, Map settings) { - DownloadProvider provider = profile.getDependency().getDownloadProvider(); + DownloadProvider provider = profile.getDependency().getPrimaryDownloadProvider(); switch (step) { case 0: return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", provider, "game", () -> controller.onNext(new InstallersPage(controller, profile.getRepository(), provider))); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AbstractDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AbstractDependencyManager.java index b70c5da39..75859b3c3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AbstractDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AbstractDependencyManager.java @@ -17,19 +17,23 @@ */ package org.jackhuang.hmcl.download; +import java.util.List; + /** * * @author huangyuhui */ public abstract class AbstractDependencyManager implements DependencyManager { - public abstract DownloadProvider getDownloadProvider(); + public abstract DownloadProvider getPrimaryDownloadProvider(); + + public abstract List getPreferredDownloadProviders(); @Override public abstract DefaultCacheRepository getCacheRepository(); @Override public VersionList getVersionList(String id) { - return getDownloadProvider().getVersionListById(id); + return getPrimaryDownloadProvider().getVersionListById(id); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java index 49ec919b1..e8efa256a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -29,6 +29,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalFunction; import java.io.IOException; import java.nio.file.Path; +import java.util.List; /** * Note: This class has no state. @@ -39,11 +40,13 @@ public class DefaultDependencyManager extends AbstractDependencyManager { private final DefaultGameRepository repository; private final DownloadProvider downloadProvider; + private final List preferredDownloadProviders; private final DefaultCacheRepository cacheRepository; - public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, DefaultCacheRepository cacheRepository) { + public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, List preferredDownloadProviders, DefaultCacheRepository cacheRepository) { this.repository = repository; this.downloadProvider = downloadProvider; + this.preferredDownloadProviders = preferredDownloadProviders; this.cacheRepository = cacheRepository; } @@ -53,10 +56,15 @@ public class DefaultDependencyManager extends AbstractDependencyManager { } @Override - public DownloadProvider getDownloadProvider() { + public DownloadProvider getPrimaryDownloadProvider() { return downloadProvider; } + @Override + public List getPreferredDownloadProviders() { + return preferredDownloadProviders; + } + @Override public DefaultCacheRepository getCacheRepository() { return cacheRepository; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java index d943e1646..9a8433c2b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java @@ -34,7 +34,7 @@ public class DefaultGameBuilder extends GameBuilder { public DefaultGameBuilder(DefaultDependencyManager dependencyManager) { this.dependencyManager = dependencyManager; - this.downloadProvider = dependencyManager.getDownloadProvider(); + this.downloadProvider = dependencyManager.getPrimaryDownloadProvider(); } public DefaultDependencyManager getDependencyManager() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java index 5d7f276f6..c30431c75 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java @@ -50,7 +50,7 @@ public final class FabricInstallTask extends Task { this.version = version; this.remote = remoteVersion; - launchMetaTask = new GetTask(NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(getLaunchMetaUrl(remote.getGameVersion(), remote.getSelfVersion())))) + launchMetaTask = new GetTask(NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(getLaunchMetaUrl(remote.getGameVersion(), remote.getSelfVersion())))) .setCacheRepository(dependencyManager.getCacheRepository()); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java index 0ea5376ea..e6472fe9c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java @@ -32,9 +32,11 @@ import org.jackhuang.hmcl.util.gson.JsonUtils; import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; /** * @@ -101,8 +103,12 @@ public final class GameAssetDownloadTask extends Task { if (file.isFile()) dependencyManager.getCacheRepository().tryCacheFile(file.toPath(), CacheRepository.SHA1, assetObject.getHash()); else { - String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation(); - FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash())); + List urls = dependencyManager.getPreferredDownloadProviders().stream() + .map(downloadProvider -> downloadProvider.getAssetBaseURL() + assetObject.getLocation()) + .map(NetworkUtils::toURL) + .collect(Collectors.toList()); + + FileDownloadTask task = new FileDownloadTask(urls, file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash())); task.setName(assetObject.getHash()); dependencies.add(task .setCacheRepository(dependencyManager.getCacheRepository()) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java index c8226acc5..717bad4f3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java @@ -65,7 +65,7 @@ public final class GameAssetIndexDownloadTask extends Task { // We should not check the hash code of asset index file since this file is not consistent // And Mojang will modify this file anytime. So assetIndex.hash might be outdated. dependencies.add(new FileDownloadTask( - NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())), + NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(assetIndexInfo.getUrl())), assetIndexFile ).setCacheRepository(dependencyManager.getCacheRepository())); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java index d759a2ec7..d56ad0b13 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java @@ -58,7 +58,7 @@ public final class GameDownloadTask extends Task { File jar = dependencyManager.getGameRepository().getVersionJar(version); FileDownloadTask task = new FileDownloadTask( - NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())), + NetworkUtils.toURL(dependencyManager.getPrimaryDownloadProvider().injectURL(version.getDownloadInfo().getUrl())), jar, IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1())) .setCaching(true) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java index e77157ade..e2a02ac29 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java @@ -68,7 +68,7 @@ public class LibraryDownloadTask extends Task { this.library = library; this.cacheRepository = dependencyManager.getCacheRepository(); - url = dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl()); + url = dependencyManager.getPrimaryDownloadProvider().injectURL(library.getDownload().getUrl()); jar = file; xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java index 37e952158..f5181901c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java @@ -69,7 +69,7 @@ public final class VersionJsonDownloadTask extends Task { RemoteVersion remoteVersion = gameVersionList.getVersion(gameVersion, gameVersion) .orElseThrow(() -> new IOException("Cannot find specific version " + gameVersion + " in remote repository")); dependencies.add(new GetTask( - dependencyManager.getDownloadProvider().injectURLs(remoteVersion.getUrl()) + dependencyManager.getPrimaryDownloadProvider().injectURLs(remoteVersion.getUrl()) .map(NetworkUtils::toURL).collect(Collectors.toList()), UTF_8).storeTo(this::setResult)); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java index a7c4e639c..f1ee79799 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java @@ -109,7 +109,7 @@ public class FileDownloadTask extends Task { * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed */ public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck) { - this(url, file, integrityCheck, 5); + this(Collections.singletonList(url), file, integrityCheck); } /** @@ -119,13 +119,7 @@ public class FileDownloadTask extends Task { * @param retry the times for retrying if downloading fails. */ public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck, int retry) { - this.urls = Collections.singletonList(url); - this.file = file; - this.integrityCheck = integrityCheck; - this.retry = retry; - - setName(file.getName()); - setExecutor(Schedulers.io()); + this(Collections.singletonList(url), file, integrityCheck, retry); } /** @@ -135,13 +129,24 @@ public class FileDownloadTask extends Task { * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed */ public FileDownloadTask(List urls, File file, IntegrityCheck integrityCheck) { + this(urls, file, integrityCheck, 3); + } + + /** + * Constructor. + * @param urls urls of remote file, will be attempted in order. + * @param file the location that download to. + * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed + * @param retry the times for retrying if downloading fails. + */ + public FileDownloadTask(List urls, File file, IntegrityCheck integrityCheck, int retry) { if (urls == null || urls.isEmpty()) throw new IllegalArgumentException("At least one URL is required"); this.urls = new ArrayList<>(urls); this.file = file; this.integrityCheck = integrityCheck; - this.retry = urls.size(); + this.retry = retry; setName(file.getName()); setExecutor(Schedulers.io()); @@ -208,8 +213,8 @@ public class FileDownloadTask extends Task { Logging.LOG.log(Level.FINER, "Downloading " + urls.get(0) + " to " + file); Exception exception = null; - for (int repeat = 0; repeat < retry; repeat++) { - URL url = urls.get(repeat % urls.size()); + for (int repeat = 0; repeat < retry * urls.size(); repeat++) { + URL url = urls.get(repeat / urls.size()); if (isCancelled()) { break; }