diff --git a/backend/database/sql/schemas/_libraries.sql b/backend/database/sql/schemas/_libraries.sql new file mode 100644 index 0000000..66aa163 --- /dev/null +++ b/backend/database/sql/schemas/_libraries.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS libraries ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + path TEXT NOT NULL UNIQUE, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/backend/database/sql/schemas/audio_files.sql b/backend/database/sql/schemas/audio_files.sql index 4f3436c..29489da 100644 --- a/backend/database/sql/schemas/audio_files.sql +++ b/backend/database/sql/schemas/audio_files.sql @@ -10,9 +10,14 @@ CREATE TABLE IF NOT EXISTS audio_files ( bitrate int NOT NULL DEFAULT 0, file_size int NOT NULL DEFAULT 0, basename text NOT NULL DEFAULT '', + library_id int NOT NULL DEFAULT 0, FOREIGN KEY(file_type_id) REFERENCES file_types(id), - FOREIGN KEY(recording_id) REFERENCES recordings(id) + FOREIGN KEY(recording_id) REFERENCES recordings(id), + FOREIGN KEY(library_id) REFERENCES libraries(id) ); CREATE INDEX IF NOT EXISTS idx_audio_files_recording_id ON audio_files(recording_id); + +CREATE INDEX IF NOT EXISTS idx_audio_files_library_id + ON audio_files(library_id); diff --git a/backend/database/sql/schemas/playlist_tracks.sql b/backend/database/sql/schemas/playlist_tracks.sql index 0d0bb9d..eea4d89 100644 --- a/backend/database/sql/schemas/playlist_tracks.sql +++ b/backend/database/sql/schemas/playlist_tracks.sql @@ -1,10 +1,16 @@ CREATE TABLE IF NOT EXISTS playlist_tracks ( id INTEGER PRIMARY KEY, playlist_id INTEGER NOT NULL, - audio_file_id INTEGER NOT NULL, + audio_file_id INTEGER, position INTEGER NOT NULL, + phantom_title TEXT, + phantom_artist TEXT, + phantom_album TEXT, + phantom_duration_ms INTEGER, + phantom_genre TEXT, + phantom_cover_art_path TEXT, FOREIGN KEY(playlist_id) REFERENCES playlists(id) ON DELETE CASCADE, - FOREIGN KEY(audio_file_id) REFERENCES audio_files(id) ON DELETE CASCADE + FOREIGN KEY(audio_file_id) REFERENCES audio_files(id) ON DELETE SET NULL ); CREATE INDEX IF NOT EXISTS idx_playlist_tracks_playlist_id diff --git a/backend/database/sql/schemas/track_metadata_view.sql b/backend/database/sql/schemas/track_metadata_view.sql index 68f0879..703aa8f 100644 --- a/backend/database/sql/schemas/track_metadata_view.sql +++ b/backend/database/sql/schemas/track_metadata_view.sql @@ -22,7 +22,8 @@ SELECT af.bit_depth, af.channels, af.bitrate, - af.file_size + af.file_size, + af.library_id FROM audio_files af LEFT JOIN recordings r ON af.recording_id = r.id LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id diff --git a/backend/database/sql/sqlcgen/audio_files.sql.go b/backend/database/sql/sqlcgen/audio_files.sql.go index ca8f676..2057579 100644 --- a/backend/database/sql/sqlcgen/audio_files.sql.go +++ b/backend/database/sql/sqlcgen/audio_files.sql.go @@ -24,7 +24,7 @@ func (q *Queries) CountAudioFiles(ctx context.Context) (int64, error) { const createAudioFile = `-- name: CreateAudioFile :one INSERT INTO audio_files (file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) -RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename +RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id ` type CreateAudioFileParams struct { @@ -66,6 +66,7 @@ func (q *Queries) CreateAudioFile(ctx context.Context, arg CreateAudioFileParams &i.Bitrate, &i.FileSize, &i.Basename, + &i.LibraryID, ) return i, err } @@ -122,7 +123,7 @@ func (q *Queries) GetAllAudioFilePaths(ctx context.Context) ([]GetAllAudioFilePa } const getAllAudioFiles = `-- name: GetAllAudioFiles :many -SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename FROM audio_files +SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files ` func (q *Queries) GetAllAudioFiles(ctx context.Context) ([]AudioFile, error) { @@ -146,6 +147,7 @@ func (q *Queries) GetAllAudioFiles(ctx context.Context) ([]AudioFile, error) { &i.Bitrate, &i.FileSize, &i.Basename, + &i.LibraryID, ); err != nil { return nil, err } @@ -307,7 +309,7 @@ func (q *Queries) GetAllTracksWithFullMetadata(ctx context.Context) ([]GetAllTra } const getAudioFile = `-- name: GetAudioFile :one -SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename FROM audio_files +SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files WHERE id = ? LIMIT 1 ` @@ -326,12 +328,13 @@ func (q *Queries) GetAudioFile(ctx context.Context, id int64) (AudioFile, error) &i.Bitrate, &i.FileSize, &i.Basename, + &i.LibraryID, ) return i, err } const getAudioFileByPath = `-- name: GetAudioFileByPath :one -SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename FROM audio_files +SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files WHERE file_path = ? LIMIT 1 ` @@ -350,6 +353,7 @@ func (q *Queries) GetAudioFileByPath(ctx context.Context, filePath string) (Audi &i.Bitrate, &i.FileSize, &i.Basename, + &i.LibraryID, ) return i, err } @@ -448,7 +452,7 @@ func (q *Queries) GetAudioFilesByReleaseGroup(ctx context.Context, releaseGroupI } const getAudioFilesNeedingMetadata = `-- name: GetAudioFilesNeedingMetadata :many -SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename FROM audio_files +SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files WHERE recording_id = 0 ` @@ -473,6 +477,7 @@ func (q *Queries) GetAudioFilesNeedingMetadata(ctx context.Context) ([]AudioFile &i.Bitrate, &i.FileSize, &i.Basename, + &i.LibraryID, ); err != nil { return nil, err } diff --git a/backend/database/sql/sqlcgen/models.go b/backend/database/sql/sqlcgen/models.go index 6a40a57..ea86716 100644 --- a/backend/database/sql/sqlcgen/models.go +++ b/backend/database/sql/sqlcgen/models.go @@ -37,6 +37,7 @@ type AudioFile struct { Bitrate int64 FileSize int64 Basename string + LibraryID int64 } type CoverArt struct { @@ -56,6 +57,13 @@ type Genre struct { Name string } +type Library struct { + ID int64 + Name string + Path string + CreatedAt time.Time +} + type PlayerState struct { ID int64 Volume int64 @@ -72,10 +80,16 @@ type Playlist struct { } type PlaylistTrack struct { - ID int64 - PlaylistID int64 - AudioFileID int64 - Position int64 + ID int64 + PlaylistID int64 + AudioFileID sql.NullInt64 + Position int64 + PhantomTitle sql.NullString + PhantomArtist sql.NullString + PhantomAlbum sql.NullString + PhantomDurationMs sql.NullInt64 + PhantomGenre sql.NullString + PhantomCoverArtPath sql.NullString } type Queue struct { @@ -155,4 +169,5 @@ type TrackMetadatum struct { Channels int64 Bitrate int64 FileSize int64 + LibraryID int64 } diff --git a/backend/database/sql/sqlcgen/playlists.sql.go b/backend/database/sql/sqlcgen/playlists.sql.go index 56a1000..e54660a 100644 --- a/backend/database/sql/sqlcgen/playlists.sql.go +++ b/backend/database/sql/sqlcgen/playlists.sql.go @@ -7,16 +7,17 @@ package sqlcgen import ( "context" + "database/sql" ) const addPlaylistTrack = `-- name: AddPlaylistTrack :one INSERT INTO playlist_tracks (playlist_id, audio_file_id, position) VALUES (?, ?, ?) -RETURNING id, playlist_id, audio_file_id, position +RETURNING id, playlist_id, audio_file_id, position, phantom_title, phantom_artist, phantom_album, phantom_duration_ms, phantom_genre, phantom_cover_art_path ` type AddPlaylistTrackParams struct { PlaylistID int64 - AudioFileID int64 + AudioFileID sql.NullInt64 Position int64 } @@ -28,6 +29,12 @@ func (q *Queries) AddPlaylistTrack(ctx context.Context, arg AddPlaylistTrackPara &i.PlaylistID, &i.AudioFileID, &i.Position, + &i.PhantomTitle, + &i.PhantomArtist, + &i.PhantomAlbum, + &i.PhantomDurationMs, + &i.PhantomGenre, + &i.PhantomCoverArtPath, ) return i, err } @@ -116,7 +123,7 @@ ORDER BY pt.playlist_id, pt.position type GetAllPlaylistTracksWithMetadataRow struct { ID int64 PlaylistID int64 - AudioFileID int64 + AudioFileID sql.NullInt64 Position int64 FilePath string LengthMilliseconds int64 @@ -262,7 +269,7 @@ ORDER BY pt.position type GetPlaylistTracksRow struct { ID int64 PlaylistID int64 - AudioFileID int64 + AudioFileID sql.NullInt64 Position int64 FilePath string } @@ -326,7 +333,7 @@ ORDER BY pt.position type GetPlaylistTracksWithMetadataRow struct { ID int64 PlaylistID int64 - AudioFileID int64 + AudioFileID sql.NullInt64 Position int64 FilePath string LengthMilliseconds int64 diff --git a/backend/playlist/playlist.go b/backend/playlist/playlist.go index 889b265..5531b09 100644 --- a/backend/playlist/playlist.go +++ b/backend/playlist/playlist.go @@ -3,6 +3,7 @@ package playlist import ( "context" + "database/sql" "errors" "fmt" "log/slog" @@ -867,7 +868,7 @@ func (s *Service) ImportPlaylist( s.db.Ctx, sqlcgen.AddPlaylistTrackParams{ PlaylistID: created.ID, - AudioFileID: audioFile.ID, + AudioFileID: sql.NullInt64{Int64: audioFile.ID, Valid: true}, Position: int64(position), }, ) @@ -1065,7 +1066,7 @@ func (s *Service) restoreSinglePlaylist( s.db.Ctx, sqlcgen.AddPlaylistTrackParams{ PlaylistID: playlistID, - AudioFileID: audioFile.ID, + AudioFileID: sql.NullInt64{Int64: audioFile.ID, Valid: true}, Position: int64(position), }, ) @@ -1125,7 +1126,7 @@ func (s *Service) addSingleTrack( s.db.Ctx, sqlcgen.AddPlaylistTrackParams{ PlaylistID: playlistID, - AudioFileID: audioFile.ID, + AudioFileID: sql.NullInt64{Int64: audioFile.ID, Valid: true}, Position: position, }, ) @@ -1654,7 +1655,7 @@ func (s *Service) ResolvePhantomTracks( s.db.Ctx, sqlcgen.AddPlaylistTrackParams{ PlaylistID: playlistID, - AudioFileID: audioFile.ID, + AudioFileID: sql.NullInt64{Int64: audioFile.ID, Valid: true}, Position: nextPos + int64(resolved), }, )