diff --git a/Epub/KoeBook.Epub/Contracts/Services/IScrapingClientService.cs b/Epub/KoeBook.Epub/Contracts/Services/IScrapingClientService.cs
index 44cff57..c2a0b81 100644
--- a/Epub/KoeBook.Epub/Contracts/Services/IScrapingClientService.cs
+++ b/Epub/KoeBook.Epub/Contracts/Services/IScrapingClientService.cs
@@ -1,10 +1,18 @@
-namespace KoeBook.Epub.Contracts.Services;
+using System.Net.Http.Headers;
+
+namespace KoeBook.Epub.Contracts.Services;
public interface IScrapingClientService
{
///
/// スクレイピングでGETする用
- /// APIは不要
+ /// APIを叩く際は不要
+ ///
+ Task GetAsStringAsync(string url, CancellationToken ct);
+
+ ///
+ /// スクレイピングでGETする用
+ /// APIを叩く際は不要
///
- ValueTask GetAsync(string url, CancellationToken ct);
+ Task GetAsStreamAsync(string url, Stream destination, CancellationToken ct);
}
diff --git a/Epub/KoeBook.Epub/Services/ScrapingClientService.cs b/Epub/KoeBook.Epub/Services/ScrapingClientService.cs
index 9a0ab20..1367980 100644
--- a/Epub/KoeBook.Epub/Services/ScrapingClientService.cs
+++ b/Epub/KoeBook.Epub/Services/ScrapingClientService.cs
@@ -1,22 +1,103 @@
-using KoeBook.Epub.Contracts.Services;
+using System.Net.Http.Headers;
+using KoeBook.Epub.Contracts.Services;
namespace KoeBook.Epub.Services;
-public sealed class ScrapingClientService(IHttpClientFactory httpClientFactory, TimeProvider timeProvider) : IScrapingClientService, IDisposable
+public sealed class ScrapingClientService : IScrapingClientService, IDisposable
{
- private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
- private readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(10), timeProvider);
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly PeriodicTimer _periodicTimer;
+ private readonly Queue> _actionQueue = [];
+ private bool _workerActivated;
+
+ public ScrapingClientService(IHttpClientFactory httpClientFactory, TimeProvider timeProvider)
+ {
+ _httpClientFactory = httpClientFactory;
+ _periodicTimer = new(TimeSpan.FromSeconds(10), timeProvider);
+
+ Worker();
+ }
public void Dispose()
{
_periodicTimer.Dispose();
}
- public async ValueTask GetAsync(string url, CancellationToken ct)
+ private async void Worker()
+ {
+ lock (_actionQueue)
+ {
+ _workerActivated = true;
+ }
+
+ while (await _periodicTimer.WaitForNextTickAsync().ConfigureAwait(false) && _actionQueue.Count > 0)
+ {
+ if (_actionQueue.TryDequeue(out var action))
+ {
+ await action(_httpClientFactory.CreateClient()).ConfigureAwait(false);
+ }
+ }
+
+ lock (_actionQueue)
+ {
+ _workerActivated = false;
+ }
+ }
+
+ public Task GetAsStringAsync(string url, CancellationToken ct)
{
- await _periodicTimer.WaitForNextTickAsync(ct).ConfigureAwait(false);
+ var taskCompletion = new TaskCompletionSource();
+ _actionQueue.Enqueue(async httpClient =>
+ {
+ if (ct.IsCancellationRequested)
+ taskCompletion.SetCanceled(ct);
+
+ try
+ {
+ var response = await httpClient.GetAsync(url, ct).ConfigureAwait(false);
+ taskCompletion.SetResult(await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false));
+ }
+ catch (Exception ex)
+ {
+ taskCompletion.SetException(ex);
+ }
+ });
+
+ lock (_actionQueue)
+ {
+ if (!_workerActivated)
+ Worker();
+ }
+
+ return taskCompletion.Task;
+ }
+
+ public Task GetAsStreamAsync(string url, Stream destination, CancellationToken ct)
+ {
+ var taskCompletion = new TaskCompletionSource();
+ _actionQueue.Enqueue(async httpClient =>
+ {
+ if (ct.IsCancellationRequested)
+ taskCompletion.SetCanceled(ct);
+
+ try
+ {
+ var response = await httpClient.GetAsync(url, ct).ConfigureAwait(false);
+ await response.Content.CopyToAsync(destination, ct).ConfigureAwait(false);
+ taskCompletion.SetResult(response.Content.Headers.ContentDisposition);
+ }
+ catch (Exception ex)
+ {
+ taskCompletion.SetException(ex);
+ }
+ });
+
+ lock (_actionQueue)
+ {
+ if (!_workerActivated)
+ Worker();
+ }
- var httpClient = _httpClientFactory.CreateClient();
- return await httpClient.GetAsync(url, ct).ConfigureAwait(false);
+ return taskCompletion.Task;
}
}