async def get_game_stats(self, game_id):
        """
        Gets the game information.
        """
        query = """
            SELECT
                `game_stats`.`startTime` AS start_time,
                `game_stats`.`endTime` AS end_time,
                `game_stats`.`gameType` AS game_type,
                `login`.`login` AS host,
                `game_stats`.`gameName` AS game_name,
                `game_featuredMods`.`gamemod` AS game_mod,
                `map`.`display_name` as map_name,
                `map_version`.`filename` AS file_name
            FROM `game_stats`
            LEFT JOIN `map`
              ON `game_stats`.`mapId` = `map`.`id`
            LEFT JOIN `map_version`
              ON `map_version`.`map_id` = `map`.`id`
            LEFT JOIN `login`
              ON `login`.id = `game_stats`.`host`
            LEFT JOIN  `game_featuredMods`
              ON `game_stats`.`gameMod` = `game_featuredMods`.`id`
            WHERE `game_stats`.`id` = %s
        """
        player_query = """
           SELECT COUNT(*) FROM `game_player_stats`
           WHERE `game_player_stats`.`gameId` = %s
        """
        game_stats = await self._db.execute(query, (game_id,))
        player_count = await self._db.execute(player_query, (game_id,))
        if not game_stats:
            raise BookkeepingError(f"No stats found for game {game_id}")
        if not player_count:
            raise BookkeepingError(f"No players found for game {game_id}")
        start_time = game_stats[0]['start_time'].timestamp()

        # We might end a replay before end_time is set in the db!
        end_time = game_stats[0]['end_time']
        if end_time is None:
            end_time = time.time()
        else:
            end_time = end_time.timestamp()
        return {
            'featured_mod': game_stats[0]['game_mod'],
            'game_type': game_stats[0]['game_type'],
            'recorder': game_stats[0]['host'],
            'host': game_stats[0]['host'],
            'launched_at': start_time,
            'game_end': end_time,
            'title': game_stats[0]['game_name'],
            'mapname': game_stats[0]['map_name'],
            'map_file_path': game_stats[0]['file_name'],
            'num_players': player_count[0]['COUNT(*)']
        }
Ejemplo n.º 2
0
 async def save_replay(self, game_id, stream):
     if stream.header is None:
         raise BookkeepingError("Saved replay has no header")
     info = await self._get_replay_info(game_id, stream.header.struct)
     rfile = self._paths.get(game_id)
     try:
         with open(rfile, "wb") as f:
             await self._write_replay_in_thread(
                 f, info, stream.header.data + stream.data.bytes())
     except IOError as e:
         raise BookkeepingError(f"Failed to write replay: {short_exc(e)}")
Ejemplo n.º 3
0
 async def execute(self, query, params=[]):
     if self._connection_pool is None:
         raise BookkeepingError("Tried to run query while pool is closed!")
     try:
         async with self._connection_pool.acquire() as conn:
             async with conn.cursor(aiomysql.DictCursor) as cur:
                 await cur.execute(query, *params)
                 data = await cur.fetchall()
             await conn.commit()
         return data
     except (DatabaseError, RuntimeError) as e:
         raise BookkeepingError(f"Failed to run database query: {str(e)}")
Ejemplo n.º 4
0
    def get_replay_ticks(self, data):
        """Parse the replay data and extract the total number of ticks

        Parameters
        ----------
        data: The replay body only (without the header). The first byte must be
            the start of a valid replay command.
        """
        if not data:
            raise BookkeepingError("No replay data")

        try:
            return body_ticks(data)
        except ReplayReadError as e:
            raise BookkeepingError(f"Failed to parse replay: {e}")
Ejemplo n.º 5
0
    async def _get_replay_info(self, game_id, header):
        result = {}
        result['uid'] = game_id
        result['complete'] = True
        result['state'] = 'PLAYING'
        try:
            result['sim_mods'] = {
                mod['uid']: mod['version']
                for mod in header.get('mods', {}).values()
            }
        except KeyError:  # TODO - validate elsewhere?
            raise BookkeepingError("Replay header has invalid sim_mods")

        game_stats = await self._database.get_game_stats(game_id)
        teams = await self._database.get_teams_in_game(game_id)
        result.update(game_stats)
        result['teams'] = self._fixup_team_dict(teams)

        game_mod = game_stats["featured_mod"]
        if game_mod is None:
            featured_mods = {}
        else:
            featured_mods = await self._database.get_mod_versions(game_mod)
        result['featured_mod_versions'] = featured_mods
        return result
