remove websocket-client dependency in favor of QWebSocket

This commit is contained in:
dries.k
2023-08-05 16:36:12 +02:00
parent 6b5434978b
commit db8bfc0f44
5 changed files with 94 additions and 126 deletions

View File

@@ -1,8 +1,9 @@
import logging import logging
from typing import Callable
import requests import requests
from PyQt6 import QtCore
from .listener import Listener from .listener import Listener
from .models import ( from .models import (
GotifyApplicationModel, GotifyApplicationModel,
@@ -17,8 +18,9 @@ from .models import (
logger = logging.getLogger("gotify-tray") logger = logging.getLogger("gotify-tray")
class GotifySession(object): class GotifySession(QtCore.QObject):
def __init__(self, url: str, token: str): def __init__(self, url: str, token: str):
super(GotifySession, self).__init__()
self.session = requests.Session() self.session = requests.Session()
self.update_auth(url.rstrip("/"), token) self.update_auth(url.rstrip("/"), token)
@@ -46,9 +48,6 @@ class GotifySession(object):
class GotifyApplication(GotifySession): class GotifyApplication(GotifySession):
def __init__(self, url: str, application_token: str):
super(GotifyApplication, self).__init__(url, application_token)
def push( def push(
self, title: str = "", message: str = "", priority: int = 0, extras: dict | None = None self, title: str = "", message: str = "", priority: int = 0, extras: dict | None = None
) -> GotifyMessageModel | GotifyErrorModel: ) -> GotifyMessageModel | GotifyErrorModel:
@@ -72,6 +71,23 @@ class GotifyApplication(GotifySession):
class GotifyClient(GotifySession): class GotifyClient(GotifySession):
new_message = QtCore.pyqtSignal(GotifyMessageModel)
opened = QtCore.pyqtSignal()
closed = QtCore.pyqtSignal()
def __init__(self, url: str, client_token: str):
self.listener = Listener(url, client_token)
super(GotifyClient, self).__init__(url, client_token)
self.listener.opened.connect(self.opened.emit)
self.listener.closed.connect(self.closed.emit)
self.listener.new_message.connect(self.new_message.emit)
def update_auth(self, url: str | None = None, token: str | None = None):
super().update_auth(url, token)
self.listener.update_auth(url, token)
""" """
Application Application
@@ -180,48 +196,26 @@ class GotifyClient(GotifySession):
response = self._delete(f"/message/{message_id}") response = self._delete(f"/message/{message_id}")
return None if response.ok else GotifyErrorModel(response) return None if response.ok else GotifyErrorModel(response)
def listen( def listen(self):
self,
opened_callback: (Callable[[], None]) | None = None,
closed_callback: Callable[[int, str], None] | None = None,
new_message_callback: Callable[[GotifyMessageModel], None] | None = None,
error_callback: Callable[[Exception], None] | None = None,
):
def dummy(*args):
...
self.listener = Listener(self.url, self.token)
self.listener.opened.connect(lambda: self.opened_callback(opened_callback))
self.listener.closed.connect(closed_callback or dummy)
self.listener.new_message.connect(new_message_callback or dummy)
self.listener.error.connect(error_callback or dummy)
self.listener.start() self.listener.start()
def opened_callback(self, user_callback: Callable[[], None] | None = None):
self.reset_wait_time()
if user_callback:
user_callback()
def reconnect(self): def reconnect(self):
if not self.is_listening(): self.listener.reconnect()
self.listener.start()
def stop_final(self): def quit(self):
self.listener.stop_final() """Close the listener and disconnect from the closed signal so it doesn't get reopened
"""
try:
self.listener.closed.disconnect()
except TypeError:
logger.error(f"listener.closed was already disconnected.")
self.listener.close()
def stop(self, reset_wait: bool = False): def stop(self):
if reset_wait: self.listener.close()
self.reset_wait_time()
self.listener.stop()
def is_listening(self) -> bool: def is_listening(self) -> bool:
return self.listener.running return self.listener.is_connected()
def increase_wait_time(self):
self.listener.increase_wait_time()
def get_wait_time(self) -> int:
return self.listener.wait_time
def reset_wait_time(self): def reset_wait_time(self):
self.listener.reset_wait_time() self.listener.reset_wait_time()

View File

@@ -1,42 +1,52 @@
import json import json
import logging import logging
import platform
import ssl
import websocket
from PyQt6 import QtCore from PyQt6 import QtCore
from PyQt6 import QtNetwork, QtWebSockets
from .models import GotifyMessageModel, GotifyErrorModel from .models import GotifyMessageModel
logger = logging.getLogger("gotify-tray") logger = logging.getLogger("gotify-tray")
class Listener(QtCore.QThread): class Listener(QtWebSockets.QWebSocket):
new_message = QtCore.pyqtSignal(GotifyMessageModel) new_message = QtCore.pyqtSignal(GotifyMessageModel)
error = QtCore.pyqtSignal(Exception)
opened = QtCore.pyqtSignal() opened = QtCore.pyqtSignal()
closed = QtCore.pyqtSignal(int, str) closed = QtCore.pyqtSignal()
def __init__(self, url: str, client_token: str): def __init__(self, url: str, client_token: str):
super(Listener, self).__init__() super(Listener, self).__init__()
qurl = QtCore.QUrl(url.rstrip("/") + "/") self.update_auth(url, client_token)
qurl.setScheme("wss" if qurl.scheme() == "https" else "ws")
qurl.setPath(qurl.path() + "stream")
qurl.setQuery(f"token={client_token}")
self.ws = websocket.WebSocketApp( self.connected.connect(self._on_connect)
qurl.toString(), self.disconnected.connect(self._on_disconnect)
on_message=self._on_message, self.error.connect(self._on_error)
on_error=self._on_error, self.textMessageReceived.connect(self._on_message)
on_open=self._on_open,
on_close=self._on_close,
)
self.wait_time = 0 self.reset_wait_time()
self.running = False def update_auth(self, url: str, client_token: str):
self.qurl = QtCore.QUrl(url.rstrip("/") + "/")
self.qurl.setScheme("wss" if self.qurl.scheme() == "https" else "ws")
self.qurl.setPath(self.qurl.path() + "stream")
self.qurl.setQuery(f"token={client_token}")
def start(self):
logger.debug("Opening connection.")
self.open(self.qurl)
def stop(self):
logger.debug("Stopping listener.")
self.close()
def reconnect(self):
self.increase_wait_time()
QtCore.QTimer.singleShot(self.wait_time * 1000, self.start)
def is_connected(self) -> bool:
return self.state() == QtNetwork.QAbstractSocket.SocketState.ConnectedState
def reset_wait_time(self): def reset_wait_time(self):
self.wait_time = 0 self.wait_time = 0
@@ -47,42 +57,17 @@ class Listener(QtCore.QThread):
else: else:
self.wait_time = min(self.wait_time * 2, 10 * 60) self.wait_time = min(self.wait_time * 2, 10 * 60)
def _on_message(self, ws: websocket.WebSocketApp, message: str): def _on_connect(self):
logger.debug("Connection established.")
self.reset_wait_time()
self.opened.emit()
def _on_disconnect(self):
logger.debug(f"Connection was closed: {self.closeCode()}.")
self.closed.emit()
def _on_message(self, message: str):
self.new_message.emit(GotifyMessageModel(json.loads(message))) self.new_message.emit(GotifyMessageModel(json.loads(message)))
def _on_error(self, ws: websocket.WebSocketApp, error: Exception): def _on_error(self):
logger.error(f"websocket error: {error}") logger.error(f"Listener socker error: {self.errorString()}")
self.error.emit(error)
def _on_open(self, ws: websocket.WebSocketApp):
self.opened.emit()
self.reset_wait_time()
def _on_close(
self, ws: websocket.WebSocketApp, close_status_code: int, close_msg: str
):
self.closed.emit(close_status_code, close_msg)
def stop_final(self):
def dummy(*args):
...
self.ws.on_close = dummy
self.ws.close()
self.running = False
def stop(self):
logger.debug("Listener: stopping.")
self.ws.close()
self.running = False
def run(self):
self.running = True
try:
if platform.system() == "Darwin":
self.ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
else:
self.ws.run_forever()
finally:
logger.debug("Listener: stopped.")
self.running = False

View File

@@ -81,18 +81,13 @@ class MainApplication(QtWidgets.QApplication):
self.first_connect = True self.first_connect = True
self.gotify_client.listen(
opened_callback=self.listener_opened_callback,
closed_callback=self.listener_closed_callback,
new_message_callback=self.new_message_callback,
error_callback=self.listener_error_callback,
)
self.watchdog = ServerConnectionWatchdogTask(self.gotify_client) self.watchdog = ServerConnectionWatchdogTask(self.gotify_client)
self.link_callbacks() self.link_callbacks()
self.init_shortcuts() self.init_shortcuts()
self.gotify_client.listen()
if settings.value("watchdog/enabled", type=bool): if settings.value("watchdog/enabled", type=bool):
self.watchdog.start() self.watchdog.start()
@@ -151,22 +146,17 @@ class MainApplication(QtWidgets.QApplication):
self.get_missed_messages_task.success.connect(get_missed_messages_callback) self.get_missed_messages_task.success.connect(get_missed_messages_callback)
self.get_missed_messages_task.start() self.get_missed_messages_task.start()
def listener_closed_callback(self, close_status_code: int, close_msg: str): def listener_closed_callback(self):
self.main_window.set_connecting()
self.tray.set_icon_error()
self.gotify_client.increase_wait_time()
QtCore.QTimer.singleShot(self.gotify_client.get_wait_time() * 1000, self.gotify_client.reconnect)
def listener_error_callback(self, exception: Exception):
self.main_window.set_connecting() self.main_window.set_connecting()
self.tray.set_icon_error() self.tray.set_icon_error()
self.gotify_client.reconnect()
def reconnect_callback(self): def reconnect_callback(self):
self.gotify_client.reset_wait_time()
if not self.gotify_client.is_listening(): if not self.gotify_client.is_listening():
self.gotify_client.listener.reset_wait_time()
self.gotify_client.reconnect() self.gotify_client.reconnect()
else: else:
self.gotify_client.stop(reset_wait=True) self.gotify_client.stop()
def abort_get_messages_task(self): def abort_get_messages_task(self):
""" """
@@ -322,18 +312,12 @@ class MainApplication(QtWidgets.QApplication):
settings_dialog.apply_settings() settings_dialog.apply_settings()
if settings_dialog.server_changed: if settings_dialog.server_changed:
# Restart the listener # Update the server parameters and trigger a listener restart
self.gotify_client.stop_final()
self.gotify_client.update_auth( self.gotify_client.update_auth(
settings.value("Server/url", type=str), settings.value("Server/url", type=str),
settings.value("Server/client_token", type=str), settings.value("Server/client_token", type=str),
) )
self.gotify_client.listen( self.gotify_client.stop()
new_message_callback=self.new_message_callback,
opened_callback=self.listener_opened_callback,
closed_callback=self.listener_closed_callback,
error_callback=self.listener_error_callback,
)
def tray_notification_clicked_callback(self): def tray_notification_clicked_callback(self):
if settings.value("tray/notifications/click", type=bool): if settings.value("tray/notifications/click", type=bool):
@@ -368,7 +352,11 @@ class MainApplication(QtWidgets.QApplication):
self.messages_model.rowsInserted.connect(self.main_window.display_message_widgets) self.messages_model.rowsInserted.connect(self.main_window.display_message_widgets)
self.watchdog.closed.connect(lambda: self.listener_closed_callback(0, 0)) self.gotify_client.opened.connect(self.listener_opened_callback)
self.gotify_client.closed.connect(self.listener_closed_callback)
self.gotify_client.new_message.connect(self.new_message_callback)
self.watchdog.closed.connect(self.listener_closed_callback)
def init_shortcuts(self): def init_shortcuts(self):
self.shortcut_quit = QtGui.QShortcut( self.shortcut_quit = QtGui.QShortcut(
@@ -384,14 +372,16 @@ class MainApplication(QtWidgets.QApplication):
self.lock_file.setStaleLockTime(0) self.lock_file.setStaleLockTime(0)
return self.lock_file.tryLock() return self.lock_file.tryLock()
def quit(self) -> None: def quit(self):
logger.debug("Quit requested.")
self.main_window.store_state() self.main_window.store_state()
self.tray.hide() self.tray.hide()
self.lock_file.unlock() self.lock_file.unlock()
self.gotify_client.stop_final() self.gotify_client.quit()
super(MainApplication, self).quit() super(MainApplication, self).quit()
sys.exit(0) sys.exit(0)

View File

@@ -1,3 +1,2 @@
requests==2.31.0 requests==2.31.0
websocket-client==1.5.2
pyqt6==6.5.1 pyqt6==6.5.1

View File

@@ -15,7 +15,7 @@ with open("version.txt", "r") as f:
# What packages are required for this module to be executed? # What packages are required for this module to be executed?
REQUIRED = [ REQUIRED = [
'requests==2.31.0', 'pyqt6==6.5.1', 'websocket-client==1.5.2' 'requests==2.31.0', 'pyqt6==6.5.1'
] ]
# What packages are optional? # What packages are optional?