From 8770a8b18eb31cf6889ff588db4b16aca0665384 Mon Sep 17 00:00:00 2001 From: Flaminel Date: Tue, 21 Apr 2026 16:56:58 +0300 Subject: [PATCH] Add hour intervals for Seeker (#578) --- .../Configuration/Seeker/SeekerConfigTests.cs | 12 +++++++++++- .../Models/Configuration/Seeker/SeekerConfig.cs | 17 +++++++++++++++-- .../Cleanuparr.Shared/Helpers/Constants.cs | 2 +- .../settings/seeker/seeker.component.ts | 5 +++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/code/backend/Cleanuparr.Persistence.Tests/Models/Configuration/Seeker/SeekerConfigTests.cs b/code/backend/Cleanuparr.Persistence.Tests/Models/Configuration/Seeker/SeekerConfigTests.cs index 93ffa23c..c8254174 100644 --- a/code/backend/Cleanuparr.Persistence.Tests/Models/Configuration/Seeker/SeekerConfigTests.cs +++ b/code/backend/Cleanuparr.Persistence.Tests/Models/Configuration/Seeker/SeekerConfigTests.cs @@ -28,6 +28,11 @@ public sealed class SeekerConfigTests [InlineData((ushort)15)] [InlineData((ushort)20)] [InlineData((ushort)30)] + [InlineData((ushort)60)] + [InlineData((ushort)120)] + [InlineData((ushort)180)] + [InlineData((ushort)240)] + [InlineData((ushort)360)] public void Validate_WithValidIntervals_DoesNotThrow(ushort interval) { var config = new SeekerConfig { SearchInterval = interval }; @@ -51,7 +56,7 @@ public sealed class SeekerConfigTests [Fact] public void Validate_WithIntervalAboveMaximum_ThrowsValidationException() { - var config = new SeekerConfig { SearchInterval = 31 }; + var config = new SeekerConfig { SearchInterval = 361 }; var exception = Should.Throw(() => config.Validate()); exception.Message.ShouldContain("at most"); @@ -81,6 +86,11 @@ public sealed class SeekerConfigTests [InlineData((ushort)5, "0 */5 * * * ?")] [InlineData((ushort)10, "0 */10 * * * ?")] [InlineData((ushort)30, "0 */30 * * * ?")] + [InlineData((ushort)60, "0 0 * * * ?")] + [InlineData((ushort)120, "0 0 */2 * * ?")] + [InlineData((ushort)180, "0 0 */3 * * ?")] + [InlineData((ushort)240, "0 0 */4 * * ?")] + [InlineData((ushort)360, "0 0 */6 * * ?")] public void ToCronExpression_ReturnsCorrectCron(ushort interval, string expectedCron) { var config = new SeekerConfig { SearchInterval = interval }; diff --git a/code/backend/Cleanuparr.Persistence/Models/Configuration/Seeker/SeekerConfig.cs b/code/backend/Cleanuparr.Persistence/Models/Configuration/Seeker/SeekerConfig.cs index 2ad732ad..76c71706 100644 --- a/code/backend/Cleanuparr.Persistence/Models/Configuration/Seeker/SeekerConfig.cs +++ b/code/backend/Cleanuparr.Persistence/Models/Configuration/Seeker/SeekerConfig.cs @@ -63,7 +63,7 @@ public sealed record SeekerConfig : IConfig $"{nameof(SearchInterval)} must be at most {Constants.MaxSearchIntervalMinutes} minutes"); } - if (!new List { 2, 3, 4, 5, 6, 10, 12, 15, 20, 30 }.Contains(SearchInterval)) + 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}"); } @@ -77,5 +77,18 @@ public sealed record SeekerConfig : IConfig /// /// Generates the internal cron expression from the SearchInterval. /// - public string ToCronExpression() => $"0 */{SearchInterval} * * * ?"; + public string ToCronExpression() + { + if (SearchInterval < 60) + { + return $"0 */{SearchInterval} * * * ?"; + } + + if (SearchInterval == 60) + { + return "0 0 * * * ?"; + } + + return $"0 0 */{SearchInterval / 60} * * ?"; + } } diff --git a/code/backend/Cleanuparr.Shared/Helpers/Constants.cs b/code/backend/Cleanuparr.Shared/Helpers/Constants.cs index 24fc917a..bc5cf41c 100644 --- a/code/backend/Cleanuparr.Shared/Helpers/Constants.cs +++ b/code/backend/Cleanuparr.Shared/Helpers/Constants.cs @@ -17,7 +17,7 @@ public static class Constants public const ushort DefaultSearchIntervalMinutes = 3; public const ushort MinSearchIntervalMinutes = 2; - public const ushort MaxSearchIntervalMinutes = 30; + public const ushort MaxSearchIntervalMinutes = 360; public const string LogoUrl = "https://cdn.jsdelivr.net/gh/Cleanuparr/Cleanuparr@main/Logo/48.png"; diff --git a/code/frontend/src/app/features/settings/seeker/seeker.component.ts b/code/frontend/src/app/features/settings/seeker/seeker.component.ts index 416a585e..c5563ebc 100644 --- a/code/frontend/src/app/features/settings/seeker/seeker.component.ts +++ b/code/frontend/src/app/features/settings/seeker/seeker.component.ts @@ -27,6 +27,11 @@ const INTERVAL_OPTIONS: SelectOption[] = [ { label: '15 minutes', value: 15 }, { label: '20 minutes', value: 20 }, { label: '30 minutes', value: 30 }, + { label: '1 hour', value: 60 }, + { label: '2 hours', value: 120 }, + { label: '3 hours', value: 180 }, + { label: '4 hours', value: 240 }, + { label: '6 hours', value: 360 }, ]; const STRATEGY_OPTIONS: SelectOption[] = [