From 3e0822df3b14eeaa7e0494d2e078a004bc23c4eb Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Sun, 10 Aug 2025 17:35:39 +0200 Subject: [PATCH] trackinfos optimiert laden ohne zu viele api aufrufe --- src/main/java/eric/Roullette/App.java | 22 +++++----- .../Roullette/controller/GameController.java | 42 ++++++++++--------- .../eric/Roullette/service/GameService.java | 19 +++++++++ .../Roullette/service/SpotifyAuthService.java | 9 +++- .../websocket/GameWebSocketHandler.java | 31 ++++++-------- src/main/resources/public/js/game.js | 14 +++---- 6 files changed, 79 insertions(+), 58 deletions(-) diff --git a/src/main/java/eric/Roullette/App.java b/src/main/java/eric/Roullette/App.java index 663a0ca..437b444 100644 --- a/src/main/java/eric/Roullette/App.java +++ b/src/main/java/eric/Roullette/App.java @@ -36,17 +36,17 @@ public class App { config.staticFiles.add("/public", Location.CLASSPATH); }).start(cfg.port); - app.before(ctx -> { - System.out.println("→ " + ctx.method() + " " + ctx.fullUrl()); - }); - app.after(ctx -> { - String limit = ctx.header("x-rate-limit-limit"); - String remaining = ctx.header("x-rate-limit-remaining"); - String reset = ctx.header("x-rate-limit-reset"); - String retryAfter = ctx.header("Retry-After"); - System.out.printf("← %d | limit=%s remaining=%s reset=%s retry-after=%s%n", - ctx.status().getCode(), limit, remaining, reset, retryAfter); - }); +// app.before(ctx -> { +// System.out.println("→ " + ctx.method() + " " + ctx.fullUrl()); +// }); +// app.after(ctx -> { +// String limit = ctx.header("x-rate-limit-limit"); +// String remaining = ctx.header("x-rate-limit-remaining"); +// String reset = ctx.header("x-rate-limit-reset"); +// String retryAfter = ctx.header("Retry-After"); +// System.out.printf("← %d | limit=%s remaining=%s reset=%s retry-after=%s%n", +// ctx.status().getCode(), limit, remaining, reset, retryAfter); +// }); app.exception(Exception.class, (e, ctx) -> { log.error("Unhandled error", e); diff --git a/src/main/java/eric/Roullette/controller/GameController.java b/src/main/java/eric/Roullette/controller/GameController.java index 80a9abe..9fc42d9 100644 --- a/src/main/java/eric/Roullette/controller/GameController.java +++ b/src/main/java/eric/Roullette/controller/GameController.java @@ -1,6 +1,6 @@ package eric.Roullette.controller; - import com.fasterxml.jackson.core.type.TypeReference; +// import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import io.javalin.Javalin; @@ -13,18 +13,18 @@ package eric.Roullette.controller; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.MediaType; - import com.fasterxml.jackson.databind.node.JsonNodeFactory; - import com.fasterxml.jackson.databind.node.ObjectNode; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; +// import com.fasterxml.jackson.databind.node.JsonNodeFactory; +// import com.fasterxml.jackson.databind.node.ObjectNode; +// import org.slf4j.Logger; +// import org.slf4j.LoggerFactory; import java.awt.*; - import java.io.IOException; - import java.util.List; +// import java.io.IOException; +// import java.util.List; import java.util.Map; - import java.util.Objects; - import java.util.UUID; +// import java.util.Objects; +// import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; - import java.util.concurrent.TimeUnit; +// import java.util.concurrent.TimeUnit; public class GameController { private final GameService gameService; @@ -65,9 +65,9 @@ public class GameController { private void setToken(String accessToken) { this.accessToken = accessToken; } - + @SuppressWarnings("unchecked") private void createGame(Context ctx) throws InterruptedException { - Map body = ctx.bodyAsClass(Map.class); + Map body = (Map) ctx.bodyAsClass(Map.class); String user = (String) body.get("username"); if (user == null || user.isBlank()) { ctx.status(400).result("username fehlt"); @@ -152,9 +152,10 @@ public class GameController { .build(); try (Response trackResp = client.newCall(getTrack).execute()) { if (!trackResp.isSuccessful()) { - ctx.status(trackResp.code()).result("Fehler beim Laden der Track-Details: " + trackResp.body().string()); + //ctx.status(trackResp.code()).result("Fehler beim Laden der Track-Details: " + trackResp.body().string()); return; } + assert trackResp.body() != null; var node = new com.fasterxml.jackson.databind.ObjectMapper().readTree(trackResp.body().string()); long durationMs = node.get("duration_ms").asLong(); long startOffset = durationMs / 2; @@ -171,16 +172,17 @@ public class GameController { .build(); try (Response playResp = client.newCall(playReq).execute()) { - ctx.status(playResp.code()); - ctx.header("Retry-After", playResp.header("Retry-After") != null ? playResp.header("Retry-After") : ""); - ctx.header("x-rate-limit-limit", playResp.header("x-rate-limit-limit")); - ctx.header("x-rate-limit-remaining", playResp.header("x-rate-limit-remaining")); - ctx.header("x-rate-limit-reset", playResp.header("x-rate-limit-reset")); - ctx.result(playResp.body().string()); +// ctx.status(playResp.code()); +// ctx.header("Retry-After", playResp.header("Retry-After") != null ? playResp.header("Retry-After") : ""); +// ctx.header("x-rate-limit-limit", playResp.header("x-rate-limit-limit")); +// ctx.header("x-rate-limit-remaining", playResp.header("x-rate-limit-remaining")); +// ctx.header("x-rate-limit-reset", playResp.header("x-rate-limit-reset")); +// ctx.result(playResp.body().string()); if (playResp.isSuccessful()) { ctx.status(204).result("Track erfolgreich abgespielt"); } else { - ctx.status(playResp.code()).result("Fehler beim Abspielen: " + playResp.body().string()); + //ctx.status(playResp.code()).result("Fehler beim Abspielen: " + playResp.body().string()); + System.out.println("Fehler beim Abspielen des Tracks"); } } } diff --git a/src/main/java/eric/Roullette/service/GameService.java b/src/main/java/eric/Roullette/service/GameService.java index dd7f08c..2161e7d 100644 --- a/src/main/java/eric/Roullette/service/GameService.java +++ b/src/main/java/eric/Roullette/service/GameService.java @@ -18,11 +18,30 @@ package eric.Roullette.service; this.authService = authService; } + public Map> getTrackInfos(Map> allPlayerTracks) { + // für jeden String Spieler in allPlayerTracks die Liste der Tracks an authservice übergeben + Map> trackInfos = new ConcurrentHashMap<>(); + for (Map.Entry> entry : allPlayerTracks.entrySet()) { + String player = entry.getKey(); + List tracks = entry.getValue(); + if (tracks.isEmpty()) continue; // Keine Tracks, skip + try { + List trackInfo = authService.getTrackInfos(tracks); + trackInfos.put(player, trackInfo); + } catch (Exception e) { + log.error("Fehler beim Abrufen der Track-Infos für Spieler {}: {}", player, e.getMessage()); + } + } + return trackInfos; + } + + public record Game(String id, List players, Map scores, String currentOwner, String currentSong, List allTracks, Map> playerTracks) { public static Game create(String id) { return new Game(id, new CopyOnWriteArrayList<>(), new ConcurrentHashMap<>(), null, null, new ArrayList<>(), new ConcurrentHashMap<>()); } + } public Game getOrCreateGame(String gameId) { diff --git a/src/main/java/eric/Roullette/service/SpotifyAuthService.java b/src/main/java/eric/Roullette/service/SpotifyAuthService.java index b94d986..17cd358 100644 --- a/src/main/java/eric/Roullette/service/SpotifyAuthService.java +++ b/src/main/java/eric/Roullette/service/SpotifyAuthService.java @@ -36,6 +36,7 @@ public class SpotifyAuthService { } public URI getAuthorizationUri(String user) { + System.out.println("Erstelle Auth-URL für Benutzer: " + user); // Temporäre API-Instanz nur für die Erstellung der Auth-URL SpotifyApi tempApi = new SpotifyApi.Builder() .setClientId(clientId) @@ -68,6 +69,7 @@ public class SpotifyAuthService { } public List getRecentTracks(String user) { + System.out.println("Hole kürzlich gespielte Tracks für Benutzer: " + user); int limit = 2; SpotifyApi userApi = userApis.get(user); @@ -112,6 +114,7 @@ public class SpotifyAuthService { } private List getSavedTracks(String user, int limit, int offset) { + System.out.println("Hole gespeicherte Tracks für Benutzer: " + user + ", Limit: " + limit + ", Offset: " + offset); SpotifyApi userApi = userApis.get(user); if (userApi == null) { @@ -143,10 +146,11 @@ public class SpotifyAuthService { return Collections.emptyList(); } } - public List getTrackInfos(List allTracks) { + public List getTrackInfos(List tracks) { + System.out.println("Hole Track-Infos für " + tracks.size() + " Tracks."); //für jede URI den titel holen List trackInfos = new ArrayList<>(); - for (String uri : allTracks) { + for (String uri : tracks) { SpotifyApi userApi = userApis.values().stream().findFirst().orElse(null); if (userApi == null) { System.err.println("Kein SpotifyApi-Client gefunden."); @@ -176,6 +180,7 @@ public class SpotifyAuthService { public String getAccessTokenForUser(String username) { + System.out.println("Hole AccessToken für Benutzer: " + username); SpotifyApi userApi = userApis.get(username); if (userApi == null) { System.err.println("Kein SpotifyApi-Client für Benutzer gefunden: " + username); diff --git a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java index 20b6022..ec4cdf8 100644 --- a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java +++ b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java @@ -25,8 +25,8 @@ public class GameWebSocketHandler { private final Map> trackInfoCache = new ConcurrentHashMap<>(); private final Map> allTracksCache = new ConcurrentHashMap<>(); //Map> - private final Map>> playerTracksCache = new ConcurrentHashMap<>(); - private final Map>> playerTrackInfoCache = new ConcurrentHashMap<>(); + private Map>> playerTracksCache = new ConcurrentHashMap<>(); + private Map>> playerTrackInfoCache = new ConcurrentHashMap<>(); public GameWebSocketHandler(GameService gameService) { this.service = gameService; @@ -84,14 +84,20 @@ public class GameWebSocketHandler { var currentGame = service.getOrCreateGame(gameId); if (currentGame.players().isEmpty()) return; // Tracks pro Spieler sammeln - Map> playerTracks = currentGame.playerTracks(); - List allTracks = playerTracks.values().stream() + Map> allPlayerTracks = currentGame.playerTracks(); + // alle tracks sammeln + List allTracks = allPlayerTracks.values().stream() .flatMap(List::stream) .toList(); - System.out.println("AlltracksCache für Spiel " + gameId + " hat " + allTracks.size() + " Songs (rundesnstart)"); + System.out.println("AlltracksCache für Spiel " + gameId + " hat " + allTracks.size() + " Songs (rundenstart)"); if (!allTracks.isEmpty()) { service.startRound(gameId, allTracks); } + // Trackinfos für alle Spieler sammeln + Map> allTrackInfos = service.getTrackInfos(allPlayerTracks); + // Cache für Trackinfos pro Spiel-ID + playerTrackInfoCache.put(gameId, allTrackInfos); + System.out.println("TrackInfosCache für Spiel " + gameId + " hat " + allTrackInfos.size() + " Spieler (rundenstart)"); broadcastRoundStart(gameId); } } @@ -101,9 +107,7 @@ public class GameWebSocketHandler { public void nextround(String gameId) { var game = service.getOrCreateGame(gameId); if (game.players().isEmpty()) return; - // Songs von allen Spielern sammeln - Map> playerTracks = game.playerTracks(); List allTracks = new ArrayList<>(); for (String player : game.players()) { allTracks.addAll(game.playerTracks().getOrDefault(player, List.of())); @@ -112,7 +116,7 @@ public class GameWebSocketHandler { // TODO: Fehler an Client senden, dass keine Songs da sind return; } - + // TODO funktionalität bei neu joinenden Spielern überprüfen // Runde im Service starten, um Song und Owner zu setzen service.startRound(gameId, allTracks); // Jetzt Broadcast mit den aktuellen Daten @@ -126,17 +130,8 @@ public class GameWebSocketHandler { List opts = game.players(); String songUri = game.currentSong(); List allTracks = game.allTracks(); - List trackInfos = game.allTracks(); + Map> trackInfos = playerTrackInfoCache.get(gameId); // Cache pro Spiel-ID nutzen -// List trackInfos = trackInfoCache.get(gameId); -// if (trackInfos == null || trackInfos.isEmpty()) { -// System.out.println("TrackInfoCache ist leer, hole Infos von Spotify"); -// trackInfos = authService.getTrackInfos(allTracks); -// trackInfoCache.put(gameId, trackInfos); -// System.out.println("TrackInfoCache für Spiel " + gameId + " hat " + trackInfos.size() + " Infos"); -// } else { -// System.out.println("TrackInfoCache ist nicht leer, nutze gecachte Infos"); -// } String msg = JsonUtil.toJson(Map.of( "type", "round-start", diff --git a/src/main/resources/public/js/game.js b/src/main/resources/public/js/game.js index e98fafb..a9e7505 100644 --- a/src/main/resources/public/js/game.js +++ b/src/main/resources/public/js/game.js @@ -163,13 +163,13 @@ async function handleRoundStart({ownerOptions, songUri, allTracks, trackInfos}) const songList = document.getElementById("songList"); songList.innerHTML = ""; - if (Array.isArray(trackInfos)) { - trackInfos.forEach(trackInfo => { - const li = document.createElement("li"); - li.textContent = trackInfo; - songList.appendChild(li); - }); - } + //trackinfos ist eine map bestehend aus aus Spielername und Liste von Track-Infos + const userTracks = trackInfos?.[username] ?? []; + userTracks.forEach(trackInfo => { + const li = document.createElement("li"); + li.textContent = trackInfo; + songList.appendChild(li); + }); //playLock = false; }