mirror of
https://github.com/Sonarr/Sonarr
synced 2025-12-06 16:32:24 +01:00
New: Option to treat downloads with non-media extensions as failed
Closes #7369
This commit is contained in:
parent
8c67a3bdee
commit
776143cc81
25 changed files with 229 additions and 51 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Indexers.Torznab;
|
using NzbDrone.Core.Indexers.Torznab;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
@ -16,9 +16,9 @@ public class SeedConfigProviderFixture : CoreTest<SeedConfigProvider>
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_return_config_for_non_existent_indexer()
|
public void should_not_return_config_for_non_existent_indexer()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IIndexerFactory>()
|
Mocker.GetMock<ICachedIndexerSettingsProvider>()
|
||||||
.Setup(v => v.Get(It.IsAny<int>()))
|
.Setup(v => v.GetSettings(It.IsAny<int>()))
|
||||||
.Throws(new ModelNotFoundException(typeof(IndexerDefinition), 0));
|
.Returns<CachedIndexerSettings>(null);
|
||||||
|
|
||||||
var result = Subject.GetSeedConfiguration(new RemoteEpisode
|
var result = Subject.GetSeedConfiguration(new RemoteEpisode
|
||||||
{
|
{
|
||||||
|
|
@ -38,11 +38,12 @@ public void should_return_season_time_for_season_packs()
|
||||||
var settings = new TorznabSettings();
|
var settings = new TorznabSettings();
|
||||||
settings.SeedCriteria.SeasonPackSeedTime = 10;
|
settings.SeedCriteria.SeasonPackSeedTime = 10;
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerFactory>()
|
Mocker.GetMock<ICachedIndexerSettingsProvider>()
|
||||||
.Setup(v => v.Get(It.IsAny<int>()))
|
.Setup(v => v.GetSettings(It.IsAny<int>()))
|
||||||
.Returns(new IndexerDefinition
|
.Returns(new CachedIndexerSettings
|
||||||
{
|
{
|
||||||
Settings = settings
|
FailDownloads = new HashSet<FailDownloads> { FailDownloads.Executables },
|
||||||
|
SeedCriteriaSettings = settings.SeedCriteria
|
||||||
});
|
});
|
||||||
|
|
||||||
var result = Subject.GetSeedConfiguration(new RemoteEpisode
|
var result = Subject.GetSeedConfiguration(new RemoteEpisode
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@ public NzbDroneValidationResult Validate()
|
||||||
public string BaseUrl { get; set; }
|
public string BaseUrl { get; set; }
|
||||||
|
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public class CompletedDownloadService : ICompletedDownloadService
|
||||||
private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
|
private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
|
||||||
private readonly IEpisodeService _episodeService;
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly IMediaFileService _mediaFileService;
|
private readonly IMediaFileService _mediaFileService;
|
||||||
|
private readonly IRejectedImportService _rejectedImportService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public CompletedDownloadService(IEventAggregator eventAggregator,
|
public CompletedDownloadService(IEventAggregator eventAggregator,
|
||||||
|
|
@ -46,6 +47,7 @@ public CompletedDownloadService(IEventAggregator eventAggregator,
|
||||||
ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
|
ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
|
||||||
IEpisodeService episodeService,
|
IEpisodeService episodeService,
|
||||||
IMediaFileService mediaFileService,
|
IMediaFileService mediaFileService,
|
||||||
|
IRejectedImportService rejectedImportService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
|
|
@ -57,6 +59,7 @@ public CompletedDownloadService(IEventAggregator eventAggregator,
|
||||||
_trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
|
_trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
|
||||||
_episodeService = episodeService;
|
_episodeService = episodeService;
|
||||||
_mediaFileService = mediaFileService;
|
_mediaFileService = mediaFileService;
|
||||||
|
_rejectedImportService = rejectedImportService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,10 +168,8 @@ public void Import(TrackedDownload trackedDownload)
|
||||||
{
|
{
|
||||||
var firstResult = importResults.First();
|
var firstResult = importResults.First();
|
||||||
|
|
||||||
if (firstResult.Result == ImportResultType.Rejected && firstResult.ImportDecision.LocalEpisode == null)
|
if (_rejectedImportService.Process(trackedDownload, firstResult))
|
||||||
{
|
{
|
||||||
trackedDownload.Warn(new TrackedDownloadStatusMessage(firstResult.Errors.First(), new List<string>()));
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,18 @@ public void Execute(ProcessMonitoredDownloadsCommand message)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Process completed items followed by failed, this allows failed imports to have
|
||||||
|
// their state changed and be processed immediately instead of the next execution.
|
||||||
|
|
||||||
|
if (enableCompletedDownloadHandling && trackedDownload.State == TrackedDownloadState.ImportPending)
|
||||||
|
{
|
||||||
|
_completedDownloadService.Import(trackedDownload);
|
||||||
|
}
|
||||||
|
|
||||||
if (trackedDownload.State == TrackedDownloadState.FailedPending)
|
if (trackedDownload.State == TrackedDownloadState.FailedPending)
|
||||||
{
|
{
|
||||||
_failedDownloadService.ProcessFailed(trackedDownload);
|
_failedDownloadService.ProcessFailed(trackedDownload);
|
||||||
}
|
}
|
||||||
else if (enableCompletedDownloadHandling && trackedDownload.State == TrackedDownloadState.ImportPending)
|
|
||||||
{
|
|
||||||
_completedDownloadService.Import(trackedDownload);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
50
src/NzbDrone.Core/Download/RejectedImportService.cs
Normal file
50
src/NzbDrone.Core/Download/RejectedImportService.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download;
|
||||||
|
|
||||||
|
public interface IRejectedImportService
|
||||||
|
{
|
||||||
|
bool Process(TrackedDownload trackedDownload, ImportResult importResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RejectedImportService : IRejectedImportService
|
||||||
|
{
|
||||||
|
private readonly ICachedIndexerSettingsProvider _cachedIndexerSettingsProvider;
|
||||||
|
|
||||||
|
public RejectedImportService(ICachedIndexerSettingsProvider cachedIndexerSettingsProvider)
|
||||||
|
{
|
||||||
|
_cachedIndexerSettingsProvider = cachedIndexerSettingsProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Process(TrackedDownload trackedDownload, ImportResult importResult)
|
||||||
|
{
|
||||||
|
if (importResult.Result != ImportResultType.Rejected || importResult.ImportDecision.LocalEpisode != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexerSettings = _cachedIndexerSettingsProvider.GetSettings(trackedDownload.RemoteEpisode.Release.IndexerId);
|
||||||
|
var rejectionReason = importResult.ImportDecision.Rejections.FirstOrDefault()?.Reason;
|
||||||
|
|
||||||
|
if (rejectionReason == ImportRejectionReason.DangerousFile &&
|
||||||
|
indexerSettings.FailDownloads.Contains(FailDownloads.PotentiallyDangerous))
|
||||||
|
{
|
||||||
|
trackedDownload.Fail();
|
||||||
|
}
|
||||||
|
else if (rejectionReason == ImportRejectionReason.ExecutableFile &&
|
||||||
|
indexerSettings.FailDownloads.Contains(FailDownloads.Executables))
|
||||||
|
{
|
||||||
|
trackedDownload.Fail();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trackedDownload.Warn(new TrackedDownloadStatusMessage(importResult.Errors.First(), new List<string>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,12 @@ public void Warn(params TrackedDownloadStatusMessage[] statusMessages)
|
||||||
Status = TrackedDownloadStatus.Warning;
|
Status = TrackedDownloadStatus.Warning;
|
||||||
StatusMessages = statusMessages;
|
StatusMessages = statusMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Fail()
|
||||||
|
{
|
||||||
|
Status = TrackedDownloadStatus.Error;
|
||||||
|
State = TrackedDownloadState.FailedPending;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TrackedDownloadState
|
public enum TrackedDownloadState
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,11 @@ public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, Do
|
||||||
{
|
{
|
||||||
trackedDownload.RemoteEpisode.Release.IndexerFlags = flags;
|
trackedDownload.RemoteEpisode.Release.IndexerFlags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (downloadHistory != null)
|
||||||
|
{
|
||||||
|
trackedDownload.RemoteEpisode.Release.IndexerId = downloadHistory.IndexerId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ public BroadcastheNetSettings()
|
||||||
BaseUrl = "https://api.broadcasthe.net/";
|
BaseUrl = "https://api.broadcasthe.net/";
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")]
|
[FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")]
|
||||||
|
|
@ -48,6 +49,9 @@ public BroadcastheNetSettings()
|
||||||
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|
|
||||||
69
src/NzbDrone.Core/Indexers/CachedIndexerSettingsProvider.cs
Normal file
69
src/NzbDrone.Core/Indexers/CachedIndexerSettingsProvider.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.ThingiProvider.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Indexers;
|
||||||
|
|
||||||
|
public interface ICachedIndexerSettingsProvider
|
||||||
|
{
|
||||||
|
CachedIndexerSettings GetSettings(int indexerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CachedIndexerSettingsProvider : ICachedIndexerSettingsProvider, IHandle<ProviderUpdatedEvent<IIndexer>>
|
||||||
|
{
|
||||||
|
private readonly IIndexerFactory _indexerFactory;
|
||||||
|
private readonly ICached<CachedIndexerSettings> _cache;
|
||||||
|
|
||||||
|
public CachedIndexerSettingsProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager)
|
||||||
|
{
|
||||||
|
_indexerFactory = indexerFactory;
|
||||||
|
_cache = cacheManager.GetRollingCache<CachedIndexerSettings>(GetType(), "settingsByIndexer", TimeSpan.FromHours(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CachedIndexerSettings GetSettings(int indexerId)
|
||||||
|
{
|
||||||
|
if (indexerId == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cache.Get(indexerId.ToString(), () => FetchIndexerSettings(indexerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CachedIndexerSettings FetchIndexerSettings(int indexerId)
|
||||||
|
{
|
||||||
|
var indexer = _indexerFactory.Get(indexerId);
|
||||||
|
var indexerSettings = indexer.Settings as IIndexerSettings;
|
||||||
|
|
||||||
|
if (indexerSettings == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = new CachedIndexerSettings
|
||||||
|
{
|
||||||
|
FailDownloads = indexerSettings.FailDownloads.Select(f => (FailDownloads)f).ToHashSet()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (indexer.Settings is ITorrentIndexerSettings torrentIndexerSettings)
|
||||||
|
{
|
||||||
|
settings.SeedCriteriaSettings = torrentIndexerSettings.SeedCriteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ProviderUpdatedEvent<IIndexer> message)
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CachedIndexerSettings
|
||||||
|
{
|
||||||
|
public HashSet<FailDownloads> FailDownloads { get; set; }
|
||||||
|
public SeedCriteriaSettings SeedCriteriaSettings { get; set; }
|
||||||
|
}
|
||||||
12
src/NzbDrone.Core/Indexers/FailDownloads.cs
Normal file
12
src/NzbDrone.Core/Indexers/FailDownloads.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Indexers;
|
||||||
|
|
||||||
|
public enum FailDownloads
|
||||||
|
{
|
||||||
|
[FieldOption(Label = "Executables")]
|
||||||
|
Executables = 0,
|
||||||
|
|
||||||
|
[FieldOption(Label = "Potentially Dangerous")]
|
||||||
|
PotentiallyDangerous = 1
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ public FanzubSettings()
|
||||||
{
|
{
|
||||||
BaseUrl = "http://fanzub.com/rss/";
|
BaseUrl = "http://fanzub.com/rss/";
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "IndexerSettingsRssUrl", HelpText = "IndexerSettingsRssUrlHelpText")]
|
[FieldDefinition(0, Label = "IndexerSettingsRssUrl", HelpText = "IndexerSettingsRssUrlHelpText")]
|
||||||
|
|
@ -36,6 +37,9 @@ public FanzubSettings()
|
||||||
[FieldDefinition(2, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
[FieldDefinition(2, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ public FileListSettings()
|
||||||
|
|
||||||
AnimeCategories = Array.Empty<int>();
|
AnimeCategories = Array.Empty<int>();
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
|
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
|
||||||
|
|
@ -67,6 +68,9 @@ public FileListSettings()
|
||||||
[FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
[FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
||||||
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ public HDBitsSettings()
|
||||||
Codecs = Array.Empty<int>();
|
Codecs = Array.Empty<int>();
|
||||||
Mediums = Array.Empty<int>();
|
Mediums = Array.Empty<int>();
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")]
|
[FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")]
|
||||||
|
|
@ -64,6 +65,9 @@ public HDBitsSettings()
|
||||||
[FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
[FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(10, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,7 @@ public interface IIndexerSettings : IProviderConfig
|
||||||
string BaseUrl { get; set; }
|
string BaseUrl { get; set; }
|
||||||
|
|
||||||
IEnumerable<int> MultiLanguages { get; set; }
|
IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
IEnumerable<int> FailDownloads { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public IPTorrentsSettings()
|
||||||
{
|
{
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "IndexerIPTorrentsSettingsFeedUrl", HelpText = "IndexerIPTorrentsSettingsFeedUrlHelpText")]
|
[FieldDefinition(0, Label = "IndexerIPTorrentsSettingsFeedUrl", HelpText = "IndexerIPTorrentsSettingsFeedUrlHelpText")]
|
||||||
|
|
@ -51,6 +52,9 @@ public IPTorrentsSettings()
|
||||||
[FieldDefinition(4, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
[FieldDefinition(4, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ public NewznabSettings()
|
||||||
Categories = new[] { 5030, 5040 };
|
Categories = new[] { 5030, 5040 };
|
||||||
AnimeCategories = Enumerable.Empty<int>();
|
AnimeCategories = Enumerable.Empty<int>();
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "URL")]
|
[FieldDefinition(0, Label = "URL")]
|
||||||
|
|
@ -86,6 +87,9 @@ public NewznabSettings()
|
||||||
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
// Field 8 is used by TorznabSettings MinimumSeeders
|
// Field 8 is used by TorznabSettings MinimumSeeders
|
||||||
// If you need to add another field here, update TorznabSettings as well and this comment
|
// If you need to add another field here, update TorznabSettings as well and this comment
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ public NyaaSettings()
|
||||||
AdditionalParameters = "&cats=1_0&filter=1";
|
AdditionalParameters = "&cats=1_0&filter=1";
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")]
|
[FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")]
|
||||||
|
|
@ -53,6 +54,9 @@ public NyaaSettings()
|
||||||
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using NzbDrone.Common.Cache;
|
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Download.Clients;
|
using NzbDrone.Core.Download.Clients;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.ThingiProvider.Events;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers
|
namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
|
|
@ -14,15 +10,13 @@ public interface ISeedConfigProvider
|
||||||
TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason);
|
TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeedConfigProvider : ISeedConfigProvider, IHandle<ProviderUpdatedEvent<IIndexer>>
|
public class SeedConfigProvider : ISeedConfigProvider
|
||||||
{
|
{
|
||||||
private readonly IIndexerFactory _indexerFactory;
|
private readonly ICachedIndexerSettingsProvider _cachedIndexerSettingsProvider;
|
||||||
private readonly ICached<SeedCriteriaSettings> _cache;
|
|
||||||
|
|
||||||
public SeedConfigProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager)
|
public SeedConfigProvider(ICachedIndexerSettingsProvider cachedIndexerSettingsProvider)
|
||||||
{
|
{
|
||||||
_indexerFactory = indexerFactory;
|
_cachedIndexerSettingsProvider = cachedIndexerSettingsProvider;
|
||||||
_cache = cacheManager.GetRollingCache<SeedCriteriaSettings>(GetType(), "criteriaByIndexer", TimeSpan.FromHours(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TorrentSeedConfiguration GetSeedConfiguration(RemoteEpisode remoteEpisode)
|
public TorrentSeedConfiguration GetSeedConfiguration(RemoteEpisode remoteEpisode)
|
||||||
|
|
@ -47,7 +41,8 @@ public TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSea
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var seedCriteria = _cache.Get(indexerId.ToString(), () => FetchSeedCriteria(indexerId));
|
var settings = _cachedIndexerSettingsProvider.GetSettings(indexerId);
|
||||||
|
var seedCriteria = settings?.SeedCriteriaSettings;
|
||||||
|
|
||||||
if (seedCriteria == null)
|
if (seedCriteria == null)
|
||||||
{
|
{
|
||||||
|
|
@ -67,25 +62,5 @@ public TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSea
|
||||||
|
|
||||||
return seedConfig;
|
return seedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SeedCriteriaSettings FetchSeedCriteria(int indexerId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var indexer = _indexerFactory.Get(indexerId);
|
|
||||||
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
|
||||||
|
|
||||||
return torrentIndexerSettings?.SeedCriteria;
|
|
||||||
}
|
|
||||||
catch (ModelNotFoundException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Handle(ProviderUpdatedEvent<IIndexer> message)
|
|
||||||
{
|
|
||||||
_cache.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ public TorrentRssIndexerSettings()
|
||||||
AllowZeroSize = false;
|
AllowZeroSize = false;
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "IndexerSettingsRssUrl")]
|
[FieldDefinition(0, Label = "IndexerSettingsRssUrl")]
|
||||||
|
|
@ -51,6 +52,9 @@ public TorrentRssIndexerSettings()
|
||||||
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ public TorrentleechSettings()
|
||||||
BaseUrl = "http://rss.torrentleech.org";
|
BaseUrl = "http://rss.torrentleech.org";
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||||
MultiLanguages = Array.Empty<int>();
|
MultiLanguages = Array.Empty<int>();
|
||||||
|
FailDownloads = Array.Empty<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")]
|
[FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")]
|
||||||
|
|
@ -48,6 +49,9 @@ public TorrentleechSettings()
|
||||||
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
|
||||||
public IEnumerable<int> MultiLanguages { get; set; }
|
public IEnumerable<int> MultiLanguages { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
|
||||||
|
public IEnumerable<int> FailDownloads { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
|
|
||||||
|
|
@ -52,13 +52,13 @@ public TorznabSettings()
|
||||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(8, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)]
|
[FieldDefinition(9, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)]
|
||||||
public int MinimumSeeders { get; set; }
|
public int MinimumSeeders { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(9)]
|
[FieldDefinition(10)]
|
||||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
|
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
|
||||||
|
|
||||||
[FieldDefinition(10, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
[FieldDefinition(11, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
|
||||||
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
|
||||||
|
|
||||||
public override NzbDroneValidationResult Validate()
|
public override NzbDroneValidationResult Validate()
|
||||||
|
|
|
||||||
|
|
@ -998,6 +998,8 @@
|
||||||
"IndexerSettingsCategoriesHelpText": "Drop down list, leave blank to disable standard/daily shows",
|
"IndexerSettingsCategoriesHelpText": "Drop down list, leave blank to disable standard/daily shows",
|
||||||
"IndexerSettingsCookie": "Cookie",
|
"IndexerSettingsCookie": "Cookie",
|
||||||
"IndexerSettingsCookieHelpText": "If your site requires a login cookie to access the rss, you'll have to retrieve it via a browser.",
|
"IndexerSettingsCookieHelpText": "If your site requires a login cookie to access the rss, you'll have to retrieve it via a browser.",
|
||||||
|
"IndexerSettingsFailDownloads": "Fail Downloads",
|
||||||
|
"IndexerSettingsFailDownloadsHelpText": "While processing completed downloads {appName} will treat selected errors preventing importing as failed downloads.",
|
||||||
"IndexerSettingsMinimumSeeders": "Minimum Seeders",
|
"IndexerSettingsMinimumSeeders": "Minimum Seeders",
|
||||||
"IndexerSettingsMinimumSeedersHelpText": "Minimum number of seeders required.",
|
"IndexerSettingsMinimumSeedersHelpText": "Minimum number of seeders required.",
|
||||||
"IndexerSettingsMultiLanguageRelease": "Multi Languages",
|
"IndexerSettingsMultiLanguageRelease": "Multi Languages",
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,11 @@ private ImportResult CheckEmptyResultForIssue(string folder)
|
||||||
{
|
{
|
||||||
var files = _diskProvider.GetFiles(folder, true);
|
var files = _diskProvider.GetFiles(folder, true);
|
||||||
|
|
||||||
|
if (files.Any(file => FileExtensions.DangerousExtensions.Contains(Path.GetExtension(file))))
|
||||||
|
{
|
||||||
|
return RejectionResult(ImportRejectionReason.DangerousFile, "Caution: Found potentially dangerous file");
|
||||||
|
}
|
||||||
|
|
||||||
if (files.Any(file => FileExtensions.ExecutableExtensions.Contains(Path.GetExtension(file))))
|
if (files.Any(file => FileExtensions.ExecutableExtensions.Contains(Path.GetExtension(file))))
|
||||||
{
|
{
|
||||||
return RejectionResult(ImportRejectionReason.ExecutableFile, "Caution: Found executable file");
|
return RejectionResult(ImportRejectionReason.ExecutableFile, "Caution: Found executable file");
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ public enum ImportRejectionReason
|
||||||
Unknown,
|
Unknown,
|
||||||
FileLocked,
|
FileLocked,
|
||||||
UnknownSeries,
|
UnknownSeries,
|
||||||
|
DangerousFile,
|
||||||
ExecutableFile,
|
ExecutableFile,
|
||||||
ArchiveFile,
|
ArchiveFile,
|
||||||
SeriesFolder,
|
SeriesFolder,
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,27 @@ internal static class FileExtensions
|
||||||
".tb2",
|
".tb2",
|
||||||
".tbz2",
|
".tbz2",
|
||||||
".tgz",
|
".tgz",
|
||||||
".zip",
|
".zip"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static List<string> _dangerousExtensions = new List<string>
|
||||||
|
{
|
||||||
|
".lnk",
|
||||||
|
".ps1",
|
||||||
|
".vbs",
|
||||||
".zipx"
|
".zipx"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static List<string> _executableExtensions = new List<string>
|
private static List<string> _executableExtensions = new List<string>
|
||||||
{
|
{
|
||||||
".exe",
|
|
||||||
".bat",
|
".bat",
|
||||||
".cmd",
|
".cmd",
|
||||||
|
".exe",
|
||||||
".sh"
|
".sh"
|
||||||
};
|
};
|
||||||
|
|
||||||
public static HashSet<string> ArchiveExtensions => new HashSet<string>(_archiveExtensions, StringComparer.OrdinalIgnoreCase);
|
public static HashSet<string> ArchiveExtensions => new HashSet<string>(_archiveExtensions, StringComparer.OrdinalIgnoreCase);
|
||||||
|
public static HashSet<string> DangerousExtensions => new HashSet<string>(_dangerousExtensions, StringComparer.OrdinalIgnoreCase);
|
||||||
public static HashSet<string> ExecutableExtensions => new HashSet<string>(_executableExtensions, StringComparer.OrdinalIgnoreCase);
|
public static HashSet<string> ExecutableExtensions => new HashSet<string>(_executableExtensions, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue