Skip to content

Commit 69bcf56

Browse files
authored
paper: consider release channel when selecting version/build (#437)
1 parent 0fcd865 commit 69bcf56

File tree

8 files changed

+2955
-78
lines changed

8 files changed

+2955
-78
lines changed

src/main/java/me/itzg/helpers/paper/InstallPaperCommand.java

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
import me.itzg.helpers.files.ManifestException;
1717
import me.itzg.helpers.files.Manifests;
1818
import me.itzg.helpers.files.ResultsFileWriter;
19+
import me.itzg.helpers.http.FailedRequestException;
1920
import me.itzg.helpers.http.Fetch;
2021
import me.itzg.helpers.http.SharedFetch;
2122
import me.itzg.helpers.http.SharedFetchArgs;
2223
import me.itzg.helpers.json.ObjectMappers;
24+
import me.itzg.helpers.paper.model.ReleaseChannel;
2325
import me.itzg.helpers.paper.model.VersionMeta;
2426
import me.itzg.helpers.sync.MultiCopyManifest;
27+
import org.jetbrains.annotations.NotNull;
2528
import picocli.CommandLine;
2629
import picocli.CommandLine.ArgGroup;
2730
import picocli.CommandLine.Command;
@@ -73,6 +76,9 @@ public void setVersion(String version) {
7376

7477
@Option(names = "--build")
7578
Integer build;
79+
80+
@Option(names = "--channel", defaultValue = "default")
81+
ReleaseChannel channel;
7682
}
7783
}
7884

@@ -106,9 +112,11 @@ public Integer call() throws Exception {
106112
result = downloadCustom(inputs.downloadUrl);
107113
}
108114
else {
109-
result = useCoordinates(client, inputs.coordinates.project,
110-
inputs.coordinates.version, inputs.coordinates.build
111-
);
115+
result = downloadUsingCoordinates(client, inputs.coordinates.project,
116+
inputs.coordinates.version, inputs.coordinates.build,
117+
inputs.coordinates.channel
118+
)
119+
.block();
112120
}
113121
}
114122

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

