diff --git a/Sockseek.Core.Tests/SearcherTests.cs b/Sockseek.Core.Tests/SearcherTests.cs index 182e0e9a..4318668e 100644 --- a/Sockseek.Core.Tests/SearcherTests.cs +++ b/Sockseek.Core.Tests/SearcherTests.cs @@ -84,6 +84,76 @@ public async Task SearchSong_FiltersKnownWrongLength_ByDefault() Assert.AreEqual(0, song.Candidates?.Count ?? 0); } + [TestMethod] + public async Task SearchSong_RetriesEachArtistWhenCombinedArtistSearchHasNoResults() + { + var response = new SearchResponse("User", 1, true, 1000, 0, + [ + TestHelpers.CreateSlFile(@"Music\Knock2\What's the Move.mp3", length: 200), + ]); + var client = CreateMockClient([response]); + var settings = TestHelpers.CreateDefaultSettings().Download; + var searcher = CreateSearcher(client, settings); + var song = new SongJob(new SongQuery + { + Artist = "Henry Fong;Knock2;General Degree", + Title = "What's the Move", + }); + + await searcher.SearchSong(song, settings.Search, new ResponseData(), CancellationToken.None); + + Assert.AreEqual(3, client.SearchCallCount); + Assert.AreEqual(1, song.Candidates?.Count ?? 0); + } + + [TestMethod] + public async Task SearchSong_RetriesSingleArtistWithoutRequiredLengthTolerance() + { + var response = new SearchResponse("User", 1, true, 1000, 0, + [ + TestHelpers.CreateSlFile(@"Music\Knock2\No Limit\No Limit.mp3", length: 200), + ]); + var client = CreateMockClient([response]); + var settings = TestHelpers.CreateDefaultSettings().Download; + var searcher = CreateSearcher(client, settings); + var song = new SongJob(new SongQuery + { + Artist = "Knock2", + Album = "No Limit", + Title = "No Limit", + Length = 240, + }); + + await searcher.SearchSong(song, settings.Search, new ResponseData(), CancellationToken.None); + + Assert.AreEqual(2, client.SearchCallCount); + Assert.AreEqual(1, song.Candidates?.Count ?? 0); + } + + [TestMethod] + public async Task SearchSong_RetriesWithoutFeaturedArtistInTitle() + { + var response = new SearchResponse("User", 1, true, 1000, 0, + [ + TestHelpers.CreateSlFile(@"Music\Knock2\Shyne 4 Me\Shyne 4 Me.mp3", length: 200), + ]); + var client = CreateMockClient([response]); + var settings = TestHelpers.CreateDefaultSettings().Download; + var searcher = CreateSearcher(client, settings); + var song = new SongJob(new SongQuery + { + Artist = "Knock2;Warren Hue;Holly;PIAO", + Album = "Shyne 4 Me", + Title = "Shyne 4 Me (feat. PIAO)", + Length = 200, + }); + + await searcher.SearchSong(song, settings.Search, new ResponseData(), CancellationToken.None); + + Assert.AreEqual(3, client.SearchCallCount); + Assert.AreEqual(1, song.Candidates?.Count ?? 0); + } + [TestMethod] public async Task SearchSong_UpdatesActivityPhaseThroughSearchAndProjection() { diff --git a/Sockseek.Core/Search/Searcher.cs b/Sockseek.Core/Search/Searcher.cs index 7075b315..de3a0ba4 100644 --- a/Sockseek.Core/Search/Searcher.cs +++ b/Sockseek.Core/Search/Searcher.cs @@ -585,6 +585,32 @@ public async Task RunSearches(SongQuery query, SlDictionary results, await Task.WhenAll(searchTasks); + if (results.IsEmpty && artist && title) + { + bool sameTitleAlbum = album + && string.Equals(query.Album.Trim(), query.Title.Trim(), StringComparison.OrdinalIgnoreCase); + var fallbackNecessaryCond = sameTitleAlbum + ? new FileConditions(search.NecessaryCond) { LengthTolerance = -1 } + : search.NecessaryCond; + var fallbackOpts = getSearchOptions(search.SearchTimeout, fallbackNecessaryCond, search.PreferredCond); + foreach (string primaryArtist in GetArtists(query.Artist)) + { + foreach (string fallbackTitle in GetSearchTitles(query.Title)) + { + searchTasks.Clear(); + searchTasks.Add(DoSearch($"{primaryArtist} {fallbackTitle}", fallbackOpts, + responseHandler, noRemoveSpecialChars, ct, onSearch, ownerJob)); + await Task.WhenAll(searchTasks); + + if (!results.IsEmpty) + break; + } + + if (!results.IsEmpty) + break; + } + } + if (results.IsEmpty && query.ArtistMaybeWrong && title) { var inferred = InferSongQuery(query.Title, new SongQuery()); @@ -691,6 +717,18 @@ private static string GetSearchString(SongQuery query, bool isAlbum) } } + private static IEnumerable GetArtists(string artist) + => artist.Split([';', ',', '&'], StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + private static IEnumerable GetSearchTitles(string title) + { + yield return title; + + string titleWithoutFeaturedArtist = title.RemoveFt(); + if (!string.Equals(titleWithoutFeaturedArtist, title, StringComparison.Ordinal)) + yield return titleWithoutFeaturedArtist; + } + private static string CleanSearchString(string str, bool removeSpecialChars) { str = str.ToLower();