Skip to content

Commit

Permalink
paper: consider release channel when selecting version/build (#437)
Browse files Browse the repository at this point in the history
  • Loading branch information
itzg committed Jun 15, 2024
1 parent 0fcd865 commit 69bcf56
Show file tree
Hide file tree
Showing 8 changed files with 2,955 additions and 78 deletions.
117 changes: 65 additions & 52 deletions src/main/java/me/itzg/helpers/paper/InstallPaperCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
import me.itzg.helpers.files.ManifestException;
import me.itzg.helpers.files.Manifests;
import me.itzg.helpers.files.ResultsFileWriter;
import me.itzg.helpers.http.FailedRequestException;
import me.itzg.helpers.http.Fetch;
import me.itzg.helpers.http.SharedFetch;
import me.itzg.helpers.http.SharedFetchArgs;
import me.itzg.helpers.json.ObjectMappers;
import me.itzg.helpers.paper.model.ReleaseChannel;
import me.itzg.helpers.paper.model.VersionMeta;
import me.itzg.helpers.sync.MultiCopyManifest;
import org.jetbrains.annotations.NotNull;
import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
Expand Down Expand Up @@ -73,6 +76,9 @@ public void setVersion(String version) {

@Option(names = "--build")
Integer build;

@Option(names = "--channel", defaultValue = "default")
ReleaseChannel channel;
}
}

Expand Down Expand Up @@ -106,9 +112,11 @@ public Integer call() throws Exception {
result = downloadCustom(inputs.downloadUrl);
}
else {
result = useCoordinates(client, inputs.coordinates.project,
inputs.coordinates.version, inputs.coordinates.build
);
result = downloadUsingCoordinates(client, inputs.coordinates.project,
inputs.coordinates.version, inputs.coordinates.build,
inputs.coordinates.channel
)
.block();
}
}

Expand All @@ -126,30 +134,60 @@ public Integer call() throws Exception {
return ExitCode.OK;
}

private Result useCoordinates(PaperDownloadsClient client, String project, String version, Integer build) {
return resolveVersion(client, project, version)
.flatMap(v -> resolveBuild(client, project, v, build)
.flatMap(b -> {
log.info("Resolved {} to version {} build {}", project, v, b);

return client.download(project, v, b, outputDirectory, Fetch.loggingDownloadStatusHandler(log))
.map(serverJar ->
Result.builder()
.newManifest(
PaperManifest.builder()
.project(project)
.minecraftVersion(v)
.build(b)
.files(Collections.singleton(Manifests.relativize(outputDirectory, serverJar)))
.build()
)
.serverJar(serverJar)
.version(v)
.build()
);
})
)
.block();
private Mono<Result> downloadUsingCoordinates(PaperDownloadsClient client, String project,
String version, Integer build, ReleaseChannel channel
) {
if (isSpecificVersion(version)) {
if (build != null) {
return download(client, project, version, build)
.onErrorMap(
FailedRequestException::isNotFound,
throwable -> new InvalidParameterException(
String.format("Requested version %s, build %d is not available", version, build))
);
}
else {
return client.getLatestBuild(project, version, channel)
.onErrorMap(
FailedRequestException::isNotFound,
throwable -> new InvalidParameterException(
String.format("Requested version %s is not available", version))
)
.switchIfEmpty(Mono.error(() -> new InvalidParameterException(
String.format("No build found for version %s with channel %s", version, channel)
)))
.flatMap(resolvedBuild -> download(client, project, version, resolvedBuild));
}
}
else {
return client.getLatestVersionBuild(project, channel)
.switchIfEmpty(
Mono.error(() -> new InvalidParameterException("No build found with channel " + channel))
)
.flatMap(resolved -> download(client, project, resolved.getVersion(), resolved.getBuild()));
}
}

private static boolean isSpecificVersion(String version) {
return version != null && !version.equalsIgnoreCase("latest");
}

private @NotNull Mono<Result> download(PaperDownloadsClient client, String project, String v, Integer b) {
return client.download(project, v, b, outputDirectory, Fetch.loggingDownloadStatusHandler(log))
.map(serverJar ->
Result.builder()
.newManifest(
PaperManifest.builder()
.project(project)
.minecraftVersion(v)
.build(b)
.files(Collections.singleton(Manifests.relativize(outputDirectory, serverJar)))
.build()
)
.serverJar(serverJar)
.version(v)
.build()
);
}

private Result downloadCustom(URI downloadUrl) {
Expand Down Expand Up @@ -217,29 +255,4 @@ private PaperManifest loadOldManifest() {
}
}

private Mono<String> resolveVersion(PaperDownloadsClient client, String project, String version) {
if (version.equals("latest")) {
return client.getLatestProjectVersion(project);
}
return client.hasVersion(project, version)
.flatMap(exists -> exists ? Mono.just(version) : Mono.error(() -> new InvalidParameterException(
String.format("Version %s does not exist for the project %s",
version, project
))));
}

private Mono<Integer> resolveBuild(PaperDownloadsClient client, String project, String version, Integer build) {
if (build == null) {
return client.getLatestBuild(project, version);
}
else {
return client.hasBuild(project, version, build)
.flatMap(exists -> exists ? Mono.just(build) : Mono.error(() ->
new InvalidParameterException(String.format("Build %d does not exist for project %s version %s",
build, project, version
)
)
));
}
}
}
70 changes: 44 additions & 26 deletions src/main/java/me/itzg/helpers/paper/PaperDownloadsClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package me.itzg.helpers.paper;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.itzg.helpers.errors.GenericException;
import me.itzg.helpers.http.Fetch;
import me.itzg.helpers.http.FileDownloadStatusHandler;
Expand All @@ -9,12 +16,14 @@
import me.itzg.helpers.paper.model.BuildInfo;
import me.itzg.helpers.paper.model.BuildInfo.DownloadInfo;
import me.itzg.helpers.paper.model.ProjectInfo;
import me.itzg.helpers.paper.model.VersionInfo;
import me.itzg.helpers.paper.model.ReleaseChannel;
import me.itzg.helpers.paper.model.VersionBuilds;
import reactor.core.publisher.Mono;

