From a3158d7f9a7c11f12ffddfe98013932e560592c9 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:41:36 +0900 Subject: [PATCH] Fixes performance regression in downloading Rollback to Refit. The performance in downloading while using restsharp is incredibly slow and falling back to refit solves the performance issue. I may wrongly use restsharp though. --- asuka.Provider.Nhentai/Api/IGalleryApi.cs | 17 +++ asuka.Provider.Nhentai/Api/IGalleryImage.cs | 9 ++ .../Api/Requests/GallerySearchQuery.cs | 15 +++ .../Mappers/GalleryResponseToSeriesMapper.cs | 2 +- asuka.Provider.Nhentai/Provider.cs | 114 ++++++++---------- .../asuka.Provider.Nhentai.csproj | 6 +- 6 files changed, 97 insertions(+), 66 deletions(-) create mode 100644 asuka.Provider.Nhentai/Api/IGalleryApi.cs create mode 100644 asuka.Provider.Nhentai/Api/IGalleryImage.cs create mode 100644 asuka.Provider.Nhentai/Api/Requests/GallerySearchQuery.cs diff --git a/asuka.Provider.Nhentai/Api/IGalleryApi.cs b/asuka.Provider.Nhentai/Api/IGalleryApi.cs new file mode 100644 index 0000000..1b1d8b4 --- /dev/null +++ b/asuka.Provider.Nhentai/Api/IGalleryApi.cs @@ -0,0 +1,17 @@ +using asuka.Provider.Nhentai.Api.Requests; +using asuka.Provider.Nhentai.Contracts; +using Refit; + +namespace asuka.Provider.Nhentai.Api; + +internal interface IGalleryApi +{ + [Get("/api/gallery/{code}")] + Task FetchSingle(string code, CancellationToken cancellationToken = default); + + [Get("/api/gallery/{code}/related")] + Task FetchRecommended(string code, CancellationToken cancellationToken = default); + + [Get("/api/galleries/search")] + Task SearchGallery(GallerySearchQuery queries, CancellationToken cancellationToken = default); +} diff --git a/asuka.Provider.Nhentai/Api/IGalleryImage.cs b/asuka.Provider.Nhentai/Api/IGalleryImage.cs new file mode 100644 index 0000000..585a87f --- /dev/null +++ b/asuka.Provider.Nhentai/Api/IGalleryImage.cs @@ -0,0 +1,9 @@ +using Refit; + +namespace asuka.Provider.Nhentai.Api; + +internal interface IGalleryImage +{ + [Get("/galleries/{mediaId}/{filename}")] + Task GetImage(string mediaId, string filename, CancellationToken cancellationToken = default); +} diff --git a/asuka.Provider.Nhentai/Api/Requests/GallerySearchQuery.cs b/asuka.Provider.Nhentai/Api/Requests/GallerySearchQuery.cs new file mode 100644 index 0000000..f876beb --- /dev/null +++ b/asuka.Provider.Nhentai/Api/Requests/GallerySearchQuery.cs @@ -0,0 +1,15 @@ +using Refit; + +namespace asuka.Provider.Nhentai.Api.Requests; + +internal sealed class GallerySearchQuery +{ + [AliasAs("query")] + public required string Queries { get; init; } + + [AliasAs("page")] + public int PageNumber { get; init; } + + [AliasAs("sort")] + public required string Sort { get; init; } +} diff --git a/asuka.Provider.Nhentai/Mappers/GalleryResponseToSeriesMapper.cs b/asuka.Provider.Nhentai/Mappers/GalleryResponseToSeriesMapper.cs index 2235609..d00766d 100644 --- a/asuka.Provider.Nhentai/Mappers/GalleryResponseToSeriesMapper.cs +++ b/asuka.Provider.Nhentai/Mappers/GalleryResponseToSeriesMapper.cs @@ -45,7 +45,7 @@ public static Series ToSeries(this GalleryResponse response) return new Chapter.ChapterImages { - ImageRemotePath = $"/galleries/{response.MediaId}/{pageNumber}{extension}", + ImageRemotePath = $"{response.MediaId},{pageNumber}{extension}", Filename = filename }; }) diff --git a/asuka.Provider.Nhentai/Provider.cs b/asuka.Provider.Nhentai/Provider.cs index c01bd06..abe99b6 100644 --- a/asuka.Provider.Nhentai/Provider.cs +++ b/asuka.Provider.Nhentai/Provider.cs @@ -1,23 +1,25 @@ using System.Net; using System.Reflection; using System.Security.Cryptography; +using System.Text.Json; using System.Text.RegularExpressions; -using asuka.Provider.Nhentai.Contracts; +using asuka.Provider.Nhentai.Api; +using asuka.Provider.Nhentai.Api.Requests; using asuka.Provider.Nhentai.Mappers; using asuka.ProviderSdk; -using RestSharp; +using Refit; namespace asuka.Provider.Nhentai; public sealed partial class Provider : MetaInfo { - private readonly RestClientOptions _clientOptions; - private readonly RestClientOptions _imageClientOptions; + private readonly IGalleryApi _gallery; + private readonly IGalleryImage _galleryImage; public Provider() { Id = "asuka.provider.nhentai"; - Version = new Version(1, 0, 0, 0); + Version = new Version(1, 1, 0, 0); ProviderAliases = [ "nh", @@ -25,26 +27,16 @@ public Provider() ]; // Configure request - _clientOptions = new RestClientOptions("https://nhentai.net/") + _gallery = RestService.For(CreateHttpClient("https://nhentai.net/"), new RefitSettings { - ThrowOnAnyError = true, - UserAgent = GetUserAgentFromFile() ?? $"asuka.Provider.Nhentai {Version.Major}.{Version.Minor}", - CookieContainer = new CookieContainer() - }; - - _imageClientOptions = new RestClientOptions("https://i.nhentai.net/") - { - ThrowOnAnyError = true, - UserAgent = GetUserAgentFromFile() ?? $"asuka.Provider.Nhentai {Version.Major}.{Version.Minor}", - CookieContainer = new CookieContainer() - }; - - // Read cookies from file and load them into RestClientOptions - foreach (var cookie in ReadCookiesFromFile()) - { - _clientOptions.CookieContainer.Add(cookie); - _imageClientOptions.CookieContainer.Add(cookie); - } + ContentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }) + }); + + _galleryImage = RestService.For(CreateHttpClient("https://i.nhentai.net/")); } public override bool IsGallerySupported(string galleryId) @@ -68,33 +60,20 @@ public override async Task GetSeries(string galleryId, CancellationToken var code = codeRegex.Match(galleryId).Value; // Request - var client = new RestClient(_clientOptions); - var request = new RestRequest($"/api/gallery/{code}"); - - var response = await client.GetAsync(request, cancellationToken); - if (response == null) - { - throw new Exception("Failed to deserialize request."); - } - return response.ToSeries(); + var request = await _gallery.FetchSingle(code, cancellationToken); + return request.ToSeries(); } public override async Task> Search(SearchQuery query, CancellationToken cancellationToken = default) { - var client = new RestClient(_clientOptions); - var request = new RestRequest("/api/galleries/search"); - - request.AddParameter("query", string.Join(" ", query.SearchQueries)); - request.AddParameter("page", query.PageNumber); - request.AddParameter("sort", query.Sort); - - var response = await client.GetAsync(request, cancellationToken); - if (response?.Result == null) + var request = await _gallery.SearchGallery(new GallerySearchQuery { - throw new Exception($"Unable to retrieve search results. Request URL: {client.BuildUri(request).ToString()}"); - } + Queries = string.Join(" ", query.SearchQueries), + PageNumber = query.PageNumber, + Sort = query.Sort ?? "popularity" + }, cancellationToken); - return response.Result + return request.Result .Select(x => x.ToSeries()) .ToList(); } @@ -117,31 +96,22 @@ public override async Task> GetRecommendations(string galleryId, Ca var codeRegex = CodeOnlyRegex(); var code = codeRegex.Match(galleryId).Value; - var client = new RestClient(_clientOptions); - var request = new RestRequest($"/api/gallery/{code}/related"); - - var response = await client.GetAsync(request, cancellationToken); - if (response == null) - { - throw new Exception("Unable to fetch recommendations"); - } - - return response.Result + var request = await _gallery.FetchRecommended(code, cancellationToken); + return request.Result .Select(x => x.ToSeries()) .ToList(); } public override async Task GetImage(string remotePath, CancellationToken cancellationToken = default) { - var client = new RestClient(_imageClientOptions); - var request = new RestRequest(remotePath); - - var data = await client.DownloadDataAsync(request, cancellationToken); - if (data == null) + var pathArguments = remotePath.Split(","); + if (pathArguments.Length != 2) { - throw new Exception("Unable to download file."); + throw new Exception($"Unable to download due to malformed remote path. remotePath: {remotePath}"); } - return data; + + var request = await _galleryImage.GetImage(pathArguments[0], pathArguments[1], cancellationToken); + return await request.ReadAsByteArrayAsync(cancellationToken); } /// @@ -194,6 +164,26 @@ private static List ReadCookiesFromFile() : []; } + private HttpClient CreateHttpClient(string hostname) + { + var handler = new HttpClientHandler(); + + // Read cookies from file and load them into RestClientOptions + foreach (var cookie in ReadCookiesFromFile()) + { + handler.CookieContainer.Add(cookie); + } + + var httpClient = new HttpClient(handler) + { + BaseAddress = new Uri(hostname) + }; + httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd( + GetUserAgentFromFile() ?? $"asuka.Provider.Nhentai {Version.Major}.{Version.Minor}"); + + return httpClient; + } + [GeneratedRegex(@"^http(s)?:\/\/(nhentai\.net)\b([//g]*)\b([\d]{1,6})\/?$")] private static partial Regex FullUrlRegex(); diff --git a/asuka.Provider.Nhentai/asuka.Provider.Nhentai.csproj b/asuka.Provider.Nhentai/asuka.Provider.Nhentai.csproj index 28411c4..1ed4f2a 100644 --- a/asuka.Provider.Nhentai/asuka.Provider.Nhentai.csproj +++ b/asuka.Provider.Nhentai/asuka.Provider.Nhentai.csproj @@ -7,8 +7,8 @@ latestmajor true false - 1.0.0.0 - 1.0.0.0 + 1.1.0.0 + 1.1.0.0 @@ -16,7 +16,7 @@ - +