diff --git a/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesCommand.java b/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesCommand.java index 059f6986..4d1fdd40 100644 --- a/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesCommand.java +++ b/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesCommand.java @@ -20,6 +20,7 @@ import me.itzg.helpers.curseforge.model.CurseForgeFile; import me.itzg.helpers.curseforge.model.CurseForgeMod; import me.itzg.helpers.curseforge.model.FileDependency; +import me.itzg.helpers.curseforge.model.FileRelationType; import me.itzg.helpers.curseforge.model.ModLoaderType; import me.itzg.helpers.errors.GenericException; import me.itzg.helpers.errors.InvalidParameterException; @@ -34,6 +35,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; @Command(name = "curseforge-files", description = "Download and manage individual mod/plugin files from CurseForge") @Slf4j @@ -168,31 +171,70 @@ private Mono> processModFileRefs(CategoryInfo categoryInfo, return Flux.fromIterable(modFiles) .flatMap(modFile -> { - for (final FileDependency dependency : modFile.getDependencies()) { - if (!requestedModIds.contains(dependency.getModId())) { - log.warn("The mod file {} depends on mod project ID {}, but that is not listed", - modFile.getDisplayName(), dependency.getModId() - ); - } - } + + final Mono retrieval; final ModFileIds modFileIds = idsFrom(modFile); final FileEntry entry = previousFiles.get(modFileIds); if (entry != null && Files.exists(outputDir.resolve(entry.getFilePath())) ) { log.debug("Mod file {} already exists at {}", modFile.getFileName(), entry.getFilePath()); - return Mono.just(entry); + retrieval = Mono.just(entry); } else { - return retrieveModFile(apiClient, categoryInfo, modFile) + retrieval = retrieveModFile(apiClient, categoryInfo, modFile) .map(path -> new FileEntry(modFileIds, outputDir.relativize(path).toString())); } + + return reportMissingDependencies(apiClient, modFile, requestedModIds) + .then(retrieval); }); } ) .collectList(); } + /** + * + * @return flux of missing, required dependencies + */ + private static Flux> reportMissingDependencies(CurseForgeApiClient apiClient, + CurseForgeFile modFile, Set requestedModIds + ) { + return Flux.fromIterable(modFile.getDependencies()) + .mapNotNull(dependency -> { + if (!requestedModIds.contains(dependency.getModId())) { + switch (dependency.getRelationType()) { + case RequiredDependency: + case OptionalDependency: + return dependency; + default: + return null; + } + } + else { + return null; + } + }) + .flatMap(dependency -> + apiClient.getModInfo(dependency.getModId()) + .map(curseForgeMod -> Tuples.of(curseForgeMod, dependency)) + ) + .doOnNext(missingDep -> { + if (missingDep.getT2().getRelationType() == FileRelationType.RequiredDependency) { + log.warn("The mod file '{}' depends on mod project '{}' at {}, but it is not listed", + modFile.getDisplayName(), missingDep.getT1().getName(), missingDep.getT1().getLinks().getWebsiteUrl() + ); + } + else if (missingDep.getT2().getRelationType() == FileRelationType.OptionalDependency) { + log.debug("The mod file '{}' optionally depends on mod project '{}', but it is not listed", + modFile.getDisplayName(), missingDep.getT1().getName() + ); + } + }) + .filter(missingDep -> missingDep.getT2().getRelationType() == FileRelationType.RequiredDependency); + } + @NotNull private static Map buildPreviousFilesFromManifest(CurseForgeFilesManifest oldManifest) { return oldManifest != null ? diff --git a/src/test/resources/curseforge/mappings/v1_mods_31043.json b/src/test/resources/curseforge/mappings/v1_mods_31043.json new file mode 100644 index 00000000..bfb349ec --- /dev/null +++ b/src/test/resources/curseforge/mappings/v1_mods_31043.json @@ -0,0 +1,24 @@ +{ + "name" : "v1_mods_31043", + "request" : { + "url" : "/v1/mods/31043", + "method" : "GET" + }, + "response" : { + "status" : 200, + "jsonBody" : { + "data": { + "name": "WorldEdit for Bukkit", + "links": { + "websiteUrl": "https://www.curseforge.com/minecraft/bukkit-plugins/worldedit", + "wikiUrl": "https://worldedit.enginehub.org/en/latest/", + "issuesUrl": "https://github.com/EngineHub/WorldEdit/issues", + "sourceUrl": "https://github.com/EngineHub/WorldEdit" + } + } + }, + "headers" : { + "Content-Type" : "application/json; charset=utf-8" + } + } +} \ No newline at end of file