Skip to content

Commit eaa5f32

Browse files
authored
auto-cf: add option to exclude files from overrides (#332)
1 parent 27c47b0 commit eaa5f32

File tree

6 files changed

+275
-109
lines changed

6 files changed

+275
-109
lines changed

src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java

Lines changed: 21 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
import java.nio.file.Files;
1313
import java.nio.file.Path;
1414
import java.nio.file.Paths;
15-
import java.nio.file.StandardCopyOption;
16-
import java.util.ArrayList;
1715
import java.util.Arrays;
1816
import java.util.Collection;
1917
import java.util.Collections;
@@ -32,6 +30,7 @@
3230
import lombok.Setter;
3331
import lombok.extern.slf4j.Slf4j;
3432
import me.itzg.helpers.curseforge.ExcludeIncludesContent.ExcludeIncludes;
33+
import me.itzg.helpers.curseforge.OverridesApplier.Result;
3534
import me.itzg.helpers.curseforge.model.Category;
3635
import me.itzg.helpers.curseforge.model.CurseForgeFile;
3736
import me.itzg.helpers.curseforge.model.CurseForgeMod;
@@ -64,8 +63,6 @@ public class CurseForgeInstaller {
6463

6564
public static final String ETERNAL_DEVELOPER_CONSOLE_URL = "https://console.curseforge.com/";
6665
public static final String CURSEFORGE_ID = "curseforge";
67-
public static final String LEVEL_DAT_SUFFIX = "/level.dat";
68-
public static final int LEVEL_DAT_SUFFIX_LEN = LEVEL_DAT_SUFFIX.length();
6966
public static final String REPO_SUBDIR_MODPACKS = "modpacks";
7067
public static final String REPO_SUBDIR_MODS = "mods";
7168
public static final String REPO_SUBDIR_WORLDS = "worlds";
@@ -108,6 +105,9 @@ public class CurseForgeInstaller {
108105
CurseForgeApiClient.CATEGORY_WORLDS
109106
));
110107

108+
@Getter @Setter
109+
private List<String> overridesExclusions;
110+
111111
/**
112112
* @throws MissingModsException if any mods need to be manually downloaded
113113
*/
@@ -118,8 +118,12 @@ public void installFromModpackZip(Path modpackZip, String slug) throws IOExcepti
118118
final MinecraftModpackManifest modpackManifest = extractModpackManifest(modpackZip);
119119

120120
processModpackManifest(context, modpackManifest,
121-
() -> applyOverrides(modpackZip, modpackManifest.getOverrides())
122-
);
121+
new OverridesFromZipApplier(
122+
outputDir, modpackZip, overridesSkipExisting,
123+
modpackManifest.getOverrides(),
124+
levelFrom, overridesExclusions
125+
)
126+
);
123127
});
124128
}
125129

@@ -146,7 +150,7 @@ public void installFromModpackManifest(String modpackManifestLoc, String slug) t
146150
}
147151

148152
processModpackManifest(context, modpackManifest,
149-
() -> new OverridesResult(Collections.emptyList(), null)
153+
() -> new Result(Collections.emptyList(), null)
150154
);
151155
});
152156
}
@@ -359,8 +363,13 @@ else if (Manifests.allFilesPresent(outputDir, context.prevInstallManifest)) {
359363
try {
360364

361365
final MinecraftModpackManifest modpackManifest = extractModpackManifest(modpackZip);
362-
results = processModpack(context, modpackManifest, () -> applyOverrides(modpackZip,
363-
modpackManifest.getOverrides()));
366+
results = processModpack(context, modpackManifest,
367+
new OverridesFromZipApplier(
368+
outputDir, modpackZip, overridesSkipExisting,
369+
modpackManifest.getOverrides(),
370+
levelFrom, overridesExclusions
371+
)
372+
);
364373
} finally {
365374
Files.delete(modpackZip);
366375
}
@@ -479,9 +488,6 @@ private void trimLevelsContent(CurseForgeManifest manifest) {
479488
});
480489
}
481490

