From 0e99a510a8a5b2082d858a137cc74d1e02e31ce0 Mon Sep 17 00:00:00 2001 From: Flaminel Date: Thu, 15 May 2025 18:15:42 +0300 Subject: [PATCH] #5 --- .../Configuration/IIgnoredDownloadsConfig.cs | 6 - .../IgnoredDownloadsConfig.cs | 15 ++ .../Controllers/ConfigurationController.cs | 111 +++++++++--- .../Controllers/IgnoredDownloadsController.cs | 121 +++++++++++++ .../DependencyInjection/ServicesDI.cs | 4 +- .../Configuration/ConfigInitializer.cs | 16 ++ .../Configuration/ConfigurationManager.cs | 28 ++- .../Providers/IgnoredDownloadsProvider.cs | 114 ------------ .../Services/ConfigurationService.cs | 155 ++++++---------- .../Services/IgnoredDownloadsService.cs | 171 ++++++++++++++++++ .../ContentBlocker/ContentBlocker.cs | 10 +- .../DownloadCleaner/DownloadCleaner.cs | 10 +- .../Verticals/QueueCleaner/QueueCleaner.cs | 10 +- 13 files changed, 511 insertions(+), 260 deletions(-) delete mode 100644 code/Common/Configuration/IIgnoredDownloadsConfig.cs create mode 100644 code/Common/Configuration/IgnoredDownloads/IgnoredDownloadsConfig.cs create mode 100644 code/Executable/Controllers/IgnoredDownloadsController.cs delete mode 100644 code/Infrastructure/Providers/IgnoredDownloadsProvider.cs create mode 100644 code/Infrastructure/Services/IgnoredDownloadsService.cs diff --git a/code/Common/Configuration/IIgnoredDownloadsConfig.cs b/code/Common/Configuration/IIgnoredDownloadsConfig.cs deleted file mode 100644 index f08e445a..00000000 --- a/code/Common/Configuration/IIgnoredDownloadsConfig.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Common.Configuration; - -public interface IIgnoredDownloadsConfig -{ - string? IgnoredDownloadsPath { get; } -} \ No newline at end of file diff --git a/code/Common/Configuration/IgnoredDownloads/IgnoredDownloadsConfig.cs b/code/Common/Configuration/IgnoredDownloads/IgnoredDownloadsConfig.cs new file mode 100644 index 00000000..f66b9020 --- /dev/null +++ b/code/Common/Configuration/IgnoredDownloads/IgnoredDownloadsConfig.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.Configuration; + +namespace Common.Configuration.IgnoredDownloads; + +/// +/// Configuration for ignored downloads +/// +public class IgnoredDownloadsConfig +{ + /// + /// List of download IDs to be ignored by all jobs + /// + [ConfigurationKeyName("IGNORED_DOWNLOADS")] + public List IgnoredDownloads { get; init; } = new(); +} diff --git a/code/Executable/Controllers/ConfigurationController.cs b/code/Executable/Controllers/ConfigurationController.cs index 8232a091..03aef25d 100644 --- a/code/Executable/Controllers/ConfigurationController.cs +++ b/code/Executable/Controllers/ConfigurationController.cs @@ -1,10 +1,10 @@ using Common.Configuration.ContentBlocker; using Common.Configuration.DownloadCleaner; +using Common.Configuration.DownloadClient; +using Common.Configuration.IgnoredDownloads; using Common.Configuration.QueueCleaner; using Infrastructure.Services; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using System.Text.Json; namespace Executable.Controllers; @@ -13,34 +13,23 @@ namespace Executable.Controllers; public class ConfigurationController : ControllerBase { private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly IOptionsMonitor _queueCleanerConfig; - private readonly IOptionsMonitor _contentBlockerConfig; - private readonly IOptionsMonitor _downloadCleanerConfig; private readonly IConfigurationService _configService; public ConfigurationController( ILogger logger, - IConfiguration configuration, - IOptionsMonitor queueCleanerConfig, - IOptionsMonitor contentBlockerConfig, - IOptionsMonitor downloadCleanerConfig, IConfigurationService configService) { _logger = logger; - _configuration = configuration; - _queueCleanerConfig = queueCleanerConfig; - _contentBlockerConfig = contentBlockerConfig; - _downloadCleanerConfig = downloadCleanerConfig; _configService = configService; } [HttpGet("queuecleaner")] - public IActionResult GetQueueCleanerConfig() + public async Task GetQueueCleanerConfig() { try { - return Ok(_queueCleanerConfig.CurrentValue); + var config = await _configService.GetQueueCleanerConfigAsync(); + return Ok(config); } catch (Exception ex) { @@ -50,11 +39,12 @@ public class ConfigurationController : ControllerBase } [HttpGet("contentblocker")] - public IActionResult GetContentBlockerConfig() + public async Task GetContentBlockerConfig() { try { - return Ok(_contentBlockerConfig.CurrentValue); + var config = await _configService.GetContentBlockerConfigAsync(); + return Ok(config); } catch (Exception ex) { @@ -64,11 +54,12 @@ public class ConfigurationController : ControllerBase } [HttpGet("downloadcleaner")] - public IActionResult GetDownloadCleanerConfig() + public async Task GetDownloadCleanerConfig() { try { - return Ok(_downloadCleanerConfig.CurrentValue); + var config = await _configService.GetDownloadCleanerConfigAsync(); + return Ok(config); } catch (Exception ex) { @@ -76,6 +67,36 @@ public class ConfigurationController : ControllerBase return StatusCode(500, "An error occurred while retrieving DownloadCleaner configuration"); } } + + [HttpGet("downloadclient")] + public async Task GetDownloadClientConfig() + { + try + { + var config = await _configService.GetDownloadClientConfigAsync(); + return Ok(config); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error retrieving DownloadClient configuration"); + return StatusCode(500, "An error occurred while retrieving DownloadClient configuration"); + } + } + + [HttpGet("ignoreddownloads")] + public async Task GetIgnoredDownloadsConfig() + { + try + { + var config = await _configService.GetIgnoredDownloadsConfigAsync(); + return Ok(config); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error retrieving IgnoredDownloads configuration"); + return StatusCode(500, "An error occurred while retrieving IgnoredDownloads configuration"); + } + } [HttpPut("queuecleaner")] public async Task UpdateQueueCleanerConfig([FromBody] QueueCleanerConfig config) @@ -86,7 +107,7 @@ public class ConfigurationController : ControllerBase config.Validate(); // Persist the configuration - var result = await _configService.UpdateConfigurationAsync(QueueCleanerConfig.SectionName, config); + var result = await _configService.UpdateQueueCleanerConfigAsync(config); if (!result) { return StatusCode(500, "Failed to save QueueCleaner configuration"); @@ -111,7 +132,7 @@ public class ConfigurationController : ControllerBase config.Validate(); // Persist the configuration - var result = await _configService.UpdateConfigurationAsync(ContentBlockerConfig.SectionName, config); + var result = await _configService.UpdateContentBlockerConfigAsync(config); if (!result) { return StatusCode(500, "Failed to save ContentBlocker configuration"); @@ -136,7 +157,7 @@ public class ConfigurationController : ControllerBase config.Validate(); // Persist the configuration - var result = await _configService.UpdateConfigurationAsync(DownloadCleanerConfig.SectionName, config); + var result = await _configService.UpdateDownloadCleanerConfigAsync(config); if (!result) { return StatusCode(500, "Failed to save DownloadCleaner configuration"); @@ -151,4 +172,48 @@ public class ConfigurationController : ControllerBase return StatusCode(500, "An error occurred while updating DownloadCleaner configuration"); } } + + [HttpPut("downloadclient")] + public async Task UpdateDownloadClientConfig([FromBody] DownloadClientConfig config) + { + try + { + // Persist the configuration + var result = await _configService.UpdateDownloadClientConfigAsync(config); + if (!result) + { + return StatusCode(500, "Failed to save DownloadClient configuration"); + } + + _logger.LogInformation("DownloadClient configuration updated successfully"); + return Ok(new { Message = "DownloadClient configuration updated successfully" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating DownloadClient configuration"); + return StatusCode(500, "An error occurred while updating DownloadClient configuration"); + } + } + + [HttpPut("ignoreddownloads")] + public async Task UpdateIgnoredDownloadsConfig([FromBody] IgnoredDownloadsConfig config) + { + try + { + // Persist the configuration + var result = await _configService.UpdateIgnoredDownloadsConfigAsync(config); + if (!result) + { + return StatusCode(500, "Failed to save IgnoredDownloads configuration"); + } + + _logger.LogInformation("IgnoredDownloads configuration updated successfully"); + return Ok(new { Message = "IgnoredDownloads configuration updated successfully" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating IgnoredDownloads configuration"); + return StatusCode(500, "An error occurred while updating IgnoredDownloads configuration"); + } + } } diff --git a/code/Executable/Controllers/IgnoredDownloadsController.cs b/code/Executable/Controllers/IgnoredDownloadsController.cs new file mode 100644 index 00000000..1e478ee3 --- /dev/null +++ b/code/Executable/Controllers/IgnoredDownloadsController.cs @@ -0,0 +1,121 @@ +using Common.Configuration.IgnoredDownloads; +using Infrastructure.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Executable.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class IgnoredDownloadsController : ControllerBase +{ + private readonly ILogger _logger; + private readonly IIgnoredDownloadsService _ignoredDownloadsService; + + public IgnoredDownloadsController( + ILogger logger, + IIgnoredDownloadsService ignoredDownloadsService) + { + _logger = logger; + _ignoredDownloadsService = ignoredDownloadsService; + } + + /// + /// Get all ignored downloads + /// + [HttpGet] + public async Task GetIgnoredDownloads() + { + try + { + var ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); + return Ok(ignoredDownloads); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error retrieving ignored downloads"); + return StatusCode(500, "An error occurred while retrieving ignored downloads"); + } + } + + /// + /// Add a new download ID to be ignored + /// + [HttpPost] + public async Task AddIgnoredDownload([FromBody] string downloadId) + { + try + { + if (string.IsNullOrWhiteSpace(downloadId)) + { + return BadRequest("Download ID cannot be empty"); + } + + var result = await _ignoredDownloadsService.AddIgnoredDownloadAsync(downloadId); + if (!result) + { + return StatusCode(500, "Failed to add download ID to ignored list"); + } + + _logger.LogInformation("Added download ID to ignored list: {DownloadId}", downloadId); + return Ok(new { Message = $"Download ID '{downloadId}' added to ignored list" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error adding download ID to ignored list"); + return StatusCode(500, "An error occurred while adding download ID to ignored list"); + } + } + + /// + /// Remove a download ID from the ignored list + /// + [HttpDelete("{downloadId}")] + public async Task RemoveIgnoredDownload(string downloadId) + { + try + { + if (string.IsNullOrWhiteSpace(downloadId)) + { + return BadRequest("Download ID cannot be empty"); + } + + var result = await _ignoredDownloadsService.RemoveIgnoredDownloadAsync(downloadId); + if (!result) + { + return StatusCode(500, "Failed to remove download ID from ignored list"); + } + + _logger.LogInformation("Removed download ID from ignored list: {DownloadId}", downloadId); + return Ok(new { Message = $"Download ID '{downloadId}' removed from ignored list" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error removing download ID from ignored list"); + return StatusCode(500, "An error occurred while removing download ID from ignored list"); + } + } + + /// + /// Clear all ignored downloads + /// + [HttpDelete] + public async Task ClearIgnoredDownloads() + { + try + { + var result = await _ignoredDownloadsService.ClearIgnoredDownloadsAsync(); + if (!result) + { + return StatusCode(500, "Failed to clear ignored downloads list"); + } + + _logger.LogInformation("Cleared all ignored downloads"); + return Ok(new { Message = "All ignored downloads have been cleared" }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error clearing ignored downloads list"); + return StatusCode(500, "An error occurred while clearing ignored downloads list"); + } + } +} diff --git a/code/Executable/DependencyInjection/ServicesDI.cs b/code/Executable/DependencyInjection/ServicesDI.cs index 232ab1c2..4ba70d1c 100644 --- a/code/Executable/DependencyInjection/ServicesDI.cs +++ b/code/Executable/DependencyInjection/ServicesDI.cs @@ -50,7 +50,5 @@ public static class ServicesDI .AddTransient() .AddTransient() .AddSingleton() - .AddSingleton>() - .AddSingleton>() - .AddSingleton>(); + .AddSingleton(); } \ No newline at end of file diff --git a/code/Infrastructure/Configuration/ConfigInitializer.cs b/code/Infrastructure/Configuration/ConfigInitializer.cs index 6417d542..4da05a0d 100644 --- a/code/Infrastructure/Configuration/ConfigInitializer.cs +++ b/code/Infrastructure/Configuration/ConfigInitializer.cs @@ -2,6 +2,7 @@ using Common.Configuration.Arr; using Common.Configuration.ContentBlocker; using Common.Configuration.DownloadCleaner; using Common.Configuration.DownloadClient; +using Common.Configuration.IgnoredDownloads; using Common.Configuration.QueueCleaner; using Microsoft.Extensions.Logging; @@ -35,6 +36,7 @@ public class ConfigInitializer await EnsureSonarrConfigAsync(); await EnsureRadarrConfigAsync(); await EnsureLidarrConfigAsync(); + await EnsureIgnoredDownloadsConfigAsync(); _logger.LogInformation("Configuration files initialized"); } @@ -173,4 +175,18 @@ public class ConfigInitializer await _configManager.SaveLidarrConfigAsync(defaultConfig); } } + + private async Task EnsureIgnoredDownloadsConfigAsync() + { + var config = await _configManager.GetIgnoredDownloadsConfigAsync(); + if (config == null) + { + _logger.LogInformation("Creating default IgnoredDownloads configuration"); + var defaultConfig = new IgnoredDownloadsConfig + { + IgnoredDownloads = new List() + }; + await _configManager.SaveIgnoredDownloadsConfigAsync(defaultConfig); + } + } } diff --git a/code/Infrastructure/Configuration/ConfigurationManager.cs b/code/Infrastructure/Configuration/ConfigurationManager.cs index c9f263d2..15bd956e 100644 --- a/code/Infrastructure/Configuration/ConfigurationManager.cs +++ b/code/Infrastructure/Configuration/ConfigurationManager.cs @@ -3,6 +3,7 @@ using Common.Configuration.Arr; using Common.Configuration.ContentBlocker; using Common.Configuration.DownloadCleaner; using Common.Configuration.DownloadClient; +using Common.Configuration.IgnoredDownloads; using Common.Configuration.QueueCleaner; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -30,6 +31,7 @@ public interface IConfigurationManager Task GetQueueCleanerConfigAsync(); Task GetDownloadCleanerConfigAsync(); Task GetDownloadClientConfigAsync(); + Task GetIgnoredDownloadsConfigAsync(); Task SaveSonarrConfigAsync(SonarrConfig config); Task SaveRadarrConfigAsync(RadarrConfig config); @@ -38,6 +40,7 @@ public interface IConfigurationManager Task SaveQueueCleanerConfigAsync(QueueCleanerConfig config); Task SaveDownloadCleanerConfigAsync(DownloadCleanerConfig config); Task SaveDownloadClientConfigAsync(DownloadClientConfig config); + Task SaveIgnoredDownloadsConfigAsync(IgnoredDownloadsConfig config); } public class ConfigurationManager : IConfigurationManager @@ -51,6 +54,7 @@ public class ConfigurationManager : IConfigurationManager private readonly IOptionsMonitor _queueCleanerConfig; private readonly IOptionsMonitor _downloadCleanerConfig; private readonly IOptionsMonitor _downloadClientConfig; + private readonly IOptionsMonitor _ignoredDownloadsConfig; // Define standard config file names private const string SonarrConfigFile = "sonarr.json"; @@ -60,6 +64,7 @@ public class ConfigurationManager : IConfigurationManager private const string QueueCleanerConfigFile = "queuecleaner.json"; private const string DownloadCleanerConfigFile = "downloadcleaner.json"; private const string DownloadClientConfigFile = "downloadclient.json"; + private const string IgnoredDownloadsConfigFile = "ignoreddownloads.json"; public ConfigurationManager( ILogger logger, @@ -70,7 +75,8 @@ public class ConfigurationManager : IConfigurationManager IOptionsMonitor contentBlockerConfig, IOptionsMonitor queueCleanerConfig, IOptionsMonitor downloadCleanerConfig, - IOptionsMonitor downloadClientConfig) + IOptionsMonitor downloadClientConfig, + IOptionsMonitor ignoredDownloadsConfig) { _logger = logger; _configProvider = configProvider; @@ -81,6 +87,7 @@ public class ConfigurationManager : IConfigurationManager _queueCleanerConfig = queueCleanerConfig; _downloadCleanerConfig = downloadCleanerConfig; _downloadClientConfig = downloadClientConfig; + _ignoredDownloadsConfig = ignoredDownloadsConfig; } // Generic configuration methods @@ -268,4 +275,23 @@ public class ConfigurationManager : IConfigurationManager return Task.FromResult(false); } } + + public async Task GetIgnoredDownloadsConfigAsync() + { + var config = await _configProvider.ReadConfigurationAsync(IgnoredDownloadsConfigFile); + return config ?? _ignoredDownloadsConfig.CurrentValue; + } + + public Task SaveIgnoredDownloadsConfigAsync(IgnoredDownloadsConfig config) + { + try + { + return _configProvider.WriteConfigurationAsync(IgnoredDownloadsConfigFile, config); + } + catch (Exception ex) + { + _logger.LogError(ex, "IgnoredDownloads configuration save failed"); + return Task.FromResult(false); + } + } } diff --git a/code/Infrastructure/Providers/IgnoredDownloadsProvider.cs b/code/Infrastructure/Providers/IgnoredDownloadsProvider.cs deleted file mode 100644 index e31dcd41..00000000 --- a/code/Infrastructure/Providers/IgnoredDownloadsProvider.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Common.Configuration; -using Infrastructure.Configuration; -using Infrastructure.Helpers; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Logging; - -namespace Infrastructure.Providers; - -public sealed class IgnoredDownloadsProvider - where T : IIgnoredDownloadsConfig -{ - private readonly ILogger> _logger; - private IIgnoredDownloadsConfig _config; - private readonly IConfigurationManager _configManager; - private readonly IMemoryCache _cache; - private DateTime _lastModified = DateTime.MinValue; - private readonly string _configType; - - public IgnoredDownloadsProvider(ILogger> logger, IConfigurationManager configManager, IMemoryCache cache) - { - _logger = logger; - _configManager = configManager; - _cache = cache; - _configType = typeof(T).Name; - - // Initialize configuration - InitializeConfig().Wait(); - } - - private async Task InitializeConfig() - { - // Get the configuration based on the type - if (typeof(T).Name.Contains("ContentBlocker")) - { - var config = await _configManager.GetContentBlockerConfigAsync(); - _config = config ?? new T(); - } - else if (typeof(T).Name.Contains("QueueCleaner")) - { - var config = await _configManager.GetQueueCleanerConfigAsync(); - _config = config ?? new T(); - } - else if (typeof(T).Name.Contains("DownloadCleaner")) - { - var config = await _configManager.GetDownloadCleanerConfigAsync(); - _config = config ?? new T(); - } - else - { - _config = new T(); - } - - if (string.IsNullOrEmpty(_config.IgnoredDownloadsPath)) - { - return; - } - - if (!File.Exists(_config.IgnoredDownloadsPath)) - { - _logger.LogWarning("Ignored downloads file not found: {path}", _config.IgnoredDownloadsPath); - } - } - - public async Task> GetIgnoredDownloads() - { - // Refresh the configuration before checking ignored downloads - await InitializeConfig(); - - if (string.IsNullOrEmpty(_config.IgnoredDownloadsPath)) - { - return Array.Empty(); - } - - FileInfo fileInfo = new(_config.IgnoredDownloadsPath); - - if (fileInfo.LastWriteTime > _lastModified || - !_cache.TryGetValue(CacheKeys.IgnoredDownloads(_configType), out IReadOnlyList? ignoredDownloads) || - ignoredDownloads is null) - { - _lastModified = fileInfo.LastWriteTime; - - return await LoadFile(); - } - - return ignoredDownloads; - } - - private async Task> LoadFile() - { - try - { - if (string.IsNullOrEmpty(_config.IgnoredDownloadsPath)) - { - return Array.Empty(); - } - - string[] ignoredDownloads = (await File.ReadAllLinesAsync(_config.IgnoredDownloadsPath)) - .Where(x => !string.IsNullOrWhiteSpace(x)) - .ToArray(); - - _cache.Set(CacheKeys.IgnoredDownloads(typeof(T).Name), ignoredDownloads); - - _logger.LogInformation("ignored downloads reloaded"); - - return ignoredDownloads; - } - catch (Exception exception) - { - _logger.LogError(exception, "error while reading ignored downloads file | {file}", _config.IgnoredDownloadsPath); - } - - return Array.Empty(); - } -} \ No newline at end of file diff --git a/code/Infrastructure/Services/ConfigurationService.cs b/code/Infrastructure/Services/ConfigurationService.cs index 02facf90..93d274f9 100644 --- a/code/Infrastructure/Services/ConfigurationService.cs +++ b/code/Infrastructure/Services/ConfigurationService.cs @@ -1,85 +1,57 @@ using Common.Configuration; -using Microsoft.Extensions.Configuration; +using Common.Configuration.ContentBlocker; +using Common.Configuration.DownloadCleaner; +using Common.Configuration.DownloadClient; +using Common.Configuration.IgnoredDownloads; +using Common.Configuration.QueueCleaner; +using Infrastructure.Configuration; using Microsoft.Extensions.Logging; -using System.Text.Json; -using System.Text.Json.Nodes; -using Microsoft.Extensions.Hosting; namespace Infrastructure.Services; public interface IConfigurationService { - Task UpdateConfigurationAsync(string sectionName, T configSection) where T : class, IConfig; - Task GetConfigurationAsync(string sectionName) where T : class, IConfig; - Task RefreshConfigurationAsync(); + Task UpdateConfigurationAsync(string sectionName, T configSection); + Task GetConfigurationAsync(string sectionName); + Task GetSonarrConfigAsync(); + Task GetRadarrConfigAsync(); + Task GetLidarrConfigAsync(); + Task GetContentBlockerConfigAsync(); + Task GetQueueCleanerConfigAsync(); + Task GetDownloadCleanerConfigAsync(); + Task GetDownloadClientConfigAsync(); + Task GetIgnoredDownloadsConfigAsync(); + Task UpdateSonarrConfigAsync(SonarrConfig config); + Task UpdateRadarrConfigAsync(RadarrConfig config); + Task UpdateLidarrConfigAsync(LidarrConfig config); + Task UpdateContentBlockerConfigAsync(ContentBlockerConfig config); + Task UpdateQueueCleanerConfigAsync(QueueCleanerConfig config); + Task UpdateDownloadCleanerConfigAsync(DownloadCleanerConfig config); + Task UpdateDownloadClientConfigAsync(DownloadClientConfig config); + Task UpdateIgnoredDownloadsConfigAsync(IgnoredDownloadsConfig config); } public class ConfigurationService : IConfigurationService { private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly string _configFilePath; + private readonly IConfigurationManager _configManager; public ConfigurationService( ILogger logger, - IConfiguration configuration, - IHostEnvironment environment) + IConfigurationManager configManager) { _logger = logger; - _configuration = configuration; - - // Find primary configuration file - var currentDirectory = environment.ContentRootPath; - _configFilePath = Path.Combine(currentDirectory, "appsettings.json"); - - if (!File.Exists(_configFilePath)) - { - _logger.LogWarning("Configuration file not found at: {path}", _configFilePath); - _configFilePath = Path.Combine(currentDirectory, "appsettings.Development.json"); - - if (!File.Exists(_configFilePath)) - { - _logger.LogError("No configuration file found"); - throw new FileNotFoundException("Configuration file not found"); - } - } - - _logger.LogInformation("Using configuration file: {path}", _configFilePath); + _configManager = configManager; } - public async Task UpdateConfigurationAsync(string sectionName, T configSection) where T : class, IConfig + public async Task UpdateConfigurationAsync(string sectionName, T configSection) { try { - // Read existing configuration - var json = await File.ReadAllTextAsync(_configFilePath); - var jsonObject = JsonNode.Parse(json)?.AsObject() - ?? throw new InvalidOperationException("Failed to parse configuration file"); - - // Create JsonObject from config section - var configJson = JsonSerializer.Serialize(configSection); - var configObject = JsonNode.Parse(configJson)?.AsObject() - ?? throw new InvalidOperationException("Failed to serialize configuration"); - - // Update or add the section - jsonObject[sectionName] = configObject; - - // Save back to file - var options = new JsonSerializerOptions { WriteIndented = true }; - var updatedJson = jsonObject.ToJsonString(options); - - // Create backup - var backupPath = $"{_configFilePath}.bak"; - await File.WriteAllTextAsync(backupPath, json); - - // Write updated configuration - await File.WriteAllTextAsync(_configFilePath, updatedJson); - - // Refresh configuration - await RefreshConfigurationAsync(); - - _logger.LogInformation("Configuration section {section} updated successfully", sectionName); - return true; + // This is just a placeholder method for backward compatibility + // The actual implementation depends on the specific config type + _logger.LogWarning("Using deprecated UpdateConfigurationAsync method with section name '{section}'.", sectionName); + return false; } catch (Exception ex) { @@ -88,52 +60,39 @@ public class ConfigurationService : IConfigurationService } } - public async Task GetConfigurationAsync(string sectionName) where T : class, IConfig + public async Task GetConfigurationAsync(string sectionName) { try { - var json = await File.ReadAllTextAsync(_configFilePath); - var jsonObject = JsonNode.Parse(json)?.AsObject(); - - if (jsonObject == null || !jsonObject.ContainsKey(sectionName)) - { - _logger.LogWarning("Section {section} not found in configuration", sectionName); - return null; - } - - var sectionObject = jsonObject[sectionName]?.ToJsonString(); - if (sectionObject == null) - { - return null; - } - - return JsonSerializer.Deserialize(sectionObject); + // This is just a placeholder method for backward compatibility + // The actual implementation depends on the specific config type + _logger.LogWarning("Using deprecated GetConfigurationAsync method with section name '{section}'.", sectionName); + return default; } catch (Exception ex) { _logger.LogError(ex, "Error retrieving configuration section {section}", sectionName); - return null; + return default; } } - public Task RefreshConfigurationAsync() - { - try - { - if (_configuration is IConfigurationRoot configRoot) - { - configRoot.Reload(); - _logger.LogInformation("Configuration reloaded"); - return Task.FromResult(true); - } - - _logger.LogWarning("Unable to reload configuration: IConfigurationRoot not available"); - return Task.FromResult(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reloading configuration"); - return Task.FromResult(false); - } - } + // Specific configuration getters + public async Task GetSonarrConfigAsync() => await _configManager.GetSonarrConfigAsync(); + public async Task GetRadarrConfigAsync() => await _configManager.GetRadarrConfigAsync(); + public async Task GetLidarrConfigAsync() => await _configManager.GetLidarrConfigAsync(); + public async Task GetContentBlockerConfigAsync() => await _configManager.GetContentBlockerConfigAsync(); + public async Task GetQueueCleanerConfigAsync() => await _configManager.GetQueueCleanerConfigAsync(); + public async Task GetDownloadCleanerConfigAsync() => await _configManager.GetDownloadCleanerConfigAsync(); + public async Task GetDownloadClientConfigAsync() => await _configManager.GetDownloadClientConfigAsync(); + public async Task GetIgnoredDownloadsConfigAsync() => await _configManager.GetIgnoredDownloadsConfigAsync(); + + // Specific configuration setters + public async Task UpdateSonarrConfigAsync(SonarrConfig config) => await _configManager.SaveSonarrConfigAsync(config); + public async Task UpdateRadarrConfigAsync(RadarrConfig config) => await _configManager.SaveRadarrConfigAsync(config); + public async Task UpdateLidarrConfigAsync(LidarrConfig config) => await _configManager.SaveLidarrConfigAsync(config); + public async Task UpdateContentBlockerConfigAsync(ContentBlockerConfig config) => await _configManager.SaveContentBlockerConfigAsync(config); + public async Task UpdateQueueCleanerConfigAsync(QueueCleanerConfig config) => await _configManager.SaveQueueCleanerConfigAsync(config); + public async Task UpdateDownloadCleanerConfigAsync(DownloadCleanerConfig config) => await _configManager.SaveDownloadCleanerConfigAsync(config); + public async Task UpdateDownloadClientConfigAsync(DownloadClientConfig config) => await _configManager.SaveDownloadClientConfigAsync(config); + public async Task UpdateIgnoredDownloadsConfigAsync(IgnoredDownloadsConfig config) => await _configManager.SaveIgnoredDownloadsConfigAsync(config); } diff --git a/code/Infrastructure/Services/IgnoredDownloadsService.cs b/code/Infrastructure/Services/IgnoredDownloadsService.cs new file mode 100644 index 00000000..6f086d2a --- /dev/null +++ b/code/Infrastructure/Services/IgnoredDownloadsService.cs @@ -0,0 +1,171 @@ +using Common.Configuration.IgnoredDownloads; +using Infrastructure.Configuration; +using Infrastructure.Helpers; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; + +namespace Infrastructure.Services; + +/// +/// Service for managing ignored downloads +/// +public interface IIgnoredDownloadsService +{ + /// + /// Gets the list of ignored download IDs + /// + Task> GetIgnoredDownloadsAsync(); + + /// + /// Adds a download ID to the ignored list + /// + Task AddIgnoredDownloadAsync(string downloadId); + + /// + /// Removes a download ID from the ignored list + /// + Task RemoveIgnoredDownloadAsync(string downloadId); + + /// + /// Clears all ignored downloads + /// + Task ClearIgnoredDownloadsAsync(); +} + +public class IgnoredDownloadsService : IIgnoredDownloadsService +{ + private readonly ILogger _logger; + private readonly IConfigurationManager _configManager; + private readonly IMemoryCache _cache; + private const string IgnoredDownloadsCacheKey = "IgnoredDownloads"; + + public IgnoredDownloadsService( + ILogger logger, + IConfigurationManager configManager, + IMemoryCache cache) + { + _logger = logger; + _configManager = configManager; + _cache = cache; + } + + public async Task> GetIgnoredDownloadsAsync() + { + // Try to get from cache first + if (_cache.TryGetValue(IgnoredDownloadsCacheKey, out IReadOnlyList? cachedList) && + cachedList != null) + { + return cachedList; + } + + // Not in cache, load from config + var config = await _configManager.GetIgnoredDownloadsConfigAsync(); + if (config == null) + { + return Array.Empty(); + } + + // Store in cache for quick access (5 minute expiration) + var ignoredDownloads = config.IgnoredDownloads.ToList(); + _cache.Set(IgnoredDownloadsCacheKey, ignoredDownloads, TimeSpan.FromMinutes(5)); + + return ignoredDownloads; + } + + public async Task AddIgnoredDownloadAsync(string downloadId) + { + if (string.IsNullOrWhiteSpace(downloadId)) + { + return false; + } + + var config = await _configManager.GetIgnoredDownloadsConfigAsync(); + if (config == null) + { + config = new IgnoredDownloadsConfig + { + IgnoredDownloads = new List { downloadId } + }; + } + else if (!config.IgnoredDownloads.Contains(downloadId, StringComparer.OrdinalIgnoreCase)) + { + var updatedList = config.IgnoredDownloads.ToList(); + updatedList.Add(downloadId); + config = new IgnoredDownloadsConfig + { + IgnoredDownloads = updatedList + }; + } + else + { + // Already in the list + return true; + } + + var result = await _configManager.SaveIgnoredDownloadsConfigAsync(config); + if (result) + { + // Update cache + _cache.Remove(IgnoredDownloadsCacheKey); + _logger.LogInformation("Added download ID to ignored list: {downloadId}", downloadId); + } + + return result; + } + + public async Task RemoveIgnoredDownloadAsync(string downloadId) + { + if (string.IsNullOrWhiteSpace(downloadId)) + { + return false; + } + + var config = await _configManager.GetIgnoredDownloadsConfigAsync(); + if (config == null || config.IgnoredDownloads.Count == 0) + { + return true; // Nothing to remove + } + + var updatedList = config.IgnoredDownloads + .Where(id => !string.Equals(id, downloadId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (updatedList.Count == config.IgnoredDownloads.Count) + { + return true; // Item wasn't in the list + } + + var newConfig = new IgnoredDownloadsConfig + { + IgnoredDownloads = updatedList + }; + + var result = await _configManager.SaveIgnoredDownloadsConfigAsync(newConfig); + if (result) + { + // Update cache + _cache.Remove(IgnoredDownloadsCacheKey); + _logger.LogInformation("Removed download ID from ignored list: {downloadId}", downloadId); + } + + return result; + } + + public async Task ClearIgnoredDownloadsAsync() + { + var config = new IgnoredDownloadsConfig + { + IgnoredDownloads = new List() + }; + + var result = await _configManager.SaveIgnoredDownloadsConfigAsync(config); + if (result) + { + // Update cache + _cache.Remove(IgnoredDownloadsCacheKey); + _logger.LogInformation("Cleared all ignored downloads"); + } + + return result; + } +} diff --git a/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs b/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs index 00678fbd..65f67bc7 100644 --- a/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs +++ b/code/Infrastructure/Verticals/ContentBlocker/ContentBlocker.cs @@ -8,7 +8,7 @@ using Domain.Models.Arr; using Domain.Models.Arr.Queue; using Infrastructure.Configuration; using Infrastructure.Helpers; -using Infrastructure.Providers; +using Infrastructure.Services; using Infrastructure.Verticals.Arr; using Infrastructure.Verticals.Arr.Interfaces; using Infrastructure.Verticals.Context; @@ -27,7 +27,7 @@ public sealed class ContentBlocker : GenericHandler { private readonly ContentBlockerConfig _config; private readonly BlocklistProvider _blocklistProvider; - private readonly IgnoredDownloadsProvider _ignoredDownloadsProvider; + private readonly IIgnoredDownloadsService _ignoredDownloadsService; private readonly IConfigurationManager _configManager; public ContentBlocker( @@ -40,7 +40,7 @@ public sealed class ContentBlocker : GenericHandler BlocklistProvider blocklistProvider, DownloadServiceFactory downloadServiceFactory, INotificationPublisher notifier, - IgnoredDownloadsProvider ignoredDownloadsProvider + IIgnoredDownloadsService ignoredDownloadsService ) : base( logger, cache, messageBus, arrClientFactory, arrArrQueueIterator, @@ -49,7 +49,7 @@ public sealed class ContentBlocker : GenericHandler { _configManager = configManager; _blocklistProvider = blocklistProvider; - _ignoredDownloadsProvider = ignoredDownloadsProvider; + _ignoredDownloadsService = ignoredDownloadsService; // Initialize the configuration var configTask = _configManager.GetContentBlockerConfigAsync(); @@ -101,7 +101,7 @@ public sealed class ContentBlocker : GenericHandler protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config) { - IReadOnlyList ignoredDownloads = await _ignoredDownloadsProvider.GetIgnoredDownloads(); + IReadOnlyList ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); using var _ = LogContext.PushProperty("InstanceName", instanceType.ToString()); diff --git a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs index cadf641c..78aae559 100644 --- a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs +++ b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs @@ -4,7 +4,7 @@ using Common.Configuration.DownloadClient; using Domain.Enums; using Domain.Models.Arr.Queue; using Infrastructure.Configuration; -using Infrastructure.Providers; +using Infrastructure.Services; using Infrastructure.Verticals.Arr; using Infrastructure.Verticals.Arr.Interfaces; using Infrastructure.Verticals.DownloadClient; @@ -20,7 +20,7 @@ namespace Infrastructure.Verticals.DownloadCleaner; public sealed class DownloadCleaner : GenericHandler { private readonly DownloadCleanerConfig _config; - private readonly IgnoredDownloadsProvider _ignoredDownloadsProvider; + private readonly IIgnoredDownloadsService _ignoredDownloadsService; private readonly HashSet _excludedHashes = []; private readonly IConfigurationManager _configManager; @@ -35,7 +35,7 @@ public sealed class DownloadCleaner : GenericHandler ArrQueueIterator arrArrQueueIterator, DownloadServiceFactory downloadServiceFactory, INotificationPublisher notifier, - IgnoredDownloadsProvider ignoredDownloadsProvider + IIgnoredDownloadsService ignoredDownloadsService ) : base( logger, cache, messageBus, arrClientFactory, arrArrQueueIterator, downloadServiceFactory, @@ -43,7 +43,7 @@ public sealed class DownloadCleaner : GenericHandler ) { _configManager = configManager; - _ignoredDownloadsProvider = ignoredDownloadsProvider; + _ignoredDownloadsService = ignoredDownloadsService; // Initialize the configuration var configTask = _configManager.GetDownloadCleanerConfigAsync(); @@ -87,7 +87,7 @@ public sealed class DownloadCleaner : GenericHandler return; } - IReadOnlyList ignoredDownloads = await _ignoredDownloadsProvider.GetIgnoredDownloads(); + IReadOnlyList ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); await _downloadService.LoginAsync(); List? downloads = await _downloadService.GetSeedingDownloads(); diff --git a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs index faf92a79..cbeb56f2 100644 --- a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs +++ b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs @@ -6,7 +6,7 @@ using Domain.Models.Arr; using Domain.Models.Arr.Queue; using Infrastructure.Configuration; using Infrastructure.Helpers; -using Infrastructure.Providers; +using Infrastructure.Services; using Infrastructure.Verticals.Arr; using Infrastructure.Verticals.Arr.Interfaces; using Infrastructure.Verticals.Context; @@ -26,7 +26,7 @@ public sealed class QueueCleaner : GenericHandler private readonly QueueCleanerConfig _config; private readonly IMemoryCache _cache; private readonly IConfigurationManager _configManager; - private readonly IgnoredDownloadsProvider _ignoredDownloadsProvider; + private readonly IIgnoredDownloadsService _ignoredDownloadsService; public QueueCleaner( ILogger logger, @@ -37,7 +37,7 @@ public sealed class QueueCleaner : GenericHandler ArrQueueIterator arrArrQueueIterator, DownloadServiceFactory downloadServiceFactory, INotificationPublisher notifier, - IgnoredDownloadsProvider ignoredDownloadsProvider + IIgnoredDownloadsService ignoredDownloadsService ) : base( logger, cache, messageBus, arrClientFactory, arrArrQueueIterator, downloadServiceFactory, @@ -46,7 +46,7 @@ public sealed class QueueCleaner : GenericHandler { _configManager = configManager; _cache = cache; - _ignoredDownloadsProvider = ignoredDownloadsProvider; + _ignoredDownloadsService = ignoredDownloadsService; // Initialize the configuration var configTask = _configManager.GetQueueCleanerConfigAsync(); @@ -72,7 +72,7 @@ public sealed class QueueCleaner : GenericHandler protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config) { - IReadOnlyList ignoredDownloads = await _ignoredDownloadsProvider.GetIgnoredDownloads(); + IReadOnlyList ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); using var _ = LogContext.PushProperty("InstanceName", instanceType.ToString());