Simplify the inserting and processing of messages.

Message widgets are now inserted into the listView through the `rowsInserted` callback of the messages model.

Messages are processed in the GetMessagesTask and GetApplicationMessagesTask when fetching multiple new messages. Single new incoming messages are processed in ProcessMessageTask.
This commit is contained in:
dries.k
2023-05-13 18:13:21 +02:00
parent bc221d6c8f
commit 7d47c79898
7 changed files with 105 additions and 151 deletions

View File

@@ -18,7 +18,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
@@ -153,9 +152,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:
@@ -184,49 +183,6 @@ 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: Union[ApplicationModelItem, ApplicationAllMessagesItem]
): ):
@@ -237,62 +193,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(
0,
message,
application_item.data(
ApplicationItemDataRole.ApplicationRole
),
)
elif isinstance(
selected_application_item, ApplicationAllMessagesItem
): ):
self.messages_model.insert_message(0, message)
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
),
)
if process:
self.process_message_task = ProcessMessageTask(message) self.process_message_task = ProcessMessageTask(message)
self.process_message_task.finished.connect(insert_message_helper) self.process_message_task.finished.connect(insert_message_helper)
self.process_message_task.start() self.process_message_task.start()
else: else:
logger.error( insert_message_helper()
f"App id {message.appid} could not be found. Refreshing applications." else:
) logger.error(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 +239,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
@@ -438,6 +372,8 @@ 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.messages_model.rowsInserted.connect(self.main_window.display_message_widgets)
self.watchdog.closed.connect(lambda: self.listener_closed_callback(None, None)) self.watchdog.closed.connect(lambda: self.listener_closed_callback(None, None))
def init_shortcuts(self): def init_shortcuts(self):

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

@@ -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,18 +106,21 @@ 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 = MessageWidget(self.app, self.listView_messages, message_item, icon=application_item.icon())
message_widget.deletion_requested.connect(self.delete_message.emit) message_widget.deletion_requested.connect(self.delete_message.emit)
message_widget.image_popup.connect(self.image_popup.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,9 +23,9 @@ 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,
): ):
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)
@@ -59,15 +59,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()

View File

@@ -118,7 +118,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)

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)
@@ -205,35 +224,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, List, Optional
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|$)',