Skip to content

Commit 95e52f6

Browse files
authored
cf: write out "needs download" file (#258)
1 parent b627c2f commit 95e52f6

File tree

3 files changed

+89
-46
lines changed

3 files changed

+89
-46
lines changed

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

+80-45
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,12 @@
11
package me.itzg.helpers.curseforge;
22

3-
import static java.util.Collections.emptySet;
4-
import static java.util.Objects.requireNonNull;
5-
import static java.util.Optional.ofNullable;
6-
import static me.itzg.helpers.singles.MoreCollections.safeStreamFrom;
7-
8-
import java.io.IOException;
9-
import java.nio.file.Files;
10-
import java.nio.file.Path;
11-
import java.nio.file.Paths;
12-
import java.nio.file.StandardCopyOption;
13-
import java.util.ArrayList;
14-
import java.util.Arrays;
15-
import java.util.Collection;
16-
import java.util.Collections;
17-
import java.util.HashSet;
18-
import java.util.List;
19-
import java.util.Objects;
20-
import java.util.Optional;
21-
import java.util.Set;
22-
import java.util.stream.Collectors;
23-
import java.util.stream.Stream;
24-
import java.util.zip.ZipEntry;
25-
import java.util.zip.ZipInputStream;
263
import lombok.AllArgsConstructor;
274
import lombok.Getter;
285
import lombok.RequiredArgsConstructor;
296
import lombok.Setter;
307
import lombok.extern.slf4j.Slf4j;
318
import me.itzg.helpers.curseforge.ExcludeIncludesContent.ExcludeIncludes;
32-
import me.itzg.helpers.curseforge.model.Category;
33-
import me.itzg.helpers.curseforge.model.CurseForgeFile;
34-
import me.itzg.helpers.curseforge.model.CurseForgeMod;
35-
import me.itzg.helpers.curseforge.model.ManifestFileRef;
36-
import me.itzg.helpers.curseforge.model.ManifestType;
37-
import me.itzg.helpers.curseforge.model.MinecraftModpackManifest;
38-
import me.itzg.helpers.curseforge.model.ModLoader;
9+
import me.itzg.helpers.curseforge.model.*;
3910
import me.itzg.helpers.errors.GenericException;
4011
import me.itzg.helpers.errors.InvalidParameterException;
4112
import me.itzg.helpers.fabric.FabricLauncherInstaller;
@@ -49,6 +20,23 @@
4920
import reactor.core.publisher.Mono;
5021
import reactor.core.scheduler.Schedulers;
5122

23+
import java.io.BufferedWriter;
24+
import java.io.IOException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.Paths;
28+
import java.nio.file.StandardCopyOption;
29+
import java.util.*;
30+
import java.util.stream.Collectors;
31+
import java.util.stream.Stream;
32+
import java.util.zip.ZipEntry;
33+
import java.util.zip.ZipInputStream;
34+
35+
import static java.util.Collections.emptySet;
36+
import static java.util.Objects.requireNonNull;
37+
import static java.util.Optional.ofNullable;
38+
import static me.itzg.helpers.singles.MoreCollections.safeStreamFrom;
39+
5240
@RequiredArgsConstructor
5341
@Slf4j
5442
public class CurseForgeInstaller {
@@ -406,6 +394,23 @@ private void finalizeResults(InstallContext context, ModPackResults results, int
406394

407395
Manifests.save(outputDir, CURSEFORGE_ID, newManifest);
408396

397+
final Path needsDownloadFile = outputDir.resolve("MODS_NEED_DOWNLOAD.txt");
398+
if (!results.getNeedsDownload().isEmpty()) {
399+
try (BufferedWriter writer = Files.newBufferedWriter(needsDownloadFile)) {
400+
for (PathWithInfo info : results.getNeedsDownload()) {
401+
writer.write(String.format("%s :: \"%s\" FROM %s",
402+
info.getModInfo().getName(),
403+
info.getCurseForgeFile().getDisplayName(),
404+
info.getModInfo().getLinks().getWebsiteUrl()
405+
));
406+
writer.newLine();
407+
}
408+
}
409+
}
410+
else {
411+
Files.deleteIfExists(needsDownloadFile);
412+
}
413+
409414
if (resultsFile != null) {
410415
try (ResultsFileWriter resultsFileWriter = new ResultsFileWriter(resultsFile, true)) {
411416
if (results.getLevelName() != null) {
@@ -509,11 +514,21 @@ private ModPackResults buildResults(MinecraftModpackManifest modpackManifest, Mo
509514
.setName(modpackManifest.getName())
510515
.setVersion(modpackManifest.getVersion())
511516
.setFiles(Stream.concat(
512-
modFiles != null ? modFiles.stream().map(PathWithInfo::getPath) : Stream.empty(),
517+
modFiles != null ?
518+
// NOTE: this purposely includes files needing download to ensure
519+
// they are considered for re-processing since they'll be missing still
520+
modFiles.stream().map(PathWithInfo::getPath)
521+
: Stream.empty(),
513522
overridesResult.paths.stream()
514523
)
515524
.collect(Collectors.toList())
516525
)
526+
.setNeedsDownload(modFiles != null ?
527+
modFiles.stream()
528+
.filter(PathWithInfo::isDownloadNeeded)
529+
.collect(Collectors.toList())
530+
: Collections.emptyList()
531+
)
517532
.setLevelName(resolveLevelName(modFiles, overridesResult))
518533
.setMinecraftVersion(modpackManifest.getMinecraft().getVersion())
519534
.setModLoaderId(modLoader.getId());
@@ -728,24 +743,42 @@ else if (category.getSlug().equals("worlds")) {
728743
projectID, fileID
729744
);
730745

731-
732-
final Mono<Path> resolvedFileMono =
746+
final Mono<ResolveResult> resolvedFileMono =
733747
resolveToOutputFile(context, modInfo, isWorld, baseDir, cfFile);
734748

735749
return isWorld ?
736750
resolvedFileMono
737-
.map(path -> extractWorldZip(modInfo, path, outputPaths.getWorldsDir()))
751+
.map(resolveResult ->
752+
resolveResult.downloadNeeded ?
753+
new PathWithInfo(resolveResult.path)
754+
.setDownloadNeeded(resolveResult.downloadNeeded)
755+
.setModInfo(modInfo)
756+
.setCurseForgeFile(cfFile)
757+
: extractWorldZip(modInfo, resolveResult.path, outputPaths.getWorldsDir())
758+
)
738759
: resolvedFileMono
739-
.map(PathWithInfo::new);
760+
.map(resolveResult ->
761+
new PathWithInfo(resolveResult.path)
762+
.setDownloadNeeded(resolveResult.downloadNeeded)
763+
.setModInfo(modInfo)
764+
.setCurseForgeFile(cfFile)
765+
);
740766
});
741767
});
742768
}
743769

744-
private Mono<Path> resolveToOutputFile(InstallContext context, CurseForgeMod modInfo, boolean isWorld, Path baseDir, CurseForgeFile cfFile) {
770+
@RequiredArgsConstructor
771+
static class ResolveResult {
772+
final Path path;
773+
@Setter
774+
boolean downloadNeeded;
775+
}
776+
777+
private Mono<ResolveResult> resolveToOutputFile(InstallContext context, CurseForgeMod modInfo, boolean isWorld, Path baseDir, CurseForgeFile cfFile) {
745778
final Path outputFile = baseDir.resolve(cfFile.getFileName());
746779
if (!isWorld && Files.exists(outputFile)) {
747780
log.info("Mod file {} already exists", outputDir.relativize(outputFile));
748-
return Mono.just(outputFile);
781+
return Mono.just(new ResolveResult(outputFile));
749782
}
750783
else if (cfFile.getDownloadUrl() == null) {
751784
final Path resolved =
@@ -757,21 +790,22 @@ else if (cfFile.getDownloadUrl() == null) {
757790
"Manually download the file '{}' from {} and supply via downloads repo or separately.",
758791
modInfo.getName(), cfFile.getDisplayName(), modInfo.getLinks().getWebsiteUrl()
759792
);
760-
return Mono.empty();
793+
return Mono.just(new ResolveResult(outputFile).setDownloadNeeded(true));
761794
}
762795
else {
763796
return Mono.just(resolved)
764797
.publishOn(Schedulers.boundedElastic())
765-
.handle((src, sink) -> {
798+
.flatMap(path -> {
766799
try {
767-
sink.next(Files.copy(resolved, outputFile));
768800
log.info("Mod file {} obtained from downloads repo", outputDir.relativize(outputFile));
801+
//noinspection BlockingMethodInNonBlockingContext because IntelliJ is confused
802+
return Mono.just(Files.copy(resolved, outputFile));
769803
} catch (IOException e) {
770-
sink.error(
771-
new GenericException("Failed to copy file from downloads repo", e)
772-
);
804+
return Mono.error(new GenericException("Failed to copy file from downloads repo", e));
773805
}
774-
});
806+
807+
})
808+
.map(ResolveResult::new);
775809
}
776810
}
777811
else {
@@ -784,7 +818,8 @@ else if (cfFile.getDownloadUrl() == null) {
784818
log.info("Downloaded mod file {}", outputDir.relativize(f));
785819
break;
786820
}
787-
});
821+
})
822+
.map(ResolveResult::new);
788823
}
789824
}
790825

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

+1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ public class ModPackResults {
1313
private String minecraftVersion;
1414
private String modLoaderId;
1515
private String levelName;
16+
private List<PathWithInfo> needsDownload;
1617
}

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package me.itzg.helpers.curseforge;
22

3-
import java.nio.file.Path;
43
import lombok.Getter;
54
import lombok.RequiredArgsConstructor;
65
import lombok.Setter;
6+
import me.itzg.helpers.curseforge.model.CurseForgeFile;
7+
import me.itzg.helpers.curseforge.model.CurseForgeMod;
8+
9+
import java.nio.file.Path;
710

811
@RequiredArgsConstructor
912
@Getter @Setter
@@ -13,4 +16,8 @@ public class PathWithInfo {
1316
* If this is a world mod file, then this will be the level name that would reference it
1417
*/
1518
private String levelName;
19+
20+
private boolean downloadNeeded;
21+
private CurseForgeMod modInfo;
22+
private CurseForgeFile curseForgeFile;
1623
}

0 commit comments

Comments
 (0)