Skip to content

Commit 3860e68

Browse files
authored
Feature: 下载并发控制 (#5026)
1 parent 74d0a89 commit 3860e68

5 files changed

Lines changed: 178 additions & 93 deletions

File tree

HMCL/src/main/java/org/jackhuang/hmcl/setting/ProxyManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.jackhuang.hmcl.setting;
1919

2020
import javafx.beans.InvalidationListener;
21+
import org.jackhuang.hmcl.task.FetchTask;
2122
import org.jackhuang.hmcl.util.StringUtils;
2223
import org.jackhuang.hmcl.util.io.NetworkUtils;
2324
import org.jetbrains.annotations.NotNull;
@@ -114,6 +115,8 @@ protected PasswordAuthentication getPasswordAuthentication() {
114115
config().hasProxyAuthProperty().addListener(updateAuthenticator);
115116
config().proxyUserProperty().addListener(updateAuthenticator);
116117
config().proxyPassProperty().addListener(updateAuthenticator);
118+
119+
FetchTask.notifyInitialized();
117120
}
118121

119122
private static abstract class AbstractProxySelector extends ProxySelector {

HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,12 +1217,14 @@ public static Image newBuiltinImage(String url, double requestedWidth, double re
12171217

12181218
public static Task<Image> getRemoteImageTask(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
12191219
return new CacheFileTask(url)
1220+
.setSignificance(Task.TaskSignificance.MINOR)
12201221
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
12211222
.setSignificance(Task.TaskSignificance.MINOR);
12221223
}
12231224

12241225
public static Task<Image> getRemoteImageTask(URI uri, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
12251226
return new CacheFileTask(uri)
1227+
.setSignificance(Task.TaskSignificance.MINOR)
12261228
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
12271229
.setSignificance(Task.TaskSignificance.MINOR);
12281230
}
@@ -1237,6 +1239,7 @@ public static ObservableValue<Image> newRemoteImage(String url, int requestedWid
12371239
LOG.warning("An exception encountered while loading remote image: " + url, exception);
12381240
}
12391241
})
1242+
.setSignificance(Task.TaskSignificance.MINOR)
12401243
.start();
12411244
return image;
12421245
}

HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java

Lines changed: 94 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@
3535
import java.io.InputStream;
3636
import java.nio.file.Files;
3737
import java.nio.file.Path;
38-
import java.util.*;
38+
import java.util.ArrayList;
39+
import java.util.Collections;
40+
import java.util.Comparator;
41+
import java.util.HashMap;
42+
import java.util.List;
43+
import java.util.Locale;
44+
import java.util.Map;
45+
import java.util.Optional;
46+
import java.util.concurrent.Semaphore;
3947
import java.util.stream.Stream;
4048

4149
import static org.jackhuang.hmcl.util.Lang.mapOf;
@@ -46,6 +54,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
4654

4755
private static final String PREFIX = "https://api.curseforge.com";
4856
private static final String apiKey = System.getProperty("hmcl.curseforge.apikey", JarUtils.getAttribute("hmcl.curseforge.apikey", ""));
57+
private static final Semaphore SEMAPHORE = new Semaphore(16);
4958

5059
private static final int WORD_PERFECT_MATCH_WEIGHT = 5;
5160