129-
private Result useCoordinates(PaperDownloadsClient client, String project, String version, Integer build) {
130-
return resolveVersion(client, project, version)
131-
.flatMap(v -> resolveBuild(client, project, v, build)
132-
.flatMap(b -> {
133-
log.info("Resolved {} to version {} build {}", project, v, b);
134-
135-
return client.download(project, v, b, outputDirectory, Fetch.loggingDownloadStatusHandler(log))
136-
.map(serverJar ->
137-
Result.builder()
138-
.newManifest(
139-
PaperManifest.builder()
140-
.project(project)
141-
.minecraftVersion(v)
142-
.build(b)
143-
.files(Collections.singleton(Manifests.relativize(outputDirectory, serverJar)))
144-
.build()
145-
)
146-
.serverJar(serverJar)
147-
.version(v)
148-
.build()
149-
);
150-
})
151-
)
152-
.block();
137+
private Mono<Result> downloadUsingCoordinates(PaperDownloadsClient client, String project,
138+
String version, Integer build, ReleaseChannel channel
139+
) {
140+
if (isSpecificVersion(version)) {
141+
if (build != null) {
142+
return download(client, project, version, build)
143+
.onErrorMap(
144+
FailedRequestException::isNotFound,
145+
throwable -> new InvalidParameterException(
146+
String.format("Requested version %s, build %d is not available", version, build))
147+
);
148+
}
149+
else {
150+
return client.getLatestBuild(project, version, channel)
151+
.onErrorMap(
152+
FailedRequestException::isNotFound,
153+
throwable -> new InvalidParameterException(
154+
String.format("Requested version %s is not available", version))
155+
)
156+
.switchIfEmpty(Mono.error(() -> new InvalidParameterException(
157+
String.format("No build found for version %s with channel %s", version, channel)
158+
)))
159+
.flatMap(resolvedBuild -> download(client, project, version, resolvedBuild));
160+
}
161+
}
162+
else {
163+
return client.getLatestVersionBuild(project, channel)
164+
.switchIfEmpty(
165+
Mono.error(() -> new InvalidParameterException("No build found with channel " + channel))
166+
)
167+
.flatMap(resolved -> download(client, project, resolved.getVersion(), resolved.getBuild()));
168+
}
169+
}
170+
171+
private static boolean isSpecificVersion(String version) {
172+
return version != null && !version.equalsIgnoreCase("latest");
173+
}
174+
175+
private @NotNull Mono<Result> download(PaperDownloadsClient client, String project, String v, Integer b) {
176+
return client.download(project, v, b, outputDirectory, Fetch.loggingDownloadStatusHandler(log))
177+
.map(serverJar ->
178+
Result.builder()
179+
.newManifest(
180+
PaperManifest.builder()
181+
.project(project)
182+
.minecraftVersion(v)
183+
.build(b)
184+
.files(Collections.singleton(Manifests.relativize(outputDirectory, serverJar)))
185+
.build()
186+
)
187+
.serverJar(serverJar)
188+
.version(v)
189+
.build()
190+
);
153191
}
154192

155193
private Result downloadCustom(URI downloadUrl) {
@@ -217,29 +255,4 @@ private PaperManifest loadOldManifest() {
217255
}
218256
}
219257

220-
private Mono<String> resolveVersion(PaperDownloadsClient client, String project, String version) {
221-
if (version.equals("latest")) {
222-
return client.getLatestProjectVersion(project);
223-
}
224-
return client.hasVersion(project, version)
225-
.flatMap(exists -> exists ? Mono.just(version) : Mono.error(() -> new InvalidParameterException(
226-
String.format("Version %s does not exist for the project %s",
227-
version, project
228-
))));
229-
}
230-
231-
private Mono<Integer> resolveBuild(PaperDownloadsClient client, String project, String version, Integer build) {
232-
if (build == null) {
233-
return client.getLatestBuild(project, version);
234-
}
235-
else {
236-
return client.hasBuild(project, version, build)
237-
.flatMap(exists -> exists ? Mono.just(build) : Mono.error(() ->
238-
new InvalidParameterException(String.format("Build %d does not exist for project %s version %s",
239-
build, project, version
240-
)
241-
)
242-
));
243-
}
244-
}
245258
}

src/main/java/me/itzg/helpers/paper/PaperDownloadsClient.java

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package me.itzg.helpers.paper;
22

33
import java.nio.file.Path;
4+
import java.util.ArrayList;
5+
import java.util.Collections;
6+
import java.util.Comparator;
7+
import java.util.List;
8+
import lombok.Data;
9+
import lombok.RequiredArgsConstructor;
10+
import lombok.extern.slf4j.Slf4j;
411
import me.itzg.helpers.errors.GenericException;
512
import me.itzg.helpers.http.Fetch;
613
import me.itzg.helpers.http.FileDownloadStatusHandler;
@@ -9,12 +16,14 @@
916
import me.itzg.helpers.paper.model.BuildInfo;
1017
import me.itzg.helpers.paper.model.BuildInfo.DownloadInfo;
1118
import me.itzg.helpers.paper.model.ProjectInfo;
12-
import me.itzg.helpers.paper.model.VersionInfo;
19+
import me.itzg.helpers.paper.model.ReleaseChannel;
20+
import me.itzg.helpers.paper.model.VersionBuilds;
1321
import reactor.core.publisher.Mono;
1422

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

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

