package io.gitlab.jfronny.inceptum.launcher.model.inceptum; import io.gitlab.jfronny.commons.HashUtils; import io.gitlab.jfronny.commons.data.MutCollection; import io.gitlab.jfronny.commons.data.delegate.DelegateMap; import io.gitlab.jfronny.commons.serialize.gson.api.v1.Ignore; import io.gitlab.jfronny.gson.compile.annotations.GPrefer; import io.gitlab.jfronny.gson.compile.annotations.GSerializable; import io.gitlab.jfronny.inceptum.common.GsonPreset; import io.gitlab.jfronny.inceptum.common.Utils; import io.gitlab.jfronny.inceptum.launcher.api.CurseforgeApi; import io.gitlab.jfronny.inceptum.launcher.api.ModrinthApi; import io.gitlab.jfronny.inceptum.launcher.gson.ModMetaSourcesAdapter; import io.gitlab.jfronny.inceptum.launcher.model.curseforge.response.FingerprintMatchesResponse; import io.gitlab.jfronny.inceptum.launcher.system.instance.Mod; import io.gitlab.jfronny.inceptum.launcher.system.source.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @GSerializable(configure = GsonPreset.Config.class) public record ModMeta( Sources sources, //key: source, value: update String sha1, Long murmur2, List dependents, // by file name List dependencies, // by file name boolean explicit ) { @GSerializable(with = ModMetaSourcesAdapter.class, configure = GsonPreset.Config.class) public static class Sources extends DelegateMap> { public Sources() { super(MutCollection.mapOf()); } private Optional getPreferredMetadataSource() { return keySet().stream().max((left, right) -> { if (left.equals(right)) return 0; if (left instanceof ModrinthModSource) return 1; if (right instanceof ModrinthModSource) return -1; if (left instanceof CurseforgeModSource) return 1; if (right instanceof CurseforgeModSource) return -1; return 1; }); } public @NotNull String getBestSummary() { return getPreferredMetadataSource() .map(ModSource::getSummary) .orElse("Local Mod"); } public @Nullable String getBestDescription() { return getPreferredMetadataSource() .map(ModSource::getDescription) .orElse(null); } } @GPrefer public ModMeta {} public static ModMeta of(Path mod) { String sha1 = null; Long murmur2 = null; if (!Files.isDirectory(mod)) { try { byte[] data = Files.readAllBytes(mod); sha1 = HashUtils.sha1(data); murmur2 = HashUtils.murmur2(data); } catch (IOException e) { Utils.LOGGER.error("Could not read file hash", e); } } return new ModMeta( new Sources(), sha1, murmur2, new ArrayList<>(), new ArrayList<>(), true ); } public static ModMeta of(String sha1, Long murmur2, @Nullable ModSource knownSource, String gameVersion) { ModMeta res = new ModMeta( new Sources(), sha1, murmur2, new ArrayList<>(), new ArrayList<>(), true ); if (knownSource != null) res.addSource(knownSource, gameVersion); res.initialize(gameVersion); return res; } public boolean initialize(String gameVersion) { boolean modrinth = false; boolean curseforge = false; for (ModSource source : sources.keySet().toArray(ModSource[]::new)) { if (source instanceof ModrinthModSource) modrinth = true; if (source instanceof CurseforgeModSource) curseforge = true; addSource(source, gameVersion); } boolean changed = false; if (!modrinth) { try { addSource(new ModrinthModSource(ModrinthApi.getVersionByHash(sha1).id()), gameVersion); changed = true; } catch (IOException e) { // not found } } if (!curseforge) { try { FingerprintMatchesResponse.Result cf = CurseforgeApi.checkFingerprint(murmur2); if (!cf.exactMatches().isEmpty()) { FingerprintMatchesResponse.Result.Match f = cf.exactMatches().get(0); addSource(new CurseforgeModSource(f.id(), f.file().id()), gameVersion); changed = true; } } catch (IOException | URISyntaxException e) { // not found } } return changed; } public void addSource(ModSource source, String gameVersion) { try { sources.put(source, source.getUpdate(gameVersion)); } catch (IOException e) { Utils.LOGGER.error("Could not check " + source.getName() + " for updates", e); } } }