@@ -110,46 +119,51 @@ private int calculateTotalPages(Response<List<CurseAddon>> response, int pageSiz
110119

111120
@Override
112121
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
113-
int categoryId = 0;
114-
if (category != null && category.getSelf() instanceof CurseAddon.Category) {
115-
categoryId = ((CurseAddon.Category) category.getSelf()).getId();
116-
}
117-
Response<List<CurseAddon>> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", mapOf(
118-
pair("gameId", "432"),
119-
pair("classId", Integer.toString(section)),
120-
pair("categoryId", Integer.toString(categoryId)),
121-
pair("gameVersion", gameVersion),
122-
pair("searchFilter", searchFilter),
123-
pair("sortField", Integer.toString(toModsSearchSortField(sortType))),
124-
pair("sortOrder", toSortOrder(sortOrder)),
125-
pair("index", Integer.toString(pageOffset * pageSize)),
126-
pair("pageSize", Integer.toString(pageSize)))))))
127-
.getJson(Response.typeOf(listTypeOf(CurseAddon.class)));
128-
if (searchFilter.isEmpty()) {
129-
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
130-
}
122+
SEMAPHORE.acquireUninterruptibly();
123+
try {
124+
int categoryId = 0;
125+
if (category != null && category.getSelf() instanceof CurseAddon.Category) {
126+
categoryId = ((CurseAddon.Category) category.getSelf()).getId();
127+
}
128+
Response<List<CurseAddon>> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", mapOf(
129+
pair("gameId", "432"),
130+
pair("classId", Integer.toString(section)),
131+
pair("categoryId", Integer.toString(categoryId)),
132+
pair("gameVersion", gameVersion),
133+
pair("searchFilter", searchFilter),
134+
pair("sortField", Integer.toString(toModsSearchSortField(sortType))),
135+
pair("sortOrder", toSortOrder(sortOrder)),
136+
pair("index", Integer.toString(pageOffset * pageSize)),
137+
pair("pageSize", Integer.toString(pageSize)))))))
138+
.getJson(Response.typeOf(listTypeOf(CurseAddon.class)));
139+
if (searchFilter.isEmpty()) {
140+
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
141+
}
131142

132-
// https://github.com/HMCL-dev/HMCL/issues/1549
133-
String lowerCaseSearchFilter = searchFilter.toLowerCase(Locale.ROOT);
134-
Map<String, Integer> searchFilterWords = new HashMap<>();
135-
for (String s : StringUtils.tokenize(lowerCaseSearchFilter)) {
136-
searchFilterWords.put(s, searchFilterWords.getOrDefault(s, 0) + 1);
137-
}
143+
// https://github.com/HMCL-dev/HMCL/issues/1549
144+
String lowerCaseSearchFilter = searchFilter.toLowerCase(Locale.ROOT);
145+
Map<String, Integer> searchFilterWords = new HashMap<>();
146+
for (String s : StringUtils.tokenize(lowerCaseSearchFilter)) {
147+
searchFilterWords.put(s, searchFilterWords.getOrDefault(s, 0) + 1);
148+
}
138149

139-
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
150+
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
140151

141-
return new SearchResult(response.getData().stream().map(CurseAddon::toMod).map(remoteMod -> {
142-
String lowerCaseResult = remoteMod.getTitle().toLowerCase(Locale.ROOT);
143-
int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult);
152+
return new SearchResult(response.getData().stream().map(CurseAddon::toMod).map(remoteMod -> {
153+
String lowerCaseResult = remoteMod.getTitle().toLowerCase(Locale.ROOT);
154+
int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult);
144155

145-
for (String s : StringUtils.tokenize(lowerCaseResult)) {
146-
if (searchFilterWords.containsKey(s)) {
147-
diff -= WORD_PERFECT_MATCH_WEIGHT * searchFilterWords.get(s) * s.length();
156+
for (String s : StringUtils.tokenize(lowerCaseResult)) {
157+
if (searchFilterWords.containsKey(s)) {
158+
diff -= WORD_PERFECT_MATCH_WEIGHT * searchFilterWords.get(s) * s.length();
159+
}
148160
}
149-
}
150161

151-
return pair(remoteMod, diff);
152-
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
162+
return pair(remoteMod, diff);
163+
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
164+
} finally {
165+
SEMAPHORE.release();
166+
}
153167
}
154168

155169
@Override
@@ -173,48 +187,69 @@ public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile loca
173187
return Optional.empty();
174188
}
175189

176-
Response<FingerprintMatchesResult> response = withApiKey(HttpRequest.POST(PREFIX + "/v1/fingerprints/432"))
177-
.json(mapOf(pair("fingerprints", Collections.singletonList(hash))))
178-
.getJson(Response.typeOf(FingerprintMatchesResult.class));
190+
SEMAPHORE.acquireUninterruptibly();
191+
try {
192+
Response<FingerprintMatchesResult> response = withApiKey(HttpRequest.POST(PREFIX + "/v1/fingerprints/432"))
193+
.json(mapOf(pair("fingerprints", Collections.singletonList(hash))))
194+
.getJson(Response.typeOf(FingerprintMatchesResult.class));
179195

180-
if (response.getData().getExactMatches() == null || response.getData().getExactMatches().isEmpty()) {
181-
return Optional.empty();
182-
}
196+
if (response.getData().getExactMatches() == null || response.getData().getExactMatches().isEmpty()) {
197+
return Optional.empty();
198+
}
183199

184-
return Optional.of(response.getData().getExactMatches().get(0).getFile().toVersion());
200+
return Optional.of(response.getData().getExactMatches().get(0).getFile().toVersion());
201+
} finally {
202+
SEMAPHORE.release();
203+
}
185204
}
186205

187206
@Override
188207
public RemoteMod getModById(String id) throws IOException {
189-
Response<CurseAddon> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id))
190-
.getJson(Response.typeOf(CurseAddon.class));
191-
return response.data.toMod();
208+
SEMAPHORE.acquireUninterruptibly();
209+
try {
210+
Response<CurseAddon> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id))
211+
.getJson(Response.typeOf(CurseAddon.class));
212+
return response.data.toMod();
213+
} finally {
214+
SEMAPHORE.release();
215+
}
192216
}
193217

