Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import static java.util.Objects.requireNonNull;
import static me.itzg.helpers.curseforge.MoreCollections.safeStreamFrom;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
Expand Down Expand Up @@ -32,6 +34,7 @@
import me.itzg.helpers.curseforge.model.Category;
import me.itzg.helpers.curseforge.model.CurseForgeFile;
import me.itzg.helpers.curseforge.model.CurseForgeMod;
import me.itzg.helpers.curseforge.model.CurseForgeResponse;
import me.itzg.helpers.curseforge.model.GetCategoriesResponse;
import me.itzg.helpers.curseforge.model.GetModFileResponse;
import me.itzg.helpers.curseforge.model.GetModFilesResponse;
Expand All @@ -41,10 +44,12 @@
import me.itzg.helpers.curseforge.model.ModLoader;
import me.itzg.helpers.curseforge.model.ModsSearchResponse;
import me.itzg.helpers.errors.GenericException;
import me.itzg.helpers.errors.InvalidParameterException;
import me.itzg.helpers.fabric.FabricLauncherInstaller;
import me.itzg.helpers.files.Manifests;
import me.itzg.helpers.files.ResultsFileWriter;
import me.itzg.helpers.forge.ForgeInstaller;
import me.itzg.helpers.http.FailedRequestException;
import me.itzg.helpers.http.Fetch;
import me.itzg.helpers.http.SharedFetch;
import me.itzg.helpers.http.SharedFetch.Options;
Expand Down Expand Up @@ -369,12 +374,12 @@ private ExcludeIncludeIds resolveExcludeIncludes(
SharedFetch preparedFetch, UriBuilder uriBuilder, CategoryInfo categoryInfo,
String modpackSlug
) {
log.debug("Reconciling exclude/includes from given {}", excludeIncludes);

if (excludeIncludes == null) {
return new ExcludeIncludeIds(emptySet(), emptySet());
}

log.debug("Reconciling exclude/includes from given {}", excludeIncludes);

final ExcludeIncludes specific =
excludeIncludes.getModpacks() != null ? excludeIncludes.getModpacks().get(modpackSlug) : null;

Expand Down Expand Up @@ -679,9 +684,29 @@ private static Mono<CurseForgeFile> getModFileInfo(SharedFetch preparedFetch, Ur
)
.toObject(GetModFileResponse.class)
.assemble()
.onErrorMap(FailedRequestException.class::isInstance, e -> {
final FailedRequestException fre = (FailedRequestException) e;
if (fre.getStatusCode() == 400) {
if (isNotFoundResponse(fre.getBody())) {
return new InvalidParameterException("Requested file not found for modpack", e);
}
}
return e;
})
.map(GetModFileResponse::getData);
}

private static boolean isNotFoundResponse(String body) {
try {
final CurseForgeResponse<Void> resp = ObjectMappers.defaultMapper().readValue(
body, new TypeReference<CurseForgeResponse<Void>>() {}
);
return resp.getError().startsWith("Error: 404");
} catch (JsonProcessingException e) {
throw new GenericException("Unable to parse error response", e);
}
}

private static Mono<CurseForgeMod> getModInfo(
SharedFetch preparedFetch, UriBuilder uriBuilder,
int projectID
Expand Down Expand Up @@ -733,7 +758,9 @@ private MinecraftModpackManifest extractModpackManifest(Path modpackZip) throws
}
}

throw new GenericException("Modpack is missing manifest.json. Make sure to reference a non-server file.");
throw new InvalidParameterException(
"Modpack file is missing a manifest. Make sure to reference a non-server file."
);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.itzg.helpers.curseforge.model;

import lombok.Data;

@Data
public class CurseForgeResponse<T> {
private boolean ok;
private String error;
private T data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@
import io.netty.handler.codec.http.HttpResponseStatus;
import java.net.URI;
import lombok.Getter;
import lombok.ToString;

@Getter @ToString
public class FailedRequestException extends RuntimeException {

@Getter
private final URI uri;
@Getter
private final int statusCode;
private final String body;

/**
* Reactor Netty flavor
*/
public FailedRequestException(HttpResponseStatus status, URI uri, String msg) {
public FailedRequestException(HttpResponseStatus status, URI uri, String body, String msg) {
super(
String.format("HTTP request of %s failed with %s: %s", uri, status, msg)
);
this.uri = uri;
this.statusCode = status.code();
this.body = body;
}

@SuppressWarnings("unused")
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/me/itzg/helpers/http/FetchBuilderBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import me.itzg.helpers.json.ObjectMappers;
import org.slf4j.Logger;
import reactor.core.publisher.Mono;
import reactor.netty.ByteBufMono;
import reactor.netty.Connection;
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.client.HttpClientRequest;
Expand Down Expand Up @@ -144,8 +145,10 @@ protected <R> R useReactiveClient(ReactiveClientUser<R> user) {
);
}

protected <R> Mono<R> failedRequestMono(HttpClientResponse resp, String description) {
return Mono.error(new FailedRequestException(resp.status(), uri(), description));
protected <R> Mono<R> failedRequestMono(HttpClientResponse resp, ByteBufMono bodyMono, String description) {
return (bodyMono != null ? bodyMono.asString() : Mono.just(""))
.defaultIfEmpty("")
.flatMap(body -> Mono.error(new FailedRequestException(resp.status(), uri(), body, description)));
}

protected static boolean notSuccess(HttpClientResponse resp) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/me/itzg/helpers/http/ObjectFetchBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private <R> Mono<R> assembleCommon() {

private <R> Mono<R> handleResponse(HttpClientResponse resp, ByteBufMono bodyMono) {
if (notSuccess(resp)) {
return failedRequestMono(resp, "Fetching object content");
return failedRequestMono(resp, bodyMono, "Fetching object content");
}
if (notExpectedContentType(resp)) {
return failedContentTypeMono(resp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,10 @@ public Mono<Path> assemble() {
.doOnRequest(debugLogRequest(log, "file head fetch"))
.head()
.uri(uri())
.response()
.flatMap(resp ->
notSuccess(resp) ? failedRequestMono(resp, "Extracting filename")
.responseSingle((resp, bodyMono) ->
notSuccess(resp) ? failedRequestMono(resp, bodyMono, "Extracting filename")
: Mono.just(outputDirectory.resolve(extractFilename(resp)))
)
)
.flatMap(outputFile ->
assembleFileDownload(client, outputFile)
)
Expand All @@ -84,12 +83,12 @@ private Mono<Path> assembleFileDownload(HttpClient client, Path outputFile) {
.doOnRequest(debugLogRequest(log, "file fetch"))
.get()
.uri(uri())
.responseSingle((resp, byteBufMono) -> {
.responseSingle((resp, bodyMono) -> {
if (notSuccess(resp)) {
return failedRequestMono(resp, "Downloading file");
return failedRequestMono(resp, bodyMono, "Downloading file");
}

return byteBufMono.asInputStream()
return bodyMono.asInputStream()
.publishOn(Schedulers.boundedElastic())
.flatMap(inputStream -> {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public Mono<Path> assemble() {
}

if (notSuccess(resp)) {
return failedRequestMono(resp, "Trying to retrieve file");
return failedRequestMono(resp, bodyMono, "Trying to retrieve file");
}

if (notExpectedContentType(resp)) {
Expand Down