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
34 changes: 19 additions & 15 deletions src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");

Expand All @@ -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");

Expand All @@ -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 ->
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -241,6 +252,9 @@ private int hashModpackFileReferences(List<ManifestFileRef> files) {
return hash;
}

/**
* @throws MissingModsException if any mods need to be manually downloaded
*/
private void resolveModpackFileAndProcess(
InstallContext context,
CurseForgeMod mod, Integer fileId,
Expand Down Expand Up @@ -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())
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
})
Expand Down Expand Up @@ -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/(?<slug>.+?)(/files(/(?<fileId>\\d+)?)?)?");

Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/me/itzg/helpers/curseforge/MissingModsException.java
Original file line number Diff line number Diff line change
@@ -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<PathWithInfo> needsDownload;

public MissingModsException(List<PathWithInfo> needsDownload) {

this.needsDownload = needsDownload;
}
}
63 changes: 63 additions & 0 deletions src/main/java/me/itzg/helpers/files/TabularOutput.java
Original file line number Diff line number Diff line change
@@ -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<String[]> 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);
}
}
}