diff --git a/Telegrator.Analyzers/GlobalSuppressions.cs b/Telegrator.Analyzers/GlobalSuppressions.cs new file mode 100644 index 0000000..361f387 --- /dev/null +++ b/Telegrator.Analyzers/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0090")] diff --git a/Telegrator.Generators/GlobalSuppressions.cs b/Telegrator.Generators/GlobalSuppressions.cs new file mode 100644 index 0000000..361f387 --- /dev/null +++ b/Telegrator.Generators/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0090")] diff --git a/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs b/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs index b2cccf6..b62ac92 100644 --- a/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs +++ b/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; @@ -22,7 +21,7 @@ namespace Telegrator.Hosting.Web.Polling private readonly ITelegramBotWebHost _botHost; private readonly ITelegramBotClient _botClient; private readonly IUpdateRouter _updateRouter; - private readonly TelegramBotWebOptions _options; + private readonly TelegratorWebOptions _options; /// /// Initiallizes new instance of @@ -32,7 +31,7 @@ namespace Telegrator.Hosting.Web.Polling /// /// /// - public HostedUpdateWebhooker(ITelegramBotWebHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options) + public HostedUpdateWebhooker(ITelegramBotWebHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options) { if (string.IsNullOrEmpty(options.Value.WebhookUri)) throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving"); diff --git a/Telegrator.Hosting.Web/TelegramBotWebHost.cs b/Telegrator.Hosting.Web/TelegramBotWebHost.cs index 5686ea4..44eb3e8 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebHost.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebHost.cs @@ -5,8 +5,10 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System.Text; using Telegram.Bot.Types.Enums; +using Telegrator.Configuration; using Telegrator.Hosting.Components; using Telegrator.Hosting.Providers; using Telegrator.Hosting.Web.Components; @@ -22,7 +24,7 @@ namespace Telegrator.Hosting.Web { private readonly WebApplication _innerApp; private readonly IUpdateRouter _updateRouter; - private readonly ILogger _logger; + private readonly ILogger _logger; private bool _disposed; @@ -43,7 +45,7 @@ namespace Telegrator.Hosting.Web /// /// This application's logger /// - public ILogger Logger => _logger; + public ILogger Logger => _logger; // Private interface fields IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services; @@ -53,12 +55,20 @@ namespace Telegrator.Hosting.Web internal TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder, HostHandlersCollection handlers) { + // Registering this host in services for easy access RegisterHostServices(webApplicationBuilder, handlers); + + // Building proxy application _innerApp = webApplicationBuilder.Build(); - _updateRouter = Services.GetRequiredService(); - _logger = Services.GetRequiredService>(); + // Initializing bot info, as it requires to make a request via tg bot + Services.GetRequiredService(); + // Reruesting services for this host + _updateRouter = Services.GetRequiredService(); + _logger = Services.GetRequiredService>(); + + // Logging registering handlers in DEBUG purposes LogHandlers(handlers); } diff --git a/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs b/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs index cab6f3d..65b9402 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs @@ -43,10 +43,6 @@ namespace Telegrator.Hosting.Web _innerBuilder = webApplicationBuilder; _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _handlers = new HostHandlersCollection(Services, _settings); - - Services.AddSingleton>(Options.Create(settings)); - Services.Configure(Configuration.GetSection(nameof(TelegratorOptions))); - Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); } /// @@ -67,6 +63,14 @@ namespace Telegrator.Hosting.Web } } + if (!_settings.DisableAutoConfigure) + { + Services.Configure(Configuration.GetSection(nameof(TelegratorWebOptions))); + Services.Configure(Configuration.GetSection(nameof(TelegratorOptions))); + Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); + } + + Services.AddSingleton>(Options.Create(_settings)); return new TelegramBotWebHost(_innerBuilder, _handlers); } } diff --git a/Telegrator.Hosting.Web/TelegramBotWebOptions.cs b/Telegrator.Hosting.Web/TelegramBotWebOptions.cs index 3682259..267ccb6 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebOptions.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebOptions.cs @@ -1,37 +1,18 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Options; using System.Diagnostics.CodeAnalysis; namespace Telegrator.Hosting.Web { /// - /// Configuration options for Telegram bot behavior and execution settings. - /// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies. + /// Options for configuring the behavior for TelegramBotWebHost. /// public class TelegramBotWebOptions : TelegratorOptions { /// - /// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration + /// Disables automatic configuration for all of required instances /// - [StringSyntax(StringSyntaxAttribute.Uri)] - public required string WebhookUri { get; set; } - - /// - /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, 1-256 characters. - /// Only characters A-Z, a-z, 0-9, _ and - are allowed. - /// The header is useful to ensure that the request comes from a webhook set by you. - /// - public string? SecretToken { get; set; } - - /// - /// The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. - /// Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. - /// - public int MaxConnections { get; set; } = 40; - - /// - /// Pass true to drop all pending updates - /// - public bool DropPendingUpdates { get; set; } + public bool DisableAutoConfigure { get; set; } /// public string[]? Args { get; init; } diff --git a/Telegrator.Hosting.Web/TelegratorWebOptions.cs b/Telegrator.Hosting.Web/TelegratorWebOptions.cs new file mode 100644 index 0000000..2b809f9 --- /dev/null +++ b/Telegrator.Hosting.Web/TelegratorWebOptions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Telegrator.Hosting.Web +{ + /// + /// Configuration options for Telegram bot behavior and execution settings. + /// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies. + /// + public class TelegratorWebOptions + { + /// + /// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration + /// + [StringSyntax(StringSyntaxAttribute.Uri)] + public required string WebhookUri { get; set; } + + /// + /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, 1-256 characters. + /// Only characters A-Z, a-z, 0-9, _ and - are allowed. + /// The header is useful to ensure that the request comes from a webhook set by you. + /// + public string? SecretToken { get; set; } + + /// + /// The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. + /// Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. + /// + public int MaxConnections { get; set; } = 40; + + /// + /// Pass true to drop all pending updates + /// + public bool DropPendingUpdates { get; set; } + } +} diff --git a/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs b/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs index 82bb1a2..ad21b7a 100644 --- a/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs +++ b/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Telegrator.MadiatorCore.Descriptors; using Telegrator.Polling; namespace Telegrator.Hosting.Polling @@ -9,12 +8,5 @@ namespace Telegrator.Hosting.Polling public class HostUpdateHandlersPool(IOptions options, ILogger logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken) { private readonly ILogger _logger = logger; - - /// - protected override async Task ExecuteHandlerWrapper(DescribedHandlerInfo enqueuedHandler) - { - //_logger.LogInformation("Handler \"{0}\" has entered execution pool", enqueuedHandler.DisplayString); - await base.ExecuteHandlerWrapper(enqueuedHandler); - } } } diff --git a/Telegrator.Hosting/TelegramBotHostBuilder.cs b/Telegrator.Hosting/TelegramBotHostBuilder.cs index a5b3d39..0bba6bc 100644 --- a/Telegrator.Hosting/TelegramBotHostBuilder.cs +++ b/Telegrator.Hosting/TelegramBotHostBuilder.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Telegram.Bot; using Telegram.Bot.Polling; using Telegrator.Hosting; @@ -46,6 +47,7 @@ namespace Telegrator.Hosting { _innerBuilder = hostApplicationBuilder; _settings = settings ?? new TelegramBotHostBuilderSettings(); + _handlers = new HostHandlersCollection(Services, _settings); _innerBuilder.Logging.ClearProviders(); @@ -71,11 +73,11 @@ namespace Telegrator.Hosting if (!_settings.DisableAutoConfigure) { - Services.Configure(Configuration.GetSection(nameof(TelegratorOptions))); Services.Configure(Configuration.GetSection(nameof(ReceiverOptions))); Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); } + Services.AddSingleton>(Options.Create(_settings)); return new TelegramBotHost(_innerBuilder, _handlers); } } diff --git a/Telegrator.Tests/TestUpdateHandler.cs b/Telegrator.Tests/TestUpdateHandler.cs index 6248edf..341fcb5 100644 --- a/Telegrator.Tests/TestUpdateHandler.cs +++ b/Telegrator.Tests/TestUpdateHandler.cs @@ -13,11 +13,11 @@ namespace Telegrator.Tests { public bool WasExecuted { get; private set; } - public override Task Execute(IAbstractHandlerContainer container, CancellationToken cancellationToken) + public override Task Execute(IAbstractHandlerContainer container, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); WasExecuted = true; - return Task.CompletedTask; + return Task.FromResult(Result.Ok()); } } } diff --git a/Telegrator/Configuration/ITelegratorOptions.cs b/Telegrator/Configuration/ITelegratorOptions.cs index 82b61b9..5c0787b 100644 --- a/Telegrator/Configuration/ITelegratorOptions.cs +++ b/Telegrator/Configuration/ITelegratorOptions.cs @@ -6,11 +6,18 @@ /// public interface ITelegratorOptions { + /* /// /// Gets or sets a value indicating whether only the first found handler should be executed for each update. /// public bool ExecuteOnlyFirstFoundHanlder { get; set; } + /// + /// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default) + /// + public bool DescendDescriptorIndex { get; set; } + */ + /// /// Gets or sets the maximum number of parallel working handlers. Null means no limit. /// @@ -21,19 +28,14 @@ /// public bool ExclusiveAwaitingHandlerRouting { get; set; } - /// - /// Gets or sets the global cancellation token for all bot operations. - /// - public CancellationToken GlobalCancellationToken { get; set; } - - /// - /// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default) - /// - public bool DescendDescriptorIndex { get; set; } - /// /// Gets or sets a value indicating whether to exclude intersecting command aliases. /// public bool ExceptIntersectingCommandAliases { get; set; } + + /// + /// Gets or sets the global cancellation token for all bot operations. + /// + public CancellationToken GlobalCancellationToken { get; set; } } } diff --git a/Telegrator/Handlers/Building/AwaiterHandler.cs b/Telegrator/Handlers/Building/AwaiterHandler.cs index 3912aa8..acfd31a 100644 --- a/Telegrator/Handlers/Building/AwaiterHandler.cs +++ b/Telegrator/Handlers/Building/AwaiterHandler.cs @@ -51,10 +51,10 @@ namespace Telegrator.Handlers.Building /// The handler container (unused). /// The cancellation token (unused). /// A completed task. - protected override Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellation) + protected override Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellation) { ResetEvent.Set(); - return Task.CompletedTask; + return Task.FromResult(Result.Ok()); } /// diff --git a/Telegrator/Handlers/Building/BuildedAbstractHandler.cs b/Telegrator/Handlers/Building/BuildedAbstractHandler.cs index ff926cd..14c215b 100644 --- a/Telegrator/Handlers/Building/BuildedAbstractHandler.cs +++ b/Telegrator/Handlers/Building/BuildedAbstractHandler.cs @@ -31,7 +31,7 @@ namespace Telegrator.Handlers.Building /// The handler container with execution context. /// The cancellation token. /// A task representing the asynchronous execution. - public override Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) + public override Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) => HandlerAction.Invoke(container, cancellation); } } diff --git a/Telegrator/Handlers/Building/HandlerBuilder.cs b/Telegrator/Handlers/Building/HandlerBuilder.cs index a9dda1c..5a00776 100644 --- a/Telegrator/Handlers/Building/HandlerBuilder.cs +++ b/Telegrator/Handlers/Building/HandlerBuilder.cs @@ -11,7 +11,7 @@ namespace Telegrator.Handlers.Building /// The handler container with execution context. /// The cancellation token. /// A task representing the asynchronous execution. - public delegate Task AbstractHandlerAction(IAbstractHandlerContainer container, CancellationToken cancellation) where TUpdate : class; + public delegate Task AbstractHandlerAction(IAbstractHandlerContainer container, CancellationToken cancellation) where TUpdate : class; /// /// Builder class for creating regular handlers that can process updates. diff --git a/Telegrator/Handlers/Components/AbstractUpdateHandler.cs b/Telegrator/Handlers/Components/AbstractUpdateHandler.cs index db5b9c4..794204b 100644 --- a/Telegrator/Handlers/Components/AbstractUpdateHandler.cs +++ b/Telegrator/Handlers/Components/AbstractUpdateHandler.cs @@ -74,10 +74,10 @@ namespace Telegrator.Handlers.Components /// The handler container. /// Cancellation token. /// A task representing the asynchronous operation. - protected override sealed async Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken) + protected override sealed async Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken) { Container = (IAbstractHandlerContainer)container; - await Execute(Container, cancellationToken); + return await Execute(Container, cancellationToken); } /// @@ -86,6 +86,6 @@ namespace Telegrator.Handlers.Components /// The handler container. /// Cancellation token. /// A task representing the asynchronous operation. - public abstract Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation); + public abstract Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation); } } diff --git a/Telegrator/Handlers/Components/BranchingUpdateHandler.cs b/Telegrator/Handlers/Components/BranchingUpdateHandler.cs index a8b6108..a15cd6f 100644 --- a/Telegrator/Handlers/Components/BranchingUpdateHandler.cs +++ b/Telegrator/Handlers/Components/BranchingUpdateHandler.cs @@ -118,13 +118,13 @@ namespace Telegrator.Handlers.Components /// The handler container. /// The cancellation token. /// Thrown when no branch method is set. - public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) + public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) { if (branchMethodInfo is null) throw new Exception(); Cancellation = cancellation; - await BranchExecuteWrapper(container, branchMethodInfo); + return await BranchExecuteWrapper(container, branchMethodInfo); } /// @@ -132,21 +132,20 @@ namespace Telegrator.Handlers.Components /// /// The handler container. /// The method to execute. - protected virtual async Task BranchExecuteWrapper(IAbstractHandlerContainer container, MethodInfo methodInfo) + protected virtual async Task BranchExecuteWrapper(IAbstractHandlerContainer container, MethodInfo methodInfo) { if (methodInfo.ReturnType == typeof(void)) { methodInfo.Invoke(this, []); - return; + return Result.Ok(); } else { object branchReturn = methodInfo.Invoke(this, []); - if (branchReturn == null) - return; - - if (branchReturn is Task branchTask) - await branchTask; + if (branchReturn is not Task branchTask) + throw new InvalidOperationException(); + + return await branchTask; } } diff --git a/Telegrator/Handlers/Components/UpdateHandlerBase.cs b/Telegrator/Handlers/Components/UpdateHandlerBase.cs index a4ef62f..d2e3a37 100644 --- a/Telegrator/Handlers/Components/UpdateHandlerBase.cs +++ b/Telegrator/Handlers/Components/UpdateHandlerBase.cs @@ -24,10 +24,16 @@ namespace Telegrator.Handlers.Components /// The for the update. /// The cancellation token. /// A representing the asynchronous operation. - public async Task Execute(IHandlerContainer container, CancellationToken cancellationToken = default) + public async Task Execute(IHandlerContainer container, CancellationToken cancellationToken = default) { - await ExecuteInternal(container, cancellationToken); - LifetimeToken.LifetimeEnded(); + try + { + return await ExecuteInternal(container, cancellationToken); + } + finally + { + LifetimeToken.LifetimeEnded(); + } } /// @@ -36,6 +42,6 @@ namespace Telegrator.Handlers.Components /// The for the update. /// The cancellation token. /// A representing the asynchronous operation. - protected abstract Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken); + protected abstract Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken); } } diff --git a/Telegrator/Handlers/Result.cs b/Telegrator/Handlers/Result.cs new file mode 100644 index 0000000..589e7e0 --- /dev/null +++ b/Telegrator/Handlers/Result.cs @@ -0,0 +1,30 @@ +namespace Telegrator.Handlers +{ + public sealed class Result + { + public bool Positive { get; } + + public bool RouteNext { get; } + + public Type? NextType { get; } + + internal Result(bool positive, bool routeNext, Type? nextType) + { + Positive = positive; + RouteNext = routeNext; + NextType = nextType; + } + + public static Result Ok() + => new Result(true, false, null); + + public static Result Fault() + => new Result(false, false, null); + + public static Result Next() + => new Result(true, true, null); + + public static Result Next() + => new Result(true, true, typeof(T)); + } +} diff --git a/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs b/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs index caa272b..f8a7eaf 100644 --- a/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs +++ b/Telegrator/MadiatorCore/Descriptors/DefaultCustomDescriptors.cs @@ -36,7 +36,7 @@ namespace Telegrator.MadiatorCore.Descriptors { private readonly MethodInfo Method = method; - public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) + public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation) { if (Method is null) throw new Exception(); @@ -44,16 +44,15 @@ namespace Telegrator.MadiatorCore.Descriptors if (Method.ReturnType == typeof(void)) { Method.Invoke(this, [container, cancellation]); - return; + return Result.Ok(); } else { object branchReturn = Method.Invoke(this, [container, cancellation]); - if (branchReturn == null) - return; + if (branchReturn is not Task branchTask) + throw new InvalidOperationException(); - if (branchReturn is Task branchTask) - await branchTask; + return await branchTask; } } } diff --git a/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs b/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs index 8506fc3..b5ea82a 100644 --- a/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs +++ b/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs @@ -2,6 +2,7 @@ using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegrator.Filters.Components; +using Telegrator.Handlers; using Telegrator.Handlers.Components; namespace Telegrator.MadiatorCore.Descriptors @@ -81,36 +82,43 @@ namespace Telegrator.MadiatorCore.Descriptors /// Cancellation token. /// A task representing the asynchronous operation. /// Thrown if the handler lifetime has ended or the handler is not a container factory. - public async Task Execute(CancellationToken cancellationToken) + public async Task Execute(CancellationToken cancellationToken) { if (HandlerLifetime.IsEnded) throw new Exception(); - IHandlerContainerFactory? containerFactory = HandlerInstance is IHandlerContainerFactory handlerDefainedContainerFactory - ? handlerDefainedContainerFactory - : UpdateRouter.DefaultContainerFactory is not null - ? UpdateRouter.DefaultContainerFactory - : throw new Exception(); - try { - HandlerContainer = containerFactory.CreateContainer(UpdateRouter.AwaitingProvider, this); - await HandlerInstance.Execute(HandlerContainer, cancellationToken); + HandlerContainer = GetContainer(UpdateRouter.AwaitingProvider, this); + return await HandlerInstance.Execute(HandlerContainer, cancellationToken); } catch (OperationCanceledException) { // Cancelled _ = 0xBAD + 0xC0DE; - return; + return Result.Ok(); } catch (Exception exception) { await UpdateRouter .HandleErrorAsync(Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken) .ConfigureAwait(false); + + return Result.Fault(); } } + private IHandlerContainer GetContainer(IAwaitingProvider awaitingProvider, DescribedHandlerInfo handlerInfo) + { + if (HandlerInstance is IHandlerContainerFactory handlerDefainedContainerFactory) + return handlerDefainedContainerFactory.CreateContainer(awaitingProvider, handlerInfo); + + if (UpdateRouter.DefaultContainerFactory is not null) + return UpdateRouter.DefaultContainerFactory.CreateContainer(awaitingProvider, handlerInfo); + + throw new Exception(); + } + /// public override string ToString() => DisplayString ?? HandlerInstance.GetType().Name; diff --git a/Telegrator/MadiatorCore/IUpdateHandlersPool.cs b/Telegrator/MadiatorCore/IUpdateHandlersPool.cs index 7547626..82b8817 100644 --- a/Telegrator/MadiatorCore/IUpdateHandlersPool.cs +++ b/Telegrator/MadiatorCore/IUpdateHandlersPool.cs @@ -7,6 +7,7 @@ namespace Telegrator.MadiatorCore /// /// The for the enqueued handler. public delegate void HandlerEnqueued(DescribedHandlerInfo args); + /// /// Represents a delegate for when a handler is executing. /// @@ -24,7 +25,7 @@ namespace Telegrator.MadiatorCore public event HandlerEnqueued? HandlerEnqueued; /// - /// Occurs when a handler is executing. + /// Occurs when a handler is entering execution. /// public event HandlerExecuting? HandlerExecuting; @@ -32,8 +33,9 @@ namespace Telegrator.MadiatorCore /// Enqueues a collection of handlers for execution. /// /// The handlers to enqueue. - public void Enqueue(IEnumerable handlers); + public Task Enqueue(IEnumerable handlers); + /* /// /// Enqueues a single handler for execution. /// @@ -45,5 +47,6 @@ namespace Telegrator.MadiatorCore /// /// The of the handler to dequeue. public void Dequeue(HandlerLifetimeToken token); + */ } } diff --git a/Telegrator/Polling/LimitedDictionary.cs b/Telegrator/Polling/LimitedDictionary.cs new file mode 100644 index 0000000..0d8bd47 --- /dev/null +++ b/Telegrator/Polling/LimitedDictionary.cs @@ -0,0 +1,52 @@ +using System.Collections; +using System.Collections.Concurrent; + +namespace Telegrator.Polling +{ + public class LimitedDictionary : IEnumerable>, IDisposable + { + private readonly int? _maximum; + private readonly SemaphoreSlim _semaphore = null!; + private readonly ConcurrentDictionary _dict = []; + + public LimitedDictionary(int? maximum) + { + _maximum = maximum; + if (maximum != null) + { + int value = maximum.Value; + _semaphore = new SemaphoreSlim(value, value); + } + } + + public async Task Add(TKey key, TValue value, CancellationToken cancellationToken) + { + if (_semaphore != null) + await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + return _dict.TryAdd(key, value); + } + + public bool Remove(TKey key, out TValue result) + { + if (_dict.TryRemove(key, out result)) + { + _semaphore?.Release(1); + return true; + } + + return false; + } + + public IEnumerator> GetEnumerator() => _dict.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _dict.GetEnumerator(); + + /// + public void Dispose() + { + GC.SuppressFinalize(this); + _semaphore.Dispose(); + } + } +} diff --git a/Telegrator/Polling/LimitedQueue.cs b/Telegrator/Polling/LimitedQueue.cs new file mode 100644 index 0000000..3d0978c --- /dev/null +++ b/Telegrator/Polling/LimitedQueue.cs @@ -0,0 +1,42 @@ +using System.Collections.Concurrent; + +namespace Telegrator.Polling +{ + public class LimitedQueue + { + private readonly int? _maximum; + private readonly ConcurrentQueue _queue = []; + private readonly SemaphoreSlim _semaphore = null!; + + public LimitedQueue(int? maximum) + { + _maximum = maximum; + if (maximum != null) + { + int value = maximum.Value; + _semaphore = new SemaphoreSlim(value, value); + } + } + + public async Task Enqueue(T item, CancellationToken cancellationToken) + { + if (_maximum.HasValue) + await _semaphore.WaitAsync(cancellationToken); + + _queue.Enqueue(item); + } + + public bool Dequeue(out T result) + { + if (_queue.TryDequeue(out result)) + { + if (_maximum.HasValue) + _semaphore?.Release(1); + + return true; + } + + return false; + } + } +} diff --git a/Telegrator/Polling/UpdateHandlersPool.cs b/Telegrator/Polling/UpdateHandlersPool.cs index b1a3ee9..2252fe2 100644 --- a/Telegrator/Polling/UpdateHandlersPool.cs +++ b/Telegrator/Polling/UpdateHandlersPool.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using Telegrator.Handlers; using Telegrator.MadiatorCore; using Telegrator.MadiatorCore.Descriptors; @@ -15,6 +16,7 @@ namespace Telegrator.Polling /// protected object SyncObj = new object(); + /* /// /// Event that signals when awaiting handlers are queued. /// @@ -34,6 +36,13 @@ namespace Telegrator.Polling /// Dictionary for tracking currently executing handlers. /// protected readonly ConcurrentDictionary ExecutingHandlersPool = []; + */ + + //protected readonly ConcurrentDictionary> AwaitingHandlersQueue; + + //protected readonly LimitedDictionary ExecutingHandlersPool; + + protected SemaphoreSlim ExecutingHandlersSemaphore = null!; /// /// The bot configuration options. @@ -65,26 +74,62 @@ namespace Telegrator.Polling { Options = options; GlobalCancellationToken = globalCancellationToken; + //AwaitingHandlersQueue = new ConcurrentDictionary>(); + //ExecutingHandlersPool = new LimitedDictionary(options.MaximumParallelWorkingHandlers); if (options.MaximumParallelWorkingHandlers != null) { - ExecutingHandlersSemaphore = new SemaphoreSlim(options.MaximumParallelWorkingHandlers ?? 0); - AwaitingHandlersQueuedEvent = new ManualResetEventSlim(false); + ExecutingHandlersSemaphore = new SemaphoreSlim(options.MaximumParallelWorkingHandlers.Value); + //AwaitingHandlersQueuedEvent = new ManualResetEventSlim(false); } + /* if (Options.MaximumParallelWorkingHandlers != null) HandlersCheckpoint(); + */ } /// - public void Enqueue(IEnumerable handlers) + public async Task Enqueue(IEnumerable handlers) { - handlers.ForEach(Enqueue); - } + //handlers.ForEach(Enqueue); + Result? lastResult = null; + foreach (DescribedHandlerInfo handlerInfo in handlers) + { + if (lastResult?.NextType != null) + { + if (lastResult.NextType != handlerInfo.HandlerInstance.GetType()) + continue; + } + + if (ExecutingHandlersSemaphore != null) + { + await ExecutingHandlersSemaphore.WaitAsync(); + } + + try + { + HandlerExecuting?.Invoke(handlerInfo); + lastResult = await handlerInfo.Execute(GlobalCancellationToken); + ExecutingHandlersSemaphore?.Release(1); + } + catch (OperationCanceledException) + { + break; + } + + if (!lastResult.RouteNext) + break; + } + } + + /* /// public void Enqueue(DescribedHandlerInfo handlerInfo) { + throw new NotImplementedException(); + if (Options.MaximumParallelWorkingHandlers == null) { Task.Run(async () => await ExecuteHandlerWrapper(handlerInfo)); @@ -111,7 +156,9 @@ namespace Telegrator.Polling ExecutingHandlersSemaphore.Release(1); } } + */ + /* /// /// Main checkpoint method that manages handler execution in a loop. /// Continuously processes queued handlers while respecting concurrency limits. @@ -206,6 +253,7 @@ namespace Telegrator.Polling return AwaitingHandlersQueue.TryDequeue(out enqueuedHandler); } } + */ /// /// Disposes of the handlers pool and releases all resources. @@ -221,11 +269,13 @@ namespace Telegrator.Polling ExecutingHandlersSemaphore = null!; } + /* if (AwaitingHandlersQueuedEvent != null) { AwaitingHandlersQueuedEvent.Dispose(); AwaitingHandlersQueuedEvent = null!; } + */ if (SyncObj != null) SyncObj = null!; diff --git a/Telegrator/Polling/UpdateRouter.cs b/Telegrator/Polling/UpdateRouter.cs index 691fc13..f75f49e 100644 --- a/Telegrator/Polling/UpdateRouter.cs +++ b/Telegrator/Polling/UpdateRouter.cs @@ -96,7 +96,7 @@ namespace Telegrator.Polling /// The update to handle. /// The cancellation token. /// A task representing the asynchronous update handling operation. - public virtual Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + public virtual async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { // Logging Alligator.RouterWriteLine("Received Update ({0}) of type \"{1}\"", update.Id, update.Type); @@ -109,31 +109,28 @@ namespace Telegrator.Polling if (handlers.Any()) { // Enqueuing found awiting handlers - HandlersPool.Enqueue(handlers); + await HandlersPool.Enqueue(handlers); // Chicking if awaiting handlers has exclusive routing if (Options.ExclusiveAwaitingHandlerRouting) { Alligator.RouterWriteLine("Receiving Update ({0}) completed with only awaiting handlers", update.Id); - return Task.CompletedTask; + return; } } // Queuing reagular handlers for execution - HandlersPool.Enqueue(GetHandlers(HandlersProvider, this, botClient, update, cancellationToken)); + await HandlersPool.Enqueue(GetHandlers(HandlersProvider, this, botClient, update, cancellationToken)); Alligator.RouterWriteLine("Receiving Update ({0}) finished", update.Id); - return Task.CompletedTask; } catch (OperationCanceledException) { Alligator.RouterWriteLine("Receiving Update ({0}) cancelled", update.Id); - return Task.CompletedTask; } catch (Exception ex) { Alligator.RouterWriteLine("Receiving Update ({0}) finished with exception {1}", update.Id, ex.Message); ExceptionHandler?.HandleException(botClient, ex, HandleErrorSource.PollingError, cancellationToken); - return Task.CompletedTask; } } @@ -162,10 +159,11 @@ namespace Telegrator.Polling return []; } - IEnumerable described = DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); - Alligator.RouterWriteLine("Described total of {0} handlers for Update ({1}) from {2} provider", described.Count(), update.Id, provider.GetType().Name); - Alligator.RouterWriteLine("Described handlers : {0}", string.Join(", ", described)); - return described; + //IEnumerable described = DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); + //Alligator.RouterWriteLine("Described total of {0} handlers for Update ({1}) from {2} provider", described.Count(), update.Id, provider.GetType().Name); + //Alligator.RouterWriteLine("Described handlers : {0}", string.Join(", ", described)); + + return DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); } /// @@ -192,8 +190,11 @@ namespace Telegrator.Polling continue; yield return describedHandler; + + /* if (Options.ExecuteOnlyFirstFoundHanlder) break; + */ } } finally diff --git a/Telegrator/TelegratorOptions.cs b/Telegrator/TelegratorOptions.cs index 6343b2d..0787b96 100644 --- a/Telegrator/TelegratorOptions.cs +++ b/Telegrator/TelegratorOptions.cs @@ -8,23 +8,24 @@ namespace Telegrator /// public class TelegratorOptions : ITelegratorOptions { + /* /// public bool ExecuteOnlyFirstFoundHanlder { get; set; } + /// + public bool DescendDescriptorIndex { get; set; } = true; + */ + /// public int? MaximumParallelWorkingHandlers { get; set; } /// public bool ExclusiveAwaitingHandlerRouting { get; set; } - /// - public CancellationToken GlobalCancellationToken { get; set; } - - /// - public bool DescendDescriptorIndex { get; set; } = true; - /// public bool ExceptIntersectingCommandAliases { get; set; } = true; + /// + public CancellationToken GlobalCancellationToken { get; set; } } }