Feature: 下载并发控制 (#5026)
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.setting;
|
package org.jackhuang.hmcl.setting;
|
||||||
|
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
|
import org.jackhuang.hmcl.task.FetchTask;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -114,6 +115,8 @@ public final class ProxyManager {
|
|||||||
config().hasProxyAuthProperty().addListener(updateAuthenticator);
|
config().hasProxyAuthProperty().addListener(updateAuthenticator);
|
||||||
config().proxyUserProperty().addListener(updateAuthenticator);
|
config().proxyUserProperty().addListener(updateAuthenticator);
|
||||||
config().proxyPassProperty().addListener(updateAuthenticator);
|
config().proxyPassProperty().addListener(updateAuthenticator);
|
||||||
|
|
||||||
|
FetchTask.notifyInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static abstract class AbstractProxySelector extends ProxySelector {
|
private static abstract class AbstractProxySelector extends ProxySelector {
|
||||||
|
|||||||
@@ -1217,12 +1217,14 @@ public final class FXUtils {
|
|||||||
|
|
||||||
public static Task<Image> getRemoteImageTask(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
public static Task<Image> getRemoteImageTask(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||||
return new CacheFileTask(url)
|
return new CacheFileTask(url)
|
||||||
|
.setSignificance(Task.TaskSignificance.MINOR)
|
||||||
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
|
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
|
||||||
.setSignificance(Task.TaskSignificance.MINOR);
|
.setSignificance(Task.TaskSignificance.MINOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<Image> getRemoteImageTask(URI uri, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
public static Task<Image> getRemoteImageTask(URI uri, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||||
return new CacheFileTask(uri)
|
return new CacheFileTask(uri)
|
||||||
|
.setSignificance(Task.TaskSignificance.MINOR)
|
||||||
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
|
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
|
||||||
.setSignificance(Task.TaskSignificance.MINOR);
|
.setSignificance(Task.TaskSignificance.MINOR);
|
||||||
}
|
}
|
||||||
@@ -1237,6 +1239,7 @@ public final class FXUtils {
|
|||||||
LOG.warning("An exception encountered while loading remote image: " + url, exception);
|
LOG.warning("An exception encountered while loading remote image: " + url, exception);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.setSignificance(Task.TaskSignificance.MINOR)
|
||||||
.start();
|
.start();
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,15 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||||
@@ -46,6 +54,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
|
|
||||||
private static final String PREFIX = "https://api.curseforge.com";
|
private static final String PREFIX = "https://api.curseforge.com";
|
||||||
private static final String apiKey = System.getProperty("hmcl.curseforge.apikey", JarUtils.getAttribute("hmcl.curseforge.apikey", ""));
|
private static final String apiKey = System.getProperty("hmcl.curseforge.apikey", JarUtils.getAttribute("hmcl.curseforge.apikey", ""));
|
||||||
|
private static final Semaphore SEMAPHORE = new Semaphore(16);
|
||||||
|
|
||||||
private static final int WORD_PERFECT_MATCH_WEIGHT = 5;
|
private static final int WORD_PERFECT_MATCH_WEIGHT = 5;
|
||||||
|
|
||||||
@@ -110,6 +119,8 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
|
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
int categoryId = 0;
|
int categoryId = 0;
|
||||||
if (category != null && category.getSelf() instanceof CurseAddon.Category) {
|
if (category != null && category.getSelf() instanceof CurseAddon.Category) {
|
||||||
categoryId = ((CurseAddon.Category) category.getSelf()).getId();
|
categoryId = ((CurseAddon.Category) category.getSelf()).getId();
|
||||||
@@ -150,6 +161,9 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
|
|
||||||
return pair(remoteMod, diff);
|
return pair(remoteMod, diff);
|
||||||
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
|
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -173,6 +187,8 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
Response<FingerprintMatchesResult> response = withApiKey(HttpRequest.POST(PREFIX + "/v1/fingerprints/432"))
|
Response<FingerprintMatchesResult> response = withApiKey(HttpRequest.POST(PREFIX + "/v1/fingerprints/432"))
|
||||||
.json(mapOf(pair("fingerprints", Collections.singletonList(hash))))
|
.json(mapOf(pair("fingerprints", Collections.singletonList(hash))))
|
||||||
.getJson(Response.typeOf(FingerprintMatchesResult.class));
|
.getJson(Response.typeOf(FingerprintMatchesResult.class));
|
||||||
@@ -182,39 +198,58 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of(response.getData().getExactMatches().get(0).getFile().toVersion());
|
return Optional.of(response.getData().getExactMatches().get(0).getFile().toVersion());
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemoteMod getModById(String id) throws IOException {
|
public RemoteMod getModById(String id) throws IOException {
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
Response<CurseAddon> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id))
|
Response<CurseAddon> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id))
|
||||||
.getJson(Response.typeOf(CurseAddon.class));
|
.getJson(Response.typeOf(CurseAddon.class));
|
||||||
return response.data.toMod();
|
return response.data.toMod();
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
|
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
Response<CurseAddon.LatestFile> response = withApiKey(HttpRequest.GET(String.format("%s/v1/mods/%s/files/%s", PREFIX, modId, fileId)))
|
Response<CurseAddon.LatestFile> response = withApiKey(HttpRequest.GET(String.format("%s/v1/mods/%s/files/%s", PREFIX, modId, fileId)))
|
||||||
.getJson(Response.typeOf(CurseAddon.LatestFile.class));
|
.getJson(Response.typeOf(CurseAddon.LatestFile.class));
|
||||||
return response.getData().toVersion().getFile();
|
return response.getData().toVersion().getFile();
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
Response<List<CurseAddon.LatestFile>> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files",
|
Response<List<CurseAddon.LatestFile>> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files",
|
||||||
pair("pageSize", "10000")))
|
pair("pageSize", "10000")))
|
||||||
.getJson(Response.typeOf(listTypeOf(CurseAddon.LatestFile.class)));
|
.getJson(Response.typeOf(listTypeOf(CurseAddon.LatestFile.class)));
|
||||||
return response.getData().stream().map(CurseAddon.LatestFile::toVersion);
|
return response.getData().stream().map(CurseAddon.LatestFile::toVersion);
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CurseAddon.Category> getCategoriesImpl() throws IOException {
|
|
||||||
Response<List<CurseAddon.Category>> categories = withApiKey(HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432")))
|
|
||||||
.getJson(Response.typeOf(listTypeOf(CurseAddon.Category.class)));
|
|
||||||
return reorganizeCategories(categories.getData(), section);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteModRepository.Category> getCategories() throws IOException {
|
public Stream<RemoteModRepository.Category> getCategories() throws IOException {
|
||||||
return getCategoriesImpl().stream().map(CurseAddon.Category::toCategory);
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
|
Response<List<CurseAddon.Category>> categories = withApiKey(HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432")))
|
||||||
|
.getJson(Response.typeOf(listTypeOf(CurseAddon.Category.class)));
|
||||||
|
return reorganizeCategories(categories.getData(), section).stream().map(CurseAddon.Category::toCategory);
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CurseAddon.Category> reorganizeCategories(List<CurseAddon.Category> categories, int rootId) {
|
private List<CurseAddon.Category> reorganizeCategories(List<CurseAddon.Category> categories, int rootId) {
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ import org.jackhuang.hmcl.mod.LocalModFile;
|
|||||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||||
import org.jackhuang.hmcl.util.*;
|
import org.jackhuang.hmcl.util.DigestUtils;
|
||||||
|
import org.jackhuang.hmcl.util.Immutable;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
import org.jackhuang.hmcl.util.Pair;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
@@ -35,7 +39,14 @@ import java.io.IOException;
|
|||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -49,6 +60,8 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
public static final ModrinthRemoteModRepository RESOURCE_PACKS = new ModrinthRemoteModRepository("resourcepack");
|
public static final ModrinthRemoteModRepository RESOURCE_PACKS = new ModrinthRemoteModRepository("resourcepack");
|
||||||
public static final ModrinthRemoteModRepository SHADER_PACKS = new ModrinthRemoteModRepository("shader");
|
public static final ModrinthRemoteModRepository SHADER_PACKS = new ModrinthRemoteModRepository("shader");
|
||||||
|
|
||||||
|
private static final Semaphore SEMAPHORE = new Semaphore(16);
|
||||||
|
|
||||||
private static final String PREFIX = "https://api.modrinth.com";
|
private static final String PREFIX = "https://api.modrinth.com";
|
||||||
|
|
||||||
private final String projectType;
|
private final String projectType;
|
||||||
@@ -81,6 +94,8 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
|
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
List<List<String>> facets = new ArrayList<>();
|
List<List<String>> facets = new ArrayList<>();
|
||||||
facets.add(Collections.singletonList("project_type:" + projectType));
|
facets.add(Collections.singletonList("project_type:" + projectType));
|
||||||
if (StringUtils.isNotBlank(gameVersion)) {
|
if (StringUtils.isNotBlank(gameVersion)) {
|
||||||
@@ -99,12 +114,16 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
Response<ProjectSearchResult> response = HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v2/search", query)))
|
Response<ProjectSearchResult> response = HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v2/search", query)))
|
||||||
.getJson(Response.typeOf(ProjectSearchResult.class));
|
.getJson(Response.typeOf(ProjectSearchResult.class));
|
||||||
return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize));
|
return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize));
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
|
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
|
||||||
String sha1 = DigestUtils.digestToString("SHA-1", file);
|
String sha1 = DigestUtils.digestToString("SHA-1", file);
|
||||||
|
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
try {
|
try {
|
||||||
ProjectVersion mod = HttpRequest.GET(PREFIX + "/v2/version_file/" + sha1,
|
ProjectVersion mod = HttpRequest.GET(PREFIX + "/v2/version_file/" + sha1,
|
||||||
pair("algorithm", "sha1"))
|
pair("algorithm", "sha1"))
|
||||||
@@ -118,14 +137,21 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
}
|
}
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemoteMod getModById(String id) throws IOException {
|
public RemoteMod getModById(String id) throws IOException {
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
id = StringUtils.removePrefix(id, "local-");
|
id = StringUtils.removePrefix(id, "local-");
|
||||||
Project project = HttpRequest.GET(PREFIX + "/v2/project/" + id).getJson(Project.class);
|
Project project = HttpRequest.GET(PREFIX + "/v2/project/" + id).getJson(Project.class);
|
||||||
return project.toMod();
|
return project.toMod();
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -135,20 +161,28 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||||
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
id = StringUtils.removePrefix(id, "local-");
|
id = StringUtils.removePrefix(id, "local-");
|
||||||
List<ProjectVersion> versions = HttpRequest.GET(PREFIX + "/v2/project/" + id + "/version")
|
List<ProjectVersion> versions = HttpRequest.GET(PREFIX + "/v2/project/" + id + "/version")
|
||||||
.getJson(listTypeOf(ProjectVersion.class));
|
.getJson(listTypeOf(ProjectVersion.class));
|
||||||
return versions.stream().map(ProjectVersion::toVersion).flatMap(Lang::toStream);
|
return versions.stream().map(ProjectVersion::toVersion).flatMap(Lang::toStream);
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Category> getCategoriesImpl() throws IOException {
|
|
||||||
List<Category> categories = HttpRequest.GET(PREFIX + "/v2/tag/category").getJson(listTypeOf(Category.class));
|
|
||||||
return categories.stream().filter(category -> category.getProjectType().equals(projectType)).collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteModRepository.Category> getCategories() throws IOException {
|
public Stream<RemoteModRepository.Category> getCategories() throws IOException {
|
||||||
return getCategoriesImpl().stream().map(Category::toCategory);
|
SEMAPHORE.acquireUninterruptibly();
|
||||||
|
try {
|
||||||
|
List<Category> categories = HttpRequest.GET(PREFIX + "/v2/tag/category").getJson(listTypeOf(Category.class));
|
||||||
|
return categories.stream()
|
||||||
|
.filter(category -> category.getProjectType().equals(projectType))
|
||||||
|
.map(Category::toCategory);
|
||||||
|
} finally {
|
||||||
|
SEMAPHORE.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Category {
|
public static class Category {
|
||||||
@@ -160,7 +194,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
private final String projectType;
|
private final String projectType;
|
||||||
|
|
||||||
public Category() {
|
public Category() {
|
||||||
this("","","");
|
this("", "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Category(String icon, String name, String projectType) {
|
public Category(String icon, String name, String projectType) {
|
||||||
|
|||||||
@@ -509,12 +509,22 @@ public abstract class FetchTask<T> extends Task<T> {
|
|||||||
return downloadExecutorConcurrency;
|
return downloadExecutorConcurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static volatile boolean initialized = false;
|
||||||
|
|
||||||
|
public static void notifyInitialized() {
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensure that [#HTTP_CLIENT] is initialized after ProxyManager has been initialized.
|
/// Ensure that [#HTTP_CLIENT] is initialized after ProxyManager has been initialized.
|
||||||
private static final class Holder {
|
private static final class Holder {
|
||||||
private static final HttpClient HTTP_CLIENT;
|
private static final HttpClient HTTP_CLIENT;
|
||||||
private static final String USER_AGENT = System.getProperty("http.agent", "HMCL");
|
private static final String USER_AGENT = System.getProperty("http.agent", "HMCL");
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
if (!initialized) {
|
||||||
|
throw new AssertionError("FetchTask.Holder accessed before ProxyManager initialization.");
|
||||||
|
}
|
||||||
|
|
||||||
boolean useHttp2 = !"false".equalsIgnoreCase(System.getProperty("hmcl.http2"));
|
boolean useHttp2 = !"false".equalsIgnoreCase(System.getProperty("hmcl.http2"));
|
||||||
|
|
||||||
HTTP_CLIENT = HttpClient.newBuilder()
|
HTTP_CLIENT = HttpClient.newBuilder()
|
||||||
|
|||||||
Reference in New Issue
Block a user