using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Cleanuparr.Domain.Enums; using Cleanuparr.Shared.Helpers; using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException; namespace Cleanuparr.Persistence.Models.Configuration.Seeker; /// /// The Seeker job is always running; only its behavior is configurable. /// public sealed record SeekerConfig : IConfig { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } = Guid.NewGuid(); /// /// Master toggle for all searching (reactive and proactive). /// When disabled, no searches are triggered at all. /// public bool SearchEnabled { get; set; } = true; /// /// Interval in minutes between Seeker runs. Controls how frequently searches are triggered. /// Valid values: 2, 3, 4, 5, 6, 10, 12, 15, 20, 30 (must divide 60 evenly for cron compatibility). /// public ushort SearchInterval { get; set; } = Constants.DefaultSearchIntervalMinutes; /// /// Enables proactive searching for missing items and quality upgrades. /// When disabled, only reactive searches (replacement after removal) are performed. /// public bool ProactiveSearchEnabled { get; set; } /// /// Strategy used to select which items to search during proactive searches /// public SelectionStrategy SelectionStrategy { get; set; } = SelectionStrategy.BalancedWeighted; /// /// Process one instance per run to spread indexer load during proactive searches /// public bool UseRoundRobin { get; set; } = true; /// /// Hours to wait after content is released before searching. /// Gives indexers time to process new releases. 0 = disabled. /// public int PostReleaseGraceHours { get; set; } = 6; public void Validate() { if (SearchInterval < Constants.MinSearchIntervalMinutes) { throw new ValidationException( $"{nameof(SearchInterval)} must be at least {Constants.MinSearchIntervalMinutes} minute(s)"); } if (SearchInterval > Constants.MaxSearchIntervalMinutes) { throw new ValidationException( $"{nameof(SearchInterval)} must be at most {Constants.MaxSearchIntervalMinutes} minutes"); } if (!new List { 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60, 120, 180, 240, 360 }.Contains(SearchInterval)) { throw new ValidationException($"Invalid search interval {SearchInterval}"); } if (PostReleaseGraceHours is < 0 or > 72) { throw new ValidationException($"{nameof(PostReleaseGraceHours)} must be between 0 and 72"); } } /// /// Generates the internal cron expression from the SearchInterval. /// public string ToCronExpression() { if (SearchInterval < 60) { return $"0 */{SearchInterval} * * * ?"; } if (SearchInterval == 60) { return "0 0 * * * ?"; } return $"0 0 */{SearchInterval / 60} * * ?"; } }