From 588405a0699deb769fbb4e9a33a9eb8cf5adafe6 Mon Sep 17 00:00:00 2001 From: Geoff Bourne Date: Tue, 11 Jul 2023 22:48:42 -0500 Subject: [PATCH] cf: fail if any manual mod downloads are needed --- .../curseforge/CurseForgeInstaller.java | 34 ++++---- .../curseforge/InstallCurseForgeCommand.java | 81 +++++++++++++++---- .../curseforge/MissingModsException.java | 15 ++++ .../me/itzg/helpers/files/TabularOutput.java | 63 +++++++++++++++ 4 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 src/main/java/me/itzg/helpers/curseforge/MissingModsException.java create mode 100644 src/main/java/me/itzg/helpers/files/TabularOutput.java diff --git a/src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java b/src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java index 0cf1165d..595189ce 100644 --- a/src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java +++ b/src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java @@ -20,7 +20,6 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -91,6 +90,9 @@ public class CurseForgeInstaller { "worlds" )); + /** + * @throws MissingModsException if any mods need to be manually downloaded + */ public void installFromModpackZip(Path modpackZip, String slug) throws IOException { requireNonNull(modpackZip, "modpackZip is required"); @@ -103,6 +105,9 @@ public void installFromModpackZip(Path modpackZip, String slug) throws IOExcepti }); } + /** + * @throws MissingModsException if any mods need to be manually downloaded + */ public void installFromModpackManifest(Path modpackManifestPath, String slug) throws IOException { requireNonNull(modpackManifestPath, "modpackManifest is required"); @@ -116,6 +121,9 @@ public void installFromModpackManifest(Path modpackManifestPath, String slug) th }); } + /** + * @throws MissingModsException if any mods need to be manually downloaded + */ public void install(String slug, String fileMatcher, Integer fileId) throws IOException { install(slug, context -> @@ -188,6 +196,9 @@ private void installByRetrievingModpackZip(InstallContext context, String fileMa resolveModpackFileAndProcess(context, curseForgeMod, fileId, fileMatcher); } + /** + * @throws MissingModsException if any mods need to be manually downloaded + */ private void processModpackManifest(InstallContext context, MinecraftModpackManifest modpackManifest, OverridesApplier overridesApplier ) throws IOException { @@ -241,6 +252,9 @@ private int hashModpackFileReferences(List files) { return hash; } + /** + * @throws MissingModsException if any mods need to be manually downloaded + */ private void resolveModpackFileAndProcess( InstallContext context, CurseForgeMod mod, Integer fileId, @@ -376,6 +390,9 @@ private void finalizeExistingInstallation(CurseForgeManifest prevManifest) throw } } + /** + * @throws MissingModsException if any mods need to be manually downloaded + */ private void finalizeResults(InstallContext context, ModPackResults results, int modId, int fileId, String displayName) throws IOException { final CurseForgeManifest newManifest = CurseForgeManifest.builder() .modpackName(results.getName()) @@ -394,21 +411,8 @@ private void finalizeResults(InstallContext context, ModPackResults results, int Manifests.save(outputDir, CURSEFORGE_ID, newManifest); - final Path needsDownloadFile = outputDir.resolve("MODS_NEED_DOWNLOAD.txt"); if (!results.getNeedsDownload().isEmpty()) { - try (BufferedWriter writer = Files.newBufferedWriter(needsDownloadFile)) { - for (PathWithInfo info : results.getNeedsDownload()) { - writer.write(String.format("%s :: \"%s\" FROM %s", - info.getModInfo().getName(), - info.getCurseForgeFile().getDisplayName(), - info.getModInfo().getLinks().getWebsiteUrl() - )); - writer.newLine(); - } - } - } - else { - Files.deleteIfExists(needsDownloadFile); + throw new MissingModsException(results.getNeedsDownload()); } if (resultsFile != null) { diff --git a/src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java b/src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java index c3b97cd8..bf1cdf18 100644 --- a/src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java +++ b/src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java @@ -1,16 +1,7 @@ package me.itzg.helpers.curseforge; -import static me.itzg.helpers.http.Fetch.fetch; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; import me.itzg.helpers.files.ResultsFileWriter; +import me.itzg.helpers.files.TabularOutput; import me.itzg.helpers.http.PathOrUri; import me.itzg.helpers.http.PathOrUriConverter; import me.itzg.helpers.http.SharedFetchArgs; @@ -20,6 +11,20 @@ import picocli.CommandLine.ExitCode; import picocli.CommandLine.Option; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static me.itzg.helpers.http.Fetch.fetch; + @Command(name = "install-curseforge", subcommands = { SchemasCommand.class }) @@ -132,6 +137,9 @@ static class Listed { @ArgGroup(exclusive = false) SharedFetchArgs sharedFetchArgs = new SharedFetchArgs(); + @Option(names = "--missing-mods-filename", defaultValue = "MODS_NEED_DOWNLOAD.txt") + String missingModsFilename; + private static final Pattern PAGE_URL_PATTERN = Pattern.compile( "https://(www|beta)\\.curseforge\\.com/minecraft/modpacks/(?.+?)(/files(/(?\\d+)?)?)?"); @@ -181,15 +189,54 @@ public Integer call() throws Exception { installer.setApiBaseUrl(apiBaseUrl); } - if (modpackZip != null) { - installer.installFromModpackZip(modpackZip, slug); - } else if (modpackManifest != null) { - installer.installFromModpackManifest(modpackManifest, slug); - } else { - installer.install(slug, filenameMatcher, fileId); + final Path needsDownloadFile = missingModsFilename != null && !missingModsFilename.isEmpty() ? + outputDirectory.resolve(missingModsFilename) + : null; + + try { + if (modpackZip != null) { + installer.installFromModpackZip(modpackZip, slug); + } else if (modpackManifest != null) { + installer.installFromModpackManifest(modpackManifest, slug); + } else { + installer.install(slug, filenameMatcher, fileId); + } + if (needsDownloadFile != null) { + Files.deleteIfExists(needsDownloadFile); + } + + return ExitCode.OK; + } catch (MissingModsException e) { + + final TabularOutput tabOut = new TabularOutput('=', " ", "Mod", "Slug", "Filename", "Download page"); + for (PathWithInfo info : e.getNeedsDownload()) { + tabOut.addRow( + info.getModInfo().getName(), + info.getModInfo().getSlug(), + info.getCurseForgeFile().getDisplayName(), + info.getModInfo().getLinks().getWebsiteUrl() + ); + } + + if (needsDownloadFile != null) { + try (BufferedWriter writer = Files.newBufferedWriter(needsDownloadFile)) { + tabOut.output(new PrintWriter(writer)); + } + } + + System.err.println(); + System.err.println("Missing mods need to be manually downloaded into repo or excluded:"); + if (needsDownloadFile != null) { + System.err.printf("(Also written to %s%n", needsDownloadFile); + } + System.err.println(); + final PrintWriter out = new PrintWriter(System.err); + tabOut.output(out); + out.close(); + + return ExitCode.USAGE; } - return ExitCode.OK; } private ExcludeIncludesContent loadExcludeIncludes() throws IOException { diff --git a/src/main/java/me/itzg/helpers/curseforge/MissingModsException.java b/src/main/java/me/itzg/helpers/curseforge/MissingModsException.java new file mode 100644 index 00000000..c64dab75 --- /dev/null +++ b/src/main/java/me/itzg/helpers/curseforge/MissingModsException.java @@ -0,0 +1,15 @@ +package me.itzg.helpers.curseforge; + +import lombok.Getter; + +import java.util.List; + +public class MissingModsException extends RuntimeException { + @Getter + private final List needsDownload; + + public MissingModsException(List needsDownload) { + + this.needsDownload = needsDownload; + } +} diff --git a/src/main/java/me/itzg/helpers/files/TabularOutput.java b/src/main/java/me/itzg/helpers/files/TabularOutput.java new file mode 100644 index 00000000..42b59b07 --- /dev/null +++ b/src/main/java/me/itzg/helpers/files/TabularOutput.java @@ -0,0 +1,63 @@ +package me.itzg.helpers.files; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public class TabularOutput { + private final char headerDivider; + private final String columnDivider; + private final String[] headers; + private final List rows = new ArrayList<>(); + private final int[] widths; + + public TabularOutput(char headerDivider, String columnDivider, String... headers) { + this.headerDivider = headerDivider; + this.columnDivider = columnDivider; + this.headers = headers; + this.widths = new int[headers.length]; + for (int i = 0; i < headers.length; i++) { + widths[i] = headers[i].length(); + } + } + + public void addRow(String... cells) { + if (cells.length != headers.length) { + throw new IllegalArgumentException(String.format("Row has %d columns but header has %d", cells.length, headers.length)); + } + + rows.add(cells); + + for (int i = 0; i < cells.length; i++) { + widths[i] = Math.max(widths[i], cells[i].length()); + } + } + + public void output(PrintWriter out) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < widths.length; i++) { + sb.append("%-").append(widths[i]).append("s"); + if (i < widths.length - 1) { + sb.append(columnDivider); + } + } + sb.append("%n"); + final String format = sb.toString(); + + out.printf(format, (Object[]) headers); + + for (int i = 0; i < widths.length; i++) { + for (int j = 0; j < widths[i]; j++) { + out.print(headerDivider); + } + if (i < widths.length - 1) { + out.print(columnDivider); + } + } + out.println(); + + for (String[] row : rows) { + out.printf(format, (Object[]) row); + } + } +}