482-
/**
483-
* @param overridesApplier typically calls {@link #applyOverrides(Path, String)}
484-
*/
485491
private ModPackResults processModpack(InstallContext context,
486492
MinecraftModpackManifest modpackManifest, OverridesApplier overridesApplier
487493
) throws IOException {
@@ -540,14 +546,14 @@ private ModPackResults processModpack(InstallContext context,
540546
.collectList()
541547
.block();
542548

543-
final OverridesResult overridesResult = overridesApplier.apply();
549+
final Result overridesResult = overridesApplier.apply();
544550

545551
prepareModLoader(modLoader.getId(), modpackManifest.getMinecraft().getVersion());
546552

547553
return buildResults(modpackManifest, modLoader, modFiles, overridesResult);
548554
}
549555

550-
private ModPackResults buildResults(MinecraftModpackManifest modpackManifest, ModLoader modLoader, List<PathWithInfo> modFiles, OverridesResult overridesResult) {
556+
private ModPackResults buildResults(MinecraftModpackManifest modpackManifest, ModLoader modLoader, List<PathWithInfo> modFiles, Result overridesResult) {
551557
return new ModPackResults()
552558
.setName(modpackManifest.getName())
553559
.setVersion(modpackManifest.getVersion())
@@ -572,7 +578,7 @@ private ModPackResults buildResults(MinecraftModpackManifest modpackManifest, Mo
572578
.setModLoaderId(modLoader.getId());
573579
}
574580

575-
private String resolveLevelName(List<PathWithInfo> modFiles, OverridesResult overridesResult) {
581+
private String resolveLevelName(List<PathWithInfo> modFiles, Result overridesResult) {
576582
if (levelFrom == LevelFrom.OVERRIDES && overridesResult.levelName != null) {
577583
return overridesResult.levelName;
578584
}
@@ -638,96 +644,6 @@ private Mono<Set<Integer>> resolveFromSlugOrIds(
638644
.collect(Collectors.toSet());
639645
}
640646

641-
@AllArgsConstructor
642-
static class OverridesResult {
643-
List<Path> paths;
644-
String levelName;
645-
}
646-
647-
interface OverridesApplier {
648-
OverridesResult apply() throws IOException;
649-
}
650-
651-
private OverridesResult applyOverrides(Path modpackZip, String overridesDir) throws IOException {
652-
log.debug("Applying overrides from '{}' in zip file", overridesDir);
653-
654-
final String levelEntryName = findLevelEntryInOverrides(modpackZip, overridesDir);
655-
final String levelEntryNamePrefix = levelEntryName != null ? levelEntryName+"/" : null;
656-
657-
final boolean worldOutputDirExists = levelEntryName != null &&
658-
Files.exists(outputDir.resolve(levelEntryName));
659-
660-
log.debug("While applying overrides, found level entry='{}' in modpack overrides and worldOutputDirExists={}",
661-
levelEntryName, worldOutputDirExists);
662-
663-
final String overridesDirPrefix = overridesDir + "/";
664-
final int overridesPrefixLen = overridesDirPrefix.length();
665-
666-
final List<Path> overrides = new ArrayList<>();
667-
try (ZipInputStream zip = new ZipInputStream(Files.newInputStream(modpackZip))) {
668-
ZipEntry entry;
669-
while ((entry = zip.getNextEntry()) != null) {
670-
if (entry.getName().startsWith(overridesDirPrefix)) {
671-
if (!entry.isDirectory()) {
672-
if (log.isTraceEnabled()) {
673-
log.trace("Processing override entry={}:{}", entry.isDirectory() ? "D":"F", entry.getName());
674-
}
675-
final String subpath = entry.getName().substring(overridesPrefixLen);
676-
final Path outPath = outputDir.resolve(subpath);
677-
678-
// Rules
679-
// - don't ever overwrite world data
680-
// - user has option to not overwrite any existing file from overrides
681-
// - otherwise user will want latest modpack's overrides content
682-
683-
final boolean isInWorldDirectory = levelEntryNamePrefix != null &&
684-
subpath.startsWith(levelEntryNamePrefix);
685-
686-
if (worldOutputDirExists && isInWorldDirectory) {
687-
continue;
688-
}
689-
690-
if ( !(overridesSkipExisting && Files.exists(outPath)) ) {
691-
log.trace("Applying override {}", subpath);
692-
// zip files don't always list the directories before the files, so just create-as-needed
693-
Files.createDirectories(outPath.getParent());
694-
Files.copy(zip, outPath, StandardCopyOption.REPLACE_EXISTING);
695-
}
696-
else {
697-
log.trace("Skipping override={} since the file already existed", subpath);
698-
}
699-
700-
// Track this path for later cleanup
701-
// UNLESS it is within a world/level directory
702-
if (levelEntryName == null || !isInWorldDirectory) {
703-
overrides.add(outPath);
704-
}
705-
}
706-
}
707-
}
708-
}
709-
710-
return new OverridesResult(overrides,
711-
levelFrom == LevelFrom.OVERRIDES ? levelEntryName : null
712-
);
713-
}
714-
715-
/**
716-
* @return if present, the subpath to a world/level directory with the overrides prefix removed otherwise null
717-
*/
718-
private String findLevelEntryInOverrides(Path modpackZip, String overridesDir) throws IOException {
719-
try (ZipInputStream zip = new ZipInputStream(Files.newInputStream(modpackZip))) {
720-
ZipEntry entry;
721-
while ((entry = zip.getNextEntry()) != null) {
722-
final String name = entry.getName();
723-
if (!entry.isDirectory() && name.startsWith(overridesDir + "/") && name.endsWith(LEVEL_DAT_SUFFIX)) {
724-
return name.substring(overridesDir.length()+1, name.length() - LEVEL_DAT_SUFFIX_LEN);
725-
}
726-
}
727-
}
728-
return null;
729-
}
730-
731647
/**
732648
* Downloads the referenced project-file into the appropriate subdirectory from outputPaths
733649
*/

src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.io.PrintWriter;
88
import java.nio.file.Files;
99
import java.nio.file.Path;
10+
import java.util.List;
1011
import java.util.Set;
1112
import java.util.concurrent.Callable;
1213
import java.util.regex.Matcher;
@@ -25,9 +26,10 @@
2526
import picocli.CommandLine.ExitCode;
2627
import picocli.CommandLine.Option;
2728

28-
@Command(name = "install-curseforge", subcommands = {
29-
SchemasCommand.class
30-
})
29+
@Command(name = "install-curseforge",
30+
description = "Downloads, installs, and upgrades CurseForge modpacks",
31+
subcommands = {SchemasCommand.class}
32+
)
3133
public class InstallCurseForgeCommand implements Callable<Integer> {
3234

3335
@Option(names = {"--help","-h"}, usageHelp = true)
@@ -114,7 +116,6 @@ static class Listed {
114116
}
115117
}
116118

117-
118119
@Option(names = "--filename-matcher", paramLabel = "STR",
119120
description = "Substring to select specific modpack filename")
120121
String filenameMatcher;
@@ -135,6 +136,15 @@ static class Listed {
135136
)
136137
boolean overridesSkipExisting;
137138

139+
@Option(names = "--overrides-exclusions",
140+
split = "\n|,", splitSynopsisLabel = "NL or ,",
141+
description = "Excludes files from the overrides that match these ant-style patterns\n"
142+
+ "* : matches any non-slash characters\n"
143+
+ "** : matches any characters\n"
144+
+ "? : matches one character"
145+
)
146+
List<String> overridesExclusions;
147+
138148
@ArgGroup(exclusive = false)
139149
SharedFetchArgs sharedFetchArgs = new SharedFetchArgs();
140150

@@ -182,6 +192,7 @@ public Integer call() throws Exception {
182192
.setForceSynchronize(forceSynchronize)
183193
.setLevelFrom(levelFrom)
184194
.setOverridesSkipExisting(overridesSkipExisting)
195+
.setOverridesExclusions(overridesExclusions)
185196
.setSharedFetchOptions(sharedFetchArgs.options())
186197
.setApiKey(apiKey)
187198
.setDownloadsRepo(downloadsRepo);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package me.itzg.helpers.curseforge;
2+
3+
import java.io.IOException;
4+
import java.nio.file.Path;
5+
import java.util.List;
6+
import lombok.AllArgsConstructor;
7+
8+
interface OverridesApplier {
9+
10+
Result apply() throws IOException;
11+
12+
@AllArgsConstructor
13+
class Result {
14+
15+
List<Path> paths;
16+
String levelName;
17+
}
18+
}

0 commit comments

Comments
 (0)