Skip to content

Commit e2a9df2

Browse files
committed
support folder mode
1 parent 44dd296 commit e2a9df2

File tree

6 files changed

+144
-6
lines changed

6 files changed

+144
-6
lines changed

de.peeeq.wurstscript/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ dependencies {
107107
implementation 'commons-lang:commons-lang:2.6'
108108
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
109109
implementation 'org.xerial:sqlite-jdbc:3.46.1.3'
110-
implementation 'com.github.inwc3:jmpq3:29b55f2c32'
110+
implementation 'com.github.inwc3:jmpq3:e28f6999c0'
111111
implementation 'com.github.inwc3:wc3libs:6a96a79595'
112112
implementation('com.github.wurstscript:wurstsetup:393cf5ea39') {
113113
exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit'

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ protected boolean canUseCachedMap(File cachedMap) {
395395
}
396396

397397
// Check if source map is newer
398-
if (map.isPresent() && map.get().lastModified() > cachedMap.lastModified()) {
398+
if (map.isPresent() && getSourceMapLastModified(map.get()) > cachedMap.lastModified()) {
399399
WLogger.info("Source map is newer than cache");
400400
return false;
401401
}
@@ -514,16 +514,69 @@ protected File ensureCachedMap(WurstGui gui) throws IOException {
514514

515515
File sourceMap = map.get();
516516

517+
long sourceLastModified = getSourceMapLastModified(sourceMap);
517518
// If cached map doesn't exist or source is newer, update cache
518-
if (!cachedMap.exists() || sourceMap.lastModified() > cachedMap.lastModified()) {
519+
if (!cachedMap.exists() || sourceLastModified > cachedMap.lastModified()) {
519520
WLogger.info("Updating cached map from source");
520521
gui.sendProgress("Updating cached map");
521-
Files.copy(sourceMap, cachedMap);
522+
if (sourceMap.isDirectory()) {
523+
buildArchiveFromFolder(sourceMap, cachedMap);
524+
} else {
525+
Files.copy(sourceMap, cachedMap);
526+
}
522527
}
523528

524529
return cachedMap;
525530
}
526531

532+
protected static long getSourceMapLastModified(File sourceMap) {
533+
if (!sourceMap.isDirectory()) {
534+
return sourceMap.lastModified();
535+
}
536+
try (java.util.stream.Stream<Path> files = java.nio.file.Files.walk(sourceMap.toPath())) {
537+
return files
538+
.map(Path::toFile)
539+
.mapToLong(File::lastModified)
540+
.max()
541+
.orElse(sourceMap.lastModified());
542+
} catch (IOException e) {
543+
WLogger.warning("Could not inspect folder map timestamp: " + sourceMap + " (" + e.getMessage() + ")");
544+
return sourceMap.lastModified();
545+
}
546+
}
547+
548+
private static void buildArchiveFromFolder(File sourceFolder, File cachedMap) throws IOException {
549+
try {
550+
java.nio.file.Files.deleteIfExists(cachedMap.toPath());
551+
MpqEditorFactory.createEmptyArchive(cachedMap);
552+
try (MpqEditor mpqEditor = MpqEditorFactory.getEditor(Optional.of(cachedMap))) {
553+
try (java.util.stream.Stream<Path> files = java.nio.file.Files.walk(sourceFolder.toPath())) {
554+
List<Path> regularFiles = files
555+
.filter(java.nio.file.Files::isRegularFile)
556+
.sorted(Comparator.comparing(path -> sourceFolder.toPath().relativize(path).toString().replace('\\', '/')))
557+
.collect(Collectors.toList());
558+
for (Path file : regularFiles) {
559+
String filenameInMpq = sourceFolder.toPath().relativize(file).toString().replace('/', '\\');
560+
if (isInternalMpqMetadataFile(filenameInMpq)) {
561+
continue;
562+
}
563+
mpqEditor.insertFile(filenameInMpq, file.toFile());
564+
}
565+
}
566+
}
567+
} catch (IOException e) {
568+
throw e;
569+
} catch (Exception e) {
570+
throw new IOException("Could not build MPQ archive from folder map: " + sourceFolder.getAbsolutePath(), e);
571+
}
572+
}
573+
574+
private static boolean isInternalMpqMetadataFile(String filenameInMpq) {
575+
return filenameInMpq.equals("(listfile)")
576+
|| filenameInMpq.equals("(attributes)")
577+
|| filenameInMpq.equals("(signature)");
578+
}
579+
527580
private void cleanupOppositeModeCacheAndOutputs() {
528581
if (cachedMapFileName.isEmpty()) {
529582
File cacheDir = new File(getBuildDir(), "cache");
@@ -685,7 +738,7 @@ protected File executeBuildMapPipeline(ModelManager modelManager, WurstGui gui,
685738
throw new RequestFailedException(MessageType.Error, map.get().getAbsolutePath() + " does not exist.");
686739
}
687740

688-
mapLastModified = map.get().lastModified();
741+
mapLastModified = getSourceMapLastModified(map.get());
689742
mapPath = map.get().getAbsolutePath();
690743

691744
gui.sendProgress("Copying map");
@@ -830,6 +883,17 @@ protected byte[] extractMapScript(Optional<File> mapCopy) throws Exception {
830883
if (!mapCopy.isPresent()) {
831884
return null;
832885
}
886+
if (mapCopy.get().isDirectory()) {
887+
File rootScript = new File(mapCopy.get(), "war3map.j");
888+
if (rootScript.exists()) {
889+
return java.nio.file.Files.readAllBytes(rootScript.toPath());
890+
}
891+
File scriptsScript = new File(new File(mapCopy.get(), "scripts"), "war3map.j");
892+
if (scriptsScript.exists()) {
893+
return java.nio.file.Files.readAllBytes(scriptsScript.toPath());
894+
}
895+
return null;
896+
}
833897
try (@Nullable MpqEditor mpqEditor = MpqEditorFactory.getEditor(mapCopy, true)) {
834898
if (mpqEditor.hasFile("war3map.j")) {
835899
return mpqEditor.extractFile("war3map.j");

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ private String compileMap(ModelManager modelManager, WurstGui gui, WurstProjectC
9999
// first we copy in same location to ensure validity
100100
File buildDir = getBuildDir();
101101
if (map.isPresent()) {
102-
mapLastModified = map.get().lastModified();
102+
mapLastModified = getSourceMapLastModified(map.get());
103103
mapPath = map.get().getAbsolutePath();
104104
}
105105
if (!runArgs.isHotReload()) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/mpq/Jmpq3BasedEditor.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.io.File;
99
import java.io.FileNotFoundException;
1010
import java.io.IOException;
11+
import java.lang.reflect.InvocationTargetException;
1112

1213
class Jmpq3BasedEditor implements MpqEditor {
1314
private final JMpqEditor editor;
@@ -25,6 +26,22 @@ public Jmpq3BasedEditor(File mpqArchive, boolean readonly) throws Exception {
2526

2627
}
2728

29+
static void createEmptyArchive(File mpqArchive) throws IOException {
30+
try {
31+
JMpqEditor.class.getMethod("createEmptyArchive", File.class).invoke(null, mpqArchive);
32+
} catch (NoSuchMethodException e) {
33+
throw new IOException("JMPQ3 is missing createEmptyArchive(File); update the JMPQ3 dependency.", e);
34+
} catch (IllegalAccessException e) {
35+
throw new IOException("Cannot access JMPQ3 createEmptyArchive(File).", e);
36+
} catch (InvocationTargetException e) {
37+
Throwable cause = e.getCause();
38+
if (cause instanceof IOException) {
39+
throw (IOException) cause;
40+
}
41+
throw new IOException("JMPQ3 could not create an empty MPQ archive.", cause);
42+
}
43+
}
44+
2845
@Override
2946
public void insertFile(String filenameInMpq, byte[] contents) {
3047
getEditor().deleteFile(filenameInMpq);

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/mpq/MpqEditorFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ public class MpqEditorFactory {
1717
}
1818
return new Jmpq3BasedEditor(f.get(), readOnly);
1919
}
20+
21+
static public void createEmptyArchive(File f) throws Exception {
22+
Jmpq3BasedEditor.createEmptyArchive(f);
23+
}
2024
}

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/HotReloadPipelineTests.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
import de.peeeq.wurstio.languageserver.WFile;
77
import de.peeeq.wurstio.languageserver.WurstLanguageServer;
88
import de.peeeq.wurstio.languageserver.requests.MapRequest;
9+
import de.peeeq.wurstio.mpq.MpqEditor;
10+
import de.peeeq.wurstio.mpq.MpqEditorFactory;
911
import de.peeeq.wurstio.utils.FileUtils;
1012
import de.peeeq.wurstscript.gui.WurstGui;
1113
import de.peeeq.wurstscript.gui.WurstGuiLogger;
14+
import org.testng.SkipException;
1215
import org.testng.annotations.Test;
16+
import systems.crigges.jmpq3.JMpqEditor;
1317

1418
import java.io.File;
1519
import java.nio.charset.StandardCharsets;
@@ -92,6 +96,51 @@ public void cachedMapFileNameIsModeSpecific() throws Exception {
9296
assertEquals(jassCache.getName().contains("_jass_cached.w3x"), true);
9397
}
9498

99+
@Test
100+
public void folderMapInputIsMaterializedAsCachedArchive() throws Exception {
101+
if (!jmpqCreateEmptyArchiveAvailable()) {
102+
throw new SkipException("Requires JMPQ3 createEmptyArchive(File).");
103+
}
104+
105+
File projectFolder = new File("./temp/testProject_folder_map_cache/");
106+
File wurstFolder = new File(projectFolder, "wurst");
107+
newCleanFolder(wurstFolder);
108+
109+
File sourceMap = new File(projectFolder, "folder_map.w3x");
110+
Files.createDirectories(sourceMap.toPath());
111+
Files.writeString(new File(sourceMap, "war3map.j").toPath(), "folder script");
112+
File nestedFile = new File(sourceMap, "war3mapImported\\asset.txt");
113+
Files.createDirectories(nestedFile.getParentFile().toPath());
114+
Files.writeString(nestedFile.toPath(), "asset data");
115+
116+
WurstLanguageServer langServer = new WurstLanguageServer();
117+
TestMapRequest request = new TestMapRequest(
118+
langServer,
119+
Optional.of(sourceMap),
120+
List.of(),
121+
WFile.create(projectFolder),
122+
Map.of()
123+
);
124+
125+
File cachedMap = request.ensureCachedMapForTest(new WurstGuiLogger());
126+
127+
try (MpqEditor mpqEditor = MpqEditorFactory.getEditor(Optional.of(cachedMap), true)) {
128+
assertEquals(mpqEditor.hasFile("war3map.j"), true);
129+
assertEquals(mpqEditor.hasFile("war3mapImported\\asset.txt"), true);
130+
assertEquals(new String(mpqEditor.extractFile("war3map.j"), StandardCharsets.UTF_8), "folder script");
131+
assertEquals(new String(mpqEditor.extractFile("war3mapImported\\asset.txt"), StandardCharsets.UTF_8), "asset data");
132+
}
133+
}
134+
135+
private boolean jmpqCreateEmptyArchiveAvailable() {
136+
try {
137+
JMpqEditor.class.getMethod("createEmptyArchive", File.class);
138+
return true;
139+
} catch (NoSuchMethodException e) {
140+
return false;
141+
}
142+
}
143+
95144
@Test
96145
public void jhcrPipelineRenamesOutputScript() throws Exception {
97146
File projectFolder = new File("./temp/testProject_jhcr_output/");
@@ -230,5 +279,9 @@ private File renameJhcrOutputForTest(File buildDir) throws Exception {
230279
private File getCachedMapFileForTest() {
231280
return getCachedMapFile();
232281
}
282+
283+
private File ensureCachedMapForTest(WurstGui gui) throws Exception {
284+
return ensureCachedMap(gui);
285+
}
233286
}
234287
}

0 commit comments

Comments
 (0)