From 8ebe1a8e51c23711262335e33e28a8524c60c4fc Mon Sep 17 00:00:00 2001 From: Roman Ciesielski <> Date: Tue, 27 Apr 2021 21:37:37 +0200 Subject: [PATCH] added queue manager --- .../ServiceCollectionExtensions.cs | 2 +- .../Client/WarcraftClient.cs | 10 ++- .../Interfaces/IQueueManager.cs | 15 ++++ .../Utilities/QueueManager.cs | 80 +++++++++++++++++++ .../Utilities/QueueManagerFactory.cs | 33 ++++++++ 5 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 src/ArgentPonyWarcraftClient/Interfaces/IQueueManager.cs create mode 100644 src/ArgentPonyWarcraftClient/Utilities/QueueManager.cs create mode 100644 src/ArgentPonyWarcraftClient/Utilities/QueueManagerFactory.cs diff --git a/src/ArgentPonyWarcraftClient.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/ArgentPonyWarcraftClient.Extensions.DependencyInjection/ServiceCollectionExtensions.cs index 4714e349..37e728bb 100644 --- a/src/ArgentPonyWarcraftClient.Extensions.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/ArgentPonyWarcraftClient.Extensions.DependencyInjection/ServiceCollectionExtensions.cs @@ -57,7 +57,7 @@ public static IServiceCollection AddWarcraftClients(this IServiceCollection serv "ArgentPonyWarcraftClient.WarcraftClient.HttpClient", client => client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")) ).AddTypedClient(httpClient => - new WarcraftClient(clientId, clientSecret, region, locale, httpClient) + new WarcraftClient(clientId, clientSecret, region, locale, httpClient, QueueManagerFactory.Instance) ); services.AddTransientUsingServiceProvider() diff --git a/src/ArgentPonyWarcraftClient/Client/WarcraftClient.cs b/src/ArgentPonyWarcraftClient/Client/WarcraftClient.cs index bf256eec..27ba1f10 100644 --- a/src/ArgentPonyWarcraftClient/Client/WarcraftClient.cs +++ b/src/ArgentPonyWarcraftClient/Client/WarcraftClient.cs @@ -14,6 +14,7 @@ public partial class WarcraftClient : IWarcraftClient private static readonly JsonSerializerOptions s_jsonSerializerOptions; private readonly HttpClient _client; + private readonly IQueueManager _queueManager; private readonly string _clientId; private readonly string _clientSecret; private readonly Region _region; @@ -54,7 +55,7 @@ public WarcraftClient(string clientId, string clientSecret) : this(clientId, cli /// Specifies the language that the result will be in. Visit /// https://develop.battle.net/documentation/world-of-warcraft/guides/localization to see a list of available locales. /// - public WarcraftClient(string clientId, string clientSecret, Region region, Locale locale) : this(clientId, clientSecret, region, locale, InternalHttpClient.Instance) + public WarcraftClient(string clientId, string clientSecret, Region region, Locale locale) : this(clientId, clientSecret, region, locale, InternalHttpClient.Instance, QueueManagerFactory.Instance) { } @@ -69,9 +70,11 @@ public WarcraftClient(string clientId, string clientSecret, Region region, Local /// https://develop.battle.net/documentation/world-of-warcraft/guides/localization to see a list of available locales. /// /// The that communicates with Blizzard. - public WarcraftClient(string clientId, string clientSecret, Region region, Locale locale, HttpClient client) + /// The that manage request sending. + public WarcraftClient(string clientId, string clientSecret, Region region, Locale locale, HttpClient client, IQueueManager queueManager) { _client = client ?? throw new ArgumentNullException(nameof(client)); + _queueManager = queueManager ?? throw new ArgumentNullException(nameof(queueManager)); _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); _clientSecret = clientSecret ?? throw new ArgumentNullException(nameof(clientSecret)); @@ -154,6 +157,9 @@ private async Task> GetAsync(string requestUri, string acces // Add an authentication header with the token. _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + //Wait for access to send + _queueManager.WaitForAccessToSend(); + // Retrieve the response. HttpResponseMessage response = await _client.GetAsync(requestUri).ConfigureAwait(false); diff --git a/src/ArgentPonyWarcraftClient/Interfaces/IQueueManager.cs b/src/ArgentPonyWarcraftClient/Interfaces/IQueueManager.cs new file mode 100644 index 00000000..f24aa983 --- /dev/null +++ b/src/ArgentPonyWarcraftClient/Interfaces/IQueueManager.cs @@ -0,0 +1,15 @@ +using System; + +namespace ArgentPonyWarcraftClient +{ + /// + /// Queue manager which grand access not often then 100 times per second + /// + public interface IQueueManager : IDisposable + { + /// + /// Wait untill acces granted. + /// + void WaitForAccessToSend(); + } +} diff --git a/src/ArgentPonyWarcraftClient/Utilities/QueueManager.cs b/src/ArgentPonyWarcraftClient/Utilities/QueueManager.cs new file mode 100644 index 00000000..b5491dd5 --- /dev/null +++ b/src/ArgentPonyWarcraftClient/Utilities/QueueManager.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ArgentPonyWarcraftClient +{ + /// + /// Queue manager which grand access not often then 100 times per second + /// + public class QueueManager : IQueueManager + { + private Queue _queue; + private CancellationTokenSource _cancelator; + private Queue _sentTimes; + private object _syncObject = new object(); + private static int _maxRequestPerSecond = 100; + private static int _second = 1000; + + /// + /// Queue manager which grand access not often then 100 times per second + /// + public QueueManager() + { + _queue = new Queue(); + _cancelator = new CancellationTokenSource(); + _sentTimes = new Queue(); + Task.Run(CheckAndGrand, _cancelator.Token); + } + + /// + /// Wait untill acces granted. + /// + public void WaitForAccessToSend() + { + AutoResetEvent autoEvent = new AutoResetEvent(false); + lock(_syncObject) + { + _queue.Enqueue(autoEvent); + } + autoEvent.WaitOne(); + autoEvent.Dispose(); + } + + /// + /// Stop grant access + /// + public void Dispose() + { + + _cancelator.Cancel(); + } + + + private void CheckAndGrand() + { + while (true) + { + lock(_syncObject) + { + if(_queue.Count > 0) + { + if(_sentTimes.Count == _maxRequestPerSecond) + { + double delta = (DateTime.Now - _sentTimes.Dequeue()).TotalMilliseconds; + if(delta <= 1 * _second) + { + Thread.Sleep((int)(1000 - delta)); + } + } + _queue.Dequeue().Set(); + _sentTimes.Enqueue(DateTime.Now); + } + } + } + } + } +} diff --git a/src/ArgentPonyWarcraftClient/Utilities/QueueManagerFactory.cs b/src/ArgentPonyWarcraftClient/Utilities/QueueManagerFactory.cs new file mode 100644 index 00000000..5fb2d1ac --- /dev/null +++ b/src/ArgentPonyWarcraftClient/Utilities/QueueManagerFactory.cs @@ -0,0 +1,33 @@ +using System.Net.Http; +using System.Net.Http.Headers; + +namespace ArgentPonyWarcraftClient +{ + /// + /// QueueManagerFactory + /// + public static class QueueManagerFactory + { + private static IQueueManager _instance; + + /// + /// Gets the current IQueueManager instance. + /// + public static IQueueManager Instance + { + get + { + if (_instance != null) + { + return _instance; + } + else + { + _instance = new QueueManager(); + + return _instance; + } + } + } + } +}