Merge pull request #461 from exislow/423-long-unresponsive-hangs-on-cover-loading

👷 Lists and cover will be loaded in background. Gui won't hang anymore. Fixes #423
This commit is contained in:
exislow
2025-06-29 20:34:38 +02:00
committed by GitHub
2 changed files with 72 additions and 30 deletions
+1 -3
View File
@@ -761,9 +761,7 @@ class Download:
is_album: bool = isinstance(media, Album) is_album: bool = isinstance(media, Album)
# TODO: Refactor strings to constants (also in cfg.py) # TODO: Refactor strings to constants (also in cfg.py)
sort_by_track_num: bool = ( sort_by_track_num: bool = bool("album_track_num" in file_name_relative or "list_pos" in file_name_relative)
True if "album_track_num" in file_name_relative or "list_pos" in file_name_relative else False
)
result_dirs: [pathlib.Path] = [] result_dirs: [pathlib.Path] = []
list_total: int = len(items) list_total: int = len(items)
+58 -14
View File
@@ -111,7 +111,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
dl: Download dl: Download
threadpool: QtCore.QThreadPool threadpool: QtCore.QThreadPool
tray: QtWidgets.QSystemTrayIcon tray: QtWidgets.QSystemTrayIcon
spinner: QtWaitingSpinner spinners: dict
cover_url_current: str = "" cover_url_current: str = ""
shutdown: bool = False shutdown: bool = False
model_tr_results: QtGui.QStandardItemModel = QtGui.QStandardItemModel() model_tr_results: QtGui.QStandardItemModel = QtGui.QStandardItemModel()
@@ -150,6 +150,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.settings = Settings() self.settings = Settings()
self._init_threads() self._init_threads()
self._init_gui()
self._init_tree_results_model(self.model_tr_results) self._init_tree_results_model(self.model_tr_results)
self._init_tree_results(self.tr_results, self.model_tr_results) self._init_tree_results(self.tr_results, self.model_tr_results)
self._init_tree_lists(self.tr_lists_user) self._init_tree_lists(self.tr_lists_user)
@@ -166,6 +167,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
logger_gui.debug("All setup.") logger_gui.debug("All setup.")
def _init_gui(self):
self.spinners = {}
def init_tidal(self, tidal: Tidal = None): def init_tidal(self, tidal: Tidal = None):
result: bool = False result: bool = False
@@ -413,13 +417,29 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
item["element"].setCurrentIndex(item["default_id"]) item["element"].setCurrentIndex(item["default_id"])
def on_spinner_start(self, parent: QtWidgets.QWidget): def on_spinner_start(self, parent: QtWidgets.QWidget):
self.spinner = QtWaitingSpinner(parent, True, True) # Stop any existing spinner for this parent
self.spinner.setColor(QtGui.QColor(255, 255, 255)) if parent in self.spinners:
self.spinner.start() spinner = self.spinners[parent]
spinner.stop()
spinner.deleteLater()
del self.spinners[parent]
# Create and start a new spinner for this parent
spinner = QtWaitingSpinner(parent, True, True)
spinner.setColor(QtGui.QColor(255, 255, 255))
spinner.start()
self.spinners[parent] = spinner
def on_spinner_stop(self): def on_spinner_stop(self):
self.spinner.stop() # Stop all spinners
self.spinner = None for spinner in list(self.spinners.values()):
spinner.stop()
spinner.deleteLater()
self.spinners.clear()
def menu_context_tree_lists(self, point: QtCore.QPoint): def menu_context_tree_lists(self, point: QtCore.QPoint):
# Infos about the node selected. # Infos about the node selected.
@@ -833,40 +853,59 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.tidal.settings_apply() self.tidal.settings_apply()
def on_list_items_show(self, item: QtWidgets.QTreeWidgetItem): def on_list_items_show(self, item: QtWidgets.QTreeWidgetItem):
self.thread_it(self.list_items_show, item)
def list_items_show(self, item: QtWidgets.QTreeWidgetItem):
media_list: Album | Playlist | str = get_user_list_media_item(item) media_list: Album | Playlist | str = get_user_list_media_item(item)
# Only if clicked item is not a top level item. # Only if clicked item is not a top level item.
if media_list: if media_list:
# Show spinner while loading list
self.s_spinner_start.emit(self.tr_results)
try:
if isinstance(media_list, str) and media_list.startswith("fav_"): if isinstance(media_list, str) and media_list.startswith("fav_"):
function_list = favorite_function_factory(self.tidal, media_list) function_list = favorite_function_factory(self.tidal, media_list)
self.list_items_show_result(favorite_function=function_list) self.list_items_show_result(favorite_function=function_list)
else: else:
self.list_items_show_result(media_list) self.list_items_show_result(media_list)
self.cover_show(media_list) # Load cover asynchronously to avoid blocking the GUI
self.thread_it(self.cover_show, media_list)
finally:
self.s_spinner_stop.emit()
def on_result_item_clicked(self, index: QtCore.QModelIndex) -> None: def on_result_item_clicked(self, index: QtCore.QModelIndex) -> None:
media: Track | Video | Album | Artist = get_results_media_item( media: Track | Video | Album | Artist = get_results_media_item(
index, self.proxy_tr_results, self.model_tr_results index, self.proxy_tr_results, self.model_tr_results
) )
self.cover_show(media) # Load cover asynchronously to avoid blocking the GUI
self.thread_it(self.cover_show, media)
def on_queue_download_item_clicked(self, item: QtWidgets.QTreeWidgetItem, column: int) -> None: def on_queue_download_item_clicked(self, item: QtWidgets.QTreeWidgetItem, column: int) -> None:
media: Track | Video | Album | Artist | Mix | Playlist = get_queue_download_media(item) media: Track | Video | Album | Artist | Mix | Playlist = get_queue_download_media(item)
self.cover_show(media) # Load cover asynchronously to avoid blocking the GUI
self.thread_it(self.cover_show, media)
def cover_show(self, media: Album | Playlist | Track | Video | Album | Artist) -> None: def cover_show(self, media: Album | Playlist | Track | Video | Album | Artist) -> None:
cover_url: str cover_url: str = ""
# Show spinner in the cover label itself
parent_widget = self.l_pm_cover
# Show spinner while loading
self.s_spinner_start.emit(parent_widget)
try:
try: try:
cover_url = media.album.image() cover_url = media.album.image()
except: except Exception:
# Only call image() if it exists
if hasattr(media, "image") and callable(getattr(media, "image", None)):
try: try:
cover_url = media.image() cover_url = media.image()
except: except Exception:
logger_gui.info(f"No cover available (media ID: {media.id}).") logger_gui.info(f"No cover available (media ID: {getattr(media, 'id', 'unknown')}).")
else:
logger_gui.info(f"No cover available (media ID: {getattr(media, 'id', 'unknown')}).")
if cover_url and self.cover_url_current != cover_url: if cover_url and self.cover_url_current != cover_url:
self.cover_url_current = cover_url self.cover_url_current = cover_url
@@ -874,6 +913,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
pixmap: QtGui.QPixmap = QtGui.QPixmap() pixmap: QtGui.QPixmap = QtGui.QPixmap()
pixmap.loadFromData(data_cover) pixmap.loadFromData(data_cover)
self.l_pm_cover.setPixmap(pixmap) self.l_pm_cover.setPixmap(pixmap)
else:
path_image: str = resource_path("tidal_dl_ng/ui/default_album_image.png")
self.l_pm_cover.setPixmap(QtGui.QPixmap(path_image))
finally:
self.s_spinner_stop.emit()
def list_items_show_result( def list_items_show_result(
self, self,