@@ -20,15 +20,14 @@ import java.nio.file.SimpleFileVisitor;
|
|||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
import java.util.logging.Logger;
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/** Manages game metadata stored as JSON files on disk. */
|
/** Manages game metadata stored as JSON files on disk. */
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class GameService implements GameStore {
|
public class GameService implements GameStore {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(GameService.class.getName());
|
|
||||||
|
|
||||||
private final ObjectMapper mapper = new ObjectMapper()
|
private final ObjectMapper mapper = new ObjectMapper()
|
||||||
.registerModule(new JavaTimeModule())
|
.registerModule(new JavaTimeModule())
|
||||||
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
|
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
|
||||||
@@ -121,7 +120,7 @@ public class GameService implements GameStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Delete a game and all its files. */
|
/** Delete a game and all its files. */
|
||||||
public boolean delete(String id) throws IOException {
|
public void delete(String id) throws IOException {
|
||||||
Path dir = gameDir(id);
|
Path dir = gameDir(id);
|
||||||
if (Files.exists(dir)) {
|
if (Files.exists(dir)) {
|
||||||
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
||||||
@@ -140,7 +139,6 @@ public class GameService implements GameStore {
|
|||||||
// Clean up old flat .jsdos locations (pre-game-dir layout — backward compat)
|
// Clean up old flat .jsdos locations (pre-game-dir layout — backward compat)
|
||||||
Files.deleteIfExists(gamesDir.resolve(id + ".jsdos"));
|
Files.deleteIfExists(gamesDir.resolve(id + ".jsdos"));
|
||||||
Files.deleteIfExists(gamesDir.resolve(id + ".setup.jsdos"));
|
Files.deleteIfExists(gamesDir.resolve(id + ".setup.jsdos"));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sanitize a string for use as a game directory ID. */
|
/** Sanitize a string for use as a game directory ID. */
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public interface GameStore {
|
|||||||
Game load(String id) throws IOException, NoSuchFileException;
|
Game load(String id) throws IOException, NoSuchFileException;
|
||||||
void save(Game game) throws IOException;
|
void save(Game game) throws IOException;
|
||||||
List<Game> list() throws IOException;
|
List<Game> list() throws IOException;
|
||||||
boolean delete(String id) throws IOException;
|
void delete(String id) throws IOException;
|
||||||
Path gameDir(String id);
|
Path gameDir(String id);
|
||||||
Path gameJsonPath(String id);
|
Path gameJsonPath(String id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import jakarta.ws.rs.Consumes;
|
|||||||
import jakarta.ws.rs.DefaultValue;
|
import jakarta.ws.rs.DefaultValue;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.POST;
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.PathParam;
|
import jakarta.ws.rs.PathParam;
|
||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
import jakarta.ws.rs.QueryParam;
|
import jakarta.ws.rs.QueryParam;
|
||||||
@@ -13,7 +14,7 @@ import jakarta.ws.rs.core.Response;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/** IGDB metadata & artwork integration. Enabled when TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET are set. */
|
/** IGDB metadata & artwork integration. Enabled when TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET are set. */
|
||||||
@jakarta.ws.rs.Path("/api/igdb")
|
@Path("/api/igdb")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public class IgdbController {
|
public class IgdbController {
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ public class IgdbController {
|
|||||||
GameService games;
|
GameService games;
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@jakarta.ws.rs.Path("/search")
|
@Path("/search")
|
||||||
public Response search(@QueryParam("q") @DefaultValue("") String query) {
|
public Response search(@QueryParam("q") @DefaultValue("") String query) {
|
||||||
if (query.isBlank()) {
|
if (query.isBlank()) {
|
||||||
return Response.status(400).entity(Map.of("error", "Query parameter 'q' is required")).build();
|
return Response.status(400).entity(Map.of("error", "Query parameter 'q' is required")).build();
|
||||||
@@ -45,7 +46,7 @@ public class IgdbController {
|
|||||||
* Body: {} — auto-search using the game's title.
|
* Body: {} — auto-search using the game's title.
|
||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@jakarta.ws.rs.Path("/scrape/{gameId}")
|
@Path("/scrape/{gameId}")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response scrape(@PathParam("gameId") String gameId, Map<String, Object> body) {
|
public Response scrape(@PathParam("gameId") String gameId, Map<String, Object> body) {
|
||||||
try {
|
try {
|
||||||
@@ -72,7 +73,7 @@ public class IgdbController {
|
|||||||
|
|
||||||
/** Check if IGDB credentials are configured. */
|
/** Check if IGDB credentials are configured. */
|
||||||
@GET
|
@GET
|
||||||
@jakarta.ws.rs.Path("/status")
|
@Path("/status")
|
||||||
public Response status() {
|
public Response status() {
|
||||||
boolean configured = svc.isConfigured();
|
boolean configured = svc.isConfigured();
|
||||||
return Response.ok(Map.of(
|
return Response.ok(Map.of(
|
||||||
|
|||||||
Reference in New Issue
Block a user