using System.Net; using Cleanuparr.Domain.Enums; using Cleanuparr.Persistence.Models.Configuration.General; using Microsoft.Extensions.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Cleanuparr.Infrastructure.Http.DynamicHttpClientSystem; /// /// Implementation of the dynamic HttpClient factory /// public class DynamicHttpClientFactory : IDynamicHttpClientFactory { private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientConfigStore _configStore; private readonly IOptionsMonitorCache _optionsCache; private readonly ILogger _logger; public DynamicHttpClientFactory( IHttpClientFactory httpClientFactory, IHttpClientConfigStore configStore, IOptionsMonitorCache optionsCache, ILogger logger) { _httpClientFactory = httpClientFactory; _configStore = configStore; _optionsCache = optionsCache; _logger = logger; } public HttpClient CreateClient(string clientName, HttpClientConfig config) { _configStore.AddConfiguration(clientName, config); return _httpClientFactory.CreateClient(clientName); } public HttpClient CreateClient(string clientName) { if (!_configStore.TryGetConfiguration(clientName, out _)) { throw new InvalidOperationException($"No configuration found for client '{clientName}'. Register configuration first."); } return _httpClientFactory.CreateClient(clientName); } public void RegisterConfiguration(string clientName, HttpClientConfig config) { _configStore.AddConfiguration(clientName, config); } public void RegisterRetryClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent) { HttpClientConfig config = new() { Name = clientName, Timeout = timeout, Type = HttpClientType.WithRetry, RetryConfig = retryConfig, CertificateValidationType = certificateType, SendUserAgent = sendUserAgent }; RegisterConfiguration(clientName, config); } public void RegisterDelugeClient(string clientName, int timeout, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent) { HttpClientConfig config = new() { Name = clientName, Timeout = timeout, Type = HttpClientType.Deluge, RetryConfig = retryConfig, AllowAutoRedirect = true, CertificateValidationType = certificateType, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, SendUserAgent = sendUserAgent }; RegisterConfiguration(clientName, config); } public void RegisterDownloadClient(string clientName, int timeout, HttpClientType clientType, RetryConfig retryConfig, CertificateValidationType certificateType, bool sendUserAgent) { HttpClientConfig config = new() { Name = clientName, Timeout = timeout, Type = clientType, RetryConfig = retryConfig, CertificateValidationType = certificateType, SendUserAgent = sendUserAgent }; // Configure Deluge-specific settings if needed if (clientType == HttpClientType.Deluge) { config.AllowAutoRedirect = true; config.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; } RegisterConfiguration(clientName, config); } public void RegisterPlainClient(string clientName, bool sendUserAgent) { HttpClientConfig config = new() { Name = clientName, Type = HttpClientType.Plain, SendUserAgent = sendUserAgent }; RegisterConfiguration(clientName, config); } public void UnregisterConfiguration(string clientName) { _configStore.RemoveConfiguration(clientName); // Also invalidate the cached options for this client _optionsCache.TryRemove(clientName); _logger.LogDebug("Unregistered and invalidated HTTP client configuration: {ClientName}", clientName); } public void UpdateAllClientsFromGeneralConfig(GeneralConfig generalConfig) { var allConfigurations = _configStore.GetAllConfigurations().ToList(); if (!allConfigurations.Any()) { _logger.LogDebug("No HTTP client configurations to update"); return; } var updatedConfigurations = allConfigurations.Select(kvp => { var config = kvp.Value; // Update timeout and certificate validation for all clients config.Timeout = generalConfig.HttpTimeout; config.CertificateValidationType = generalConfig.HttpCertificateValidation; config.SendUserAgent = generalConfig.HttpSendUserAgent; // Update retry configuration if it exists if (config.RetryConfig != null) { config.RetryConfig.MaxRetries = generalConfig.HttpMaxRetries; } return new KeyValuePair(kvp.Key, config); }).ToList(); // Apply all updates to our configuration store _configStore.UpdateConfigurations(updatedConfigurations); // CRITICAL: Invalidate IHttpClientFactory's cached configurations // This forces the factory to call our Configure() method again with updated settings var clientNames = updatedConfigurations.Select(kvp => kvp.Key).ToList(); foreach (string clientName in clientNames) { _optionsCache.TryRemove(clientName); } _logger.LogInformation("Updated and invalidated {Count} HTTP client configurations with new general settings: " + "Timeout={Timeout}s, MaxRetries={MaxRetries}, CertificateValidation={CertValidation}", updatedConfigurations.Count, generalConfig.HttpTimeout, generalConfig.HttpMaxRetries, generalConfig.HttpCertificateValidation); } }