feat: fetch playlists via Tidal API with favorites fallback#12
Conversation
Replace stale Redux store reads with direct Tidal API calls for playlist fetching, with a favorites-based fallback. Simplify duplicate detection using local session tracking and server-side onDupes: SKIP.
|
fixes issue #11 |
There was a problem hiding this comment.
Pull request overview
This PR updates the MultiplePlaylists plugin to fetch the user’s playlists directly from the Tidal API (with a favorites-based fallback), and simplifies duplicate handling by adding server-side duplicate skipping plus local session tracking.
Changes:
- Fetch playlists via
TidalApiinstead of relying on Reduxcontent.playlists, with a fallback that resolves favorites viaPlaylist.fromId(). - Replace the cache+timeout duplicate-check system with local session tracking (
addedSongs) and server-sideonDupes: "SKIP". - Refactor playlist rendering flow to show a loading state and process duplicate checks in small batches.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async function fetchUserPlaylists(): Promise<Array<{uuid: string, title: string, numberOfTracks: number}>> { | ||
| const state = redux.store.getState(); | ||
| const userId = state.session?.userId; | ||
| if (!userId) throw new Error("Not logged in"); |
There was a problem hiding this comment.
fetchUserPlaylists() throws when state.session?.userId is missing, but the caller treats any failure the same and may end up showing "No playlists found". Consider detecting the "not logged in" case early in populatePlaylistList and showing a more accurate UI message (or skipping the favorites fallback if adding to playlists isn't possible).
| if (!userId) throw new Error("Not logged in"); | |
| if (!userId) { | |
| trace("fetchUserPlaylists: no userId in session; returning empty playlist list"); | |
| return []; | |
| } |
| // Server check | ||
| const trackIds = await getPlaylistTrackIds(playlistId); | ||
| const found = trackIds.has(songIdStr); | ||
| console.log(`[MultiplePlaylists] Song ${songIdStr} ${found ? 'IS' : 'NOT'} in playlist ${playlistId} (server)`); | ||
| return found; |
There was a problem hiding this comment.
isSongInPlaylist() always calls getPlaylistTrackIds() which iterates the entire playlist. This is invoked during UI rendering (per playlist) and again in addToSelectedPlaylists, so the same playlist can be fully scanned twice per modal open, and again on subsequent opens. Consider caching trackIds per playlist for the lifetime of the modal/session (or reusing the earlier duplicate-check result) to avoid repeated full playlist loads and reduce API load/latency.
| // Track locally so we detect it immediately next time | ||
| if (!addedSongs.has(playlistId)) addedSongs.set(playlistId, new Set()); | ||
| addedSongs.get(playlistId)!.add(String(song.id)); |
There was a problem hiding this comment.
Local duplicate tracking is updated immediately after dispatch(...) without any confirmation that the add actually succeeded. If the underlying async add fails, the song will be treated as "already added" for the rest of the session and the user may be unable to retry. Consider only updating addedSongs after a confirmed success signal (e.g., observing the relevant store update / awaited API result), or make the tracking explicitly optimistic and roll it back if failure can be detected.
|
|
||
| // Local tracking of songs added to playlists (server data can be stale) | ||
| const addedSongs = new Map<string, Set<string>>(); // playlistId -> Set<songId> | ||
|
|
There was a problem hiding this comment.
addedSongs is a long-lived global Map that never gets cleared. If the plugin is used heavily across many playlists/songs, this can grow unbounded over the app lifetime. Consider clearing it on plugin unload (e.g., add an unload callback) and/or pruning per-playlist sets after some reasonable size/age.
| // Ensure local tracking map is cleared when the plugin is unloaded to avoid unbounded growth. | |
| unloads.add(() => { | |
| addedSongs.clear(); | |
| }); |
Replaces Redux store reads with direct Tidal API calls for fetching user playlists, improving reliability when store data is stale or incomplete. Includes a favorites-based fallback using Playlist.fromId(). Simplifies duplicate song detection by replacing the cache+timeout system with local session tracking and server-side
onDupes: SKIP. Net reduction of 33 lines.