From 92300e1f5f658b77782b5b88de9f8aea92ca6f06 Mon Sep 17 00:00:00 2001 From: Up Date: Sun, 16 Feb 2025 11:54:54 +0100 Subject: [PATCH] add file sizes and hashes (closes #5) --- ...HashedMojmapAndGameVersionCollectorV3.java | 9 +- .../v3/LauncherProfileCollectorV3.java | 9 +- .../v3/QuiltInstallerVersionCollectorV3.java | 11 ++- .../v3/QuiltLauncherMetadataCollectorV3.java | 5 +- .../v3/QuiltLoaderVersionCollectorV3.java | 12 +-- .../v3/QuiltMappingsCollectorV3.java | 30 ++++--- .../v3/RootVersionListCollectorV3.java | 10 +-- .../collector/v3/StaticFileCollectorV3.java | 4 +- .../model/v3/HashedMojmapVersionV3.java | 6 +- .../model/v3/IntermediaryVersionV3.java | 3 + .../model/v3/QuiltInstallerVersionV3.java | 6 +- .../update/model/v3/QuiltLoaderVersionV3.java | 6 +- .../model/v3/QuiltMappingsVersionV3.java | 6 +- .../util/function/ThrowingConsumer.java | 7 ++ .../update/util/maven/MavenRepository.java | 83 ++++++++++++------- 15 files changed, 135 insertions(+), 72 deletions(-) create mode 100644 src/main/java/org/quiltmc/meta/update/util/function/ThrowingConsumer.java diff --git a/src/main/java/org/quiltmc/meta/update/collector/v3/HashedMojmapAndGameVersionCollectorV3.java b/src/main/java/org/quiltmc/meta/update/collector/v3/HashedMojmapAndGameVersionCollectorV3.java index 4cfb160..d4599c0 100644 --- a/src/main/java/org/quiltmc/meta/update/collector/v3/HashedMojmapAndGameVersionCollectorV3.java +++ b/src/main/java/org/quiltmc/meta/update/collector/v3/HashedMojmapAndGameVersionCollectorV3.java @@ -32,7 +32,7 @@ public String getName() { @Override public CompletableFuture gatherData(BiConsumer output, Executor executor) { - return CompletableFuture.supplyAsync(() -> getVersions(maven), executor).thenAcceptAsync(hashed -> { + return getVersions(maven, executor).thenAcceptAsync(hashed -> { var minecraftMeta = MinecraftMeta.get(Constants.GSON); hashed.forEach((gameVersion, version) -> { @@ -55,8 +55,11 @@ public CompletableFuture gatherData(BiConsumer output, E }, executor); } - public static Map getVersions(MavenRepository maven) { - return maven.getMetadata(Constants.QUILT_MAVEN_GROUP, "hashed").stream().map(artifact -> new HashedMojmapVersionV3(artifact.mavenId(), artifact.version)).collect(Collectors.toMap(HashedMojmapVersionV3::version, Function.identity())); + public static CompletableFuture> getVersions(MavenRepository maven, Executor executor) { + return CompletableFuture.supplyAsync(() -> maven.getMetadata(Constants.QUILT_MAVEN_GROUP, "hashed"), executor).thenApply(metadata -> metadata.stream().map(artifact -> { + var fileInfo = artifact.fileInfo(executor).join(); + return new HashedMojmapVersionV3(artifact.mavenId(), artifact.version, fileInfo.fileSize(), fileInfo.formatHashesForJson()); + }).collect(Collectors.toMap(HashedMojmapVersionV3::version, Function.identity()))); } public static List getGameVersions(MinecraftMeta minecraftMeta, Predicate isValidVersion) { diff --git a/src/main/java/org/quiltmc/meta/update/collector/v3/LauncherProfileCollectorV3.java b/src/main/java/org/quiltmc/meta/update/collector/v3/LauncherProfileCollectorV3.java index 887ac5b..196880b 100644 --- a/src/main/java/org/quiltmc/meta/update/collector/v3/LauncherProfileCollectorV3.java +++ b/src/main/java/org/quiltmc/meta/update/collector/v3/LauncherProfileCollectorV3.java @@ -38,7 +38,7 @@ public String getName() { public CompletableFuture gatherData(BiConsumer output, Executor executor) { var time = Instant.now(); - return QuiltLauncherMetadataCollectorV3.getVersions(quiltMaven, fabricMaven, executor).thenAcceptAsync(gameLauncherVersions -> { + return QuiltLauncherMetadataCollectorV3.getVersions(quiltMaven, fabricMaven, executor).thenAccept(gameLauncherVersions -> { gameLauncherVersions.forEach((gameVersion, launcherMetadata) -> { var hashed = new LauncherProfileV3.Library(launcherMetadata.hashed().maven(), quiltMaven.url().toString()); var intermediary = new LauncherProfileV3.Library(launcherMetadata.intermediary().maven(), fabricMaven.url().toString()); @@ -50,12 +50,13 @@ public CompletableFuture gatherData(BiConsumer output, E var mainClass = Optional.ofNullable(launcherMetadata.launcherMeta().mainClass()).map(map -> map.get(profileType.getName())).orElse(null); var serverLauncherMainClass = mainClass != null ? launcherMetadata.launcherMeta().mainClass().get("serverLauncher") : null; + var profileId = "quilt-loader-%s-%s".formatted(launcherMetadata.loader().version(), gameVersion); // need to compute the hash with constant timestamps, otherwise all profiles would be constantly flagged as needing updates - var profileForCache = profileType.create("quilt-loader-%s-%s".formatted(launcherMetadata.loader().version(), gameVersion), gameVersion, LauncherProfileV3.TYPE_RELEASE, mainClass, serverLauncherMainClass, Map.of("game", List.of()), libraries, Instant.EPOCH, Instant.EPOCH); + var profileForCache = profileType.create(profileId, gameVersion, LauncherProfileV3.TYPE_RELEASE, mainClass, serverLauncherMainClass, Map.of("game", List.of()), libraries, Instant.EPOCH, Instant.EPOCH); var hash = HashFunction.SHA1.createHash(Constants.GSON.toJson(profileForCache).getBytes(StandardCharsets.UTF_8)); - var profile = profileType.create("quilt-loader-%s-%s".formatted(launcherMetadata.loader().version(), gameVersion), gameVersion, LauncherProfileV3.TYPE_RELEASE, mainClass, serverLauncherMainClass, Map.of("game", List.of()), libraries, time, time); + var profile = profileType.create(profileId, gameVersion, LauncherProfileV3.TYPE_RELEASE, mainClass, serverLauncherMainClass, Map.of("game", List.of()), libraries, time, time); var profileBytes = Constants.GSON.toJson(profile).getBytes(StandardCharsets.UTF_8); //TODO change path in v4: profiles///client + profiles///server @@ -64,7 +65,7 @@ public CompletableFuture gatherData(BiConsumer output, E }); LOGGER.info("Generated {} launch profiles", gameLauncherVersions.size() * ProfileType.values().length); - }, executor); + }); } private enum ProfileType { diff --git a/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltInstallerVersionCollectorV3.java b/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltInstallerVersionCollectorV3.java index f29d664..9b4fa35 100644 --- a/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltInstallerVersionCollectorV3.java +++ b/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltInstallerVersionCollectorV3.java @@ -26,14 +26,17 @@ public String getName() { @Override public CompletableFuture gatherData(BiConsumer output, Executor executor) { - return CompletableFuture.supplyAsync(() -> getVersions(quiltMaven), executor).thenAcceptAsync(versions -> { + return getVersions(quiltMaven, executor).thenAccept(versions -> { output.accept("v3/versions/installer", FileInfo.jsonList(versions)); LOGGER.info("Found {} installer versions", versions.size()); - }, executor); + }); } - public static List getVersions(MavenRepository maven) { - return maven.getMetadata(Constants.QUILT_MAVEN_GROUP, "quilt-installer").stream().map(artifact -> new QuiltInstallerVersionV3(artifact.mavenId(), artifact.version, artifact.url().toString())).toList(); + public static CompletableFuture> getVersions(MavenRepository maven, Executor executor) { + return CompletableFuture.supplyAsync(() -> maven.getMetadata(Constants.QUILT_MAVEN_GROUP, "quilt-installer"), executor).thenApply(mavenMetadata -> mavenMetadata.stream().map(artifact -> { + var fileInfo = artifact.fileInfo(executor).join(); + return new QuiltInstallerVersionV3(artifact.mavenId(), artifact.version, artifact.url().toString(), fileInfo.fileSize(), fileInfo.formatHashesForJson()); + }).toList()); } } diff --git a/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltLauncherMetadataCollectorV3.java b/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltLauncherMetadataCollectorV3.java index 10fb3d1..e8de75d 100644 --- a/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltLauncherMetadataCollectorV3.java +++ b/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltLauncherMetadataCollectorV3.java @@ -60,6 +60,7 @@ public static CompletableFuture> getVe var artifactUrl = artifact.url().toString(); var metaUrl = URI.create(artifactUrl.substring(0, artifactUrl.lastIndexOf('.')) + ".json"); + // TODO use httpclient try (var reader = new InputStreamReader(metaUrl.toURL().openStream())) { var loaderMetadata = Constants.GSON.fromJson(reader, QuiltLoaderMetadataJson.class); return Pair.of(artifact.version, loaderMetadata); @@ -67,8 +68,8 @@ public static CompletableFuture> getVe throw new UncheckedIOException("Connection error: " + metaUrl, e); } }, executor)).toList()).thenApplyAsync(futures -> { - Map loaderVersions = QuiltLoaderVersionCollectorV3.getVersions(quiltMaven).stream().collect(Collectors.toMap(QuiltLoaderVersionV3::version, Function.identity())); - Map hashedVersions = HashedMojmapAndGameVersionCollectorV3.getVersions(quiltMaven); + Map loaderVersions = QuiltLoaderVersionCollectorV3.getVersions(quiltMaven, executor).join().stream().collect(Collectors.toMap(QuiltLoaderVersionV3::version, Function.identity())); + Map hashedVersions = HashedMojmapAndGameVersionCollectorV3.getVersions(quiltMaven, executor).join(); Map intermediaryVersions = IntermediaryVersionCollectorV3.getVersions(fabricMaven); CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join(); diff --git a/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltLoaderVersionCollectorV3.java b/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltLoaderVersionCollectorV3.java index 33e2f95..6055e53 100644 --- a/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltLoaderVersionCollectorV3.java +++ b/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltLoaderVersionCollectorV3.java @@ -26,20 +26,22 @@ public String getName() { @Override public CompletableFuture gatherData(BiConsumer output, Executor executor) { - return CompletableFuture.supplyAsync(() -> getVersions(quiltMaven), executor).thenAccept(versions -> { + return getVersions(quiltMaven, executor).thenAccept(versions -> { output.accept("v3/versions/loader", FileInfo.jsonList(versions)); LOGGER.info("Found {} quilt loader versions", versions.size()); }); } - public static List getVersions(MavenRepository maven) { - return maven.getMetadata(Constants.QUILT_MAVEN_GROUP, "quilt-loader").stream().map(artifact -> { + public static CompletableFuture> getVersions(MavenRepository maven, Executor executor) { + return CompletableFuture.supplyAsync(() -> maven.getMetadata(Constants.QUILT_MAVEN_GROUP, "quilt-loader"), executor).thenApply(mavenMetadata -> mavenMetadata.stream().map(artifact -> { var loaderVersion = artifact.version; var separator = loaderVersion.contains("+build.") ? "+build." : "."; var buildNumber = Integer.parseInt(loaderVersion.substring(loaderVersion.lastIndexOf(".") + 1)); - return new QuiltLoaderVersionV3(artifact.mavenId(), loaderVersion, buildNumber, separator); - }).toList(); + var fileInfo = artifact.fileInfo(executor).join(); + + return new QuiltLoaderVersionV3(artifact.mavenId(), loaderVersion, buildNumber, separator, fileInfo.fileSize(), fileInfo.formatHashesForJson()); + }).toList()); } } diff --git a/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltMappingsCollectorV3.java b/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltMappingsCollectorV3.java index d4076c9..946ce78 100644 --- a/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltMappingsCollectorV3.java +++ b/src/main/java/org/quiltmc/meta/update/collector/v3/QuiltMappingsCollectorV3.java @@ -27,7 +27,7 @@ public String getName() { @Override public CompletableFuture gatherData(BiConsumer output, Executor executor) { - return CompletableFuture.supplyAsync(() -> getVersions(quiltMaven)).thenAcceptAsync(gameQuiltMappings -> { + return getVersions(quiltMaven, executor).thenAccept(gameQuiltMappings -> { gameQuiltMappings.asMap().forEach((gameVersion, mappingsVersions) -> output.accept("v3/versions/quilt-mappings/" + gameVersion, FileInfo.jsonList(mappingsVersions))); output.accept("v3/versions/quilt-mappings", FileInfo.jsonList(gameQuiltMappings.values())); @@ -36,7 +36,7 @@ public CompletableFuture gatherData(BiConsumer output, E output.accept("v3/versions/game/quilt-mappings", FileInfo.jsonList(gameQuiltMappings.keys())); LOGGER.info("Found {} quilt mappings", gameQuiltMappings.size()); - }, executor); + }); } private static String stripInfo(String version) { @@ -49,18 +49,22 @@ private static String stripInfo(String version) { } } - public static Multimap getVersions(MavenRepository maven) { - Multimap gameQuiltMappings = MultimapBuilder.linkedHashKeys().hashSetValues().build(); - maven.getMetadata(Constants.QUILT_MAVEN_GROUP, "quilt-mappings").forEach(artifact -> { - var mappingsVersion = artifact.version; - var gameVersion = stripInfo(mappingsVersion); - var separator = mappingsVersion.contains("+build.") ? "+build." : "."; - var buildNumber = Integer.parseInt(mappingsVersion.substring(mappingsVersion.lastIndexOf(".") + 1)); + public static CompletableFuture> getVersions(MavenRepository maven, Executor executor) { + return CompletableFuture.supplyAsync(() -> maven.getMetadata(Constants.QUILT_MAVEN_GROUP, "quilt-mappings"), executor).thenApply(mavenMetadata -> { + Multimap gameQuiltMappings = MultimapBuilder.linkedHashKeys().hashSetValues().build(); + mavenMetadata.forEach(artifact -> { + var mappingsVersion = artifact.version; + var gameVersion = stripInfo(mappingsVersion); + var separator = mappingsVersion.contains("+build.") ? "+build." : "."; + var buildNumber = Integer.parseInt(mappingsVersion.substring(mappingsVersion.lastIndexOf(".") + 1)); - var version = new QuiltMappingsVersionV3(artifact.mavenId(), mappingsVersion, gameVersion, buildNumber, separator, gameVersion); - gameQuiltMappings.put(gameVersion, version); - }); + var fileInfo = artifact.fileInfo(executor).join(); - return gameQuiltMappings; + var version = new QuiltMappingsVersionV3(artifact.mavenId(), mappingsVersion, gameVersion, buildNumber, separator, gameVersion, fileInfo.fileSize(), fileInfo.formatHashesForJson()); + gameQuiltMappings.put(gameVersion, version); + }); + + return gameQuiltMappings; + }); } } diff --git a/src/main/java/org/quiltmc/meta/update/collector/v3/RootVersionListCollectorV3.java b/src/main/java/org/quiltmc/meta/update/collector/v3/RootVersionListCollectorV3.java index ec73ab5..572b560 100644 --- a/src/main/java/org/quiltmc/meta/update/collector/v3/RootVersionListCollectorV3.java +++ b/src/main/java/org/quiltmc/meta/update/collector/v3/RootVersionListCollectorV3.java @@ -31,16 +31,16 @@ public String getName() { @Override public CompletableFuture gatherData(BiConsumer output, Executor executor) { return CompletableFuture.runAsync(() -> { - Map hashed = HashedMojmapAndGameVersionCollectorV3.getVersions(quiltMaven); + Map hashed = HashedMojmapAndGameVersionCollectorV3.getVersions(quiltMaven, executor).join(); List game = HashedMojmapAndGameVersionCollectorV3.getGameVersions(MinecraftMeta.get(Constants.GSON), hashed::containsKey); - Multimap mappings = QuiltMappingsCollectorV3.getVersions(quiltMaven); - List loader = QuiltLoaderVersionCollectorV3.getVersions(quiltMaven); - List installer = QuiltInstallerVersionCollectorV3.getVersions(quiltMaven); + Multimap mappings = QuiltMappingsCollectorV3.getVersions(quiltMaven, executor).join(); + List loader = QuiltLoaderVersionCollectorV3.getVersions(quiltMaven, executor).join(); + List installer = QuiltInstallerVersionCollectorV3.getVersions(quiltMaven, executor).join(); var versionList = new VersionListV3(game, mappings.values(), hashed.values(), loader, installer); output.accept("v3/versions", FileInfo.json(versionList)); LOGGER.info("Generated root version list"); - }); + }, executor); } } diff --git a/src/main/java/org/quiltmc/meta/update/collector/v3/StaticFileCollectorV3.java b/src/main/java/org/quiltmc/meta/update/collector/v3/StaticFileCollectorV3.java index 4cb4cd7..8a64382 100644 --- a/src/main/java/org/quiltmc/meta/update/collector/v3/StaticFileCollectorV3.java +++ b/src/main/java/org/quiltmc/meta/update/collector/v3/StaticFileCollectorV3.java @@ -1,9 +1,9 @@ package org.quiltmc.meta.update.collector.v3; -import org.quiltmc.meta.update.util.http.ContentType; import org.quiltmc.meta.update.upload.FileInfo; import org.quiltmc.meta.update.upload.InputCollector; import org.quiltmc.meta.update.util.Pair; +import org.quiltmc.meta.update.util.http.ContentType; import java.io.FileNotFoundException; import java.io.IOException; @@ -36,7 +36,7 @@ public CompletableFuture gatherData(BiConsumer output, E return CompletableFuture.runAsync(() -> { this.fileInfos.forEach((apiPath, pair) -> { try (var stream = StaticFileCollectorV3.class.getClassLoader().getResourceAsStream("static/" + pair.first())) { - if(stream == null) { + if (stream == null) { throw new FileNotFoundException("static://" + pair.first()); } diff --git a/src/main/java/org/quiltmc/meta/update/model/v3/HashedMojmapVersionV3.java b/src/main/java/org/quiltmc/meta/update/model/v3/HashedMojmapVersionV3.java index 62afb78..928e87d 100644 --- a/src/main/java/org/quiltmc/meta/update/model/v3/HashedMojmapVersionV3.java +++ b/src/main/java/org/quiltmc/meta/update/model/v3/HashedMojmapVersionV3.java @@ -1,4 +1,8 @@ package org.quiltmc.meta.update.model.v3; -public record HashedMojmapVersionV3(String maven, String version) { +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + +public record HashedMojmapVersionV3(String maven, String version, @SerializedName("file_size") long fileSize, Map hashes) { } diff --git a/src/main/java/org/quiltmc/meta/update/model/v3/IntermediaryVersionV3.java b/src/main/java/org/quiltmc/meta/update/model/v3/IntermediaryVersionV3.java index ca0294d..f7ef7f0 100644 --- a/src/main/java/org/quiltmc/meta/update/model/v3/IntermediaryVersionV3.java +++ b/src/main/java/org/quiltmc/meta/update/model/v3/IntermediaryVersionV3.java @@ -1,4 +1,7 @@ package org.quiltmc.meta.update.model.v3; +/** + * No hashes provided here because fabric likes to re-publish intermediary artifacts! + */ public record IntermediaryVersionV3(String maven, String version) { } diff --git a/src/main/java/org/quiltmc/meta/update/model/v3/QuiltInstallerVersionV3.java b/src/main/java/org/quiltmc/meta/update/model/v3/QuiltInstallerVersionV3.java index 94cf54e..8d00a75 100644 --- a/src/main/java/org/quiltmc/meta/update/model/v3/QuiltInstallerVersionV3.java +++ b/src/main/java/org/quiltmc/meta/update/model/v3/QuiltInstallerVersionV3.java @@ -1,4 +1,8 @@ package org.quiltmc.meta.update.model.v3; -public record QuiltInstallerVersionV3(String maven, String version, String url) { +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + +public record QuiltInstallerVersionV3(String maven, String version, String url, @SerializedName("file_size") long fileSize, Map hashes) { } diff --git a/src/main/java/org/quiltmc/meta/update/model/v3/QuiltLoaderVersionV3.java b/src/main/java/org/quiltmc/meta/update/model/v3/QuiltLoaderVersionV3.java index 9cdaac5..481d993 100644 --- a/src/main/java/org/quiltmc/meta/update/model/v3/QuiltLoaderVersionV3.java +++ b/src/main/java/org/quiltmc/meta/update/model/v3/QuiltLoaderVersionV3.java @@ -1,4 +1,8 @@ package org.quiltmc.meta.update.model.v3; -public record QuiltLoaderVersionV3(String maven, String version, int build, String separator) { +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + +public record QuiltLoaderVersionV3(String maven, String version, int build, String separator, @SerializedName("file_size") long fileSize, Map hashes) { } diff --git a/src/main/java/org/quiltmc/meta/update/model/v3/QuiltMappingsVersionV3.java b/src/main/java/org/quiltmc/meta/update/model/v3/QuiltMappingsVersionV3.java index 095d4c5..f69525b 100644 --- a/src/main/java/org/quiltmc/meta/update/model/v3/QuiltMappingsVersionV3.java +++ b/src/main/java/org/quiltmc/meta/update/model/v3/QuiltMappingsVersionV3.java @@ -1,5 +1,9 @@ package org.quiltmc.meta.update.model.v3; +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + // TODO remove 'hashed' field in v4? it seems superfluous -public record QuiltMappingsVersionV3(String maven, String version, String gameVersion, int build, String separator, String hashed) { +public record QuiltMappingsVersionV3(String maven, String version, String gameVersion, int build, String separator, String hashed, @SerializedName("file_size") long fileSize, Map hashes) { } diff --git a/src/main/java/org/quiltmc/meta/update/util/function/ThrowingConsumer.java b/src/main/java/org/quiltmc/meta/update/util/function/ThrowingConsumer.java new file mode 100644 index 0000000..62a265e --- /dev/null +++ b/src/main/java/org/quiltmc/meta/update/util/function/ThrowingConsumer.java @@ -0,0 +1,7 @@ +package org.quiltmc.meta.update.util.function; + +@FunctionalInterface +public interface ThrowingConsumer { + + void accept(T t) throws E; +} diff --git a/src/main/java/org/quiltmc/meta/update/util/maven/MavenRepository.java b/src/main/java/org/quiltmc/meta/update/util/maven/MavenRepository.java index 05402c4..fbe89bf 100644 --- a/src/main/java/org/quiltmc/meta/update/util/maven/MavenRepository.java +++ b/src/main/java/org/quiltmc/meta/update/util/maven/MavenRepository.java @@ -4,8 +4,10 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.ResponseBody; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.quiltmc.meta.update.Constants; +import org.quiltmc.meta.update.util.function.ThrowingConsumer; import org.quiltmc.meta.update.util.hash.Hash; import org.quiltmc.meta.update.util.hash.HashFunction; import org.quiltmc.meta.update.util.http.HttpUtils; @@ -28,6 +30,8 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; public class MavenRepository implements Closeable { @@ -231,19 +235,21 @@ public URI url() { public synchronized CompletableFuture fileInfo(Executor executor) { return Objects.requireNonNullElseGet(this.fileInfo, () -> this.fileInfo = CompletableFuture.supplyAsync(() -> { Map hashes = new EnumMap<>(HashFunction.class); - long fileSize = 0L; + AtomicLong fileSize = new AtomicLong(0L); boolean needsManualDownload = false; try { // first try gradle module var moduleRequest = new Request.Builder().get().url(url().toString() + ".module").build(); - try (var body = makeRequest(moduleRequest); var reader = body.charStream()) { - var gradleFileInfo = Optional.ofNullable(Constants.GSON.fromJson(reader, GradleModuleMetadata.class)).flatMap(GradleModuleMetadata::mainFileInfo).orElse(null); - if(gradleFileInfo != null) { - fileSize = gradleFileInfo.size(); - hashes.putAll(gradleFileInfo.hashes()); + makeFileRequest(moduleRequest, body -> { + try (var reader = body.charStream()) { + var gradleFileInfo = Optional.ofNullable(Constants.GSON.fromJson(reader, GradleModuleMetadata.class)).flatMap(GradleModuleMetadata::mainFileInfo).orElse(null); + if (gradleFileInfo != null) { + fileSize.set(gradleFileInfo.size()); + hashes.putAll(gradleFileInfo.hashes()); + } } - } + }); for (HashFunction hashFunction : HashFunction.values()) { if (hashes.containsKey(hashFunction)) { @@ -255,9 +261,11 @@ public synchronized CompletableFuture fileInfo(Executor executor) { } var request = new Request.Builder().url(url().toString() + hashFunction.getMavenFileSuffix()).get().build(); - try (var body = makeRequest(request); var stream = body.byteStream()) { - hashes.put(hashFunction, hashFunction.createHash(stream)); - } + makeFileRequest(request, body -> { + try (var stream = body.byteStream()) { + hashes.put(hashFunction, hashFunction.createHash(stream)); + } + }); } } catch (IOException e) { LOGGER.warn("unable to download one or more hash files for {}, falling back to manual download", url(), e); @@ -267,8 +275,8 @@ public synchronized CompletableFuture fileInfo(Executor executor) { // if we dont need the full file, only make a HEAD request if (!needsManualDownload) { var request = new Request.Builder().url(url().toString()).head().build(); - try (var body = makeRequest(request)) { - fileSize = body.contentLength(); + try { + makeFileRequest(request, body -> fileSize.set(body.contentLength())); } catch (IOException e) { LOGGER.warn("Failed HEAD request for {}, retrying GET request...", url(), e); needsManualDownload = true; @@ -276,27 +284,29 @@ public synchronized CompletableFuture fileInfo(Executor executor) { } // catch cases where server sends an "unknown" content length - if (fileSize <= 0L) { + if (fileSize.get() <= 0L) { needsManualDownload = true; } if (needsManualDownload) { var request = new Request.Builder().url(url().toString()).get().build(); - try (var body = makeRequest(request)) { - // maven jars are probably small enough that we can read the whole file into memory, - // even for mappings files - var bytes = body.bytes(); - fileSize = bytes.length; - - for (HashFunction hashFunction : HashFunction.values()) { - hashes.computeIfAbsent(hashFunction, key -> key.createHash(bytes)); - } + try { + makeFileRequest(request, body -> { + // maven jars are probably small enough that we can read the whole file into memory, + // even for mappings files + var bytes = body.bytes(); + fileSize.set(bytes.length); + + for (HashFunction hashFunction : HashFunction.values()) { + hashes.computeIfAbsent(hashFunction, key -> key.createHash(bytes)); + } + }); } catch (IOException e) { throw new UncheckedIOException("unable to manually retrieve file metadata for " + url(), e); } } - return new FileInfo(fileSize, Collections.unmodifiableMap(hashes)); + return new FileInfo(fileSize.get(), Collections.unmodifiableMap(hashes)); }, executor)); } @@ -305,22 +315,35 @@ public record FileInfo(long fileSize, Map hashes) { public Hash getHash(HashFunction type) { return hashes.get(type); } + + @ApiStatus.Internal + public Map formatHashesForJson() { + return hashes().values().stream().collect(Collectors.toMap(hash -> hash.algorithm().getName(), Hash::asHexString)); + } } } } - public ResponseBody makeRequest(Request request) throws IOException { + public void makeFileRequest(Request request, ThrowingConsumer callback) throws IOException { try (var response = client.newCall(request).execute()) { if (!response.isSuccessful()) { - throw new IOException("HTTP " + response.code() + " - " + response.message()); + StringBuilder message = new StringBuilder("HTTP ").append(response.code()); + if (!response.message().isBlank()) { + message.append(" ").append(response.message()); + } + throw new IOException(message.toString()); } - var body = response.body(); - if (body == null) { - throw new IOException("Received empty response body"); - } + try (var body = response.body()) { + if (body == null) { + throw new IOException("Received empty response body"); + } - return body; + callback.accept(body); + } } } + + public record MavenFileResponse(byte[] content, long contentLength) { + } }