diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs index b99d13b26..4d09ed606 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Moq; @@ -28,6 +28,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests private Mock _fail2; private Mock _fail3; + private Mock _failDelayed1; + [SetUp] public void Setup() { @@ -39,6 +41,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _fail2 = new Mock(); _fail3 = new Mock(); + _failDelayed1 = new Mock(); + _pass1.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Accept); _pass2.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Accept); _pass3.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Accept); @@ -47,6 +51,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _fail2.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Reject("fail2")); _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Reject("fail3")); + _failDelayed1.Setup(c => c.IsSatisfiedBy(It.IsAny(), null)).Returns(Decision.Reject("failDelayed1")); + _failDelayed1.SetupGet(c => c.Priority).Returns(SpecificationPriority.Disk); + _reports = new List { new ReleaseInfo { Title = "Coldplay-A Head Full Of Dreams-CD-FLAC-2015-PERFECT" } }; _remoteAlbum = new RemoteAlbum { Artist = new Artist(), @@ -78,6 +85,25 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _pass3.Verify(c => c.IsSatisfiedBy(_remoteAlbum, null), Times.Once()); } + [Test] + public void should_call_delayed_specifications_if_non_delayed_passed() + { + GivenSpecifications(_pass1, _failDelayed1); + + Subject.GetRssDecision(_reports).ToList(); + _failDelayed1.Verify(c => c.IsSatisfiedBy(_remoteAlbum, null), Times.Once()); + } + + [Test] + public void should_not_call_delayed_specifications_if_non_delayed_failed() + { + GivenSpecifications(_fail1, _failDelayed1); + + Subject.GetRssDecision(_reports).ToList(); + + _failDelayed1.Verify(c => c.IsSatisfiedBy(_remoteAlbum, null), Times.Never()); + } + [Test] public void should_return_rejected_if_single_specs_fail() { @@ -287,4 +313,4 @@ namespace NzbDrone.Core.Test.DecisionEngineTests ExceptionVerification.ExpectedErrors(1); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs new file mode 100644 index 000000000..173b061e3 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DeletedTrackFileSpecificationFixture.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.DecisionEngine.Specifications.RssSync; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Music; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Common.Disk; +using Moq; +using NzbDrone.Test.Common; +using System.IO; + +namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync +{ + [TestFixture] + public class DeletedTrackFileSpecificationFixture : CoreTest + { + private RemoteAlbum _parseResultMulti; + private RemoteAlbum _parseResultSingle; + private TrackFile _firstFile; + private TrackFile _secondFile; + + [SetUp] + public void Setup() + { + _firstFile = + new TrackFile{ + Id = 1, + RelativePath = "My.Artist.S01E01.mp3", + Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)), + DateAdded = DateTime.Now, + AlbumId = 1 + + }; + _secondFile = + new TrackFile{ + Id = 2, + RelativePath = "My.Artist.S01E02.mp3", + Quality = new QualityModel(Quality.FLAC, new Revision(version: 1)), + DateAdded = DateTime.Now, + AlbumId = 2 + + }; + + var singleAlbumList = new List { new Album { Id = 1 } }; + var doubleAlbumList = new List { + new Album { Id = 1 }, + new Album { Id = 2 } + }; + + var firstTrack = new Track { TrackFile = _firstFile, TrackFileId = 1, AlbumId = 1 }; + var secondTrack = new Track { TrackFile = _secondFile, TrackFileId = 2, AlbumId = 2 }; + + var fakeArtist = Builder.CreateNew() + .With(c => c.Profile = new Profile { Cutoff = Quality.FLAC }) + .With(c => c.Path = @"C:\Music\My.Artist".AsOsAgnostic()) + .Build(); + + _parseResultMulti = new RemoteAlbum + { + Artist = fakeArtist, + ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)) }, + Albums = doubleAlbumList + }; + + _parseResultSingle = new RemoteAlbum + { + Artist = fakeArtist, + ParsedAlbumInfo = new ParsedAlbumInfo { Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2)) }, + Albums = singleAlbumList + }; + + GivenUnmonitorDeletedTracks(true); + } + + private void GivenUnmonitorDeletedTracks(bool enabled) + { + Mocker.GetMock() + .SetupGet(v => v.AutoUnmonitorPreviouslyDownloadedTracks) + .Returns(enabled); + } + + private void SetupMediaFile(List files) + { + Mocker.GetMock() + .Setup(v => v.GetFilesByAlbum(It.IsAny(), It.IsAny())) + .Returns(files); + } + + private void WithExistingFile(TrackFile trackFile) + { + var path = Path.Combine(@"C:\Music\My.Artist".AsOsAgnostic(), trackFile.RelativePath); + + Mocker.GetMock() + .Setup(v => v.FileExists(path)) + .Returns(true); + } + + [Test] + public void should_return_true_when_unmonitor_deleted_tracks_is_off() + { + GivenUnmonitorDeletedTracks(false); + + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_when_searching() + { + Subject.IsSatisfiedBy(_parseResultSingle, new ArtistSearchCriteria()).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_if_file_exists() + { + WithExistingFile(_firstFile); + SetupMediaFile(new List { _firstFile }); + + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_file_is_missing() + { + SetupMediaFile(new List { _firstFile }); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_return_true_if_both_of_multiple_episode_exist() + { + WithExistingFile(_firstFile); + WithExistingFile(_secondFile); + SetupMediaFile(new List { _firstFile, _secondFile }); + + Subject.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_one_of_multiple_episode_is_missing() + { + WithExistingFile(_firstFile); + SetupMediaFile(new List { _firstFile, _secondFile }); + + Subject.IsSatisfiedBy(_parseResultMulti, null).Accepted.Should().BeFalse(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index bf7907dd5..c37b48540 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -152,6 +152,7 @@ + diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index 7976f57b8..e5c1f573e 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NLog; @@ -111,9 +111,16 @@ namespace NzbDrone.Core.DecisionEngine private DownloadDecision GetDecisionForReport(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria = null) { - var reasons = _specifications.Select(c => EvaluateSpec(c, remoteAlbum, searchCriteria)) - .Where(c => c != null); + var reasons = new Rejection[0]; + foreach (var specifications in _specifications.GroupBy(v => v.Priority).OrderBy(v => v.Key)) + { + reasons = specifications.Select(c => EvaluateSpec(c, remoteAlbum, searchCriteria)) + .Where(c => c != null) + .ToArray(); + + if (reasons.Any()) break; + } return new DownloadDecision(remoteAlbum, reasons.ToArray()); } diff --git a/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs b/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs index 937ce8d40..cafede0fb 100644 --- a/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/IDecisionEngineSpecification.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.DecisionEngine @@ -7,6 +7,8 @@ namespace NzbDrone.Core.DecisionEngine { RejectionType Type { get; } + SpecificationPriority Priority { get; } + Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria); } } diff --git a/src/NzbDrone.Core/DecisionEngine/SpecificationPriority.cs b/src/NzbDrone.Core/DecisionEngine/SpecificationPriority.cs new file mode 100644 index 000000000..2d720c4b9 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/SpecificationPriority.cs @@ -0,0 +1,10 @@ +namespace NzbDrone.Core.DecisionEngine +{ + public enum SpecificationPriority + { + Default = 0, + Parsing = 0, + Database = 0, + Disk = 1 + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs index 09045d5fe..62a7738f8 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.IndexerSearch.Definitions; @@ -19,6 +19,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs index 3e487abb0..0f471dad9 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/BlacklistSpecification.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Core.Blacklisting; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Database; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index 761b26482..1801235fc 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _mediaFileService = mediaFileService; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/DiscographySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/DiscographySpecification.cs index ff25de723..a1e79112d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/DiscographySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/DiscographySpecification.cs @@ -1,4 +1,4 @@ -using System; +using System; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs index affc82b04..51b9be10d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs index 47b09ab66..632d9c9a2 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/MinimumAgeSpecification.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Temporary; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs index ab4e2e3ff..825ca3c3d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/NotSampleSpecification.cs @@ -1,4 +1,4 @@ -using System; +using System; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -9,6 +9,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { private readonly Logger _logger; + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public NotSampleSpecification(Logger logger) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs index 7d37b8770..dae103196 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Core.Indexers; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -18,6 +18,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs index cc2f44331..a02ee49e2 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -13,6 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index d139a9cf4..28cbc52b9 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -22,6 +22,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs index c079cefce..a6a6acd54 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RawDiskSpecification.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.IndexerSearch.Definitions; @@ -17,6 +17,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs index 37de16679..ffa752f94 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NLog; @@ -20,6 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs index f2f3b1fa3..c3deb05fd 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RetentionSpecification.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index 17484a814..0627459df 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -31,6 +31,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Database; public RejectionType Type => RejectionType.Temporary; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DeletedTrackFileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DeletedTrackFileSpecification.cs new file mode 100644 index 000000000..232753992 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DeletedTrackFileSpecification.cs @@ -0,0 +1,76 @@ +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Music; + +namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync +{ + public class DeletedTrackFileSpecification : IDecisionEngineSpecification + { + private readonly IDiskProvider _diskProvider; + private readonly IConfigService _configService; + private readonly IMediaFileService _albumService; + private readonly Logger _logger; + + public DeletedTrackFileSpecification(IDiskProvider diskProvider, + IConfigService configService, + IMediaFileService albumService, + Logger logger) + { + _diskProvider = diskProvider; + _configService = configService; + _albumService = albumService; + _logger = logger; + } + + public SpecificationPriority Priority => SpecificationPriority.Disk; + public RejectionType Type => RejectionType.Temporary; + + public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) + { + if (!_configService.AutoUnmonitorPreviouslyDownloadedTracks) + { + return Decision.Accept(); + } + + if (searchCriteria != null) + { + _logger.Debug("Skipping deleted trackfile check during search"); + return Decision.Accept(); + } + + var missingTrackFiles = subject.Albums + .SelectMany(v => _albumService.GetFilesByAlbum(v.ArtistId, v.Id)) + .DistinctBy(v => v.Id) + .Where(v => IsTrackFileMissing(subject.Artist, v)) + .ToArray(); + + + if (missingTrackFiles.Any()) + { + foreach (var missingTrackFile in missingTrackFiles) + { + _logger.Trace("Track file {0} is missing from disk.", missingTrackFile.RelativePath); + } + + _logger.Debug("Files for this album exist in the database but not on disk, will be unmonitored on next diskscan. skipping."); + return Decision.Reject("Artist is not monitored"); + } + + return Decision.Accept(); + } + + private bool IsTrackFileMissing(Artist artist, TrackFile trackFile) + { + var fullPath = Path.Combine(artist.Path, trackFile.RelativePath); + + return !_diskProvider.FileExists(fullPath); + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index 699214b11..ab9b4f30b 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -26,6 +26,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Database; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredAlbumSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredAlbumSpecification.cs index 335d31497..404b39c3d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredAlbumSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/MonitoredAlbumSpecification.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -14,6 +14,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs index daf654b58..64418cfb6 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs @@ -23,6 +23,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/SameTracksGrabSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/SameTracksGrabSpecification.cs index c7098f11d..8f03c4cee 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/SameTracksGrabSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/SameTracksGrabSpecification.cs @@ -1,4 +1,4 @@ -using System; +using System; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/AlbumRequestedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/AlbumRequestedSpecification.cs index b513b575d..2be1a20b8 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/AlbumRequestedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/AlbumRequestedSpecification.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -16,6 +16,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/ArtistSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/ArtistSpecification.cs index cef67f820..7eb972b80 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/ArtistSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/ArtistSpecification.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -13,6 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria) @@ -33,4 +34,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyAudioMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyAudioMatchSpecification.cs index e023a92f4..76fc116b9 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyAudioMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyAudioMatchSpecification.cs @@ -1,4 +1,4 @@ -using System; +using System; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -14,6 +14,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Database; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) @@ -23,4 +24,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search // TODO Rework for Daily Audio/Podcasts } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs index 0a78db5e6..f934f2e55 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs @@ -1,4 +1,4 @@ -using System; +using System; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -14,6 +14,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) @@ -21,4 +22,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleAlbumSearchMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleAlbumSearchMatchSpecification.cs index 08919597d..09ba2c376 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleAlbumSearchMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleAlbumSearchMatchSpecification.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using NLog; using NzbDrone.Core.IndexerSearch.Definitions; @@ -15,6 +15,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria) @@ -45,4 +46,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs index 3ceb3ef1a..0495b001e 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -13,6 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public Decision IsSatisfiedBy(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria) @@ -33,4 +34,4 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index 52ffda1e2..853a04a12 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } + public SpecificationPriority Priority => SpecificationPriority.Default; public RejectionType Type => RejectionType.Permanent; public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 12fbd92fd..3affe55b8 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -328,6 +328,7 @@ + @@ -341,6 +342,7 @@ +