mirror of
https://github.com/Xevion/tcp-chat.git
synced 2025-12-06 01:16:32 -06:00
greatly improve client logging with dict/signal based inter-thread communication, better exception handling via try/except, attempt to improve window closing
This commit is contained in:
112
client/gui.py
112
client/gui.py
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import logging
|
||||
import socket
|
||||
import traceback
|
||||
|
||||
@@ -13,6 +14,10 @@ from client.dialog import NicknameDialog
|
||||
IP = '127.0.0.1'
|
||||
PORT = 55555
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='[%(asctime)s] [%(levelname)s] [%(threadName)s] %(message)s')
|
||||
logger = logging.getLogger('gui')
|
||||
|
||||
HEADER_LENGTH = 10
|
||||
|
||||
|
||||
@@ -21,11 +26,16 @@ class ReceiveWorker(QThread):
|
||||
client_list = pyqtSignal(list)
|
||||
error = pyqtSignal()
|
||||
sent_nick = pyqtSignal()
|
||||
logs = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, client: socket.socket, nickname: str, parent=None):
|
||||
QThread.__init__(self, parent)
|
||||
self.client = client
|
||||
self.nickname = nickname
|
||||
self.__isRunning = True
|
||||
|
||||
def stop(self) -> None:
|
||||
self.__isRunning = False
|
||||
|
||||
def __extract_message(self, data) -> dict:
|
||||
return {
|
||||
@@ -36,39 +46,58 @@ class ReceiveWorker(QThread):
|
||||
'id': data['id']
|
||||
}
|
||||
|
||||
def log(self, message: str, level: int = logging.INFO, error: Exception = None):
|
||||
"""Send a log message out from this QThread to the MainThread"""
|
||||
self.logs.emit(
|
||||
{
|
||||
'message': message,
|
||||
'level': level,
|
||||
'error': error
|
||||
}
|
||||
)
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
try:
|
||||
raw_length = self.client.recv(HEADER_LENGTH).decode('utf-8')
|
||||
if not raw_length:
|
||||
continue
|
||||
raw = self.client.recv(int(raw_length)).decode('utf-8')
|
||||
if not raw:
|
||||
continue
|
||||
message = json.loads(raw)
|
||||
try:
|
||||
while self.__isRunning:
|
||||
try:
|
||||
raw_length = self.client.recv(HEADER_LENGTH).decode('utf-8')
|
||||
if not raw_length:
|
||||
continue
|
||||
raw = self.client.recv(int(raw_length)).decode('utf-8')
|
||||
if not raw:
|
||||
continue
|
||||
message = json.loads(raw)
|
||||
|
||||
if message['type'] == constants.Types.REQUEST:
|
||||
if message['request'] == constants.Requests.REQUEST_NICK:
|
||||
self.client.send(helpers.prepare_json(
|
||||
{
|
||||
'type': constants.Types.NICKNAME,
|
||||
'nickname': self.nickname
|
||||
}
|
||||
))
|
||||
self.sent_nick.emit()
|
||||
elif message['type'] == constants.Types.MESSAGE:
|
||||
self.messages.emit(self.__extract_message(message))
|
||||
elif message['type'] == constants.Types.USER_LIST:
|
||||
self.client_list.emit(message['users'])
|
||||
elif message['type'] == constants.Types.MESSAGE_HISTORY:
|
||||
for submessage in message['messages']:
|
||||
self.messages.emit(self.__extract_message(submessage))
|
||||
if message['type'] == constants.Types.REQUEST:
|
||||
self.log(f'Data[{int(raw_length)}] received, {message["type"]}/{message["request"]}.', level=logging.DEBUG)
|
||||
else:
|
||||
self.log(f'Data[{int(raw_length)}] received, {message["type"]}.', level=logging.DEBUG)
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
self.error.emit()
|
||||
self.client.close()
|
||||
break
|
||||
|
||||
if message['type'] == constants.Types.REQUEST:
|
||||
if message['request'] == constants.Requests.REQUEST_NICK:
|
||||
self.client.send(helpers.prepare_json(
|
||||
{
|
||||
'type': constants.Types.NICKNAME,
|
||||
'nickname': self.nickname
|
||||
}
|
||||
))
|
||||
self.sent_nick.emit()
|
||||
elif message['type'] == constants.Types.MESSAGE:
|
||||
self.messages.emit(self.__extract_message(message))
|
||||
elif message['type'] == constants.Types.USER_LIST:
|
||||
self.client_list.emit(message['users'])
|
||||
elif message['type'] == constants.Types.MESSAGE_HISTORY:
|
||||
for submessage in message['messages']:
|
||||
self.messages.emit(self.__extract_message(submessage))
|
||||
|
||||
except Exception as e:
|
||||
self.log(str(e), level=logging.CRITICAL, error=e)
|
||||
self.error.emit()
|
||||
break
|
||||
finally:
|
||||
self.log('Closing socket.', level=logging.INFO)
|
||||
self.client.close()
|
||||
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
@@ -83,7 +112,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
self.nicknameDialog = NicknameDialog(self)
|
||||
self.nicknameDialog.exec_()
|
||||
self.nickname = self.nicknameDialog.lineEdit.text().strip()
|
||||
if len(self.nickname) >= 3 or self.closed:
|
||||
if len(self.nickname) >= 3:
|
||||
self.closed = True
|
||||
break
|
||||
elif self.closed:
|
||||
break
|
||||
|
||||
# Connect to server
|
||||
@@ -94,6 +126,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
self.receiveThread = ReceiveWorker(self.client, self.nickname)
|
||||
self.receiveThread.messages.connect(self.addMessage)
|
||||
self.receiveThread.client_list.connect(self.update_connections)
|
||||
self.receiveThread.logs.connect(self.log)
|
||||
self.receiveThread.start()
|
||||
|
||||
self.connectionsListTimer = QTimer()
|
||||
@@ -106,14 +139,23 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
|
||||
self.messages = []
|
||||
|
||||
def log(self, log_data: dict) -> None:
|
||||
logger.log(level=log_data['level'], msg=log_data['message'], exc_info=log_data['error'])
|
||||
|
||||
def _ready(self):
|
||||
logger.debug('Nickname sent. Ready to communicate with server further...')
|
||||
self.refresh_connections()
|
||||
self.get_message_history()
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.nicknameDialog:
|
||||
if self.nicknameDialog and not self.closed:
|
||||
logger.debug('Closing nickname dialog before main window')
|
||||
self.closed = True
|
||||
self.nicknameDialog.close()
|
||||
else:
|
||||
self.receiveThread.stop()
|
||||
self.connectionsListTimer.stop()
|
||||
|
||||
event.accept() # let the window close
|
||||
|
||||
def eventFilter(self, obj, event) -> bool:
|
||||
@@ -130,11 +172,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
|
||||
def addMessage(self, message: dict) -> None:
|
||||
self.messages.append(message)
|
||||
self.messageHistory.append(f'<<span style="color: {message["color"]}">{message["nickname"]}</span>> {message["message"]}')
|
||||
self.messageHistory.append(
|
||||
f'<<span style="color: {message["color"]}">{message["nickname"]}</span>> {message["message"]}')
|
||||
|
||||
def sendMessage(self, message: str) -> None:
|
||||
message = message.strip()
|
||||
if len(message) > 0:
|
||||
logger.info(f'Sending message of length {len(message)}')
|
||||
self.client.send(helpers.prepare_json(
|
||||
{
|
||||
'type': constants.Types.MESSAGE,
|
||||
@@ -143,6 +187,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
))
|
||||
|
||||
def refresh_connections(self) -> None:
|
||||
logger.info('Requesting connections list')
|
||||
self.client.send(helpers.prepare_json(
|
||||
{
|
||||
'type': constants.Types.REQUEST,
|
||||
@@ -151,6 +196,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
))
|
||||
|
||||
def get_message_history(self) -> None:
|
||||
logger.info('Requesting message history')
|
||||
self.client.send(helpers.prepare_json(
|
||||
{
|
||||
'type': constants.Types.REQUEST,
|
||||
|
||||
Reference in New Issue
Block a user