1
0
mirror of synced 2024-11-28 16:00:51 +01:00
bemaniutils/bemani/data/mysql/lobby.py
2019-12-08 21:43:49 +00:00

295 lines
10 KiB
Python

import copy
from sqlalchemy import Table, Column, UniqueConstraint # type: ignore
from sqlalchemy.types import String, Integer, JSON # type: ignore
from sqlalchemy.dialects.mysql import BIGINT as BigInteger # type: ignore
from typing import Optional, Dict, List, Tuple, Any
from bemani.common import ValidatedDict, Time
from bemani.data.mysql.base import BaseData, metadata
from bemani.data.types import UserID
"""
Table for storing logistical information about a player who's session is
live. Mostly, this is used to store IP addresses and such for players that
could potentially match.
"""
playsession = Table( # type: ignore
'playsession',
metadata,
Column('id', Integer, nullable=False, primary_key=True),
Column('game', String(32), nullable=False),
Column('version', Integer, nullable=False),
Column('userid', BigInteger(unsigned=True), nullable=False),
Column('time', Integer, nullable=False, index=True),
Column('data', JSON, nullable=False),
UniqueConstraint('game', 'version', 'userid', name='game_version_userid'),
mysql_charset='utf8mb4',
)
"""
Table for storing open lobbies for matching between games.
"""
lobby = Table( # type: ignore
'lobby',
metadata,
Column('id', Integer, nullable=False, primary_key=True),
Column('game', String(32), nullable=False),
Column('version', Integer, nullable=False),
Column('userid', BigInteger(unsigned=True), nullable=False),
Column('time', Integer, nullable=False, index=True),
Column('data', JSON, nullable=False),
UniqueConstraint('game', 'version', 'userid', name='game_version_userid'),
mysql_charset='utf8mb4',
)
class LobbyData(BaseData):
def get_play_session_info(self, game: str, version: int, userid: UserID) -> Optional[ValidatedDict]:
"""
Given a game, version and a user ID, look up play session information for that user.
Parameters:
game - String identifying a game series.
version - Integer identifying the version of the game in the series.
userid - Integer identifying a user, as possibly looked up by UserData.
Returns:
A dictionary representing play session info stored by a game class, or None
if there is no active session for this game/version/user. The dictionary will
always contain an 'id' field which is the play session ID, and a 'time' field
which represents the timestamp when the play session began.
"""
sql = (
"SELECT id, time, data FROM playsession "
"WHERE game = :game AND version = :version AND userid = :userid "
"AND time > :time"
)
cursor = self.execute(
sql,
{
'game': game,
'version': version,
'userid': userid,
'time': Time.now() - Time.SECONDS_IN_HOUR,
},
)
if cursor.rowcount != 1:
# Settings doesn't exist
return None
result = cursor.fetchone()
data = ValidatedDict(self.deserialize(result['data']))
data['id'] = result['id']
data['time'] = result['time']
return data
def get_all_play_session_infos(self, game: str, version: int) -> List[Tuple[UserID, ValidatedDict]]:
"""
Given a game and version, look up all play session information.
Parameters:
game - String identifying a game series.
version - Integer identifying the version of the game in the series.
Returns:
A list of Tuples, consisting of a UserID and the dictionary that would be
returned for that user if get_play_session_info() was called for that user.
"""
sql = (
"SELECT id, time, userid, data FROM playsession "
"WHERE game = :game AND version = :version "
"AND time > :time"
)
cursor = self.execute(
sql,
{
'game': game,
'version': version,
'time': Time.now() - Time.SECONDS_IN_HOUR,
},
)
ret = []
for result in cursor.fetchall():
data = ValidatedDict(self.deserialize(result['data']))
data['id'] = result['id']
data['time'] = result['time']
ret.append((UserID(result['userid']), data))
return ret
def put_play_session_info(self, game: str, version: int, userid: UserID, data: Dict[str, Any]) -> None:
"""
Given a game, version and a user ID, save play session information for that user.
Parameters:
game - String identifying a game series.
version - Integer identifying the version of the game in the series.
userid - Integer identifying a user.
data - A dictionary of play session information to store.
"""
data = copy.deepcopy(data)
if 'id' in data:
del data['id']
# Add json to player session
sql = (
"INSERT INTO playsession (game, version, userid, time, data) " +
"VALUES (:game, :version, :userid, :time, :data) " +
"ON DUPLICATE KEY UPDATE time=VALUES(time), data=VALUES(data)"
)
self.execute(
sql,
{
'game': game,
'version': version,
'userid': userid,
'time': Time.now(),
'data': self.serialize(data),
},
)
def destroy_play_session_info(self, game: str, version: int, userid: UserID) -> None:
"""
Given a game, version and a user ID, throw away session info for that play session.
Parameters:
game - String identifying a game series.
version - Integer identifying the version of the game in the series.
userid - Integer identifying a user, as possibly looked up by UserData.
"""
# Kill this play session
sql = (
"DELETE FROM playsession WHERE game = :game AND version = :version AND userid = :userid"
)
self.execute(
sql,
{
'game': game,
'version': version,
'userid': userid,
},
)
# Prune any orphaned lobbies too
sql = "DELETE FROM playsession WHERE time <= :time"
self.execute(sql, {'time': Time.now() - Time.SECONDS_IN_HOUR})
def get_lobby(self, game: str, version: int, userid: UserID) -> Optional[ValidatedDict]:
"""
Given a game, version and a user ID, look up lobby information for that user.
Parameters:
game - String identifying a game series.
version - Integer identifying the version of the game in the series.
userid - Integer identifying a user, as possibly looked up by UserData.
Returns:
A dictionary representing lobby info stored by a game class, or None
if there is no active session for this game/version/user. The dictionary will
always contain an 'id' field which is the lobby ID, and a 'time' field representing
the timestamp the lobby was created.
"""
sql = (
"SELECT id, time, data FROM lobby "
"WHERE game = :game AND version = :version AND userid = :userid "
"AND time > :time"
)
cursor = self.execute(
sql,
{
'game': game,
'version': version,
'userid': userid,
'time': Time.now() - Time.SECONDS_IN_HOUR,
},
)
if cursor.rowcount != 1:
# Settings doesn't exist
return None
result = cursor.fetchone()
data = ValidatedDict(self.deserialize(result['data']))
data['id'] = result['id']
data['time'] = result['time']
return data
def get_all_lobbies(self, game: str, version: int) -> List[Tuple[UserID, ValidatedDict]]:
"""
Given a game and version, look up all active lobbies.
Parameters:
game - String identifying a game series.
version - Integer identifying the version of the game in the series.
Returns:
A list of dictionaries representing lobby info stored by a game class.
"""
sql = (
"SELECT userid, id, data FROM lobby "
"WHERE game = :game AND version = :version AND time > :time"
)
cursor = self.execute(
sql,
{
'game': game,
'version': version,
'time': Time.now() - Time.SECONDS_IN_HOUR,
},
)
ret = []
for result in cursor.fetchall():
data = ValidatedDict(self.deserialize(result['data']))
data['id'] = result['id']
ret.append((UserID(result['userid']), data))
return ret
def put_lobby(self, game: str, version: int, userid: UserID, data: Dict[str, Any]) -> None:
"""
Given a game, version and a user ID, save lobby information for that user.
Parameters:
game - String identifying a game series.
version - Integer identifying the version of the game in the series.
userid - Integer identifying a user.
data - A dictionary of lobby information to store.
"""
data = copy.deepcopy(data)
if 'id' in data:
del data['id']
# Add json to lobby
sql = (
"INSERT INTO lobby (game, version, userid, time, data) " +
"VALUES (:game, :version, :userid, :time, :data) " +
"ON DUPLICATE KEY UPDATE time=VALUES(time), data=VALUES(data)"
)
self.execute(
sql,
{
'game': game,
'version': version,
'userid': userid,
'time': Time.now(),
'data': self.serialize(data),
},
)
def destroy_lobby(self, lobbyid: int) -> None:
"""
Given a lobby ID, destroy the lobby. The lobby ID can be obtained by reading
the 'id' field of the get_lobby response.
Parameters:
lobbyid: Integer identifying a lobby.
"""
# Delete this lobby
sql = "DELETE FROM lobby WHERE id = :id"
self.execute(sql, {'id': lobbyid})
# Prune any orphaned lobbies too
sql = "DELETE FROM lobby WHERE time <= :time"
self.execute(sql, {'time': Time.now() - Time.SECONDS_IN_HOUR})