fix: remove sockdrive mode, always create .jsdos ZIP bundles
Build & Deploy / build-and-deploy (push) Successful in 40s
Build & Deploy / build-and-deploy (push) Successful in 40s
js-dos v8's window.Dos(container, { url }) expects the URL to point
to a .jsdos ZIP archive, not a loose dosbox.conf file. The
'sockdrive' mode was serving game files loosely with a URL pointing
to a raw config file, causing: 'Unable to add .jsdos/jsdos.json
into bundle.zip'.
Changes:
- Removed SOCKDRIVE_THRESHOLD_MB and the entire sockdrive branch
- Always create a .jsdos ZIP bundle via createBundle()
- Optimized createBundle: no temp dir copy (saves 2x I/O for large
games), uses STORE compression (level 0) since DOS files are
often already compressed
- Removed dirSizeMB(), copyDir() and setup config builders —
no longer needed after removing sockdrive mode
- frontend bundleUrl()/setupBundleUrl() always use the standard
/games/{id}.jsdos path
- GameService.delete cleanup simplified
This commit is contained in:
@@ -105,17 +105,11 @@ export function artworkUrl(path) {
|
|||||||
/** Get game bundle URL for normal play */
|
/** Get game bundle URL for normal play */
|
||||||
export function bundleUrl(game) {
|
export function bundleUrl(game) {
|
||||||
if (!game) return null;
|
if (!game) return null;
|
||||||
if (game.bundle_type === "sockdrive") {
|
|
||||||
return `/api/sockdrive/${game.id}/file?path=.jsdos/dosbox.conf`;
|
|
||||||
}
|
|
||||||
return `/games/${game.id}.jsdos`;
|
return `/games/${game.id}.jsdos`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get game bundle URL for setup mode (runs SETUP.EXE instead) */
|
/** Get game bundle URL for setup mode (runs SETUP.EXE instead) */
|
||||||
export function setupBundleUrl(game) {
|
export function setupBundleUrl(game) {
|
||||||
if (!game) return null;
|
if (!game) return null;
|
||||||
if (game.bundle_type === "sockdrive") {
|
|
||||||
return `/api/sockdrive/${game.id}/file?path=.jsdos-setup/dosbox.conf`;
|
|
||||||
}
|
|
||||||
return `/games/${game.id}.setup.jsdos`;
|
return `/games/${game.id}.setup.jsdos`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,22 +123,6 @@ public class GameService {
|
|||||||
Files.deleteIfExists(gamesDir.resolve(id + ".jsdos"));
|
Files.deleteIfExists(gamesDir.resolve(id + ".jsdos"));
|
||||||
// Remove flat setup .jsdos bundle (if any)
|
// Remove flat setup .jsdos bundle (if any)
|
||||||
Files.deleteIfExists(gamesDir.resolve(id + ".setup.jsdos"));
|
Files.deleteIfExists(gamesDir.resolve(id + ".setup.jsdos"));
|
||||||
// Remove .jsdos-setup dir (sockdrive mode, if any)
|
|
||||||
Path setupDir = dir.resolve(".jsdos-setup");
|
|
||||||
if (Files.exists(setupDir)) {
|
|
||||||
Files.walkFileTree(setupDir, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
|
|
||||||
Files.delete(f);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public FileVisitResult postVisitDirectory(Path d, IOException e) throws IOException {
|
|
||||||
Files.delete(d);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import java.util.logging.Logger;
|
|||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public class UploadResource {
|
public class UploadResource {
|
||||||
|
|
||||||
private static final double SOCKDRIVE_THRESHOLD_MB = 80.0;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GameService svc;
|
GameService svc;
|
||||||
|
|
||||||
@@ -30,6 +28,8 @@ public class UploadResource {
|
|||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(UploadResource.class.getName());
|
private static final Logger LOG = Logger.getLogger(UploadResource.class.getName());
|
||||||
|
|
||||||
|
// ─── Upload ──────────────────────────────────────────────────
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||||
public Response upload(
|
public Response upload(
|
||||||
@@ -76,37 +76,12 @@ public class UploadResource {
|
|||||||
// Find setup executable (SETUP.EXE, INSTALL.EXE, CONFIG.EXE, etc.)
|
// Find setup executable (SETUP.EXE, INSTALL.EXE, CONFIG.EXE, etc.)
|
||||||
String setupExe = findSetupExe(extractDir);
|
String setupExe = findSetupExe(extractDir);
|
||||||
|
|
||||||
// Determine bundle strategy
|
|
||||||
double sizeMb = dirSizeMB(extractDir);
|
|
||||||
String bundleType = sizeMb > SOCKDRIVE_THRESHOLD_MB ? "sockdrive" : "standard";
|
|
||||||
|
|
||||||
// Create game directory
|
// Create game directory
|
||||||
Path gameDir = svc.gameDir(gameId);
|
Path gameDir = svc.gameDir(gameId);
|
||||||
Files.createDirectories(gameDir);
|
Files.createDirectories(gameDir);
|
||||||
|
|
||||||
String bundleFile;
|
// Create .jsdos bundle (ZIP with game files + .jsdos/ config)
|
||||||
if ("sockdrive".equals(bundleType)) {
|
String bundleFile = gameId + ".jsdos";
|
||||||
// Copy files loose
|
|
||||||
copyDir(extractDir, gameDir);
|
|
||||||
// Write js-dos config
|
|
||||||
Path jsdos = gameDir.resolve(".jsdos");
|
|
||||||
Files.createDirectories(jsdos);
|
|
||||||
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";
|
|
||||||
|
|
||||||
// Write setup config if a setup executable was found
|
|
||||||
if (setupExe != null) {
|
|
||||||
Path setupDir = gameDir.resolve(".jsdos-setup");
|
|
||||||
Files.createDirectories(setupDir);
|
|
||||||
String relSetup = extractDir.relativize(Path.of(setupExe)).toString();
|
|
||||||
Files.writeString(setupDir.resolve("dosbox.conf"), buildSetupDosboxConf(relSetup));
|
|
||||||
Files.write(setupDir.resolve("jsdos.json"), buildSetupJsdosJson(relSetup));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Create .jsdos bundle (flat in games dir)
|
|
||||||
bundleFile = gameId + ".jsdos";
|
|
||||||
Path bundlePath = svc.getGamesDir().resolve(bundleFile);
|
Path bundlePath = svc.getGamesDir().resolve(bundleFile);
|
||||||
createBundle(extractDir, mainExe, bundlePath);
|
createBundle(extractDir, mainExe, bundlePath);
|
||||||
|
|
||||||
@@ -115,11 +90,10 @@ public class UploadResource {
|
|||||||
Path setupBundlePath = svc.getGamesDir().resolve(gameId + ".setup.jsdos");
|
Path setupBundlePath = svc.getGamesDir().resolve(gameId + ".setup.jsdos");
|
||||||
createBundle(extractDir, setupExe, setupBundlePath);
|
createBundle(extractDir, setupExe, setupBundlePath);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Save metadata
|
// Save metadata
|
||||||
Game game = new Game(gameId, title);
|
Game game = new Game(gameId, title);
|
||||||
game.setBundleType(bundleType);
|
game.setBundleType("standard");
|
||||||
game.setBundleFile(bundleFile);
|
game.setBundleFile(bundleFile);
|
||||||
game.setSetupExe(setupExe != null ? extractDir.relativize(Path.of(setupExe)).toString() : null);
|
game.setSetupExe(setupExe != null ? extractDir.relativize(Path.of(setupExe)).toString() : null);
|
||||||
game.setReady(true);
|
game.setReady(true);
|
||||||
@@ -297,24 +271,20 @@ public class UploadResource {
|
|||||||
|
|
||||||
// ─── Bundle Creation ─────────────────────────────────────────
|
// ─── Bundle Creation ─────────────────────────────────────────
|
||||||
|
|
||||||
private void createBundle(Path extractDir, String mainExe, Path bundlePath) throws IOException {
|
private void createBundle(Path extractDir, String exePath, Path bundlePath) throws IOException {
|
||||||
// Create temporary bundle dir
|
// Write .jsdos config directly into the extract directory
|
||||||
Path tmpBundle = Files.createTempDirectory("dostalgia-bundle-");
|
Path jsdos = extractDir.resolve(".jsdos");
|
||||||
try {
|
|
||||||
copyDir(extractDir, tmpBundle);
|
|
||||||
|
|
||||||
// Create .jsdos config
|
|
||||||
Path jsdos = tmpBundle.resolve(".jsdos");
|
|
||||||
Files.createDirectories(jsdos);
|
Files.createDirectories(jsdos);
|
||||||
String relExe = extractDir.relativize(Path.of(mainExe)).toString();
|
String relExe = extractDir.relativize(Path.of(exePath)).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));
|
||||||
|
|
||||||
// ZIP it up
|
// ZIP it up directly from extractDir (no temp copy)
|
||||||
try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundlePath))) {
|
try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundlePath))) {
|
||||||
Files.walk(tmpBundle).filter(Files::isRegularFile).forEach(f -> {
|
zos.setLevel(0); // Store mode — DOS files are often already compressed
|
||||||
|
Files.walk(extractDir).filter(Files::isRegularFile).forEach(f -> {
|
||||||
try {
|
try {
|
||||||
String entryName = tmpBundle.relativize(f).toString().replace('\\', '/');
|
String entryName = extractDir.relativize(f).toString().replace('\\', '/');
|
||||||
zos.putNextEntry(new java.util.zip.ZipEntry(entryName));
|
zos.putNextEntry(new java.util.zip.ZipEntry(entryName));
|
||||||
Files.copy(f, zos);
|
Files.copy(f, zos);
|
||||||
zos.closeEntry();
|
zos.closeEntry();
|
||||||
@@ -323,9 +293,11 @@ public class UploadResource {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
deleteDir(tmpBundle);
|
// Clean up .jsdos config files from the extract dir
|
||||||
}
|
Files.walk(jsdos).sorted(Comparator.reverseOrder()).forEach(p -> {
|
||||||
|
try { Files.deleteIfExists(p); } catch (IOException ignored) {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Configuration Helpers ───────────────────────────────────
|
// ─── Configuration Helpers ───────────────────────────────────
|
||||||
@@ -380,86 +352,8 @@ public class UploadResource {
|
|||||||
return s.replace("\\", "\\\\").replace("\"", "\\\"");
|
return s.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Setup Config Builders ───────────────────────────────────
|
|
||||||
|
|
||||||
private byte[] buildSetupJsdosJson(String relSetup) {
|
|
||||||
String json = "{"
|
|
||||||
+ "\"autoexec\":{"
|
|
||||||
+ "\"options\":{\"script\":{\"value\":\"" + escapeJson(relSetup) + "\"}}"
|
|
||||||
+ "},"
|
|
||||||
+ "\"output\":{"
|
|
||||||
+ "\"options\":{\"autolock\":{\"value\":true}}}"
|
|
||||||
+ "}";
|
|
||||||
return json.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildSetupDosboxConf(String relSetup) {
|
|
||||||
return "[sdl]\n"
|
|
||||||
+ "autolock=true\n"
|
|
||||||
+ "usescancodes=true\n"
|
|
||||||
+ "output=surface\n"
|
|
||||||
+ "\n"
|
|
||||||
+ "[cpu]\n"
|
|
||||||
+ "core=auto\n"
|
|
||||||
+ "cycles=auto\n"
|
|
||||||
+ "\n"
|
|
||||||
+ "[mixer]\n"
|
|
||||||
+ "nosound=false\n"
|
|
||||||
+ "rate=44100\n"
|
|
||||||
+ "\n"
|
|
||||||
+ "[sblaster]\n"
|
|
||||||
+ "sbtype=sb16\n"
|
|
||||||
+ "irq=7\n"
|
|
||||||
+ "dma=1\n"
|
|
||||||
+ "hdma=5\n"
|
|
||||||
+ "\n"
|
|
||||||
+ "[dos]\n"
|
|
||||||
+ "xms=true\n"
|
|
||||||
+ "ems=true\n"
|
|
||||||
+ "umb=true\n"
|
|
||||||
+ "\n"
|
|
||||||
+ "[autoexec]\n"
|
|
||||||
+ "@echo off\n"
|
|
||||||
+ "mount c .\n"
|
|
||||||
+ "c:\n"
|
|
||||||
+ "cls\n"
|
|
||||||
+ "echo DOS Setup Utility\n"
|
|
||||||
+ "echo -----------------\n"
|
|
||||||
+ "echo Configure controls, sound, and other settings.\n"
|
|
||||||
+ "echo When done, exit to return here. Changes are saved automatically.\n"
|
|
||||||
+ "echo.\n"
|
|
||||||
+ relSetup + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── File Utilities ──────────────────────────────────────────
|
// ─── File Utilities ──────────────────────────────────────────
|
||||||
|
|
||||||
private double dirSizeMB(Path dir) throws IOException {
|
|
||||||
long[] total = {0};
|
|
||||||
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path f, BasicFileAttributes a) {
|
|
||||||
total[0] += a.size();
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return total[0] / (1024.0 * 1024.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyDir(Path src, Path dst) throws IOException {
|
|
||||||
Files.walkFileTree(src, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
|
||||||
public FileVisitResult preVisitDirectory(Path d, BasicFileAttributes a) throws IOException {
|
|
||||||
Files.createDirectories(dst.resolve(src.relativize(d)));
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
|
|
||||||
Files.copy(f, dst.resolve(src.relativize(f)), StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteDir(Path dir) throws IOException {
|
private void deleteDir(Path dir) throws IOException {
|
||||||
if (!Files.exists(dir)) return;
|
if (!Files.exists(dir)) return;
|
||||||
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
||||||
|
|||||||
Reference in New Issue
Block a user