From 3c4d5115f24b340928ec58e0d8fdeb0ee7a4bfc9 Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 21 Jan 2021 15:54:27 -0600 Subject: [PATCH] message history exchange implementation, include database message IDs with all messages sent --- constants.py | 6 +++--- helpers.py | 24 ++++++++++++++++++++++-- server/handler.py | 42 +++++++++++++++++++++++++++++++++++------- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/constants.py b/constants.py index 34d8292..fd2ab3b 100644 --- a/constants.py +++ b/constants.py @@ -16,11 +16,11 @@ class Types: A message could be a request, a posting of information, a message etc. The Types class provides a universal naming for the types of messages that will be exchanged. """ - SERVER_MESSAGE = 'SERVER_MESSAGE' REQUEST = 'REQUEST' + MESSAGE = 'MESSAGE' NICKNAME = 'NICKNAME' USER_LIST = 'USER_LIST' - MESSAGE = 'MESSAGE' + MESSAGE_HISTORY = 'MESSAGE_HISTORY' class Requests: @@ -30,7 +30,7 @@ class Requests: """ REQUEST_NICK = 'REQUEST_NICK' # Send the server the client's nickname REFRESH_CONNECTIONS_LIST = 'REFRESH_CLIENT_LIST' # Send the client all connections to the server and their information - GET_HISTORY = 'GET_HISTORY' # Send a short history of the chat to the client + GET_MESSAGE_HISTORY = 'GET_MESSAGE_HISTORY' # Send the client a detailed list of all messages sent up to a certain point. class Color(): diff --git a/helpers.py b/helpers.py index 7600937..415ae48 100644 --- a/helpers.py +++ b/helpers.py @@ -1,5 +1,6 @@ import json import time +from typing import List, Tuple import constants @@ -22,14 +23,33 @@ def prepare_json(object) -> bytes: return prepare(json.dumps(object)) -def prepare_message(nickname: str, message: str, color: str, msgtime: int = None): +def prepare_message(nickname: str, message: str, color: str, message_id: int, timestamp: int = None) -> bytes: return prepare_json( { 'type': constants.Types.MESSAGE, 'nickname': nickname, 'content': message, 'color': color, - 'time': msgtime or int(time.time()) + 'time': timestamp or int(time.time()), + 'id': message_id, + } + ) + + +def prepare_message_history(messages: List[Tuple[int, str, str, str, int]]) -> bytes: + return prepare_json( + { + 'type': constants.Types.MESSAGE_HISTORY, + 'messages': [ + { + 'type': constants.Types.MESSAGE, + 'nickname': nickname, + 'content': message, + 'color': color, + 'time': timestamp, + 'id': message_id + } for message_id, nickname, color, message, timestamp in messages + ] } ) diff --git a/server/handler.py b/server/handler.py index 397b3c1..509f668 100644 --- a/server/handler.py +++ b/server/handler.py @@ -29,14 +29,16 @@ class BaseClient(object): """Sends a string message as the server to this client.""" # db.add_message('Server', 'server', constants.Colors.BLACK.hex, message, int(time.time())) self.conn.send(helpers.prepare_message( - nickname='Server', message=message, color=constants.Colors.BLACK.hex + nickname='Server', message=message, color=constants.Colors.BLACK.hex, message_id=-1 )) def broadcast_message(self, message: str) -> None: """Sends a string message to all connected clients as the Server.""" - db.add_message('Server', 'server', constants.Colors.BLACK.hex, message, int(time.time())) + timestamp = int(time.time()) + message_id = db.add_message('Server', 'server', constants.Colors.BLACK.hex, message, timestamp) prepared = helpers.prepare_message( - nickname='Server', message=message, color=constants.Colors.BLACK.hex + nickname='Server', message=message, color=constants.Colors.BLACK.hex, message_id=message_id, + timestamp=timestamp ) for client in self.all_clients: client.send(prepared) @@ -82,6 +84,25 @@ class Client(BaseClient): } )) + def send_message_history(self, limit: int, time_limit: int) -> None: + limit = min(100, max(0, limit)) + time_limit = min(60 * 30, max(0, time_limit)) + min_time = int(time.time()) - time_limit + + cur = db.conn.cursor() + try: + cur.execute('''SELECT id, nickname, color, message, timestamp + FROM message + WHERE timestamp >= ? + ORDER BY timestamp + LIMIT ?''', + [min_time, limit]) + + messages = cur.fetchall() + self.send(helpers.prepare_message_history(messages)) + finally: + cur.close() + def receive(self) -> Any: length = int(self.conn.recv(constants.HEADER_LENGTH).decode('utf-8')) logger.debug(f'Header received - Length {length}') @@ -106,18 +127,25 @@ class Client(BaseClient): if data['type'] == constants.Types.REQUEST: if data['request'] == constants.Requests.REFRESH_CONNECTIONS_LIST: self.send_connections_list() + if data['request'] == constants.Requests.GET_MESSAGE_HISTORY: + self.send_message_history( + limit=data.get('limit', 50), time_limit=data.get('time_limit', 60 * 30) + ) + elif data['type'] == constants.Types.NICKNAME: self.handle_nickname(data['nickname']) elif data['type'] == constants.Types.MESSAGE: + # Record the message in the DB. + message_id = db.add_message(self.nickname, self.id, self.color.hex, data['content'], + int(time.time())) + self.broadcast(helpers.prepare_message( nickname=self.nickname, message=data['content'], - color=self.color.hex + color=self.color.hex, + message_id=message_id )) - # Record the message in the DB. - db.add_message(self.nickname, self.id, self.color.hex, data['content'], int(time.time())) - # Process commands command = data['content'].strip() if command.startswith('/'):