3535import java .io .InputStream ;
3636import java .nio .file .Files ;
3737import 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 ;
3947import java .util .stream .Stream ;
4048
4149import 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