diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 33bb333aa..9d7515415 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -493,7 +493,7 @@ public final class Config extends ObservableSetting { } @SerializedName("downloadType") - private final StringProperty downloadType = new SimpleStringProperty(DownloadProviders.DEFAULT_RAW_PROVIDER_ID); + private final StringProperty downloadType = new SimpleStringProperty(DownloadProviders.DEFAULT_DIRECT_PROVIDER_ID); public StringProperty downloadTypeProperty() { return downloadType; @@ -523,7 +523,7 @@ public final class Config extends ObservableSetting { } @SerializedName("versionListSource") - private final StringProperty versionListSource = new SimpleStringProperty("balanced"); + private final StringProperty versionListSource = new SimpleStringProperty(DownloadProviders.DEFAULT_AUTO_PROVIDER_ID); public StringProperty versionListSourceProperty() { return versionListSource; 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 66b9973a7..60ab3a892 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java @@ -21,9 +21,10 @@ import javafx.beans.InvalidationListener; import org.jackhuang.hmcl.download.*; import org.jackhuang.hmcl.task.DownloadException; import org.jackhuang.hmcl.task.FetchTask; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; +import org.jackhuang.hmcl.util.i18n.LocaleUtils; import org.jackhuang.hmcl.util.io.ResponseCodeException; import javax.net.ssl.SSLHandshakeException; @@ -31,104 +32,88 @@ import java.io.FileNotFoundException; import java.net.SocketTimeoutException; import java.net.URI; import java.nio.file.AccessDeniedException; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.CancellationException; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.task.FetchTask.DEFAULT_CONCURRENCY; +import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class DownloadProviders { private DownloadProviders() { } - private static final DownloadProviderWrapper provider; + public static final String DEFAULT_AUTO_PROVIDER_ID = "balanced"; + public static final String DEFAULT_DIRECT_PROVIDER_ID = "mojang"; - public static final Map providersById; - public static final Map rawProviders; - private static final AdaptedDownloadProvider fileDownloadProvider = new AdaptedDownloadProvider(); + private static final DownloadProviderWrapper PROVIDER_WRAPPER; - private static final MojangDownloadProvider MOJANG; - private static final BMCLAPIDownloadProvider BMCLAPI; - - public static final String DEFAULT_PROVIDER_ID = "balanced"; - public static final String DEFAULT_RAW_PROVIDER_ID = "bmclapi"; - - @SuppressWarnings("unused") - private static final InvalidationListener observer; + private static final DownloadProvider DEFAULT_PROVIDER; + public static final Map DIRECT_PROVIDERS; + public static final Map AUTO_PROVIDERS; static { - String bmclapiRoot = "https://bmclapi2.bangbang93.com"; - String bmclapiRootOverride = System.getProperty("hmcl.bmclapi.override"); - if (bmclapiRootOverride != null) bmclapiRoot = bmclapiRootOverride; + String bmclapiRoot = System.getProperty("hmcl.bmclapi.override", "https://bmclapi2.bangbang93.com"); + BMCLAPIDownloadProvider bmclapiRaw = new BMCLAPIDownloadProvider(bmclapiRoot); - MOJANG = new MojangDownloadProvider(); - BMCLAPI = new BMCLAPIDownloadProvider(bmclapiRoot); - rawProviders = Map.of( - "mojang", MOJANG, - "bmclapi", BMCLAPI + DownloadProvider mojang = new MojangDownloadProvider(); + DownloadProvider bmclapi = new AutoDownloadProvider(bmclapiRaw, mojang); + + DEFAULT_PROVIDER = mojang; + DIRECT_PROVIDERS = Lang.mapOf( + pair("mojang", mojang), + pair("bmclapi", bmclapi) ); - AdaptedDownloadProvider fileProvider = new AdaptedDownloadProvider(); - fileProvider.setDownloadProviderCandidates(List.of(BMCLAPI, MOJANG)); - BalancedDownloadProvider balanced = new BalancedDownloadProvider(MOJANG, BMCLAPI); + AUTO_PROVIDERS = Lang.mapOf( + pair("balanced", LocaleUtils.IS_CHINA_MAINLAND ? bmclapi : mojang), + pair("official", LocaleUtils.IS_CHINA_MAINLAND ? new AutoDownloadProvider( + List.of(mojang, bmclapiRaw), + List.of(bmclapiRaw, mojang) + ) : mojang), + pair("mirror", bmclapi) + ); - providersById = Map.of( - "official", new AutoDownloadProvider(MOJANG, fileProvider), - "balanced", new AutoDownloadProvider(balanced, fileProvider), - "mirror", new AutoDownloadProvider(BMCLAPI, fileProvider)); - - observer = FXUtils.observeWeak(() -> { - FetchTask.setDownloadExecutorConcurrency( - config().getAutoDownloadThreads() ? DEFAULT_CONCURRENCY : config().getDownloadThreads()); - }, config().autoDownloadThreadsProperty(), config().downloadThreadsProperty()); - - provider = new DownloadProviderWrapper(MOJANG); + PROVIDER_WRAPPER = new DownloadProviderWrapper(DEFAULT_PROVIDER); } static void init() { - InvalidationListener onChangeDownloadSource = observable -> { - String versionListSource = Objects.requireNonNullElse(config().getVersionListSource(), ""); - if (config().isAutoChooseDownloadType()) { - DownloadProvider currentDownloadProvider = providersById.get(versionListSource); - if (currentDownloadProvider == null) - currentDownloadProvider = Objects.requireNonNull(providersById.get(DEFAULT_PROVIDER_ID), - "default provider is null"); + InvalidationListener onChangeDownloadThreads = observable -> { + FetchTask.setDownloadExecutorConcurrency(config().getAutoDownloadThreads() + ? DEFAULT_CONCURRENCY + : config().getDownloadThreads()); + }; + config().autoDownloadThreadsProperty().addListener(onChangeDownloadThreads); + config().downloadThreadsProperty().addListener(onChangeDownloadThreads); + onChangeDownloadThreads.invalidated(null); - provider.setProvider(currentDownloadProvider); + InvalidationListener onChangeDownloadSource = observable -> { + if (config().isAutoChooseDownloadType()) { + String versionListSource = config().getVersionListSource(); + DownloadProvider downloadProvider = versionListSource != null + ? AUTO_PROVIDERS.getOrDefault(versionListSource, DEFAULT_PROVIDER) + : DEFAULT_PROVIDER; + PROVIDER_WRAPPER.setProvider(downloadProvider); } else { - provider.setProvider(fileDownloadProvider); + String downloadType = config().getDownloadType(); + PROVIDER_WRAPPER.setProvider(downloadType != null + ? DIRECT_PROVIDERS.getOrDefault(downloadType, DEFAULT_PROVIDER) + : DEFAULT_PROVIDER); } }; config().versionListSourceProperty().addListener(onChangeDownloadSource); config().autoChooseDownloadTypeProperty().addListener(onChangeDownloadSource); - + config().downloadTypeProperty().addListener(onChangeDownloadSource); onChangeDownloadSource.invalidated(null); - - FXUtils.onChangeAndOperate(config().downloadTypeProperty(), downloadType -> { - DownloadProvider primary = Objects.requireNonNullElseGet( - rawProviders.get(Objects.requireNonNullElse(downloadType, "")), - () -> rawProviders.get(DEFAULT_RAW_PROVIDER_ID)); - - List providers = new ArrayList<>(rawProviders.size()); - providers.add(primary); - for (DownloadProvider provider : rawProviders.values()) { - if (provider != primary) - providers.add(provider); - } - - fileDownloadProvider.setDownloadProviderCandidates(providers); - }); } /** * Get current primary preferred download provider */ public static DownloadProvider getDownloadProvider() { - return provider; + return PROVIDER_WRAPPER; } public static String localizeErrorMessage(Throwable exception) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index 859c7c255..0e7d535d8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -78,7 +78,7 @@ public class DownloadSettingsPage extends StackPane { versionListSourcePane.setRight(cboVersionListSource); FXUtils.setLimitWidth(cboVersionListSource, 400); - cboVersionListSource.getItems().setAll(DownloadProviders.providersById.keySet()); + cboVersionListSource.getItems().setAll(DownloadProviders.AUTO_PROVIDERS.keySet()); selectedItemPropertyFor(cboVersionListSource).bindBidirectional(config().versionListSourceProperty()); } @@ -95,7 +95,7 @@ public class DownloadSettingsPage extends StackPane { downloadSourcePane.setRight(cboDownloadSource); FXUtils.setLimitWidth(cboDownloadSource, 420); - cboDownloadSource.getItems().setAll(DownloadProviders.rawProviders.keySet()); + cboDownloadSource.getItems().setAll(DownloadProviders.DIRECT_PROVIDERS.keySet()); selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty()); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AdaptedDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AdaptedDownloadProvider.java deleted file mode 100644 index d899df35b..000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AdaptedDownloadProvider.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.download; - -import org.jetbrains.annotations.Unmodifiable; - -import java.net.URI; -import java.util.List; -import java.util.stream.Collectors; - -/** - * The download provider that changes the real download source in need. - * - * @author huangyuhui - */ -public class AdaptedDownloadProvider implements DownloadProvider { - - private @Unmodifiable List downloadProviderCandidates; - - public void setDownloadProviderCandidates(List downloadProviderCandidates) { - this.downloadProviderCandidates = List.copyOf(downloadProviderCandidates); - } - - public DownloadProvider getPreferredDownloadProvider() { - List d = downloadProviderCandidates; - if (d == null || d.isEmpty()) { - throw new IllegalStateException("No download provider candidate"); - } - return d.get(0); - } - - @Override - public String getVersionListURL() { - return getPreferredDownloadProvider().getVersionListURL(); - } - - @Override - public String getAssetBaseURL() { - return getPreferredDownloadProvider().getAssetBaseURL(); - } - - @Override - public String injectURL(String baseURL) { - return getPreferredDownloadProvider().injectURL(baseURL); - } - - @Override - public List getAssetObjectCandidates(String assetObjectLocation) { - return downloadProviderCandidates.stream() - .flatMap(d -> d.getAssetObjectCandidates(assetObjectLocation).stream()) - .collect(Collectors.toList()); - } - - @Override - public List injectURLWithCandidates(String baseURL) { - return downloadProviderCandidates.stream() - .flatMap(d -> d.injectURLWithCandidates(baseURL).stream()) - .collect(Collectors.toList()); - } - - @Override - public List injectURLsWithCandidates(List urls) { - return downloadProviderCandidates.stream() - .flatMap(d -> d.injectURLsWithCandidates(urls).stream()) - .collect(Collectors.toList()); - } - - @Override - public VersionList getVersionListById(String id) { - return getPreferredDownloadProvider().getVersionListById(id); - } - - @Override - public int getConcurrency() { - return getPreferredDownloadProvider().getConcurrency(); - } -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java index d886dec4f..78513ad59 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java @@ -18,60 +18,99 @@ package org.jackhuang.hmcl.download; import java.net.URI; +import java.util.LinkedHashSet; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; -/** - * Official Download Provider fetches version list from Mojang and - * download files from mcbbs. - * - * @author huangyuhui - */ -public class AutoDownloadProvider implements DownloadProvider { - private final DownloadProvider versionListProvider; - private final DownloadProvider fileProvider; +/// @author huangyuhui +public final class AutoDownloadProvider implements DownloadProvider { + private final List versionListProviders; + private final List fileProviders; + private final ConcurrentMap> versionLists = new ConcurrentHashMap<>(); - public AutoDownloadProvider(DownloadProvider versionListProvider, DownloadProvider fileProvider) { - this.versionListProvider = versionListProvider; - this.fileProvider = fileProvider; + public AutoDownloadProvider( + List versionListProviders, + List fileProviders) { + if (versionListProviders == null || versionListProviders.isEmpty()) { + throw new IllegalArgumentException("versionListProviders must not be null or empty"); + } + + if (fileProviders == null || fileProviders.isEmpty()) { + throw new IllegalArgumentException("fileProviders must not be null or empty"); + } + + this.versionListProviders = versionListProviders; + this.fileProviders = fileProviders; + } + + public AutoDownloadProvider(DownloadProvider... downloadProviderCandidate) { + if (downloadProviderCandidate.length == 0) { + throw new IllegalArgumentException("Download provider must have at least one download provider"); + } + + this.versionListProviders = List.of(downloadProviderCandidate); + this.fileProviders = versionListProviders; + } + + private DownloadProvider getPreferredDownloadProvider() { + return fileProviders.get(0); + } + + private static List getAll( + List providers, + Function> function) { + LinkedHashSet result = new LinkedHashSet<>(); + for (DownloadProvider provider : providers) { + result.addAll(function.apply(provider)); + } + return List.copyOf(result); } @Override - public String getVersionListURL() { - return versionListProvider.getVersionListURL(); - } - - @Override - public String getAssetBaseURL() { - return fileProvider.getAssetBaseURL(); + public List getVersionListURLs() { + return getAll(versionListProviders, DownloadProvider::getVersionListURLs); } @Override public String injectURL(String baseURL) { - return fileProvider.injectURL(baseURL); + return getPreferredDownloadProvider().injectURL(baseURL); } @Override public List getAssetObjectCandidates(String assetObjectLocation) { - return fileProvider.getAssetObjectCandidates(assetObjectLocation); + return getAll(fileProviders, provider -> provider.getAssetObjectCandidates(assetObjectLocation)); } @Override public List injectURLWithCandidates(String baseURL) { - return fileProvider.injectURLWithCandidates(baseURL); + return getAll(fileProviders, provider -> provider.injectURLWithCandidates(baseURL)); } @Override public List injectURLsWithCandidates(List urls) { - return fileProvider.injectURLsWithCandidates(urls); + return getAll(fileProviders, provider -> provider.injectURLsWithCandidates(urls)); } @Override public VersionList getVersionListById(String id) { - return versionListProvider.getVersionListById(id); + return versionLists.computeIfAbsent(id, value -> { + VersionList[] lists = new VersionList[versionListProviders.size()]; + for (int i = 0; i < versionListProviders.size(); i++) { + lists[i] = versionListProviders.get(i).getVersionListById(value); + } + return new MultipleSourceVersionList(lists); + }); } @Override public int getConcurrency() { - return fileProvider.getConcurrency(); + return getPreferredDownloadProvider().getConcurrency(); + } + + @Override + public String toString() { + return "AutoDownloadProvider[versionListProviders=%s, fileProviders=%s]".formatted(versionListProviders, fileProviders); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java index ef16b02b0..22b85cbf9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -28,7 +28,9 @@ import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; import org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList; import org.jackhuang.hmcl.download.quilt.QuiltVersionList; import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.io.NetworkUtils; +import java.net.URI; import java.util.Arrays; import java.util.List; @@ -99,13 +101,13 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider { } @Override - public String getVersionListURL() { - return apiRoot + "/mc/game/version_manifest.json"; + public List getVersionListURLs() { + return List.of(URI.create(apiRoot + "/mc/game/version_manifest.json")); } @Override - public String getAssetBaseURL() { - return apiRoot + "/assets/"; + public List getAssetObjectCandidates(String assetObjectLocation) { + return List.of(NetworkUtils.toURI(apiRoot + "/assets/" + assetObjectLocation)); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BalancedDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BalancedDownloadProvider.java deleted file mode 100644 index 32afd93b3..000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BalancedDownloadProvider.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2021 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.download; - -import java.util.HashMap; -import java.util.Map; - -/** - * Official Download Provider fetches version list from Mojang and - * download files from mcbbs. - * - * @author huangyuhui - */ -public final class BalancedDownloadProvider implements DownloadProvider { - private final DownloadProvider[] candidates; - private final Map> versionLists = new HashMap<>(); - - public BalancedDownloadProvider(DownloadProvider... candidates) { - this.candidates = candidates; - } - - @Override - public String getVersionListURL() { - throw new UnsupportedOperationException(); - } - - @Override - public String getAssetBaseURL() { - throw new UnsupportedOperationException(); - } - - @Override - public String injectURL(String baseURL) { - throw new UnsupportedOperationException(); - } - - @Override - public VersionList getVersionListById(String id) { - return versionLists.computeIfAbsent(id, value -> { - VersionList[] lists = new VersionList[candidates.length]; - for (int i = 0; i < candidates.length; i++) { - lists[i] = candidates[i].getVersionListById(value); - } - return new MultipleSourceVersionList(lists); - }); - } - - @Override - public int getConcurrency() { - throw new UnsupportedOperationException(); - } -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java index 4be18b881..0c6117e49 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java @@ -20,64 +20,55 @@ package org.jackhuang.hmcl.download; import org.jackhuang.hmcl.util.io.NetworkUtils; import java.net.URI; +import java.util.LinkedHashSet; import java.util.List; -import java.util.stream.Collectors; -/** - * The service provider that provides Minecraft online file downloads. - * - * @author huangyuhui - */ +/// The service provider that provides Minecraft online file downloads. +/// +/// @author huangyuhui public interface DownloadProvider { - String getVersionListURL(); + List getVersionListURLs(); - String getAssetBaseURL(); + List getAssetObjectCandidates(String assetObjectLocation); - default List getAssetObjectCandidates(String assetObjectLocation) { - return List.of(NetworkUtils.toURI(getAssetBaseURL() + assetObjectLocation)); - } - - /** - * Inject into original URL provided by Mojang and Forge. - * - * Since there are many provided URLs that are written in JSONs and are unmodifiable, - * this method provides a way to change them. - * - * @param baseURL original URL provided by Mojang and Forge. - * @return the URL that is equivalent to [baseURL], but belongs to your own service provider. - */ + /// Inject into original URL provided by Mojang and Forge. + /// + /// Since there are many provided URLs that are written in JSONs and are unmodifiable, + /// this method provides a way to change them. + /// + /// @param baseURL original URL provided by Mojang and Forge. + /// @return the URL that is equivalent to `baseURL``, but belongs to your own service provider. String injectURL(String baseURL); - /** - * Inject into original URL provided by Mojang and Forge. - * - * Since there are many provided URLs that are written in JSONs and are unmodifiable, - * this method provides a way to change them. - * - * @param baseURL original URL provided by Mojang and Forge. - * @return the URL that is equivalent to [baseURL], but belongs to your own service provider. - */ + /// Inject into original URL provided by Mojang and Forge. + /// + /// Since there are many provided URLs that are written in JSONs and are unmodifiable, + /// this method provides a way to change them. + /// + /// @param baseURL original URL provided by Mojang and Forge. + /// @return the URL that is equivalent to `baseURL`, but belongs to your own service provider. default List injectURLWithCandidates(String baseURL) { return List.of(NetworkUtils.toURI(injectURL(baseURL))); } default List injectURLsWithCandidates(List urls) { - return urls.stream().flatMap(url -> injectURLWithCandidates(url).stream()).collect(Collectors.toList()); + LinkedHashSet result = new LinkedHashSet<>(); + for (String url : urls) { + result.addAll(injectURLWithCandidates(url)); + } + return List.copyOf(result); } - /** - * the specific version list that this download provider provides. i.e. "fabric", "forge", "liteloader", "game", "optifine" - * - * @param id the id of specific version list that this download provider provides. i.e. "fabric", "forge", "liteloader", "game", "optifine" - * @return the version list - * @throws IllegalArgumentException if the version list does not exist - */ + /// the specific version list that this download provider provides. i.e. "fabric", "forge", "liteloader", "game", "optifine" + /// + /// @param id the id of specific version list that this download provider provides. i.e. "fabric", "forge", "liteloader", "game", "optifine" + /// @return the version list + /// @throws IllegalArgumentException if the version list does not exist VersionList getVersionListById(String id); - /** - * The maximum download concurrency that this download provider supports. - * @return the maximum download concurrency. - */ + /// The maximum download concurrency that this download provider supports. + /// + /// @return the maximum download concurrency. int getConcurrency(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProviderWrapper.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProviderWrapper.java index 286f17790..770edbc31 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProviderWrapper.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProviderWrapper.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.download; +import org.jackhuang.hmcl.task.Task; + import java.net.URI; import java.util.List; import java.util.Objects; @@ -26,7 +28,7 @@ import java.util.Objects; */ public final class DownloadProviderWrapper implements DownloadProvider { - private DownloadProvider provider; + private volatile DownloadProvider provider; public DownloadProviderWrapper(DownloadProvider provider) { this.provider = provider; @@ -46,13 +48,8 @@ public final class DownloadProviderWrapper implements DownloadProvider { } @Override - public String getVersionListURL() { - return getProvider().getVersionListURL(); - } - - @Override - public String getAssetBaseURL() { - return getProvider().getAssetBaseURL(); + public List getVersionListURLs() { + return getProvider().getVersionListURLs(); } @Override @@ -72,11 +69,41 @@ public final class DownloadProviderWrapper implements DownloadProvider { @Override public VersionList getVersionListById(String id) { - return getProvider().getVersionListById(id); + + return new VersionList<>() { + @Override + public boolean hasType() { + return getProvider().getVersionListById(id).hasType(); + } + + @Override + public Task refreshAsync() { + throw new UnsupportedOperationException(); + } + + @Override + public Task refreshAsync(String gameVersion) { + return getProvider().getVersionListById(id).refreshAsync(gameVersion) + .thenComposeAsync(() -> { + lock.writeLock().lock(); + try { + versions.putAll(gameVersion, getProvider().getVersionListById(id).getVersions(gameVersion)); + } finally { + lock.writeLock().unlock(); + } + return null; + }); + } + }; } @Override public int getConcurrency() { return getProvider().getConcurrency(); } + + @Override + public String toString() { + return "DownloadProviderWrapper[provider=%s]".formatted(provider); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java index 2c5a5dba4..df8424c1e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -27,6 +27,10 @@ import org.jackhuang.hmcl.download.neoforge.NeoForgeOfficialVersionList; import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; import org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList; import org.jackhuang.hmcl.download.quilt.QuiltVersionList; +import org.jackhuang.hmcl.util.io.NetworkUtils; + +import java.net.URI; +import java.util.List; /** * @author huangyuhui @@ -61,13 +65,13 @@ public class MojangDownloadProvider implements DownloadProvider { } @Override - public String getVersionListURL() { - return "https://piston-meta.mojang.com/mc/game/version_manifest.json"; + public List getVersionListURLs() { + return List.of(URI.create("https://piston-meta.mojang.com/mc/game/version_manifest.json")); } @Override - public String getAssetBaseURL() { - return "https://resources.download.minecraft.net/"; + public List getAssetObjectCandidates(String assetObjectLocation) { + return List.of(NetworkUtils.toURI("https://resources.download.minecraft.net/" + assetObjectLocation)); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MultipleSourceVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MultipleSourceVersionList.java index b6ca45a46..4a77cc513 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MultipleSourceVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MultipleSourceVersionList.java @@ -40,11 +40,6 @@ public class MultipleSourceVersionList extends VersionList { return hasType; } - @Override - public Task loadAsync() { - throw new UnsupportedOperationException("MultipleSourceVersionList does not support loading the entire remote version list."); - } - @Override public Task refreshAsync() { throw new UnsupportedOperationException("MultipleSourceVersionList does not support loading the entire remote version list."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java index 5519b8663..0ac125384 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java @@ -71,17 +71,6 @@ public abstract class VersionList { return refreshAsync(); } - public Task loadAsync() { - return Task.composeAsync(() -> { - lock.readLock().lock(); - try { - return isLoaded() ? null : refreshAsync(); - } finally { - lock.readLock().unlock(); - } - }); - } - public Task loadAsync(String gameVersion) { return Task.composeAsync(() -> { lock.readLock().lock(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index 1c1782dfe..14dbcaae3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -56,11 +56,6 @@ public final class ForgeBMCLVersionList extends VersionList return false; } - @Override - public Task loadAsync() { - throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list."); - } - @Override public Task refreshAsync() { throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java index 97690772c..cf8428b80 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java @@ -53,7 +53,7 @@ public final class GameVersionList extends VersionList { @Override public Task refreshAsync() { - return new GetTask(downloadProvider.getVersionListURL()).thenGetJsonAsync(GameRemoteVersions.class) + return new GetTask(downloadProvider.getVersionListURLs()).thenGetJsonAsync(GameRemoteVersions.class) .thenAcceptAsync(root -> { GameRemoteVersions unlistedVersions = null; @@ -91,4 +91,9 @@ public final class GameVersionList extends VersionList { } }); } + + @Override + public String toString() { + return "GameVersionList[downloadProvider=%s]".formatted(downloadProvider); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBMCLVersionList.java index d8d5d04b7..4b146d51a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBMCLVersionList.java @@ -60,17 +60,19 @@ public final class LiteLoaderBMCLVersionList extends VersionList refreshAsync(String gameVersion) { - return new GetTask(NetworkUtils.withQuery(downloadProvider.injectURLWithCandidates("https://bmclapi2.bangbang93.com/liteloader/list"), Map.of("mcversion", gameVersion))) + return new GetTask( + NetworkUtils.withQuery(downloadProvider.getApiRoot() + "/liteloader/list", Map.of( + "mcversion", gameVersion + ))) .thenGetJsonAsync(LiteLoaderBMCLVersion.class) .thenAcceptAsync(v -> { lock.writeLock().lock(); try { versions.clear(); - versions.put(gameVersion, new LiteLoaderRemoteVersion( gameVersion, v.version, RemoteVersion.Type.UNCATEGORIZED, Collections.singletonList(NetworkUtils.withQuery( - downloadProvider.injectURL("https://bmclapi2.bangbang93.com/liteloader/download"), + downloadProvider.getApiRoot() + "/liteloader/download", Collections.singletonMap("version", v.version) )), v.build.getTweakClass(), v.build.getLibraries() diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java index 42b9dd117..dffb8cd2e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java @@ -52,7 +52,7 @@ public final class LiteLoaderVersionList extends VersionList refreshAsync(String gameVersion) { - return new GetTask(downloadProvider.injectURL(LITELOADER_LIST)) + return new GetTask(downloadProvider.injectURLWithCandidates(LITELOADER_LIST)) .thenGetJsonAsync(LiteLoaderVersionsRoot.class) .thenAcceptAsync(root -> { LiteLoaderGameVersions versions = root.getVersions().get(gameVersion); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java index ab0e7ace4..d0466fd52 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java @@ -45,11 +45,6 @@ public final class NeoForgeBMCLVersionList extends VersionList loadAsync() { - throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list."); - } - @Override public Task refreshAsync() { throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java index 2ebfc03d1..4e98cce3d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java @@ -24,7 +24,6 @@ public final class NeoForgeOfficialVersionList extends VersionList refreshAsync() { return Task.allOf( - new GetTask(downloadProvider.injectURL(OLD_URL)).thenGetJsonAsync(OfficialAPIResult.class), - new GetTask(downloadProvider.injectURL(META_URL)).thenGetJsonAsync(OfficialAPIResult.class) + new GetTask(downloadProvider.injectURLWithCandidates(OLD_URL)).thenGetJsonAsync(OfficialAPIResult.class), + new GetTask(downloadProvider.injectURLWithCandidates(META_URL)).thenGetJsonAsync(OfficialAPIResult.class) ).thenAcceptAsync(results -> { lock.writeLock().lock(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java index 7d8dd0f1d..608a1ca04 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java @@ -80,7 +80,7 @@ public final class OptiFineBMCLVersionList extends VersionList duplicates = new HashSet<>(); for (OptiFineVersion element : root) { String version = element.getType() + "_" + element.getPatch(); - String mirror = "https://bmclapi2.bangbang93.com/optifine/" + toLookupVersion(element.getGameVersion()) + "/" + element.getType() + "/" + element.getPatch(); + String mirror = apiRoot + "/optifine/" + toLookupVersion(element.getGameVersion()) + "/" + element.getType() + "/" + element.getPatch(); if (!duplicates.add(mirror)) continue;