From 0c342eff7b99d8fadb80ee52b39e80945b6ca947 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Thu, 4 Apr 2019 13:44:48 +0200 Subject: [PATCH] temporary session are now available --- README.md | 12 +++- connection_handler.py | 130 +++++++++++++++++++++++------------------- create_database.py | 4 +- main.py | 2 +- match.py | 3 +- match_manager.py | 15 +++-- session_manager.py | 38 +++++++++--- user_manager.py | 2 - 8 files changed, 125 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index a33b5d3..8970d6c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ communication with the web client is done by a (far from any standard and almost "data": { "id": "...", "revoke_time": , + "ranked": ", "match_state": > } } @@ -101,12 +102,18 @@ server response: **login or register**: + + +keep pw null for requesting a temporary session (will be deleted after one hour of inactivity and matches are not ranked) + + + ```json { "type": "login", "data": { "name": "", - "pw": "" + "pw": " | null" } } ``` @@ -118,7 +125,8 @@ response: "type": "login_response", "data": { "success": , - "id": "", + "id": "", + "registered": , "msg": "..." } } diff --git a/connection_handler.py b/connection_handler.py index fe901e5..32ce0b7 100644 --- a/connection_handler.py +++ b/connection_handler.py @@ -141,11 +141,14 @@ class ConnectionHandler(object): "success": True, "id": conn.id, "user": conn.user_name, + "registered": conn.is_registered_user, "msg": "" } })) + await self.send_elo(conn) - await self.send_friends(conn) + if conn.is_registered_user: + await self.send_friends(conn) await self._on_match_state_req(conn, None) return conn await socket.send(json.dumps({ @@ -154,45 +157,12 @@ class ConnectionHandler(object): "success": False, "id": None, "user": None, + "registered": False, "msg": "session not available" } })) return None - elif msg['type'] == 'temp_session': - name = msg['data']['name'].lower() - if valid_name(name) and len(self.session_manager.get_session_by_temp_user(name)) == 0: - if len(self.user_manager.get_user(name)) == 0: - if len(msg['data']['name']) < 16 and ';' not in name and '\'' not in name and '\"' not in name: - id = self.session_manager.create_session_for_temp_user( - name) - - conn = Connection( - id=id, user_name=name, registered=False, websocket=socket) - - self._add_connection(conn) - - await socket.send(json.dumps({ - "type": "login_response", - "data": { - "success": True, - "id": id, - "msg": "logged in as temporary user " + name - } - })) - - return conn - - await socket.send(json.dumps({ - "type": "login_response", - "data": { - "success": False, - "id": None, - "msg": "user name not available" - } - })) - return None - elif msg['type'] == 'login': response_msg = "" success = False @@ -200,6 +170,7 @@ class ConnectionHandler(object): name = None pw = None conn = None + registered_user = True try: @@ -225,15 +196,32 @@ class ConnectionHandler(object): else: response_msg = "invalid password for user " + name + + elif valid_name(name) and len(name) <= 16 and len(pw) == 0: + # no password -> temporary account + registered_user = False + + # check whether already a temporary session exists for that user: + if (len(self.session_manager.get_session_by_temp_user(name)) > 0) or (len(self.user_manager.get_user(name)) > 0): + response_msg = f"user '{name}' already exists" + success = False + + else: + session_id = self.session_manager.create_session_for_temp_user( + name) + response_msg = f"created a temporary session for user {name}" + success = True + else: response_msg = "invalid username or pw. Only usernames containing alphanumerical symbols (including '_','-') between 3 and 16 characters are allowed!" except Exception as e: + print("error: " + str(e)) response_msg = "invalid username or pw. Only usernames containing alphanumerical symbols (including '_','-') between 3 and 16 characters are allowed!" if success: conn = Connection(id=session_id, user_name=name, - registered=True, websocket=socket) + registered=registered_user, websocket=socket) self._add_connection(conn) await socket.send(json.dumps({ @@ -241,6 +229,7 @@ class ConnectionHandler(object): "data": { "success": success, "id": session_id, + "registered": registered_user, "msg": response_msg } })) @@ -265,6 +254,7 @@ class ConnectionHandler(object): "data": { "id": m.id, "revoke_time": m.get_sql_revoke_time(), + "ranked": m.ranked, "match_state": state } } @@ -278,6 +268,7 @@ class ConnectionHandler(object): "data": { "id": m.id, "revoke_time": m.get_sql_revoke_time(), + "ranked": m.ranked, "match_state": state } } @@ -355,7 +346,7 @@ class ConnectionHandler(object): return try: if valid_name(opponent): - if len(self.user_manager.get_user(opponent)) > 0: + if len(self.user_manager.get_user(opponent)) > 0 or len(self.session_manager.get_session_by_temp_user(opponent)) > 0: if len(self.match_manager.get_matches_for_user(opponent)) >= 5: await conn.websocket.send( @@ -435,6 +426,7 @@ class ConnectionHandler(object): "data": { "id": db_match['id'], "revoke_time": match.get_sql_revoke_time(), + "ranked": match.ranked, "match_state": json.loads(match.to_json_state()) } })) @@ -469,6 +461,7 @@ class ConnectionHandler(object): 'data': { 'id': match.id, 'revoke_time': match.get_sql_revoke_time(), + 'ranked': match.ranked, 'match_state': json.loads(match_state) } })) @@ -487,6 +480,7 @@ class ConnectionHandler(object): 'data': { 'id': match.id, 'revoke_time': match.get_sql_revoke_time(), + 'ranked': match.ranked, 'match_state': json.loads(match_state) } })) @@ -507,34 +501,37 @@ class ConnectionHandler(object): return if not match.game_over: - # check whether both player made a move. If so, the match is ranked as lost for the player who aborted the match - if match.complete_field.__contains__(Match.FIELD_USER_A) and match.complete_field.__contains__(Match.FIELD_USER_B): + if match.ranked: + # check whether both player made a move. If so, the match is ranked as lost for the player who aborted the match + if match.complete_field.__contains__(Match.FIELD_USER_A) and match.complete_field.__contains__(Match.FIELD_USER_B): - # update rankings: - player_lost = conn.user_name - player_won = match.player_a_name if player_lost == match.player_b_name else match.player_b_name + # update rankings: + player_lost = conn.user_name + player_won = match.player_a_name if player_lost == match.player_b_name else match.player_b_name - elo_won = self.user_manager.get_elo(player_won) - elo_lost = self.user_manager.get_elo(player_lost) + elo_won = self.user_manager.get_elo(player_won) + elo_lost = self.user_manager.get_elo(player_lost) - # calculate elo values: - p_won = elo_p_win(elo_won, elo_lost) - p_lost = 1 - p_won + # calculate elo values: + p_won = elo_p_win(elo_won, elo_lost) + p_lost = 1 - p_won - new_elo_won = elo_update(elo_won, 1, p_won) - new_elo_lost = elo_update(elo_lost, 0, p_lost) + new_elo_won = elo_update(elo_won, 1, p_won) + new_elo_lost = elo_update(elo_lost, 0, p_lost) - self.user_manager.update_elo(player_won, new_elo_won) - self.user_manager.update_elo(player_lost, new_elo_lost) + self.user_manager.update_elo( + player_won, new_elo_won) + self.user_manager.update_elo( + player_lost, new_elo_lost) - await self.send_elo(conn) + await self.send_elo(conn) - if player_won in self.open_connections_by_user: - other_conn = self.open_connections_by_user[player_won] - await self.send_elo(other_conn) + if player_won in self.open_connections_by_user: + other_conn = self.open_connections_by_user[player_won] + await self.send_elo(other_conn) - debug( - f"Match {match.id} is aborted by {player_lost} (against {player_won}). Update elo-rankings: {elo_won}->{new_elo_won} and {elo_lost}->{new_elo_lost}") + debug( + f"Match {match.id} is aborted by {player_lost} (against {player_won}). Update elo-rankings: {elo_won}->{new_elo_won} and {elo_lost}->{new_elo_lost}") match.game_over = True @@ -547,6 +544,7 @@ class ConnectionHandler(object): 'data': { 'id': match_id, 'revoke_time': match.get_sql_revoke_time(), + 'ranked': match.ranked, 'match_state': json.loads(match_state) } }) @@ -562,7 +560,7 @@ class ConnectionHandler(object): match_state = None if match is not None: match_state = match.to_json_state() - + if match_state is not None: await conn.send(json.dumps({ @@ -570,6 +568,7 @@ class ConnectionHandler(object): 'data': { 'id': match.id, 'revoke_time': match.get_sql_revoke_time(), + 'ranked': match.ranked, 'match_state': json.loads(match_state) } })) @@ -656,6 +655,18 @@ class ConnectionHandler(object): top_names, top_elos = self.user_manager.get_highscores(100, 0) + if not conn.is_registered_user: + await conn.send(json.dumps({ + 'type': 'elo_update', + 'data': { + 'elo': None, + 'rank': None, + 'top_names': top_names, + 'top_elos': top_elos + } + })) + return + await conn.send(json.dumps({ 'type': 'elo_update', 'data': { @@ -687,7 +698,8 @@ class ConnectionHandler(object): t = msg['type'] - self.user_manager.touch_user(conn.user_name) + if conn.is_registered_user: + self.user_manager.touch_user(conn.user_name) self.session_manager.touch_session(conn.id) if t == "match_request": await self._on_match_req(conn, msg['data']) diff --git a/create_database.py b/create_database.py index fa9d7d0..9d0b590 100755 --- a/create_database.py +++ b/create_database.py @@ -17,10 +17,10 @@ def create_tables(): queries = [ "DROP TABLE IF EXISTS matches", "DROP TABLE IF EXISTS sessions", - "DROP TABLE IF EXISTS users", "DROP TABLE IF EXISTS friends", + "DROP TABLE IF EXISTS users", "CREATE TABLE users (name varchar(16) NOT NULL, pw_hash varchar(128) NOT NULL, pw_salt varchar(32) NOT NULL, last_seen datetime NOT NULL, elo int NOT NULL, PRIMARY KEY (name)) CHARACTER SET " + settings.db_charset, - "CREATE TABLE matches (id varchar(32) NOT NULL, user_a varchar(16) NOT NULL, user_b varchar(16) NOT NULL, match_state varchar(4096) NOT NULL, active_user varchar(16), last_active datetime NOT NULL, FOREIGN KEY (user_a) REFERENCES users(name), FOREIGN KEY (user_b) REFERENCES users(name), FOREIGN KEY (active_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset, + "CREATE TABLE matches (id varchar(32) NOT NULL, user_a varchar(16) NOT NULL, user_b varchar(16) NOT NULL, match_state varchar(4096) NOT NULL, active_user varchar(16), last_active datetime NOT NULL, ranked BOOLEAN NOT NULL) CHARACTER SET " + settings.db_charset, "CREATE TABLE sessions (id varchar(32) NOT NULL, registered_user varchar(16), temp_user varchar(16), last_seen datetime NOT NULL, PRIMARY KEY (id), FOREIGN KEY(registered_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset, "CREATE TABLE friends (user varchar(16) NOT NULL, friend varchar(16) NOT NULL, FOREIGN KEY(user) REFERENCES users(name), FOREIGN KEY(friend) REFERENCES users(name)) CHARACTER SET " + settings.db_charset ] diff --git a/main.py b/main.py index d424429..e0a800e 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,7 @@ import datetime from tools import debug um = UserManager(account_revoke_time) -sm = SessionManager(session_revove_time) +sm = SessionManager(session_revove_time, temporary_session_revoke_time) mm = MatchManager(um, match_revoke_time) ch = ConnectionHandler(sm, um, mm, revoke_check_interval) diff --git a/match.py b/match.py index cd55e59..af14ff6 100644 --- a/match.py +++ b/match.py @@ -38,7 +38,7 @@ class Match(object): FIELD_USER_B = 2 FIELD_DRAW = 3 - def __init__(self, n, match_id, revoke_time, player_a_name, player_b_name, json_state=None): + def __init__(self, n, match_id, revoke_time, player_a_name, player_b_name, json_state=None, ranked=False): self.n = n self.id = match_id self.complete_field = np.zeros(shape=(n*n, n*n), dtype=int) @@ -51,6 +51,7 @@ class Match(object): self.player_a_name = player_a_name self.player_b_name = player_b_name self.revoke_time = revoke_time + self.ranked = ranked if json_state is not None: self.from_json_state(json_state) diff --git a/match_manager.py b/match_manager.py index 7f0f9d0..e87188a 100644 --- a/match_manager.py +++ b/match_manager.py @@ -22,7 +22,7 @@ class MatchManager(object): revoke_time = result[0]['last_active'] + self.match_lifespan_timedelta match = Match(n=settings.n, match_id=id, revoke_time=revoke_time, player_a_name=result[0]['user_a'], - player_b_name=result[0]['user_b'], json_state=result[0]['match_state']) + player_b_name=result[0]['user_b'], json_state=result[0]['match_state'], ranked=result[0]['ranked']) return match def get_matches_for_user(self, user_name: str) -> list: @@ -35,13 +35,16 @@ class MatchManager(object): if len(DatabaseConnection.global_single_query("SELECT id FROM matches WHERE id=%s", (match_id))) > 0: return self.create_new_match(user_a, user_b) + ranked = len(self.user_manager.get_user(user_a)) > 0 and len( + self.user_manager.get_user(user_b)) > 0 + now = datetime.datetime.now() match = Match(n=settings.n, match_id=match_id, revoke_time=now + self.match_lifespan_timedelta, - player_a_name=user_a, player_b_name=user_b) - - query = "INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active) VALUES (%s, %s, %s, %s, %s,%s)" + player_a_name=user_a, player_b_name=user_b, ranked=ranked) + + query = "INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active, ranked) VALUES (%s, %s, %s, %s, %s,%s, %s)" DatabaseConnection.global_single_execution( - query, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now))) + query, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now), ranked)) return match def update_match(self, match_id: str, match: Match, update_in_db=True) -> None: @@ -53,7 +56,7 @@ class MatchManager(object): query, (match.to_json_state(), match.get_current_player(), now, match_id)) # check whether we have to update the elo values (game over triggered by the last move) - if match.game_over: + if match.game_over and match.ranked: if match.player_won is not None: player_won = match.player_won diff --git a/session_manager.py b/session_manager.py index 50a578f..3db2eea 100644 --- a/session_manager.py +++ b/session_manager.py @@ -7,8 +7,9 @@ from tools import debug class SessionManager(object): - def __init__(self, session_lifespan_timedelta): + def __init__(self, session_lifespan_timedelta, temp_session_lifespan_timedelta): self.session_lifespan_timedelta = session_lifespan_timedelta + self.temp_session_lifespan_timedelta = temp_session_lifespan_timedelta def get_session_by_id(self, session_id): query = "SELECT * FROM sessions WHERE id=%s" @@ -16,7 +17,8 @@ class SessionManager(object): def touch_session(self, session_id): query = "UPDATE sessions SET last_seen=%s WHERE id=%s" - DatabaseConnection.global_single_execution(query, (datetime.datetime.now(), session_id)) + DatabaseConnection.global_single_execution( + query, (datetime.datetime.now(), session_id)) def get_session_by_registered_user(self, user_name): query = "SELECT * FROM sessions WHERE registered_user=%s" @@ -38,7 +40,8 @@ class SessionManager(object): DatabaseConnection.global_single_execution(query, (user_name)) query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, %s, NULL, %s)" - DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now())) + DatabaseConnection.global_single_execution( + query, (new_id, user_name, datetime.datetime.now())) return new_id @@ -54,19 +57,38 @@ class SessionManager(object): DatabaseConnection.global_single_execution(query, (user_name)) query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, NULL, %s, %s)" - DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now())) + DatabaseConnection.global_single_execution( + query, (new_id, user_name, datetime.datetime.now())) return new_id def delete_session(self, session_id): query = "DELETE FROM sessions WHERE id=%s" DatabaseConnection.global_single_execution(query, (session_id)) + def revoke_inactive_temporary_sessions(self): + revoke_time = datetime.datetime.now() - self.temp_session_lifespan_timedelta + query = "SELECT * from sessions WHERE last_seen < %s AND temp_user IS NOT NULL" + revoked_temp_sessions = DatabaseConnection.global_single_query( + query, (get_sql_time(revoke_time))) + + query = "DELETE FROM sessions WHERE last_seen < %s AND temp_user IS NOT NULL" + DatabaseConnection.global_single_execution( + query, (get_sql_time(revoke_time))) + + for session in revoked_temp_sessions: + # remove from matches: + query = "DELETE FROM matches WHERE user_a=%s OR user_b=%s" + DatabaseConnection.global_single_execution( + query, (session['temp_user'], session['temp_user'])) + + debug("delete revoked sessions: " + str(revoked_temp_sessions)) + def revoke_inactive_sessions(self): revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta query = "SELECT * from sessions WHERE last_seen < %s" - revoked_sessions = DatabaseConnection.global_single_query(query, (get_sql_time(revoke_time))) + revoked_sessions = DatabaseConnection.global_single_query( + query, (get_sql_time(revoke_time))) query = "DELETE FROM sessions WHERE last_seen < %s" - DatabaseConnection.global_single_execution(query, (get_sql_time(revoke_time))) + DatabaseConnection.global_single_execution( + query, (get_sql_time(revoke_time))) debug("delete revoked sessions: " + str(revoked_sessions)) - - diff --git a/user_manager.py b/user_manager.py index 89ea5e8..edd271a 100644 --- a/user_manager.py +++ b/user_manager.py @@ -61,8 +61,6 @@ class UserManager(object): query, (user_name, pw_hash, pw_salt, datetime.datetime.now(), elo_start_value)) def touch_user(self, user_name): - matches = self.get_user(user_name) - assert len(matches) == 1 query = "UPDATE users SET last_seen=%s WHERE name=%s" DatabaseConnection.global_single_execution( query, (datetime.datetime.now(), user_name))