diff --git a/src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs index 72e122dd66..d219a54930 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/SeedConfigProviderFixture.cs @@ -1,8 +1,10 @@ -using FluentAssertions; +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; using NzbDrone.Core.Test.Framework; @@ -14,13 +16,13 @@ public class SeedConfigProviderFixture : CoreTest [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 RemoteMovie { - Release = new ReleaseInfo() + Release = new ReleaseInfo { DownloadProtocol = DownloadProtocol.Torrent, IndexerId = 0 @@ -29,5 +31,33 @@ public void should_not_return_config_for_non_existent_indexer() result.Should().BeNull(); } + + [Test] + public void should_return_seed_time_for_movies() + { + var settings = new TorznabSettings(); + settings.SeedCriteria.SeedTime = 10; + + Mocker.GetMock() + .Setup(v => v.GetSettings(It.IsAny())) + .Returns(new CachedIndexerSettings + { + FailDownloads = new HashSet { FailDownloads.Executables }, + SeedCriteriaSettings = settings.SeedCriteria + }); + + var result = Subject.GetSeedConfiguration(new RemoteMovie + { + Release = new ReleaseInfo + { + DownloadProtocol = DownloadProtocol.Torrent, + IndexerId = 1 + }, + ParsedMovieInfo = new ParsedMovieInfo() + }); + + result.Should().NotBeNull(); + result.SeedTime.Should().Be(TimeSpan.FromMinutes(10)); + } } } diff --git a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs index 948867108c..706e87c16e 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs @@ -15,5 +15,6 @@ public NzbDroneValidationResult Validate() 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 20a8ea397e..e9b3feb667 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -33,6 +33,7 @@ public class CompletedDownloadService : ICompletedDownloadService private readonly IParsingService _parsingService; private readonly IMovieService _movieService; private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported; + private readonly IRejectedImportService _rejectedImportService; private readonly Logger _logger; public CompletedDownloadService(IEventAggregator eventAggregator, @@ -42,6 +43,7 @@ public CompletedDownloadService(IEventAggregator eventAggregator, IParsingService parsingService, IMovieService movieService, ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported, + IRejectedImportService rejectedImportService, Logger logger) { _eventAggregator = eventAggregator; @@ -51,6 +53,7 @@ public CompletedDownloadService(IEventAggregator eventAggregator, _parsingService = parsingService; _movieService = movieService; _trackedDownloadAlreadyImported = trackedDownloadAlreadyImported; + _rejectedImportService = rejectedImportService; _logger = logger; } @@ -159,10 +162,8 @@ public void Import(TrackedDownload trackedDownload) { var firstResult = importResults.First(); - if (firstResult.Result == ImportResultType.Rejected && firstResult.ImportDecision.LocalMovie == 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 1e88e257c8..53ec5cee17 100644 --- a/src/NzbDrone.Core/Download/DownloadProcessingService.cs +++ b/src/NzbDrone.Core/Download/DownloadProcessingService.cs @@ -55,14 +55,18 @@ public void Execute(ProcessMonitoredDownloadsCommand message) { 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 0000000000..270d90787d --- /dev/null +++ b/src/NzbDrone.Core/Download/RejectedImportService.cs @@ -0,0 +1,60 @@ +using System.Linq; +using NLog; +using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.MediaFiles.MovieImport; + +namespace NzbDrone.Core.Download; + +public interface IRejectedImportService +{ + bool Process(TrackedDownload trackedDownload, ImportResult importResult); +} + +public class RejectedImportService : IRejectedImportService +{ + private readonly ICachedIndexerSettingsProvider _cachedIndexerSettingsProvider; + private readonly Logger _logger; + + public RejectedImportService(ICachedIndexerSettingsProvider cachedIndexerSettingsProvider, Logger logger) + { + _cachedIndexerSettingsProvider = cachedIndexerSettingsProvider; + _logger = logger; + } + + public bool Process(TrackedDownload trackedDownload, ImportResult importResult) + { + if (importResult.Result != ImportResultType.Rejected || trackedDownload.RemoteMovie?.Release == null) + { + return false; + } + + var indexerSettings = _cachedIndexerSettingsProvider.GetSettings(trackedDownload.RemoteMovie.Release.IndexerId); + var rejectionReason = importResult.ImportDecision.Rejections.FirstOrDefault()?.Reason; + + if (indexerSettings == null) + { + trackedDownload.Warn(new TrackedDownloadStatusMessage(trackedDownload.DownloadItem.Title, importResult.Errors)); + return true; + } + + if (rejectionReason == ImportRejectionReason.DangerousFile && + indexerSettings.FailDownloads.Contains(FailDownloads.PotentiallyDangerous)) + { + _logger.Trace("Download '{0}' contains potentially dangerous file, marking as failed", trackedDownload.DownloadItem.Title); + trackedDownload.Fail(); + } + else if (rejectionReason == ImportRejectionReason.ExecutableFile && + indexerSettings.FailDownloads.Contains(FailDownloads.Executables)) + { + _logger.Trace("Download '{0}' contains executable file, marking as failed", trackedDownload.DownloadItem.Title); + trackedDownload.Fail(); + } + else + { + trackedDownload.Warn(new TrackedDownloadStatusMessage(trackedDownload.DownloadItem.Title, importResult.Errors)); + } + + return true; + } +} diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs index 77f628b7a3..7ca3c66c20 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs @@ -35,6 +35,12 @@ public void Warn(params TrackedDownloadStatusMessage[] statusMessages) 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 fee706f7b1..96d28c07e4 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -166,6 +166,11 @@ public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, Do { trackedDownload.RemoteMovie.Release.IndexerFlags = flags; } + + if (downloadHistory != null) + { + trackedDownload.RemoteMovie.Release.IndexerId = downloadHistory.IndexerId; + } } } diff --git a/src/NzbDrone.Core/Indexers/CachedIndexerSettingsProvider.cs b/src/NzbDrone.Core/Indexers/CachedIndexerSettingsProvider.cs new file mode 100644 index 0000000000..f5cb3064cd --- /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 0000000000..bccb8eeb3a --- /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/FileList/FileListSettings.cs b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs index 2396f26710..c3fa0c5c32 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs @@ -38,6 +38,7 @@ public FileListSettings() }; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); RequiredFlags = Array.Empty(); } @@ -65,7 +66,10 @@ public FileListSettings() [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(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] + [FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + + [FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] public IEnumerable RequiredFlags { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs index bef2b52e8b..32add7831b 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs @@ -33,6 +33,7 @@ public HDBitsSettings() Codecs = Array.Empty(); Mediums = Array.Empty(); MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); RequiredFlags = Array.Empty(); } @@ -66,7 +67,10 @@ public HDBitsSettings() [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(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] + [FieldDefinition(10, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + + [FieldDefinition(11, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] public IEnumerable RequiredFlags { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs index 1bc6075611..04550bf522 100644 --- a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs @@ -9,5 +9,7 @@ public interface IIndexerSettings : IProviderConfig // TODO: Need to Create UI field for this and turn functionality back on per indexer. 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 0e2af53a4c..dd4940f2f4 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs @@ -36,6 +36,7 @@ public IPTorrentsSettings() BaseUrl = string.Empty; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); RequiredFlags = Array.Empty(); } @@ -54,7 +55,10 @@ public IPTorrentsSettings() [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(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] + [FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + + [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] public IEnumerable RequiredFlags { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs index 70c1a4337a..00707a6c27 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs @@ -58,6 +58,7 @@ public NewznabSettings() ApiPath = "/api"; Categories = new[] { 2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060 }; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); } [FieldDefinition(0, Label = "URL")] @@ -79,7 +80,10 @@ public NewznabSettings() [FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] public IEnumerable MultiLanguages { get; set; } - [FieldDefinition(6, Type = FieldType.Checkbox, Label = "IndexerSettingsRemoveYear", HelpText = "IndexerSettingsRemoveYearHelpText", Advanced = true)] + [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + + [FieldDefinition(7, Type = FieldType.Checkbox, Label = "IndexerSettingsRemoveYear", HelpText = "IndexerSettingsRemoveYearHelpText", Advanced = true)] public bool RemoveYear { get; set; } // Field 8 is used by TorznabSettings MinimumSeeders diff --git a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs index 13d27c82c2..a6487dc9bc 100644 --- a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs +++ b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs @@ -31,6 +31,7 @@ public NyaaSettings() AdditionalParameters = "&cats=1_0&filter=1"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); RequiredFlags = Array.Empty(); } @@ -52,7 +53,10 @@ public NyaaSettings() [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(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] + [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + + [FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] public IEnumerable RequiredFlags { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs index 090b039da4..67f40618e6 100644 --- a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs +++ b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs @@ -30,6 +30,7 @@ public PassThePopcornSettings() BaseUrl = "https://passthepopcorn.me"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); RequiredFlags = Array.Empty(); } @@ -54,7 +55,10 @@ public PassThePopcornSettings() [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(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] + [FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + + [FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] public IEnumerable RequiredFlags { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs b/src/NzbDrone.Core/Indexers/SeedConfigProvider.cs index 868ca91d1f..b699c7fc6b 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 @@ public interface ISeedConfigProvider TorrentSeedConfiguration GetSeedConfiguration(int indexerId); } - 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(RemoteMovie remoteMovie) @@ -47,7 +41,8 @@ public TorrentSeedConfiguration GetSeedConfiguration(int indexerId) return null; } - var seedCriteria = _cache.Get(indexerId.ToString(), () => FetchSeedCriteria(indexerId)); + var settings = _cachedIndexerSettingsProvider.GetSettings(indexerId); + var seedCriteria = settings?.SeedCriteriaSettings; if (seedCriteria == null) { @@ -68,25 +63,5 @@ public TorrentSeedConfiguration GetSeedConfiguration(int indexerId) 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/TorrentPotato/TorrentPotatoSettings.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs index 42aa7b4ac3..5b9656603e 100644 --- a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs @@ -28,6 +28,7 @@ public TorrentPotatoSettings() BaseUrl = "http://127.0.0.1"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); RequiredFlags = Array.Empty(); } @@ -52,7 +53,10 @@ public TorrentPotatoSettings() [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(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] + [FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + + [FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] public IEnumerable RequiredFlags { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs index 06debfa312..9658f2f98b 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs @@ -29,6 +29,7 @@ public TorrentRssIndexerSettings() AllowZeroSize = false; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; MultiLanguages = Array.Empty(); + FailDownloads = Array.Empty(); RequiredFlags = Array.Empty(); } @@ -53,7 +54,10 @@ public TorrentRssIndexerSettings() [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(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] + [FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)] + public IEnumerable FailDownloads { get; set; } + + [FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] public IEnumerable RequiredFlags { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs index 80aaabdaf1..703cfeeeb6 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs @@ -55,16 +55,16 @@ public TorznabSettings() RequiredFlags = Array.Empty(); } - [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; } - [FieldDefinition(11, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] + [FieldDefinition(12, Type = FieldType.Select, SelectOptions = typeof(IndexerFlags), Label = "IndexerSettingsRequiredFlags", HelpText = "IndexerSettingsRequiredFlagsHelpText", HelpLink = "https://wiki.servarr.com/radarr/settings#indexer-flags", Advanced = true)] public IEnumerable RequiredFlags { 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 df22be2986..a5276b74dd 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -889,6 +889,8 @@ "IndexerSettingsCategories": "Categories", "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/DownloadedMovieImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs index e8a0b715c0..dc50cac168 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs @@ -277,6 +277,26 @@ private List ProcessFile(FileInfo fileInfo, ImportMode importMode, var extension = Path.GetExtension(fileInfo.Name); + if (FileExtensions.DangerousExtensions.Contains(extension)) + { + return new List + { + new ImportResult(new ImportDecision(new LocalMovie { Path = fileInfo.FullName }, + new ImportRejection(ImportRejectionReason.DangerousFile, $"Caution: Found potentially dangerous file with extension: {extension}")), + $"Caution: Found potentially dangerous file with extension: {extension}") + }; + } + + if (FileExtensions.ExecutableExtensions.Contains(extension)) + { + return new List + { + new ImportResult(new ImportDecision(new LocalMovie { Path = fileInfo.FullName }, + new ImportRejection(ImportRejectionReason.ExecutableFile, $"Caution: Found executable file with extension: '{extension}'")), + $"Caution: Found executable file with extension: '{extension}'") + }; + } + if (extension.IsNullOrWhiteSpace() || !MediaFileExtensions.Extensions.Contains(extension)) { _logger.Debug("[{0}] has an unsupported extension: '{1}'", fileInfo.FullName, extension); @@ -335,6 +355,11 @@ private ImportResult CheckEmptyResultForIssue(string folder) { 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/FileExtensions.cs b/src/NzbDrone.Core/MediaFiles/FileExtensions.cs index 77d7876456..ee55e54b23 100644 --- a/src/NzbDrone.Core/MediaFiles/FileExtensions.cs +++ b/src/NzbDrone.Core/MediaFiles/FileExtensions.cs @@ -18,19 +18,30 @@ internal static class FileExtensions ".tb2", ".tbz2", ".tgz", - ".zip", + ".zip" + }; + + private static List _dangerousExtensions = new List + { + ".arj", + ".lnk", + ".lzh", + ".ps1", + ".scr", + ".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); } } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportRejectionReason.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportRejectionReason.cs index 9240599fd7..91963665ba 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportRejectionReason.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportRejectionReason.cs @@ -5,6 +5,7 @@ public enum ImportRejectionReason Unknown, FileLocked, UnknownMovie, + DangerousFile, ExecutableFile, ArchiveFile, MovieFolder,