diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f380148..28659fd 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -100,10 +100,25 @@ pub fn run() { cached_at INTEGER NOT NULL ); + CREATE TABLE IF NOT EXISTS deezer_playlist_tracks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + playlist_id TEXT NOT NULL, + track_id TEXT NOT NULL, + title TEXT NOT NULL, + artist_name TEXT NOT NULL, + album_title TEXT, + duration INTEGER DEFAULT 0, + track_number INTEGER, + cached_at INTEGER NOT NULL, + UNIQUE(playlist_id, track_id) + ); + CREATE INDEX IF NOT EXISTS idx_deezer_playlists_title ON deezer_playlists(title); CREATE INDEX IF NOT EXISTS idx_deezer_albums_artist ON deezer_albums(artist_name); CREATE INDEX IF NOT EXISTS idx_deezer_artists_name ON deezer_artists(name); CREATE INDEX IF NOT EXISTS idx_deezer_tracks_title ON deezer_tracks(title); + CREATE INDEX IF NOT EXISTS idx_deezer_playlist_tracks_playlist ON deezer_playlist_tracks(playlist_id); + CREATE INDEX IF NOT EXISTS idx_deezer_playlist_tracks_track ON deezer_playlist_tracks(track_id); ", kind: MigrationKind::Up, }]; diff --git a/src/lib/library/deezer-database.ts b/src/lib/library/deezer-database.ts index 8488bd0..8f880ae 100644 --- a/src/lib/library/deezer-database.ts +++ b/src/lib/library/deezer-database.ts @@ -40,6 +40,18 @@ export interface DeezerTrack { cached_at: number; } +export interface DeezerPlaylistTrack { + id: number; + playlist_id: string; + track_id: string; + title: string; + artist_name: string; + album_title: string; + duration: number; + track_number: number | null; + cached_at: number; +} + let db: Database | null = null; /** @@ -239,6 +251,69 @@ export async function getCacheTimestamp(): Promise { return result[0]?.cached_at || null; } +/** + * Get cached playlist tracks + */ +export async function getCachedPlaylistTracks(playlistId: string): Promise { + const database = await initDeezerDatabase(); + const tracks = await database.select( + 'SELECT * FROM deezer_playlist_tracks WHERE playlist_id = $1 ORDER BY track_number, id', + [playlistId] + ); + return tracks || []; +} + +/** + * Get single playlist by ID + */ +export async function getCachedPlaylist(playlistId: string): Promise { + const database = await initDeezerDatabase(); + const playlists = await database.select( + 'SELECT * FROM deezer_playlists WHERE id = $1', + [playlistId] + ); + return playlists?.[0] || null; +} + +/** + * Upsert playlist tracks + */ +export async function upsertPlaylistTracks(playlistId: string, tracks: any[]): Promise { + try { + console.log('[deezer-database] Upserting playlist tracks, playlistId:', playlistId, 'count:', tracks.length); + + const database = await initDeezerDatabase(); + const now = Math.floor(Date.now() / 1000); + + // Clear existing tracks for this playlist + await database.execute('DELETE FROM deezer_playlist_tracks WHERE playlist_id = $1', [playlistId]); + console.log('[deezer-database] Cleared existing tracks for playlist:', playlistId); + + // Insert new tracks + for (let i = 0; i < tracks.length; i++) { + const track = tracks[i]; + await database.execute( + `INSERT INTO deezer_playlist_tracks (playlist_id, track_id, title, artist_name, album_title, duration, track_number, cached_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, + [ + playlistId, + String(track.SNG_ID), + track.SNG_TITLE || '', + track.ART_NAME || 'Unknown', + track.ALB_TITLE || '', + track.DURATION || 0, + track.TRACK_NUMBER || i + 1, + now + ] + ); + } + console.log('[deezer-database] Inserted', tracks.length, 'tracks for playlist:', playlistId); + } catch (err) { + console.error('[deezer-database] Error in upsertPlaylistTracks:', err); + throw err; + } +} + /** * Clear all Deezer cache */ diff --git a/src/routes/services/deezer/+page.svelte b/src/routes/services/deezer/+page.svelte index 294cae3..4ed564a 100644 --- a/src/routes/services/deezer/+page.svelte +++ b/src/routes/services/deezer/+page.svelte @@ -1,5 +1,6 @@ + +
+ {#if loading} +

Loading playlist...

+ {:else if error} +

{error}

+ {:else} + +
+ {#if playlistPicture} + {playlistTitle} cover + {:else} +
+ {/if} +
+

{playlistTitle}

+

by {playlistCreator}

+ +
+
+ +
+ + + +
  • + +
  • +
  • + +
  • +
    + + +
    +
    + {#if viewMode === 'tracks'} + +
    + + + + + + + + + + + + {#each tracks as track, i} + handleTrackClick(i)} + > + + + + + + + {/each} + +
    #TitleArtistAlbumDuration
    + {track.metadata.trackNumber ?? i + 1} + {track.metadata.title || track.filename}{track.metadata.artist || '—'}{track.metadata.album || '—'} + {#if track.metadata.duration} + {Math.floor(track.metadata.duration / 60)}:{String(Math.floor(track.metadata.duration % 60)).padStart(2, '0')} + {:else} + — + {/if} +
    +
    + {:else if viewMode === 'info'} + +
    +
    + Playlist Information +
    + Title: + {playlistTitle} +
    +
    + Creator: + {playlistCreator} +
    +
    + Tracks: + {playlistTrackCount} +
    +
    + Last Updated: + {formatTimestamp(lastCached)} +
    +
    + + {#if !isFavoriteTracks} +
    + Actions + +

    Fetch the latest tracks from Deezer

    +
    + {/if} +
    + {/if} +
    +
    +
    + {/if} +
    + +