|
28 | 28 | import com.falsepattern.lib.updates.UpdateCheckException;
|
29 | 29 | import com.google.gson.JsonArray;
|
30 | 30 | import com.google.gson.JsonElement;
|
| 31 | +import com.google.gson.JsonObject; |
31 | 32 | import com.google.gson.JsonParser;
|
32 | 33 | import lombok.val;
|
| 34 | +import lombok.var; |
33 | 35 |
|
34 | 36 | import net.minecraft.client.resources.I18n;
|
35 | 37 | import net.minecraft.event.ClickEvent;
|
36 | 38 | import net.minecraft.util.IChatComponent;
|
37 | 39 | import cpw.mods.fml.common.Loader;
|
38 | 40 |
|
| 41 | +import java.io.InputStreamReader; |
39 | 42 | import java.net.MalformedURLException;
|
40 | 43 | import java.net.URL;
|
41 | 44 | import java.util.ArrayList;
|
42 | 45 | import java.util.Collections;
|
43 | 46 | import java.util.List;
|
44 | 47 | import java.util.concurrent.CompletableFuture;
|
45 | 48 | import java.util.concurrent.CompletionException;
|
46 |
| -import java.util.concurrent.atomic.AtomicBoolean; |
| 49 | +import java.util.concurrent.atomic.AtomicReference; |
47 | 50 |
|
48 | 51 | public final class UpdateCheckerImpl {
|
49 |
| - private static final AtomicBoolean jsonLibraryLoaded = new AtomicBoolean(false); |
50 |
| - |
51 | 52 | public static List<IChatComponent> updateListToChatMessages(String initiator, List<ModUpdateInfo> updates) {
|
52 | 53 | if (updates == null || updates.size() == 0) {
|
53 | 54 | return null;
|
@@ -84,32 +85,37 @@ public static List<IChatComponent> updateListToChatMessages(String initiator, Li
|
84 | 85 | return updateText;
|
85 | 86 | }
|
86 | 87 |
|
| 88 | + private static JsonArray fetchRootListShared(String url) { |
| 89 | + if (!LibraryConfig.ENABLE_UPDATE_CHECKER) { |
| 90 | + throw new CompletionException(new UpdateCheckException("Update checker is disabled in config!")); |
| 91 | + } |
| 92 | + URL URL; |
| 93 | + try { |
| 94 | + URL = new URL(url); |
| 95 | + } catch (MalformedURLException e) { |
| 96 | + throw new CompletionException(new UpdateCheckException("Invalid URL: " + url, e)); |
| 97 | + } |
| 98 | + JsonElement parsed; |
| 99 | + try { |
| 100 | + parsed = new JsonParser().parse(Internet.download(URL).thenApply(String::new).join()); |
| 101 | + } catch (CompletionException e) { |
| 102 | + throw new CompletionException(new UpdateCheckException("Failed to download update checker JSON file!", |
| 103 | + e.getCause() == null ? e : e.getCause())); |
| 104 | + } |
| 105 | + JsonArray modList; |
| 106 | + if (parsed.isJsonArray()) { |
| 107 | + modList = parsed.getAsJsonArray(); |
| 108 | + } else { |
| 109 | + modList = new JsonArray(); |
| 110 | + modList.add(parsed); |
| 111 | + } |
| 112 | + return modList; |
| 113 | + } |
| 114 | + |
87 | 115 | public static CompletableFuture<List<ModUpdateInfo>> fetchUpdatesAsync(String url) {
|
88 | 116 | return CompletableFuture.supplyAsync(() -> {
|
89 |
| - if (!LibraryConfig.ENABLE_UPDATE_CHECKER) { |
90 |
| - throw new CompletionException(new UpdateCheckException("Update checker is disabled in config!")); |
91 |
| - } |
92 |
| - URL URL; |
93 |
| - try { |
94 |
| - URL = new URL(url); |
95 |
| - } catch (MalformedURLException e) { |
96 |
| - throw new CompletionException(new UpdateCheckException("Invalid URL: " + url, e)); |
97 |
| - } |
| 117 | + val modList = fetchRootListShared(url); |
98 | 118 | val result = new ArrayList<ModUpdateInfo>();
|
99 |
| - JsonElement parsed; |
100 |
| - try { |
101 |
| - parsed = new JsonParser().parse(Internet.download(URL).thenApply(String::new).join()); |
102 |
| - } catch (CompletionException e) { |
103 |
| - throw new CompletionException(new UpdateCheckException("Failed to download update checker JSON file!", |
104 |
| - e.getCause() == null ? e : e.getCause())); |
105 |
| - } |
106 |
| - JsonArray modList; |
107 |
| - if (parsed.isJsonArray()) { |
108 |
| - modList = parsed.getAsJsonArray(); |
109 |
| - } else { |
110 |
| - modList = new JsonArray(); |
111 |
| - modList.add(parsed); |
112 |
| - } |
113 | 119 | val installedMods = Loader.instance().getIndexedModList();
|
114 | 120 | for (val node : modList) {
|
115 | 121 | if (!node.isJsonObject()) {
|
@@ -170,4 +176,159 @@ public static List<ModUpdateInfo> fetchUpdates(String url) throws UpdateCheckExc
|
170 | 176 | }
|
171 | 177 | }
|
172 | 178 | }
|
| 179 | + |
| 180 | + public static CompletableFuture<List<ModUpdateInfo>> fetchUpdatesAsyncV2(String url) { |
| 181 | + return CompletableFuture.supplyAsync(() -> { |
| 182 | + val modList = fetchRootListShared(url); |
| 183 | + val result = new ArrayList<ModUpdateInfo>(); |
| 184 | + val installedMods = Loader.instance().getIndexedModList(); |
| 185 | + for (val node : modList) { |
| 186 | + if (!node.isJsonObject()) { |
| 187 | + continue; |
| 188 | + } |
| 189 | + val obj = node.getAsJsonObject(); |
| 190 | + if (!obj.has("modid")) { |
| 191 | + continue; |
| 192 | + } |
| 193 | + if (!obj.has("versions")) { |
| 194 | + continue; |
| 195 | + } |
| 196 | + val modid = obj.get("modid").getAsString(); |
| 197 | + if (!installedMods.containsKey(modid)) { |
| 198 | + continue; |
| 199 | + } |
| 200 | + val mod = installedMods.get(modid); |
| 201 | + var versions = obj.get("versions"); |
| 202 | + if (!versions.isJsonArray()) { |
| 203 | + if (!versions.isJsonObject()) { |
| 204 | + continue; |
| 205 | + } |
| 206 | + val ver = versions.getAsJsonObject(); |
| 207 | + versions = new JsonArray(); |
| 208 | + versions.getAsJsonArray().add(ver); |
| 209 | + } |
| 210 | + val parsedVersions = parseVersionSpecs(versions.getAsJsonArray()).join(); |
| 211 | + val latestVersions = new ArrayList<String>(); |
| 212 | + for (val version : parsedVersions) { |
| 213 | + val versionObj = version.getAsJsonObject(); |
| 214 | + latestVersions.add(versionObj.get("version").getAsString()); |
| 215 | + } |
| 216 | + val currentVersion = mod.getVersion(); |
| 217 | + if (latestVersions.contains(currentVersion)) { |
| 218 | + continue; |
| 219 | + } |
| 220 | + val latest = parsedVersions.get(0).getAsJsonObject(); |
| 221 | + result.add(new ModUpdateInfo(modid, currentVersion, latest.get("version").getAsString(), latest.get("url").getAsString())); |
| 222 | + } |
| 223 | + return result; |
| 224 | + }); |
| 225 | + } |
| 226 | + |
| 227 | + private static String getString(JsonObject object, String entry) { |
| 228 | + if (!object.has(entry)) { |
| 229 | + return null; |
| 230 | + } |
| 231 | + val element = object.get(entry); |
| 232 | + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { |
| 233 | + return element.getAsString(); |
| 234 | + } |
| 235 | + return null; |
| 236 | + } |
| 237 | + |
| 238 | + private static CompletableFuture<JsonArray> parseVersionSpecs(JsonArray versions) { |
| 239 | + return CompletableFuture.supplyAsync(() -> { |
| 240 | + val result = new JsonArray(); |
| 241 | + for (val entry: versions) { |
| 242 | + if (!entry.isJsonObject()) { |
| 243 | + continue; |
| 244 | + } |
| 245 | + val obj = entry.getAsJsonObject(); |
| 246 | + val type = getString(obj, "type"); |
| 247 | + if (type == null) { |
| 248 | + continue; |
| 249 | + } |
| 250 | + switch (type) { |
| 251 | + case "raw": { |
| 252 | + val version = getString(obj, "version"); |
| 253 | + val url = getString(obj, "updateURL"); |
| 254 | + if (version == null || url == null) { |
| 255 | + continue; |
| 256 | + } |
| 257 | + val res = new JsonObject(); |
| 258 | + res.addProperty("version", version); |
| 259 | + res.addProperty("url", url); |
| 260 | + result.add(res); |
| 261 | + } |
| 262 | + case "github": { |
| 263 | + val repo = getString(obj, "repo"); |
| 264 | + if (repo == null) { |
| 265 | + continue; |
| 266 | + } |
| 267 | + JsonObject response; |
| 268 | + try { |
| 269 | + response = parseLatestGithubVersion(repo); |
| 270 | + } catch (MalformedURLException e) { |
| 271 | + e.printStackTrace(); |
| 272 | + continue; |
| 273 | + } |
| 274 | + if (response == null) { |
| 275 | + continue; |
| 276 | + } |
| 277 | + result.add(response); |
| 278 | + } |
| 279 | + } |
| 280 | + } |
| 281 | + return result; |
| 282 | + }); |
| 283 | + } |
| 284 | + |
| 285 | + private static JsonObject parseLatestGithubVersion(String repo) throws MalformedURLException { |
| 286 | + val parts = repo.split("/"); |
| 287 | + if (parts.length != 2) { |
| 288 | + return null; |
| 289 | + } |
| 290 | + val owner = parts[0]; |
| 291 | + val name = parts[1]; |
| 292 | + val response = new AtomicReference<JsonObject>(); |
| 293 | + val result = new JsonObject(); |
| 294 | + Internet.connect(new URL("https://api.github.com/repos/" + owner + "/" + name + "/releases/latest"), |
| 295 | + Internet.constructHeaders("Accept", "application/vnd.github+json", |
| 296 | + "X-GitHub-Api-Version", "2022-11-28"), |
| 297 | + (e) -> {throw new CompletionException(e); |
| 298 | + }, |
| 299 | + (stream) -> { |
| 300 | + val parser = new JsonParser(); |
| 301 | + val json = parser.parse(new InputStreamReader(stream)); |
| 302 | + if (!json.isJsonObject()) { |
| 303 | + return; |
| 304 | + } |
| 305 | + response.set(json.getAsJsonObject()); |
| 306 | + }); |
| 307 | + if (response.get() == null) { |
| 308 | + return null; |
| 309 | + } |
| 310 | + val payload = response.get(); |
| 311 | + val url = getString(payload, "html_url"); |
| 312 | + val version = getString(payload, "tag_name"); |
| 313 | + if (version == null) { |
| 314 | + return null; |
| 315 | + } |
| 316 | + result.addProperty("version", version); |
| 317 | + result.addProperty("url", url == null ? "" : url); |
| 318 | + return result; |
| 319 | + } |
| 320 | + |
| 321 | + public static List<ModUpdateInfo> fetchUpdatesV2(String url) throws UpdateCheckException { |
| 322 | + try { |
| 323 | + return fetchUpdatesAsyncV2(url).join(); |
| 324 | + } catch (CompletionException e) { |
| 325 | + try { |
| 326 | + throw e.getCause(); |
| 327 | + } catch (UpdateCheckException e1) { |
| 328 | + throw e1; |
| 329 | + } catch (Throwable e1) { |
| 330 | + throw new UpdateCheckException("Failed to check for updates!", e1); |
| 331 | + } |
| 332 | + } |
| 333 | + } |
173 | 334 | }
|
0 commit comments