Merge branch 'cleanup' into develop

This commit is contained in:
dries.k
2023-05-22 23:19:59 +02:00
16 changed files with 223 additions and 338 deletions

View File

@@ -18,6 +18,7 @@ DEFAULT_SETTINGS = {
"tray/notifications/click": True, "tray/notifications/click": True,
"tray/icon/unread": False, "tray/icon/unread": False,
"watchdog/interval/s": 60, "watchdog/interval/s": 60,
"MessageWidget/height/min": 100,
"MessageWidget/image/size": 33, "MessageWidget/image/size": 33,
"MessageWidget/content_image/W_percentage": 1.0, "MessageWidget/content_image/W_percentage": 1.0,
"MessageWidget/content_image/H_percentage": 0.5, "MessageWidget/content_image/H_percentage": 0.5,

View File

@@ -1,5 +1,5 @@
import logging import logging
from typing import Callable, List, Optional, Union from typing import Callable
import requests import requests
@@ -22,12 +22,12 @@ class GotifySession(object):
self.session = requests.Session() self.session = requests.Session()
self.update_auth(url.rstrip("/"), token) self.update_auth(url.rstrip("/"), token)
def update_auth(self, url: str = None, token: str = None): def update_auth(self, url: str | None = None, token: str | None = None):
if url: if url:
self.url = url self.url = url
if token: if token:
self.token = token self.token = token
self.session.headers.update({"X-Gotify-Key": token}) self.session.headers.update({"X-Gotify-Key": token})
def _get(self, endpoint: str = "/", **kwargs) -> requests.Response: def _get(self, endpoint: str = "/", **kwargs) -> requests.Response:
return self.session.get(self.url + endpoint, **kwargs) return self.session.get(self.url + endpoint, **kwargs)
@@ -50,8 +50,8 @@ class GotifyApplication(GotifySession):
super(GotifyApplication, self).__init__(url, application_token) super(GotifyApplication, self).__init__(url, application_token)
def push( def push(
self, title: str = "", message: str = "", priority: int = 0, extras: dict = None self, title: str = "", message: str = "", priority: int = 0, extras: dict | None = None
) -> Union[GotifyMessageModel, GotifyErrorModel]: ) -> GotifyMessageModel | GotifyErrorModel:
response = self._post( response = self._post(
"/message", "/message",
json={ json={
@@ -77,7 +77,7 @@ class GotifyClient(GotifySession):
Application Application
""" """
def get_applications(self) -> Union[List[GotifyApplicationModel], GotifyErrorModel]: def get_applications(self) -> list[GotifyApplicationModel] | GotifyErrorModel:
response = self._get("/application") response = self._get("/application")
return ( return (
[GotifyApplicationModel(x) for x in response.json()] [GotifyApplicationModel(x) for x in response.json()]
@@ -87,7 +87,7 @@ class GotifyClient(GotifySession):
def create_application( def create_application(
self, name: str, description: str = "" self, name: str, description: str = ""
) -> Union[GotifyApplicationModel, GotifyErrorModel]: ) -> GotifyApplicationModel | GotifyErrorModel:
response = self._post( response = self._post(
"/application", json={"name": name, "description": description} "/application", json={"name": name, "description": description}
) )
@@ -99,7 +99,7 @@ class GotifyClient(GotifySession):
def update_application( def update_application(
self, application_id: int, name: str, description: str = "" self, application_id: int, name: str, description: str = ""
) -> Union[GotifyApplicationModel, GotifyErrorModel]: ) -> GotifyApplicationModel | GotifyErrorModel:
response = self._put( response = self._put(
f"/application/{application_id}", f"/application/{application_id}",
json={"name": name, "description": description}, json={"name": name, "description": description},
@@ -115,7 +115,7 @@ class GotifyClient(GotifySession):
def upload_application_image( def upload_application_image(
self, application_id: int, img_path: str self, application_id: int, img_path: str
) -> Optional[Union[GotifyApplicationModel, GotifyErrorModel]]: ) -> GotifyApplicationModel | GotifyErrorModel | None:
try: try:
with open(img_path, "rb") as f: with open(img_path, "rb") as f:
response = self._post( response = self._post(
@@ -137,8 +137,8 @@ class GotifyClient(GotifySession):
""" """
def get_application_messages( def get_application_messages(
self, application_id: int, limit: int = 100, since: int = None self, application_id: int, limit: int = 100, since: int | None = None
) -> Union[GotifyPagedMessagesModel, GotifyErrorModel]: ) -> GotifyPagedMessagesModel | GotifyErrorModel:
response = self._get( response = self._get(
f"/application/{application_id}/message", f"/application/{application_id}/message",
params={"limit": limit, "since": since}, params={"limit": limit, "since": since},
@@ -155,8 +155,8 @@ class GotifyClient(GotifySession):
return self._delete(f"/application/{application_id}/message").ok return self._delete(f"/application/{application_id}/message").ok
def get_messages( def get_messages(
self, limit: int = 100, since: int = None self, limit: int = 100, since: int | None = None
) -> Union[GotifyPagedMessagesModel, GotifyErrorModel]: ) -> GotifyPagedMessagesModel | GotifyErrorModel:
response = self._get("/message", params={"limit": limit, "since": since}) response = self._get("/message", params={"limit": limit, "since": since})
if not response.ok: if not response.ok:
return GotifyErrorModel(response) return GotifyErrorModel(response)
@@ -174,10 +174,10 @@ class GotifyClient(GotifySession):
def listen( def listen(
self, self,
opened_callback: Callable[[], None] = None, opened_callback: (Callable[[], None]) | None = None,
closed_callback: Callable[[int, str], None] = None, closed_callback: Callable[[int, str], None] | None = None,
new_message_callback: Callable[[GotifyMessageModel], None] = None, new_message_callback: Callable[[GotifyMessageModel], None] | None = None,
error_callback: Callable[[Exception], None] = None, error_callback: Callable[[Exception], None] | None = None,
): ):
def dummy(*args): def dummy(*args):
... ...
@@ -189,7 +189,7 @@ class GotifyClient(GotifySession):
self.listener.error.connect(error_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): def opened_callback(self, user_callback: Callable[[], None] | None = None):
self.reset_wait_time() self.reset_wait_time()
if user_callback: if user_callback:
user_callback() user_callback()
@@ -222,7 +222,7 @@ class GotifyClient(GotifySession):
Health Health
""" """
def health(self) -> Union[GotifyHealthModel, GotifyErrorModel]: def health(self) -> GotifyHealthModel | GotifyErrorModel:
response = self._get("/health") response = self._get("/health")
return ( return (
GotifyHealthModel(response.json()) GotifyHealthModel(response.json())
@@ -234,7 +234,7 @@ class GotifyClient(GotifySession):
Version Version
""" """
def version(self) -> Union[GotifyVersionModel, GotifyErrorModel]: def version(self) -> GotifyVersionModel | GotifyErrorModel:
response = self._get("/version") response = self._get("/version")
return ( return (
GotifyVersionModel(response.json()) GotifyVersionModel(response.json())

View File

@@ -1,7 +1,6 @@
import datetime import datetime
from dateutil.parser import isoparse from dateutil.parser import isoparse
import logging import logging
from typing import List, Optional
import requests import requests
@@ -33,7 +32,7 @@ class GotifyApplicationModel(AttributeDict):
class GotifyPagingModel(AttributeDict): class GotifyPagingModel(AttributeDict):
limit: int limit: int
next: Optional[str] = None next: str | None = None
since: int since: int
size: int size: int
@@ -41,11 +40,11 @@ class GotifyPagingModel(AttributeDict):
class GotifyMessageModel(AttributeDict): class GotifyMessageModel(AttributeDict):
appid: int appid: int
date: datetime.datetime date: datetime.datetime
extras: Optional[dict] = None extras: dict | None = None
id: int id: int
message: str message: str
priority: Optional[int] = None priority: int | None = None
title: Optional[str] = None title: str | None = None
def __init__(self, d: dict, *args, **kwargs): def __init__(self, d: dict, *args, **kwargs):
d.update( d.update(
@@ -55,7 +54,7 @@ class GotifyMessageModel(AttributeDict):
class GotifyPagedMessagesModel(AttributeDict): class GotifyPagedMessagesModel(AttributeDict):
messages: List[GotifyMessageModel] messages: list[GotifyMessageModel]
paging: GotifyPagingModel paging: GotifyPagingModel

View File

@@ -4,7 +4,6 @@ import os
import platform import platform
import sys import sys
import tempfile import tempfile
from typing import List, Union
from gotify_tray import gotify from gotify_tray import gotify
from gotify_tray.__version__ import __title__ from gotify_tray.__version__ import __title__
@@ -18,7 +17,6 @@ from gotify_tray.tasks import (
GetApplicationMessagesTask, GetApplicationMessagesTask,
GetMessagesTask, GetMessagesTask,
ProcessMessageTask, ProcessMessageTask,
ProcessMessagesTask,
ServerConnectionWatchdogTask, ServerConnectionWatchdogTask,
) )
from gotify_tray.gui.themes import set_theme from gotify_tray.gui.themes import set_theme
@@ -51,9 +49,7 @@ def init_logger(logger: logging.Logger):
else: else:
logging.disable() logging.disable()
logdir = QtCore.QStandardPaths.standardLocations( logdir = QtCore.QStandardPaths.standardLocations(QtCore.QStandardPaths.StandardLocation.AppDataLocation)[0]
QtCore.QStandardPaths.StandardLocation.AppDataLocation
)[0]
if not os.path.exists(logdir): if not os.path.exists(logdir):
os.mkdir(logdir) os.mkdir(logdir)
logging.basicConfig( logging.basicConfig(
@@ -88,9 +84,9 @@ class MainApplication(QtWidgets.QApplication):
self.first_connect = True self.first_connect = True
self.gotify_client.listen( self.gotify_client.listen(
new_message_callback=self.new_message_callback,
opened_callback=self.listener_opened_callback, opened_callback=self.listener_opened_callback,
closed_callback=self.listener_closed_callback, closed_callback=self.listener_closed_callback,
new_message_callback=self.new_message_callback,
error_callback=self.listener_error_callback, error_callback=self.listener_error_callback,
) )
@@ -108,29 +104,17 @@ class MainApplication(QtWidgets.QApplication):
self.application_model.setItem(0, 0, ApplicationAllMessagesItem()) self.application_model.setItem(0, 0, ApplicationAllMessagesItem())
self.get_applications_task = GetApplicationsTask(self.gotify_client) self.get_applications_task = GetApplicationsTask(self.gotify_client)
self.get_applications_task.success.connect( self.get_applications_task.success.connect(self.get_applications_success_callback)
self.get_applications_success_callback self.get_applications_task.started.connect(self.main_window.disable_applications)
) self.get_applications_task.finished.connect(self.main_window.enable_applications)
self.get_applications_task.started.connect(
self.main_window.disable_applications
)
self.get_applications_task.finished.connect(
self.main_window.enable_applications
)
self.get_applications_task.start() self.get_applications_task.start()
def get_applications_success_callback( def get_applications_success_callback(
self, applications: List[gotify.GotifyApplicationModel], self, applications: list[gotify.GotifyApplicationModel],
): ):
for i, application in enumerate(applications): for i, application in enumerate(applications):
icon = QtGui.QIcon( icon = QtGui.QIcon(self.downloader.get_filename(f"{self.gotify_client.url}/{application.image}"))
self.downloader.get_filename( self.application_model.setItem(i + 1, 0, ApplicationModelItem(application, icon))
f"{self.gotify_client.url}/{application.image}"
)
)
self.application_model.setItem(
i + 1, 0, ApplicationModelItem(application, icon),
)
def update_last_id(self, i: int): def update_last_id(self, i: int):
if i > settings.value("message/last", type=int): if i > settings.value("message/last", type=int):
@@ -153,9 +137,9 @@ class MainApplication(QtWidgets.QApplication):
for message in page.messages: for message in page.messages:
if message.id > last_id: if message.id > last_id:
if settings.value("message/check_missed/notify", type=bool): if settings.value("message/check_missed/notify", type=bool):
self.new_message_callback(message) self.new_message_callback(message, process=False)
else: else:
self.add_message_to_model(message) self.add_message_to_model(message, process=False)
ids.append(message.id) ids.append(message.id)
if ids: if ids:
@@ -169,9 +153,7 @@ class MainApplication(QtWidgets.QApplication):
self.main_window.set_connecting() self.main_window.set_connecting()
self.tray.set_icon_error() self.tray.set_icon_error()
self.gotify_client.increase_wait_time() self.gotify_client.increase_wait_time()
QtCore.QTimer.singleShot( QtCore.QTimer.singleShot(self.gotify_client.get_wait_time() * 1000, self.gotify_client.reconnect)
self.gotify_client.get_wait_time() * 1000, self.gotify_client.reconnect
)
def listener_error_callback(self, exception: Exception): def listener_error_callback(self, exception: Exception):
self.main_window.set_connecting() self.main_window.set_connecting()
@@ -184,51 +166,8 @@ class MainApplication(QtWidgets.QApplication):
else: else:
self.gotify_client.stop(reset_wait=True) self.gotify_client.stop(reset_wait=True)
def insert_message(
self,
row: int,
message: gotify.GotifyMessageModel,
application: gotify.GotifyApplicationModel,
):
"""Insert a message gotify message into the messages model. Also add the message widget to the listview
Args:
row (int): >=0: insert message at specified position, <0: append message to the end of the model
message (gotify.GotifyMessageModel): message
application (gotify.GotifyApplicationModel): application
"""
self.update_last_id(message.id)
message_item = MessagesModelItem(message)
if row >= 0:
self.messages_model.insertRow(row, message_item)
else:
self.messages_model.appendRow(message_item)
self.main_window.insert_message_widget(
message_item,
self.downloader.get_filename(
f"{self.gotify_client.url}/{application.image}"
),
)
def get_messages_finished_callback(self, page: gotify.GotifyPagedMessagesModel):
"""Process messages before inserting them into the main window
"""
def insert_helper(message: gotify.GotifyMessageModel):
if item := self.application_model.itemFromId(message.appid):
self.insert_message(
-1, message, item.data(ApplicationItemDataRole.ApplicationRole)
)
self.processEvents()
self.process_messages_task = ProcessMessagesTask(page)
self.process_messages_task.message_processed.connect(insert_helper)
self.process_messages_task.start()
def application_selection_changed_callback( def application_selection_changed_callback(
self, item: Union[ApplicationModelItem, ApplicationAllMessagesItem] self, item: ApplicationModelItem | ApplicationAllMessagesItem
): ):
self.messages_model.clear() self.messages_model.clear()
@@ -237,62 +176,44 @@ class MainApplication(QtWidgets.QApplication):
item.data(ApplicationItemDataRole.ApplicationRole).id, item.data(ApplicationItemDataRole.ApplicationRole).id,
self.gotify_client, self.gotify_client,
) )
self.get_application_messages_task.success.connect( self.get_application_messages_task.message.connect(self.messages_model.append_message)
self.get_messages_finished_callback
)
self.get_application_messages_task.start() self.get_application_messages_task.start()
elif isinstance(item, ApplicationAllMessagesItem): elif isinstance(item, ApplicationAllMessagesItem):
self.get_messages_task = GetMessagesTask(self.gotify_client) self.get_messages_task = GetMessagesTask(self.gotify_client)
self.get_messages_task.success.connect(self.get_messages_finished_callback) self.get_messages_task.message.connect(self.messages_model.append_message)
self.get_messages_task.start() self.get_messages_task.start()
def add_message_to_model(self, message: gotify.GotifyMessageModel): def add_message_to_model(self, message: gotify.GotifyMessageModel, process: bool = True):
if application_item := self.application_model.itemFromId(message.appid): if self.application_model.itemFromId(message.appid):
application_index = self.main_window.currentApplicationIndex() application_index = self.main_window.currentApplicationIndex()
if selected_application_item := self.application_model.itemFromIndex( if selected_application_item := self.application_model.itemFromIndex(application_index):
application_index
):
def insert_message_helper(): def insert_message_helper():
if isinstance(selected_application_item, ApplicationModelItem): if isinstance(selected_application_item, ApplicationModelItem):
# A single application is selected # A single application is selected
# -> Only insert the message if the appid matches the selected appid
if ( if (
message.appid message.appid
== selected_application_item.data( == selected_application_item.data(ApplicationItemDataRole.ApplicationRole).id
ApplicationItemDataRole.ApplicationRole
).id
): ):
self.insert_message( self.messages_model.insert_message(0, message)
0, elif isinstance(selected_application_item, ApplicationAllMessagesItem):
message,
application_item.data(
ApplicationItemDataRole.ApplicationRole
),
)
elif isinstance(
selected_application_item, ApplicationAllMessagesItem
):
# "All messages' is selected # "All messages' is selected
self.insert_message( self.messages_model.insert_message(0, message)
0,
message,
application_item.data(
ApplicationItemDataRole.ApplicationRole
),
)
self.process_message_task = ProcessMessageTask(message) if process:
self.process_message_task.finished.connect(insert_message_helper) self.process_message_task = ProcessMessageTask(message)
self.process_message_task.start() self.process_message_task.finished.connect(insert_message_helper)
self.process_message_task.start()
else:
insert_message_helper()
else: else:
logger.error( logger.error(f"App id {message.appid} could not be found. Refreshing applications.")
f"App id {message.appid} could not be found. Refreshing applications."
)
self.refresh_applications() self.refresh_applications()
def new_message_callback(self, message: gotify.GotifyMessageModel): def new_message_callback(self, message: gotify.GotifyMessageModel, process: bool = True):
self.add_message_to_model(message) self.add_message_to_model(message, process=process)
# Change the tray icon to show there are unread notifications # Change the tray icon to show there are unread notifications
if ( if (
@@ -301,23 +222,19 @@ class MainApplication(QtWidgets.QApplication):
): ):
self.tray.set_icon_unread() self.tray.set_icon_unread()
# Show a notification # Don't show a notification if it's low priority or the window is active
if ( if (
message.priority < settings.value("tray/notifications/priority", type=int) message.priority < settings.value("tray/notifications/priority", type=int)
or self.main_window.isActiveWindow() or self.main_window.isActiveWindow()
): ):
return return
if settings.value("tray/notifications/icon/show", type=bool): # Get the application icon
if application_item := self.application_model.itemFromId(message.appid): if (
image_url = f"{self.gotify_client.url}/{application_item.data(ApplicationItemDataRole.ApplicationRole).image}" settings.value("tray/notifications/icon/show", type=bool)
icon = QtGui.QIcon(self.downloader.get_filename(image_url)) and (application_item := self.application_model.itemFromId(message.appid))
else: ):
logger.error( icon = application_item.icon()
f"MainWindow.new_message_callback: App id {message.appid} could not be found. Refreshing applications."
)
self.refresh_applications()
icon = QtWidgets.QSystemTrayIcon.MessageIcon.Information
else: else:
icon = QtWidgets.QSystemTrayIcon.MessageIcon.Information icon = QtWidgets.QSystemTrayIcon.MessageIcon.Information
@@ -336,7 +253,7 @@ class MainApplication(QtWidgets.QApplication):
self.delete_message_task.start() self.delete_message_task.start()
def delete_all_messages_callback( def delete_all_messages_callback(
self, item: Union[ApplicationModelItem, ApplicationAllMessagesItem] self, item: ApplicationModelItem | ApplicationAllMessagesItem
): ):
if isinstance(item, ApplicationModelItem): if isinstance(item, ApplicationModelItem):
self.delete_application_messages_task = DeleteApplicationMessagesTask( self.delete_application_messages_task = DeleteApplicationMessagesTask(
@@ -375,17 +292,13 @@ class MainApplication(QtWidgets.QApplication):
# Update the message widget icons # Update the message widget icons
for r in range(self.messages_model.rowCount()): for r in range(self.messages_model.rowCount()):
message_widget: MessageWidget = self.main_window.listView_messages.indexWidget( message_widget: MessageWidget = self.main_window.listView_messages.indexWidget(self.messages_model.index(r, 0))
self.messages_model.index(r, 0)
)
message_widget.set_icons() message_widget.set_icons()
def settings_callback(self): def settings_callback(self):
settings_dialog = SettingsDialog(self) settings_dialog = SettingsDialog(self)
settings_dialog.quit_requested.connect(self.quit) settings_dialog.quit_requested.connect(self.quit)
settings_dialog.theme_change_requested.connect( settings_dialog.theme_change_requested.connect(self.theme_change_requested_callback)
self.theme_change_requested_callback
)
accepted = settings_dialog.exec() accepted = settings_dialog.exec()
if accepted and settings_dialog.settings_changed: if accepted and settings_dialog.settings_changed:
@@ -428,9 +341,7 @@ class MainApplication(QtWidgets.QApplication):
self.main_window.refresh.connect(self.refresh_applications) self.main_window.refresh.connect(self.refresh_applications)
self.main_window.delete_all.connect(self.delete_all_messages_callback) self.main_window.delete_all.connect(self.delete_all_messages_callback)
self.main_window.application_selection_changed.connect( self.main_window.application_selection_changed.connect(self.application_selection_changed_callback)
self.application_selection_changed_callback
)
self.main_window.delete_message.connect(self.delete_message_callback) self.main_window.delete_message.connect(self.delete_message_callback)
self.main_window.image_popup.connect(self.image_popup_callback) self.main_window.image_popup.connect(self.image_popup_callback)
self.main_window.hidden.connect(self.main_window_hidden_callback) self.main_window.hidden.connect(self.main_window_hidden_callback)
@@ -438,7 +349,9 @@ class MainApplication(QtWidgets.QApplication):
self.styleHints().colorSchemeChanged.connect(lambda _: self.theme_change_requested_callback(settings.value("theme", type=str))) self.styleHints().colorSchemeChanged.connect(lambda _: self.theme_change_requested_callback(settings.value("theme", type=str)))
self.watchdog.closed.connect(lambda: self.listener_closed_callback(None, None)) self.messages_model.rowsInserted.connect(self.main_window.display_message_widgets)
self.watchdog.closed.connect(lambda: self.listener_closed_callback(0, 0))
def init_shortcuts(self): def init_shortcuts(self):
self.shortcut_quit = QtGui.QShortcut( self.shortcut_quit = QtGui.QShortcut(
@@ -449,9 +362,7 @@ class MainApplication(QtWidgets.QApplication):
def acquire_lock(self) -> bool: def acquire_lock(self) -> bool:
temp_dir = tempfile.gettempdir() temp_dir = tempfile.gettempdir()
lock_filename = os.path.join( lock_filename = os.path.join(temp_dir, __title__ + "-" + getpass.getuser() + ".lock")
temp_dir, __title__ + "-" + getpass.getuser() + ".lock"
)
self.lock_file = QtCore.QLockFile(lock_filename) self.lock_file = QtCore.QLockFile(lock_filename)
self.lock_file.setStaleLockTime(0) self.lock_file.setStaleLockTime(0)
return self.lock_file.tryLock() return self.lock_file.tryLock()

View File

@@ -1,6 +1,5 @@
import enum import enum
from typing import Optional, Union
from PyQt6 import QtCore, QtGui from PyQt6 import QtCore, QtGui
from gotify_tray import gotify from gotify_tray import gotify
from gotify_tray.database import Settings from gotify_tray.database import Settings
@@ -18,7 +17,7 @@ class ApplicationModelItem(QtGui.QStandardItem):
def __init__( def __init__(
self, self,
application: gotify.GotifyApplicationModel, application: gotify.GotifyApplicationModel,
icon: Optional[QtGui.QIcon] = None, icon: QtGui.QIcon | None = None,
*args, *args,
**kwargs, **kwargs,
): ):
@@ -55,24 +54,22 @@ class ApplicationAllMessagesItem(QtGui.QStandardItem):
class ApplicationModel(QtGui.QStandardItemModel): class ApplicationModel(QtGui.QStandardItemModel):
def __init__(self): def __init__(self):
super(ApplicationModel, self).__init__() super(ApplicationModel, self).__init__()
self.setItemPrototype( self.setItemPrototype(ApplicationModelItem(gotify.GotifyApplicationModel({"name": ""}), None))
ApplicationModelItem(gotify.GotifyApplicationModel({"name": ""}), None)
)
def setItem( def setItem(
self, self,
row: int, row: int,
column: int, column: int,
item: Union[ApplicationModelItem, ApplicationAllMessagesItem], item: ApplicationModelItem | ApplicationAllMessagesItem,
) -> None: ) -> None:
super(ApplicationModel, self).setItem(row, column, item) super(ApplicationModel, self).setItem(row, column, item)
def itemFromIndex( def itemFromIndex(
self, index: QtCore.QModelIndex self, index: QtCore.QModelIndex
) -> Union[ApplicationModelItem, ApplicationAllMessagesItem]: ) -> ApplicationModelItem | ApplicationAllMessagesItem:
return super(ApplicationModel, self).itemFromIndex(index) return super(ApplicationModel, self).itemFromIndex(index)
def itemFromId(self, appid: int) -> Optional[ApplicationModelItem]: def itemFromId(self, appid: int) -> ApplicationModelItem | None:
for row in range(self.rowCount()): for row in range(self.rowCount()):
item = self.item(row, 0) item = self.item(row, 0)
if not isinstance(item, ApplicationModelItem): if not isinstance(item, ApplicationModelItem):

View File

@@ -3,6 +3,10 @@ import enum
from typing import cast from typing import cast
from PyQt6 import QtCore, QtGui from PyQt6 import QtCore, QtGui
from gotify_tray import gotify from gotify_tray import gotify
from gotify_tray.database import Settings
settings = Settings("gotify-tray")
class MessageItemDataRole(enum.IntEnum): class MessageItemDataRole(enum.IntEnum):
@@ -16,6 +20,20 @@ class MessagesModelItem(QtGui.QStandardItem):
class MessagesModel(QtGui.QStandardItemModel): class MessagesModel(QtGui.QStandardItemModel):
def update_last_id(self, i: int):
if i > settings.value("message/last", type=int):
settings.setValue("message/last", i)
def insert_message(self, row: int, message: gotify.GotifyMessageModel):
self.update_last_id(message.id)
message_item = MessagesModelItem(message)
self.insertRow(row, message_item)
def append_message(self, message: gotify.GotifyMessageModel):
self.update_last_id(message.id)
message_item = MessagesModelItem(message)
self.appendRow(message_item)
def setItem(self, row: int, column: int, item: MessagesModelItem) -> None: def setItem(self, row: int, column: int, item: MessagesModelItem) -> None:
super(MessagesModel, self).setItem(row, column, item) super(MessagesModel, self).setItem(row, column, item)

View File

@@ -45,7 +45,7 @@ def set_theme(app: QtWidgets.QApplication, theme: str = "automatic"):
app.setStyleSheet(stylesheet) app.setStyleSheet(stylesheet)
def get_theme_file(app: QtWidgets.QApplication, file: str, theme: str = None) -> str: def get_theme_file(app: QtWidgets.QApplication, file: str, theme: str | None = None) -> str:
theme = settings.value("theme", type=str) if not theme else theme theme = settings.value("theme", type=str) if not theme else theme
if not is_valid_theme(theme): if not is_valid_theme(theme):

View File

@@ -6,19 +6,27 @@ QPushButton:default:hover, QPushButton:checked:hover {
background: #441b85; background: #441b85;
} }
QPushButton[state="success"] { ServerInfoDialog QPushButton[state="success"] {
background-color: #960b7a0b; background-color: #960b7a0b;
color: white; color: white;
} }
QPushButton[state="failed"] { ServerInfoDialog QPushButton[state="success"]:!default:hover {
background: #960b7a0b;
}
ServerInfoDialog QPushButton[state="failed"] {
background-color: #8ebb2929; background-color: #8ebb2929;
color: white; color: white;
} }
QLineEdit[state="success"] {} ServerInfoDialog QPushButton[state="failed"]:!default:hover {
background: #8ebb2929;
}
QLineEdit[state="failed"] { ServerInfoDialog QLineEdit[state="success"] {}
ServerInfoDialog QLineEdit[state="failed"] {
border: 1px solid red; border: 1px solid red;
} }

View File

@@ -6,19 +6,27 @@ QPushButton:default:hover, QPushButton:checked:hover {
background: #5c24b6; background: #5c24b6;
} }
QPushButton[state="success"] { ServerInfoDialog QPushButton[state="success"] {
background-color: #6400FF00; background-color: #6400FF00;
color: black; color: black;
} }
QPushButton[state="failed"] { ServerInfoDialog QPushButton[state="success"]:!default:hover {
background: #6400FF00;
}
ServerInfoDialog QPushButton[state="failed"] {
background-color: #64FF0000; background-color: #64FF0000;
color: black; color: black;
} }
QLineEdit[state="success"] {} ServerInfoDialog QPushButton[state="failed"]:!default:hover {
background: #64FF0000;
}
QLineEdit[state="failed"] { ServerInfoDialog QLineEdit[state="success"] {}
ServerInfoDialog QLineEdit[state="failed"] {
border: 1px solid red; border: 1px solid red;
} }

View File

@@ -8,7 +8,7 @@ settings = Settings("gotify-tray")
class ImagePopup(QtWidgets.QLabel): class ImagePopup(QtWidgets.QLabel):
def __init__(self, filename: str, pos: QtCore.QPoint, link: str = None): def __init__(self, filename: str, pos: QtCore.QPoint, link: str | None = None):
"""Create and show a pop-up image under the cursor """Create and show a pop-up image under the cursor
Args: Args:

View File

@@ -11,6 +11,8 @@ from . import MessageWidget
from gotify_tray.__version__ import __title__ from gotify_tray.__version__ import __title__
from gotify_tray.database import Settings from gotify_tray.database import Settings
from gotify_tray.gui.themes import get_theme_file from gotify_tray.gui.themes import get_theme_file
from gotify_tray.gui.models import MessageItemDataRole
from gotify_tray import gotify
settings = Settings("gotify-tray") settings = Settings("gotify-tray")
@@ -104,17 +106,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def set_error(self): def set_error(self):
self.status_widget.set_error() self.status_widget.set_error()
def insert_message_widget( def display_message_widgets(self, parent: QtCore.QModelIndex, first: int, last: int):
self, message_item: MessagesModelItem, image_path: str = "" for i in range(first, last+1):
): if index := self.messages_model.index(i, 0, parent):
message_widget = MessageWidget( message_item = self.messages_model.itemFromIndex(index)
self.app, self.listView_messages, message_item, image_path=image_path
) message: gotify.GotifyMessageModel = self.messages_model.data(index, MessageItemDataRole.MessageRole)
self.listView_messages.setIndexWidget(
self.messages_model.indexFromItem(message_item), message_widget application_item = self.application_model.itemFromId(message.appid)
)
message_widget.deletion_requested.connect(self.delete_message.emit) message_widget = MessageWidget(self.app, self.listView_messages, message_item, icon=application_item.icon())
message_widget.image_popup.connect(self.image_popup.emit) message_widget.deletion_requested.connect(self.delete_message.emit)
message_widget.image_popup.connect(self.image_popup.emit)
self.listView_messages.setIndexWidget(self.messages_model.indexFromItem(message_item), message_widget)
def currentApplicationIndex(self) -> QtCore.QModelIndex: def currentApplicationIndex(self) -> QtCore.QModelIndex:
return self.listView_applications.selectionModel().currentIndex() return self.listView_applications.selectionModel().currentIndex()

View File

@@ -23,11 +23,11 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
app: QtWidgets.QApplication, app: QtWidgets.QApplication,
parent: QtWidgets.QWidget, parent: QtWidgets.QWidget,
message_item: MessagesModelItem, message_item: MessagesModelItem,
image_path: str = "", icon: QtGui.QIcon | None = None,
): ):
super(MessageWidget, self).__init__() super(MessageWidget, self).__init__(parent)
self.app = app self.app = app
self.parent = parent self._parent = parent
self.setupUi(self) self.setupUi(self)
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
self.message_item = message_item self.message_item = message_item
@@ -43,10 +43,7 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
self.label_title.setText(message.title) self.label_title.setText(message.title)
self.label_date.setText(message.date.strftime("%Y-%m-%d, %H:%M")) self.label_date.setText(message.date.strftime("%Y-%m-%d, %H:%M"))
if markdown := ( if message.get("extras", {}).get("client::display", {}).get("contentType") == "text/markdown":
message.get("extras", {}).get("client::display", {}).get("contentType")
== "text/markdown"
):
self.label_message.setTextFormat(QtCore.Qt.TextFormat.MarkdownText) self.label_message.setTextFormat(QtCore.Qt.TextFormat.MarkdownText)
# If the message is only an image URL, then instead of showing the message, # If the message is only an image URL, then instead of showing the message,
@@ -59,15 +56,9 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
self.label_message.setText(convert_links(message.message)) self.label_message.setText(convert_links(message.message))
# Show the application icon # Show the application icon
if image_path: if icon:
image_size = settings.value("MessageWidget/image/size", type=int) image_size = settings.value("MessageWidget/image/size", type=int)
self.label_image.setFixedSize(QtCore.QSize(image_size, image_size)) pixmap = icon.pixmap(QtCore.QSize(image_size, image_size))
pixmap = QtGui.QPixmap(image_path).scaled(
image_size,
image_size,
aspectRatioMode=QtCore.Qt.AspectRatioMode.KeepAspectRatioByExpanding,
transformMode=QtCore.Qt.TransformationMode.SmoothTransformation,
)
self.label_image.setPixmap(pixmap) self.label_image.setPixmap(pixmap)
else: else:
self.label_image.hide() self.label_image.hide()
@@ -77,7 +68,12 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
self.gridLayout.setContentsMargins(4, 5, 4, 0) self.gridLayout.setContentsMargins(4, 5, 4, 0)
self.adjustSize() self.adjustSize()
size_hint = self.message_item.sizeHint() size_hint = self.message_item.sizeHint()
self.message_item.setSizeHint(QtCore.QSize(size_hint.width(), self.height())) self.message_item.setSizeHint(
QtCore.QSize(
size_hint.width(),
max(settings.value("MessageWidget/height/min", type=int), self.height())
)
)
self.set_icons() self.set_icons()
@@ -113,13 +109,10 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
pixmap = QtGui.QPixmap(filename) pixmap = QtGui.QPixmap(filename)
# Make sure the image fits within the listView # Make sure the image fits within the listView
W = settings.value("MessageWidget/content_image/W_percentage", type=float) * ( W = settings.value("MessageWidget/content_image/W_percentage", type=float)
self.parent.width() - self.label_image.width() H = settings.value("MessageWidget/content_image/H_percentage", type=float)
) W *= self._parent.width() - self.label_image.width()
H = ( H *= self._parent.height()
settings.value("MessageWidget/content_image/H_percentage", type=float)
* self.parent.height()
)
if pixmap.width() > W or pixmap.height() > H: if pixmap.width() > W or pixmap.height() > H:
pixmap = pixmap.scaled( pixmap = pixmap.scaled(
@@ -150,7 +143,5 @@ class MessageWidget(QtWidgets.QWidget, Ui_Form):
self.image_popup.emit(link, QtGui.QCursor.pos()) self.image_popup.emit(link, QtGui.QCursor.pos())
def link_callbacks(self): def link_callbacks(self):
self.pb_delete.clicked.connect( self.pb_delete.clicked.connect(lambda: self.deletion_requested.emit(self.message_item))
lambda: self.deletion_requested.emit(self.message_item)
)
self.label_message.linkHovered.connect(self.link_hovered_callback) self.label_message.linkHovered.connect(self.link_hovered_callback)

View File

@@ -19,9 +19,7 @@ class ServerInfoDialog(QtWidgets.QDialog, Ui_Dialog):
self.line_url.setPlaceholderText("https://gotify.example.com") self.line_url.setPlaceholderText("https://gotify.example.com")
self.line_url.setText(url) self.line_url.setText(url)
self.line_token.setText(token) self.line_token.setText(token)
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setDisabled( self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setDisabled(True)
True
)
self.pb_import.setVisible(enable_import) self.pb_import.setVisible(enable_import)
self.link_callbacks() self.link_callbacks()
@@ -37,9 +35,7 @@ class ServerInfoDialog(QtWidgets.QDialog, Ui_Dialog):
return return
self.pb_test.setDisabled(True) self.pb_test.setDisabled(True)
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setDisabled( self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setDisabled(True)
True
)
self.task = VerifyServerInfoTask(url, client_token) self.task = VerifyServerInfoTask(url, client_token)
self.task.success.connect(self.server_info_success) self.task.success.connect(self.server_info_success)
@@ -59,9 +55,7 @@ class ServerInfoDialog(QtWidgets.QDialog, Ui_Dialog):
self.update_widget_state(self.pb_test, "success") self.update_widget_state(self.pb_test, "success")
self.update_widget_state(self.line_token, "success") self.update_widget_state(self.line_token, "success")
self.update_widget_state(self.line_url, "success") self.update_widget_state(self.line_url, "success")
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setEnabled( self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setEnabled(True)
True
)
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setFocus() self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setFocus()
def incorrect_token_callback(self, version: GotifyVersionModel): def incorrect_token_callback(self, version: GotifyVersionModel):
@@ -80,6 +74,10 @@ class ServerInfoDialog(QtWidgets.QDialog, Ui_Dialog):
self.update_widget_state(self.line_url, "failed") self.update_widget_state(self.line_url, "failed")
self.line_url.setFocus() self.line_url.setFocus()
def input_changed_callback(self):
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setDisabled(True)
self.update_widget_state(self.pb_test, "")
def import_success_callback(self): def import_success_callback(self):
self.line_url.setText(settings.value("Server/url", type=str)) self.line_url.setText(settings.value("Server/url", type=str))
self.line_token.setText(settings.value("Server/client_token")) self.line_token.setText(settings.value("Server/client_token"))
@@ -95,14 +93,6 @@ class ServerInfoDialog(QtWidgets.QDialog, Ui_Dialog):
def link_callbacks(self): def link_callbacks(self):
self.pb_test.clicked.connect(self.test_server_info) self.pb_test.clicked.connect(self.test_server_info)
self.line_url.textChanged.connect( self.line_url.textChanged.connect(self.input_changed_callback)
lambda: self.buttonBox.button( self.line_token.textChanged.connect(self.input_changed_callback)
QtWidgets.QDialogButtonBox.StandardButton.Ok
).setDisabled(True)
)
self.line_token.textChanged.connect(
lambda: self.buttonBox.button(
QtWidgets.QDialogButtonBox.StandardButton.Ok
).setDisabled(True)
)
self.pb_import.clicked.connect(self.import_callback) self.pb_import.clicked.connect(self.import_callback)

View File

@@ -43,42 +43,28 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
self.link_callbacks() self.link_callbacks()
def initUI(self): def initUI(self):
self.buttonBox.button( self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(False)
QtWidgets.QDialogButtonBox.StandardButton.Apply
).setEnabled(False)
# Notifications # Notifications
self.spin_priority.setValue( self.spin_priority.setValue(settings.value("tray/notifications/priority", type=int))
settings.value("tray/notifications/priority", type=int)
)
self.spin_duration.setValue( self.spin_duration.setValue(settings.value("tray/notifications/duration_ms", type=int))
settings.value("tray/notifications/duration_ms", type=int)
)
if platform.system() == "Windows": if platform.system() == "Windows":
# The notification duration setting is ignored by windows # The notification duration setting is ignored by windows
self.label_notification_duration.hide() self.label_notification_duration.hide()
self.spin_duration.hide() self.spin_duration.hide()
self.label_notification_duration_ms.hide() self.label_notification_duration_ms.hide()
self.cb_notify.setChecked( self.cb_notify.setChecked(settings.value("message/check_missed/notify", type=bool))
settings.value("message/check_missed/notify", type=bool)
)
self.cb_notification_click.setChecked( self.cb_notification_click.setChecked(settings.value("tray/notifications/click", type=bool))
settings.value("tray/notifications/click", type=bool)
)
self.cb_tray_icon_unread.setChecked( self.cb_tray_icon_unread.setChecked(settings.value("tray/icon/unread", type=bool))
settings.value("tray/icon/unread", type=bool)
)
# Interface # Interface
self.combo_theme.addItems(get_themes()) self.combo_theme.addItems(get_themes())
self.combo_theme.setCurrentText(settings.value("theme", type=str)) self.combo_theme.setCurrentText(settings.value("theme", type=str))
self.cb_priority_colors.setChecked( self.cb_priority_colors.setChecked(settings.value("MessageWidget/priority_color", type=bool))
settings.value("MessageWidget/priority_color", type=bool)
)
# Logging # Logging
self.combo_logging.addItems( self.combo_logging.addItems(
@@ -96,9 +82,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
self.add_message_widget() self.add_message_widget()
# Advanced # Advanced
self.groupbox_image_popup.setChecked( self.groupbox_image_popup.setChecked(settings.value("ImagePopup/enabled", type=bool))
settings.value("ImagePopup/enabled", type=bool)
)
self.spin_popup_w.setValue(settings.value("ImagePopup/w", type=int)) self.spin_popup_w.setValue(settings.value("ImagePopup/w", type=int))
self.spin_popup_h.setValue(settings.value("ImagePopup/h", type=int)) self.spin_popup_h.setValue(settings.value("ImagePopup/h", type=int))
self.label_cache.setText("0 MB") self.label_cache.setText("0 MB")
@@ -118,7 +102,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
} }
) )
), ),
get_icon("gotify-small"), QtGui.QIcon(get_icon("gotify-small")),
) )
self.layout_fonts_message.addWidget(self.message_widget) self.layout_fonts_message.addWidget(self.message_widget)
@@ -132,16 +116,12 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
def settings_changed_callback(self, *args, **kwargs): def settings_changed_callback(self, *args, **kwargs):
self.settings_changed = True self.settings_changed = True
self.buttonBox.button( self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).setEnabled(True)
QtWidgets.QDialogButtonBox.StandardButton.Apply
).setEnabled(True)
def change_font_callback(self, name: str): def change_font_callback(self, name: str):
label: QtWidgets.QLabel = getattr(self.message_widget, "label_" + name) label: QtWidgets.QLabel = getattr(self.message_widget, "label_" + name)
font, accepted = QtWidgets.QFontDialog.getFont( font, accepted = QtWidgets.QFontDialog.getFont(label.font(), self, f"Select a {name} font")
label.font(), self, f"Select a {name} font"
)
if accepted: if accepted:
self.settings_changed_callback() self.settings_changed_callback()
@@ -205,9 +185,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
self.label_cache.setText("0 MB") self.label_cache.setText("0 MB")
def link_callbacks(self): def link_callbacks(self):
self.buttonBox.button( self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Apply).clicked.connect(self.apply_settings)
QtWidgets.QDialogButtonBox.StandardButton.Apply
).clicked.connect(self.apply_settings)
# Notifications # Notifications
self.spin_priority.valueChanged.connect(self.settings_changed_callback) self.spin_priority.valueChanged.connect(self.settings_changed_callback)
@@ -225,22 +203,14 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
# Logging # Logging
self.combo_logging.currentTextChanged.connect(self.settings_changed_callback) self.combo_logging.currentTextChanged.connect(self.settings_changed_callback)
self.pb_open_log.clicked.connect( self.pb_open_log.clicked.connect(lambda: open_file(logger.root.handlers[0].baseFilename))
lambda: open_file(logger.root.handlers[0].baseFilename)
)
# Fonts # Fonts
self.pb_reset_fonts.clicked.connect(self.reset_fonts_callback) self.pb_reset_fonts.clicked.connect(self.reset_fonts_callback)
self.pb_font_message_title.clicked.connect( self.pb_font_message_title.clicked.connect(lambda: self.change_font_callback("title"))
lambda: self.change_font_callback("title") self.pb_font_message_date.clicked.connect(lambda: self.change_font_callback("date"))
) self.pb_font_message_content.clicked.connect(lambda: self.change_font_callback("message"))
self.pb_font_message_date.clicked.connect(
lambda: self.change_font_callback("date")
)
self.pb_font_message_content.clicked.connect(
lambda: self.change_font_callback("message")
)
# Advanced # Advanced
self.pb_export.clicked.connect(self.export_callback) self.pb_export.clicked.connect(self.export_callback)
@@ -257,9 +227,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
settings.setValue("tray/notifications/priority", self.spin_priority.value()) settings.setValue("tray/notifications/priority", self.spin_priority.value())
settings.setValue("tray/notifications/duration_ms", self.spin_duration.value()) settings.setValue("tray/notifications/duration_ms", self.spin_duration.value())
settings.setValue("message/check_missed/notify", self.cb_notify.isChecked()) settings.setValue("message/check_missed/notify", self.cb_notify.isChecked())
settings.setValue( settings.setValue("tray/notifications/click", self.cb_notification_click.isChecked())
"tray/notifications/click", self.cb_notification_click.isChecked()
)
settings.setValue("tray/icon/unread", self.cb_tray_icon_unread.isChecked()) settings.setValue("tray/icon/unread", self.cb_tray_icon_unread.isChecked())
# Interface # Interface
@@ -269,9 +237,7 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
settings.setValue("theme", selected_theme) settings.setValue("theme", selected_theme)
self.theme_change_requested.emit(selected_theme) self.theme_change_requested.emit(selected_theme)
settings.setValue( settings.setValue("MessageWidget/priority_color", self.cb_priority_colors.isChecked())
"MessageWidget/priority_color", self.cb_priority_colors.isChecked()
)
# Logging # Logging
selected_level = self.combo_logging.currentText() selected_level = self.combo_logging.currentText()
@@ -283,17 +249,9 @@ class SettingsDialog(QtWidgets.QDialog, Ui_Dialog):
logger.setLevel(selected_level) logger.setLevel(selected_level)
# Fonts # Fonts
settings.setValue( settings.setValue("MessageWidget/font/title", self.message_widget.label_title.font().toString())
"MessageWidget/font/title", settings.setValue("MessageWidget/font/date", self.message_widget.label_date.font().toString())
self.message_widget.label_title.font().toString(), settings.setValue("MessageWidget/font/message", self.message_widget.label_message.font().toString())
)
settings.setValue(
"MessageWidget/font/date", self.message_widget.label_date.font().toString()
)
settings.setValue(
"MessageWidget/font/message",
self.message_widget.label_message.font().toString(),
)
# Advanced # Advanced
settings.setValue("ImagePopup/enabled", self.groupbox_image_popup.isChecked()) settings.setValue("ImagePopup/enabled", self.groupbox_image_popup.isChecked())

View File

@@ -8,10 +8,10 @@ from functools import reduce
from PyQt6 import QtCore from PyQt6 import QtCore
from PyQt6.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from gotify_tray.database import Cache, Downloader, Settings from gotify_tray.database import Cache, Settings
from gotify_tray.gotify.api import GotifyClient from gotify_tray.gotify.api import GotifyClient
from gotify_tray.gotify.models import GotifyVersionModel from gotify_tray.gotify.models import GotifyVersionModel
from gotify_tray.utils import get_image from gotify_tray.utils import process_messages
from . import gotify from . import gotify
@@ -97,7 +97,7 @@ class GetApplicationsTask(BaseTask):
class GetApplicationMessagesTask(BaseTask): class GetApplicationMessagesTask(BaseTask):
success = pyqtSignal(gotify.GotifyPagedMessagesModel) message = pyqtSignal(gotify.GotifyMessageModel)
error = pyqtSignal(gotify.GotifyErrorModel) error = pyqtSignal(gotify.GotifyErrorModel)
def __init__(self, appid: int, gotify_client: gotify.GotifyClient): def __init__(self, appid: int, gotify_client: gotify.GotifyClient):
@@ -110,10 +110,16 @@ class GetApplicationMessagesTask(BaseTask):
if isinstance(result, gotify.GotifyErrorModel): if isinstance(result, gotify.GotifyErrorModel):
self.error.emit(result) self.error.emit(result)
else: else:
self.success.emit(result) for message in process_messages(result.messages):
self.message.emit(message)
# Prevent locking up the UI when there are a lot of messages ready at the same time
# -- side effect: switching application while the previous messages are still being inserted causes mixing of messages
time.sleep(0.001)
class GetMessagesTask(BaseTask): class GetMessagesTask(BaseTask):
message = pyqtSignal(gotify.GotifyMessageModel)
success = pyqtSignal(gotify.GotifyPagedMessagesModel) success = pyqtSignal(gotify.GotifyPagedMessagesModel)
error = pyqtSignal(gotify.GotifyErrorModel) error = pyqtSignal(gotify.GotifyErrorModel)
@@ -126,9 +132,22 @@ class GetMessagesTask(BaseTask):
if isinstance(result, gotify.GotifyErrorModel): if isinstance(result, gotify.GotifyErrorModel):
self.error.emit(result) self.error.emit(result)
else: else:
for message in process_messages(result.messages):
self.message.emit(message)
time.sleep(0.001)
self.success.emit(result) self.success.emit(result)
class ProcessMessageTask(BaseTask):
def __init__(self, message: gotify.GotifyMessageModel):
super(ProcessMessageTask, self).__init__()
self.message = message
def task(self):
for _ in process_messages([self.message]):
pass
class VerifyServerInfoTask(BaseTask): class VerifyServerInfoTask(BaseTask):
success = pyqtSignal(GotifyVersionModel) success = pyqtSignal(GotifyVersionModel)
incorrect_token = pyqtSignal(GotifyVersionModel) incorrect_token = pyqtSignal(GotifyVersionModel)
@@ -176,9 +195,7 @@ class ServerConnectionWatchdogTask(BaseTask):
time.sleep(settings.value("watchdog/interval/s", type=int)) time.sleep(settings.value("watchdog/interval/s", type=int))
if not self.gotify_client.is_listening(): if not self.gotify_client.is_listening():
self.closed.emit() self.closed.emit()
logger.debug( logger.debug("ServerConnectionWatchdogTask: gotify_client is not listening")
"ServerConnectionWatchdogTask: gotify_client is not listening"
)
class ExportSettingsTask(BaseTask): class ExportSettingsTask(BaseTask):
@@ -205,35 +222,6 @@ class ImportSettingsTask(BaseTask):
self.success.emit() self.success.emit()
class ProcessMessageTask(BaseTask):
def __init__(self, message: gotify.GotifyMessageModel):
super(ProcessMessageTask, self).__init__()
self.message = message
def task(self):
if image_url := get_image(self.message.message):
downloader = Downloader()
downloader.get_filename(image_url)
class ProcessMessagesTask(BaseTask):
message_processed = pyqtSignal(gotify.GotifyMessageModel)
def __init__(self, page: gotify.GotifyPagedMessagesModel):
super(ProcessMessagesTask, self).__init__()
self.page = page
def task(self):
downloader = Downloader()
for message in self.page.messages:
if image_url := get_image(message.message):
downloader.get_filename(image_url)
self.message_processed.emit(message)
# Prevent locking up the UI when there are a lot of messages with images ready at the same time
time.sleep(0.001)
class CacheSizeTask(BaseTask): class CacheSizeTask(BaseTask):
size = pyqtSignal(int) size = pyqtSignal(int)

View File

@@ -4,7 +4,10 @@ import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Iterator
from gotify_tray import gotify
from gotify_tray.database import Downloader
def verify_server(force_new: bool = False, enable_import: bool = True) -> bool: def verify_server(force_new: bool = False, enable_import: bool = True) -> bool:
@@ -28,6 +31,14 @@ def verify_server(force_new: bool = False, enable_import: bool = True) -> bool:
return True return True
def process_messages(messages: list[gotify.GotifyMessageModel]) -> Iterator[gotify.GotifyMessageModel]:
downloader = Downloader()
for message in messages:
if image_url := get_image(message.message):
downloader.get_filename(image_url)
yield message
def convert_links(text): def convert_links(text):
_link = re.compile( _link = re.compile(
r'(?:(https://|http://)|(www\.))(\S+\b/?)([!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}~]*)(\s|$)', r'(?:(https://|http://)|(www\.))(\S+\b/?)([!"#$%&\'()*+,\-./:;<=>?@[\\\]^_`{|}~]*)(\s|$)',
@@ -45,7 +56,7 @@ def convert_links(text):
return _link.sub(replace, text) return _link.sub(replace, text)
def get_image(s: str) -> Optional[str]: def get_image(s: str) -> str | None:
"""If `s` contains only an image URL, this function returns that URL. """If `s` contains only an image URL, this function returns that URL.
This function also extracts a URL in the `![](<url>)` markdown image format. This function also extracts a URL in the `![](<url>)` markdown image format.
""" """