From 02e8b58c09d37f0dd55dea0a1bc34d39d7f827d3 Mon Sep 17 00:00:00 2001 From: vLuckyyy Date: Sun, 3 Aug 2025 19:22:44 +0200 Subject: [PATCH 1/6] Introduce simple update checker, using Modrinth api. --- .../build.gradle.kts | 7 ++ .../updater/example/ExampleChecker.java | 17 ++++ .../updater/example/ExampleUpdateService.java | 13 +++ eternalcode-commons-updater/build.gradle.kts | 10 ++ .../commons/updater/UpdateChecker.java | 6 ++ .../commons/updater/UpdateResult.java | 8 ++ .../eternalcode/commons/updater/Version.java | 34 +++++++ .../updater/impl/ModrinthUpdateChecker.java | 92 +++++++++++++++++++ settings.gradle.kts | 2 + 9 files changed, 189 insertions(+) create mode 100644 eternalcode-commons-updater-example/build.gradle.kts create mode 100644 eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java create mode 100644 eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleUpdateService.java create mode 100644 eternalcode-commons-updater/build.gradle.kts create mode 100644 eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateChecker.java create mode 100644 eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateResult.java create mode 100644 eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java create mode 100644 eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java diff --git a/eternalcode-commons-updater-example/build.gradle.kts b/eternalcode-commons-updater-example/build.gradle.kts new file mode 100644 index 0000000..918c7ee --- /dev/null +++ b/eternalcode-commons-updater-example/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `commons-java-17` +} + +dependencies { + implementation(project(":eternalcode-commons-updater")) +} diff --git a/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java b/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java new file mode 100644 index 0000000..51cc3ad --- /dev/null +++ b/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java @@ -0,0 +1,17 @@ +package com.eternalcode.commons.updater.example; + +import com.eternalcode.commons.updater.UpdateResult; + +public class ExampleChecker { + public static void main(String[] args) { + ExampleUpdateService service = new ExampleUpdateService(); + + UpdateResult modrinthResult = service.checkModrinth("EternalCombat", "2.2.0"); + System.out.println("Modrinth update available: " + modrinthResult.isUpdateAvailable()); + if (modrinthResult.isUpdateAvailable()) { + System.out.println("Latest: " + modrinthResult.latestVersion()); + System.out.println("Download: " + modrinthResult.downloadUrl()); + System.out.println("Release page: " + modrinthResult.releaseUrl()); + } + } +} diff --git a/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleUpdateService.java b/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleUpdateService.java new file mode 100644 index 0000000..893063f --- /dev/null +++ b/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleUpdateService.java @@ -0,0 +1,13 @@ +package com.eternalcode.commons.updater.example; + +import com.eternalcode.commons.updater.UpdateResult; +import com.eternalcode.commons.updater.Version; +import com.eternalcode.commons.updater.impl.ModrinthUpdateChecker; + +public final class ExampleUpdateService { + private final ModrinthUpdateChecker modrinthChecker = new ModrinthUpdateChecker(); + + public UpdateResult checkModrinth(String projectId, String currentVersion) { + return modrinthChecker.check(projectId, new Version(currentVersion)); + } +} diff --git a/eternalcode-commons-updater/build.gradle.kts b/eternalcode-commons-updater/build.gradle.kts new file mode 100644 index 0000000..f6010c9 --- /dev/null +++ b/eternalcode-commons-updater/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + `commons-java-17` + `commons-publish` + `commons-repositories` + `commons-java-unit-test` +} + +tasks.test { + useJUnitPlatform() +} diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateChecker.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateChecker.java new file mode 100644 index 0000000..b599f91 --- /dev/null +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateChecker.java @@ -0,0 +1,6 @@ +package com.eternalcode.commons.updater; + +public interface UpdateChecker { + + UpdateResult check(String projectId, Version currentVersion); +} diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateResult.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateResult.java new file mode 100644 index 0000000..48e9a6f --- /dev/null +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateResult.java @@ -0,0 +1,8 @@ +package com.eternalcode.commons.updater; + +public record UpdateResult(Version currentVersion, Version latestVersion, String downloadUrl, String releaseUrl) { + + public boolean isUpdateAvailable() { + return this.latestVersion.isNewerThan(this.currentVersion); + } +} diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java new file mode 100644 index 0000000..b1ca27c --- /dev/null +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java @@ -0,0 +1,34 @@ +package com.eternalcode.commons.updater; + +public class Version implements Comparable { + + private final String value; + + public Version(String version) { + this.value = version.trim(); + } + + @Override + public int compareTo(Version version) { + return this.value.compareTo(version.value); + } + + public boolean isNewerThan(Version version) { + return this.value.compareTo(version.value) > 0; + } + + @Override + public boolean equals(Object object) { + return object instanceof Version other && value.equals(other.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return this.value; + } +} diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java new file mode 100644 index 0000000..cc39b7f --- /dev/null +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java @@ -0,0 +1,92 @@ +package com.eternalcode.commons.updater.impl; + +import com.eternalcode.commons.updater.UpdateChecker; +import com.eternalcode.commons.updater.UpdateResult; +import com.eternalcode.commons.updater.Version; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Optional; + +public final class ModrinthUpdateChecker implements UpdateChecker { + + private static final String API_BASE_URL = "https://api.modrinth.com/v2"; + private static final String MODRINTH_BASE_URL = "https://modrinth.com/mod"; + private static final String USER_AGENT = "UpdateChecker/1.0"; + private static final Duration TIMEOUT = Duration.ofSeconds(10); + + private final HttpClient client; + + public ModrinthUpdateChecker() { + this.client = HttpClient.newBuilder().connectTimeout(TIMEOUT).build(); + } + + @Override + public UpdateResult check(String projectId, Version currentVersion) { + if (projectId == null || projectId.trim().isEmpty()) { + throw new IllegalArgumentException("Project ID cannot be null or empty"); + } + + try { + String url = API_BASE_URL + "/project/" + projectId + "/version"; + + HttpRequest request = + HttpRequest.newBuilder().uri(URI.create(url)).header("User-Agent", USER_AGENT).timeout(TIMEOUT).build(); + + HttpResponse response = this.client.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + return createEmptyResult(currentVersion); + } + + String json = response.body(); + if (json == null || json.trim().isEmpty()) { + return createEmptyResult(currentVersion); + } + + Optional versionNumber = extractJsonValue(json, "version_number"); + Optional downloadUrl = extractJsonValue(json, "url"); + + if (versionNumber.isEmpty()) { + return createEmptyResult(currentVersion); + } + + String releaseUrl = MODRINTH_BASE_URL + "/" + projectId + "/version/" + versionNumber.get(); + Version latestVersion = new Version(versionNumber.get()); + + return new UpdateResult(currentVersion, latestVersion, downloadUrl.orElse(null), releaseUrl); + } + catch (Exception exception) { + throw new RuntimeException("Failed to check Modrinth updates for project: " + projectId, exception); + } + } + + private UpdateResult createEmptyResult(Version currentVersion) { + return new UpdateResult(currentVersion, currentVersion, null, null); + } + + private Optional extractJsonValue(String json, String key) { + if (json == null || key == null) { + return Optional.empty(); + } + + String pattern = "\"" + key + "\":\""; + int start = json.indexOf(pattern); + + if (start == -1) { + return Optional.empty(); + } + + start += pattern.length(); + int end = json.indexOf("\"", start); + + if (end == -1) { + return Optional.empty(); + } + + String value = json.substring(start, end); + return value.isEmpty() ? Optional.empty() : Optional.of(value); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 661c551..8aa583e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,3 +4,5 @@ include(":eternalcode-commons-bukkit") include(":eternalcode-commons-adventure") include(":eternalcode-commons-shared") include("eternalcode-commons-folia") +include("eternalcode-commons-updater") +include("eternalcode-commons-updater-example") From fa95908bfa3e426490c4c5402da08b85b4eb9254 Mon Sep 17 00:00:00 2001 From: vLuckyyy Date: Sun, 3 Aug 2025 19:25:25 +0200 Subject: [PATCH 2/6] Use old eternalcombat version for testing purposes. --- .../eternalcode/commons/updater/example/ExampleChecker.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java b/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java index 51cc3ad..d39fe67 100644 --- a/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java +++ b/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java @@ -3,10 +3,13 @@ import com.eternalcode.commons.updater.UpdateResult; public class ExampleChecker { + + private static final String OLD_ETERNALCOMBAT_VERSION = "1.3.3"; + public static void main(String[] args) { ExampleUpdateService service = new ExampleUpdateService(); - UpdateResult modrinthResult = service.checkModrinth("EternalCombat", "2.2.0"); + UpdateResult modrinthResult = service.checkModrinth("EternalCombat", OLD_ETERNALCOMBAT_VERSION); System.out.println("Modrinth update available: " + modrinthResult.isUpdateAvailable()); if (modrinthResult.isUpdateAvailable()) { System.out.println("Latest: " + modrinthResult.latestVersion()); From 12df81d8624827f93dca98a9614018a256c15ca9 Mon Sep 17 00:00:00 2001 From: vLuckyyy Date: Sun, 3 Aug 2025 19:47:02 +0200 Subject: [PATCH 3/6] Use `org.json` for json parsing. --- buildSrc/build.gradle.kts | 1 + .../build.gradle.kts | 1 + .../updater/example/ExampleChecker.java | 4 +- eternalcode-commons-updater/build.gradle.kts | 6 +- .../updater/impl/ModrinthUpdateChecker.java | 72 +++++++++---------- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 8cd15cd..a116a08 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,6 +3,7 @@ plugins { } repositories { + mavenCentral() gradlePluginPortal() } diff --git a/eternalcode-commons-updater-example/build.gradle.kts b/eternalcode-commons-updater-example/build.gradle.kts index 918c7ee..d4ebdee 100644 --- a/eternalcode-commons-updater-example/build.gradle.kts +++ b/eternalcode-commons-updater-example/build.gradle.kts @@ -1,5 +1,6 @@ plugins { `commons-java-17` + `commons-repositories` } dependencies { diff --git a/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java b/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java index d39fe67..feef245 100644 --- a/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java +++ b/eternalcode-commons-updater-example/src/main/java/com/eternalcode/commons/updater/example/ExampleChecker.java @@ -7,9 +7,9 @@ public class ExampleChecker { private static final String OLD_ETERNALCOMBAT_VERSION = "1.3.3"; public static void main(String[] args) { - ExampleUpdateService service = new ExampleUpdateService(); + ExampleUpdateService updateService = new ExampleUpdateService(); - UpdateResult modrinthResult = service.checkModrinth("EternalCombat", OLD_ETERNALCOMBAT_VERSION); + UpdateResult modrinthResult = updateService.checkModrinth("EternalCombat", OLD_ETERNALCOMBAT_VERSION); System.out.println("Modrinth update available: " + modrinthResult.isUpdateAvailable()); if (modrinthResult.isUpdateAvailable()) { System.out.println("Latest: " + modrinthResult.latestVersion()); diff --git a/eternalcode-commons-updater/build.gradle.kts b/eternalcode-commons-updater/build.gradle.kts index f6010c9..58e8991 100644 --- a/eternalcode-commons-updater/build.gradle.kts +++ b/eternalcode-commons-updater/build.gradle.kts @@ -2,9 +2,9 @@ plugins { `commons-java-17` `commons-publish` `commons-repositories` - `commons-java-unit-test` } -tasks.test { - useJUnitPlatform() +dependencies { + api("org.json:json:20240303") + api(project(":eternalcode-commons-shared")) } diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java index cc39b7f..46aa589 100644 --- a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java @@ -1,5 +1,6 @@ package com.eternalcode.commons.updater.impl; +import com.eternalcode.commons.Lazy; import com.eternalcode.commons.updater.UpdateChecker; import com.eternalcode.commons.updater.UpdateResult; import com.eternalcode.commons.updater.Version; @@ -8,7 +9,9 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; -import java.util.Optional; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; public final class ModrinthUpdateChecker implements UpdateChecker { @@ -17,11 +20,7 @@ public final class ModrinthUpdateChecker implements UpdateChecker { private static final String USER_AGENT = "UpdateChecker/1.0"; private static final Duration TIMEOUT = Duration.ofSeconds(10); - private final HttpClient client; - - public ModrinthUpdateChecker() { - this.client = HttpClient.newBuilder().connectTimeout(TIMEOUT).build(); - } + private final Lazy client = new Lazy<>(() -> HttpClient.newBuilder().connectTimeout(TIMEOUT).build()); @Override public UpdateResult check(String projectId, Version currentVersion) { @@ -35,7 +34,7 @@ public UpdateResult check(String projectId, Version currentVersion) { HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).header("User-Agent", USER_AGENT).timeout(TIMEOUT).build(); - HttpResponse response = this.client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = this.client.get().send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { return createEmptyResult(currentVersion); @@ -46,47 +45,48 @@ public UpdateResult check(String projectId, Version currentVersion) { return createEmptyResult(currentVersion); } - Optional versionNumber = extractJsonValue(json, "version_number"); - Optional downloadUrl = extractJsonValue(json, "url"); - - if (versionNumber.isEmpty()) { - return createEmptyResult(currentVersion); - } - - String releaseUrl = MODRINTH_BASE_URL + "/" + projectId + "/version/" + versionNumber.get(); - Version latestVersion = new Version(versionNumber.get()); - - return new UpdateResult(currentVersion, latestVersion, downloadUrl.orElse(null), releaseUrl); + return parseVersionResponse(json, currentVersion, projectId); } catch (Exception exception) { throw new RuntimeException("Failed to check Modrinth updates for project: " + projectId, exception); } } - private UpdateResult createEmptyResult(Version currentVersion) { - return new UpdateResult(currentVersion, currentVersion, null, null); - } + private UpdateResult parseVersionResponse(String json, Version currentVersion, String projectId) { + try { + JSONArray versions = new JSONArray(json); - private Optional extractJsonValue(String json, String key) { - if (json == null || key == null) { - return Optional.empty(); - } + if (versions.isEmpty()) { + return createEmptyResult(currentVersion); + } - String pattern = "\"" + key + "\":\""; - int start = json.indexOf(pattern); + JSONObject latestVersionObj = versions.getJSONObject(0); - if (start == -1) { - return Optional.empty(); - } + String versionNumber = latestVersionObj.optString("version_number", null); + if (versionNumber == null || versionNumber.trim().isEmpty()) { + return createEmptyResult(currentVersion); + } - start += pattern.length(); - int end = json.indexOf("\"", start); + String downloadUrl = null; + if (latestVersionObj.has("files")) { + JSONArray files = latestVersionObj.getJSONArray("files"); + if (!files.isEmpty()) { + JSONObject firstFile = files.getJSONObject(0); + downloadUrl = firstFile.optString("url", null); + } + } - if (end == -1) { - return Optional.empty(); + String releaseUrl = MODRINTH_BASE_URL + "/" + projectId + "/version/" + versionNumber; + Version latestVersion = new Version(versionNumber); + + return new UpdateResult(currentVersion, latestVersion, downloadUrl, releaseUrl); + } + catch (JSONException exception) { + return createEmptyResult(currentVersion); } + } - String value = json.substring(start, end); - return value.isEmpty() ? Optional.empty() : Optional.of(value); + private UpdateResult createEmptyResult(Version currentVersion) { + return new UpdateResult(currentVersion, currentVersion, null, null); } } From 7abfca4f4a954142e4cf0faaa9e25173df40fbac Mon Sep 17 00:00:00 2001 From: vLuckyyy Date: Sun, 3 Aug 2025 19:50:53 +0200 Subject: [PATCH 4/6] Fix. --- .../eternalcode/commons/updater/impl/ModrinthUpdateChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java index 46aa589..87a0249 100644 --- a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java @@ -16,7 +16,7 @@ public final class ModrinthUpdateChecker implements UpdateChecker { private static final String API_BASE_URL = "https://api.modrinth.com/v2"; - private static final String MODRINTH_BASE_URL = "https://modrinth.com/mod"; + private static final String MODRINTH_BASE_URL = "https://modrinth.com/plugin"; private static final String USER_AGENT = "UpdateChecker/1.0"; private static final Duration TIMEOUT = Duration.ofSeconds(10); From 0e7c8155e8d180a72d205b68c8690b3b0e8144c1 Mon Sep 17 00:00:00 2001 From: vLuckyyy Date: Mon, 11 Aug 2025 20:13:33 +0200 Subject: [PATCH 5/6] Fix. --- eternalcode-commons-updater/build.gradle.kts | 8 ++ .../eternalcode/commons/updater/Version.java | 73 ++++++++++++++++--- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/eternalcode-commons-updater/build.gradle.kts b/eternalcode-commons-updater/build.gradle.kts index 58e8991..07d0c47 100644 --- a/eternalcode-commons-updater/build.gradle.kts +++ b/eternalcode-commons-updater/build.gradle.kts @@ -2,9 +2,17 @@ plugins { `commons-java-17` `commons-publish` `commons-repositories` + `commons-java-unit-test` } +tasks.test { + useJUnitPlatform() +} + + dependencies { api("org.json:json:20240303") api(project(":eternalcode-commons-shared")) + + api("org.jetbrains:annotations:24.1.0") } diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java index b1ca27c..c769d36 100644 --- a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java @@ -1,34 +1,89 @@ package com.eternalcode.commons.updater; -public class Version implements Comparable { +import org.jetbrains.annotations.NotNull; +public class Version implements Comparable { private final String value; + private final int[] parts; public Version(String version) { + if (version == null || version.trim().isEmpty()) { + throw new IllegalArgumentException("Version cannot be null or empty"); + } + this.value = version.trim(); + this.parts = parseVersion(this.value); + } + + private int[] parseVersion(String version) { + String cleaned = version.startsWith("v") ? version.substring(1) : version; + + int dashIndex = cleaned.indexOf('-'); + if (dashIndex > 0) { + cleaned = cleaned.substring(0, dashIndex); + } + + String[] stringParts = cleaned.split("\\."); + int[] intParts = new int[stringParts.length]; + + for (int i = 0; i < stringParts.length; i++) { + try { + intParts[i] = Integer.parseInt(stringParts[i]); + } + catch (NumberFormatException exception) { + throw new IllegalArgumentException("Invalid version format: " + version); + } + } + + return intParts; } @Override - public int compareTo(Version version) { - return this.value.compareTo(version.value); + public int compareTo(@NotNull Version other) { + if (other == null) { + return 1; + } + + int maxLength = Math.max(this.parts.length, other.parts.length); + + for (int i = 0; i < maxLength; i++) { + int thisPart = i < this.parts.length ? this.parts[i] : 0; + int otherPart = i < other.parts.length ? other.parts[i] : 0; + + int result = Integer.compare(thisPart, otherPart); + if (result != 0) { + return result; + } + } + + return 0; } - public boolean isNewerThan(Version version) { - return this.value.compareTo(version.value) > 0; + public boolean isNewerThan(Version other) { + return this.compareTo(other) > 0; } @Override - public boolean equals(Object object) { - return object instanceof Version other && value.equals(other.value); + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + Version version = (Version) obj; + return this.compareTo(version) == 0; } @Override public int hashCode() { - return value.hashCode(); + return java.util.Arrays.hashCode(parts); } @Override public String toString() { - return this.value; + return value; } } From a828e2317e7018ec74e05c080d9175e13e80670a Mon Sep 17 00:00:00 2001 From: Rollczi Date: Mon, 11 Aug 2025 21:12:10 +0200 Subject: [PATCH 6/6] Cleanup and use gson --- eternalcode-commons-updater/build.gradle.kts | 2 +- .../commons/updater/UpdateResult.java | 5 ++ .../eternalcode/commons/updater/Version.java | 56 +++++++------ .../updater/impl/ModrinthUpdateChecker.java | 80 +++++++++---------- 4 files changed, 77 insertions(+), 66 deletions(-) diff --git a/eternalcode-commons-updater/build.gradle.kts b/eternalcode-commons-updater/build.gradle.kts index 07d0c47..bc67ab6 100644 --- a/eternalcode-commons-updater/build.gradle.kts +++ b/eternalcode-commons-updater/build.gradle.kts @@ -11,7 +11,7 @@ tasks.test { dependencies { - api("org.json:json:20240303") + api("com.google.code.gson:gson:2.13.1") api(project(":eternalcode-commons-shared")) api("org.jetbrains:annotations:24.1.0") diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateResult.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateResult.java index 48e9a6f..ea8d4e1 100644 --- a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateResult.java +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/UpdateResult.java @@ -2,7 +2,12 @@ public record UpdateResult(Version currentVersion, Version latestVersion, String downloadUrl, String releaseUrl) { + public static UpdateResult empty(Version currentVersion) { + return new UpdateResult(currentVersion, currentVersion, null, null); + } + public boolean isUpdateAvailable() { return this.latestVersion.isNewerThan(this.currentVersion); } + } diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java index c769d36..0215db7 100644 --- a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/Version.java @@ -1,10 +1,14 @@ package com.eternalcode.commons.updater; +import java.util.Arrays; import org.jetbrains.annotations.NotNull; public class Version implements Comparable { + + private static final int DEFAULT_VERSION_COMPONENT_VALUE = 0; + private final String value; - private final int[] parts; + private final int[] versionComponents; public Version(String version) { if (version == null || version.trim().isEmpty()) { @@ -12,45 +16,45 @@ public Version(String version) { } this.value = version.trim(); - this.parts = parseVersion(this.value); + this.versionComponents = parseVersion(this.value); } private int[] parseVersion(String version) { - String cleaned = version.startsWith("v") ? version.substring(1) : version; - - int dashIndex = cleaned.indexOf('-'); - if (dashIndex > 0) { - cleaned = cleaned.substring(0, dashIndex); - } - - String[] stringParts = cleaned.split("\\."); - int[] intParts = new int[stringParts.length]; + String cleaned = cleanVersion(version); + String[] rawVersionComponents = cleaned.split("\\."); + int[] versionComponents = new int[rawVersionComponents.length]; - for (int i = 0; i < stringParts.length; i++) { + for (int i = 0; i < rawVersionComponents.length; i++) { try { - intParts[i] = Integer.parseInt(stringParts[i]); + versionComponents[i] = Integer.parseInt(rawVersionComponents[i]); } catch (NumberFormatException exception) { throw new IllegalArgumentException("Invalid version format: " + version); } } - return intParts; + return versionComponents; } - @Override - public int compareTo(@NotNull Version other) { - if (other == null) { - return 1; + private static String cleanVersion(String version) { + String cleaned = version.startsWith("v") ? version.substring(1) : version; + int dashIndex = cleaned.indexOf('-'); + if (dashIndex > 0) { + return cleaned.substring(0, dashIndex); } - int maxLength = Math.max(this.parts.length, other.parts.length); + return cleaned; + } + + @Override + public int compareTo(@NotNull Version other) { + int maxLength = Math.max(this.versionComponents.length, other.versionComponents.length); for (int i = 0; i < maxLength; i++) { - int thisPart = i < this.parts.length ? this.parts[i] : 0; - int otherPart = i < other.parts.length ? other.parts[i] : 0; + int thisComponent = getComponentAtIndex(i, this); + int otherComponent = getComponentAtIndex(i, other); - int result = Integer.compare(thisPart, otherPart); + int result = Integer.compare(thisComponent, otherComponent); if (result != 0) { return result; } @@ -59,6 +63,12 @@ public int compareTo(@NotNull Version other) { return 0; } + private int getComponentAtIndex(int index, Version version) { + return index < version.versionComponents.length + ? version.versionComponents[index] + : DEFAULT_VERSION_COMPONENT_VALUE; + } + public boolean isNewerThan(Version other) { return this.compareTo(other) > 0; } @@ -79,7 +89,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return java.util.Arrays.hashCode(parts); + return Arrays.hashCode(versionComponents); } @Override diff --git a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java index 87a0249..f875259 100644 --- a/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java +++ b/eternalcode-commons-updater/src/main/java/com/eternalcode/commons/updater/impl/ModrinthUpdateChecker.java @@ -1,51 +1,51 @@ package com.eternalcode.commons.updater.impl; -import com.eternalcode.commons.Lazy; import com.eternalcode.commons.updater.UpdateChecker; import com.eternalcode.commons.updater.UpdateResult; import com.eternalcode.commons.updater.Version; +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import java.util.List; +import java.util.Objects; public final class ModrinthUpdateChecker implements UpdateChecker { private static final String API_BASE_URL = "https://api.modrinth.com/v2"; private static final String MODRINTH_BASE_URL = "https://modrinth.com/plugin"; private static final String USER_AGENT = "UpdateChecker/1.0"; - private static final Duration TIMEOUT = Duration.ofSeconds(10); - private final Lazy client = new Lazy<>(() -> HttpClient.newBuilder().connectTimeout(TIMEOUT).build()); + private static final Gson GSON = new Gson(); + + private final HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(60)) + .build(); @Override public UpdateResult check(String projectId, Version currentVersion) { - if (projectId == null || projectId.trim().isEmpty()) { + if (projectId == null || projectId.isBlank()) { throw new IllegalArgumentException("Project ID cannot be null or empty"); } try { - String url = API_BASE_URL + "/project/" + projectId + "/version"; - - HttpRequest request = - HttpRequest.newBuilder().uri(URI.create(url)).header("User-Agent", USER_AGENT).timeout(TIMEOUT).build(); - - HttpResponse response = this.client.get().send(request, HttpResponse.BodyHandlers.ofString()); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(API_BASE_URL + "/project/" + projectId + "/version")) + .header("User-Agent", USER_AGENT) + .timeout(Duration.ofSeconds(30)) + .build(); + HttpResponse response = this.client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { - return createEmptyResult(currentVersion); + return UpdateResult.empty(currentVersion); } - String json = response.body(); - if (json == null || json.trim().isEmpty()) { - return createEmptyResult(currentVersion); - } - - return parseVersionResponse(json, currentVersion, projectId); + return this.parseVersionResponse(response.body(), currentVersion, projectId); } catch (Exception exception) { throw new RuntimeException("Failed to check Modrinth updates for project: " + projectId, exception); @@ -54,39 +54,35 @@ public UpdateResult check(String projectId, Version currentVersion) { private UpdateResult parseVersionResponse(String json, Version currentVersion, String projectId) { try { - JSONArray versions = new JSONArray(json); - - if (versions.isEmpty()) { - return createEmptyResult(currentVersion); + List versions = GSON.fromJson(json, new TypeToken<>(){}); + if (versions == null || versions.isEmpty()) { + return UpdateResult.empty(currentVersion); } - JSONObject latestVersionObj = versions.getJSONObject(0); - - String versionNumber = latestVersionObj.optString("version_number", null); + ModrinthVersion latestVersionData = versions.get(0); + String versionNumber = latestVersionData.versionNumber(); if (versionNumber == null || versionNumber.trim().isEmpty()) { - return createEmptyResult(currentVersion); - } - - String downloadUrl = null; - if (latestVersionObj.has("files")) { - JSONArray files = latestVersionObj.getJSONArray("files"); - if (!files.isEmpty()) { - JSONObject firstFile = files.getJSONObject(0); - downloadUrl = firstFile.optString("url", null); - } + return UpdateResult.empty(currentVersion); } String releaseUrl = MODRINTH_BASE_URL + "/" + projectId + "/version/" + versionNumber; - Version latestVersion = new Version(versionNumber); + String downloadUrl = latestVersionData.files().stream() + .map(modrinthFile -> modrinthFile.url()) + .filter(obj -> Objects.nonNull(obj)) + .findFirst() + .orElse(releaseUrl); + Version latestVersion = new Version(versionNumber); return new UpdateResult(currentVersion, latestVersion, downloadUrl, releaseUrl); } - catch (JSONException exception) { - return createEmptyResult(currentVersion); + catch (JsonParseException exception) { + return UpdateResult.empty(currentVersion); } } - private UpdateResult createEmptyResult(Version currentVersion) { - return new UpdateResult(currentVersion, currentVersion, null, null); + private record ModrinthVersion(@SerializedName("version_number") String versionNumber, List files) { + } + + private record ModrinthFile(String url) { } }