From 9e0e4d5bb8a02265f0064c40a4c8bd4d1a44cb41 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 12 Aug 2026 01:18:17 -0400 Subject: [PATCH] perf(library): resolve album and genre file paths in one query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Play this artist" awaited `GetAlbumTracks` inside a for loop — 13 sequential round trips for a 12-album artist — and every one of the four sites doing that asked for whole track rows to read `FilePath` off them. Five genres cost 6 MB across the IPC. `GetFilePathsByAlbums(ids, libraryID)` and `GetFilePathsByGenres(names, libraryID)` answer once and carry only the paths. Measured at 50 000 tracks: an artist 13 calls / 74.2 kB -> 2 / 19.2 kB, twenty albums 20 / 117.5 kB / 7.8 ms -> 1 / 26.0 kB / 1.7 ms, five genres 5 / 6 014 kB / 213 ms -> 1 / 1 291 kB / 32.6 ms, with the returned path lists identical. They return the paths grouped by album id or genre name rather than flattened, because the caller owns the order — an album list is sorted by name, not by id, and a flattened result would silently reorder a queue — and because the album drag cache stores them per album. A libraryID of 0 means "every library", matching an unset filter. --- backend/database/sql/queries/audio_files.sql | 23 ++ backend/database/sql/queries/genres.sql | 23 ++ .../database/sql/sqlcgen/audio_files.sql.go | 107 ++++++++ backend/database/sql/sqlcgen/genres.sql.go | 108 ++++++++ backend/library/filepaths_test.go | 231 ++++++++++++++++++ backend/library/query.go | 121 +++++++++ frontend/wailsjs/go/library/Library.d.ts | 4 + frontend/wailsjs/go/library/Library.js | 8 + 8 files changed, 625 insertions(+) create mode 100644 backend/library/filepaths_test.go diff --git a/backend/database/sql/queries/audio_files.sql b/backend/database/sql/queries/audio_files.sql index e2c0cd2..ded9537 100644 --- a/backend/database/sql/queries/audio_files.sql +++ b/backend/database/sql/queries/audio_files.sql @@ -295,3 +295,26 @@ LEFT JOIN release_groups rg ON rgr.release_group_id = rg.id LEFT JOIN file_types ft ON af.file_type_id = ft.id WHERE rgr.release_group_id = ? AND af.library_id = ? ORDER BY rgr.disc_number, rgr.track_number; + +-- "Play this artist" and "play these albums" wanted file paths and asked +-- for whole track rows to get them, one round trip per album (perf.m2). +-- These answer the same question in one query and carry only what the +-- caller uses; the release group id comes back so the caller can keep +-- its own album ordering. + +-- name: GetFilePathsByReleaseGroups :many +SELECT rgr.release_group_id, af.file_path +FROM release_group_recordings rgr +JOIN recordings r ON rgr.recording_id = r.id +JOIN audio_files af ON af.recording_id = r.id +WHERE rgr.release_group_id IN (sqlc.slice('release_group_ids')) +ORDER BY rgr.disc_number, rgr.track_number; + +-- name: GetFilePathsByReleaseGroupsByLibrary :many +SELECT rgr.release_group_id, af.file_path +FROM release_group_recordings rgr +JOIN recordings r ON rgr.recording_id = r.id +JOIN audio_files af ON af.recording_id = r.id +WHERE rgr.release_group_id IN (sqlc.slice('release_group_ids')) + AND af.library_id = ? +ORDER BY rgr.disc_number, rgr.track_number; diff --git a/backend/database/sql/queries/genres.sql b/backend/database/sql/queries/genres.sql index f2a87e2..2feb3cf 100644 --- a/backend/database/sql/queries/genres.sql +++ b/backend/database/sql/queries/genres.sql @@ -125,3 +125,26 @@ JOIN audio_files af ON af.recording_id = r.id WHERE af.library_id = ? GROUP BY g.id, g.name ORDER BY g.name; + +-- Same as GetFilePathsByReleaseGroups, for "play these genres" (perf.m2): +-- one query instead of one per genre, and file paths instead of whole +-- track rows, which was 6 MB over the IPC for five genres. + +-- name: GetFilePathsByGenres :many +SELECT g.name AS genre_name, af.file_path +FROM genres g +JOIN recording_genres rg ON g.id = rg.genre_id +JOIN recordings r ON rg.recording_id = r.id +JOIN audio_files af ON af.recording_id = r.id +WHERE g.name IN (sqlc.slice('genre_names')) +ORDER BY r.name; + +-- name: GetFilePathsByGenresByLibrary :many +SELECT g.name AS genre_name, af.file_path +FROM genres g +JOIN recording_genres rg ON g.id = rg.genre_id +JOIN recordings r ON rg.recording_id = r.id +JOIN audio_files af ON af.recording_id = r.id +WHERE g.name IN (sqlc.slice('genre_names')) + AND af.library_id = ? +ORDER BY r.name; diff --git a/backend/database/sql/sqlcgen/audio_files.sql.go b/backend/database/sql/sqlcgen/audio_files.sql.go index 63305e3..96c47b1 100644 --- a/backend/database/sql/sqlcgen/audio_files.sql.go +++ b/backend/database/sql/sqlcgen/audio_files.sql.go @@ -907,6 +907,113 @@ func (q *Queries) GetAudioFilesNeedingMetadata(ctx context.Context) ([]AudioFile return items, nil } +const getFilePathsByReleaseGroups = `-- name: GetFilePathsByReleaseGroups :many + +SELECT rgr.release_group_id, af.file_path +FROM release_group_recordings rgr +JOIN recordings r ON rgr.recording_id = r.id +JOIN audio_files af ON af.recording_id = r.id +WHERE rgr.release_group_id IN (/*SLICE:release_group_ids*/?) +ORDER BY rgr.disc_number, rgr.track_number +` + +type GetFilePathsByReleaseGroupsRow struct { + ReleaseGroupID int64 + FilePath string +} + +// "Play this artist" and "play these albums" wanted file paths and asked +// for whole track rows to get them, one round trip per album (perf.m2). +// These answer the same question in one query and carry only what the +// caller uses; the release group id comes back so the caller can keep +// its own album ordering. +func (q *Queries) GetFilePathsByReleaseGroups(ctx context.Context, releaseGroupIds []int64) ([]GetFilePathsByReleaseGroupsRow, error) { + query := getFilePathsByReleaseGroups + var queryParams []interface{} + if len(releaseGroupIds) > 0 { + for _, v := range releaseGroupIds { + queryParams = append(queryParams, v) + } + query = strings.Replace(query, "/*SLICE:release_group_ids*/?", strings.Repeat(",?", len(releaseGroupIds))[1:], 1) + } else { + query = strings.Replace(query, "/*SLICE:release_group_ids*/?", "NULL", 1) + } + rows, err := q.db.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetFilePathsByReleaseGroupsRow + for rows.Next() { + var i GetFilePathsByReleaseGroupsRow + if err := rows.Scan(&i.ReleaseGroupID, &i.FilePath); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getFilePathsByReleaseGroupsByLibrary = `-- name: GetFilePathsByReleaseGroupsByLibrary :many +SELECT rgr.release_group_id, af.file_path +FROM release_group_recordings rgr +JOIN recordings r ON rgr.recording_id = r.id +JOIN audio_files af ON af.recording_id = r.id +WHERE rgr.release_group_id IN (/*SLICE:release_group_ids*/?) + AND af.library_id = ? +ORDER BY rgr.disc_number, rgr.track_number +` + +type GetFilePathsByReleaseGroupsByLibraryParams struct { + ReleaseGroupIds []int64 + LibraryID int64 +} + +type GetFilePathsByReleaseGroupsByLibraryRow struct { + ReleaseGroupID int64 + FilePath string +} + +func (q *Queries) GetFilePathsByReleaseGroupsByLibrary(ctx context.Context, arg GetFilePathsByReleaseGroupsByLibraryParams) ([]GetFilePathsByReleaseGroupsByLibraryRow, error) { + query := getFilePathsByReleaseGroupsByLibrary + var queryParams []interface{} + if len(arg.ReleaseGroupIds) > 0 { + for _, v := range arg.ReleaseGroupIds { + queryParams = append(queryParams, v) + } + query = strings.Replace(query, "/*SLICE:release_group_ids*/?", strings.Repeat(",?", len(arg.ReleaseGroupIds))[1:], 1) + } else { + query = strings.Replace(query, "/*SLICE:release_group_ids*/?", "NULL", 1) + } + queryParams = append(queryParams, arg.LibraryID) + rows, err := q.db.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetFilePathsByReleaseGroupsByLibraryRow + for rows.Next() { + var i GetFilePathsByReleaseGroupsByLibraryRow + if err := rows.Scan(&i.ReleaseGroupID, &i.FilePath); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getLibraryMaxModifiedAt = `-- name: GetLibraryMaxModifiedAt :one SELECT CAST(COALESCE(MAX(modified_at), 0) AS INTEGER) FROM audio_files WHERE library_id = ? diff --git a/backend/database/sql/sqlcgen/genres.sql.go b/backend/database/sql/sqlcgen/genres.sql.go index e577a79..6794ddf 100644 --- a/backend/database/sql/sqlcgen/genres.sql.go +++ b/backend/database/sql/sqlcgen/genres.sql.go @@ -8,6 +8,7 @@ package sqlcgen import ( "context" "database/sql" + "strings" ) const countGenreReferences = `-- name: CountGenreReferences :one @@ -148,6 +149,113 @@ func (q *Queries) GetAllGenresWithCountsByLibrary(ctx context.Context, libraryID return items, nil } +const getFilePathsByGenres = `-- name: GetFilePathsByGenres :many + +SELECT g.name AS genre_name, af.file_path +FROM genres g +JOIN recording_genres rg ON g.id = rg.genre_id +JOIN recordings r ON rg.recording_id = r.id +JOIN audio_files af ON af.recording_id = r.id +WHERE g.name IN (/*SLICE:genre_names*/?) +ORDER BY r.name +` + +type GetFilePathsByGenresRow struct { + GenreName string + FilePath string +} + +// Same as GetFilePathsByReleaseGroups, for "play these genres" (perf.m2): +// one query instead of one per genre, and file paths instead of whole +// track rows, which was 6 MB over the IPC for five genres. +func (q *Queries) GetFilePathsByGenres(ctx context.Context, genreNames []string) ([]GetFilePathsByGenresRow, error) { + query := getFilePathsByGenres + var queryParams []interface{} + if len(genreNames) > 0 { + for _, v := range genreNames { + queryParams = append(queryParams, v) + } + query = strings.Replace(query, "/*SLICE:genre_names*/?", strings.Repeat(",?", len(genreNames))[1:], 1) + } else { + query = strings.Replace(query, "/*SLICE:genre_names*/?", "NULL", 1) + } + rows, err := q.db.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetFilePathsByGenresRow + for rows.Next() { + var i GetFilePathsByGenresRow + if err := rows.Scan(&i.GenreName, &i.FilePath); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getFilePathsByGenresByLibrary = `-- name: GetFilePathsByGenresByLibrary :many +SELECT g.name AS genre_name, af.file_path +FROM genres g +JOIN recording_genres rg ON g.id = rg.genre_id +JOIN recordings r ON rg.recording_id = r.id +JOIN audio_files af ON af.recording_id = r.id +WHERE g.name IN (/*SLICE:genre_names*/?) + AND af.library_id = ? +ORDER BY r.name +` + +type GetFilePathsByGenresByLibraryParams struct { + GenreNames []string + LibraryID int64 +} + +type GetFilePathsByGenresByLibraryRow struct { + GenreName string + FilePath string +} + +func (q *Queries) GetFilePathsByGenresByLibrary(ctx context.Context, arg GetFilePathsByGenresByLibraryParams) ([]GetFilePathsByGenresByLibraryRow, error) { + query := getFilePathsByGenresByLibrary + var queryParams []interface{} + if len(arg.GenreNames) > 0 { + for _, v := range arg.GenreNames { + queryParams = append(queryParams, v) + } + query = strings.Replace(query, "/*SLICE:genre_names*/?", strings.Repeat(",?", len(arg.GenreNames))[1:], 1) + } else { + query = strings.Replace(query, "/*SLICE:genre_names*/?", "NULL", 1) + } + queryParams = append(queryParams, arg.LibraryID) + rows, err := q.db.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetFilePathsByGenresByLibraryRow + for rows.Next() { + var i GetFilePathsByGenresByLibraryRow + if err := rows.Scan(&i.GenreName, &i.FilePath); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getGenresByRecordingID = `-- name: GetGenresByRecordingID :many SELECT g.id, g.name FROM genres g diff --git a/backend/library/filepaths_test.go b/backend/library/filepaths_test.go new file mode 100644 index 0000000..5b86a56 --- /dev/null +++ b/backend/library/filepaths_test.go @@ -0,0 +1,231 @@ +package library + +import ( + "database/sql" + "testing" + + "yellowjacket/backend/database/sql/sqlcgen" +) + +// seedAlbumsAndGenres builds two albums in two libraries, with one track +// carrying two genres — enough shape for the batched path lookups to be +// wrong in an interesting way if they group or filter incorrectly. +func seedAlbumsAndGenres(t *testing.T, lib *Library) (albumIDs []int64, libraryID int64) { + t.Helper() + + ctx := lib.ctx + q := lib.db.Queries + + library, err := q.CreateLibrary(ctx, sqlcgen.CreateLibraryParams{ + Name: "Main", + Path: "/music", + }) + if err != nil { + t.Fatalf("create library: %v", err) + } + + other, err := q.CreateLibrary(ctx, sqlcgen.CreateLibraryParams{ + Name: "Other", + Path: "/other", + }) + if err != nil { + t.Fatalf("create other library: %v", err) + } + + ac, err := q.UpsertArtistCredit(ctx, "Test Artist") + if err != nil { + t.Fatalf("upsert artist credit: %v", err) + } + + genreIDs := map[string]int64{} + + for _, name := range []string{"Ambient", "Baroque"} { + g, err := q.UpsertGenre(ctx, name) + if err != nil { + t.Fatalf("upsert genre %s: %v", name, err) + } + + genreIDs[name] = g.ID + } + + // Two albums; the second lives in the other library so the + // library-scoped variants have something to exclude. + type spec struct { + album string + track string + path string + library int64 + disc int64 + number int64 + genres []string + } + + specs := []spec{ + {"First", "A2", "/music/a2.mp3", library.ID, 1, 2, []string{"Ambient"}}, + {"First", "A1", "/music/a1.mp3", library.ID, 1, 1, []string{"Ambient", "Baroque"}}, + {"Second", "B1", "/other/b1.mp3", other.ID, 1, 1, []string{"Baroque"}}, + } + + byAlbum := map[string]int64{} + + for _, s := range specs { + rec, err := q.CreateRecordingFull(ctx, sqlcgen.CreateRecordingFullParams{ + Name: s.track, + ArtistCreditID: ac.ID, + }) + if err != nil { + t.Fatalf("create recording: %v", err) + } + + rgID, ok := byAlbum[s.album] + + if !ok { + rg, err := q.UpsertReleaseGroup(ctx, sqlcgen.UpsertReleaseGroupParams{ + Name: s.album, + AlbumArtistCreditID: sql.NullInt64{Int64: ac.ID, Valid: true}, + }) + if err != nil { + t.Fatalf("upsert release group: %v", err) + } + + rgID = rg.ID + byAlbum[s.album] = rgID + albumIDs = append(albumIDs, rgID) + } + + if _, err := q.CreateReleaseGroupRecording( + ctx, sqlcgen.CreateReleaseGroupRecordingParams{ + ReleaseGroupID: rgID, + RecordingID: rec.ID, + TrackNumber: sql.NullInt64{Int64: s.number, Valid: true}, + DiscNumber: sql.NullInt64{Int64: s.disc, Valid: true}, + }, + ); err != nil { + t.Fatalf("link recording: %v", err) + } + + if _, err := q.CreateAudioFile(ctx, sqlcgen.CreateAudioFileParams{ + FilePath: s.path, + LengthMilliseconds: 1000, + RecordingID: rec.ID, + LibraryID: s.library, + Basename: s.track + ".mp3", + }); err != nil { + t.Fatalf("create audio file: %v", err) + } + + for _, g := range s.genres { + if err := q.CreateRecordingGenre( + ctx, sqlcgen.CreateRecordingGenreParams{ + RecordingID: rec.ID, + GenreID: genreIDs[g], + }, + ); err != nil { + t.Fatalf("link genre: %v", err) + } + } + } + + return albumIDs, library.ID +} + +// perf.m2: "play this artist" asked for whole track rows, one round trip +// per album, to read one field off each. These two answer in one query, +// and the thing worth pinning is that they still group by the entity the +// caller ordered by — a flattened result would silently reorder a queue. +func TestGetFilePathsByAlbums(t *testing.T) { + t.Parallel() + + lib, _ := setupTestLibrary(t) + albumIDs, libraryID := seedAlbumsAndGenres(t, lib) + + got, err := lib.GetFilePathsByAlbums(albumIDs, 0) + if err != nil { + t.Fatalf("GetFilePathsByAlbums: %v", err) + } + + if len(got) != 2 { + t.Fatalf("albums returned = %d, want 2", len(got)) + } + + // Ordered by disc then track within an album, not by insertion. + first := got[albumIDs[0]] + if len(first) != 2 || first[0] != "/music/a1.mp3" || first[1] != "/music/a2.mp3" { + t.Errorf("first album paths = %v, want [a1 a2] in track order", first) + } + + scoped, err := lib.GetFilePathsByAlbums(albumIDs, libraryID) + if err != nil { + t.Fatalf("GetFilePathsByAlbums scoped: %v", err) + } + + if _, ok := scoped[albumIDs[1]]; ok { + t.Errorf("library-scoped result includes an album from another library") + } + + if len(scoped[albumIDs[0]]) != 2 { + t.Errorf("scoped first album = %v, want 2 paths", scoped[albumIDs[0]]) + } +} + +func TestGetFilePathsByAlbums_Empty(t *testing.T) { + t.Parallel() + + lib, _ := setupTestLibrary(t) + + got, err := lib.GetFilePathsByAlbums(nil, 0) + if err != nil { + t.Fatalf("GetFilePathsByAlbums(nil): %v", err) + } + + if len(got) != 0 { + t.Errorf("got %v, want empty", got) + } +} + +func TestGetFilePathsByGenres(t *testing.T) { + t.Parallel() + + lib, _ := setupTestLibrary(t) + _, libraryID := seedAlbumsAndGenres(t, lib) + + got, err := lib.GetFilePathsByGenres([]string{"Ambient", "Baroque"}, 0) + if err != nil { + t.Fatalf("GetFilePathsByGenres: %v", err) + } + + if len(got["Ambient"]) != 2 { + t.Errorf("Ambient = %v, want 2 paths", got["Ambient"]) + } + + // One track is in both genres: the overlap is returned under each, + // because de-duplicating is the caller's job — it is the one that + // knows the order the genres were selected in. + if len(got["Baroque"]) != 2 { + t.Errorf("Baroque = %v, want 2 paths", got["Baroque"]) + } + + scoped, err := lib.GetFilePathsByGenres([]string{"Baroque"}, libraryID) + if err != nil { + t.Fatalf("GetFilePathsByGenres scoped: %v", err) + } + + if len(scoped["Baroque"]) != 1 || scoped["Baroque"][0] != "/music/a1.mp3" { + t.Errorf("scoped Baroque = %v, want just the main library's track", scoped["Baroque"]) + } +} + +func TestGetFilePathsByGenres_Empty(t *testing.T) { + t.Parallel() + + lib, _ := setupTestLibrary(t) + + got, err := lib.GetFilePathsByGenres(nil, 0) + if err != nil { + t.Fatalf("GetFilePathsByGenres(nil): %v", err) + } + + if len(got) != 0 { + t.Errorf("got %v, want empty", got) + } +} diff --git a/backend/library/query.go b/backend/library/query.go index 077d5f7..1217ebe 100644 --- a/backend/library/query.go +++ b/backend/library/query.go @@ -1083,3 +1083,124 @@ func (l *Library) GetAllLibrariesWithTrackCounts() ([]Info, error) { return result, nil } + +// GetFilePathsByAlbums returns the file paths of every track in the +// given albums, grouped by album id. +// +// "Play this artist", "play these albums" and the album drag cache each +// resolved paths with one binding call per album, sequentially, and each +// asked for whole track rows to read one field off them (perf.m2). This +// is that question asked once. The result is grouped rather than +// flattened because the caller owns the ordering — an album list is +// sorted by name, not by id — and because the drag cache stores it per +// album. +// +// A library id of 0 means "every library", matching the caller's +// selected-library filter being unset. +func (l *Library) GetFilePathsByAlbums( + albumIDs []int64, libraryID int64, +) (map[int64][]string, error) { + paths := make(map[int64][]string, len(albumIDs)) + + if len(albumIDs) == 0 { + return paths, nil + } + + if libraryID > 0 { + rows, err := l.db.ReadQueries.GetFilePathsByReleaseGroupsByLibrary( + l.ctx, sqlcgen.GetFilePathsByReleaseGroupsByLibraryParams{ + ReleaseGroupIds: albumIDs, + LibraryID: libraryID, + }, + ) + if err != nil { + l.logger.Error( + "could not retrieve album file paths for library", + "albums", len(albumIDs), + "libraryID", libraryID, + "error", err, + ) + + return nil, fmt.Errorf("could not get album file paths: %w", err) + } + + for _, row := range rows { + paths[row.ReleaseGroupID] = append(paths[row.ReleaseGroupID], row.FilePath) + } + + return paths, nil + } + + rows, err := l.db.ReadQueries.GetFilePathsByReleaseGroups(l.ctx, albumIDs) + if err != nil { + l.logger.Error( + "could not retrieve album file paths", + "albums", len(albumIDs), + "error", err, + ) + + return nil, fmt.Errorf("could not get album file paths: %w", err) + } + + for _, row := range rows { + paths[row.ReleaseGroupID] = append(paths[row.ReleaseGroupID], row.FilePath) + } + + return paths, nil +} + +// GetFilePathsByGenres returns the file paths of every track tagged with +// the given genres, grouped by genre name. See GetFilePathsByAlbums — +// same finding, same shape, and the caller still owns the de-duplication +// across genres because it owns the order. +func (l *Library) GetFilePathsByGenres( + genreNames []string, libraryID int64, +) (map[string][]string, error) { + paths := make(map[string][]string, len(genreNames)) + + if len(genreNames) == 0 { + return paths, nil + } + + if libraryID > 0 { + rows, err := l.db.ReadQueries.GetFilePathsByGenresByLibrary( + l.ctx, sqlcgen.GetFilePathsByGenresByLibraryParams{ + GenreNames: genreNames, + LibraryID: libraryID, + }, + ) + if err != nil { + l.logger.Error( + "could not retrieve genre file paths for library", + "genres", len(genreNames), + "libraryID", libraryID, + "error", err, + ) + + return nil, fmt.Errorf("could not get genre file paths: %w", err) + } + + for _, row := range rows { + paths[row.GenreName] = append(paths[row.GenreName], row.FilePath) + } + + return paths, nil + } + + rows, err := l.db.ReadQueries.GetFilePathsByGenres(l.ctx, genreNames) + if err != nil { + l.logger.Error( + "could not retrieve genre file paths", + "genres", len(genreNames), + "error", err, + ) + + return nil, fmt.Errorf("could not get genre file paths: %w", err) + } + + for _, row := range rows { + paths[row.GenreName] = append(paths[row.GenreName], row.FilePath) + } + + return paths, nil +} diff --git a/frontend/wailsjs/go/library/Library.d.ts b/frontend/wailsjs/go/library/Library.d.ts index 1e96801..e3443f0 100755 --- a/frontend/wailsjs/go/library/Library.d.ts +++ b/frontend/wailsjs/go/library/Library.d.ts @@ -43,6 +43,10 @@ export function GetAllTracks():Promise>; export function GetAllTracksByLibrary(arg1:number):Promise>; +export function GetFilePathsByAlbums(arg1:Array,arg2:number):Promise>>; + +export function GetFilePathsByGenres(arg1:Array,arg2:number):Promise>>; + export function GetRemovalImpact(arg1:number):Promise; export function GetScanQueueLength():Promise; diff --git a/frontend/wailsjs/go/library/Library.js b/frontend/wailsjs/go/library/Library.js index 411cea3..8c03669 100755 --- a/frontend/wailsjs/go/library/Library.js +++ b/frontend/wailsjs/go/library/Library.js @@ -78,6 +78,14 @@ export function GetAllTracksByLibrary(arg1) { return window['go']['library']['Library']['GetAllTracksByLibrary'](arg1); } +export function GetFilePathsByAlbums(arg1, arg2) { + return window['go']['library']['Library']['GetFilePathsByAlbums'](arg1, arg2); +} + +export function GetFilePathsByGenres(arg1, arg2) { + return window['go']['library']['Library']['GetFilePathsByGenres'](arg1, arg2); +} + export function GetRemovalImpact(arg1) { return window['go']['library']['Library']['GetRemovalImpact'](arg1); }