diff --git a/TODO.md b/TODO.md index 357cdfde..3983537d 100644 --- a/TODO.md +++ b/TODO.md @@ -7,8 +7,6 @@ it should be the peer's display name instead. - Support "Empty room (was ...)" after peer left -- Catch network errors in socket operations - - Proper logoff when closing client - Handle cases where an avatar char is # or @ (#alias room, @user\_id) diff --git a/harmonyqml/backend/client.py b/harmonyqml/backend/client.py index 478a7477..c2b79126 100644 --- a/harmonyqml/backend/client.py +++ b/harmonyqml/backend/client.py @@ -79,10 +79,7 @@ class Client(QObject): @pyqtSlot(str, str) @futurize def login(self, password: str, device_name: str = "") -> None: - self.net.write(self.nio.connect()) response = self.net.talk(self.nio.login, password, device_name) - - self.net_sync.write(self.nio_sync.connect()) self.nio_sync.receive_response(response) @@ -90,11 +87,8 @@ class Client(QObject): @futurize def resumeSession(self, user_id: str, token: str, device_id: str ) -> None: - self.net.write(self.nio.connect()) response = nr.LoginResponse(user_id, device_id, token) self.nio.receive_response(response) - - self.net_sync.write(self.nio_sync.connect()) self.nio_sync.receive_response(response) @@ -102,8 +96,8 @@ class Client(QObject): @futurize def logout(self) -> None: self._stop_sync.set() - self.net.write(self.nio.disconnect()) - self.net_sync.write(self.nio_sync.disconnect()) + self.net.http_disconnect() + self.net_sync.http_disconnect() @pyqtSlot() @@ -181,24 +175,19 @@ class Client(QObject): set_for_secs = 5 last_set, last_time = self._last_typing_set[room_id] - print(last_set, last_time) - if not typing and last_set is False: - print("ignore 1") return if typing and time.time() - last_time < set_for_secs - 1: - print("ignore 2") return - print("SET", typing) self._last_typing_set[room_id] = (typing, time.time()) self.net.talk( self.nio.room_typing, - room_id = room_id, - typing_state = typing, - timeout = set_for_secs * 1000, + room_id = room_id, + typing_state = typing, + timeout = set_for_secs * 1000, ) diff --git a/harmonyqml/backend/html_filter.py b/harmonyqml/backend/html_filter.py index 9fa019f5..e7782c29 100644 --- a/harmonyqml/backend/html_filter.py +++ b/harmonyqml/backend/html_filter.py @@ -3,11 +3,12 @@ import re -import html_sanitizer.sanitizer as sanitizer import mistune from lxml.html import HtmlElement, etree from PyQt5.QtCore import QObject, pyqtProperty, pyqtSlot +import html_sanitizer.sanitizer as sanitizer + class HtmlFilter(QObject): link_regexes = [re.compile(r, re.IGNORECASE) for r in [ diff --git a/harmonyqml/backend/network_manager.py b/harmonyqml/backend/network_manager.py index ecc7ffee..3224cc2b 100644 --- a/harmonyqml/backend/network_manager.py +++ b/harmonyqml/backend/network_manager.py @@ -11,6 +11,7 @@ from uuid import UUID import nio import nio.responses as nr +from nio.exceptions import RemoteTransportError OptSock = Optional[ssl.SSLSocket] NioRequestFunc = Callable[..., Tuple[UUID, bytes]] @@ -48,7 +49,10 @@ class NetworkManager: @staticmethod - def _close_socket(sock: socket.socket) -> None: + def _close_socket(sock: Optional[socket.socket]) -> None: + if not sock: + return + try: sock.shutdown(how=socket.SHUT_RDWR) except OSError: # Already closer by server @@ -56,6 +60,14 @@ class NetworkManager: sock.close() + def http_disconnect(self) -> None: + data = self.nio.disconnect() + try: + self.write(data) + except (OSError, RemoteTransportError): + pass + + def read(self, with_sock: OptSock = None) -> nr.Response: sock = with_sock or self._get_socket() @@ -78,9 +90,6 @@ class NetworkManager: def write(self, data: bytes, with_sock: OptSock = None) -> None: - if not data: - return - sock = with_sock or self._get_socket() sock.sendall(data) @@ -88,34 +97,47 @@ class NetworkManager: self._close_socket(sock) - def talk(self, nio_func: NioRequestFunc, *args, **kwargs) -> nr.Response: + def talk(self, + nio_func: NioRequestFunc, + *args, + **kwargs) -> nr.Response: with self._lock: while True: - to_send = nio_func(*args, **kwargs)[1] - sock = self._get_socket() + sock = None try: + sock = self._get_socket() + + if not self.nio.connection: + # Establish HTTP protocol connection: + self.write(self.nio.connect(), sock) + + to_send = nio_func(*args, **kwargs)[1] self.write(to_send, sock) response = self.read(sock) + except OSError as err: + logging.error("Socket error for %s: %s", + nio_func.__name__, err.strerror) + self._close_socket(sock) + time.sleep(2) + + except RemoteTransportError as err: + logging.error("HTTP transport error for %s: %s", + nio_func.__name__, err) + self._close_socket(sock) + self.http_disconnect() + time.sleep(2) + except NioErrorResponse as err: - logging.error("bad read for %s: %s", nio_func, err) + logging.error("Nio response error for %s: %s", + nio_func.__name__, err) self._close_socket(sock) - if self._should_abort_talk(err): - logging.error("aborting talk") - break + if err.response.status_code in self.http_retry_codes: + return response - time.sleep(10) + time.sleep(2) else: - break - - self._close_socket(sock) - return response - - - def _should_abort_talk(self, err: NioErrorResponse) -> bool: - if err.response.status_code in self.http_retry_codes: - return False - return True + return response diff --git a/harmonyqml/components/chat/SendBox.qml b/harmonyqml/components/chat/SendBox.qml index e6d91e41..26e7ea9f 100644 --- a/harmonyqml/components/chat/SendBox.qml +++ b/harmonyqml/components/chat/SendBox.qml @@ -62,7 +62,6 @@ Rectangle { } if (textArea.text === "") { return } - Backend.clientManager.clients[chatPage.user_id] .sendMarkdown(chatPage.room.room_id, textArea.text) textArea.clear()