重构下载源选择逻辑 (#4856)

This commit is contained in:
Glavo
2025-11-30 15:27:33 +08:00
committed by GitHub
parent 310a344f96
commit 8351ee4094
19 changed files with 214 additions and 345 deletions

View File

@@ -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;

View File

@@ -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<String, DownloadProvider> providersById;
public static final Map<String, DownloadProvider> 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<String, DownloadProvider> DIRECT_PROVIDERS;
public static final Map<String, DownloadProvider> 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<DownloadProvider> 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) {

View File

@@ -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());
}

View File

@@ -1,92 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
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<DownloadProvider> downloadProviderCandidates;
public void setDownloadProviderCandidates(List<DownloadProvider> downloadProviderCandidates) {
this.downloadProviderCandidates = List.copyOf(downloadProviderCandidates);
}
public DownloadProvider getPreferredDownloadProvider() {
List<DownloadProvider> 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<URI> getAssetObjectCandidates(String assetObjectLocation) {
return downloadProviderCandidates.stream()
.flatMap(d -> d.getAssetObjectCandidates(assetObjectLocation).stream())
.collect(Collectors.toList());
}
@Override
public List<URI> injectURLWithCandidates(String baseURL) {
return downloadProviderCandidates.stream()
.flatMap(d -> d.injectURLWithCandidates(baseURL).stream())
.collect(Collectors.toList());
}
@Override
public List<URI> injectURLsWithCandidates(List<String> 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();
}
}

View File

@@ -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<DownloadProvider> versionListProviders;
private final List<DownloadProvider> fileProviders;
private final ConcurrentMap<String, VersionList<?>> versionLists = new ConcurrentHashMap<>();
public AutoDownloadProvider(DownloadProvider versionListProvider, DownloadProvider fileProvider) {
this.versionListProvider = versionListProvider;
this.fileProvider = fileProvider;
public AutoDownloadProvider(
List<DownloadProvider> versionListProviders,
List<DownloadProvider> 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<URI> getAll(
List<DownloadProvider> providers,
Function<DownloadProvider, List<URI>> function) {
LinkedHashSet<URI> 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<URI> getVersionListURLs() {
return getAll(versionListProviders, DownloadProvider::getVersionListURLs);
}
@Override
public String injectURL(String baseURL) {
return fileProvider.injectURL(baseURL);
return getPreferredDownloadProvider().injectURL(baseURL);
}
@Override
public List<URI> getAssetObjectCandidates(String assetObjectLocation) {
return fileProvider.getAssetObjectCandidates(assetObjectLocation);
return getAll(fileProviders, provider -> provider.getAssetObjectCandidates(assetObjectLocation));
}
@Override
public List<URI> injectURLWithCandidates(String baseURL) {
return fileProvider.injectURLWithCandidates(baseURL);
return getAll(fileProviders, provider -> provider.injectURLWithCandidates(baseURL));
}
@Override
public List<URI> injectURLsWithCandidates(List<String> 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);
}
}

View File

@@ -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<URI> getVersionListURLs() {
return List.of(URI.create(apiRoot + "/mc/game/version_manifest.json"));
}
@Override
public String getAssetBaseURL() {
return apiRoot + "/assets/";
public List<URI> getAssetObjectCandidates(String assetObjectLocation) {
return List.of(NetworkUtils.toURI(apiRoot + "/assets/" + assetObjectLocation));
}
@Override

View File

@@ -1,67 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
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<String, VersionList<?>> 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();
}
}

View File

@@ -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<URI> getVersionListURLs();
String getAssetBaseURL();
List<URI> getAssetObjectCandidates(String assetObjectLocation);
default List<URI> 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<URI> injectURLWithCandidates(String baseURL) {
return List.of(NetworkUtils.toURI(injectURL(baseURL)));
}
default List<URI> injectURLsWithCandidates(List<String> urls) {
return urls.stream().flatMap(url -> injectURLWithCandidates(url).stream()).collect(Collectors.toList());
LinkedHashSet<URI> 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();
}

View File

@@ -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<URI> 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);
}
}

View File

@@ -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<URI> 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<URI> getAssetObjectCandidates(String assetObjectLocation) {
return List.of(NetworkUtils.toURI("https://resources.download.minecraft.net/" + assetObjectLocation));
}
@Override

View File

@@ -40,11 +40,6 @@ public class MultipleSourceVersionList extends VersionList<RemoteVersion> {
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.");

View File

@@ -71,17 +71,6 @@ public abstract class VersionList<T extends RemoteVersion> {
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();

View File

@@ -56,11 +56,6 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
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.");

View File

@@ -53,7 +53,7 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
@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<GameRemoteVersion> {
}
});
}
@Override
public String toString() {
return "GameVersionList[downloadProvider=%s]".formatted(downloadProvider);
}
}

View File

@@ -60,17 +60,19 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
@Override
public Task<?> 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()

View File

@@ -52,7 +52,7 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
@Override
public Task<?> 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);

View File

@@ -45,11 +45,6 @@ public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVer
return true;
}
@Override
public Task<?> 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.");

View File

@@ -24,7 +24,6 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
}
private static final String OLD_URL = "https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge";
private static final String META_URL = "https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge";
@Override
@@ -38,8 +37,8 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
@Override
public Task<?> 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();

View File

@@ -80,7 +80,7 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
Set<String> 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;