diff --git a/comprl-web-reflex/comprl_web/pages/games.py b/comprl-web-reflex/comprl_web/pages/games.py index 02fa860..da40833 100644 --- a/comprl-web-reflex/comprl_web/pages/games.py +++ b/comprl-web-reflex/comprl_web/pages/games.py @@ -19,9 +19,13 @@ def show_game(game: GameInfo) -> rx.Component: rx.table.cell(game.time), rx.table.cell(game.id), rx.table.cell( - rx.button( - "Download game data", - on_click=lambda: UserGamesState.download_game(game.id), + rx.cond( + game.has_game_file, + rx.button( + "Download game data", + on_click=lambda: UserGamesState.download_game(game.id), + ), + rx.text("No game data", align="center", style={"font-style": "italic"}), ) ), ) diff --git a/comprl-web-reflex/comprl_web/protected_state.py b/comprl-web-reflex/comprl_web/protected_state.py index ad7ff3a..d9043a4 100644 --- a/comprl-web-reflex/comprl_web/protected_state.py +++ b/comprl-web-reflex/comprl_web/protected_state.py @@ -1,6 +1,7 @@ """State for the protected pages.""" import dataclasses +import pathlib from typing import Sequence import reflex as rx @@ -27,6 +28,7 @@ class GameInfo: result: str time: str id: str + has_game_file: bool class ProtectedState(reflex_local_auth.LocalAuthState): @@ -217,6 +219,9 @@ def clear_search(self): self.search_id = "" self.load_user_games() + def _get_game_file_path(self, game_id: str) -> pathlib.Path: + return config.get_config().data_dir / "game_actions" / f"{game_id}.pkl" + @rx.event def load_user_games(self) -> None: self.user_games = [] @@ -230,6 +235,8 @@ def load_user_games(self) -> None: else: result = "Unknown" + game_file_exists = self._get_game_file_path(game.game_id).exists() + self.user_games.append( GameInfo( game.user1_.username, @@ -237,6 +244,7 @@ def load_user_games(self) -> None: result, str(game.start_time.strftime("%Y-%m-%d %H:%M:%S")), str(game.game_id), + game_file_exists, ) ) @@ -244,12 +252,11 @@ def load_user_games(self) -> None: @rx.event def download_game(self, game_id: str): - game_file_name = f"{game_id}.pkl" - game_file_path = config.get_config().data_dir / "game_actions" / game_file_name + game_file_path = self._get_game_file_path(game_id) try: data = game_file_path.read_bytes() except Exception: raise RuntimeError("Game file not found") from None - return rx.download(filename=game_file_name, data=data) + return rx.download(filename=game_file_path.name, data=data) diff --git a/comprl/src/comprl/server/interfaces.py b/comprl/src/comprl/server/interfaces.py index 1964678..44b58f5 100644 --- a/comprl/src/comprl/server/interfaces.py +++ b/comprl/src/comprl/server/interfaces.py @@ -150,22 +150,27 @@ def _end(self, reason="unknown"): Args: reason (str): The reason why the game has ended. Defaults to "unknown". """ - - # store actions: - # TODO: maybe add multithreading here to ease the load on the main server thread - # as storing the actions can take a while - self.game_info["actions"] = np.array(self.all_actions) - - data_dir = get_config().data_dir - # should already be checked during config loading but just to be sure - assert data_dir.is_dir(), f"data_dir '{data_dir}' is not a directory" - game_actions_dir = data_dir / "game_actions" - game_actions_dir.mkdir(exist_ok=True) - output_file = game_actions_dir / f"{self.id}.pkl" - - logging.debug("Save game actions to %s", output_file) - with open(output_file, "wb") as f: - pickle.dump(self.game_info, f) + if self.disconnected_player_id is None: + # store actions: + try: + # TODO: maybe add multithreading here to ease the load on the main + # server thread as storing the actions can take a while + self.game_info["actions"] = np.array(self.all_actions) + + data_dir = get_config().data_dir + # should already be checked during config loading but just to be sure + assert data_dir.is_dir(), f"data_dir '{data_dir}' is not a directory" + game_actions_dir = data_dir / "game_actions" + game_actions_dir.mkdir(exist_ok=True) + output_file = game_actions_dir / f"{self.id}.pkl" + + logging.debug("Save game actions to %s", output_file) + with open(output_file, "wb") as f: + pickle.dump(self.game_info, f) + except Exception as e: + logging.error( + "Error while saving game actions: %s | game_id=%s", e, self.id + ) # notify end for callback in self.finish_callbacks: