// Drift row → model adapters and reverse-write adapters for populating // drift from REST responses (#357 plan C). // // The cache loses some server-derived fields: // - ArtistRef.coverUrl (server computes from most-recent album) // - AlbumRef.coverUrl (server-emitted derived path) // - TrackRef.streamUrl (could be reconstructed but kept empty for clarity) // UI already handles empty coverUrl/streamUrl gracefully. REST cold-cache // fallback briefly shows the real values before drift takes over. import 'package:drift/drift.dart' as drift; import '../models/album.dart'; import '../models/artist.dart'; import '../models/playlist.dart'; import '../models/track.dart'; import 'db.dart'; extension CachedArtistAdapter on CachedArtist { ArtistRef toRef() => ArtistRef( id: id, name: name, sortName: sortName, ); } extension ArtistRefDriftWrite on ArtistRef { CachedArtistsCompanion toDrift() => CachedArtistsCompanion.insert( id: id, name: name, sortName: sortName.isNotEmpty ? sortName : name, ); } extension CachedAlbumAdapter on CachedAlbum { /// `artistName` is supplied by the joined CachedArtists row at query time. /// `coverUrl` is reconstructed deterministically from the album id — /// the server emits the same shape (see internal/api/convert.go:69). /// We don't need to persist it, so AlbumRef.coverUrl is non-empty /// even when the row was populated from a sync that didn't carry the /// derived URL. AlbumRef toRef({String artistName = ''}) => AlbumRef( id: id, title: title, sortTitle: sortTitle, artistId: artistId, artistName: artistName, coverUrl: '/api/albums/$id/cover', ); } extension AlbumRefDriftWrite on AlbumRef { CachedAlbumsCompanion toDrift() => CachedAlbumsCompanion.insert( id: id, artistId: artistId, title: title, sortTitle: sortTitle.isNotEmpty ? sortTitle : title, ); } extension CachedTrackAdapter on CachedTrack { /// `artistName` and `albumTitle` come from joined rows. TrackRef toRef({String artistName = '', String albumTitle = ''}) => TrackRef( id: id, title: title, albumId: albumId, albumTitle: albumTitle, artistId: artistId, artistName: artistName, trackNumber: trackNumber, discNumber: discNumber, durationSec: durationMs ~/ 1000, ); } extension TrackRefDriftWrite on TrackRef { CachedTracksCompanion toDrift() => CachedTracksCompanion.insert( id: id, albumId: albumId, artistId: artistId, title: title, durationMs: drift.Value(durationSec * 1000), trackNumber: drift.Value(trackNumber), discNumber: drift.Value(discNumber), ); } extension CachedPlaylistAdapter on CachedPlaylist { /// `ownerUsername` is server-derived; cache stores the userId only. /// Pass empty unless a join supplies it. /// `coverUrl` is reconstructed deterministically from the playlist /// id — the server serves the cached collage at this path /// (handleGetPlaylistCover). Mirrors the album-cover trick. When /// the server hasn't built a collage yet (system playlists with no /// tracks at build time), the endpoint 404s and PlaylistCard's /// ServerImage falls back to its slate placeholder. Playlist toRef({String ownerUsername = ''}) => Playlist( id: id, userId: userId, name: name, description: description, isPublic: isPublic, systemVariant: systemVariant, trackCount: trackCount, coverUrl: '/api/playlists/$id/cover', ownerUsername: ownerUsername, createdAt: '', updatedAt: '', ); } extension PlaylistDriftWrite on Playlist { CachedPlaylistsCompanion toDrift() => CachedPlaylistsCompanion.insert( id: id, userId: userId, name: name, description: drift.Value(description), isPublic: drift.Value(isPublic), trackCount: drift.Value(trackCount), systemVariant: drift.Value(systemVariant), ); }