diff --git a/internal/service.go b/internal/service.go index bef36d8..198ffcc 100644 --- a/internal/service.go +++ b/internal/service.go @@ -588,23 +588,58 @@ func (service *MusicAgregatorService) searchIndexer(album *metadataPb.Album, tra artistName = album.GetArtists()[0].GetArtist().GetName() } - query := album.GetTitle() - if artistName != "" { - query = artistName + " " + query - } - if tracker == "" { tracker = "all" } - result, err := service.indexer.Search(query, -1, tracker) - if err != nil { - log.Error().Err(err).Str("query", query).Msg("indexer search failed") - return nil, err + title := album.GetTitle() + queries := buildSearchQueries(artistName, title) + + for _, query := range queries { + result, err := service.indexer.Search(query, -1, tracker) + if err != nil { + log.Error().Err(err).Str("query", query).Msg("indexer search failed") + return nil, err + } + + log.Debug().Int("results", len(result.Items)).Str("query", query).Msg("indexer search completed") + + if len(result.Items) > 0 { + return result, nil + } + + log.Debug().Str("query", query).Msg("no results, trying broader search") } - log.Debug().Int("results", len(result.Items)).Str("query", query).Msg("indexer search completed") - return result, nil + return &indexer.SearchResponse{}, nil +} + +func buildSearchQueries(artist, title string) []string { + base := title + if artist != "" { + base = artist + " " + title + } + + queries := []string{base} + + stripped := stripParenthetical(title) + if stripped != title { + q := stripped + if artist != "" { + q = artist + " " + stripped + } + queries = append(queries, q) + } + + return queries +} + +func stripParenthetical(s string) string { + idx := strings.Index(s, "(") + if idx > 0 { + return strings.TrimSpace(s[:idx]) + } + return s } func (service *MusicAgregatorService) parseSearchResults(searchResult *indexer.SearchResponse, album *metadataPb.Album) []parsedItem { diff --git a/test/component/monitor_album_test.go b/test/component/monitor_album_test.go index e7d3462..049669b 100644 --- a/test/component/monitor_album_test.go +++ b/test/component/monitor_album_test.go @@ -1131,3 +1131,78 @@ func TestRecoverOrphaned_FindsActiveDownloads(t *testing.T) { assert.Equal(t, "orphan-hash", active[0].QbitHash) assert.Equal(t, "downloading", active[0].State) } + +func TestMonitorAlbum_SearchRetryAllExhausted(t *testing.T) { + suite := setupSuite(t) + cleanTables(t, suite.pool) + + suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) { + return &metadataPb.GetAlbumResponse{ + Album: newMetadataAlbum("test-album-ext-id", "Ain't My Bitch (remastered)", "artist-ext-id", "Metallica"), + }, nil + } + + var queryCount int + suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) { + queryCount++ + return newSearchResponse(), nil + } + + ctx := context.Background() + resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{ + AlbumId: "test-album-ext-id", + Quality: pb.QualityType_QUALITY_LOSSLESS, + }) + + require.NoError(t, err) + require.NotNil(t, resp.Album) + assert.Nil(t, resp.Release) + assert.Equal(t, 2, queryCount) +} + +func TestMonitorAlbum_SearchRetryWithStrippedTitle(t *testing.T) { + suite := setupSuite(t) + cleanTables(t, suite.pool) + + suite.mocks.metadata.GetAlbumFunc = func(ctx context.Context, in *metadataPb.GetAlbumRequest, opts ...grpc.CallOption) (*metadataPb.GetAlbumResponse, error) { + return &metadataPb.GetAlbumResponse{ + Album: newMetadataAlbum("test-album-ext-id", "Ain't My Bitch (remastered)", "artist-ext-id", "Metallica"), + }, nil + } + + var queries []string + suite.mocks.indexer.SearchFunc = func(query string, limit int32, idx string) (*indexer.SearchResponse, error) { + queries = append(queries, query) + if len(queries) == 1 { + return newSearchResponse(), nil + } + return newSearchResponse( + newSearchItem("Metallica - Load [FLAC]", 50, "magnet:?xt=urn:btih:abc123"), + ), nil + } + + suite.mocks.magnet.ResolveFunc = func(magnetURI string) ([]byte, error) { + return newTorrentData(), nil + } + + suite.mocks.torrent.FindFunc = func(opts torrent.FindOptions) ([]torrent.TorrentInfo, error) { + return []torrent.TorrentInfo{}, nil + } + + suite.mocks.torrent.AddMagnetFunc = func(magnetURI string, savePath string) error { + return nil + } + + ctx := context.Background() + resp, err := suite.client.MonitorAlbum(ctx, &pb.MonitorAlbumRequest{ + AlbumId: "test-album-ext-id", + Quality: pb.QualityType_QUALITY_LOSSLESS, + }) + + require.NoError(t, err) + require.NotNil(t, resp.Release) + + require.Len(t, queries, 2) + assert.Equal(t, "Metallica Ain't My Bitch (remastered)", queries[0]) + assert.Equal(t, "Metallica Ain't My Bitch", queries[1]) +}