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: 33 additions & 0 deletions src/main/java/me/itzg/helpers/modrinth/ExcludeIncludesContent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package me.itzg.helpers.modrinth;

import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle;
import java.util.Map;
import java.util.Set;
import lombok.Data;

/**
* Similar to {@link me.itzg.helpers.curseforge.ExcludeIncludesContent}, but trimmed down to match
* supported functionality
*/
@JsonSchemaTitle("Mods Exclude File Content")
@Data
public class ExcludeIncludesContent {

@JsonPropertyDescription("Mods/files by slug|id to exclude for all modpacks")
private Set<String> globalExcludes;
@JsonPropertyDescription("Mods by slug|id to force include for all modpacks")
private Set<String> globalForceIncludes;

@JsonPropertyDescription("Specific exclude/includes by modpack slug")
private Map<String, me.itzg.helpers.curseforge.ExcludeIncludesContent.ExcludeIncludes> modpacks;

@Data
public static class ExcludeIncludes {
@JsonPropertyDescription("Mods by slug|id to exclude for this modpack")
private Set<String> excludes;
@JsonPropertyDescription("Mods by slug|id to force include for this modpack")
private Set<String> forceIncludes;
}

}
110 changes: 110 additions & 0 deletions src/main/java/me/itzg/helpers/modrinth/FileInclusionCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package me.itzg.helpers.modrinth;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import me.itzg.helpers.curseforge.ExcludeIncludesContent.ExcludeIncludes;
import me.itzg.helpers.modrinth.model.Env;
import me.itzg.helpers.modrinth.model.EnvType;
import me.itzg.helpers.modrinth.model.ModpackIndex;

