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");
|
||||
unzip(zipPath, extractDir);
|
||||
// If the ZIP has a single root directory, flatten it
|
||||
flattenSingleDir(extractDir);
|
||||
|
||||
// Find main executable
|
||||
String mainExe = findMainExe(extractDir);
|
||||
@@ -86,7 +88,7 @@ public class UploadResource {
|
||||
// Write js-dos config
|
||||
Path jsdos = gameDir.resolve(".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.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe));
|
||||
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 ────────────────────────────────────
|
||||
|
||||
private String findMainExe(Path dir) throws IOException {
|
||||
@@ -211,7 +247,7 @@ public class UploadResource {
|
||||
// Create .jsdos config
|
||||
Path jsdos = tmpBundle.resolve(".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.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe));
|
||||
|
||||
@@ -236,11 +272,10 @@ public class UploadResource {
|
||||
// ─── Configuration Helpers ───────────────────────────────────
|
||||
|
||||
private byte[] buildJsdosJson(String relExe) {
|
||||
String parts[] = splitPath(relExe);
|
||||
String script = (parts[0].isEmpty() ? "" : "cd " + parts[0] + " && ") + parts[1];
|
||||
// relExe is always just a filename after flattenSingleDir
|
||||
String json = "{"
|
||||
+ "\"autoexec\":{"
|
||||
+ "\"options\":{\"script\":{\"value\":\"" + escapeJson(script) + "\"}}"
|
||||
+ "\"options\":{\"script\":{\"value\":\"" + escapeJson(relExe) + "\"}}"
|
||||
+ "},"
|
||||
+ "\"output\":{"
|
||||
+ "\"options\":{\"autolock\":{\"value\":true}}}"
|
||||
@@ -249,10 +284,7 @@ public class UploadResource {
|
||||
}
|
||||
|
||||
private String buildDosboxConf(String relExe) {
|
||||
String parts[] = splitPath(relExe);
|
||||
String dir = parts[0];
|
||||
String exe = parts[1];
|
||||
|
||||
// relExe is always just a filename after flattenSingleDir
|
||||
return "[sdl]\n"
|
||||
+ "autolock=true\n"
|
||||
+ "usescancodes=true\n"
|
||||
@@ -282,21 +314,7 @@ public class UploadResource {
|
||||
+ "mount c .\n"
|
||||
+ "c:\n"
|
||||
+ "cls\n"
|
||||
+ (dir.isEmpty() ? "" : "cd " + dir + "\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};
|
||||
+ relExe + "\n";
|
||||
}
|
||||
|
||||
private String escapeJson(String s) {
|
||||
|
||||
Reference in New Issue
Block a user