From 648aa95f326b0344eb4da3862b19f62384ccc79c Mon Sep 17 00:00:00 2001 From: Brian Bicknell Date: Sun, 25 May 2025 19:19:45 -0400 Subject: [PATCH] Manga search provider updates. --- .../Extensions/ServiceCollectionExtensions.cs | 25 +++++++++++++++++++ .../Search/IMangaSearchCoordinator.cs | 6 +++++ .../Search/IMangaSearchProvider.cs | 11 +++----- .../Search/MangaDex/MangaDexSearchProvider.cs | 14 ++++++++--- .../Search/MangaSearchCoordinator.cs | 12 +++++++++ .../Search/MangaSearchProviderBase.cs | 4 +-- .../Search/MangaDex/MangaDexSearchTests.cs | 2 +- .../NatoManga/NatoMangaWebSearchTests.cs | 2 +- 8 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 MangaReader.Core/Extensions/ServiceCollectionExtensions.cs create mode 100644 MangaReader.Core/Search/IMangaSearchCoordinator.cs create mode 100644 MangaReader.Core/Search/MangaSearchCoordinator.cs diff --git a/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs b/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..e5a41f4 --- /dev/null +++ b/MangaReader.Core/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,25 @@ +using MangaReader.Core.Search; +using MangaReader.Core.Search.MangaDex; +using MangaReader.Core.Search.NatoManga; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace Microsoft.Extensions.DependencyInjection; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddMangaReader(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } +} \ No newline at end of file diff --git a/MangaReader.Core/Search/IMangaSearchCoordinator.cs b/MangaReader.Core/Search/IMangaSearchCoordinator.cs new file mode 100644 index 0000000..c478bab --- /dev/null +++ b/MangaReader.Core/Search/IMangaSearchCoordinator.cs @@ -0,0 +1,6 @@ +namespace MangaReader.Core.Search; + +public interface IMangaSearchCoordinator +{ + Task SearchAsync(string keyword, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/MangaReader.Core/Search/IMangaSearchProvider.cs b/MangaReader.Core/Search/IMangaSearchProvider.cs index 6cfd7f4..6a2328d 100644 --- a/MangaReader.Core/Search/IMangaSearchProvider.cs +++ b/MangaReader.Core/Search/IMangaSearchProvider.cs @@ -1,11 +1,6 @@ namespace MangaReader.Core.Search; -public interface IMangaSearchProvider +public interface IMangaSearchProvider { - Task SearchAsync(string keyword); -} - -//public class MangaDexWebSearch : IMangaWebSearch -//{ -// // https://api.mangadex.org/manga?title=gal can't be&limit=5 -//} \ No newline at end of file + Task SearchAsync(string keyword, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/MangaReader.Core/Search/MangaDex/MangaDexSearchProvider.cs b/MangaReader.Core/Search/MangaDex/MangaDexSearchProvider.cs index fdc4e21..66681ff 100644 --- a/MangaReader.Core/Search/MangaDex/MangaDexSearchProvider.cs +++ b/MangaReader.Core/Search/MangaDex/MangaDexSearchProvider.cs @@ -4,8 +4,14 @@ using System.Text.RegularExpressions; namespace MangaReader.Core.Search.MangaDex; -public class MangaDexSearchProvider(IHttpService httpService) : MangaSearchProviderBase(httpService) +public partial class MangaDexSearchProvider(IHttpService httpService) : MangaSearchProviderBase(httpService) { + [GeneratedRegex(@"[^a-z0-9\s-]")] + private static partial Regex InvalidSlugCharactersRegex(); + + [GeneratedRegex(@"\s+")] + private static partial Regex WhitespaceRegex(); + protected override string GetSearchUrl(string keyword) { string normalizedKeyword = keyword.ToLowerInvariant().Normalize(NormalizationForm.FormD); @@ -41,9 +47,9 @@ public class MangaDexSearchProvider(IHttpService httpService) : MangaSearchProvi // title.ToLowerInvariant().Normalize(NormalizationForm.FormD); title = title.ToLowerInvariant(); - //title = Regex.Replace(title, @"[^a-z0-9\s-]", ""); // remove invalid chars - title = Regex.Replace(title, @"[^a-z0-9\s-]", "-"); // replace invalid chars with dash - title = Regex.Replace(title, @"\s+", "-"); // replace spaces with dash + //title = InvalidSlugCharactersRegex().Replace(title, ""); // remove invalid chars + title = InvalidSlugCharactersRegex().Replace(title, "-"); // replace invalid chars with dash + title = WhitespaceRegex().Replace(title, "-"); // replace spaces with dash return title.Trim('-'); } diff --git a/MangaReader.Core/Search/MangaSearchCoordinator.cs b/MangaReader.Core/Search/MangaSearchCoordinator.cs new file mode 100644 index 0000000..4414f42 --- /dev/null +++ b/MangaReader.Core/Search/MangaSearchCoordinator.cs @@ -0,0 +1,12 @@ +namespace MangaReader.Core.Search; + +public class MangaSearchCoordinator(IEnumerable searchProviders) : IMangaSearchCoordinator +{ + public async Task SearchAsync(string keyword, CancellationToken cancellationToken) + { + var searchTasks = searchProviders.Select(searchProvider => searchProvider.SearchAsync(keyword, cancellationToken)); + var allResults = await Task.WhenAll(searchTasks); + + return [.. allResults.SelectMany(mangaSearchResults => mangaSearchResults)]; + } +} \ No newline at end of file diff --git a/MangaReader.Core/Search/MangaSearchProviderBase.cs b/MangaReader.Core/Search/MangaSearchProviderBase.cs index 13897fd..5a25bb8 100644 --- a/MangaReader.Core/Search/MangaSearchProviderBase.cs +++ b/MangaReader.Core/Search/MangaSearchProviderBase.cs @@ -3,14 +3,14 @@ using System.Text.Json; namespace MangaReader.Core.Search; -public abstract class MangaSearchProviderBase(IHttpService httpService) : IMangaSearchProvider +public abstract class MangaSearchProviderBase(IHttpService httpService) : IMangaSearchProvider { private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { PropertyNameCaseInsensitive = true }; - public async Task SearchAsync(string keyword) + public async Task SearchAsync(string keyword, CancellationToken cancellationToken) { T? searchResult = await GetSearchResultAsync(keyword); diff --git a/MangaReader.Tests/Search/MangaDex/MangaDexSearchTests.cs b/MangaReader.Tests/Search/MangaDex/MangaDexSearchTests.cs index 88fd9a3..89d52a4 100644 --- a/MangaReader.Tests/Search/MangaDex/MangaDexSearchTests.cs +++ b/MangaReader.Tests/Search/MangaDex/MangaDexSearchTests.cs @@ -40,7 +40,7 @@ public class MangaDexSearchTests .Returns(Task.FromResult(searchResultJson)); MangaDexSearchProvider searchProvider = new(httpService); - MangaSearchResult[] searchResult = await searchProvider.SearchAsync("Gal Can't Be Kind"); + MangaSearchResult[] searchResult = await searchProvider.SearchAsync("Gal Can't Be Kind", CancellationToken.None); searchResult.Length.ShouldBe(3); searchResult[0].Title.ShouldBe("Gals Can’t Be Kind to Otaku!?"); diff --git a/MangaReader.Tests/Search/NatoManga/NatoMangaWebSearchTests.cs b/MangaReader.Tests/Search/NatoManga/NatoMangaWebSearchTests.cs index 00207c5..07f6a53 100644 --- a/MangaReader.Tests/Search/NatoManga/NatoMangaWebSearchTests.cs +++ b/MangaReader.Tests/Search/NatoManga/NatoMangaWebSearchTests.cs @@ -35,7 +35,7 @@ public class NatoMangaWebSearchTests .Returns(Task.FromResult(searchResultJson)); NatoMangaSearchProvider searchProvider = new(httpService); - MangaSearchResult[] searchResult = await searchProvider.SearchAsync("Gal Can't Be Kind"); + MangaSearchResult[] searchResult = await searchProvider.SearchAsync("Gal Can't Be Kind", CancellationToken.None); searchResult.Length.ShouldBe(2); searchResult[0].Title.ShouldBe("Gal Can't Be Kind to Otaku!");