[Chores] 优化 addon 类型推断逻辑 (#6536)

This commit is contained in:
ToobLac
2026-08-02 19:58:33 +08:00
committed by GitHub
parent ccca396d91
commit 81634b14df
11 changed files with 145 additions and 104 deletions
@@ -49,7 +49,7 @@ public abstract class LocalizedRemoteAddonRepository implements RemoteAddonRepos
Set<String> englishSearchFiltersSet = new LinkedHashSet<>(INITIAL_CAPACITY);
int count = 0;
for (ModTranslations.Mod mod : ModTranslations.getTranslationsByRepositoryType(getType()).searchMod(searchFilter)) {
for (ModTranslations.Mod mod : ModTranslations.getTranslationsByAddonType(getType()).searchMod(searchFilter)) {
String englishSearchFilter = String.join(" ", StringUtils.tokenize(StringUtils.isNotBlank(mod.getSubname()) ? mod.getSubname() : mod.getName()));
if (StringUtils.isNotBlank(englishSearchFilter)) {
englishSearchFiltersSet.add(englishSearchFilter);
@@ -82,7 +82,7 @@ public abstract class LocalizedRemoteAddonRepository implements RemoteAddonRepos
continue;
}
ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteAddon.slug());
ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByAddonType(getType()).getModByCurseForgeId(remoteAddon.slug());
if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) {
searchResultArray[chineseIndex++] = remoteAddon;
} else {
@@ -94,7 +94,7 @@ public abstract class LocalizedRemoteAddonRepository implements RemoteAddonRepos
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
return new SearchResult(Stream.concat(Arrays.stream(searchResultArray, 0, chineseIndex).map(remoteMod -> {
ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.slug());
ModTranslations.Mod chineseRemoteMod = ModTranslations.getTranslationsByAddonType(getType()).getModByCurseForgeId(remoteMod.slug());
if (chineseRemoteMod == null || StringUtils.isBlank(chineseRemoteMod.getName()) || !StringUtils.containsChinese(chineseRemoteMod.getName())) {
return Pair.pair(remoteMod, Integer.MAX_VALUE);
}
@@ -586,12 +586,12 @@ public class DownloadListPage extends Control implements DecoratorPage, GameInst
setGraphic(null);
} else {
setPadding(
getIndex() == getListView().getItems().size() - 1
? LAST_PADDING
: PADDING
getIndex() == getListView().getItems().size() - 1
? LAST_PADDING
: PADDING
);
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(getSkinnable().repository.getType()).getModByCurseForgeId(item.slug());
ModTranslations.Mod mod = ModTranslations.getTranslationsByAddonType(getSkinnable().repository.getType()).getModByCurseForgeId(item.slug());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : item.title());
String description = item.description();
if (description != null) {
@@ -70,7 +70,7 @@ public class DownloadPage extends Control implements DecoratorPage {
private final HMCLGameRepository.InstanceReference instanceReference;
private final DownloadCallback callback;
private final DownloadListPage page;
private final RemoteAddonRepository.Type type;
private final RemoteAddon.Type type;
private SimpleMultimap<String, RemoteAddon.Version, List<RemoteAddon.Version>> versions;
@@ -78,8 +78,8 @@ public class DownloadPage extends Control implements DecoratorPage {
this.page = page;
this.repository = page.repository;
this.addon = addon;
this.type = Objects.requireNonNullElse(addon.repoType(), repository.getType());
this.translations = ModTranslations.getTranslationsByRepositoryType(this.type);
this.type = Objects.requireNonNullElse(addon.type(), repository.getType());
this.translations = ModTranslations.getTranslationsByAddonType(this.type);
this.mod = translations.getModByCurseForgeId(addon.slug());
this.instanceReference = instanceReference;
this.callback = callback;
@@ -281,7 +281,7 @@ public class DownloadPage extends Control implements DecoratorPage {
resolve:
for (RemoteAddon.Version modVersion : modVersions) {
if (getSkinnable().type == RemoteAddonRepository.Type.MOD) {
if (getSkinnable().type == RemoteAddon.Type.MOD) {
for (ModLoaderType loader : modVersion.loaders()) {
if (targetLoaders.contains(loader)) {
list.getContent().addAll(
@@ -386,8 +386,8 @@ public class DownloadPage extends Control implements DecoratorPage {
pane.getChildren().setAll(imageView, content);
FXUtils.setLimitHeight(this, 60);
RemoteAddonRepository.Type type = addon.repoType();
DownloadCallback callback = switch (type) {
RemoteAddon.Type type = addon.type();
DownloadCallback callback = type == null ? null : switch (type) {
case MOD -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_MOD;
case RESOURCE_PACK -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_RESOURCE_PACK;
case SHADER_PACK -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_SHADER;
@@ -400,7 +400,7 @@ public class DownloadPage extends Control implements DecoratorPage {
setNode(IDX_LEADING, pane);
if (addon != RemoteAddon.BROKEN) {
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(type).getModByCurseForgeId(addon.slug());
ModTranslations.Mod mod = ModTranslations.getTranslationsByAddonType(type).getModByCurseForgeId(addon.slug());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.title());
content.setSubtitle(addon.description());
for (String category : addon.categories()) {
@@ -510,7 +510,7 @@ public class DownloadPage extends Control implements DecoratorPage {
private static final class AddonVersion extends JFXDialogLayout {
public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage selfPage) {
RemoteAddonRepository.Type type = selfPage.type;
RemoteAddon.Type type = selfPage.type;
String title = switch (type) {
case WORLD -> "world.download.title";
@@ -546,10 +546,10 @@ public class DownloadPage extends Control implements DecoratorPage {
JFXButton downloadButton = null;
if (selfPage.callback != null) {
downloadButton = new JFXButton(type == RemoteAddonRepository.Type.MODPACK ? i18n("install.modpack") : i18n("mods.install"));
downloadButton = new JFXButton(type == RemoteAddon.Type.MODPACK ? i18n("install.modpack") : i18n("mods.install"));
downloadButton.getStyleClass().add("dialog-accept");
downloadButton.setOnAction(e -> {
if (type == RemoteAddonRepository.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
if (type == RemoteAddon.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
fireEvent(new DialogCloseEvent());
}
selfPage.download(mod, version);
@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.ui.instances;
import org.jackhuang.hmcl.addon.RemoteAddon;
import org.jackhuang.hmcl.game.LocalizedRemoteAddonRepository;
import org.jackhuang.hmcl.addon.RemoteAddonRepository;
import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository;
@@ -31,40 +32,40 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
public static DownloadListPage ofMod(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddonRepository.Type.MOD, CurseForgeRemoteAddonRepository.MODS, ModrinthRemoteAddonRepository.MODS);
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.MOD, CurseForgeRemoteAddonRepository.MODS, ModrinthRemoteAddonRepository.MODS);
}
public static DownloadListPage ofCurseForgeMod(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddonRepository.Type.MOD, CurseForgeRemoteAddonRepository.MODS, null);
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.MOD, CurseForgeRemoteAddonRepository.MODS, null);
}
public static DownloadListPage ofModrinthMod(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddonRepository.Type.MOD, null, ModrinthRemoteAddonRepository.MODS);
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.MOD, null, ModrinthRemoteAddonRepository.MODS);
}
public static DownloadListPage ofModPack(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddonRepository.Type.MODPACK, CurseForgeRemoteAddonRepository.MODPACKS, ModrinthRemoteAddonRepository.MODPACKS);
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.MODPACK, CurseForgeRemoteAddonRepository.MODPACKS, ModrinthRemoteAddonRepository.MODPACKS);
}
public static DownloadListPage ofResourcePack(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddonRepository.Type.RESOURCE_PACK, CurseForgeRemoteAddonRepository.RESOURCE_PACKS, ModrinthRemoteAddonRepository.RESOURCE_PACKS);
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.RESOURCE_PACK, CurseForgeRemoteAddonRepository.RESOURCE_PACKS, ModrinthRemoteAddonRepository.RESOURCE_PACKS);
}
public static DownloadListPage ofCurseForgeResourcePack(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddonRepository.Type.RESOURCE_PACK, CurseForgeRemoteAddonRepository.RESOURCE_PACKS, null);
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.RESOURCE_PACK, CurseForgeRemoteAddonRepository.RESOURCE_PACKS, null);
}
public static DownloadListPage ofModrinthResourcePack(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddonRepository.Type.RESOURCE_PACK, null, ModrinthRemoteAddonRepository.RESOURCE_PACKS);
return new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.RESOURCE_PACK, null, ModrinthRemoteAddonRepository.RESOURCE_PACKS);
}
public static DownloadListPage ofShaderPack(DownloadPage.DownloadCallback callback, boolean instanceSelection) {
var page = new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddonRepository.Type.SHADER_PACK, CurseForgeRemoteAddonRepository.SHADERS, ModrinthRemoteAddonRepository.SHADER_PACKS);
var page = new HMCLLocalizedDownloadListPage(callback, instanceSelection, RemoteAddon.Type.SHADER_PACK, CurseForgeRemoteAddonRepository.SHADERS, ModrinthRemoteAddonRepository.SHADER_PACKS);
page.supportChinese.set(false);
return page;
}
private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean instanceSelection, RemoteAddonRepository.Type type, CurseForgeRemoteAddonRepository curseForge, ModrinthRemoteAddonRepository modrinth) {
private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean instanceSelection, RemoteAddon.Type type, CurseForgeRemoteAddonRepository curseForge, ModrinthRemoteAddonRepository modrinth) {
super(null, callback, instanceSelection);
repository = new Repository(type, curseForge, modrinth);
@@ -98,11 +99,11 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
}
private class Repository extends LocalizedRemoteAddonRepository {
private final RemoteAddonRepository.Type type;
private final RemoteAddon.Type type;
private final CurseForgeRemoteAddonRepository curseForge;
private final ModrinthRemoteAddonRepository modrinth;
public Repository(Type type, CurseForgeRemoteAddonRepository curseForge, ModrinthRemoteAddonRepository modrinth) {
public Repository(RemoteAddon.Type type, CurseForgeRemoteAddonRepository curseForge, ModrinthRemoteAddonRepository modrinth) {
this.type = type;
this.curseForge = curseForge;
this.modrinth = modrinth;
@@ -127,7 +128,7 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
}
@Override
public Type getType() {
public RemoteAddon.Type getType() {
return type;
}
}
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.ui.instances;
import org.jackhuang.hmcl.addon.RemoteAddonRepository;
import org.jackhuang.hmcl.addon.RemoteAddon;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jetbrains.annotations.NotNull;
@@ -56,7 +56,8 @@ public enum ModTranslations {
}
};
public static ModTranslations getTranslationsByRepositoryType(RemoteAddonRepository.Type type) {
public static ModTranslations getTranslationsByAddonType(@Nullable RemoteAddon.Type type) {
if (type == null) return EMPTY;
return switch (type) {
case MOD -> MOD;
case MODPACK -> MODPACK;
@@ -32,7 +32,7 @@ import java.util.Map;
import java.util.stream.Stream;
public record RemoteAddon(String slug, String author, String title, String description, List<String> categories,
String pageUrl, String iconUrl, IAddon data, RemoteAddonRepository.Type repoType) {
String pageUrl, String iconUrl, IAddon data, @Nullable Type type) {
public static final RemoteAddon BROKEN = new RemoteAddon("", "", "RemoteAddon.BROKEN", "", Collections.emptyList(), "", "", new IAddon() {
@Override
@@ -44,7 +44,7 @@ public record RemoteAddon(String slug, String author, String title, String descr
public Stream<Version> loadVersions(RemoteAddonRepository repo, DownloadProvider downloadProvider) throws IOException {
throw new IOException();
}
}, RemoteAddonRepository.Type.MOD);
}, Type.MOD);
public enum VersionType {
Release,
@@ -67,23 +67,23 @@ public record RemoteAddon(String slug, String author, String title, String descr
private final DependencyType type;
private final @Nullable RemoteAddonRepository remoteAddonRepository;
private final @Nullable Source source;
private final @Nullable String id;
private transient RemoteAddon remoteAddon = null;
private Dependency(DependencyType type, @Nullable RemoteAddonRepository remoteAddonRepository, @Nullable String id) {
private Dependency(DependencyType type, @Nullable Source source, @Nullable String id) {
this.type = type;
this.remoteAddonRepository = remoteAddonRepository;
this.source = source;
this.id = id;
}
public static Dependency ofGeneral(DependencyType type, RemoteAddonRepository remoteAddonRepository, String id) {
public static Dependency ofGeneral(DependencyType type, Source source, String id) {
if (type == DependencyType.BROKEN) {
return ofBroken();
} else {
return new Dependency(type, remoteAddonRepository, id);
return new Dependency(type, source, id);
}
}
@@ -99,8 +99,8 @@ public record RemoteAddon(String slug, String author, String title, String descr
}
@Nullable
public RemoteAddonRepository getRemoteModRepository() {
return this.remoteAddonRepository;
public Source getSource() {
return this.source;
}
@Nullable
@@ -113,7 +113,7 @@ public record RemoteAddon(String slug, String author, String title, String descr
if (this.type == DependencyType.BROKEN) {
this.remoteAddon = RemoteAddon.BROKEN;
} else {
this.remoteAddon = this.remoteAddonRepository.resolveDependency(downloadProvider, this.id);
this.remoteAddon = this.source.getCommonRepo().resolveDependency(downloadProvider, this.id);
}
}
return this.remoteAddon;
@@ -127,14 +127,14 @@ public record RemoteAddon(String slug, String author, String title, String descr
Dependency that = (Dependency) o;
if (type != that.type) return false;
if (!remoteAddonRepository.equals(that.remoteAddonRepository)) return false;
if (source != that.source) return false;
return id.equals(that.id);
}
@Override
public int hashCode() {
int result = type.hashCode();
result = 31 * result + remoteAddonRepository.hashCode();
result = 31 * result + source.hashCode();
result = 31 * result + id.hashCode();
return result;
}
@@ -142,6 +142,7 @@ public record RemoteAddon(String slug, String author, String title, String descr
public enum Source {
CURSEFORGE(
CurseForgeRemoteAddonRepository.COMMON,
CurseForgeRemoteAddonRepository.MODS,
CurseForgeRemoteAddonRepository.RESOURCE_PACKS,
CurseForgeRemoteAddonRepository.SHADERS,
@@ -150,6 +151,7 @@ public record RemoteAddon(String slug, String author, String title, String descr
CurseForgeRemoteAddonRepository.CUSTOMIZATIONS
),
MODRINTH(
ModrinthRemoteAddonRepository.COMMON,
ModrinthRemoteAddonRepository.MODS,
ModrinthRemoteAddonRepository.RESOURCE_PACKS,
ModrinthRemoteAddonRepository.SHADER_PACKS,
@@ -158,15 +160,16 @@ public record RemoteAddon(String slug, String author, String title, String descr
null
);
public final RemoteAddonRepository modRepo;
public final RemoteAddonRepository resourcePackRepo;
public final RemoteAddonRepository shaderPackRepo;
public final RemoteAddonRepository worldRepo;
public final RemoteAddonRepository modpackRepo;
public final RemoteAddonRepository customizationRepo;
private final RemoteAddonRepository commonRepo;
private final RemoteAddonRepository modRepo;
private final RemoteAddonRepository resourcePackRepo;
private final RemoteAddonRepository shaderPackRepo;
private final RemoteAddonRepository worldRepo;
private final RemoteAddonRepository modpackRepo;
private final RemoteAddonRepository customizationRepo;
@Nullable
public RemoteAddonRepository getRepoForType(RemoteAddonRepository.Type type) {
public RemoteAddonRepository getRepoForType(Type type) {
return switch (type) {
case MOD -> modRepo;
case RESOURCE_PACK -> resourcePackRepo;
@@ -177,7 +180,12 @@ public record RemoteAddon(String slug, String author, String title, String descr
};
}
public RemoteAddonRepository getCommonRepo() {
return commonRepo;
}
Source(
RemoteAddonRepository commonRepo,
RemoteAddonRepository modRepo,
RemoteAddonRepository resourcePackRepo,
RemoteAddonRepository shaderPackRepo,
@@ -185,6 +193,7 @@ public record RemoteAddon(String slug, String author, String title, String descr
RemoteAddonRepository modpackRepo,
RemoteAddonRepository customizationRepo
) {
this.commonRepo = commonRepo;
this.modRepo = modRepo;
this.resourcePackRepo = resourcePackRepo;
this.shaderPackRepo = shaderPackRepo;
@@ -194,6 +203,15 @@ public record RemoteAddon(String slug, String author, String title, String descr
}
}
public enum Type {
MOD,
MODPACK,
RESOURCE_PACK,
SHADER_PACK,
WORLD,
CUSTOMIZATION
}
public interface IAddon {
List<RemoteAddon> loadDependencies(RemoteAddonRepository repo, DownloadProvider downloadProvider) throws IOException;
@@ -28,16 +28,7 @@ import java.util.stream.Stream;
public interface RemoteAddonRepository {
enum Type {
MOD,
MODPACK,
RESOURCE_PACK,
SHADER_PACK,
WORLD,
CUSTOMIZATION
}
Type getType();
RemoteAddon.Type getType();
enum SortType {
POPULARITY,
@@ -196,7 +196,7 @@ public final class LocalModFile extends LocalAddonFile implements Comparable<Loc
@Override
public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException {
RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.MOD);
RemoteAddonRepository repository = source.getRepoForType(RemoteAddon.Type.MOD);
if (repository == null) return null;
Optional<RemoteAddon.Version> currentVersion = repository.getRemoteVersionByLocalFile(file);
if (currentVersion.isEmpty()) return null;
@@ -70,16 +70,23 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
return !API_KEY.isEmpty();
}
private final Type type;
private final @Nullable RemoteAddon.Type type;
private final int section;
public CurseForgeRemoteAddonRepository(Type type, int section) {
this.type = type;
public CurseForgeRemoteAddonRepository() {
this.type = null;
this.section = -1;
}
public CurseForgeRemoteAddonRepository(int section) {
this.type = toAddonType(section);
if (type == null) throw new IllegalArgumentException("Unsupported CurseForge section id: " + section);
this.section = section;
}
@Override
public Type getType() {
public RemoteAddon.Type getType() {
if (type == null) throw new UnsupportedOperationException();
return type;
}
@@ -110,6 +117,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
@Override
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteAddonRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
if (type == null) throw new UnsupportedOperationException();
SEMAPHORE.acquireUninterruptibly();
try {
int categoryId = 0;
@@ -139,7 +147,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
response = withApiKey(HttpRequest.GET(candidate.toString()))
.getJson(Response.typeOf(listTypeOf(CurseAddon.class)));
if (searchFilter.isEmpty()) {
return new SearchResult(response.data().stream().map(addon -> addon.toAddon(type)), calculateTotalPages(response, pageSize));
return new SearchResult(response.data().stream().map(CurseAddon::toAddon), calculateTotalPages(response, pageSize));
}
break;
} catch (IOException e) {
@@ -168,7 +176,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
return new SearchResult(response.data().stream().map(addon -> addon.toAddon(type)).map(remoteAddon -> {
return new SearchResult(response.data().stream().map(CurseAddon::toAddon).map(remoteAddon -> {
String lowerCaseResult = remoteAddon.title().toLowerCase(Locale.ROOT);
int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult);
@@ -179,7 +187,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
}
return pair(remoteAddon, diff);
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.data().stream().map(addon -> addon.toAddon(type)), calculateTotalPages(response, pageSize));
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.data().stream().map(CurseAddon::toAddon), calculateTotalPages(response, pageSize));
} finally {
SEMAPHORE.release();
}
@@ -228,7 +236,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
try {
Response<CurseAddon> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id))
.getJson(Response.typeOf(CurseAddon.class));
return response.data.toAddon(type);
return response.data.toAddon();
} finally {
SEMAPHORE.release();
}
@@ -261,6 +269,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
@Override
public Stream<RemoteAddonRepository.Category> getCategories() throws IOException {
if (type == null) throw new UnsupportedOperationException();
SEMAPHORE.acquireUninterruptibly();
try {
Response<List<Category>> categories = withApiKey(HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432")))
@@ -305,12 +314,26 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
public static final int SECTION_UNKNOWN2 = 4979;
public static final int SECTION_UNKNOWN3 = 4984;
public static final CurseForgeRemoteAddonRepository MODS = new CurseForgeRemoteAddonRepository(RemoteAddonRepository.Type.MOD, SECTION_MOD);
public static final CurseForgeRemoteAddonRepository MODPACKS = new CurseForgeRemoteAddonRepository(RemoteAddonRepository.Type.MODPACK, SECTION_MODPACK);
public static final CurseForgeRemoteAddonRepository RESOURCE_PACKS = new CurseForgeRemoteAddonRepository(RemoteAddonRepository.Type.RESOURCE_PACK, SECTION_RESOURCE_PACK);
public static final CurseForgeRemoteAddonRepository WORLDS = new CurseForgeRemoteAddonRepository(RemoteAddonRepository.Type.WORLD, SECTION_WORLD);
public static final CurseForgeRemoteAddonRepository CUSTOMIZATIONS = new CurseForgeRemoteAddonRepository(RemoteAddonRepository.Type.CUSTOMIZATION, SECTION_CUSTOMIZATION);
public static final CurseForgeRemoteAddonRepository SHADERS = new CurseForgeRemoteAddonRepository(RemoteAddonRepository.Type.SHADER_PACK, SECTION_SHADER);
public static final CurseForgeRemoteAddonRepository COMMON = new CurseForgeRemoteAddonRepository();
public static final CurseForgeRemoteAddonRepository MODS = new CurseForgeRemoteAddonRepository(SECTION_MOD);
public static final CurseForgeRemoteAddonRepository MODPACKS = new CurseForgeRemoteAddonRepository(SECTION_MODPACK);
public static final CurseForgeRemoteAddonRepository RESOURCE_PACKS = new CurseForgeRemoteAddonRepository(SECTION_RESOURCE_PACK);
public static final CurseForgeRemoteAddonRepository WORLDS = new CurseForgeRemoteAddonRepository(SECTION_WORLD);
public static final CurseForgeRemoteAddonRepository CUSTOMIZATIONS = new CurseForgeRemoteAddonRepository(SECTION_CUSTOMIZATION);
public static final CurseForgeRemoteAddonRepository SHADERS = new CurseForgeRemoteAddonRepository(SECTION_SHADER);
@Nullable
private static RemoteAddon.Type toAddonType(int classId) {
return switch (classId) {
case SECTION_MODPACK -> RemoteAddon.Type.MODPACK;
case SECTION_RESOURCE_PACK -> RemoteAddon.Type.RESOURCE_PACK;
case SECTION_WORLD -> RemoteAddon.Type.WORLD;
case SECTION_CUSTOMIZATION -> RemoteAddon.Type.CUSTOMIZATION;
case SECTION_SHADER -> RemoteAddon.Type.SHADER_PACK;
case SECTION_MOD -> RemoteAddon.Type.MOD;
default -> null;
};
}
public record Pagination(int index, int pageSize, int resultCount, int totalCount) {
}
@@ -380,7 +403,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
return repo.getRemoteVersionsById(downloadProvider, Integer.toString(id));
}
public RemoteAddon toAddon(Type type) {
public RemoteAddon toAddon() {
String iconUrl = "";
if (logo != null) {
if (StringUtils.isNotBlank(logo.thumbnailUrl()))
@@ -398,7 +421,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
links.websiteUrl,
iconUrl,
this,
type
toAddonType(classId)
);
}
@@ -480,7 +503,7 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
if (!RELATION_TYPE.containsKey(dependency.relationType())) {
throw new IllegalStateException("Broken datas.");
}
return RemoteAddon.Dependency.ofGeneral(RELATION_TYPE.get(dependency.relationType()), MODS, Integer.toString(dependency.modId()));
return RemoteAddon.Dependency.ofGeneral(RELATION_TYPE.get(dependency.relationType()), RemoteAddon.Source.CURSEFORGE, Integer.toString(dependency.modId()));
}).distinct().filter(Objects::nonNull).collect(Collectors.toList()),
gameVersions.stream().filter(GameVersionNumber::isKnown).toList(),
gameVersions.stream().flatMap(version -> {
@@ -24,10 +24,12 @@ import org.jackhuang.hmcl.addon.RemoteAddonRepository;
import org.jackhuang.hmcl.addon.mod.ModLoaderType;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.gson.JsonSerializable;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.io.ResponseCodeException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@@ -49,6 +51,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/// @see <a href="https://docs.modrinth.com/api">Modrinth API Doc</a>
public final class ModrinthRemoteAddonRepository implements RemoteAddonRepository {
public static final ModrinthRemoteAddonRepository COMMON = new ModrinthRemoteAddonRepository();
public static final ModrinthRemoteAddonRepository MODS = new ModrinthRemoteAddonRepository("mod");
public static final ModrinthRemoteAddonRepository MODPACKS = new ModrinthRemoteAddonRepository("modpack");
public static final ModrinthRemoteAddonRepository RESOURCE_PACKS = new ModrinthRemoteAddonRepository("resourcepack");
@@ -89,26 +92,39 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
false
);
@Nullable
private static RemoteAddon.Type toAddonType(String projectType) {
return switch (projectType) {
case "modpack" -> RemoteAddon.Type.MODPACK;
case "resourcepack" -> RemoteAddon.Type.RESOURCE_PACK;
case "shader" -> RemoteAddon.Type.SHADER_PACK;
case "mod" -> RemoteAddon.Type.MOD;
default -> null;
};
}
private static final Semaphore SEMAPHORE = new Semaphore(16);
private static final String PREFIX = "https://api.modrinth.com";
private final String projectType;
private final @Nullable String projectType;
private final RemoteAddonRepository.Type type;
private final @Nullable RemoteAddon.Type type;
private ModrinthRemoteAddonRepository(String projectType) {
private ModrinthRemoteAddonRepository() {
this.projectType = null;
this.type = null;
}
private ModrinthRemoteAddonRepository(@NotNull String projectType) {
this.projectType = projectType;
this.type = switch (projectType) {
case "modpack" -> Type.MODPACK;
case "resourcepack" -> Type.RESOURCE_PACK;
case "shader" -> Type.SHADER_PACK;
default -> Type.MOD;
};
this.type = toAddonType(projectType);
if (type == null) throw new IllegalArgumentException("Unsupported Modrinth project type: " + projectType);
}
@Override
public Type getType() {
public RemoteAddon.Type getType() {
if (type == null) throw new UnsupportedOperationException();
return this.type;
}
@@ -131,6 +147,7 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
@Override
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteAddonRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
if (projectType == null) throw new UnsupportedOperationException();
SEMAPHORE.acquireUninterruptibly();
try {
List<List<String>> facets = new ArrayList<>();
@@ -286,6 +303,7 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
@Override
public Stream<RemoteAddonRepository.Category> getCategories() throws IOException {
if (projectType == null) throw new UnsupportedOperationException();
SEMAPHORE.acquireUninterruptibly();
try {
List<Category> categories = HttpRequest.GET(PREFIX + "/v2/tag/category").getJson(listTypeOf(Category.class));
@@ -297,6 +315,7 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
}
}
@JsonSerializable
public record Category(String icon, String name, @SerializedName("project_type") String projectType) {
public Category() {
this("", "", "");
@@ -336,12 +355,6 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
}
public RemoteAddon toAddon() {
Type type = switch (projectType) {
case "modpack" -> Type.MODPACK;
case "resourcepack" -> Type.RESOURCE_PACK;
case "shader" -> Type.SHADER_PACK;
default -> Type.MOD;
};
return new RemoteAddon(
slug,
"",
@@ -351,7 +364,7 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
String.format("https://modrinth.com/%s/%s", projectType, id),
iconUrl,
this,
type
toAddonType(projectType)
);
}
}
@@ -417,7 +430,7 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
throw new IllegalStateException("Broken datas");
}
return RemoteAddon.Dependency.ofGeneral(DEPENDENCY_TYPE.get(dependency.dependencyType), MODS, dependency.projectId);
return RemoteAddon.Dependency.ofGeneral(DEPENDENCY_TYPE.get(dependency.dependencyType), RemoteAddon.Source.MODRINTH, dependency.projectId);
}).filter(Objects::nonNull).collect(Collectors.toList()),
gameVersions,
loaders.stream().flatMap(loader -> {
@@ -468,12 +481,6 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
}
public RemoteAddon toAddon() {
Type type = switch (projectType) {
case "modpack" -> Type.MODPACK;
case "resourcepack" -> Type.RESOURCE_PACK;
case "shader" -> Type.SHADER_PACK;
default -> Type.MOD;
};
return new RemoteAddon(
slug,
author,
@@ -483,7 +490,7 @@ public final class ModrinthRemoteAddonRepository implements RemoteAddonRepositor
String.format("https://modrinth.com/%s/%s", projectType, projectId),
iconUrl,
this,
type
toAddonType(projectType)
);
}
}
@@ -91,7 +91,7 @@ final class ResourcePackZipFile extends ResourcePackFile {
@Override
public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException {
RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.RESOURCE_PACK);
RemoteAddonRepository repository = source.getRepoForType(RemoteAddon.Type.RESOURCE_PACK);
if (repository == null) return null;
Optional<RemoteAddon.Version> currentVersion = repository.getRemoteVersionByLocalFile(file);
if (currentVersion.isEmpty()) return null;