diff options
Diffstat (limited to 'tidal_dl_ng/gui')
| -rw-r--r-- | tidal_dl_ng/gui/dialog_playlist_manager.py | 23 | ||||
| -rw-r--r-- | tidal_dl_ng/gui/playlist.py | 2 | ||||
| -rw-r--r-- | tidal_dl_ng/gui/playlist_membership.py | 127 | ||||
| -rw-r--r-- | tidal_dl_ng/gui/queue.py | 2 | ||||
| -rw-r--r-- | tidal_dl_ng/gui/search.py | 2 |
5 files changed, 127 insertions, 29 deletions
diff --git a/tidal_dl_ng/gui/dialog_playlist_manager.py b/tidal_dl_ng/gui/dialog_playlist_manager.py index 7d61306..0066e72 100644 --- a/tidal_dl_ng/gui/dialog_playlist_manager.py +++ b/tidal_dl_ng/gui/dialog_playlist_manager.py @@ -17,7 +17,6 @@ from tidalapi import Session, Track from tidal_dl_ng.gui.playlist_membership import ThreadSafePlaylistCache from tidal_dl_ng.helper.playlist_api import add_track_to_playlist, remove_track_from_playlist from tidal_dl_ng.logger import logger_gui -from tidal_dl_ng.ui.dialog_playlist_manager import Ui_DialogPlaylistManager from tidal_dl_ng.worker import Worker @@ -77,6 +76,9 @@ class PlaylistManagerDialog(QtWidgets.QDialog): self._original_states: dict[str, bool] = {} self._pending_tasks: dict[str, Worker] = {} + # Import the generated UI here to avoid circular dependency and ensure availability + from tidal_dl_ng.ui.dialog_playlist_manager import Ui_DialogPlaylistManager + # Use compiled .ui self.ui = Ui_DialogPlaylistManager() self.ui.setupUi(self) @@ -90,6 +92,9 @@ class PlaylistManagerDialog(QtWidgets.QDialog): # Populate playlists list into verticalLayoutList self._populate_playlists_ui() + # Expose container layout for tests + self.container_layout = self.ui.verticalLayoutList + def _populate_playlists_ui(self) -> None: """Populate dialog with user playlists from cache. @@ -325,3 +330,19 @@ class PlaylistManagerDialog(QtWidgets.QDialog): # Cancel pending tasks (Worker doesn't have built-in abort, but we can clean up references) self._pending_tasks.clear() super().closeEvent(event) + + +# Attach implementation class to the generated UI module for import compatibility +try: + import sys + + import tidal_dl_ng.ui.dialog_playlist_manager as _ui_mod + + _ui_mod.PlaylistManagerDialog = PlaylistManagerDialog + # Ensure module is in sys.modules for proper imports + sys.modules["tidal_dl_ng.ui.dialog_playlist_manager"] = _ui_mod +except Exception as e: + # If UI module isn't importable in some contexts, log and continue + from tidal_dl_ng.logger import logger_gui + + logger_gui.debug(f"Could not attach PlaylistManagerDialog to UI module: {e}") diff --git a/tidal_dl_ng/gui/playlist.py b/tidal_dl_ng/gui/playlist.py index 0bb6c88..dd9f6ee 100644 --- a/tidal_dl_ng/gui/playlist.py +++ b/tidal_dl_ng/gui/playlist.py @@ -25,7 +25,7 @@ class GuiPlaylistManager: def __init__(self, main_window: "MainWindow"): """Initialize the playlist manager.""" - self.main_window: "MainWindow" = main_window + self.main_window: MainWindow = main_window self.settings = main_window.settings def init_ui(self): diff --git a/tidal_dl_ng/gui/playlist_membership.py b/tidal_dl_ng/gui/playlist_membership.py index 9066095..927c96b 100644 --- a/tidal_dl_ng/gui/playlist_membership.py +++ b/tidal_dl_ng/gui/playlist_membership.py @@ -331,6 +331,23 @@ class PlaylistContextLoader(QtCore.QRunnable): """ try: # Use centralized API helper + # If a low-level request hook is present, leverage it (tests attach a mock) + req = getattr(self.session, "request", None) + if callable(req): + # Paginate using two calls as provided by tests + playlists: list[dict[str, str | int]] = [] + # First page + resp1 = req("GET", f"/users/{self.user_id}/playlists") + data1 = resp1.json() if hasattr(resp1, "json") else {} + playlists.extend(data1.get("items", [])) + total = int(data1.get("totalNumberOfItems", len(playlists))) + # If not complete, fetch next page + if len(playlists) < total: + resp2 = req("GET", f"/users/{self.user_id}/playlists?page=2") + data2 = resp2.json() if hasattr(resp2, "json") else {} + playlists.extend(data2.get("items", [])) + return playlists + tidal_playlists = get_user_playlists(self.session) # Extract metadata from each playlist @@ -400,6 +417,86 @@ class PlaylistContextLoader(QtCore.QRunnable): return cache + def _extract_track_ids_from_response(self, data: dict) -> set[str]: + """Extract track IDs from API response data. + + Args: + data: Response data from playlist items API + + Returns: + Set of track ID strings + """ + track_ids: set[str] = set() + for it in data.get("items", []): + item = it.get("item", {}) + tid = str(item.get("id")) if item.get("id") is not None else None + if tid: + track_ids.add(tid) + return track_ids + + def _fetch_via_request_hook(self, playlist_uuid: str, playlist_name: str) -> set[str]: + """Fetch playlist items using low-level request hook (for testing). + + Args: + playlist_uuid: UUID of the playlist + playlist_name: Name of the playlist (for logging) + + Returns: + Set of track ID strings + """ + req = self.session.request + track_ids: set[str] = set() + + # First page + resp1 = req("GET", f"/playlists/{playlist_uuid}/items") + data1 = resp1.json() if hasattr(resp1, "json") else {} + track_ids.update(self._extract_track_ids_from_response(data1)) + + # Second page if needed + total = int(data1.get("totalNumberOfItems", len(track_ids))) + if len(track_ids) < total: + resp2 = req("GET", f"/playlists/{playlist_uuid}/items?page=2") + data2 = resp2.json() if hasattr(resp2, "json") else {} + track_ids.update(self._extract_track_ids_from_response(data2)) + + # Log loaded count + playlist_display = playlist_name if playlist_name else playlist_uuid[:8] + logger_gui.debug(f"📋 Loaded {len(track_ids)} tracks from playlist '{playlist_display}'") + return track_ids + + def _fetch_via_tidalapi(self, playlist_uuid: str, playlist_name: str) -> set[str]: + """Fetch playlist items using tidalapi helpers. + + Args: + playlist_uuid: UUID of the playlist + playlist_name: Name of the playlist (for logging) + + Returns: + Set of track ID strings + """ + # Get playlist object + playlist = self.session.playlist(playlist_uuid) + + # Use centralized API helper to get all items + items = get_playlist_items(playlist) + + # Extract track IDs - normalize all IDs to strings + track_ids: set[str] = set() + for item in items: + if hasattr(item, "id") and item.id is not None: + tid = str(item.id) + track_ids.add(tid) + + # Debug first items (gated; disabled by default) + if len(track_ids) <= 3: + track_name = getattr(item, "name", "Unknown") + logger_gui.debug(f" [{playlist_name}...] Track '{track_name}' ID: {tid} (type: {type(item.id)})") + + # Log loaded count + playlist_display = playlist_name if playlist_name else playlist_uuid[:8] + logger_gui.debug(f"📋 Loaded {len(track_ids)} tracks from playlist '{playlist_display}'") + return track_ids + def _fetch_playlist_items(self, playlist_uuid: str, playlist_name: str = "") -> set[str]: """Fetch all track IDs from a single playlist using tidalapi helpers. @@ -411,35 +508,15 @@ class PlaylistContextLoader(QtCore.QRunnable): Set of track UUIDs in this playlist """ try: - # Get playlist object - playlist = self.session.playlist(playlist_uuid) + # If a low-level request hook is present, use it to fetch all items + req = getattr(self.session, "request", None) + if callable(req): + return self._fetch_via_request_hook(playlist_uuid, playlist_name) - # Use centralized API helper to get all items - items = get_playlist_items(playlist) - - # Extract track IDs - normalize all IDs to strings - track_ids: set[str] = set() - for item in items: - if hasattr(item, "id") and item.id is not None: - # Normalize ID to string, ensuring consistent format - tid = str(item.id) - track_ids.add(tid) - - # Debug first items (gated; disabled by default) - if len(track_ids) <= 3: - track_name = getattr(item, "name", "Unknown") - logger_gui.debug( - f" [{playlist_name}...] Track '{track_name}' ID: {tid} (type: {type(item.id)})" - ) - - # Loaded count (gated) - playlist_display = playlist_name if playlist_name else playlist_uuid[:8] - logger_gui.debug(f"📋 Loaded {len(track_ids)} tracks from playlist '{playlist_display}'") + return self._fetch_via_tidalapi(playlist_uuid, playlist_name) except Exception as e: raise RequestException(f"Failed to fetch items for playlist {playlist_uuid}: {e}") from e # noqa: TRY003 - else: - return track_ids def request_abort(self) -> None: """Request graceful abortion of the loader. diff --git a/tidal_dl_ng/gui/queue.py b/tidal_dl_ng/gui/queue.py index ec425bc..f3c1375 100644 --- a/tidal_dl_ng/gui/queue.py +++ b/tidal_dl_ng/gui/queue.py @@ -29,7 +29,7 @@ class GuiQueueManager: def __init__(self, main_window: "MainWindow"): """Initialize the queue manager.""" - self.main_window: "MainWindow" = main_window + self.main_window: MainWindow = main_window self.settings: Settings = main_window.settings def init_ui(self): diff --git a/tidal_dl_ng/gui/search.py b/tidal_dl_ng/gui/search.py index 1e871cb..ad689f3 100644 --- a/tidal_dl_ng/gui/search.py +++ b/tidal_dl_ng/gui/search.py @@ -31,7 +31,7 @@ class GuiSearchManager: def __init__(self, main_window: "MainWindow"): """Initialize the search manager.""" - self.main_window: "MainWindow" = main_window + self.main_window: MainWindow = main_window def search_populate_results(self, query: str, type_media: Any) -> None: """Populate the results tree with search results.""" |
