diff --git a/game.py b/game.py index 4ba0739..0f13702 100644 --- a/game.py +++ b/game.py @@ -222,3 +222,6 @@ class Room: def remove_user(self, user: User): if user in self.users: self.users.remove(user) + + def is_empty(self): + return not len(self.users) > 0 diff --git a/main.py b/main.py index c346b1a..38f50bc 100644 --- a/main.py +++ b/main.py @@ -17,8 +17,12 @@ import logging from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi_socketio import SocketManager +from cachetools import TTLCache from game import Room, User, load_streets from objects import ErrCode, UserCheck +from threading import Timer + +ROOM_DISCONNECT_TIMEOUT = 60*5 # TODO: Сохранить остаток таймера для каждого пользователя logging.basicConfig(level=logging.DEBUG, format="%(levelname)s:\t%(asctime)s\t%(message)s") load_streets() @@ -33,9 +37,10 @@ app.add_middleware( allow_headers=["*"], ) sio = SocketManager(app=app, mount_location="/ws", socketio_path="game", cors_allowed_origins=[]) -# sio_sessions = TTLCache(maxsize=10000, ttl=24 * 60 * 60) -rooms = [] -users = [] # TODO: implement as dict +sio_sessions = TTLCache(maxsize=10000, ttl=24 * 60 * 60) +rooms = dict() +users = dict() +users_to_disconnect = dict() # TODO: Time-based tokens @@ -44,15 +49,14 @@ def check_user(username, password=None, token=None): return False else: global users - selected_users = tuple(filter(lambda usr: usr.username == username, users)) - if len(selected_users) == 0: + if username not in users: if token: logging.debug(f"Invalid token for user {username}") return UserCheck.INVALID_CREDENTIALS logging.debug(f"Invalid user {username}") return UserCheck.USER_DOESNT_EXISTS else: - user = selected_users[0] + user = users[username] if user.token == token or user.password == password: logging.debug(f"Valid credentials for user {username}") return user @@ -68,38 +72,47 @@ async def sio_create_room(sid, username, token, password): new_room = Room(password) logging.info(f"User {username} created room {new_room.room_id}") new_room.add_user(user) - rooms.append(new_room) - user.in_room = new_room.room_id # TODO: not add user on creation - user.room_password = new_room.password - await sio_send_user_info(sid, user) + rooms[new_room.room_id] = new_room + await sio.emit("roomCreated", {"room_id": new_room.room_id, "room_password": new_room.password}, room=sid) case _: return False # TODO: some errors + async def sio_send_user_info(sid, user): userdata = user.get_dict() userdata["token"] = user.token if user.in_room: userdata["in_room"] = user.in_room - userdata["password"] = user.password + userdata["room_password"] = user.room_password await sio.emit("userInfo", userdata, room=sid) @sio.on("joinRoom") -async def sio_join_room(sid, room_id, room_password): # TODO: Check if user already in room - global rooms - selected_room = tuple(filter(lambda room: room.room_id == room_id, rooms)) - if len(selected_room) == 0: - await sio_throw_error(sid, ErrCode.ROOM_DOESNT_EXIST) - else: - room = selected_room[0] - room_data = room.get_dict() - sio.enter_room(sid, room.room_id) - if room.password == room_password: - await sio.emit("roomInfo", room_data, room=sid) +async def sio_join_room(sid, username, token, room_id, room_password): # TODO: Check if user already in room + match check_user(username, token=token): + case User(username=username) as user: + global rooms + if room_id not in rooms: + logging.debug(f"Room {room_id} doesn't found") + await sio_throw_error(sid, ErrCode.ROOM_DOESNT_EXIST) + else: + room = rooms[room_id] + if room.password == room_password: + user.in_room = room.room_id + user.room_password = room.password + room_data = room.get_dict() + sio.enter_room(sid, room.room_id) + await sio_send_user_info(sid, user) + await sio.emit("roomInfo", room_data, room=sid) + case _: + await sio_throw_error(sid, ErrCode.INVALID_CREDENTIALS) + return False @sio.on("connect") async def sio_connect(sid, _, auth): + if sid in sio_sessions: + return False if not ("username" in auth and ("password" in auth or "token" in auth))\ or not auth["username"].isalnum()\ or len(auth["username"]) > 16: @@ -110,12 +123,20 @@ async def sio_connect(sid, _, auth): password = auth.get("password", None) match check_user(username, password, token): case User(username=username) as user: + user.sid = sid + sio_sessions[sid] = user + if username in users_to_disconnect: + timer = users_to_disconnect[username] + timer.cancel() + del users_to_disconnect[username] await sio_send_user_info(sid, user) case UserCheck.USER_DOESNT_EXISTS: if not password: return False user = User(username, password) - users.append(user) + users[username] = user + user.sid = sid + sio_sessions[sid] = user await sio_send_user_info(sid, user) case UserCheck.INVALID_CREDENTIALS: await sio_throw_error(sid, ErrCode.INVALID_CREDENTIALS) @@ -132,7 +153,30 @@ async def sio_throw_error(sid, error): @sio.on("disconnect") async def sio_disconnect(sid): + user = sio_sessions[sid] logging.debug(f"{sid} disconnected") + if user.in_room: + logging.info(f"User {user.username} disconnected while in the room {user.in_room}") + global users_to_disconnect + t = Timer(ROOM_DISCONNECT_TIMEOUT, disconnect_user, args=[user]) + users_to_disconnect[user.username] = t + t.start() + +def disconnect_user(user): + global rooms + room = rooms[user.in_room] + room.remove_user(user) + logging.info(f"User {user.username} disconnected from room {room.room_id}") + if room.is_empty(): + logging.info(f"Room {room.room_id} is closed") + del rooms[room.room_id] + del room + t = users_to_disconnect[user.username] + t.cancel() + del t + del users_to_disconnect[user.username] + + @sio.on("rollDices")