From 0a2eb74c265d48d6a25ef6c2c997494cb548649d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 27 Mar 2019 20:10:46 -0700 Subject: [PATCH] Release module validation in v3 --- .../Indexers/ReleasePushModule.cs | 24 ++++-- .../DiskSpace/DiskSpaceModule.cs | 4 +- src/Sonarr.Api.V3/Indexers/ReleaseModule.cs | 19 ++++- .../Indexers/ReleasePushModule.cs | 73 +++++++++++++++++-- 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/NzbDrone.Api/Indexers/ReleasePushModule.cs b/src/NzbDrone.Api/Indexers/ReleasePushModule.cs index e9e6da37f..61e5a760a 100644 --- a/src/NzbDrone.Api/Indexers/ReleasePushModule.cs +++ b/src/NzbDrone.Api/Indexers/ReleasePushModule.cs @@ -4,7 +4,6 @@ using FluentValidation; using Nancy; using Nancy.ModelBinding; using NLog; -using NzbDrone.Api.Extensions; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.DecisionEngine; @@ -12,7 +11,7 @@ using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; using Sonarr.Http.Extensions; -using Sonarr.Http.Mapping; +using Sonarr.Http.REST; namespace NzbDrone.Api.Indexers { @@ -21,6 +20,7 @@ namespace NzbDrone.Api.Indexers private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IProcessDownloadDecisions _downloadDecisionProcessor; private readonly IIndexerFactory _indexerFactory; + private ResourceValidator _releaseValidator; private readonly Logger _logger; public ReleasePushModule(IMakeDownloadDecision downloadDecisionMaker, @@ -35,14 +35,22 @@ namespace NzbDrone.Api.Indexers Post["/push"] = x => ProcessRelease(this.Bind()); - PostValidator.RuleFor(s => s.Title).NotEmpty(); - PostValidator.RuleFor(s => s.DownloadUrl).NotEmpty(); - PostValidator.RuleFor(s => s.Protocol).NotEmpty(); - PostValidator.RuleFor(s => s.PublishDate).NotEmpty(); + _releaseValidator = new ResourceValidator(); + _releaseValidator.RuleFor(s => s.Title).NotEmpty(); + _releaseValidator.RuleFor(s => s.DownloadUrl).NotEmpty(); + _releaseValidator.RuleFor(s => s.DownloadProtocol).NotEmpty(); + _releaseValidator.RuleFor(s => s.PublishDate).NotEmpty(); } private Response ProcessRelease(ReleaseResource release) { + var validationFailures = _releaseValidator.Validate(release).Errors; + + if (validationFailures.Any()) + { + throw new ValidationException(validationFailures); + } + _logger.Info("Release pushed: {0} - {1}", release.Title, release.DownloadUrl); var info = release.ToModel(); @@ -69,7 +77,7 @@ namespace NzbDrone.Api.Indexers } else { - _logger.Debug("Push Release {0} not associated with unknown indexer {1}.", release.Title, release.Indexer); + _logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.Indexer); } } else if (release.IndexerId != 0 && release.Indexer.IsNullOrWhiteSpace()) @@ -82,7 +90,7 @@ namespace NzbDrone.Api.Indexers } catch (ModelNotFoundException) { - _logger.Debug("Push Release {0} not associated with unknown indexer {0}.", release.Title, release.IndexerId); + _logger.Debug("Push Release {0} not associated with known indexer {0}.", release.Title, release.IndexerId); release.IndexerId = 0; } } diff --git a/src/Sonarr.Api.V3/DiskSpace/DiskSpaceModule.cs b/src/Sonarr.Api.V3/DiskSpace/DiskSpaceModule.cs index dedc59431..adf256a7c 100644 --- a/src/Sonarr.Api.V3/DiskSpace/DiskSpaceModule.cs +++ b/src/Sonarr.Api.V3/DiskSpace/DiskSpaceModule.cs @@ -4,12 +4,12 @@ using Sonarr.Http; namespace Sonarr.Api.V3.DiskSpace { - public class DiskSpaceModule :SonarrRestModule + public class DiskSpaceModule : SonarrRestModule { private readonly IDiskSpaceService _diskSpaceService; public DiskSpaceModule(IDiskSpaceService diskSpaceService) - :base("diskspace") + : base("diskspace") { _diskSpaceService = diskSpaceService; GetResourceAll = GetFreeSpace; diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs b/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs index 78c7e6590..9777a185d 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentValidation; using Nancy; using Nancy.ModelBinding; @@ -16,6 +17,7 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; using NzbDrone.Core.Validation; using Sonarr.Http.Extensions; +using Sonarr.Http.REST; using HttpStatusCode = System.Net.HttpStatusCode; namespace Sonarr.Api.V3.Indexers @@ -31,6 +33,7 @@ namespace Sonarr.Api.V3.Indexers private readonly IEpisodeService _episodeService; private readonly IParsingService _parsingService; private readonly Logger _logger; + private ResourceValidator _releaseValidator; private readonly ICached _remoteEpisodeCache; @@ -55,18 +58,26 @@ namespace Sonarr.Api.V3.Indexers _parsingService = parsingService; _logger = logger; + _releaseValidator = new ResourceValidator(); + _releaseValidator.RuleFor(s => s.DownloadAllowed).Equal(true); + _releaseValidator.RuleFor(s => s.IndexerId).ValidId(); + _releaseValidator.RuleFor(s => s.Guid).NotEmpty(); + GetResourceAll = GetReleases; Post["/"] = x => DownloadRelease(this.Bind()); - PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true); - PostValidator.RuleFor(s => s.IndexerId).ValidId(); - PostValidator.RuleFor(s => s.Guid).NotEmpty(); - _remoteEpisodeCache = cacheManager.GetCache(GetType(), "remoteEpisodes"); } private Response DownloadRelease(ReleaseResource release) { + var validationFailures = _releaseValidator.Validate(release).Errors; + + if (validationFailures.Any()) + { + throw new ValidationException(validationFailures); + } + var remoteEpisode = _remoteEpisodeCache.Find(GetCacheKey(release)); if (remoteEpisode == null) diff --git a/src/Sonarr.Api.V3/Indexers/ReleasePushModule.cs b/src/Sonarr.Api.V3/Indexers/ReleasePushModule.cs index 04a29c7f9..beef2f2d8 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleasePushModule.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleasePushModule.cs @@ -1,13 +1,18 @@ using System.Collections.Generic; using System.Linq; using FluentValidation; +using FluentValidation.Results; using Nancy; using Nancy.ModelBinding; using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; +using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; using Sonarr.Http.Extensions; +using Sonarr.Http.REST; namespace Sonarr.Api.V3.Indexers { @@ -15,36 +20,92 @@ namespace Sonarr.Api.V3.Indexers { private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IProcessDownloadDecisions _downloadDecisionProcessor; + private readonly IIndexerFactory _indexerFactory; + private ResourceValidator _releaseValidator; private readonly Logger _logger; public ReleasePushModule(IMakeDownloadDecision downloadDecisionMaker, IProcessDownloadDecisions downloadDecisionProcessor, + IIndexerFactory indexerFactory, Logger logger) { _downloadDecisionMaker = downloadDecisionMaker; _downloadDecisionProcessor = downloadDecisionProcessor; + _indexerFactory = indexerFactory; _logger = logger; - Post["/push"] = x => ProcessRelease(this.Bind()); + _releaseValidator = new ResourceValidator(); + _releaseValidator.RuleFor(s => s.Title).NotEmpty(); + _releaseValidator.RuleFor(s => s.DownloadUrl).NotEmpty(); + _releaseValidator.RuleFor(s => s.DownloadProtocol).NotEmpty(); + _releaseValidator.RuleFor(s => s.PublishDate).NotEmpty(); - PostValidator.RuleFor(s => s.Title).NotEmpty(); - PostValidator.RuleFor(s => s.DownloadUrl).NotEmpty(); - PostValidator.RuleFor(s => s.DownloadProtocol).NotEmpty(); - PostValidator.RuleFor(s => s.PublishDate).NotEmpty(); + Post["/push"] = x => ProcessRelease(this.Bind()); } private Response ProcessRelease(ReleaseResource release) { + var validationFailures = _releaseValidator.Validate(release).Errors; + + if (validationFailures.Any()) + { + throw new ValidationException(validationFailures); + } + _logger.Info("Release pushed: {0} - {1}", release.Title, release.DownloadUrl); var info = release.ToModel(); info.Guid = "PUSH-" + info.DownloadUrl; + ResolveIndexer(info); + var decisions = _downloadDecisionMaker.GetRssDecision(new List { info }); _downloadDecisionProcessor.ProcessDecisions(decisions); - return MapDecisions(decisions).First().AsResponse(); + var firstDecision = decisions.FirstOrDefault(); + + if (firstDecision?.RemoteEpisode.ParsedEpisodeInfo == null) + { + throw new ValidationException(new List{ new ValidationFailure("Title", "Unable to parse", release.Title) }); + } + + return MapDecisions(new [] { firstDecision }).AsResponse(); + } + + private void ResolveIndexer(ReleaseInfo release) + { + if (release.IndexerId == 0 && release.Indexer.IsNotNullOrWhiteSpace()) + { + var indexer = _indexerFactory.All().FirstOrDefault(v => v.Name == release.Indexer); + if (indexer != null) + { + release.IndexerId = indexer.Id; + _logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer); + } + else + { + _logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.Indexer); + } + } + else if (release.IndexerId != 0 && release.Indexer.IsNullOrWhiteSpace()) + { + try + { + var indexer = _indexerFactory.Get(release.IndexerId); + release.Indexer = indexer.Name; + _logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer); + } + catch (ModelNotFoundException) + { + _logger.Debug("Push Release {0} not associated with known indexer {0}.", release.Title, release.IndexerId); + release.IndexerId = 0; + } + } + else + { + _logger.Debug("Push Release {0} not associated with an indexer.", release.Title); + } } } }