@Slf4j
public class FileInclusionCalculator {

private final Set<String> excludeFiles;
private final Set<String> forceIncludeFiles;

public FileInclusionCalculator(
String modpackProjectSlug,
List<String> excludeFiles,
List<String> forceIncludeFiles,
ExcludeIncludesContent excludeIncludesContent) {

this.excludeFiles = new HashSet<>();
this.forceIncludeFiles = new HashSet<>();

if (excludeFiles != null) {
this.excludeFiles.addAll(excludeFiles);
}
if (forceIncludeFiles != null) {
this.forceIncludeFiles.addAll(forceIncludeFiles);
}
if (excludeIncludesContent != null) {
addAll(excludeIncludesContent.getGlobalExcludes(), this.excludeFiles);
addAll(excludeIncludesContent.getGlobalForceIncludes(), this.forceIncludeFiles);

if (excludeIncludesContent.getModpacks() != null && modpackProjectSlug != null) {
final ExcludeIncludes modpack = excludeIncludesContent.getModpacks().get(modpackProjectSlug);
if (modpack != null) {
addAll(modpack.getExcludes(), this.excludeFiles);
addAll(modpack.getForceIncludes(), this.forceIncludeFiles);
}
}
}
}

public static FileInclusionCalculator empty() {
return new FileInclusionCalculator(null, null, null, null);
}

boolean includeModFile(ModpackIndex.ModpackFile modFile) {
return (
// env is optional
modFile.getEnv() == null
|| modFile.getEnv().get(Env.server) != EnvType.unsupported
|| shouldForceIncludeFile(modFile.getPath())
)
&& !shouldExcludeFile(modFile.getPath());
}

private boolean shouldForceIncludeFile(String modPath) {
if (forceIncludeFiles == null || forceIncludeFiles.isEmpty()) {
return false;
}

final String normalized = FileInclusionCalculator.sanitizeModFilePath(modPath).toLowerCase();

final boolean include = forceIncludeFiles.stream()
.anyMatch(s -> normalized.contains(s.toLowerCase()));
if (include) {
log.debug("Force including '{}' as requested", modPath);
}

return include;
}

private boolean shouldExcludeFile(String modPath) {
if (excludeFiles == null || excludeFiles.isEmpty()) {
return false;
}

// to match case-insensitive
final String normalized = FileInclusionCalculator.sanitizeModFilePath(modPath).toLowerCase();

final boolean exclude = excludeFiles.stream()
.anyMatch(s -> normalized.contains(s.toLowerCase()));
if (exclude) {
log.debug("Excluding '{}' as requested", modPath);
}
return exclude;
}


static String sanitizeModFilePath(String path) {
// Using only backslash delimiters and not forward slashes?
// (mixed usage will assume backslashes were purposeful)
if (path.contains("\\") && !path.contains("/")) {
return path.replace("\\", "/");
}
else {
return path;
}
}

private void addAll(Set<String> from, Set<String> into) {
if (from != null) {
into.addAll(from);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import me.itzg.helpers.McImageHelper;
import me.itzg.helpers.errors.GenericException;
import me.itzg.helpers.files.Manifests;
import me.itzg.helpers.files.ResultsFileWriter;
import me.itzg.helpers.http.Fetch;
import me.itzg.helpers.http.PathOrUri;
import me.itzg.helpers.http.PathOrUriConverter;
import me.itzg.helpers.http.SharedFetch;
import me.itzg.helpers.http.SharedFetchArgs;
import me.itzg.helpers.json.ObjectMappers;
import me.itzg.helpers.modrinth.model.VersionType;
import org.jetbrains.annotations.VisibleForTesting;
import picocli.CommandLine;
Expand Down Expand Up @@ -101,6 +105,13 @@ public class InstallModrinthModpackCommand implements Callable<Integer> {
)
List<String> overridesExclusions;

@Option(names = "--default-exclude-includes", paramLabel = "FILE|URI",
description = "A JSON file that contains global and per modpack exclude/include declarations. "
+ "See README for schema.",
converter = PathOrUriConverter.class
)
PathOrUri defaultExcludeIncludes;

@CommandLine.ArgGroup(exclusive = false)
SharedFetchArgs sharedFetchArgs = new SharedFetchArgs();

Expand All @@ -125,9 +136,14 @@ public Integer call() throws IOException {
newManifest = buildModpackFetcher(apiClient, projectRef)
.fetchModpack(prevManifest)
.flatMap(fetchedPack ->
installerFactory.create(apiClient, fetchedPack.getMrPackFile())
.setExcludeFiles(excludeFiles)
.setForceIncludeFiles(forceIncludeFiles)
installerFactory.create(
apiClient,
fetchedPack.getMrPackFile(),
createFileInclusionCalculator(
fetchedPack.getProjectSlug(),
sharedFetch
)
)
.setOverridesExclusions(overridesExclusions)
.processModpack(sharedFetch)
.flatMap(installation -> {
Expand Down Expand Up @@ -157,20 +173,50 @@ public Integer call() throws IOException {
return ExitCode.OK;
}

private FileInclusionCalculator createFileInclusionCalculator(
String projectSlug,
SharedFetch sharedFetch
) {

final ExcludeIncludesContent excludeIncludesContent;
if (defaultExcludeIncludes == null) {
excludeIncludesContent = null;
}
else if (defaultExcludeIncludes.getPath() != null) {
try {
excludeIncludesContent = ObjectMappers.defaultMapper()
.readValue(defaultExcludeIncludes.getPath().toFile(), ExcludeIncludesContent.class);
} catch (IOException e) {
throw new GenericException("Failed to read exclude/include file", e);
}
}
else {
excludeIncludesContent = sharedFetch.fetch(defaultExcludeIncludes.getUri())
.toObject(ExcludeIncludesContent.class)
.acceptContentTypes(null)
.execute();
}

return new FileInclusionCalculator(projectSlug, excludeFiles, forceIncludeFiles, excludeIncludesContent);
}

@VisibleForTesting
@FunctionalInterface
interface ModrinthModpackInstallerFactory {

ModrinthPackInstaller create(ModrinthApiClient apiClient, Path mrPackFile);
ModrinthPackInstaller create(ModrinthApiClient apiClient, Path mrPackFile,
FileInclusionCalculator fileInclusionCalculator
);
}

@VisibleForTesting
@Setter(AccessLevel.PACKAGE)
private ModrinthModpackInstallerFactory installerFactory = (apiClient, mrPackFile) ->
private ModrinthModpackInstallerFactory installerFactory = (apiClient, mrPackFile, fileInclusionCalculator) ->
new ModrinthPackInstaller(
apiClient, this.sharedFetchArgs.options(),
mrPackFile, this.outputDirectory, this.resultsFile,
this.forceModloaderReinstall
this.forceModloaderReinstall,
fileInclusionCalculator
);

private Mono<Installation> processResultsFile(FetchedPack fetchedPack, Installation installation) {
Expand Down
71 changes: 6 additions & 65 deletions src/main/java/me/itzg/helpers/modrinth/ModrinthPackInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipFile;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import me.itzg.helpers.errors.GenericException;
import me.itzg.helpers.errors.InvalidParameterException;
Expand All @@ -27,8 +26,6 @@
import me.itzg.helpers.http.SharedFetch.Options;
import me.itzg.helpers.json.ObjectMappers;
import me.itzg.helpers.modrinth.model.DependencyId;
import me.itzg.helpers.modrinth.model.Env;
import me.itzg.helpers.modrinth.model.EnvType;
import me.itzg.helpers.modrinth.model.ModpackIndex;
import me.itzg.helpers.quilt.QuiltInstaller;
import org.jetbrains.annotations.Blocking;
Expand All @@ -44,14 +41,9 @@ public class ModrinthPackInstaller {
private final Path outputDirectory;
private final Path resultsFile;
private final boolean forceModloaderReinstall;
private final FileInclusionCalculator fileInclusionCalculator;
private final Options sharedFetchOpts;

@Setter
private List<String> excludeFiles;

@Setter
private List<String> forceIncludeFiles;

private AntPathMatcher overridesExclusions;

@FunctionalInterface
Expand All @@ -71,14 +63,16 @@ interface ModloaderPreparer {
public ModrinthPackInstaller(
ModrinthApiClient apiClient, Options sharedFetchOpts,
Path zipFile, Path outputDirectory, Path resultsFile,
boolean forceModloaderReinstall)
boolean forceModloaderReinstall,
FileInclusionCalculator fileInclusionCalculator)
{
this.apiClient = apiClient;
this.sharedFetchOpts = sharedFetchOpts;
this.zipFile = zipFile;
this.outputDirectory = outputDirectory;
this.resultsFile = resultsFile;
this.forceModloaderReinstall = forceModloaderReinstall;
this.fileInclusionCalculator = fileInclusionCalculator;
}

public ModrinthPackInstaller setOverridesExclusions(List<String> overridesExclusions) {
Expand Down Expand Up @@ -135,11 +129,11 @@ public Mono<Installation> processModpack(SharedFetch sharedFetch) {
private Flux<Path> processModFiles(ModpackIndex modpackIndex) {
return Flux.fromStream(
modpackIndex.getFiles().stream()
.filter(this::includeModFile)
.filter(fileInclusionCalculator::includeModFile)
)
.publishOn(Schedulers.boundedElastic())
.flatMap(modpackFile -> {
final String modpackFilePath = sanitizeModFilePath(modpackFile.getPath());
final String modpackFilePath = FileInclusionCalculator.sanitizeModFilePath(modpackFile.getPath());

final Path outFilePath =
this.outputDirectory.resolve(modpackFilePath);
Expand All @@ -160,59 +154,6 @@ private Flux<Path> processModFiles(ModpackIndex modpackIndex) {
});
}

private boolean includeModFile(ModpackIndex.ModpackFile modFile) {
return (
// env is optional
modFile.getEnv() == null
|| modFile.getEnv().get(Env.server) != EnvType.unsupported
|| shouldForceIncludeFile(modFile.getPath())
)
&& !shouldExcludeFile(modFile.getPath());
}

private boolean shouldForceIncludeFile(String modPath) {
if (forceIncludeFiles == null || forceIncludeFiles.isEmpty()) {
return false;
}

final String normalized = sanitizeModFilePath(modPath).toLowerCase();

final boolean include = forceIncludeFiles.stream()
.anyMatch(s -> normalized.contains(s.toLowerCase()));
if (include) {
log.debug("Force including '{}' as requested", modPath);
}

return include;
}

private boolean shouldExcludeFile(String modPath) {
if (excludeFiles == null || excludeFiles.isEmpty()) {
return false;
}

// to match case-insensitive
final String normalized = sanitizeModFilePath(modPath).toLowerCase();

final boolean exclude = excludeFiles.stream()
.anyMatch(s -> normalized.contains(s.toLowerCase()));
if (exclude) {
log.debug("Excluding '{}' as requested", modPath);
}
return exclude;
}

private String sanitizeModFilePath(String path) {
// Using only backslash delimiters and not forward slashes?
// (mixed usage will assume backslashes were purposeful)
if (path.contains("\\") && !path.contains("/")) {
return path.replace("\\", "/");
}
else {
return path;
}
}

@SuppressWarnings("SameParameterValue")
private Stream<Path> extractOverrides(String... overridesDirs) {
try (ZipFile zipFileReader = new ZipFile(zipFile.toFile())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ static InstallModrinthModpackCommand createInstallModrinthModpackCommand(
final InstallModrinthModpackCommand commandUT =
new InstallModrinthModpackCommand()
// so that the modloader prepare can be injected with a mock
.setInstallerFactory((apiClient, mrPackFile) ->
.setInstallerFactory((apiClient, mrPackFile, fileInclusionCalculator) ->
new ModrinthPackInstaller(
apiClient,
SharedFetch.Options.builder().build(),
mrPackFile, outputDir, null, false
mrPackFile, outputDir, null, false,
fileInclusionCalculator
)
.modifyModLoaderPreparer(DependencyId.forge, mockForgePreparer)
);
Expand Down
Loading