28-
public Mono<String> getLatestProjectVersion(String project) {
29-
return sharedFetch.fetch(
30-
uriBuilder.resolve("/v2/projects/{project}", project)
31-
)
32-
.toObject(ProjectInfo.class)
33-
.assemble()
34-
.map(projectInfo -> projectInfo.getVersions().get(projectInfo.getVersions().size() - 1));
37+
private static <T> Iterable<T> reverse(List<T> versions) {
38+
final ArrayList<T> result = new ArrayList<>(versions);
39+
Collections.reverse(result);
40+
return result;
41+
}
42+
43+
@Data
44+
@RequiredArgsConstructor
45+
public static class VersionBuild {
46+
final String version;
47+
final int build;
3548
}
3649

37-
public Mono<Boolean> hasVersion(String project, String version) {
50+
public Mono<VersionBuild> getLatestVersionBuild(String project, ReleaseChannel channel) {
3851
return sharedFetch.fetch(
3952
uriBuilder.resolve("/v2/projects/{project}", project)
4053
)
4154
.toObject(ProjectInfo.class)
4255
.assemble()
43-
.map(projectInfo -> projectInfo.getVersions().contains(version));
56+
.flatMapIterable(projectInfo -> reverse(projectInfo.getVersions()))
57+
.concatMap(v ->
58+
getLatestBuild(project, v, channel)
59+
.map(build -> new VersionBuild(v, build)),
60+
1
61+
)
62+
.next();
4463
}
4564

46-
public Mono<Integer> getLatestBuild(String project, String version) {
65+
public Mono<Integer> getLatestBuild(String project, String version, ReleaseChannel channel) {
66+
log.debug("Retrieving latest build for project={}, version={}", project, version);
67+
4768
return sharedFetch.fetch(
48-
uriBuilder.resolve("/v2/projects/{project}/versions/{version}",
69+
uriBuilder.resolve("/v2/projects/{project}/versions/{version}/builds",
4970
project, version
5071
)
5172
)
52-
.toObject(VersionInfo.class)
73+
.toObject(VersionBuilds.class)
5374
.assemble()
54-
.map(
55-
versionInfo -> versionInfo.getBuilds().get(versionInfo.getBuilds().size()-1)
75+
.flatMap(
76+
versionBuilds ->
77+
Mono.justOrEmpty(
78+
versionBuilds.getBuilds().stream()
79+
// sort by build ID desc
80+
.sorted(Comparator.comparingInt(BuildInfo::getBuild).reversed())
81+
.filter(buildInfo -> buildInfo.getChannel() == channel)
82+
.map(BuildInfo::getBuild)
83+
.findFirst()
84+
)
5685
);
5786
}
5887

59-
public Mono<Boolean> hasBuild(String project, String version, int build) {
60-
return sharedFetch.fetch(
61-
uriBuilder.resolve("/v2/projects/{project}/versions/{version}",
62-
project, version
63-
)
64-
)
65-
.toObject(VersionInfo.class)
66-
.assemble()
67-
.map(versionInfo -> versionInfo.getBuilds().contains(build));
68-
}
69-
7088
public Mono<Path> download(String project, String version, int build, Path outputDirectory,
7189
FileDownloadStatusHandler downloadStatusHandler
7290
) {

src/main/java/me/itzg/helpers/paper/model/BuildInfo.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
@Data
99
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
1010
public class BuildInfo {
11+
int build;
12+
ReleaseChannel channel;
1113
Map<String, DownloadInfo> downloads;
1214

1315
@Data
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package me.itzg.helpers.paper.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
public enum ReleaseChannel {
6+
@JsonProperty("default")
7+
DEFAULT,
8+
@JsonProperty("experimental")
9+
EXPERIMENTAL;
10+
11+
@Override
12+
public String toString() {
13+
return name().toLowerCase();
14+
}
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package me.itzg.helpers.paper.model;
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
5+
import java.util.List;
6+
import lombok.Data;
7+
8+
/**
9+
* 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>
10+
*/
11+
@Data
12+
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
13+
public class VersionBuilds {
14+
String version;
15+
List<BuildInfo> builds;
16+
}

0 commit comments

Comments
 (0)