194218
@Override
195219
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
196-
Response<CurseAddon.LatestFile> response = withApiKey(HttpRequest.GET(String.format("%s/v1/mods/%s/files/%s", PREFIX, modId, fileId)))
197-
.getJson(Response.typeOf(CurseAddon.LatestFile.class));
198-
return response.getData().toVersion().getFile();
220+
SEMAPHORE.acquireUninterruptibly();
221+
try {
222+
Response<CurseAddon.LatestFile> response = withApiKey(HttpRequest.GET(String.format("%s/v1/mods/%s/files/%s", PREFIX, modId, fileId)))
223+
.getJson(Response.typeOf(CurseAddon.LatestFile.class));
224+
return response.getData().toVersion().getFile();
225+
} finally {
226+
SEMAPHORE.release();
227+
}
199228
}
200229

201230
@Override
202231
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
203-
Response<List<CurseAddon.LatestFile>> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files",
204-
pair("pageSize", "10000")))
205-
.getJson(Response.typeOf(listTypeOf(CurseAddon.LatestFile.class)));
206-
return response.getData().stream().map(CurseAddon.LatestFile::toVersion);
207-
}
208-
209-
public List<CurseAddon.Category> getCategoriesImpl() throws IOException {
210-
Response<List<CurseAddon.Category>> categories = withApiKey(HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432")))
211-
.getJson(Response.typeOf(listTypeOf(CurseAddon.Category.class)));
212-
return reorganizeCategories(categories.getData(), section);
232+
SEMAPHORE.acquireUninterruptibly();
233+
try {
234+
Response<List<CurseAddon.LatestFile>> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files",
235+
pair("pageSize", "10000")))
236+
.getJson(Response.typeOf(listTypeOf(CurseAddon.LatestFile.class)));
237+
return response.getData().stream().map(CurseAddon.LatestFile::toVersion);
238+
} finally {
239+
SEMAPHORE.release();
240+
}
213241
}
214242

215243
@Override
216244
public Stream<RemoteModRepository.Category> getCategories() throws IOException {
217-
return getCategoriesImpl().stream().map(CurseAddon.Category::toCategory);
245+
SEMAPHORE.acquireUninterruptibly();
246+
try {
247+
Response<List<CurseAddon.Category>> categories = withApiKey(HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432")))
248+
.getJson(Response.typeOf(listTypeOf(CurseAddon.Category.class)));
249+
return reorganizeCategories(categories.getData(), section).stream().map(CurseAddon.Category::toCategory);
250+
} finally {
251+
SEMAPHORE.release();
252+
}
218253
}
219254

220255
private List<CurseAddon.Category> reorganizeCategories(List<CurseAddon.Category> categories, int rootId) {

0 commit comments

Comments
 (0)