diff --git a/code/Common/Configuration/DTOs/ContentBlocker/ContentBlockerConfigDto.cs b/code/Common/Configuration/DTOs/ContentBlocker/ContentBlockerConfigDto.cs index 632a1d33..8bc01975 100644 --- a/code/Common/Configuration/DTOs/ContentBlocker/ContentBlockerConfigDto.cs +++ b/code/Common/Configuration/DTOs/ContentBlocker/ContentBlockerConfigDto.cs @@ -27,11 +27,6 @@ public class ContentBlockerConfigDto /// public bool DeletePrivate { get; set; } - /// - /// Path to ignored downloads file - /// - public string IgnoredDownloadsPath { get; set; } = string.Empty; - /// /// Sonarr blocklist settings /// diff --git a/code/Common/Configuration/DTOs/DownloadCleaner/CleanCategoryDto.cs b/code/Common/Configuration/DTOs/DownloadCleaner/CleanCategoryDto.cs deleted file mode 100644 index cebf78de..00000000 --- a/code/Common/Configuration/DTOs/DownloadCleaner/CleanCategoryDto.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Newtonsoft.Json; - -namespace Common.Configuration.DTOs.DownloadCleaner; - -/// -/// DTO for clean category configuration -/// -public class CleanCategoryDto -{ - /// - /// Name of the clean category - /// - public required string Name { get; set; } - - /// - /// Max ratio before removing a download - /// - [JsonProperty("max_ratio")] - public double MaxRatio { get; set; } = -1; - - /// - /// Min number of hours to seed before removing a download, if the ratio has been met - /// - [JsonProperty("min_seed_time")] - public double MinSeedTime { get; set; } - - /// - /// Number of hours to seed before removing a download - /// - [JsonProperty("max_seed_time")] - public double MaxSeedTime { get; set; } = -1; -} diff --git a/code/Common/Configuration/DTOs/DownloadCleaner/DownloadCleanerConfigDto.cs b/code/Common/Configuration/DTOs/DownloadCleaner/DownloadCleanerConfigDto.cs deleted file mode 100644 index 83513bfe..00000000 --- a/code/Common/Configuration/DTOs/DownloadCleaner/DownloadCleanerConfigDto.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Common.Configuration.DTOs.DownloadCleaner; - -/// -/// DTO for retrieving DownloadCleaner configuration -/// -public class DownloadCleanerConfigDto -{ - /// - /// Whether the download cleaner is enabled - /// - public bool Enabled { get; set; } - - /// - /// Cron expression for scheduling - /// - public string CronExpression { get; set; } = "0 0 * * * ?"; - - /// - /// Categories for cleaning downloads - /// - public List Categories { get; set; } = new(); - - /// - /// Whether to delete private torrents - /// - public bool DeletePrivate { get; set; } - - /// - /// Path to ignored downloads file - /// - public string IgnoredDownloadsPath { get; set; } = string.Empty; - - /// - /// Target category for unlinked downloads - /// - public string UnlinkedTargetCategory { get; set; } = "cleanuparr-unlinked"; - - /// - /// Whether to use tags instead of categories for unlinked downloads - /// - public bool UnlinkedUseTag { get; set; } - - /// - /// Root directory to ignore for unlinked downloads - /// - public string UnlinkedIgnoredRootDir { get; set; } = string.Empty; - - /// - /// Categories to consider as unlinked - /// - public List UnlinkedCategories { get; set; } = new(); -} diff --git a/code/Common/Configuration/DTOs/DownloadCleaner/DownloadCleanerConfigUpdateDto.cs b/code/Common/Configuration/DTOs/DownloadCleaner/DownloadCleanerConfigUpdateDto.cs deleted file mode 100644 index 0301af37..00000000 --- a/code/Common/Configuration/DTOs/DownloadCleaner/DownloadCleanerConfigUpdateDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Common.Configuration.DTOs.DownloadCleaner; - -/// -/// DTO for updating DownloadCleaner configuration -/// -/// -/// This class inherits from DownloadCleanerConfigDto without adding any additional fields -/// since DownloadCleaner doesn't have sensitive data fields that need special handling -/// -public class DownloadCleanerConfigUpdateDto : DownloadCleanerConfigDto -{ -} diff --git a/code/Common/Configuration/DTOs/General/GeneralConfigDto.cs b/code/Common/Configuration/DTOs/General/GeneralConfigDto.cs index 5ddb2e21..b923c279 100644 --- a/code/Common/Configuration/DTOs/General/GeneralConfigDto.cs +++ b/code/Common/Configuration/DTOs/General/GeneralConfigDto.cs @@ -42,6 +42,9 @@ public class GeneralConfigDto /// Application log level /// public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; - - // EncryptionKey is intentionally excluded as it's sensitive data + + /// + /// Ignored downloads list + /// + public List IgnoredDownloads { get; set; } = []; } diff --git a/code/Common/Configuration/DTOs/General/GeneralConfigUpdateDto.cs b/code/Common/Configuration/DTOs/General/GeneralConfigUpdateDto.cs deleted file mode 100644 index b7fb30e1..00000000 --- a/code/Common/Configuration/DTOs/General/GeneralConfigUpdateDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Common.Configuration.DTOs.General; - -/// -/// DTO for updating General configuration (includes sensitive data fields) -/// -public class GeneralConfigUpdateDto : GeneralConfigDto -{ - /// - /// Encryption key used for sensitive data (only included in update DTO) - /// - public string? EncryptionKey { get; set; } -} diff --git a/code/Common/Configuration/DTOs/IgnoredDownloads/IgnoredDownloadsConfigDto.cs b/code/Common/Configuration/DTOs/IgnoredDownloads/IgnoredDownloadsConfigDto.cs deleted file mode 100644 index fc299204..00000000 --- a/code/Common/Configuration/DTOs/IgnoredDownloads/IgnoredDownloadsConfigDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Common.Configuration.DTOs.IgnoredDownloads; - -/// -/// DTO for retrieving IgnoredDownloads configuration -/// -public class IgnoredDownloadsConfigDto -{ - /// - /// List of download IDs to be ignored by all jobs - /// - public List IgnoredDownloads { get; set; } = new(); -} diff --git a/code/Common/Configuration/DTOs/IgnoredDownloads/IgnoredDownloadsConfigUpdateDto.cs b/code/Common/Configuration/DTOs/IgnoredDownloads/IgnoredDownloadsConfigUpdateDto.cs deleted file mode 100644 index 6651480f..00000000 --- a/code/Common/Configuration/DTOs/IgnoredDownloads/IgnoredDownloadsConfigUpdateDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Common.Configuration.DTOs.IgnoredDownloads; - -/// -/// DTO for updating IgnoredDownloads configuration -/// -/// -/// This class inherits from IgnoredDownloadsConfigDto without adding any additional fields -/// since IgnoredDownloads doesn't have sensitive data fields that need special handling -/// -public class IgnoredDownloadsConfigUpdateDto : IgnoredDownloadsConfigDto -{ -} diff --git a/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigDto.cs b/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigDto.cs deleted file mode 100644 index ad3209d0..00000000 --- a/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigDto.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Common.Configuration.QueueCleaner; - -namespace Common.Configuration.DTOs.QueueCleaner; - -/// -/// DTO for retrieving QueueCleaner configuration -/// -public class QueueCleanerConfigDto -{ - /// - /// Whether the queue cleaner is enabled - /// - public bool Enabled { get; set; } - - /// - /// Cron expression for scheduling - /// - public string CronExpression { get; set; } = "0 0/5 * * * ?"; - - /// - /// Maximum number of strikes for failed imports - /// - public ushort FailedImportMaxStrikes { get; set; } - - /// - /// Whether to ignore private torrents for failed imports - /// - public bool FailedImportIgnorePrivate { get; set; } - - /// - /// Whether to delete private torrents for failed imports - /// - public bool FailedImportDeletePrivate { get; set; } - - /// - /// Patterns to ignore for failed imports - /// - public IReadOnlyList FailedImportIgnorePatterns { get; set; } = Array.Empty(); - - /// - /// Maximum number of strikes for stalled downloads - /// - public ushort StalledMaxStrikes { get; set; } - - /// - /// Whether to reset strikes on progress for stalled downloads - /// - public bool StalledResetStrikesOnProgress { get; set; } - - /// - /// Whether to ignore private torrents for stalled downloads - /// - public bool StalledIgnorePrivate { get; set; } - - /// - /// Whether to delete private torrents for stalled downloads - /// - public bool StalledDeletePrivate { get; set; } - - /// - /// Maximum number of strikes for downloading metadata - /// - public ushort DownloadingMetadataMaxStrikes { get; set; } - - /// - /// Maximum number of strikes for slow downloads - /// - public ushort SlowMaxStrikes { get; set; } - - /// - /// Whether to reset strikes on progress for slow downloads - /// - public bool SlowResetStrikesOnProgress { get; set; } - - /// - /// Whether to ignore private torrents for slow downloads - /// - public bool SlowIgnorePrivate { get; set; } - - /// - /// Whether to delete private torrents for slow downloads - /// - public bool SlowDeletePrivate { get; set; } - - /// - /// Minimum speed threshold for slow downloads - /// - public string SlowMinSpeed { get; set; } = string.Empty; - - /// - /// Maximum time allowed for slow downloads - /// - public double SlowMaxTime { get; set; } - - /// - /// Size threshold above which slow downloads are ignored - /// - public string SlowIgnoreAboveSize { get; set; } = string.Empty; -} diff --git a/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigUpdateDto.cs b/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigUpdateDto.cs deleted file mode 100644 index b468ea83..00000000 --- a/code/Common/Configuration/DTOs/QueueCleaner/QueueCleanerConfigUpdateDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Common.Configuration.DTOs.QueueCleaner; - -/// -/// DTO for updating QueueCleaner configuration -/// -/// -/// This class inherits from QueueCleanerConfigDto without adding any additional fields -/// since QueueCleaner doesn't have sensitive data fields that need special handling -/// -public class QueueCleanerConfigUpdateDto : QueueCleanerConfigDto -{ -} diff --git a/code/Common/Configuration/General/GeneralConfig.cs b/code/Common/Configuration/General/GeneralConfig.cs index 8efb3370..8c6964eb 100644 --- a/code/Common/Configuration/General/GeneralConfig.cs +++ b/code/Common/Configuration/General/GeneralConfig.cs @@ -22,6 +22,8 @@ public sealed record GeneralConfig : IConfig public string EncryptionKey { get; init; } = Guid.NewGuid().ToString(); + public List IgnoredDownloads { get; set; } = []; + public void Validate() { if (HttpTimeout is 0) diff --git a/code/Common/Configuration/IgnoredDownloads/IgnoredDownloadsConfig.cs b/code/Common/Configuration/IgnoredDownloads/IgnoredDownloadsConfig.cs deleted file mode 100644 index f66b9020..00000000 --- a/code/Common/Configuration/IgnoredDownloads/IgnoredDownloadsConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -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/Common/Configuration/QueueCleaner/FailedImportConfig.cs b/code/Common/Configuration/QueueCleaner/FailedImportConfig.cs new file mode 100644 index 00000000..e80fa7aa --- /dev/null +++ b/code/Common/Configuration/QueueCleaner/FailedImportConfig.cs @@ -0,0 +1,22 @@ +using Common.Exceptions; + +namespace Common.Configuration.QueueCleaner; + +public sealed record FailedImportConfig +{ + public ushort MaxStrikes { get; init; } + + public bool IgnorePrivate { get; init; } + + public bool DeletePrivate { get; init; } + + public IReadOnlyList IgnoredPatterns { get; init; } = []; + + public void Validate() + { + if (MaxStrikes is > 0 and < 3) + { + throw new ValidationException("the minimum value for failed imports max strikes must be 3"); + } + } +} \ No newline at end of file diff --git a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs index ec71abd7..2f75ce1b 100644 --- a/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs +++ b/code/Common/Configuration/QueueCleaner/QueueCleanerConfig.cs @@ -1,20 +1,11 @@ -using System.Text.Json.Serialization; -using Common.CustomDataTypes; -using Common.Exceptions; - namespace Common.Configuration.QueueCleaner; public sealed record QueueCleanerConfig : IJobConfig { - public const string SectionName = "QueueCleaner"; - public bool Enabled { get; init; } public string CronExpression { get; init; } = "0 0/5 * * * ?"; - // TODO - public string IgnoredDownloadsPath { get; init; } = string.Empty; - public FailedImportConfig FailedImport { get; init; } = new(); public StalledConfig Stalled { get; init; } = new(); @@ -25,112 +16,9 @@ public sealed record QueueCleanerConfig : IJobConfig public void Validate() { - FailedImport.Validate(SectionName); - Stalled.Validate(SectionName); - Slow.Validate(SectionName); + FailedImport.Validate(); + Stalled.Validate(); + Slow.Validate(); ContentBlocker.Validate(); } -} - -public sealed record FailedImportConfig -{ - public ushort MaxStrikes { get; init; } - - public bool IgnorePrivate { get; init; } - - public bool DeletePrivate { get; init; } - - public IReadOnlyList IgnoredPatterns { get; init; } = []; - - public void Validate(string sectionName) - { - if (MaxStrikes is > 0 and < 3) - { - throw new ValidationException($"the minimum value for {sectionName.ToUpperInvariant()}__FAILED_IMPORT__MAX_STRIKES must be 3"); - } - } -} - -public sealed record StalledConfig -{ - public ushort MaxStrikes { get; init; } - - public bool ResetStrikesOnProgress { get; init; } - - public bool IgnorePrivate { get; init; } - - public bool DeletePrivate { get; init; } - - public ushort DownloadingMetadataMaxStrikes { get; init; } - - public void Validate(string sectionName) - { - if (MaxStrikes is > 0 and < 3) - { - throw new ValidationException($"the minimum value for {sectionName.ToUpperInvariant()}__STALLED__MAX_STRIKES must be 3"); - } - - if (DownloadingMetadataMaxStrikes is > 0 and < 3) - { - throw new ValidationException($"the minimum value for {sectionName.ToUpperInvariant()}__STALLED__DOWNLOADING_METADATA_MAX_STRIKES must be 3"); - } - } -} - -public sealed record SlowConfig -{ - public ushort MaxStrikes { get; init; } - - public bool ResetStrikesOnProgress { get; init; } - - public bool IgnorePrivate { get; init; } - - public bool DeletePrivate { get; init; } - - public string MinSpeed { get; init; } = string.Empty; - - [JsonIgnore] - public ByteSize MinSpeedByteSize => string.IsNullOrEmpty(MinSpeed) ? new ByteSize(0) : ByteSize.Parse(MinSpeed); - - public double MaxTime { get; init; } - - public string IgnoreAboveSize { get; init; } = string.Empty; - - [JsonIgnore] - public ByteSize? IgnoreAboveSizeByteSize => string.IsNullOrEmpty(IgnoreAboveSize) ? null : ByteSize.Parse(IgnoreAboveSize); - - public void Validate(string sectionName) - { - if (MaxStrikes is > 0 and < 3) - { - throw new ValidationException($"the minimum value for {sectionName.ToUpperInvariant()}__SLOW__MAX_STRIKES must be 3"); - } - - if (MaxStrikes > 0) - { - bool isSpeedSet = !string.IsNullOrEmpty(MinSpeed); - - if (isSpeedSet && ByteSize.TryParse(MinSpeed, out _) is false) - { - throw new ValidationException($"invalid value for {sectionName.ToUpperInvariant()}__SLOW__MIN_SPEED"); - } - - if (MaxTime < 0) - { - throw new ValidationException($"invalid value for {sectionName.ToUpperInvariant()}__SLOW__MAX_TIME"); - } - - if (!isSpeedSet && MaxTime is 0) - { - throw new ValidationException($"either {sectionName.ToUpperInvariant()}__SLOW__MIN_SPEED or {sectionName.ToUpperInvariant()}__SLOW__MAX_TIME must be set"); - } - - bool isIgnoreAboveSizeSet = !string.IsNullOrEmpty(IgnoreAboveSize); - - if (isIgnoreAboveSizeSet && ByteSize.TryParse(IgnoreAboveSize, out _) is false) - { - throw new ValidationException($"invalid value for {sectionName.ToUpperInvariant()}__SLOW__IGNORE_ABOVE_SIZE"); - } - } - } } \ No newline at end of file diff --git a/code/Common/Configuration/QueueCleaner/SlowConfig.cs b/code/Common/Configuration/QueueCleaner/SlowConfig.cs new file mode 100644 index 00000000..5d4f8f2c --- /dev/null +++ b/code/Common/Configuration/QueueCleaner/SlowConfig.cs @@ -0,0 +1,63 @@ +using System.Text.Json.Serialization; +using Common.CustomDataTypes; +using Common.Exceptions; + +namespace Common.Configuration.QueueCleaner; + +public sealed record SlowConfig +{ + public ushort MaxStrikes { get; init; } + + public bool ResetStrikesOnProgress { get; init; } + + public bool IgnorePrivate { get; init; } + + public bool DeletePrivate { get; init; } + + public string MinSpeed { get; init; } = string.Empty; + + [JsonIgnore] + public ByteSize MinSpeedByteSize => string.IsNullOrEmpty(MinSpeed) ? new ByteSize(0) : ByteSize.Parse(MinSpeed); + + public double MaxTime { get; init; } + + public string IgnoreAboveSize { get; init; } = string.Empty; + + [JsonIgnore] + public ByteSize? IgnoreAboveSizeByteSize => string.IsNullOrEmpty(IgnoreAboveSize) ? null : ByteSize.Parse(IgnoreAboveSize); + + public void Validate() + { + if (MaxStrikes is > 0 and < 3) + { + throw new ValidationException("the minimum value for slow max strikes must be 3"); + } + + if (MaxStrikes > 0) + { + bool isSpeedSet = !string.IsNullOrEmpty(MinSpeed); + + if (isSpeedSet && ByteSize.TryParse(MinSpeed, out _) is false) + { + throw new ValidationException("invalid value for slow min speed"); + } + + if (MaxTime < 0) + { + throw new ValidationException("invalid value for slow max time"); + } + + if (!isSpeedSet && MaxTime is 0) + { + throw new ValidationException("either slow min speed or slow max time must be set"); + } + + bool isIgnoreAboveSizeSet = !string.IsNullOrEmpty(IgnoreAboveSize); + + if (isIgnoreAboveSizeSet && ByteSize.TryParse(IgnoreAboveSize, out _) is false) + { + throw new ValidationException($"invalid value for slow ignore above size: {IgnoreAboveSize}"); + } + } + } +} \ No newline at end of file diff --git a/code/Common/Configuration/QueueCleaner/StalledConfig.cs b/code/Common/Configuration/QueueCleaner/StalledConfig.cs new file mode 100644 index 00000000..d418a113 --- /dev/null +++ b/code/Common/Configuration/QueueCleaner/StalledConfig.cs @@ -0,0 +1,29 @@ +using Common.Exceptions; + +namespace Common.Configuration.QueueCleaner; + +public sealed record StalledConfig +{ + public ushort MaxStrikes { get; init; } + + public bool ResetStrikesOnProgress { get; init; } + + public bool IgnorePrivate { get; init; } + + public bool DeletePrivate { get; init; } + + public ushort DownloadingMetadataMaxStrikes { get; init; } + + public void Validate() + { + if (MaxStrikes is > 0 and < 3) + { + throw new ValidationException("the minimum value for stalled max strikes must be 3"); + } + + if (DownloadingMetadataMaxStrikes is > 0 and < 3) + { + throw new ValidationException("the minimum value for downloading metadata max strikes must be 3"); + } + } +} \ No newline at end of file diff --git a/code/Executable/Controllers/ConfigurationController.cs b/code/Executable/Controllers/ConfigurationController.cs index de0ca4a0..a027c166 100644 --- a/code/Executable/Controllers/ConfigurationController.cs +++ b/code/Executable/Controllers/ConfigurationController.cs @@ -4,14 +4,10 @@ using Common.Configuration.DownloadCleaner; using Common.Configuration.DownloadClient; using Common.Configuration.DTOs.Arr; using Common.Configuration.DTOs.ContentBlocker; -using Common.Configuration.DTOs.DownloadCleaner; using Common.Configuration.DTOs.DownloadClient; using Common.Configuration.DTOs.General; -using Common.Configuration.DTOs.IgnoredDownloads; using Common.Configuration.DTOs.Notification; -using Common.Configuration.DTOs.QueueCleaner; using Common.Configuration.General; -using Common.Configuration.IgnoredDownloads; using Common.Configuration.Notification; using Common.Configuration.QueueCleaner; using Infrastructure.Configuration; @@ -44,24 +40,14 @@ public class ConfigurationController : ControllerBase public async Task GetQueueCleanerConfig() { var config = await _configManager.GetConfigurationAsync(); - var dto = config.Adapt(); - return Ok(dto); - } - - [HttpGet("content_blocker")] - public async Task GetContentBlockerConfig() - { - var config = await _configManager.GetConfigurationAsync(); - var dto = config.Adapt(); - return Ok(dto); + return Ok(config); } [HttpGet("download_cleaner")] public async Task GetDownloadCleanerConfig() { var config = await _configManager.GetConfigurationAsync(); - var dto = config.Adapt(); - return Ok(dto); + return Ok(config); } [HttpGet("download_client")] @@ -72,14 +58,6 @@ public class ConfigurationController : ControllerBase return Ok(dto); } - [HttpGet("ignored_downloads")] - public async Task GetIgnoredDownloadsConfig() - { - var config = await _configManager.GetConfigurationAsync(); - var dto = config.Adapt(); - return Ok(dto); - } - [HttpGet("general")] public async Task GetGeneralConfig() { @@ -121,7 +99,7 @@ public class ConfigurationController : ControllerBase } [HttpPut("queue_cleaner")] - public async Task UpdateQueueCleanerConfig([FromBody] QueueCleanerConfigUpdateDto dto) + public async Task UpdateQueueCleanerConfig([FromBody] QueueCleanerConfig dto) { // Get existing config var config = await _configManager.GetConfigurationAsync(); @@ -202,7 +180,7 @@ public class ConfigurationController : ControllerBase } [HttpPut("download_cleaner")] - public async Task UpdateDownloadCleanerConfig([FromBody] DownloadCleanerConfigUpdateDto dto) + public async Task UpdateDownloadCleanerConfig([FromBody] DownloadCleanerConfig dto) { // Get existing config var config = await _configManager.GetConfigurationAsync(); @@ -248,27 +226,8 @@ public class ConfigurationController : ControllerBase return Ok(new { Message = "DownloadClient configuration updated successfully" }); } - [HttpPut("ignored_downloads")] - public async Task UpdateIgnoredDownloadsConfig([FromBody] IgnoredDownloadsConfigUpdateDto dto) - { - // Get existing config - var config = await _configManager.GetConfigurationAsync(); - - // Apply updates from DTO - dto.Adapt(config); - - // Persist the configuration - var result = await _configManager.SaveConfigurationAsync(config); - if (!result) - { - return StatusCode(500, "Failed to save IgnoredDownloads configuration"); - } - - return Ok(new { Message = "IgnoredDownloads configuration updated successfully" }); - } - [HttpPut("general")] - public async Task UpdateGeneralConfig([FromBody] GeneralConfigUpdateDto dto) + public async Task UpdateGeneralConfig([FromBody] GeneralConfig dto) { // Get existing config to preserve sensitive data var config = await _configManager.GetConfigurationAsync(); diff --git a/code/Executable/Controllers/IgnoredDownloadsController.cs b/code/Executable/Controllers/IgnoredDownloadsController.cs deleted file mode 100644 index 1e478ee3..00000000 --- a/code/Executable/Controllers/IgnoredDownloadsController.cs +++ /dev/null @@ -1,121 +0,0 @@ -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 12d703ef..3deb1039 100644 --- a/code/Executable/DependencyInjection/ServicesDI.cs +++ b/code/Executable/DependencyInjection/ServicesDI.cs @@ -51,6 +51,5 @@ public static class ServicesDI .AddTransient() .AddTransient() .AddTransient() - .AddSingleton() - .AddSingleton(); + .AddSingleton(); } \ No newline at end of file diff --git a/code/Infrastructure/Configuration/ConfigManager.cs b/code/Infrastructure/Configuration/ConfigManager.cs index a317c3e9..f8b44b54 100644 --- a/code/Infrastructure/Configuration/ConfigManager.cs +++ b/code/Infrastructure/Configuration/ConfigManager.cs @@ -1,10 +1,8 @@ -using System.Reflection; using Common.Configuration; using Common.Configuration.Arr; using Common.Configuration.DownloadCleaner; using Common.Configuration.DownloadClient; using Common.Configuration.General; -using Common.Configuration.IgnoredDownloads; using Common.Configuration.Notification; using Common.Configuration.QueueCleaner; using Microsoft.Extensions.Logging; @@ -35,7 +33,6 @@ public class ConfigManager : IConfigManager { typeof(QueueCleanerConfig), "queue_cleaner.json" }, { typeof(DownloadCleanerConfig), "download_cleaner.json" }, { typeof(DownloadClientConfig), "download_client.json" }, - { typeof(IgnoredDownloadsConfig), "ignored_downloads.json" }, { typeof(NotificationsConfig), "notifications.json" } }; } @@ -115,11 +112,6 @@ public class ConfigManager : IConfigManager return await _configProvider.ReadConfigurationAsync(_settingsPaths[type]); } - if (type == typeof(IgnoredDownloadsConfig)) - { - return await _configProvider.ReadConfigurationAsync(_settingsPaths[type]); - } - if (type == typeof(NotificationsConfig)) { return await _configProvider.ReadConfigurationAsync(_settingsPaths[type]); diff --git a/code/Infrastructure/Configuration/IConfigManager.cs b/code/Infrastructure/Configuration/IConfigManager.cs index 8c1b313b..3c447159 100644 --- a/code/Infrastructure/Configuration/IConfigManager.cs +++ b/code/Infrastructure/Configuration/IConfigManager.cs @@ -1,12 +1,4 @@ -using Common.Configuration.Arr; -using Common.Configuration.DownloadCleaner; -using Common.Configuration.DownloadClient; -using Common.Configuration.General; -using Common.Configuration.IgnoredDownloads; -using Common.Configuration.Notification; -using Common.Configuration.QueueCleaner; - -namespace Infrastructure.Configuration; +namespace Infrastructure.Configuration; /// /// Provides configuration management for various components with thread-safe file access. diff --git a/code/Infrastructure/Services/IgnoredDownloadsService.cs b/code/Infrastructure/Services/IgnoredDownloadsService.cs deleted file mode 100644 index 01e070bc..00000000 --- a/code/Infrastructure/Services/IgnoredDownloadsService.cs +++ /dev/null @@ -1,137 +0,0 @@ -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 IConfigManager _configManager; - - public IgnoredDownloadsService( - ILogger logger, - IConfigManager configManager - ) - { - _logger = logger; - _configManager = configManager; - } - - public async Task> GetIgnoredDownloadsAsync() - { - var config = await _configManager.GetConfigurationAsync(); - - return config.IgnoredDownloads; - } - - public async Task AddIgnoredDownloadAsync(string downloadId) - { - if (string.IsNullOrWhiteSpace(downloadId)) - { - return false; - } - - var config = await _configManager.GetConfigurationAsync(); - - if (!config.IgnoredDownloads.Contains(downloadId, StringComparer.OrdinalIgnoreCase)) - { - var updatedList = config.IgnoredDownloads; - updatedList.Add(downloadId); - config = new IgnoredDownloadsConfig - { - IgnoredDownloads = updatedList - }; - } - else - { - // Already in the list - return true; - } - - var result = await _configManager.SaveConfigurationAsync(config); - if (result) - { - _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.GetConfigurationAsync(); - var updatedList = config.IgnoredDownloads - .Where(id => !string.Equals(id, downloadId, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (updatedList.Count == config.IgnoredDownloads.Count) - { - return true; - } - - var newConfig = new IgnoredDownloadsConfig - { - IgnoredDownloads = updatedList - }; - - var result = await _configManager.SaveConfigurationAsync(newConfig); - if (result) - { - _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.SaveConfigurationAsync(config); - - if (result) - { - // Update cache - _logger.LogInformation("Cleared all ignored downloads"); - } - - return result; - } -} diff --git a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs index df64e53b..9e875dc6 100644 --- a/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs +++ b/code/Infrastructure/Verticals/DownloadCleaner/DownloadCleaner.cs @@ -20,7 +20,6 @@ namespace Infrastructure.Verticals.DownloadCleaner; public sealed class DownloadCleaner : GenericHandler { private readonly DownloadCleanerConfig _config; - private readonly IIgnoredDownloadsService _ignoredDownloadsService; private readonly HashSet _excludedHashes = []; private static bool _hardLinkCategoryCreated; @@ -32,20 +31,13 @@ public sealed class DownloadCleaner : GenericHandler IBus messageBus, ArrClientFactory arrClientFactory, ArrQueueIterator arrArrQueueIterator, - DownloadServiceFactory downloadServiceFactory, - IIgnoredDownloadsService ignoredDownloadsService + DownloadServiceFactory downloadServiceFactory ) : base( logger, cache, messageBus, - arrClientFactory, arrArrQueueIterator, downloadServiceFactory + arrClientFactory, arrArrQueueIterator, downloadServiceFactory, configManager ) { - _ignoredDownloadsService = ignoredDownloadsService; - _config = configManager.GetConfiguration(); - _downloadClientConfig = configManager.GetConfiguration(); - _sonarrConfig = configManager.GetConfiguration(); - _radarrConfig = configManager.GetConfiguration(); - _lidarrConfig = configManager.GetConfiguration(); } protected override void InitializeDownloadServices() @@ -107,7 +99,7 @@ public sealed class DownloadCleaner : GenericHandler return; } - IReadOnlyList ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); + IReadOnlyList ignoredDownloads = _generalConfig.IgnoredDownloads; // Process each client separately var allDownloads = new List(); diff --git a/code/Infrastructure/Verticals/Jobs/GenericHandler.cs b/code/Infrastructure/Verticals/Jobs/GenericHandler.cs index 5fd6a21d..527e52ce 100644 --- a/code/Infrastructure/Verticals/Jobs/GenericHandler.cs +++ b/code/Infrastructure/Verticals/Jobs/GenericHandler.cs @@ -1,5 +1,6 @@ using Common.Configuration.Arr; using Common.Configuration.DownloadClient; +using Common.Configuration.General; using Data.Enums; using Data.Models.Arr; using Data.Models.Arr.Queue; @@ -17,10 +18,11 @@ namespace Infrastructure.Verticals.Jobs; public abstract class GenericHandler : IHandler, IDisposable { protected readonly ILogger _logger; - protected DownloadClientConfig _downloadClientConfig = new(); - protected SonarrConfig _sonarrConfig = new(); - protected RadarrConfig _radarrConfig = new(); - protected LidarrConfig _lidarrConfig = new(); + protected readonly GeneralConfig _generalConfig; + protected readonly DownloadClientConfig _downloadClientConfig; + protected readonly SonarrConfig _sonarrConfig; + protected readonly RadarrConfig _radarrConfig; + protected readonly LidarrConfig _lidarrConfig; protected readonly IMemoryCache _cache; protected readonly IBus _messageBus; protected readonly ArrClientFactory _arrClientFactory; @@ -28,7 +30,7 @@ public abstract class GenericHandler : IHandler, IDisposable protected readonly DownloadServiceFactory _downloadServiceFactory; // Collection of download services for use with multiple clients - protected readonly List _downloadServices = new(); + protected readonly List _downloadServices = []; protected GenericHandler( ILogger logger, @@ -36,7 +38,8 @@ public abstract class GenericHandler : IHandler, IDisposable IBus messageBus, ArrClientFactory arrClientFactory, ArrQueueIterator arrArrQueueIterator, - DownloadServiceFactory downloadServiceFactory + DownloadServiceFactory downloadServiceFactory, + IConfigManager configManager ) { _logger = logger; @@ -45,6 +48,11 @@ public abstract class GenericHandler : IHandler, IDisposable _arrClientFactory = arrClientFactory; _arrArrQueueIterator = arrArrQueueIterator; _downloadServiceFactory = downloadServiceFactory; + _generalConfig = configManager.GetConfiguration(); + _downloadClientConfig = configManager.GetConfiguration(); + _sonarrConfig = configManager.GetConfiguration(); + _radarrConfig = configManager.GetConfiguration(); + _lidarrConfig = configManager.GetConfiguration(); } /// diff --git a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs index ee04dd96..9eb9b85e 100644 --- a/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs +++ b/code/Infrastructure/Verticals/QueueCleaner/QueueCleaner.cs @@ -1,36 +1,26 @@ using Common.Configuration.Arr; -using Common.Configuration.DownloadClient; using Common.Configuration.QueueCleaner; using Data.Enums; -using Data.Models.Arr; using Data.Models.Arr.Queue; using Infrastructure.Configuration; using Infrastructure.Helpers; -using Infrastructure.Services; using Infrastructure.Verticals.Arr; using Infrastructure.Verticals.Arr.Interfaces; using Infrastructure.Verticals.Context; using Infrastructure.Verticals.DownloadClient; using Infrastructure.Verticals.DownloadClient.Factory; -using Infrastructure.Verticals.DownloadRemover.Models; using Infrastructure.Verticals.Jobs; -using Infrastructure.Verticals.Notifications; using MassTransit; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using LogContext = Serilog.Context.LogContext; -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Text.RegularExpressions; using Infrastructure.Verticals.ContentBlocker; -using BlocklistSettings = Common.Configuration.QueueCleaner.BlocklistSettings; namespace Infrastructure.Verticals.QueueCleaner; public sealed class QueueCleaner : GenericHandler { private readonly QueueCleanerConfig _config; - private readonly IIgnoredDownloadsService _ignoredDownloadsService; private readonly IDownloadClientFactory _downloadClientFactory; private readonly BlocklistProvider _blocklistProvider; @@ -42,23 +32,17 @@ public sealed class QueueCleaner : GenericHandler ArrClientFactory arrClientFactory, ArrQueueIterator arrArrQueueIterator, DownloadServiceFactory downloadServiceFactory, - IIgnoredDownloadsService ignoredDownloadsService, IDownloadClientFactory downloadClientFactory, BlocklistProvider blocklistProvider ) : base( logger, cache, messageBus, - arrClientFactory, arrArrQueueIterator, downloadServiceFactory + arrClientFactory, arrArrQueueIterator, downloadServiceFactory, configManager ) { - _ignoredDownloadsService = ignoredDownloadsService; _downloadClientFactory = downloadClientFactory; _blocklistProvider = blocklistProvider; _config = configManager.GetConfiguration(); - _downloadClientConfig = configManager.GetConfiguration(); - _sonarrConfig = configManager.GetConfiguration(); - _radarrConfig = configManager.GetConfiguration(); - _lidarrConfig = configManager.GetConfiguration(); } public override async Task ExecuteAsync() @@ -83,7 +67,7 @@ public sealed class QueueCleaner : GenericHandler protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig config) { - IReadOnlyList ignoredDownloads = await _ignoredDownloadsService.GetIgnoredDownloadsAsync(); + IReadOnlyList ignoredDownloads = _generalConfig.IgnoredDownloads; using var _ = LogContext.PushProperty(LogProperties.Category, instanceType.ToString()); diff --git a/code/UI/src/app/settings/queue-cleaner/queue-cleaner-settings.component.html b/code/UI/src/app/settings/queue-cleaner/queue-cleaner-settings.component.html index 4ae08f96..46912937 100644 --- a/code/UI/src/app/settings/queue-cleaner/queue-cleaner-settings.component.html +++ b/code/UI/src/app/settings/queue-cleaner/queue-cleaner-settings.component.html @@ -49,22 +49,6 @@ -
- -
- - When enabled, jobs will run one after another instead of in parallel -
-
- -
- -
- - Path to the file containing ignored downloads -
-
- diff --git a/code/UI/src/app/settings/queue-cleaner/queue-cleaner-settings.component.ts b/code/UI/src/app/settings/queue-cleaner/queue-cleaner-settings.component.ts index 7d596791..7f7878ec 100644 --- a/code/UI/src/app/settings/queue-cleaner/queue-cleaner-settings.component.ts +++ b/code/UI/src/app/settings/queue-cleaner/queue-cleaner-settings.component.ts @@ -91,8 +91,6 @@ export class QueueCleanerSettingsComponent implements OnDestroy { every: [{ value: 5, disabled: true }, [Validators.required, Validators.min(1)]], type: [{ value: ScheduleUnit.Minutes, disabled: true }], }), - runSequentially: [{ value: false, disabled: true }], - ignoredDownloadsPath: [{ value: "", disabled: true }], // Failed Import settings - nested group failedImport: this.formBuilder.group({ @@ -152,8 +150,6 @@ export class QueueCleanerSettingsComponent implements OnDestroy { // Build form values for the nested configuration structure const formValues: any = { enabled: config.enabled, - runSequentially: config.runSequentially, - ignoredDownloadsPath: config.ignoredDownloadsPath, }; // Add jobSchedule if it exists @@ -346,8 +342,6 @@ export class QueueCleanerSettingsComponent implements OnDestroy { if (enabled) { jobScheduleGroup.get("every")?.enable({ emitEvent: false }); jobScheduleGroup.get("type")?.enable({ emitEvent: false }); - this.queueCleanerForm.get("runSequentially")?.enable({ emitEvent: false }); - this.queueCleanerForm.get("ignoredDownloadsPath")?.enable({ emitEvent: false }); // Update individual config sections only if they are enabled const failedImportMaxStrikes = this.queueCleanerForm.get("failedImport.maxStrikes")?.value; @@ -362,8 +356,6 @@ export class QueueCleanerSettingsComponent implements OnDestroy { } else { jobScheduleGroup.get("every")?.disable({ emitEvent: false }); jobScheduleGroup.get("type")?.disable({ emitEvent: false }); - this.queueCleanerForm.get("runSequentially")?.disable({ emitEvent: false }); - this.queueCleanerForm.get("ignoredDownloadsPath")?.disable({ emitEvent: false }); // Save current active accordion state before clearing it // This will be empty when we collapse all accordions @@ -497,8 +489,6 @@ export class QueueCleanerSettingsComponent implements OnDestroy { // The cronExpression will be generated from the jobSchedule when saving cronExpression: "", jobSchedule: formValues.jobSchedule, - runSequentially: formValues.runSequentially, - ignoredDownloadsPath: formValues.ignoredDownloadsPath || "", failedImport: { maxStrikes: formValues.failedImport?.maxStrikes || 0, @@ -558,8 +548,6 @@ export class QueueCleanerSettingsComponent implements OnDestroy { every: 5, type: ScheduleUnit.Minutes, }, - runSequentially: false, - ignoredDownloadsPath: "", // Failed Import settings (nested) failedImport: { diff --git a/code/UI/src/app/shared/models/queue-cleaner-config.model.ts b/code/UI/src/app/shared/models/queue-cleaner-config.model.ts index 53f58a90..7de46713 100644 --- a/code/UI/src/app/shared/models/queue-cleaner-config.model.ts +++ b/code/UI/src/app/shared/models/queue-cleaner-config.model.ts @@ -59,8 +59,6 @@ export interface QueueCleanerConfig { enabled: boolean; cronExpression: string; jobSchedule?: JobSchedule; // UI-only field, not sent to API - runSequentially: boolean; - ignoredDownloadsPath: string; // Nested configurations failedImport: FailedImportConfig;