@@ -7,10 +7,6 @@ import java.util.*;
|
||||
|
||||
/**
|
||||
* Single source of truth for all DOSBox configuration generation.
|
||||
* Eliminates the duplication that existed between GameService and UploadResource.
|
||||
*
|
||||
* Uses Java 21 text blocks for readable, maintainable config templates.
|
||||
* Adding a new config parameter requires a change in exactly ONE place.
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class ConfigBuilder {
|
||||
@@ -20,8 +16,6 @@ public class ConfigBuilder {
|
||||
".cue", 5, ".iso", 4, ".ccd", 3, ".img", 2, ".bin", 1
|
||||
);
|
||||
|
||||
// ─── Public API ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Build a dosbox.conf for a game with a known executable.
|
||||
* @param relExe relative path to the executable inside the ZIP (e.g. "FALLOUT.EXE" or "FALLOUT/FALLOUT.EXE")
|
||||
@@ -83,9 +77,6 @@ public class ConfigBuilder {
|
||||
|
||||
public record ExeParts(String dir, String exe) {}
|
||||
|
||||
// ─── Config Templates ────────────────────────────────────────
|
||||
|
||||
// language=ini
|
||||
private static final String DOSBOX_CONF_TEMPLATE = """
|
||||
[sdl]
|
||||
autolock=true
|
||||
@@ -121,7 +112,6 @@ public class ConfigBuilder {
|
||||
${EXEC_PATH}
|
||||
""";
|
||||
|
||||
// language=ini
|
||||
private static final String CD_ONLY_DOSBOX_CONF_TEMPLATE = """
|
||||
[sdl]
|
||||
autolock=true
|
||||
@@ -159,8 +149,6 @@ public class ConfigBuilder {
|
||||
echo Type D: and press Enter, then look for the game executable (e.g. DOTT.EXE).
|
||||
""";
|
||||
|
||||
// ─── Internal Helpers ────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Build the CD mount lines for the autoexec section.
|
||||
* Mounts ALL CD images, deduplicating by parent directory.
|
||||
|
||||
@@ -32,10 +32,10 @@ public class ConfigPatcher {
|
||||
})
|
||||
.forEach(f -> {
|
||||
try {
|
||||
String text = new String(Files.readAllBytes(f), StandardCharsets.ISO_8859_1);
|
||||
String text = Files.readString(f, StandardCharsets.ISO_8859_1);
|
||||
String patched = patchText(text, extractDir);
|
||||
if (!patched.equals(text)) {
|
||||
Files.write(f, patched.getBytes(StandardCharsets.ISO_8859_1));
|
||||
Files.writeString(f, patched, StandardCharsets.ISO_8859_1);
|
||||
LOG.info("Patched absolute paths in " + f.getFileName());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -4,10 +4,16 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Detects game executables within extracted archives.
|
||||
|
||||
@@ -17,37 +17,26 @@ public class Game {
|
||||
private String publisher;
|
||||
private String description;
|
||||
private Double rating;
|
||||
|
||||
private String bundleFile; // relative path within game dir
|
||||
|
||||
private String setupExe; // relative path to SETUP.EXE (null if none)
|
||||
|
||||
private String bundleFile;
|
||||
private String setupExe;
|
||||
/** Game bundle file size in bytes. Populated dynamically at load time (not persisted). */
|
||||
private Long bundleSize;
|
||||
|
||||
/** Currently selected main executable (relative path inside the bundle). */
|
||||
private String executable;
|
||||
|
||||
/** All discoverable executables in the bundle (for the UI picker). */
|
||||
private List<String> executables;
|
||||
|
||||
/** Platform type: "dos" for pure DOS, "windows" for Windows 3.x/9x+ */
|
||||
private String platform;
|
||||
|
||||
private Integer igdbId;
|
||||
private String igdbCoverId;
|
||||
|
||||
private boolean hasCover;
|
||||
private boolean hasScreenshots;
|
||||
private List<String> screenshots;
|
||||
private List<String> videos;
|
||||
|
||||
private boolean ready;
|
||||
private Instant createdAt;
|
||||
private Instant updatedAt;
|
||||
|
||||
// ─── Constructors ────────────────────────────────────────────
|
||||
|
||||
public Game() {}
|
||||
|
||||
public Game(String id, String title) {
|
||||
@@ -58,8 +47,6 @@ public class Game {
|
||||
this.updatedAt = Instant.now();
|
||||
}
|
||||
|
||||
// ─── Getters & Setters ──────────────────────────────────────
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
package com.dostalgia;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.PATCH;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.StreamingOutput;
|
||||
@@ -27,7 +35,6 @@ public class GameResource {
|
||||
try {
|
||||
var all = svc.list();
|
||||
|
||||
// Client-side filtering (small dataset, fine for a personal library)
|
||||
if (search != null && !search.isBlank()) {
|
||||
all = all.stream()
|
||||
.filter(g -> g.getTitle().toLowerCase().contains(search.toLowerCase()))
|
||||
|
||||
@@ -12,7 +12,12 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
@@ -53,8 +58,6 @@ public class GameService implements GameStore {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Game Directory Helpers ──────────────────────────────────
|
||||
|
||||
public Path gameDir(String id) {
|
||||
return gamesDir.resolve(id);
|
||||
}
|
||||
@@ -63,8 +66,6 @@ public class GameService implements GameStore {
|
||||
return gameDir(id).resolve("game.json");
|
||||
}
|
||||
|
||||
// ─── CRUD ────────────────────────────────────────────────────
|
||||
|
||||
/** Load a game by ID. Throws if not found. */
|
||||
public Game load(String id) throws IOException {
|
||||
Path path = gameJsonPath(id);
|
||||
@@ -142,8 +143,6 @@ public class GameService implements GameStore {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─── Utility ─────────────────────────────────────────────────
|
||||
|
||||
/** Sanitize a string for use as a game directory ID. */
|
||||
public static String sanitizeId(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package com.dostalgia;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -10,11 +10,14 @@ import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/** Client for the IGDB (Internet Game Database) API via Twitch OAuth2. */
|
||||
@@ -42,8 +45,6 @@ public class IgdbService {
|
||||
private String accessToken;
|
||||
private Instant tokenExpiry;
|
||||
|
||||
// ─── Token Management ─────────────────────────────────────────
|
||||
|
||||
public boolean isConfigured() {
|
||||
return clientId.isPresent() && clientSecret.isPresent()
|
||||
&& !clientId.get().isBlank() && !clientSecret.get().isBlank();
|
||||
@@ -84,8 +85,6 @@ public class IgdbService {
|
||||
.header("Content-Type", "text/plain");
|
||||
}
|
||||
|
||||
// ─── Search ───────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Search IGDB for games matching the given query.
|
||||
* Returns a list of simplified result maps suitable for JSON serialization.
|
||||
@@ -264,8 +263,6 @@ public class IgdbService {
|
||||
return screenshots;
|
||||
}
|
||||
|
||||
// ─── Auto-Scrape (called during upload) ──────────────────────
|
||||
|
||||
/**
|
||||
* Auto-search IGDB for the game title and apply the best match.
|
||||
* Updates the Game object in-place. Does NOT save — caller must svc.save().
|
||||
@@ -439,8 +436,6 @@ public class IgdbService {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Utility ──────────────────────────────────────────────────
|
||||
|
||||
private String sanitize(String s) {
|
||||
return s.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.dostalgia;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
@@ -8,8 +7,10 @@ import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.StreamingOutput;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
/** Serves game bundles (.jsdos) and artwork from the external data directory. */
|
||||
|
||||
@@ -10,10 +10,15 @@ import jakarta.ws.rs.core.Response;
|
||||
import org.jboss.resteasy.reactive.RestForm;
|
||||
import org.jboss.resteasy.reactive.multipart.FileUpload;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@jakarta.ws.rs.Path("/api/upload")
|
||||
@@ -46,8 +51,6 @@ public class UploadResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(UploadResource.class.getName());
|
||||
|
||||
// ─── Upload ──────────────────────────────────────────────────
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
public Response upload(
|
||||
@@ -217,8 +220,6 @@ public class UploadResource {
|
||||
return Response.ok(Map.of("status", "ok")).build();
|
||||
}
|
||||
|
||||
// ─── File Utilities ──────────────────────────────────────────
|
||||
|
||||
private void deleteDir(Path dir) throws IOException {
|
||||
if (!Files.exists(dir)) return;
|
||||
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
||||
|
||||
@@ -5,8 +5,13 @@ import jakarta.inject.Inject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.dostalgia;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
@@ -8,6 +7,10 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CdImageServiceTest {
|
||||
|
||||
private final CdImageService service = new CdImageService();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.dostalgia;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
class ConfigBuilderTest {
|
||||
|
||||
private final ConfigBuilder builder = new ConfigBuilder();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.dostalgia;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
@@ -8,6 +7,12 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ConfigPatcherTest {
|
||||
|
||||
private final ConfigPatcher patcher = new ConfigPatcher();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.dostalgia;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
@@ -8,6 +7,11 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ExecutableDetectorTest {
|
||||
|
||||
private final ExecutableDetector detector = new ExecutableDetector();
|
||||
@@ -125,8 +129,6 @@ class ExecutableDetectorTest {
|
||||
assertFalse(ExecutableDetector.isLikelySelfExtractor(f));
|
||||
}
|
||||
|
||||
// ─── helpers ─────────────────────────────────────────────────
|
||||
|
||||
private static byte[] createMZ(int size) {
|
||||
byte[] buf = new byte[size];
|
||||
buf[0] = 0x4D; // M
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.dostalgia;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
class PlatformDetectorTest {
|
||||
|
||||
private final PlatformDetector detector = new PlatformDetector();
|
||||
|
||||
Reference in New Issue
Block a user