diff --git a/src/NzbDrone.Core/Indexers/IndexerBaseSettings.cs b/src/NzbDrone.Core/Indexers/IndexerBaseSettings.cs index 5849571e4..0992cbacf 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBaseSettings.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBaseSettings.cs @@ -7,20 +7,33 @@ public class IndexerCommonSettingsValidator : AbstractValidator c.QueryLimit).GreaterThan(0).When(c => c.QueryLimit.HasValue).WithMessage("Should be greater than zero"); + RuleFor(c => c.QueryLimit) + .GreaterThan(0) + .When(c => c.QueryLimit.HasValue) + .WithMessage("Should be greater than zero"); - RuleFor(c => c.GrabLimit).GreaterThan(0).When(c => c.GrabLimit.HasValue).WithMessage("Should be greater than zero"); + RuleFor(c => c.GrabLimit) + .GreaterThan(0) + .When(c => c.GrabLimit.HasValue) + .WithMessage("Should be greater than zero"); } } public class IndexerBaseSettings { - private static readonly IndexerCommonSettingsValidator Validator = new (); - - [FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of queries within a rolling 24 hour period Prowlarr will allow to the site", Advanced = true)] + [FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of max queries as specified by the respective unit that Prowlarr will allow to the site", Advanced = true)] public int? QueryLimit { get; set; } - [FieldDefinition(2, Type = FieldType.Number, Label = "Grab Limit", HelpText = "The number of grabs within a rolling 24 hour period Prowlarr will allow to the site", Advanced = true)] + [FieldDefinition(2, Type = FieldType.Number, Label = "Grab Limit", HelpText = "The number of max grabs as specified by the respective unit that Prowlarr will allow to the site", Advanced = true)] public int? GrabLimit { get; set; } + + [FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(IndexerLimitsUnit), Label = "Limits Unit", HelpText = "The unit of time for counting limits per indexer", Advanced = true)] + public int LimitsUnit { get; set; } = (int)IndexerLimitsUnit.Day; + } + + public enum IndexerLimitsUnit + { + Day = 0, + Hour = 1 } } diff --git a/src/NzbDrone.Core/Indexers/IndexerLimitService.cs b/src/NzbDrone.Core/Indexers/IndexerLimitService.cs index bad49daab..112255f17 100644 --- a/src/NzbDrone.Core/Indexers/IndexerLimitService.cs +++ b/src/NzbDrone.Core/Indexers/IndexerLimitService.cs @@ -11,6 +11,7 @@ public interface IIndexerLimitService bool AtQueryLimit(IndexerDefinition indexer); int CalculateRetryAfterDownloadLimit(IndexerDefinition indexer); int CalculateRetryAfterQueryLimit(IndexerDefinition indexer); + int CalculateIntervalLimitHours(IndexerDefinition indexer); } public class IndexerLimitService : IIndexerLimitService @@ -27,19 +28,20 @@ public IndexerLimitService(IHistoryService historyService, public bool AtDownloadLimit(IndexerDefinition indexer) { - if (indexer.Id > 0 && ((IIndexerSettings)indexer.Settings).BaseSettings.GrabLimit.HasValue) + if (indexer is { Id: > 0 } && ((IIndexerSettings)indexer.Settings).BaseSettings.GrabLimit.HasValue) { - var grabCount = _historyService.CountSince(indexer.Id, DateTime.Now.AddHours(-24), new List { HistoryEventType.ReleaseGrabbed }); + var intervalLimitHours = CalculateIntervalLimitHours(indexer); + var grabCount = _historyService.CountSince(indexer.Id, DateTime.Now.AddHours(-1 * intervalLimitHours), new List { HistoryEventType.ReleaseGrabbed }); var grabLimit = ((IIndexerSettings)indexer.Settings).BaseSettings.GrabLimit; if (grabCount >= grabLimit) { - _logger.Info("Indexer {0} has performed {1} of possible {2} grabs in last 24 hours, exceeding the maximum grab limit", indexer.Name, grabCount, grabLimit); + _logger.Info("Indexer {0} has performed {1} of possible {2} grabs in last {3} hour(s), exceeding the maximum grab limit", indexer.Name, grabCount, grabLimit, intervalLimitHours); return true; } - _logger.Debug("Indexer {0} has performed {1} of possible {2} grabs in last 24 hours, proceeding", indexer.Name, grabCount, grabLimit); + _logger.Debug("Indexer {0} has performed {1} of possible {2} grabs in last {3} hour(s), proceeding", indexer.Name, grabCount, grabLimit, intervalLimitHours); } return false; @@ -47,19 +49,20 @@ public bool AtDownloadLimit(IndexerDefinition indexer) public bool AtQueryLimit(IndexerDefinition indexer) { - if (indexer.Id > 0 && ((IIndexerSettings)indexer.Settings).BaseSettings.QueryLimit.HasValue) + if (indexer is { Id: > 0 } && ((IIndexerSettings)indexer.Settings).BaseSettings.QueryLimit.HasValue) { - var queryCount = _historyService.CountSince(indexer.Id, DateTime.Now.AddHours(-24), new List { HistoryEventType.IndexerQuery, HistoryEventType.IndexerRss }); + var intervalLimitHours = CalculateIntervalLimitHours(indexer); + var queryCount = _historyService.CountSince(indexer.Id, DateTime.Now.AddHours(-1 * intervalLimitHours), new List { HistoryEventType.IndexerQuery, HistoryEventType.IndexerRss }); var queryLimit = ((IIndexerSettings)indexer.Settings).BaseSettings.QueryLimit; if (queryCount >= queryLimit) { - _logger.Info("Indexer {0} has performed {1} of possible {2} queries in last 24 hours, exceeding the maximum query limit", indexer.Name, queryCount, queryLimit); + _logger.Info("Indexer {0} has performed {1} of possible {2} queries in last {3} hour(s), exceeding the maximum query limit", indexer.Name, queryCount, queryLimit, intervalLimitHours); return true; } - _logger.Debug("Indexer {0} has performed {1} of possible {2} queries in last 24 hours, proceeding", indexer.Name, queryCount, queryLimit); + _logger.Debug("Indexer {0} has performed {1} of possible {2} queries in last {3} hour(s), proceeding", indexer.Name, queryCount, queryLimit, intervalLimitHours); } return false; @@ -67,15 +70,16 @@ public bool AtQueryLimit(IndexerDefinition indexer) public int CalculateRetryAfterDownloadLimit(IndexerDefinition indexer) { - if (indexer.Id > 0 && ((IIndexerSettings)indexer.Settings).BaseSettings.GrabLimit.HasValue) + if (indexer is { Id: > 0 } && ((IIndexerSettings)indexer.Settings).BaseSettings.GrabLimit.HasValue) { + var intervalLimitHours = CalculateIntervalLimitHours(indexer); var grabLimit = ((IIndexerSettings)indexer.Settings).BaseSettings.GrabLimit.GetValueOrDefault(); - var firstHistorySince = _historyService.FindFirstForIndexerSince(indexer.Id, DateTime.Now.AddHours(-24), new List { HistoryEventType.ReleaseGrabbed }, grabLimit); + var firstHistorySince = _historyService.FindFirstForIndexerSince(indexer.Id, DateTime.Now.AddHours(-1 * intervalLimitHours), new List { HistoryEventType.ReleaseGrabbed }, grabLimit); if (firstHistorySince != null) { - return Convert.ToInt32(firstHistorySince.Date.ToLocalTime().AddHours(24).Subtract(DateTime.Now).TotalSeconds); + return Convert.ToInt32(firstHistorySince.Date.ToLocalTime().AddHours(intervalLimitHours).Subtract(DateTime.Now).TotalSeconds); } } @@ -84,19 +88,35 @@ public int CalculateRetryAfterDownloadLimit(IndexerDefinition indexer) public int CalculateRetryAfterQueryLimit(IndexerDefinition indexer) { - if (indexer.Id > 0 && ((IIndexerSettings)indexer.Settings).BaseSettings.QueryLimit.HasValue) + if (indexer is { Id: > 0 } && ((IIndexerSettings)indexer.Settings).BaseSettings.QueryLimit.HasValue) { + var intervalLimitHours = CalculateIntervalLimitHours(indexer); var queryLimit = ((IIndexerSettings)indexer.Settings).BaseSettings.QueryLimit.GetValueOrDefault(); - var firstHistorySince = _historyService.FindFirstForIndexerSince(indexer.Id, DateTime.Now.AddHours(-24), new List { HistoryEventType.IndexerQuery, HistoryEventType.IndexerRss }, queryLimit); + var firstHistorySince = _historyService.FindFirstForIndexerSince(indexer.Id, DateTime.Now.AddHours(-1 * intervalLimitHours), new List { HistoryEventType.IndexerQuery, HistoryEventType.IndexerRss }, queryLimit); if (firstHistorySince != null) { - return Convert.ToInt32(firstHistorySince.Date.ToLocalTime().AddHours(24).Subtract(DateTime.Now).TotalSeconds); + return Convert.ToInt32(firstHistorySince.Date.ToLocalTime().AddHours(intervalLimitHours).Subtract(DateTime.Now).TotalSeconds); } } return 0; } + + public int CalculateIntervalLimitHours(IndexerDefinition indexer) + { + if (indexer is { Id: > 0 }) + { + return ((IIndexerSettings)indexer.Settings).BaseSettings.LimitsUnit switch + { + (int)IndexerLimitsUnit.Hour => 1, + _ => 24 + }; + } + + // Fallback to limits per day + return 24; + } } } diff --git a/src/Prowlarr.Api.V1/Indexers/NewznabController.cs b/src/Prowlarr.Api.V1/Indexers/NewznabController.cs index 45b611dcc..65a617a0f 100644 --- a/src/Prowlarr.Api.V1/Indexers/NewznabController.cs +++ b/src/Prowlarr.Api.V1/Indexers/NewznabController.cs @@ -162,7 +162,10 @@ public async Task GetNewznabResponse(int id, [FromQuery] NewznabR var retryAfterQueryLimit = _indexerLimitService.CalculateRetryAfterQueryLimit(indexerDef); AddRetryAfterHeader(retryAfterQueryLimit); - return CreateResponse(CreateErrorXML(429, $"User configurable Indexer Query Limit of {((IIndexerSettings)indexer.Definition.Settings).BaseSettings.QueryLimit} reached."), statusCode: StatusCodes.Status429TooManyRequests); + var queryLimit = ((IIndexerSettings)indexer.Definition.Settings).BaseSettings.QueryLimit; + var intervalLimitHours = _indexerLimitService.CalculateIntervalLimitHours(indexerDef); + + return CreateResponse(CreateErrorXML(429, $"User configurable Indexer Query Limit of {queryLimit} in last {intervalLimitHours} hour(s) reached."), statusCode: StatusCodes.Status429TooManyRequests); } switch (requestType) @@ -226,7 +229,10 @@ public async Task GetDownload(int id, string link, string file) var retryAfterDownloadLimit = _indexerLimitService.CalculateRetryAfterDownloadLimit(indexerDef); AddRetryAfterHeader(retryAfterDownloadLimit); - return CreateResponse(CreateErrorXML(429, $"User configurable Indexer Grab Limit of {((IIndexerSettings)indexer.Definition.Settings).BaseSettings.GrabLimit} reached."), statusCode: StatusCodes.Status429TooManyRequests); + var grabLimit = ((IIndexerSettings)indexer.Definition.Settings).BaseSettings.GrabLimit; + var intervalLimitHours = _indexerLimitService.CalculateIntervalLimitHours(indexerDef); + + return CreateResponse(CreateErrorXML(429, $"User configurable Indexer Grab Limit of {grabLimit} in last {intervalLimitHours} hour(s) reached."), statusCode: StatusCodes.Status429TooManyRequests); } if (link.IsNullOrWhiteSpace() || file.IsNullOrWhiteSpace())