style
Build & Deploy / build-and-deploy (push) Successful in 39s

This commit is contained in:
david.alvarez
2026-06-09 14:03:37 +02:00
parent 397c99bd0c
commit c3a332d600
16 changed files with 82 additions and 75 deletions
@@ -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.
+2 -15
View File
@@ -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()))
+6 -7
View File
@@ -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;
+6 -11
View File
@@ -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<>() {
+7 -2
View File
@@ -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();