From 776143cc813ec1b5fa31fbf8667c3ab174b71f5c Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 13 Nov 2024 20:13:00 -0800 Subject: [PATCH] New: Option to treat downloads with non-media extensions as failed Closes #7369 --- .../IndexerTests/SeedConfigProviderFixture.cs | 17 ++--- .../IndexerTests/TestIndexerSettings.cs | 1 + .../Download/CompletedDownloadService.cs | 7 +- .../Download/DownloadProcessingService.cs | 12 ++-- .../Download/RejectedImportService.cs | 50 ++++++++++++++ .../TrackedDownloads/TrackedDownload.cs | 6 ++ .../TrackedDownloadService.cs | 5 ++ .../BroadcastheNet/BroadcastheNetSettings.cs | 4 ++ .../Indexers/CachedIndexerSettingsProvider.cs | 69 +++++++++++++++++++ src/NzbDrone.Core/Indexers/FailDownloads.cs | 12 ++++ .../Indexers/Fanzub/FanzubSettings.cs | 4 ++ .../Indexers/FileList/FileListSettings.cs | 4 ++ .../Indexers/HDBits/HDBitsSettings.cs | 4 ++ .../Indexers/IIndexerSettings.cs | 2 + .../Indexers/IPTorrents/IPTorrentsSettings.cs | 4 ++ .../Indexers/Newznab/NewznabSettings.cs | 4 ++ .../Indexers/Nyaa/NyaaSettings.cs | 4 ++ .../Indexers/SeedConfigProvider.cs | 37 ++-------- .../TorrentRss/TorrentRssIndexerSettings.cs | 4 ++ .../Torrentleech/TorrentleechSettings.cs | 4 ++ .../Indexers/Torznab/TorznabSettings.cs | 6 +- src/NzbDrone.Core/Localization/Core/en.json | 2 + .../DownloadedEpisodesImportService.cs | 5 ++ .../EpisodeImport/ImportRejectionReason.cs | 1 + .../MediaFiles/FileExtensions.cs | 12 +++- 25 files changed, 229 insertions(+), 51 deletions(-) create mode 100644 src/NzbDrone.Core/Download/RejectedImportService.cs create mode 100644 src/NzbDrone.Core/Indexers/CachedIndexerSettingsProvider.cs create mode 100644 src/NzbDrone.Core/Indexers/FailDownloads.cs diff --git a/src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs index 9a29e5193..f01d50d55 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using FluentAssertions; using Moq; using NUnit.Framework; -using NzbDrone.Core.Datastore; using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers.Torznab; using NzbDrone.Core.Parser.Model; @@ -16,9 +16,9 @@ namespace NzbDrone.Core.Test.IndexerTests [Test] public void should_not_return_config_for_non_existent_indexer() { - Mocker.GetMock() - .Setup(v => v.Get(It.IsAny())) - .Throws(new ModelNotFoundException(typeof(IndexerDefinition), 0)); + Mocker.GetMock() + .Setup(v => v.GetSettings(It.IsAny())) + .Returns(null); var result = Subject.GetSeedConfiguration(new RemoteEpisode { @@ -38,11 +38,12 @@ namespace NzbDrone.Core.Test.IndexerTests var settings = new TorznabSettings(); settings.SeedCriteria.SeasonPackSeedTime = 10; - Mocker.GetMock() - .Setup(v => v.Get(It.IsAny())) - .Returns(new IndexerDefinition + Mocker.GetMock() + .Setup(v => v.GetSettings(It.IsAny())) + .Returns(new CachedIndexerSettings { - Settings = settings + FailDownloads = new HashSet { FailDownloads.Executables }, + SeedCriteriaSettings = settings.SeedCriteria }); var result = Subject.GetSeedConfiguration(new RemoteEpisode diff --git a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs index 948867108..706e87c16 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs @@ -15,5 +15,6 @@ namespace NzbDrone.Core.Test.IndexerTests public string BaseUrl { get; set; } public IEnumerable MultiLanguages { get; set; } + public IEnumerable FailDownloads { get; set; } } } diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index a71c6b9cf..ffb7b60be 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -35,6 +35,7 @@ namespace NzbDrone.Core.Download private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported; private readonly IEpisodeService _episodeService; private readonly IMediaFileService _mediaFileService; + private readonly IRejectedImportService _rejectedImportService; private readonly Logger _logger; public CompletedDownloadService(IEventAggregator eventAggregator, @@ -46,6 +47,7 @@ namespace NzbDrone.Core.Download ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported, IEpisodeService episodeService, IMediaFileService mediaFileService, + IRejectedImportService rejectedImportService, Logger logger) { _eventAggregator = eventAggregator; @@ -57,6 +59,7 @@ namespace NzbDrone.Core.Download _trackedDownloadAlreadyImported = trackedDownloadAlreadyImported; _episodeService = episodeService; _mediaFileService = mediaFileService; + _rejectedImportService = rejectedImportService; _logger = logger; } @@ -165,10 +168,8 @@ namespace NzbDrone.Core.Download { 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())); - return; } } diff --git a/src/NzbDrone.Core/Download/DownloadProcessingService.cs b/src/NzbDrone.Core/Download/DownloadProcessingService.cs index 271d1b54a..cbc57c48b 100644 --- a/src/NzbDrone.Core/Download/DownloadProcessingService.cs +++ b/src/NzbDrone.Core/Download/DownloadProcessingService.cs @@ -55,14 +55,18 @@ namespace NzbDrone.Core.Download { 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) { _failedDownloadService.ProcessFailed(trackedDownload); } - else if (enableCompletedDownloadHandling && trackedDownload.State == TrackedDownloadState.ImportPending) - { - _completedDownloadService.Import(trackedDownload); - } } catch (Exception e) { diff --git a/src/NzbDrone.Core/Download/RejectedImportService.cs b/src/NzbDrone.Core/Download/RejectedImportService.cs new file mode 100644 index 000000000..2cbb8f523 --- /dev/null +++ b/src/NzbDrone.Core/Download/RejectedImportService.cs @@ -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())); + } + + return true; + } +} diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs index 0a982e7ff..3f0543b03 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs @@ -35,6 +35,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads Status = TrackedDownloadStatus.Warning; StatusMessages = statusMessages; } + + public void Fail() + { + Status = TrackedDownloadStatus.Error; + State = TrackedDownloadState.FailedPending; + } } public enum TrackedDownloadState diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs index bf5eda8f5..cc54dc1cb 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -166,6 +166,11 @@ namespace NzbDrone.Core.Download.TrackedDownloads { trackedDownload.RemoteEpisode.Release.IndexerFlags = flags; } + + if (downloadHistory != null) + { + trackedDownload.RemoteEpisode.Release.IndexerId = downloadHistory.IndexerId; + } } } diff --git a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs index af0169502..6c5d2c473 100644 --- a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs +++ b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs @@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet BaseUrl = "https://api.broadcasthe.net/"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] @@ -48,6 +49,9 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet [FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/CachedIndexerSettingsProvider.cs b/src/NzbDrone.Core/Indexers/CachedIndexerSettingsProvider.cs new file mode 100644 index 000000000..f5cb3064c --- /dev/null +++ b/src/NzbDrone.Core/Indexers/CachedIndexerSettingsProvider.cs @@ -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> +{ + private readonly IIndexerFactory _indexerFactory; + private readonly ICached _cache; + + public CachedIndexerSettingsProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager) + { + _indexerFactory = indexerFactory; + _cache = cacheManager.GetRollingCache(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 message) + { + _cache.Clear(); + } +} + +public class CachedIndexerSettings +{ + public HashSet FailDownloads { get; set; } + public SeedCriteriaSettings SeedCriteriaSettings { get; set; } +} diff --git a/src/NzbDrone.Core/Indexers/FailDownloads.cs b/src/NzbDrone.Core/Indexers/FailDownloads.cs new file mode 100644 index 000000000..bccb8eeb3 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/FailDownloads.cs @@ -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 +} diff --git a/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs b/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs index fe46ab0dd..2b6368a42 100644 --- a/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs +++ b/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Core.Indexers.Fanzub { BaseUrl = "http://fanzub.com/rss/"; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsRssUrl", HelpText = "IndexerSettingsRssUrlHelpText")] @@ -36,6 +37,9 @@ namespace NzbDrone.Core.Indexers.Fanzub [FieldDefinition(2, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs index 9cff94744..576acbe72 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs @@ -38,6 +38,7 @@ namespace NzbDrone.Core.Indexers.FileList AnimeCategories = Array.Empty(); MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)] @@ -67,6 +68,9 @@ namespace NzbDrone.Core.Indexers.FileList [FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + [FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs index 8bab1adde..1e2dc0d2d 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs @@ -32,6 +32,7 @@ namespace NzbDrone.Core.Indexers.HDBits Codecs = Array.Empty(); Mediums = Array.Empty(); MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] @@ -64,6 +65,9 @@ namespace NzbDrone.Core.Indexers.HDBits [FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(10, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs index 5491b7c52..395066de5 100644 --- a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs @@ -8,5 +8,7 @@ namespace NzbDrone.Core.Indexers string BaseUrl { get; set; } IEnumerable MultiLanguages { get; set; } + + IEnumerable FailDownloads { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs index f9db8deec..af6748186 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs @@ -34,6 +34,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents { MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "IndexerIPTorrentsSettingsFeedUrl", HelpText = "IndexerIPTorrentsSettingsFeedUrlHelpText")] @@ -51,6 +52,9 @@ namespace NzbDrone.Core.Indexers.IPTorrents [FieldDefinition(4, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs index 36240529d..07ab7e5cd 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs @@ -59,6 +59,7 @@ namespace NzbDrone.Core.Indexers.Newznab Categories = new[] { 5030, 5040 }; AnimeCategories = Enumerable.Empty(); MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "URL")] @@ -86,6 +87,9 @@ namespace NzbDrone.Core.Indexers.Newznab [FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + // Field 8 is used by TorznabSettings MinimumSeeders // If you need to add another field here, update TorznabSettings as well and this comment diff --git a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs index d960a77cc..6f8290530 100644 --- a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs +++ b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs @@ -30,6 +30,7 @@ namespace NzbDrone.Core.Indexers.Nyaa AdditionalParameters = "&cats=1_0&filter=1"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")] @@ -53,6 +54,9 @@ namespace NzbDrone.Core.Indexers.Nyaa [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs b/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs index 64840994f..29c995037 100644 --- a/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs +++ b/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs @@ -1,10 +1,6 @@ using System; -using NzbDrone.Common.Cache; -using NzbDrone.Core.Datastore; using NzbDrone.Core.Download.Clients; -using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.ThingiProvider.Events; namespace NzbDrone.Core.Indexers { @@ -14,15 +10,13 @@ namespace NzbDrone.Core.Indexers TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason); } - public class SeedConfigProvider : ISeedConfigProvider, IHandle> + public class SeedConfigProvider : ISeedConfigProvider { - private readonly IIndexerFactory _indexerFactory; - private readonly ICached _cache; + private readonly ICachedIndexerSettingsProvider _cachedIndexerSettingsProvider; - public SeedConfigProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager) + public SeedConfigProvider(ICachedIndexerSettingsProvider cachedIndexerSettingsProvider) { - _indexerFactory = indexerFactory; - _cache = cacheManager.GetRollingCache(GetType(), "criteriaByIndexer", TimeSpan.FromHours(1)); + _cachedIndexerSettingsProvider = cachedIndexerSettingsProvider; } public TorrentSeedConfiguration GetSeedConfiguration(RemoteEpisode remoteEpisode) @@ -47,7 +41,8 @@ namespace NzbDrone.Core.Indexers return null; } - var seedCriteria = _cache.Get(indexerId.ToString(), () => FetchSeedCriteria(indexerId)); + var settings = _cachedIndexerSettingsProvider.GetSettings(indexerId); + var seedCriteria = settings?.SeedCriteriaSettings; if (seedCriteria == null) { @@ -67,25 +62,5 @@ namespace NzbDrone.Core.Indexers 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 message) - { - _cache.Clear(); - } } } diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs index baefcb04b..43c324a18 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs @@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.TorrentRss AllowZeroSize = false; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsRssUrl")] @@ -51,6 +52,9 @@ namespace NzbDrone.Core.Indexers.TorrentRss [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs index 12415e24a..b2fbdb66f 100644 --- a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs @@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.Torrentleech BaseUrl = "http://rss.torrentleech.org"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")] @@ -48,6 +49,9 @@ namespace NzbDrone.Core.Indexers.Torrentleech [FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs index 1936529a7..35951a54c 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs @@ -52,13 +52,13 @@ namespace NzbDrone.Core.Indexers.Torznab 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; } - [FieldDefinition(9)] + [FieldDefinition(10)] 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 override NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 53321e959..5be713c1f 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -998,6 +998,8 @@ "IndexerSettingsCategoriesHelpText": "Drop down list, leave blank to disable standard/daily shows", "IndexerSettingsCookie": "Cookie", "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", "IndexerSettingsMinimumSeedersHelpText": "Minimum number of seeders required.", "IndexerSettingsMultiLanguageRelease": "Multi Languages", diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs index 98d55064b..093792cd0 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs @@ -318,6 +318,11 @@ namespace NzbDrone.Core.MediaFiles { 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)))) { return RejectionResult(ImportRejectionReason.ExecutableFile, "Caution: Found executable file"); diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportRejectionReason.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportRejectionReason.cs index f13f5d2a1..b9a282b2d 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportRejectionReason.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportRejectionReason.cs @@ -5,6 +5,7 @@ public enum ImportRejectionReason Unknown, FileLocked, UnknownSeries, + DangerousFile, ExecutableFile, ArchiveFile, SeriesFolder, diff --git a/src/NzbDrone.Core/MediaFiles/FileExtensions.cs b/src/NzbDrone.Core/MediaFiles/FileExtensions.cs index 77d787645..59a9cf3b1 100644 --- a/src/NzbDrone.Core/MediaFiles/FileExtensions.cs +++ b/src/NzbDrone.Core/MediaFiles/FileExtensions.cs @@ -18,19 +18,27 @@ namespace NzbDrone.Core.MediaFiles ".tb2", ".tbz2", ".tgz", - ".zip", + ".zip" + }; + + private static List _dangerousExtensions = new List + { + ".lnk", + ".ps1", + ".vbs", ".zipx" }; private static List _executableExtensions = new List { - ".exe", ".bat", ".cmd", + ".exe", ".sh" }; public static HashSet ArchiveExtensions => new HashSet(_archiveExtensions, StringComparer.OrdinalIgnoreCase); + public static HashSet DangerousExtensions => new HashSet(_dangerousExtensions, StringComparer.OrdinalIgnoreCase); public static HashSet ExecutableExtensions => new HashSet(_executableExtensions, StringComparer.OrdinalIgnoreCase); } }