refactor: flatten ZIP single-root-directory instead of path splitting
Build & Deploy / build-and-deploy (push) Successful in 1m45s
Build & Deploy / build-and-deploy (push) Successful in 1m45s
Completely new approach: - After unzip, check if extractDir has exactly one entry (a directory) - If so, move all its contents up and delete the empty folder - This means game files are always at the bundle root (c:\) - No more cd/subdirectory handling in config builders - No more path separator conversions (forward/backslash) - Removed splitPath() helper entirely - Config builders are now trivial — relExe is always just a filename
This commit is contained in:
@@ -62,6 +62,8 @@ public class UploadResource {
|
|||||||
|
|
||||||
Path extractDir = tmpDir.resolve("extracted");
|
Path extractDir = tmpDir.resolve("extracted");
|
||||||
unzip(zipPath, extractDir);
|
unzip(zipPath, extractDir);
|
||||||
|
// If the ZIP has a single root directory, flatten it
|
||||||
|
flattenSingleDir(extractDir);
|
||||||
|
|
||||||
// Find main executable
|
// Find main executable
|
||||||
String mainExe = findMainExe(extractDir);
|
String mainExe = findMainExe(extractDir);
|
||||||
@@ -86,7 +88,7 @@ public class UploadResource {
|
|||||||
// Write js-dos config
|
// Write js-dos config
|
||||||
Path jsdos = gameDir.resolve(".jsdos");
|
Path jsdos = gameDir.resolve(".jsdos");
|
||||||
Files.createDirectories(jsdos);
|
Files.createDirectories(jsdos);
|
||||||
String relExe = extractDir.relativize(Path.of(mainExe)).toString().replace('/', '\\');
|
String relExe = extractDir.relativize(Path.of(mainExe)).toString();
|
||||||
Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe));
|
Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe));
|
||||||
Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe));
|
Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe));
|
||||||
bundleFile = ".jsdos/dosbox.conf";
|
bundleFile = ".jsdos/dosbox.conf";
|
||||||
@@ -170,6 +172,40 @@ public class UploadResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the extraction directory contains a single subdirectory, move
|
||||||
|
* its contents up (flatten). This handles ZIPs where all game files
|
||||||
|
* are inside a folder (e.g. "game/DOOM2.EXE" → "DOOM2.EXE").
|
||||||
|
*/
|
||||||
|
private void flattenSingleDir(Path dir) throws IOException {
|
||||||
|
List<Path> entries;
|
||||||
|
try (var files = Files.list(dir)) {
|
||||||
|
entries = files.toList();
|
||||||
|
}
|
||||||
|
if (entries.size() != 1) return;
|
||||||
|
Path only = entries.get(0);
|
||||||
|
if (!Files.isDirectory(only)) return;
|
||||||
|
|
||||||
|
LOG.info("Flattening single root directory: " + only.getFileName());
|
||||||
|
// Walk in reverse order so directories are empty when we delete them
|
||||||
|
try (var walk = Files.walk(only)) {
|
||||||
|
var list = walk.sorted(Comparator.reverseOrder()).toList();
|
||||||
|
for (Path f : list) {
|
||||||
|
if (f.equals(only)) continue;
|
||||||
|
Path target = dir.resolve(only.relativize(f));
|
||||||
|
if (Files.isDirectory(f)) {
|
||||||
|
Files.createDirectories(target);
|
||||||
|
Files.delete(f);
|
||||||
|
} else {
|
||||||
|
Files.createDirectories(target.getParent());
|
||||||
|
Files.move(f, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Files.delete(only);
|
||||||
|
LOG.info("Flattened " + only.getFileName());
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Executable Detection ────────────────────────────────────
|
// ─── Executable Detection ────────────────────────────────────
|
||||||
|
|
||||||
private String findMainExe(Path dir) throws IOException {
|
private String findMainExe(Path dir) throws IOException {
|
||||||
@@ -211,7 +247,7 @@ public class UploadResource {
|
|||||||
// Create .jsdos config
|
// Create .jsdos config
|
||||||
Path jsdos = tmpBundle.resolve(".jsdos");
|
Path jsdos = tmpBundle.resolve(".jsdos");
|
||||||
Files.createDirectories(jsdos);
|
Files.createDirectories(jsdos);
|
||||||
String relExe = extractDir.relativize(Path.of(mainExe)).toString().replace('/', '\\');
|
String relExe = extractDir.relativize(Path.of(mainExe)).toString();
|
||||||
Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe));
|
Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe));
|
||||||
Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe));
|
Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe));
|
||||||
|
|
||||||
@@ -236,11 +272,10 @@ public class UploadResource {
|
|||||||
// ─── Configuration Helpers ───────────────────────────────────
|
// ─── Configuration Helpers ───────────────────────────────────
|
||||||
|
|
||||||
private byte[] buildJsdosJson(String relExe) {
|
private byte[] buildJsdosJson(String relExe) {
|
||||||
String parts[] = splitPath(relExe);
|
// relExe is always just a filename after flattenSingleDir
|
||||||
String script = (parts[0].isEmpty() ? "" : "cd " + parts[0] + " && ") + parts[1];
|
|
||||||
String json = "{"
|
String json = "{"
|
||||||
+ "\"autoexec\":{"
|
+ "\"autoexec\":{"
|
||||||
+ "\"options\":{\"script\":{\"value\":\"" + escapeJson(script) + "\"}}"
|
+ "\"options\":{\"script\":{\"value\":\"" + escapeJson(relExe) + "\"}}"
|
||||||
+ "},"
|
+ "},"
|
||||||
+ "\"output\":{"
|
+ "\"output\":{"
|
||||||
+ "\"options\":{\"autolock\":{\"value\":true}}}"
|
+ "\"options\":{\"autolock\":{\"value\":true}}}"
|
||||||
@@ -249,10 +284,7 @@ public class UploadResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String buildDosboxConf(String relExe) {
|
private String buildDosboxConf(String relExe) {
|
||||||
String parts[] = splitPath(relExe);
|
// relExe is always just a filename after flattenSingleDir
|
||||||
String dir = parts[0];
|
|
||||||
String exe = parts[1];
|
|
||||||
|
|
||||||
return "[sdl]\n"
|
return "[sdl]\n"
|
||||||
+ "autolock=true\n"
|
+ "autolock=true\n"
|
||||||
+ "usescancodes=true\n"
|
+ "usescancodes=true\n"
|
||||||
@@ -282,21 +314,7 @@ public class UploadResource {
|
|||||||
+ "mount c .\n"
|
+ "mount c .\n"
|
||||||
+ "c:\n"
|
+ "c:\n"
|
||||||
+ "cls\n"
|
+ "cls\n"
|
||||||
+ (dir.isEmpty() ? "" : "cd " + dir + "\n")
|
+ relExe + "\n";
|
||||||
+ exe + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split a DOS path into directory and filename parts.
|
|
||||||
* Handles both backslash and forward-slash separators.
|
|
||||||
* Returns [dir, exe] where dir is empty for root-level files.
|
|
||||||
*/
|
|
||||||
private String[] splitPath(String path) {
|
|
||||||
int idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
|
|
||||||
if (idx >= 0) {
|
|
||||||
return new String[]{path.substring(0, idx), path.substring(idx + 1)};
|
|
||||||
}
|
|
||||||
return new String[]{"", path};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String escapeJson(String s) {
|
private String escapeJson(String s) {
|
||||||
|
|||||||
Reference in New Issue
Block a user