mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-04-24 05:47:22 -04:00
New: (Indexers) Per indexer api and download limits
This commit is contained in:
parent
0fe2cf5c2d
commit
4116c10caa
52 changed files with 298 additions and 80 deletions
|
@ -53,6 +53,16 @@ namespace NzbDrone.Common.Extensions
|
|||
return dateTime >= afterDateTime && dateTime <= beforeDateTime;
|
||||
}
|
||||
|
||||
public static DateTime EndOfDay(this DateTime date)
|
||||
{
|
||||
return new DateTime(date.Year, date.Month, date.Day, 23, 59, 59, 999);
|
||||
}
|
||||
|
||||
public static DateTime StartOfDay(this DateTime date)
|
||||
{
|
||||
return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,6 @@ namespace NzbDrone.Core.Test.IndexerTests
|
|||
}
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
|
@ -16,6 +17,7 @@ namespace NzbDrone.Core.History
|
|||
History MostRecentForIndexer(int indexerId);
|
||||
List<History> Since(DateTime date, HistoryEventType? eventType);
|
||||
void Cleanup(int days);
|
||||
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
|
||||
}
|
||||
|
||||
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
|
||||
|
@ -87,5 +89,21 @@ namespace NzbDrone.Core.History
|
|||
|
||||
return Query(builder).OrderBy(h => h.Date).ToList();
|
||||
}
|
||||
|
||||
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
|
||||
{
|
||||
var builder = new SqlBuilder()
|
||||
.SelectCount()
|
||||
.Where<History>(x => x.IndexerId == indexerId)
|
||||
.Where<History>(x => x.Date >= date)
|
||||
.Where<History>(x => eventTypes.Contains(x.EventType));
|
||||
|
||||
var sql = builder.AddPageCountTemplate(typeof(History));
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace NzbDrone.Core.History
|
|||
List<History> GetByIndexerId(int indexerId, HistoryEventType? eventType);
|
||||
void UpdateMany(List<History> toUpdate);
|
||||
List<History> Since(DateTime date, HistoryEventType? eventType);
|
||||
int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes);
|
||||
}
|
||||
|
||||
public class HistoryService : IHistoryService,
|
||||
|
@ -205,5 +206,10 @@ namespace NzbDrone.Core.History
|
|||
{
|
||||
_historyRepository.Purge(vacuum: true);
|
||||
}
|
||||
|
||||
public int CountSince(int indexerId, DateTime date, List<HistoryEventType> eventTypes)
|
||||
{
|
||||
return _historyRepository.CountSince(indexerId, date, eventTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
public List<ReleaseInfo> Releases;
|
||||
public List<ReleaseInfo> Releases { get; set; }
|
||||
|
||||
private static string RemoveInvalidXMLChars(string text)
|
||||
{
|
||||
|
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Events;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
@ -20,19 +19,19 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
|
||||
public class NzbSearchService : ISearchForNzb
|
||||
{
|
||||
private readonly IIndexerLimitService _indexerLimitService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly IDownloadMappingService _downloadMappingService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NzbSearchService(IEventAggregator eventAggregator,
|
||||
IIndexerFactory indexerFactory,
|
||||
IDownloadMappingService downloadMappingService,
|
||||
IIndexerLimitService indexerLimitService,
|
||||
Logger logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_indexerFactory = indexerFactory;
|
||||
_downloadMappingService = downloadMappingService;
|
||||
_indexerLimitService = indexerLimitService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -163,6 +162,11 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
|
||||
private async Task<IList<ReleaseInfo>> DispatchIndexer(Func<IIndexer, Task<IndexerPageableQueryResult>> searchAction, IIndexer indexer, SearchCriteriaBase criteriaBase)
|
||||
{
|
||||
if (_indexerLimitService.AtQueryLimit((IndexerDefinition)indexer.Definition))
|
||||
{
|
||||
return new List<ReleaseInfo>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var indexerReports = await searchAction(indexer);
|
||||
|
|
|
@ -499,6 +499,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Username", HelpText = "Site Username", Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -356,6 +356,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Site Password", Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -324,6 +324,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -37,6 +37,9 @@ namespace NzbDrone.Core.Indexers.Definitions.Avistaz
|
|||
[FieldDefinition(4, Label = "PID", HelpText = "PID from My Account or My Profile page")]
|
||||
public string Pid { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -414,6 +414,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(5, Label = "Append Season", Type = FieldType.Checkbox, HelpText = "Append Season for Sonarr Compatibility")]
|
||||
public bool AppendSeason { get; set; }
|
||||
|
||||
[FieldDefinition(6)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -256,6 +256,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "RSS Key", HelpText = "RSS Key from Site", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string RssKey { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -26,6 +26,9 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
|
|||
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -24,9 +24,12 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||
[FieldDefinition(0, Hidden = HiddenType.Hidden)]
|
||||
public string DefinitionFile { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
[FieldDefinition(2, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(1)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public Dictionary<string, object> ExtraFieldData { get; set; }
|
||||
|
||||
// Field 8 is used by TorznabSettings MinimumSeeders
|
||||
|
|
|
@ -247,6 +247,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from Site", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -333,6 +333,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Passphrase", HelpText = "Pass from login cookie")]
|
||||
public string Passphrase { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -31,6 +31,9 @@ namespace NzbDrone.Core.Indexers.FileList
|
|||
[FieldDefinition(3, Label = "Passkey", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -36,6 +36,9 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
|||
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "Use Freeleech Token", HelpText = "Use Freeleech Token")]
|
||||
public bool UseFreeleechToken { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -419,6 +419,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
|
||||
public bool SearchGroupNames { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -38,6 +38,9 @@ namespace NzbDrone.Core.Indexers.HDBits
|
|||
[FieldDefinition(5, Label = "Mediums", Type = FieldType.TagSelect, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "Options: BluRay, Encode, Capture, Remux, WebDL. If unspecified, all options are used.")]
|
||||
public IEnumerable<int> Mediums { get; set; }
|
||||
|
||||
[FieldDefinition(6)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -341,6 +341,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "Site Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -381,6 +381,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Site Password", Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -36,6 +36,9 @@ namespace NzbDrone.Core.Indexers.Headphones
|
|||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public virtual NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -370,6 +370,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "FreeLeech Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Search Freeleech torrents only")]
|
||||
public bool FreeLeechOnly { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -356,6 +356,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Site Password", Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -238,6 +238,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(2, Label = "Apikey", HelpText = "Site ApiKey", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -381,6 +381,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Type = FieldType.Checkbox, Label = "Exclude VIP", HelpText = "Exclude VIP Torrents from search results")]
|
||||
public bool ExcludeVip { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -297,6 +297,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(4, Label = "Two Factor Auth", HelpText = "Two-Factor Auth")]
|
||||
public string TwoFactorAuth { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -76,6 +76,9 @@ namespace NzbDrone.Core.Indexers.Newznab
|
|||
[FieldDefinition(6, Label = "VIP Expiration", HelpText = "Enter date (yyyy-mm-dd) for VIP Expiration or blank, Prowlarr will notify 1 week from expiration of VIP")]
|
||||
public string VipExpiration { get; set; }
|
||||
|
||||
[FieldDefinition(7)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public List<IndexerCategory> Categories { get; set; }
|
||||
|
||||
// Field 8 is used by TorznabSettings MinimumSeeders
|
||||
|
|
|
@ -30,6 +30,9 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
|
|||
[FieldDefinition(3, Label = "APIKey", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string APIKey { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -409,6 +409,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(4, Label = "Password", Privacy = PrivacyLevel.Password, Type = FieldType.Password, HelpText = "Site Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -29,6 +29,9 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
|||
[FieldDefinition(3, Type = FieldType.Captcha, Label = "CAPTCHA Token", HelpText = "CAPTCHA Clearance token used to handle CloudFlare Anti-DDOS measures on shared-ip VPNs.")]
|
||||
public string CaptchaToken { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -349,6 +349,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Site Password", Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -191,6 +191,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -424,6 +424,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(4, Label = "Api Key", Hidden = HiddenType.Hidden)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -248,6 +248,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -341,6 +341,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(2, Label = "Cookie", HelpText = "Site Cookie")]
|
||||
public string Cookie { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -318,6 +318,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password, HelpText = "Site Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -266,6 +266,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult();
|
||||
|
|
|
@ -287,6 +287,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(2, Label = "Cookie", HelpText = "Site Cookie")]
|
||||
public string Cookie { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -354,6 +354,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(4, Label = "FreeLeech Only", Type = FieldType.Checkbox, Advanced = true, HelpText = "Search Freeleech torrents only")]
|
||||
public bool FreeLeechOnly { get; set; }
|
||||
|
||||
[FieldDefinition(5)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -28,6 +28,9 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
|
|||
[FieldDefinition(3, Label = "Passkey", HelpText = "The password you use at your Indexer.", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Passkey { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -360,6 +360,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password, HelpText = "Site Password", Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -26,6 +26,9 @@ namespace NzbDrone.Core.Indexers.Definitions.UNIT3D
|
|||
[FieldDefinition(2, Label = "Api Key", HelpText = "Api key generated in My Security", Privacy = PrivacyLevel.ApiKey)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -296,6 +296,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(1, Label = "Base Url", Type = FieldType.Select, SelectOptionsProviderAction = "getUrls", HelpText = "Select which baseurl Prowlarr will use for requests to the site")]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -424,6 +424,9 @@ namespace NzbDrone.Core.Indexers.Definitions
|
|||
[FieldDefinition(3, Label = "Password", HelpText = "Site Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings();
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
@ -15,7 +15,6 @@ using NzbDrone.Core.Indexers.Exceptions;
|
|||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
|
@ -52,11 +51,11 @@ namespace NzbDrone.Core.Indexers
|
|||
|
||||
public override Task<IndexerPageableQueryResult> Fetch(MovieSearchCriteria searchCriteria)
|
||||
{
|
||||
//TODO: Re-Enable when All Indexer Caps are fixed and tests don't fail
|
||||
//if (!SupportsSearch)
|
||||
//{
|
||||
// return Task.FromResult(new Task<IndexerPageableQueryResult>());
|
||||
//}
|
||||
if (!SupportsSearch)
|
||||
{
|
||||
return Task.FromResult(new IndexerPageableQueryResult());
|
||||
}
|
||||
|
||||
return FetchReleases(g => SetCookieFunctions(g).GetSearchRequests(searchCriteria));
|
||||
}
|
||||
|
||||
|
@ -158,13 +157,6 @@ namespace NzbDrone.Core.Indexers
|
|||
|
||||
var pageableRequestChain = pageableRequestChainSelector(generator);
|
||||
|
||||
var fullyUpdated = false;
|
||||
ReleaseInfo lastReleaseInfo = null;
|
||||
if (isRecent)
|
||||
{
|
||||
lastReleaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id);
|
||||
}
|
||||
|
||||
for (int i = 0; i < pageableRequestChain.Tiers; i++)
|
||||
{
|
||||
var pageableRequests = pageableRequestChain.GetTier(i);
|
||||
|
@ -187,33 +179,6 @@ namespace NzbDrone.Core.Indexers
|
|||
|
||||
pagedReleases.AddRange(page.Releases);
|
||||
|
||||
if (isRecent && page.Releases.Any())
|
||||
{
|
||||
if (lastReleaseInfo == null)
|
||||
{
|
||||
fullyUpdated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var oldestReleaseDate = page.Releases.Select(v => v.PublishDate).Min();
|
||||
if (oldestReleaseDate < lastReleaseInfo.PublishDate || page.Releases.Any(v => v.DownloadUrl == lastReleaseInfo.DownloadUrl))
|
||||
{
|
||||
fullyUpdated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pagedReleases.Count >= MaxNumResultsPerQuery &&
|
||||
oldestReleaseDate < DateTime.UtcNow - TimeSpan.FromHours(24))
|
||||
{
|
||||
fullyUpdated = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (pagedReleases.Count >= MaxNumResultsPerQuery)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!IsFullPage(page.Releases, pageSize))
|
||||
{
|
||||
break;
|
||||
|
@ -229,21 +194,6 @@ namespace NzbDrone.Core.Indexers
|
|||
}
|
||||
}
|
||||
|
||||
if (isRecent && !releases.Empty())
|
||||
{
|
||||
var ordered = releases.OrderByDescending(v => v.PublishDate).ToList();
|
||||
|
||||
if (!fullyUpdated && lastReleaseInfo != null)
|
||||
{
|
||||
var gapStart = lastReleaseInfo.PublishDate;
|
||||
var gapEnd = ordered.Last().PublishDate;
|
||||
_logger.Warn("Indexer {0} rss sync didn't cover the period between {1} and {2} UTC. Search may be required.", Definition.Name, gapStart, gapEnd);
|
||||
}
|
||||
|
||||
lastReleaseInfo = ordered.First();
|
||||
_indexerStatusService.UpdateRssSyncStatus(Definition.Id, lastReleaseInfo);
|
||||
}
|
||||
|
||||
_indexerStatusService.RecordSuccess(Definition.Id);
|
||||
}
|
||||
catch (WebException webException)
|
||||
|
|
|
@ -5,5 +5,6 @@ namespace NzbDrone.Core.Indexers
|
|||
public interface IIndexerSettings : IProviderConfig
|
||||
{
|
||||
string BaseUrl { get; set; }
|
||||
IndexerBaseSettings BaseSettings { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
23
src/NzbDrone.Core/Indexers/IndexerBaseSettings.cs
Normal file
23
src/NzbDrone.Core/Indexers/IndexerBaseSettings.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public class IndexerCommonSettingsValidator : AbstractValidator<IndexerBaseSettings>
|
||||
{
|
||||
public IndexerCommonSettingsValidator()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class IndexerBaseSettings
|
||||
{
|
||||
private static readonly IndexerCommonSettingsValidator Validator = new IndexerCommonSettingsValidator();
|
||||
|
||||
[FieldDefinition(1, Type = FieldType.Number, Label = "Query Limit", HelpText = "The number of queries per day 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 per day Prowlarr will allow to the site", Advanced = true)]
|
||||
public int? GrabLimit { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public static class IndexerDefaults
|
||||
{
|
||||
public const int MINIMUM_SEEDERS = 1;
|
||||
}
|
||||
}
|
65
src/NzbDrone.Core/Indexers/IndexerLimitService.cs
Normal file
65
src/NzbDrone.Core/Indexers/IndexerLimitService.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public interface IIndexerLimitService
|
||||
{
|
||||
bool AtDownloadLimit(IndexerDefinition indexer);
|
||||
bool AtQueryLimit(IndexerDefinition indexer);
|
||||
}
|
||||
|
||||
public class IndexerLimitService : IIndexerLimitService
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public IndexerLimitService(IEventAggregator eventAggregator,
|
||||
IHistoryService historyService,
|
||||
Logger logger)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_historyService = historyService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public bool AtDownloadLimit(IndexerDefinition indexer)
|
||||
{
|
||||
if (indexer.Id > 0 && ((IIndexerSettings)indexer.Settings).BaseSettings.GrabLimit.HasValue)
|
||||
{
|
||||
var queryCount = _historyService.CountSince(indexer.Id, DateTime.Now.StartOfDay(), new List<HistoryEventType> { HistoryEventType.ReleaseGrabbed });
|
||||
|
||||
if (queryCount > ((IIndexerSettings)indexer.Settings).BaseSettings.GrabLimit)
|
||||
{
|
||||
_logger.Info("Indexer {0} has exceeded maximum grab limit for today", indexer.Name);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool AtQueryLimit(IndexerDefinition indexer)
|
||||
{
|
||||
if (indexer.Id > 0 && ((IIndexerSettings)indexer.Settings).BaseSettings.QueryLimit.HasValue)
|
||||
{
|
||||
var queryCount = _historyService.CountSince(indexer.Id, DateTime.Now.StartOfDay(), new List<HistoryEventType> { HistoryEventType.IndexerQuery, HistoryEventType.IndexerRss });
|
||||
|
||||
if (queryCount > ((IIndexerSettings)indexer.Settings).BaseSettings.QueryLimit)
|
||||
{
|
||||
_logger.Info("Indexer {0} has exceeded maximum query limit for today", indexer.Name);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,11 +3,13 @@ using System.Collections.Generic;
|
|||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
@ -24,16 +26,19 @@ namespace NzbDrone.Api.V1.Indexers
|
|||
{
|
||||
private IIndexerFactory _indexerFactory { get; set; }
|
||||
private ISearchForNzb _nzbSearchService { get; set; }
|
||||
private IIndexerLimitService _indexerLimitService { get; set; }
|
||||
private IDownloadMappingService _downloadMappingService { get; set; }
|
||||
private IDownloadService _downloadService { get; set; }
|
||||
|
||||
public NewznabController(IndexerFactory indexerFactory,
|
||||
ISearchForNzb nzbSearchService,
|
||||
IIndexerLimitService indexerLimitService,
|
||||
IDownloadMappingService downloadMappingService,
|
||||
IDownloadService downloadService)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
_nzbSearchService = nzbSearchService;
|
||||
_indexerLimitService = indexerLimitService;
|
||||
_downloadMappingService = downloadMappingService;
|
||||
_downloadService = downloadService;
|
||||
}
|
||||
|
@ -49,7 +54,7 @@ namespace NzbDrone.Api.V1.Indexers
|
|||
|
||||
if (requestType.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new BadRequestException("Missing Function Parameter");
|
||||
return Content(CreateErrorXML(200, "Missing parameter (t)"), "application/rss+xml");
|
||||
}
|
||||
|
||||
request.imdbid = request.imdbid?.TrimStart('t') ?? null;
|
||||
|
@ -58,7 +63,7 @@ namespace NzbDrone.Api.V1.Indexers
|
|||
{
|
||||
if (!int.TryParse(request.imdbid, out var imdb) || imdb == 0)
|
||||
{
|
||||
throw new BadRequestException("Invalid Value for ImdbId");
|
||||
return Content(CreateErrorXML(201, "Incorrect parameter (imdbid)"), "application/rss+xml");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,40 +100,46 @@ namespace NzbDrone.Api.V1.Indexers
|
|||
}
|
||||
}
|
||||
|
||||
var indexer = _indexerFactory.Get(id);
|
||||
var indexerDef = _indexerFactory.Get(id);
|
||||
|
||||
if (indexer == null)
|
||||
if (indexerDef == null)
|
||||
{
|
||||
throw new NotFoundException("Indexer Not Found");
|
||||
}
|
||||
|
||||
var indexerInstance = _indexerFactory.GetInstance(indexer);
|
||||
var indexer = _indexerFactory.GetInstance(indexerDef);
|
||||
|
||||
//TODO Optimize this so it's not called here and in NzbSearchService (for manual search)
|
||||
if (_indexerLimitService.AtQueryLimit(indexerDef))
|
||||
{
|
||||
return Content(CreateErrorXML(500, $"Request limit reached ({((IIndexerSettings)indexer.Definition.Settings).BaseSettings.QueryLimit})"), "application/rss+xml");
|
||||
}
|
||||
|
||||
switch (requestType)
|
||||
{
|
||||
case "caps":
|
||||
var caps = indexerInstance.GetCapabilities();
|
||||
var caps = indexer.GetCapabilities();
|
||||
return Content(caps.ToXml(), "application/rss+xml");
|
||||
case "search":
|
||||
case "tvsearch":
|
||||
case "music":
|
||||
case "book":
|
||||
case "movie":
|
||||
var results = await _nzbSearchService.Search(request, new List<int> { indexer.Id }, false);
|
||||
var results = await _nzbSearchService.Search(request, new List<int> { indexerDef.Id }, false);
|
||||
|
||||
foreach (var result in results.Releases)
|
||||
{
|
||||
result.DownloadUrl = result.DownloadUrl != null ? _downloadMappingService.ConvertToProxyLink(new Uri(result.DownloadUrl), request.server, indexer.Id, result.Title).ToString() : null;
|
||||
result.DownloadUrl = result.DownloadUrl != null ? _downloadMappingService.ConvertToProxyLink(new Uri(result.DownloadUrl), request.server, indexerDef.Id, result.Title).ToString() : null;
|
||||
|
||||
if (result.DownloadProtocol == DownloadProtocol.Torrent)
|
||||
{
|
||||
((TorrentInfo)result).MagnetUrl = ((TorrentInfo)result).MagnetUrl != null ? _downloadMappingService.ConvertToProxyLink(new Uri(((TorrentInfo)result).MagnetUrl), request.server, indexer.Id, result.Title).ToString() : null;
|
||||
((TorrentInfo)result).MagnetUrl = ((TorrentInfo)result).MagnetUrl != null ? _downloadMappingService.ConvertToProxyLink(new Uri(((TorrentInfo)result).MagnetUrl), request.server, indexerDef.Id, result.Title).ToString() : null;
|
||||
}
|
||||
}
|
||||
|
||||
return Content(results.ToXml(indexerInstance.Protocol), "application/rss+xml");
|
||||
return Content(results.ToXml(indexer.Protocol), "application/rss+xml");
|
||||
default:
|
||||
throw new BadRequestException("Function Not Available");
|
||||
return Content(CreateErrorXML(202, $"No such function ({requestType})"), "application/rss+xml");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +150,11 @@ namespace NzbDrone.Api.V1.Indexers
|
|||
var indexerDef = _indexerFactory.Get(id);
|
||||
var indexer = _indexerFactory.GetInstance(indexerDef);
|
||||
|
||||
if (_indexerLimitService.AtDownloadLimit(indexerDef))
|
||||
{
|
||||
throw new BadRequestException("Grab limit reached");
|
||||
}
|
||||
|
||||
if (link.IsNullOrWhiteSpace() || file.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new BadRequestException("Invalid Prowlarr link");
|
||||
|
@ -146,7 +162,7 @@ namespace NzbDrone.Api.V1.Indexers
|
|||
|
||||
file = WebUtility.UrlDecode(file);
|
||||
|
||||
if (indexer == null)
|
||||
if (indexerDef == null)
|
||||
{
|
||||
throw new NotFoundException("Indexer Not Found");
|
||||
}
|
||||
|
@ -186,5 +202,16 @@ namespace NzbDrone.Api.V1.Indexers
|
|||
|
||||
return File(downloadBytes, contentType, filename);
|
||||
}
|
||||
|
||||
public static string CreateErrorXML(int code, string description)
|
||||
{
|
||||
var xdoc = new XDocument(
|
||||
new XDeclaration("1.0", "UTF-8", null),
|
||||
new XElement("error",
|
||||
new XAttribute("code", code.ToString()),
|
||||
new XAttribute("description", description)));
|
||||
|
||||
return xdoc.Declaration + Environment.NewLine + xdoc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue