Skip to content

Commit 641c585

Browse files
authored
modrinth: improve logging of non-applicable versions (#469)
1 parent 12c712b commit 641c585

11 files changed

+462
-86
lines changed

dev/modrinth.http

+17-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ GET https://api.modrinth.com/v2/project/ae2/version?loaders=fabric&game_versions
2626
###
2727
GET https://api.modrinth.com/v2/project/datacraft/version?loaders=neoforge&game_versions=1.20.1
2828

29+
###
30+
GET https://api.modrinth.com/v2/project/fALzjamp/version?loaders=["fabric"]&game_versions=["1.21.1"]
31+
32+
###
33+
GET https://api.modrinth.com/v2/project/3wmN97b8/version?loaders=["purpur","paper","spigot"]&game_versions=["1.21.1"]
34+
35+
###
36+
GET https://api.modrinth.com/v2/project/3wmN97b8/version?loaders=["fabric"]&game_versions=["1.21.1"]
37+
38+
###
39+
GET https://api.modrinth.com/v2/project/3wmN97b8
40+
2941
###
3042
GET https://api.modrinth.com/v2/project/9s6osm5g/version?loaders=%5B%22fabric%22%5D&game_versions=%5B%221.19.2%22%5D
3143

@@ -42,4 +54,8 @@ GET https://api.modrinth.com/v2/project/cobblemon-fabric/version
4254
GET https://api.modrinth.com/v2/version/viPgd7Mi
4355

4456
###
45-
GET https://cdn.modrinth.com/data/9s6osm5g/versions/o9dFD9SO/cloth-config-8.3.103-fabric.jar
57+
GET https://cdn.modrinth.com/data/9s6osm5g/versions/o9dFD9SO/cloth-config-8.3.103-fabric.jar
58+
59+
###
60+
GET https://cdn.modrinth.com/data/3wmN97b8/versions/32Chhlnp/multiverse-core-4.3.13-pre.3.jar
61+
if-modified-since: Sun, 20 Aug 2024 02:15:42 GMT

src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import lombok.extern.slf4j.Slf4j;
1010
import me.itzg.helpers.McImageHelper;
1111
import me.itzg.helpers.errors.GenericException;
12+
import me.itzg.helpers.errors.InvalidParameterException;
1213
import me.itzg.helpers.files.Manifests;
1314
import me.itzg.helpers.files.ResultsFileWriter;
1415
import me.itzg.helpers.http.Fetch;
@@ -135,6 +136,9 @@ public Integer call() throws IOException {
135136

136137
newManifest = buildModpackFetcher(apiClient, projectRef)
137138
.fetchModpack(prevManifest)
139+
.onErrorMap(throwable -> throwable instanceof NoApplicableVersionsException || throwable instanceof NoFilesAvailableException,
140+
throwable -> new InvalidParameterException(throwable.getMessage(), throwable)
141+
)
138142
.flatMap(fetchedPack ->
139143
installerFactory.create(
140144
apiClient,

src/main/java/me/itzg/helpers/modrinth/ModrinthApiClient.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.function.Function;
1515
import java.util.stream.Collectors;
1616
import java.util.stream.Stream;
17+
import lombok.extern.slf4j.Slf4j;
1718
import me.itzg.helpers.errors.GenericException;
1819
import me.itzg.helpers.errors.InvalidParameterException;
1920
import me.itzg.helpers.http.Fetch;
@@ -30,6 +31,7 @@
3031
import reactor.core.publisher.Mono;
3132
import reactor.core.scheduler.Schedulers;
3233

34+
@Slf4j
3335
public class ModrinthApiClient implements AutoCloseable {
3436

3537
private final UriBuilder uriBuilder;
@@ -136,12 +138,12 @@ public Mono<Version> resolveProjectVersion(Project project, ProjectRef projectRe
136138
}
137139
if (projectRef.hasVersionType()) {
138140
return getVersionsForProject(project.getId(), loader, gameVersion)
139-
.mapNotNull(versions -> pickVersion(versions, projectRef.getVersionType()));
141+
.mapNotNull(versions -> pickVersion(project, versions, projectRef.getVersionType()));
140142
} else if (projectRef.hasVersionId()) {
141143
return getVersionFromId(projectRef.getVersionId());
142144
} else {
143145
return getVersionsForProject(project.getId(), loader, gameVersion)
144-
.mapNotNull(versions -> pickVersion(versions, defaultVersionType));
146+
.mapNotNull(versions -> pickVersion(project, versions, defaultVersionType));
145147
}
146148
}
147149

@@ -181,10 +183,9 @@ public Mono<List<Version>> getVersionsForProject(String projectIdOrSlug,
181183
.toObjectList(Version.class)
182184
.assemble()
183185
.flatMap(versions ->
184-
versions.isEmpty() ? Mono.error(new InvalidParameterException(
185-
String.format("No files are available for the project %s for loader %s and Minecraft version %s",
186-
projectIdOrSlug, loader, gameVersion
187-
)))
186+
versions.isEmpty() ?
187+
getProject(projectIdOrSlug)
188+
.flatMap(project -> Mono.error(new NoFilesAvailableException(project, loader, gameVersion)))
188189
: Mono.just(versions)
189190
);
190191
}
@@ -213,12 +214,15 @@ public Mono<Version> getVersionFromId(String versionId) {
213214
.assemble();
214215
}
215216

216-
private Version pickVersion(List<Version> versions, VersionType versionType) {
217+
private Version pickVersion(Project project, List<Version> versions, VersionType versionType) {
217218
for (final Version version : versions) {
218219
if (version.getVersionType().sufficientFor(versionType)) {
219220
return version;
220221
}
221222
}
223+
if (!versions.isEmpty()) {
224+
throw new NoApplicableVersionsException(project, versions, versionType);
225+
}
222226
return null;
223227
}
224228

src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java

+35-73
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import me.itzg.helpers.errors.GenericException;
2424
import me.itzg.helpers.errors.InvalidParameterException;
2525
import me.itzg.helpers.files.Manifests;
26+
import me.itzg.helpers.http.Fetch;
2627
import me.itzg.helpers.http.SharedFetchArgs;
27-
import me.itzg.helpers.http.Uris;
2828
import me.itzg.helpers.json.ObjectMappers;
2929
import me.itzg.helpers.modrinth.model.DependencyType;
3030
import me.itzg.helpers.modrinth.model.Project;
@@ -109,6 +109,7 @@ private List<Path> processProjects(List<String> projects) {
109109
return
110110
modrinthApiClient.bulkGetProjects(
111111
projects.stream()
112+
.filter(s -> !s.trim().isEmpty())
112113
.map(ProjectRef::parse)
113114
)
114115
.defaultIfEmpty(Collections.emptyList())
@@ -148,31 +149,28 @@ private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient,
148149
.filter(dep -> projectsProcessed.add(dep.getProjectId()))
149150
.flatMap(dep -> {
150151
projectsProcessed.add(dep.getProjectId());
151-
try {
152-
final Version depVersion;
153-
if (dep.getVersionId() == null) {
154-
log.debug("Fetching versions of dep={} and picking", dep);
155-
depVersion = pickVersion(
156-
getVersionsForProject(modrinthApiClient, dep.getProjectId())
157-
);
158-
}
159-
else {
160-
log.debug("Fetching version for dep={}", dep);
161-
depVersion = getVersion(dep.getVersionId());
162-
}
163-
if (depVersion != null) {
164-
log.debug("Resolved version={} for dep={}", depVersion.getVersionNumber(), dep);
165-
return Stream.concat(
166-
Stream.of(depVersion),
167-
expandDependencies(modrinthApiClient, depVersion)
168-
)
169-
.peek(expandedVer -> log.debug("Expanded dependency={} into version={}", dep, expandedVer));
170-
}
171-
else {
172-
return Stream.empty();
173-
}
174-
} catch (IOException e) {
175-
throw new RuntimeException(e);
152+
final Version depVersion;
153+
if (dep.getVersionId() == null) {
154+
log.debug("Fetching versions of dep={} and picking", dep);
155+
depVersion = pickVersion(
156+
getVersionsForProject(modrinthApiClient, dep.getProjectId())
157+
);
158+
}
159+
else {
160+
log.debug("Fetching version for dep={}", dep);
161+
depVersion = modrinthApiClient.getVersionFromId(dep.getVersionId())
162+
.block();
163+
}
164+
if (depVersion != null) {
165+
log.debug("Resolved version={} for dep={}", depVersion.getVersionNumber(), dep);
166+
return Stream.concat(
167+
Stream.of(depVersion),
168+
expandDependencies(modrinthApiClient, depVersion)
169+
)
170+
.peek(expandedVer -> log.debug("Expanded dependency={} into version={}", dep, expandedVer));
171+
}
172+
else {
173+
return Stream.empty();
176174
}
177175
});
178176

@@ -190,15 +188,6 @@ private boolean filterDependency(VersionDependency dep) {
190188
);
191189
}
192190

193-
private Version getVersion(String versionId) throws IOException {
194-
return fetch(Uris.populateToUri(
195-
baseUrl + "/v2/version/{id}", versionId
196-
))
197-
.userAgentCommand("modrinth")
198-
.toObject(Version.class)
199-
.execute();
200-
}
201-
202191
private Version pickVersion(List<Version> versions) {
203192
return this.pickVersion(versions, defaultVersionType);
204193
}
@@ -213,13 +202,6 @@ private Version pickVersion(List<Version> versions, VersionType versionType) {
213202
}
214203

215204
private Path download(ProjectType projectType, VersionFile versionFile) {
216-
if (log.isDebugEnabled()) {
217-
log.debug("Downloading {}", versionFile);
218-
}
219-
else {
220-
log.info("Downloading {}", versionFile.getFilename());
221-
}
222-
223205
if (projectType != ProjectType.mod) {
224206
throw new InvalidParameterException("Only mod project types can be downloaded for now");
225207
}
@@ -236,6 +218,7 @@ private Path download(ProjectType projectType, VersionFile versionFile) {
236218
.userAgentCommand("modrinth")
237219
.toFile(outPath)
238220
.skipUpToDate(true)
221+
.handleStatus(Fetch.loggingDownloadStatusHandler(log))
239222
.execute();
240223
} catch (IOException e) {
241224
throw new RuntimeException("Downloading mod file", e);
@@ -253,22 +236,20 @@ private List<Version> getVersionsForProject(ModrinthApiClient modrinthApiClient,
253236
return versions;
254237
}
255238

256-
private Version getVersionFromId(String versionId) {
257-
return fetch(Uris.populateToUri(
258-
baseUrl + "/v2/version/{id}",
259-
versionId
260-
))
261-
.userAgentCommand("modrinth")
262-
.toObject(Version.class)
263-
.execute();
264-
}
265-
266239

267240
private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, ProjectRef projectRef, Project project) {
268241
log.debug("Starting with projectRef={}", projectRef);
269242

270243
if (projectsProcessed.add(project.getId())) {
271-
final Version version = resolveProjectVersion(modrinthApiClient, project, projectRef);
244+
final Version version;
245+
try {
246+
version = modrinthApiClient.resolveProjectVersion(
247+
project, projectRef, loader, gameVersion, defaultVersionType
248+
)
249+
.block();
250+
} catch (NoApplicableVersionsException | NoFilesAvailableException e) {
251+
throw new InvalidParameterException(e.getMessage(), e);
252+
}
272253

273254
if (version != null) {
274255
if (version.getFiles().isEmpty()) {
@@ -287,29 +268,10 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project
287268
throw new InvalidParameterException(
288269
String.format("Project %s does not have any matching versions for loader %s, game version %s",
289270
projectRef, loader, gameVersion
290-
));
291-
}
292-
}
293-
return Stream.empty();
294-
}
295-
296-
private Version resolveProjectVersion(ModrinthApiClient modrinthApiClient, Project project, ProjectRef projectRef) {
297-
if (projectRef.hasVersionType()) {
298-
return pickVersion(getVersionsForProject(modrinthApiClient, project.getId()), projectRef.getVersionType());
299-
}
300-
else if (projectRef.hasVersionId()) {
301-
return getVersionFromId(projectRef.getVersionId());
302-
}
303-
else {
304-
final List<Version> versions = getVersionsForProject(modrinthApiClient, project.getId());
305-
if (versions.isEmpty()) {
306-
throw new InvalidParameterException(
307-
String.format("No files are available for the project '%s' (%s) for %s loader and Minecraft version %s",
308-
project.getTitle(), project.getSlug(), loader, gameVersion
309271
));
310272
}
311-
return pickVersion(versions);
312273
}
274+
return Stream.empty();
313275
}
314276

315277
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package me.itzg.helpers.modrinth;
2+
3+
import java.util.List;
4+
import java.util.stream.Collectors;
5+
import lombok.Getter;
6+
import lombok.ToString;
7+
import me.itzg.helpers.modrinth.model.Project;
8+
import me.itzg.helpers.modrinth.model.Version;
9+
import me.itzg.helpers.modrinth.model.VersionType;
10+
11+
@Getter
12+
@ToString
13+
public class NoApplicableVersionsException extends RuntimeException {
14+
15+
private final Project project;
16+
private final List<Version> versions;
17+
private final VersionType versionType;
18+
19+
public NoApplicableVersionsException(Project project, List<Version> versions, VersionType versionType) {
20+
super(
21+
String.format("No candidate versions of '%s' [%s] matched versionType=%s",
22+
project.getTitle(),
23+
versions.stream().map(version -> version.getVersionNumber() + "=" + version.getVersionType())
24+
.collect(Collectors.joining(", ")),
25+
versionType
26+
)
27+
);
28+
29+
this.project = project;
30+
this.versions = versions;
31+
this.versionType = versionType;
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package me.itzg.helpers.modrinth;
2+
3+
import lombok.Getter;
4+
import lombok.ToString;
5+
import me.itzg.helpers.modrinth.model.Project;
6+
import org.jetbrains.annotations.Nullable;
7+
8+
@Getter
9+
@ToString
10+
public class NoFilesAvailableException extends RuntimeException {
11+
12+
private final Project project;
13+
private final Loader loader;
14+
private final String gameVersion;
15+
16+
public NoFilesAvailableException(Project project, @Nullable Loader loader, String gameVersion) {
17+
super(String.format("No files are available for the project '%s' for loader %s and Minecraft version %s",
18+
project.getTitle(), loader, gameVersion
19+
));
20+
21+
this.project = project;
22+
this.loader = loader;
23+
this.gameVersion = gameVersion;
24+
}
25+
}

0 commit comments

Comments
 (0)