Skip to content

Commit 990c2de

Browse files
authored
paper: now applies channel and resolves stable by default (#647)
1 parent 04ad816 commit 990c2de

10 files changed

+444
-44
lines changed

dev/paper.http

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1+
@project =paper
2+
@version =1.21.9
3+
@build =49
4+
15
###
2-
GET https://api.papermc.io/v2/projects/paper
6+
GET https://fill.papermc.io/v3/projects/paper/versions
7+
8+
###
9+
GET https://fill.papermc.io/v3/projects/{{project}}/versions/{{version}}/builds/{{build}}

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,14 @@ public Integer call() throws Exception {
120120
else {
121121
if (requestCheckUpdates) {
122122
return checkForUpdates(client, oldManifest,
123-
inputs.coordinates.project, inputs.coordinates.version, inputs.coordinates.build
123+
inputs.coordinates.project, inputs.coordinates.version, inputs.coordinates.build,
124+
inputs.coordinates.channel
124125
);
125126
}
126127

127128
result = downloadUsingCoordinates(client, inputs.coordinates.project,
128-
inputs.coordinates.version, inputs.coordinates.build
129+
inputs.coordinates.version, inputs.coordinates.build,
130+
inputs.coordinates.channel
129131
)
130132
.block();
131133
}
@@ -151,7 +153,8 @@ public Integer call() throws Exception {
151153
}
152154

153155
private Integer checkForUpdates(PaperDownloadsClient client, PaperManifest oldManifest,
154-
String project, String version, Integer build
156+
String project, String version, Integer build,
157+
RequestedChannel channel
155158
) {
156159
if (oldManifest != null && oldManifest.getCustomDownloadUrl() != null) {
157160
log.info("Using custom download URL before");
@@ -184,7 +187,7 @@ private Integer checkForUpdates(PaperDownloadsClient client, PaperManifest oldMa
184187
}
185188
}
186189
else {
187-
return client.getLatestVersionBuild(project)
190+
return client.getLatestVersionBuild(project, channel)
188191
.map(versionBuild -> {
189192
if (oldManifest == null) {
190193
return logVersion(project, versionBuild.getVersion(), versionBuild.getBuild());
@@ -223,10 +226,11 @@ private static boolean mismatchingVersions(PaperManifest oldManifest, String pro
223226
}
224227

225228
private Mono<Result> downloadUsingCoordinates(PaperDownloadsClient client, String project,
226-
String version, Integer build
229+
String version, Integer build,
230+
RequestedChannel channel
227231
) {
228232
return
229-
assembleDownload(client, project, version, build)
233+
assembleDownload(client, project, version, build, channel)
230234
.map(result ->
231235
Result.builder()
232236
.newManifest(
@@ -244,7 +248,8 @@ private Mono<Result> downloadUsingCoordinates(PaperDownloadsClient client, Strin
244248
}
245249

246250
private Mono<VersionBuildFile> assembleDownload(PaperDownloadsClient client, String project, String version,
247-
Integer build
251+
Integer build,
252+
RequestedChannel channel
248253
) {
249254
final FileDownloadStatusHandler downloadStatusHandler = Fetch.loggingDownloadStatusHandler(log);
250255

@@ -257,7 +262,7 @@ private Mono<VersionBuildFile> assembleDownload(PaperDownloadsClient client, Str
257262
}
258263
}
259264
else {
260-
return client.downloadLatest(project, outputDirectory, downloadStatusHandler);
265+
return client.downloadLatest(project, channel, outputDirectory, downloadStatusHandler);
261266
}
262267
}
263268

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

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.net.URI;
44
import java.nio.file.Path;
55
import lombok.Data;
6+
import lombok.RequiredArgsConstructor;
67
import lombok.extern.slf4j.Slf4j;
78
import me.itzg.helpers.errors.GenericException;
89
import me.itzg.helpers.errors.InvalidParameterException;
@@ -12,10 +13,11 @@
1213
import me.itzg.helpers.http.SharedFetch;
1314
import me.itzg.helpers.http.UriBuilder;
1415
import me.itzg.helpers.paper.model.BuildResponse;
16+
import me.itzg.helpers.paper.model.Channel;
1517
import me.itzg.helpers.paper.model.Download;
1618
import me.itzg.helpers.paper.model.ProjectResponse;
17-
import me.itzg.helpers.paper.model.Version;
1819
import me.itzg.helpers.paper.model.VersionResponse;
20+
import reactor.core.publisher.Flux;
1921
import reactor.core.publisher.Mono;
2022

2123
/**
@@ -45,10 +47,10 @@ public static class VersionBuildFile {
4547
final Path file;
4648
}
4749

48-
public Mono<VersionBuild> getLatestVersionBuild(String project) {
50+
public Mono<VersionBuild> getLatestVersionBuild(String project, RequestedChannel requestedChannel) {
4951
return getProjectVersions(project)
5052
.flatMap(projectResponse ->
51-
extractVersionBuild(project, projectResponse)
53+
extractLatestVersionBuild(project, requestedChannel, projectResponse)
5254
);
5355
}
5456

@@ -68,12 +70,12 @@ public Mono<Integer> getLatestBuild(String project, String version) {
6870
.map(BuildResponse::getId);
6971
}
7072

71-
public Mono<VersionBuildFile> downloadLatest(String project,
73+
public Mono<VersionBuildFile> downloadLatest(String project, RequestedChannel requestedChannel,
7274
Path outputDirectory, FileDownloadStatusHandler downloadStatusHandler
7375
) {
7476
return getProjectVersions(project)
7577
.flatMap(projectResponse ->
76-
extractVersionBuild(project, projectResponse)
78+
extractLatestVersionBuild(project, requestedChannel, projectResponse)
7779
.flatMap(versionBuild ->
7880
download(project, outputDirectory, downloadStatusHandler,
7981
versionBuild.getVersion(),
@@ -111,6 +113,15 @@ public Mono<VersionBuildFile> download(String project,
111113
String version,
112114
int build
113115
) {
116+
return getBuild(project, version, build)
117+
.flatMap(buildResponse ->
118+
downloadWithBuildResponse(outputDirectory, downloadStatusHandler, version, buildResponse)
119+
.map(path -> new VersionBuildFile(version, build, path))
120+
);
121+
}
122+
123+
private Mono<BuildResponse> getBuild(String project, String version, int build) {
124+
114125
return sharedFetch.fetch(
115126
uriBuilder.resolve("/v3/projects/{project}/versions/{version}/builds/{build}",
116127
project, version, build
@@ -122,10 +133,6 @@ public Mono<VersionBuildFile> download(String project,
122133
FailedRequestException::isNotFound,
123134
throwable -> new InvalidParameterException(
124135
String.format("Requested version %s, build %d is not available", version, build))
125-
)
126-
.flatMap(buildResponse ->
127-
downloadWithBuildResponse(outputDirectory, downloadStatusHandler, version, buildResponse)
128-
.map(path -> new VersionBuildFile(version, build, path))
129136
);
130137
}
131138

@@ -142,26 +149,37 @@ private Mono<ProjectResponse> getProjectVersions(String project) {
142149
);
143150
}
144151

145-
private Mono<VersionBuild> extractVersionBuild(String project, ProjectResponse projectResponse) {
152+
@RequiredArgsConstructor
153+
private static class VersionAndBuildResponse {
154+
final VersionResponse versionResponse;
155+
final BuildResponse buildResponse;
156+
}
157+
158+
private Mono<VersionBuild> extractLatestVersionBuild(String project, RequestedChannel requestedChannel, ProjectResponse projectResponse) {
146159
if (projectResponse.getVersions() == null ||
147160
projectResponse.getVersions().isEmpty()) {
148161
log.warn("No versions found for project={}", project);
149162
return Mono.error(() -> new InvalidParameterException("No versions found for project"));
150163
}
151164

152-
final VersionResponse versionResponse = projectResponse.getVersions().get(0);
153-
final Version version = versionResponse.getVersion();
154-
if (versionResponse.getBuilds() == null ||
155-
versionResponse.getBuilds().isEmpty()) {
156-
log.warn("No builds found for project={} version={}", project, version.getId());
157-
return Mono.error(() -> new InvalidParameterException(
158-
String.format("No builds found for project version %s", version.getId()))
159-
);
160-
}
165+
return Flux.fromIterable(projectResponse.getVersions())
166+
.filter(versionResponse -> versionResponse.getBuilds() != null && !versionResponse.getBuilds().isEmpty())
167+
.concatMap(versionResponse ->
168+
getBuild(project, versionResponse.getVersion().getId(), versionResponse.getBuilds().get(0))
169+
.map(buildResponse -> new VersionAndBuildResponse(versionResponse, buildResponse))
170+
)
171+
.takeUntil(vAndB -> acceptableChannel(vAndB.buildResponse.getChannel(), requestedChannel))
172+
.last()
173+
.map(vAndB -> new VersionBuild(vAndB.versionResponse.getVersion().getId(), vAndB.buildResponse.getId()));
174+
}
161175

162-
return Mono.just(
163-
new VersionBuild(version.getId(), versionResponse.getBuilds().get(0))
164-
);
176+
private boolean acceptableChannel(Channel channel, RequestedChannel requestedChannel) {
177+
for (final Channel mapped : requestedChannel.getMappedTo()) {
178+
if (mapped.equals(channel)) {
179+
return true;
180+
}
181+
}
182+
return false;
165183
}
166184

167185
private Mono<Path> downloadWithBuildResponse(Path outputDirectory, FileDownloadStatusHandler downloadStatusHandler,
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
package me.itzg.helpers.paper;
22

3+
import lombok.Getter;
4+
import lombok.ToString;
5+
import me.itzg.helpers.paper.model.Channel;
6+
7+
@ToString
8+
@Getter
39
public enum RequestedChannel {
4-
DEFAULT,
5-
EXPERIMENTAL
10+
DEFAULT(Channel.STABLE, Channel.RECOMMENDED),
11+
EXPERIMENTAL(Channel.ALPHA, Channel.BETA),
12+
ALPHA(Channel.ALPHA);
13+
14+
private final Channel[] mappedTo;
15+
16+
RequestedChannel(Channel... mappedTo) {
17+
this.mappedTo = mappedTo;
18+
}
619
}

src/test/java/me/itzg/helpers/paper/PaperDownloadsClientTest.java

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,66 @@
77
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
88
import java.net.URI;
99
import java.nio.file.Path;
10+
import java.util.stream.Stream;
1011
import me.itzg.helpers.http.FileDownloadStatusHandler;
1112
import me.itzg.helpers.http.SharedFetch.Options;
1213
import me.itzg.helpers.paper.PaperDownloadsClient.VersionBuild;
1314
import me.itzg.helpers.paper.PaperDownloadsClient.VersionBuildFile;
1415
import org.junit.jupiter.api.Test;
1516
import org.junit.jupiter.api.io.TempDir;
17+
import org.junit.jupiter.params.ParameterizedTest;
18+
import org.junit.jupiter.params.provider.Arguments;
19+
import org.junit.jupiter.params.provider.MethodSource;
1620
import org.mockito.Mockito;
1721

1822
@WireMockTest
1923
class PaperDownloadsClientTest {
2024

21-
@Test
22-
void latestVersionBuild(WireMockRuntimeInfo wmInfo) {
25+
public static Stream<Arguments> latestVersionBuild_args() {
26+
return Stream.of(
27+
Arguments.arguments(RequestedChannel.DEFAULT, "1.21.8", 60),
28+
Arguments.arguments(RequestedChannel.EXPERIMENTAL, "1.21.9", 49)
29+
);
30+
}
31+
32+
@ParameterizedTest
33+
@MethodSource("latestVersionBuild_args")
34+
void latestVersionBuild(RequestedChannel requestedChannel, String expectedVersion, int expectedBuild, WireMockRuntimeInfo wmInfo) {
2335
//TODO use urlPathTemplate with Wiremock 3.x
2436
stubFor(get(urlPathEqualTo("/v3/projects/paper/versions"))
2537
.willReturn(aResponse()
2638
.withHeader("Content-Type", "application/json")
27-
.withBodyFile("paper/v3/response_paper_project.json")
39+
.withBodyFile("paper/v3/projects_paper_versions_with_alphas.json")
40+
)
41+
);
42+
stubFor(get(urlPathEqualTo("/v3/projects/paper/versions/1.21.9/builds/49"))
43+
.willReturn(aResponse()
44+
.withHeader("Content-Type", "application/json")
45+
.withBodyFile("paper/v3/projects_paper_1_21_9_builds_49.json")
46+
)
47+
);
48+
stubFor(get(urlPathEqualTo("/v3/projects/paper/versions/1.21.9-rc1/builds/36"))
49+
.willReturn(aResponse()
50+
.withHeader("Content-Type", "application/json")
51+
.withBodyFile("paper/v3/projects_paper_1_21_9-rc1_builds_36.json")
52+
)
53+
);
54+
stubFor(get(urlPathEqualTo("/v3/projects/paper/versions/1.21.8/builds/60"))
55+
.willReturn(aResponse()
56+
.withHeader("Content-Type", "application/json")
57+
.withBodyFile("paper/v3/projects_paper_1_21_8_builds_60.json")
2858
)
2959
);
3060

3161
try (PaperDownloadsClient client = new PaperDownloadsClient(wmInfo.getHttpBaseUrl(),
3262
Options.builder().build()
3363
)) {
34-
final VersionBuild result = client.getLatestVersionBuild("paper")
64+
final VersionBuild result = client.getLatestVersionBuild("paper", requestedChannel)
3565
.block();
3666

3767
assertThat(result).isNotNull();
38-
assertThat(result.getVersion()).isEqualTo("1.21.6");
39-
assertThat(result.getBuild()).isEqualTo(46);
68+
assertThat(result.getVersion()).isEqualTo(expectedVersion);
69+
assertThat(result.getBuild()).isEqualTo(expectedBuild);
4070
}
4171

4272
}
@@ -46,7 +76,7 @@ void latestBuild(WireMockRuntimeInfo wmInfo) {
4676
stubFor(get(urlPathEqualTo("/v3/projects/paper/versions/1.21.6/builds/latest"))
4777
.willReturn(aResponse()
4878
.withHeader("Content-Type", "application/json")
49-
.withBodyFile("paper/v3/response_paper_build_response.json")
79+
.withBodyFile("paper/v3/projects_paper_1_21_6_builds_46.json")
5080
)
5181
);
5282

@@ -66,7 +96,7 @@ void downloadsSpecific(WireMockRuntimeInfo wmInfo, @TempDir Path tempDir) {
6696
stubFor(get(urlPathEqualTo("/v3/projects/paper/versions/1.21.6/builds/46"))
6797
.willReturn(aResponse()
6898
.withHeader("Content-Type", "application/json")
69-
.withBodyFile("paper/v3/response_paper_build_response.json")
99+
.withBodyFile("paper/v3/projects_paper_1_21_6_builds_46.json")
70100
)
71101
);
72102

@@ -112,7 +142,7 @@ void downloadsLatest(WireMockRuntimeInfo wmInfo, @TempDir Path tempDir) {
112142
stubFor(get(urlPathEqualTo("/v3/projects/paper/versions/1.21.6/builds/46"))
113143
.willReturn(aResponse()
114144
.withHeader("Content-Type", "application/json")
115-
.withBodyFile("paper/v3/response_paper_build_response.json")
145+
.withBodyFile("paper/v3/projects_paper_1_21_6_builds_46.json")
116146
)
117147
);
118148
stubFor(get(urlPathEqualTo("/v1/objects/bfca155b4a6b45644bfc1766f4e02a83c736e45fcc060e8788c71d6e7b3d56f6/paper-1.21.6-46.jar"))
@@ -129,7 +159,7 @@ void downloadsLatest(WireMockRuntimeInfo wmInfo, @TempDir Path tempDir) {
129159
.filesViaUrl(URI.create(wmInfo.getHttpBaseUrl()))
130160
.build()
131161
)) {
132-
final VersionBuildFile result = client.downloadLatest("paper", tempDir, statusHandler)
162+
final VersionBuildFile result = client.downloadLatest("paper", RequestedChannel.DEFAULT, tempDir, statusHandler)
133163
.block();
134164

135165
assertThat(result).isNotNull();
@@ -151,7 +181,7 @@ void downloadsLatestBuild(WireMockRuntimeInfo wmInfo, @TempDir Path tempDir) {
151181
stubFor(get(urlPathEqualTo("/v3/projects/paper/versions/1.21.6/builds/latest"))
152182
.willReturn(aResponse()
153183
.withHeader("Content-Type", "application/json")
154-
.withBodyFile("paper/v3/response_paper_build_response.json")
184+
.withBodyFile("paper/v3/projects_paper_1_21_6_builds_46.json")
155185
)
156186
);
157187
stubFor(get(urlPathEqualTo("/v1/objects/bfca155b4a6b45644bfc1766f4e02a83c736e45fcc060e8788c71d6e7b3d56f6/paper-1.21.6-46.jar"))
File renamed without changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"id": 60,
3+
"time": "2025-09-06T21:50:11.982Z",
4+
"channel": "STABLE",
5+
"commits": [
6+
{
7+
"sha": "29c8822d90899c89d2689338e81a98f690bcba12",
8+
"time": "2025-09-06T21:38:29Z",
9+
"message": "Remove no longer needed MC-210802 fix (#13059)\n\n"
10+
}
11+
],
12+
"downloads": {
13+
"server:default": {
14+
"name": "paper-1.21.8-60.jar",
15+
"checksums": {
16+
"sha256": "8de7c52c3b02403503d16fac58003f1efef7dd7a0256786843927fa92ee57f1e"
17+
},
18+
"size": 52811717,
19+
"url": "https://fill-data.papermc.io/v1/objects/8de7c52c3b02403503d16fac58003f1efef7dd7a0256786843927fa92ee57f1e/paper-1.21.8-60.jar"
20+
}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"id": 36,
3+
"time": "2025-09-30T13:38:40.064Z",
4+
"channel": "ALPHA",
5+
"commits": [
6+
{
7+
"sha": "5b6165c48d64b4daa44ac5f498bc11ee928e4637",
8+
"time": "2025-09-30T13:26:15Z",
9+
"message": "Add spawnreason for building copper golems (#13112)\n\n"
10+
}
11+
],
12+
"downloads": {
13+
"server:default": {
14+
"name": "paper-1.21.9-rc1-36.jar",
15+
"checksums": {
16+
"sha256": "f737c4ce0afd8ca897c5330188634859148419c6c2d2e172c65f581c47430ab1"
17+
},
18+
"size": 52515036,
19+
"url": "https://fill-data.papermc.io/v1/objects/f737c4ce0afd8ca897c5330188634859148419c6c2d2e172c65f581c47430ab1/paper-1.21.9-rc1-36.jar"
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)