1
1
package me .itzg .helpers .curseforge ;
2
2
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 ;
26
3
import lombok .AllArgsConstructor ;
27
4
import lombok .Getter ;
28
5
import lombok .RequiredArgsConstructor ;
29
6
import lombok .Setter ;
30
7
import lombok .extern .slf4j .Slf4j ;
31
8
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 .*;
39
10
import me .itzg .helpers .errors .GenericException ;
40
11
import me .itzg .helpers .errors .InvalidParameterException ;
41
12
import me .itzg .helpers .fabric .FabricLauncherInstaller ;
49
20
import reactor .core .publisher .Mono ;
50
21
import reactor .core .scheduler .Schedulers ;
51
22
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
+
52
40
@ RequiredArgsConstructor
53
41
@ Slf4j
54
42
public class CurseForgeInstaller {
@@ -406,6 +394,23 @@ private void finalizeResults(InstallContext context, ModPackResults results, int
406
394
407
395
Manifests .save (outputDir , CURSEFORGE_ID , newManifest );
408
396
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
+
409
414
if (resultsFile != null ) {
410
415
try (ResultsFileWriter resultsFileWriter = new ResultsFileWriter (resultsFile , true )) {
411
416
if (results .getLevelName () != null ) {
@@ -509,11 +514,21 @@ private ModPackResults buildResults(MinecraftModpackManifest modpackManifest, Mo
509
514
.setName (modpackManifest .getName ())
510
515
.setVersion (modpackManifest .getVersion ())
511
516
.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 (),
513
522
overridesResult .paths .stream ()
514
523
)
515
524
.collect (Collectors .toList ())
516
525
)
526
+ .setNeedsDownload (modFiles != null ?
527
+ modFiles .stream ()
528
+ .filter (PathWithInfo ::isDownloadNeeded )
529
+ .collect (Collectors .toList ())
530
+ : Collections .emptyList ()
531
+ )
517
532
.setLevelName (resolveLevelName (modFiles , overridesResult ))
518
533
.setMinecraftVersion (modpackManifest .getMinecraft ().getVersion ())
519
534
.setModLoaderId (modLoader .getId ());
@@ -728,24 +743,42 @@ else if (category.getSlug().equals("worlds")) {
728
743
projectID , fileID
729
744
);
730
745
731
-
732
- final Mono <Path > resolvedFileMono =
746
+ final Mono <ResolveResult > resolvedFileMono =
733
747
resolveToOutputFile (context , modInfo , isWorld , baseDir , cfFile );
734
748
735
749
return isWorld ?
736
750
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
+ )
738
759
: resolvedFileMono
739
- .map (PathWithInfo ::new );
760
+ .map (resolveResult ->
761
+ new PathWithInfo (resolveResult .path )
762
+ .setDownloadNeeded (resolveResult .downloadNeeded )
763
+ .setModInfo (modInfo )
764
+ .setCurseForgeFile (cfFile )
765
+ );
740
766
});
741
767
});
742
768
}
743
769
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 ) {
745
778
final Path outputFile = baseDir .resolve (cfFile .getFileName ());
746
779
if (!isWorld && Files .exists (outputFile )) {
747
780
log .info ("Mod file {} already exists" , outputDir .relativize (outputFile ));
748
- return Mono .just (outputFile );
781
+ return Mono .just (new ResolveResult ( outputFile ) );
749
782
}
750
783
else if (cfFile .getDownloadUrl () == null ) {
751
784
final Path resolved =
@@ -757,21 +790,22 @@ else if (cfFile.getDownloadUrl() == null) {
757
790
"Manually download the file '{}' from {} and supply via downloads repo or separately." ,
758
791
modInfo .getName (), cfFile .getDisplayName (), modInfo .getLinks ().getWebsiteUrl ()
759
792
);
760
- return Mono .empty ( );
793
+ return Mono .just ( new ResolveResult ( outputFile ). setDownloadNeeded ( true ) );
761
794
}
762
795
else {
763
796
return Mono .just (resolved )
764
797
.publishOn (Schedulers .boundedElastic ())
765
- .handle (( src , sink ) -> {
798
+ .flatMap ( path -> {
766
799
try {
767
- sink .next (Files .copy (resolved , outputFile ));
768
800
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 ));
769
803
} 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 ));
773
805
}
774
- });
806
+
807
+ })
808
+ .map (ResolveResult ::new );
775
809
}
776
810
}
777
811
else {
@@ -784,7 +818,8 @@ else if (cfFile.getDownloadUrl() == null) {
784
818
log .info ("Downloaded mod file {}" , outputDir .relativize (f ));
785
819
break ;
786
820
}
787
- });
821
+ })
822
+ .map (ResolveResult ::new );
788
823
}
789
824
}
790
825
0 commit comments