using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException; namespace Cleanuparr.Persistence.Models.Configuration.DownloadCleaner; public sealed record DownloadCleanerConfig : IJobConfig { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } = Guid.NewGuid(); public bool Enabled { get; set; } public string CronExpression { get; set; } = "0 0 * * * ?"; /// /// Indicates whether to use the CronExpression directly or convert from a user-friendly schedule /// public bool UseAdvancedScheduling { get; set; } public List Categories { get; set; } = []; public bool DeletePrivate { get; set; } /// /// Indicates whether unlinked download handling is enabled /// public bool UnlinkedEnabled { get; set; } = false; public string UnlinkedTargetCategory { get; set; } = "cleanuparr-unlinked"; public bool UnlinkedUseTag { get; set; } public List UnlinkedIgnoredRootDirs { get; set; } = []; public List UnlinkedCategories { get; set; } = []; public List IgnoredDownloads { get; set; } = []; public void Validate() { if (!Enabled) { return; } // Validate that at least one feature is configured bool hasSeedingCategories = Categories.Count > 0; bool hasUnlinkedFeature = UnlinkedEnabled && UnlinkedCategories.Count > 0 && !string.IsNullOrWhiteSpace(UnlinkedTargetCategory); if (!hasSeedingCategories && !hasUnlinkedFeature) { throw new ValidationException("No features are enabled"); } if (Categories.GroupBy(x => x.Name).Any(x => x.Count() > 1)) { throw new ValidationException("Duplicated clean categories found"); } Categories.ForEach(x => x.Validate()); // Only validate unlinked settings if unlinked handling is enabled if (!UnlinkedEnabled) { return; } if (string.IsNullOrEmpty(UnlinkedTargetCategory)) { throw new ValidationException("unlinked target category is required"); } if (UnlinkedCategories.Count is 0) { throw new ValidationException("No unlinked categories configured"); } if (UnlinkedCategories.Contains(UnlinkedTargetCategory)) { throw new ValidationException("The unlinked target category should not be present in unlinked categories"); } if (UnlinkedCategories.Any(string.IsNullOrEmpty)) { throw new ValidationException("Empty unlinked category filter found"); } foreach (var dir in UnlinkedIgnoredRootDirs.Where(d => !string.IsNullOrEmpty(d))) { if (!Directory.Exists(dir)) { throw new ValidationException($"{dir} root directory does not exist"); } } } }