/**
* <a href="https://api.papermc.io/docs/swagger-ui/index.html?configUrl=/openapi/swagger-config">Downloads API</a>
*/
@Slf4j
public class PaperDownloadsClient implements AutoCloseable{

private final UriBuilder uriBuilder;
Expand All @@ -25,48 +34,57 @@ public PaperDownloadsClient(String baseUrl, SharedFetch.Options options) {
sharedFetch = Fetch.sharedFetch("install-paper", options);
}

public Mono<String> getLatestProjectVersion(String project) {
return sharedFetch.fetch(
uriBuilder.resolve("/v2/projects/{project}", project)
)
.toObject(ProjectInfo.class)
.assemble()
.map(projectInfo -> projectInfo.getVersions().get(projectInfo.getVersions().size() - 1));
private static <T> Iterable<T> reverse(List<T> versions) {
final ArrayList<T> result = new ArrayList<>(versions);
Collections.reverse(result);
return result;
}

@Data
@RequiredArgsConstructor
public static class VersionBuild {
final String version;
final int build;
}

public Mono<Boolean> hasVersion(String project, String version) {
public Mono<VersionBuild> getLatestVersionBuild(String project, ReleaseChannel channel) {
return sharedFetch.fetch(
uriBuilder.resolve("/v2/projects/{project}", project)
)
.toObject(ProjectInfo.class)
.assemble()
.map(projectInfo -> projectInfo.getVersions().contains(version));
.flatMapIterable(projectInfo -> reverse(projectInfo.getVersions()))
.concatMap(v ->
getLatestBuild(project, v, channel)
.map(build -> new VersionBuild(v, build)),
1
)
.next();
}

public Mono<Integer> getLatestBuild(String project, String version) {
public Mono<Integer> getLatestBuild(String project, String version, ReleaseChannel channel) {
log.debug("Retrieving latest build for project={}, version={}", project, version);

return sharedFetch.fetch(
uriBuilder.resolve("/v2/projects/{project}/versions/{version}",
uriBuilder.resolve("/v2/projects/{project}/versions/{version}/builds",
project, version
)
)
.toObject(VersionInfo.class)
.toObject(VersionBuilds.class)
.assemble()
.map(
versionInfo -> versionInfo.getBuilds().get(versionInfo.getBuilds().size()-1)
.flatMap(
versionBuilds ->
Mono.justOrEmpty(
versionBuilds.getBuilds().stream()
// sort by build ID desc
.sorted(Comparator.comparingInt(BuildInfo::getBuild).reversed())
.filter(buildInfo -> buildInfo.getChannel() == channel)
.map(BuildInfo::getBuild)
.findFirst()
)
);
}

public Mono<Boolean> hasBuild(String project, String version, int build) {
return sharedFetch.fetch(
uriBuilder.resolve("/v2/projects/{project}/versions/{version}",
project, version
)
)
.toObject(VersionInfo.class)
.assemble()
.map(versionInfo -> versionInfo.getBuilds().contains(build));
}

public Mono<Path> download(String project, String version, int build, Path outputDirectory,
FileDownloadStatusHandler downloadStatusHandler
) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/me/itzg/helpers/paper/model/BuildInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class BuildInfo {
int build;
ReleaseChannel channel;
Map<String, DownloadInfo> downloads;

@Data
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/me/itzg/helpers/paper/model/ReleaseChannel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package me.itzg.helpers.paper.model;

import com.fasterxml.jackson.annotation.JsonProperty;

public enum ReleaseChannel {
@JsonProperty("default")
DEFAULT,
@JsonProperty("experimental")
EXPERIMENTAL;

@Override
public String toString() {
return name().toLowerCase();
}
}
16 changes: 16 additions & 0 deletions src/main/java/me/itzg/helpers/paper/model/VersionBuilds.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.itzg.helpers.paper.model;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import java.util.List;
import lombok.Data;

/**
* Response from <a href="https://api.papermc.io/docs/swagger-ui/index.html?configUrl=/openapi/swagger-config#/version-builds-controller/builds">version-builds-controller</a>
*/
@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class VersionBuilds {
String version;
List<BuildInfo> builds;
}
Loading

0 comments on commit 69bcf56

Please sign in to comment.