Ejemplo n.º 6
0
 def get(self, game_id):
     rpath = self._replay_path(game_id)
     os.makedirs(rpath, exist_ok=True)
     rfile = os.path.join(rpath, f"{str(game_id)}.fafreplay")
     if os.path.exists(rfile):
         raise BookkeepingError(f"Replay file {rfile} already exists")
     open(rfile, 'a').close()  # Touch file
     return rfile
Ejemplo n.º 7
0
 async def execute(self, query, params=[]):
     try:
         async with self._lock:
             async with self._conn.cursor(aiomysql.DictCursor) as cur:
                 await cur.execute(query, *params)
                 return await cur.fetchall()
     except (DatabaseError, RuntimeError) as e:
         raise BookkeepingError(f"Failed to execute query: {e}")
Ejemplo n.º 8
0
 def _write_replay(self, rfile, info, data):
     try:
         rfile.write(json.dumps(info).encode('UTF-8'))
         rfile.write(b"\n")
         data = struct.pack("i", len(data)) + zlib.compress(data)
         data = base64.b64encode(data)
         rfile.write(data)
     except UnicodeEncodeError:
         raise BookkeepingError("Unicode encoding error")
Ejemplo n.º 9
0
 def _write_replay(self, rfile, info, data):
     try:
         rfile.write(json.dumps(info).encode('UTF-8'))
         rfile.write(b"\n")
         data = struct.pack(">i", len(data)) + zlib.compress(data)
         data = base64.b64encode(data)
         rfile.write(data)
     # json should always produce ascii, but just in case...
     except UnicodeEncodeError:
         raise BookkeepingError("Unicode encoding error")
 async def get_teams_in_game(self, game_id):
     query = """
         SELECT
             `login`.`login` AS login,
             `game_player_stats`.`team` AS team
         FROM `game_stats`
         INNER JOIN `game_player_stats`
           ON `game_player_stats`.`gameId` = `game_stats`.`id`
         INNER JOIN `login`
           ON `login`.id = `game_player_stats`.`playerId`
         WHERE `game_stats`.`id` = %s AND `game_player_stats`.`AI` = 0
     """
     players = await self._db.execute(query, (game_id,))
     if not players:
         raise BookkeepingError("No game players found")
     teams = {}
     for player in players:
         teams.setdefault(player['team'], []).append(player['login'])
     return teams
Ejemplo n.º 11
0
    async def get_game_stats(self, game_id):
        """
        Gets the game information.
        """
        query = """
            SELECT
                `game_stats`.`startTime` AS start_time,
                `game_stats`.`endTime` AS end_time,
                `game_stats`.`gameType` AS game_type,
                `login`.`login` AS host,
                `game_stats`.`gameName` AS game_name,
                `game_featuredMods`.`gamemod` AS game_mod,
                `table_map`.`filename` AS file_name
            FROM `game_stats`
            LEFT JOIN `table_map`
              ON `game_stats`.`mapId` = `table_map`.`id`
            LEFT JOIN `login`
              ON `login`.id = `game_stats`.`host`
            LEFT JOIN  `game_featuredMods`
              ON `game_stats`.`gameMod` = `game_featuredMods`.`id`
            WHERE `game_stats`.`id` = %s
        """
        player_query = """
           SELECT COUNT(*) FROM `game_player_stats`
           WHERE `game_player_stats`.`gameId` = %s
        """
        logger.debug(f"Performing query: {query}")
        game_stats = await self._db.execute(query, (game_id, ))
        logger.debug(f"Performing query: {player_query}")
        player_count = await self._db.execute(player_query, (game_id, ))
        player_count = player_count[0]['COUNT(*)']
        if not game_stats:
            raise BookkeepingError(f"No stats found for game {game_id}")
        start_time = game_stats[0]['start_time'].timestamp()

        # 'mapname' is a filename on the content server containing the map
        mapname = game_stats[0]['file_name']
        if mapname is None:
            # Legacy replay server is forgiving like this. We replicate its
            # behaviour.
            logger.warning(f"Map missing for game {game_id}! Saving anyway.")
            mapname = "None"
        else:
            mapname = os.path.splitext(os.path.basename(mapname))[0]

        # We might end a replay before end_time is set in the db!
        end_time = game_stats[0]['end_time']
        if end_time is None:
            end_time = time.time()
        else:
            end_time = end_time.timestamp()
        return {
            'featured_mod': game_stats[0]['game_mod'],
            'game_type': game_stats[0]['game_type'],
            'recorder': game_stats[0]['host'],
            'host': game_stats[0]['host'],
            'launched_at': start_time,
            'game_end': end_time,
            'title': game_stats[0]['game_name'],
            'mapname': mapname,
            'num_players': player_count
        }