1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-05 02:22:31 +01:00

Episode import improvements

Fixed: Use folder name when file name is not parsable on import
This commit is contained in:
Mark McDowall 2015-01-26 21:57:07 -08:00
parent 5b54b02d7e
commit 20782bbbc1
41 changed files with 562 additions and 220 deletions

View File

@ -1,6 +1,6 @@
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
namespace NzbDrone.Test.Common namespace NzbDrone.Common.Extensions
{ {
public static class ObjectExtensions public static class ObjectExtensions
{ {

View File

@ -134,6 +134,7 @@
<Compile Include="Extensions\DateTimeExtensions.cs" /> <Compile Include="Extensions\DateTimeExtensions.cs" />
<Compile Include="Crypto\HashConverter.cs" /> <Compile Include="Crypto\HashConverter.cs" />
<Compile Include="Extensions\Int64Extensions.cs" /> <Compile Include="Extensions\Int64Extensions.cs" />
<Compile Include="Extensions\ObjectExtensions.cs" />
<Compile Include="Extensions\StreamExtensions.cs" /> <Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Extensions\XmlExtentions.cs" /> <Compile Include="Extensions\XmlExtentions.cs" />
<Compile Include="HashUtil.cs" /> <Compile Include="HashUtil.cs" />

View File

@ -5,6 +5,7 @@
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.History; using NzbDrone.Core.History;
@ -153,8 +154,13 @@ public void should_not_mark_as_imported_if_all_files_were_rejected()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>())) .Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult> .Returns(new List<ImportResult>
{ {
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, "Rejected!"),"Test Failure"), new ImportResult(
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"}, "Rejected!"),"Test Failure") new ImportDecision(
new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"),
new ImportResult(
new ImportDecision(
new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure")
}); });
Subject.Process(_trackedDownload); Subject.Process(_trackedDownload);

View File

@ -4,6 +4,7 @@
using Marr.Data; using Marr.Data;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
@ -12,7 +13,6 @@
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{ {

View File

@ -4,17 +4,16 @@
using Marr.Data; using Marr.Data;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{ {

View File

@ -4,6 +4,7 @@
using Marr.Data; using Marr.Data;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.Pending;
@ -14,7 +15,6 @@
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{ {

View File

@ -101,7 +101,7 @@ public void should_not_scan_extras_subfolder()
Subject.Scan(_series); Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series, false, (QualityModel)null), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series), Times.Once());
} }
[Test] [Test]
@ -119,7 +119,7 @@ public void should_not_scan_AppleDouble_subfolder()
Subject.Scan(_series); Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series, false, (QualityModel)null), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series), Times.Once());
} }
[Test] [Test]
@ -141,7 +141,7 @@ public void should_scan_extras_series_and_subfolders()
Subject.Scan(_series); Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _series, false, (QualityModel)null), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 4), _series), Times.Once());
} }
[Test] [Test]
@ -160,7 +160,7 @@ public void should_not_scan_subfolders_that_start_with_period()
Subject.Scan(_series); Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series, false, (QualityModel)null), Times.Once()); .Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series), Times.Once());
} }
} }
} }

View File

@ -78,7 +78,7 @@ public void should_skip_if_no_series_found()
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Verify(c => c.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), It.IsAny<bool>(), It.IsAny<QualityModel>()), .Verify(c => c.GetImportDecisions(It.IsAny<List<string>>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<bool>()),
Times.Never()); Times.Never());
VerifyNoImport(); VerifyNoImport();
@ -129,7 +129,7 @@ public void should_not_delete_folder_if_files_were_imported_and_video_files_rema
imported.Add(new ImportDecision(localEpisode)); imported.Add(new ImportDecision(localEpisode));
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), true, null)) .Setup(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), null, true))
.Returns(imported); .Returns(imported);
Mocker.GetMock<IImportApprovedEpisodes>() Mocker.GetMock<IImportApprovedEpisodes>()
@ -155,14 +155,14 @@ public void should_delete_folder_if_files_were_imported_and_only_sample_files_re
imported.Add(new ImportDecision(localEpisode)); imported.Add(new ImportDecision(localEpisode));
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), true, null)) .Setup(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), null, true))
.Returns(imported); .Returns(imported);
Mocker.GetMock<IImportApprovedEpisodes>() Mocker.GetMock<IImportApprovedEpisodes>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null)) .Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
.Returns(imported.Select(i => new ImportResult(i)).ToList()); .Returns(imported.Select(i => new ImportResult(i)).ToList());
Mocker.GetMock<ISampleService>() Mocker.GetMock<IDetectSample>()
.Setup(s => s.IsSample(It.IsAny<Series>(), .Setup(s => s.IsSample(It.IsAny<Series>(),
It.IsAny<QualityModel>(), It.IsAny<QualityModel>(),
It.IsAny<String>(), It.IsAny<String>(),
@ -224,14 +224,14 @@ public void should_not_delete_if_there_is_large_rar_file()
imported.Add(new ImportDecision(localEpisode)); imported.Add(new ImportDecision(localEpisode));
Mocker.GetMock<IMakeImportDecision>() Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), true, null)) .Setup(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), null, true))
.Returns(imported); .Returns(imported);
Mocker.GetMock<IImportApprovedEpisodes>() Mocker.GetMock<IImportApprovedEpisodes>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null)) .Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
.Returns(imported.Select(i => new ImportResult(i)).ToList()); .Returns(imported.Select(i => new ImportResult(i)).ToList());
Mocker.GetMock<ISampleService>() Mocker.GetMock<IDetectSample>()
.Setup(s => s.IsSample(It.IsAny<Series>(), .Setup(s => s.IsSample(It.IsAny<Series>(),
It.IsAny<QualityModel>(), It.IsAny<QualityModel>(),
It.IsAny<String>(), It.IsAny<String>(),

View File

@ -45,31 +45,20 @@ public void Setup()
_fail2 = new Mock<IImportDecisionEngineSpecification>(); _fail2 = new Mock<IImportDecisionEngineSpecification>();
_fail3 = new Mock<IImportDecisionEngineSpecification>(); _fail3 = new Mock<IImportDecisionEngineSpecification>();
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(true); _pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(Decision.Accept());
_pass1.Setup(c => c.RejectionReason).Returns("_pass1"); _pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(Decision.Accept());
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(Decision.Accept());
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(true); _fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(Decision.Reject("_fail1"));
_pass2.Setup(c => c.RejectionReason).Returns("_pass2"); _fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(Decision.Reject("_fail2"));
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(Decision.Reject("_fail3"));
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(true);
_pass3.Setup(c => c.RejectionReason).Returns("_pass3");
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(false);
_fail1.Setup(c => c.RejectionReason).Returns("_fail1");
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(false);
_fail2.Setup(c => c.RejectionReason).Returns("_fail2");
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(false);
_fail3.Setup(c => c.RejectionReason).Returns("_fail3");
_videoFiles = new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" };
_series = Builder<Series>.CreateNew() _series = Builder<Series>.CreateNew()
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
.Build(); .Build();
_quality = new QualityModel(Quality.DVD); _quality = new QualityModel(Quality.DVD);
_localEpisode = new LocalEpisode _localEpisode = new LocalEpisode
{ {
Series = _series, Series = _series,
@ -78,17 +67,24 @@ public void Setup()
}; };
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>())) .Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Boolean>()))
.Returns(_localEpisode); .Returns(_localEpisode);
Mocker.GetMock<IMediaFileService>() GivenVideoFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() });
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
.Returns(_videoFiles);
} }
private void GivenSpecifications(params Mock<IImportDecisionEngineSpecification>[] mocks) private void GivenSpecifications(params Mock<IImportDecisionEngineSpecification>[] mocks)
{ {
Mocker.SetConstant<IEnumerable<IRejectWithReason>>(mocks.Select(c => c.Object)); Mocker.SetConstant(mocks.Select(c => c.Object));
}
private void GivenVideoFiles(IEnumerable<string> videoFiles)
{
_videoFiles = videoFiles.ToList();
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
.Returns(_videoFiles);
} }
[Test] [Test]
@ -96,7 +92,7 @@ public void should_call_all_specifications()
{ {
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3); GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
Subject.GetImportDecisions(_videoFiles, new Series(), false); Subject.GetImportDecisions(_videoFiles, new Series(), null, false);
_fail1.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once()); _fail1.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once()); _fail2.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once());
@ -111,7 +107,7 @@ public void should_return_rejected_if_single_specs_fail()
{ {
GivenSpecifications(_fail1); GivenSpecifications(_fail1);
var result = Subject.GetImportDecisions(_videoFiles, new Series(), false); var result = Subject.GetImportDecisions(_videoFiles, new Series());
result.Single().Approved.Should().BeFalse(); result.Single().Approved.Should().BeFalse();
} }
@ -121,7 +117,7 @@ public void should_return_rejected_if_one_of_specs_fail()
{ {
GivenSpecifications(_pass1, _fail1, _pass2, _pass3); GivenSpecifications(_pass1, _fail1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_videoFiles, new Series(), false); var result = Subject.GetImportDecisions(_videoFiles, new Series());
result.Single().Approved.Should().BeFalse(); result.Single().Approved.Should().BeFalse();
} }
@ -131,7 +127,7 @@ public void should_return_pass_if_all_specs_pass()
{ {
GivenSpecifications(_pass1, _pass2, _pass3); GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetImportDecisions(_videoFiles, new Series(), false); var result = Subject.GetImportDecisions(_videoFiles, new Series());
result.Single().Approved.Should().BeTrue(); result.Single().Approved.Should().BeTrue();
} }
@ -141,7 +137,7 @@ public void should_have_same_number_of_rejections_as_specs_that_failed()
{ {
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3); GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
var result = Subject.GetImportDecisions(_videoFiles, new Series(), false); var result = Subject.GetImportDecisions(_videoFiles, new Series());
result.Single().Rejections.Should().HaveCount(3); result.Single().Rejections.Should().HaveCount(3);
} }
@ -151,7 +147,7 @@ public void should_not_blowup_the_process_due_to_failed_parse()
GivenSpecifications(_pass1); GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>())) .Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Boolean>()))
.Throws<TestException>(); .Throws<TestException>();
_videoFiles = new List<String> _videoFiles = new List<String>
@ -161,14 +157,12 @@ public void should_not_blowup_the_process_due_to_failed_parse()
"The.Office.S03E115.DVDRip.XviD-OSiTV" "The.Office.S03E115.DVDRip.XviD-OSiTV"
}; };
Mocker.GetMock<IMediaFileService>() GivenVideoFiles(_videoFiles);
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
.Returns(_videoFiles);
Subject.GetImportDecisions(_videoFiles, _series, false); Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()), Times.Exactly(_videoFiles.Count)); .Verify(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Boolean>()), Times.Exactly(_videoFiles.Count));
ExceptionVerification.ExpectedErrors(3); ExceptionVerification.ExpectedErrors(3);
} }
@ -179,7 +173,7 @@ public void should_use_file_quality_if_folder_quality_is_null()
GivenSpecifications(_pass1, _pass2, _pass3); GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single()); var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
var result = Subject.GetImportDecisions(_videoFiles, _series, false, null); var result = Subject.GetImportDecisions(_videoFiles, _series);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality); result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
} }
@ -190,7 +184,7 @@ public void should_use_file_quality_if_folder_quality_is_lower_than_file_quality
GivenSpecifications(_pass1, _pass2, _pass3); GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single()); var expectedQuality = QualityParser.ParseQuality(_videoFiles.Single());
var result = Subject.GetImportDecisions(_videoFiles, _series, false, new QualityModel(Quality.SDTV)); var result = Subject.GetImportDecisions(_videoFiles, _series, new ParsedEpisodeInfo{Quality = new QualityModel(Quality.SDTV)}, true);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality); result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
} }
@ -201,7 +195,7 @@ public void should_use_folder_quality_when_it_is_greater_than_file_quality()
GivenSpecifications(_pass1, _pass2, _pass3); GivenSpecifications(_pass1, _pass2, _pass3);
var expectedQuality = new QualityModel(Quality.Bluray1080p); var expectedQuality = new QualityModel(Quality.Bluray1080p);
var result = Subject.GetImportDecisions(_videoFiles, _series, false, expectedQuality); var result = Subject.GetImportDecisions(_videoFiles, _series, new ParsedEpisodeInfo { Quality = expectedQuality }, true);
result.Single().LocalEpisode.Quality.Should().Be(expectedQuality); result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
} }
@ -212,7 +206,7 @@ public void should_not_throw_if_episodes_are_not_found()
GivenSpecifications(_pass1); GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>())) .Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Boolean>()))
.Throws(new EpisodeNotFoundException("Episode not found")); .Throws(new EpisodeNotFoundException("Episode not found"));
_videoFiles = new List<String> _videoFiles = new List<String>
@ -222,14 +216,130 @@ public void should_not_throw_if_episodes_are_not_found()
"The.Office.S03E115.DVDRip.XviD-OSiTV" "The.Office.S03E115.DVDRip.XviD-OSiTV"
}; };
Mocker.GetMock<IMediaFileService>() GivenVideoFiles(_videoFiles);
.Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
.Returns(_videoFiles);
Subject.GetImportDecisions(_videoFiles, _series, false); Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()), Times.Exactly(_videoFiles.Count)); .Verify(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Boolean>()), Times.Exactly(_videoFiles.Count));
}
[Test]
public void should_not_use_folder_for_full_season()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01\S01E01.mkv".AsOsAgnostic(),
@"C:\Test\Unsorted\Series.Title.S01\S01E02.mkv".AsOsAgnostic(),
@"C:\Test\Unsorted\Series.Title.S01\S01E03.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01");
Subject.GetImportDecisions(_videoFiles, _series, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Exactly(3));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.Is<ParsedEpisodeInfo>(p => p != null), true), Times.Never());
}
[Test]
public void should_not_use_folder_when_it_contains_more_than_one_valid_video_file()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01E01\S01E01.mkv".AsOsAgnostic(),
@"C:\Test\Unsorted\Series.Title.S01E01\1x01.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
Subject.GetImportDecisions(_videoFiles, _series, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Exactly(2));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.Is<ParsedEpisodeInfo>(p => p != null), true), Times.Never());
}
[Test]
public void should_use_folder_when_only_one_video_file()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01E01\S01E01.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
Subject.GetImportDecisions(_videoFiles, _series, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), true), Times.Exactly(1));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Never());
}
[Test]
public void should_use_folder_when_only_one_video_file_and_a_sample()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01E01\S01E01.mkv".AsOsAgnostic(),
@"C:\Test\Unsorted\Series.Title.S01E01\S01E01.sample.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles.ToList());
Mocker.GetMock<IDetectSample>()
.Setup(s => s.IsSample(_series, It.IsAny<QualityModel>(), It.Is<string>(c => c.Contains("sample")), It.IsAny<long>(), It.IsAny<int>()))
.Returns(true);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
Subject.GetImportDecisions(_videoFiles, _series, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), true), Times.Exactly(2));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Never());
}
[Test]
public void should_not_use_folder_name_if_file_name_is_scene_name()
{
var videoFiles = new[]
{
@"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-LOL\Series.Title.S01E01.720p.HDTV-LOL.mkv".AsOsAgnostic()
};
GivenSpecifications(_pass1);
GivenVideoFiles(videoFiles);
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01.720p.HDTV-LOL");
Subject.GetImportDecisions(_videoFiles, _series, folderInfo, true);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), null, true), Times.Exactly(1));
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<string>(), It.IsAny<Series>(), It.Is<ParsedEpisodeInfo>(p => p != null), true), Times.Never());
} }
} }
} }

View File

@ -14,7 +14,7 @@
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
{ {
[TestFixture] [TestFixture]
public class SampleServiceFixture : CoreTest<SampleService> public class SampleServiceFixture : CoreTest<DetectSample>
{ {
private Series _series; private Series _series;
private LocalEpisode _localEpisode; private LocalEpisode _localEpisode;

View File

@ -64,7 +64,7 @@ public void should_reject_when_there_isnt_enough_disk_space()
GivenFileSize(100.Megabytes()); GivenFileSize(100.Megabytes());
GivenFreeSpace(80.Megabytes()); GivenFreeSpace(80.Megabytes());
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);
} }
@ -74,7 +74,7 @@ public void should_reject_when_there_isnt_enough_space_for_file_plus_100mb_paddi
GivenFileSize(100.Megabytes()); GivenFileSize(100.Megabytes());
GivenFreeSpace(150.Megabytes()); GivenFreeSpace(150.Megabytes());
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);
} }
@ -84,7 +84,7 @@ public void should_accept_when_there_is_enough_disk_space()
GivenFileSize(100.Megabytes()); GivenFileSize(100.Megabytes());
GivenFreeSpace(1.Gigabytes()); GivenFreeSpace(1.Gigabytes());
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -93,7 +93,7 @@ public void should_use_series_paths_parent_for_free_space_check()
GivenFileSize(100.Megabytes()); GivenFileSize(100.Megabytes());
GivenFreeSpace(1.Gigabytes()); GivenFreeSpace(1.Gigabytes());
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Verify(v => v.GetAvailableSpace(_rootFolder), Times.Once()); .Verify(v => v.GetAvailableSpace(_rootFolder), Times.Once());
@ -105,7 +105,7 @@ public void should_pass_if_free_space_is_null()
GivenFileSize(100.Megabytes()); GivenFileSize(100.Megabytes());
GivenFreeSpace(null); GivenFreeSpace(null);
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -117,7 +117,7 @@ public void should_pass_if_exception_is_thrown()
.Setup(s => s.GetAvailableSpace(It.IsAny<String>())) .Setup(s => s.GetAvailableSpace(It.IsAny<String>()))
.Throws(new TestException()); .Throws(new TestException());
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
ExceptionVerification.ExpectedErrors(1); ExceptionVerification.ExpectedErrors(1);
} }
@ -126,7 +126,7 @@ public void should_skip_check_for_files_under_series_folder()
{ {
_localEpisode.ExistingFile = true; _localEpisode.ExistingFile = true;
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Verify(s => s.GetAvailableSpace(It.IsAny<String>()), Times.Never()); .Verify(s => s.GetAvailableSpace(It.IsAny<String>()), Times.Never());
@ -141,7 +141,7 @@ public void should_return_true_if_free_space_is_null()
.Setup(s => s.GetAvailableSpace(It.IsAny<String>())) .Setup(s => s.GetAvailableSpace(It.IsAny<String>()))
.Returns(freeSpace); .Returns(freeSpace);
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -151,7 +151,7 @@ public void should_return_true_when_skip_check_is_enabled()
.Setup(s => s.SkipFreeSpaceCheckWhenImporting) .Setup(s => s.SkipFreeSpaceCheckWhenImporting)
.Returns(true); .Returns(true);
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
} }
} }

View File

@ -34,13 +34,13 @@ public void should_return_false_when_file_contains_the_full_season()
{ {
_localEpisode.ParsedEpisodeInfo.FullSeason = true; _localEpisode.ParsedEpisodeInfo.FullSeason = true;
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
} }
[Test] [Test]
public void should_return_true_when_file_does_not_contain_the_full_season() public void should_return_true_when_file_does_not_contain_the_full_season()
{ {
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
} }
} }

View File

@ -0,0 +1,84 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
{
[TestFixture]
public class MatchesFolderSpecificationFixture : CoreTest<MatchesFolderSpecification>
{
private LocalEpisode _localEpisode;
[SetUp]
public void Setup()
{
_localEpisode = Builder<LocalEpisode>.CreateNew()
.With(l => l.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E05.mkv".AsOsAgnostic())
.With(l => l.ParsedEpisodeInfo =
Builder<ParsedEpisodeInfo>.CreateNew()
.With(p => p.EpisodeNumbers = new[] {5})
.With(p => p.FullSeason = false)
.Build())
.Build();
}
[Test]
public void should_be_accepted_for_existing_file()
{
_localEpisode.ExistingFile = true;
Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_folder_name_is_not_parseable()
{
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title\S01E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
}
[Test]
public void should_should_be_accepted_for_full_season()
{
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01\S01E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_file_and_folder_have_the_same_episode()
{
_localEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_file_is_one_episode_in_folder()
{
_localEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 1 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01E02.720p.HDTV-Sonarr\S01E01.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
}
[Test]
public void should_be_rejected_if_file_and_folder_do_not_have_same_episode()
{
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E05.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
}
[Test]
public void should_be_rejected_if_file_and_folder_do_not_have_same_episodes()
{
_localEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 5, 6 };
_localEpisode.Path = @"C:\Test\Unsorted\Series.Title.S01E01E02.720p.HDTV-Sonarr\S01E05E06.mkv".AsOsAgnostic();
Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
}
}
}

View File

@ -42,7 +42,7 @@ public void Setup()
public void should_return_true_for_existing_file() public void should_return_true_for_existing_file()
{ {
_localEpisode.ExistingFile = true; _localEpisode.ExistingFile = true;
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
} }
} }

View File

@ -48,7 +48,7 @@ private void GivenLastWriteTimeUtc(DateTime time)
[Test] [Test]
public void should_return_true_if_not_in_working_folder() public void should_return_true_if_not_in_working_folder()
{ {
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -59,7 +59,7 @@ public void should_return_true_when_in_old_working_folder()
GivenInWorkingFolder(); GivenInWorkingFolder();
GivenLastWriteTimeUtc(DateTime.UtcNow.AddHours(-1)); GivenLastWriteTimeUtc(DateTime.UtcNow.AddHours(-1));
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -68,7 +68,7 @@ public void should_return_false_if_in_working_folder_and_last_write_time_was_rec
GivenInWorkingFolder(); GivenInWorkingFolder();
GivenLastWriteTimeUtc(DateTime.UtcNow); GivenLastWriteTimeUtc(DateTime.UtcNow);
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
} }
[Test] [Test]
@ -79,7 +79,7 @@ public void should_return_false_if_unopacking_on_linux()
GivenInWorkingFolder(); GivenInWorkingFolder();
GivenLastWriteTimeUtc(DateTime.UtcNow.AddDays(-5)); GivenLastWriteTimeUtc(DateTime.UtcNow.AddDays(-5));
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
} }
} }
} }

View File

@ -45,7 +45,7 @@ public void should_return_true_if_no_existing_episodeFile()
.Build() .Build()
.ToList(); .ToList();
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -58,7 +58,7 @@ public void should_return_true_if_no_existing_episodeFile_for_multi_episodes()
.Build() .Build()
.ToList(); .ToList();
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -75,7 +75,7 @@ public void should_return_true_if_upgrade_for_existing_episodeFile()
.Build() .Build()
.ToList(); .ToList();
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -92,7 +92,7 @@ public void should_return_true_if_upgrade_for_existing_episodeFile_for_multi_epi
.Build() .Build()
.ToList(); .ToList();
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeTrue();
} }
[Test] [Test]
@ -109,7 +109,7 @@ public void should_return_false_if_not_an_upgrade_for_existing_episodeFile()
.Build() .Build()
.ToList(); .ToList();
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
} }
[Test] [Test]
@ -126,7 +126,7 @@ public void should_return_false_if_not_an_upgrade_for_existing_episodeFile_for_m
.Build() .Build()
.ToList(); .ToList();
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
} }
[Test] [Test]
@ -150,7 +150,7 @@ public void should_return_false_if_not_an_upgrade_for_one_existing_episodeFile_f
.Build() .Build()
.ToList(); .ToList();
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse(); Subject.IsSatisfiedBy(_localEpisode).Accepted.Should().BeFalse();
} }
} }
} }

View File

@ -6,6 +6,7 @@
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.EpisodeImport;
@ -44,9 +45,9 @@ public void Setup()
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), "Rejected!")); _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), "Rejected!")); _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), "Rejected!")); _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
foreach (var episode in episodes) foreach (var episode in episodes)
{ {

View File

@ -227,6 +227,7 @@
<Compile Include="MediaFiles\EpisodeImport\SampleServiceFixture.cs" /> <Compile Include="MediaFiles\EpisodeImport\SampleServiceFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecificationFixture.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\MatchesFolderSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecificationFixture.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecificationFixture.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" />

View File

@ -29,6 +29,7 @@ public class CrapParserFixture : CoreTest
[TestCase("08bbc153931ce3ca5fcafe1b92d3297285feb061.mkv")] [TestCase("08bbc153931ce3ca5fcafe1b92d3297285feb061.mkv")]
[TestCase("185d86a343e39f3341e35c4dad3ff159")] [TestCase("185d86a343e39f3341e35c4dad3ff159")]
[TestCase("ah63jka93jf0jh26ahjas961.mkv")] [TestCase("ah63jka93jf0jh26ahjas961.mkv")]
[TestCase("qrdSD3rYzWb7cPdVIGSn4E7")]
public void should_not_parse_crap(string title) public void should_not_parse_crap(string title)
{ {
Parser.Parser.ParseTitle(title).Should().BeNull(); Parser.Parser.ParseTitle(title).Should().BeNull();
@ -82,5 +83,11 @@ public void should_not_parse_random(int length)
success.Should().Be(repetitions); success.Should().Be(repetitions);
} }
[TestCase("thebiggestloser1618finale")]
public void should_not_parse_file_name_without_proper_spacing(string fileName)
{
Parser.Parser.ParseTitle(fileName).Should().BeNull();
}
} }
} }

View File

@ -5,10 +5,10 @@
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MetadataSource; using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.TvTests namespace NzbDrone.Core.Test.TvTests
{ {

View File

@ -4,11 +4,11 @@
using FizzWare.NBuilder; using FizzWare.NBuilder;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MetadataSource; using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Commands; using NzbDrone.Core.Tv.Commands;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.TvTests namespace NzbDrone.Core.Test.TvTests
{ {

View File

@ -103,7 +103,7 @@ public void Scan(Series series)
_logger.Trace("Finished getting episode files for: {0} [{1}]", series, videoFilesStopwatch.Elapsed); _logger.Trace("Finished getting episode files for: {0} [{1}]", series, videoFilesStopwatch.Elapsed);
var decisionsStopwatch = Stopwatch.StartNew(); var decisionsStopwatch = Stopwatch.StartNew();
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, series, false); var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, series);
decisionsStopwatch.Stop(); decisionsStopwatch.Stop();
_logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed); _logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed);

View File

@ -4,6 +4,7 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -26,7 +27,7 @@ public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
private readonly IMakeImportDecision _importDecisionMaker; private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedEpisodes _importApprovedEpisodes; private readonly IImportApprovedEpisodes _importApprovedEpisodes;
private readonly ISampleService _sampleService; private readonly IDetectSample _detectSample;
private readonly Logger _logger; private readonly Logger _logger;
public DownloadedEpisodesImportService(IDiskProvider diskProvider, public DownloadedEpisodesImportService(IDiskProvider diskProvider,
@ -35,7 +36,7 @@ public DownloadedEpisodesImportService(IDiskProvider diskProvider,
IParsingService parsingService, IParsingService parsingService,
IMakeImportDecision importDecisionMaker, IMakeImportDecision importDecisionMaker,
IImportApprovedEpisodes importApprovedEpisodes, IImportApprovedEpisodes importApprovedEpisodes,
ISampleService sampleService, IDetectSample detectSample,
Logger logger) Logger logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
@ -44,7 +45,7 @@ public DownloadedEpisodesImportService(IDiskProvider diskProvider,
_parsingService = parsingService; _parsingService = parsingService;
_importDecisionMaker = importDecisionMaker; _importDecisionMaker = importDecisionMaker;
_importApprovedEpisodes = importApprovedEpisodes; _importApprovedEpisodes = importApprovedEpisodes;
_sampleService = sampleService; _detectSample = detectSample;
_logger = logger; _logger = logger;
} }
@ -115,9 +116,12 @@ private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series ser
} }
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var quality = QualityParser.ParseQuality(cleanedUpName); var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
_logger.Debug("{0} folder quality: {1}", cleanedUpName, quality); if (folderInfo != null)
{
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
}
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
@ -135,7 +139,7 @@ private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series ser
} }
} }
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, true, quality); var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true);
var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem); var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && importResults.Any() && ShouldDeleteFolder(directoryInfo, series)) if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && importResults.Any() && ShouldDeleteFolder(directoryInfo, series))
@ -177,7 +181,9 @@ private List<ImportResult> ProcessFile(FileInfo fileInfo, Series series, Downloa
} }
} }
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, series, true); var folderInfo = Parser.Parser.ParseTitle(fileInfo.DirectoryName);
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, series, folderInfo, true);
return _importApprovedEpisodes.Import(decisions, true, downloadClientItem); return _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
} }
@ -207,7 +213,7 @@ private bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series)
var size = _diskProvider.GetFileSize(videoFile); var size = _diskProvider.GetFileSize(videoFile);
var quality = QualityParser.ParseQuality(videoFile); var quality = QualityParser.ParseQuality(videoFile);
if (!_sampleService.IsSample(series, quality, videoFile, size, if (!_detectSample.IsSample(series, quality, videoFile, size,
episodeParseResult.SeasonNumber)) episodeParseResult.SeasonNumber))
{ {
_logger.Warn("Non-sample file detected: [{0}]", videoFile); _logger.Warn("Non-sample file detected: [{0}]", videoFile);
@ -227,14 +233,14 @@ private bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series)
private ImportResult FileIsLockedResult(string videoFile) private ImportResult FileIsLockedResult(string videoFile)
{ {
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, "Locked file, try again later"), "Locked file, try again later"); return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
} }
private ImportResult UnknownSeriesResult(string message, string videoFile = null) private ImportResult UnknownSeriesResult(string message, string videoFile = null)
{ {
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile }; var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
return new ImportResult(new ImportDecision(localEpisode, "Unknown Series"), message); return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Series")), message);
} }
} }
} }

View File

@ -8,19 +8,19 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.EpisodeImport
{ {
public interface ISampleService public interface IDetectSample
{ {
bool IsSample(Series series, QualityModel quality, string path, long size, int seasonNumber); bool IsSample(Series series, QualityModel quality, string path, long size, int seasonNumber);
} }
public class SampleService : ISampleService public class DetectSample : IDetectSample
{ {
private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly Logger _logger; private readonly Logger _logger;
private static List<Quality> _largeSampleSizeQualities = new List<Quality> { Quality.HDTV1080p, Quality.WEBDL1080p, Quality.Bluray1080p }; private static List<Quality> _largeSampleSizeQualities = new List<Quality> { Quality.HDTV1080p, Quality.WEBDL1080p, Quality.Bluray1080p };
public SampleService(IVideoFileInfoReader videoFileInfoReader, Logger logger) public DetectSample(IVideoFileInfoReader videoFileInfoReader, Logger logger)
{ {
_videoFileInfoReader = videoFileInfoReader; _videoFileInfoReader = videoFileInfoReader;
_logger = logger; _logger = logger;

View File

@ -3,8 +3,8 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.EpisodeImport
{ {
public interface IImportDecisionEngineSpecification : IRejectWithReason public interface IImportDecisionEngineSpecification
{ {
bool IsSatisfiedBy(LocalEpisode localEpisode); Decision IsSatisfiedBy(LocalEpisode localEpisode);
} }
} }

View File

@ -120,7 +120,7 @@ public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownloa
//Adding all the rejected decisions //Adding all the rejected decisions
importResults.AddRange(decisions.Where(c => !c.Approved) importResults.AddRange(decisions.Where(c => !c.Approved)
.Select(d => new ImportResult(d, d.Rejections.ToArray()))); .Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
return importResults; return importResults;
} }

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.EpisodeImport
@ -8,7 +9,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public class ImportDecision public class ImportDecision
{ {
public LocalEpisode LocalEpisode { get; private set; } public LocalEpisode LocalEpisode { get; private set; }
public IEnumerable<string> Rejections { get; private set; } public IEnumerable<Rejection> Rejections { get; private set; }
public bool Approved public bool Approved
{ {
@ -18,7 +19,7 @@ public bool Approved
} }
} }
public ImportDecision(LocalEpisode localEpisode, params string[] rejections) public ImportDecision(LocalEpisode localEpisode, params Rejection[] rejections)
{ {
LocalEpisode = localEpisode; LocalEpisode = localEpisode;
Rejections = rejections.ToList(); Rejections = rejections.ToList();

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -15,23 +17,26 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
{ {
public interface IMakeImportDecision public interface IMakeImportDecision
{ {
List<ImportDecision> GetImportDecisions(List<String> videoFiles, Series series, bool sceneSource, QualityModel quality = null); List<ImportDecision> GetImportDecisions(List<String> videoFiles, Series series);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
} }
public class ImportDecisionMaker : IMakeImportDecision public class ImportDecisionMaker : IMakeImportDecision
{ {
private readonly IEnumerable<IRejectWithReason> _specifications; private readonly IEnumerable<IImportDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IDetectSample _detectSample;
private readonly Logger _logger; private readonly Logger _logger;
public ImportDecisionMaker(IEnumerable<IRejectWithReason> specifications, public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification> specifications,
IParsingService parsingService, IParsingService parsingService,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IVideoFileInfoReader videoFileInfoReader, IVideoFileInfoReader videoFileInfoReader,
IDetectSample detectSample,
Logger logger) Logger logger)
{ {
_specifications = specifications; _specifications = specifications;
@ -39,98 +44,96 @@ public ImportDecisionMaker(IEnumerable<IRejectWithReason> specifications,
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_videoFileInfoReader = videoFileInfoReader; _videoFileInfoReader = videoFileInfoReader;
_detectSample = detectSample;
_logger = logger; _logger = logger;
} }
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, bool sceneSource, QualityModel quality = null) public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series)
{
return GetImportDecisions(videoFiles, series, null, false);
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource)
{ {
var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), series); var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), series);
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count()); _logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
return GetDecisions(newFiles, series, sceneSource, quality).ToList(); var shouldUseFolderName = ShouldUseFolderName(videoFiles, series, folderInfo);
var decisions = new List<ImportDecision>();
foreach (var file in newFiles)
{
decisions.AddIfNotNull(GetDecision(file, series, folderInfo, sceneSource, shouldUseFolderName));
}
return decisions;
} }
private IEnumerable<ImportDecision> GetDecisions(IEnumerable<String> videoFiles, Series series, bool sceneSource, QualityModel quality = null) private ImportDecision GetDecision(string file, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName)
{ {
foreach (var file in videoFiles) ImportDecision decision = null;
try
{ {
ImportDecision decision = null; var localEpisode = _parsingService.GetLocalEpisode(file, series, shouldUseFolderName ? folderInfo : null, sceneSource);
try if (localEpisode != null)
{ {
var localEpisode = _parsingService.GetLocalEpisode(file, series, sceneSource); localEpisode.Quality = GetQuality(folderInfo, localEpisode.Quality, series);
localEpisode.Size = _diskProvider.GetFileSize(file);
if (localEpisode != null) _logger.Debug("Size: {0}", localEpisode.Size);
//TODO: make it so media info doesn't ruin the import process of a new series
if (sceneSource)
{ {
if (quality != null && localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
new QualityModelComparer(localEpisode.Series.Profile).Compare(quality,
localEpisode.Quality) > 0)
{
_logger.Debug("Using quality from folder: {0}", quality);
localEpisode.Quality = quality;
}
localEpisode.Size = _diskProvider.GetFileSize(file);
_logger.Debug("Size: {0}", localEpisode.Size);
//TODO: make it so media info doesn't ruin the import process of a new series
if (sceneSource)
{
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
}
decision = GetDecision(localEpisode);
} }
else decision = GetDecision(localEpisode);
{
localEpisode = new LocalEpisode();
localEpisode.Path = file;
decision = new ImportDecision(localEpisode, "Unable to parse file");
}
} }
catch (EpisodeNotFoundException e)
else
{ {
var localEpisode = new LocalEpisode(); localEpisode = new LocalEpisode();
localEpisode.Path = file; localEpisode.Path = file;
decision = new ImportDecision(localEpisode, e.Message); decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
}
catch (Exception e)
{
_logger.ErrorException("Couldn't import file. " + file, e);
}
if (decision != null)
{
yield return decision;
} }
} }
catch (EpisodeNotFoundException e)
{
var localEpisode = new LocalEpisode();
localEpisode.Path = file;
decision = new ImportDecision(localEpisode, new Rejection(e.Message));
}
catch (Exception e)
{
_logger.ErrorException("Couldn't import file. " + file, e);
}
return decision;
} }
private ImportDecision GetDecision(LocalEpisode localEpisode) private ImportDecision GetDecision(LocalEpisode localEpisode)
{ {
var reasons = _specifications.Select(c => EvaluateSpec(c, localEpisode)) var reasons = _specifications.Select(c => EvaluateSpec(c, localEpisode))
.Where(c => !string.IsNullOrWhiteSpace(c)); .Where(c => c != null);
return new ImportDecision(localEpisode, reasons.ToArray()); return new ImportDecision(localEpisode, reasons.ToArray());
} }
private string EvaluateSpec(IRejectWithReason spec, LocalEpisode localEpisode) private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode)
{ {
try try
{ {
if (string.IsNullOrWhiteSpace(spec.RejectionReason)) var result = spec.IsSatisfiedBy(localEpisode);
{
throw new InvalidOperationException("[Need Rejection Text]");
}
var generalSpecification = spec as IImportDecisionEngineSpecification; if (!result.Accepted)
if (generalSpecification != null && !generalSpecification.IsSatisfiedBy(localEpisode))
{ {
return spec.RejectionReason; return new Rejection(result.Reason);
} }
} }
catch (Exception e) catch (Exception e)
@ -138,10 +141,55 @@ private string EvaluateSpec(IRejectWithReason spec, LocalEpisode localEpisode)
//e.Data.Add("report", remoteEpisode.Report.ToJson()); //e.Data.Add("report", remoteEpisode.Report.ToJson());
//e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson()); //e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.ErrorException("Couldn't evaluate decision on " + localEpisode.Path, e); _logger.ErrorException("Couldn't evaluate decision on " + localEpisode.Path, e);
return string.Format("{0}: {1}", spec.GetType().Name, e.Message); return new Rejection(String.Format("{0}: {1}", spec.GetType().Name, e.Message));
} }
return null; return null;
} }
private bool ShouldUseFolderName(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo)
{
if (folderInfo == null)
{
return false;
}
if (folderInfo.FullSeason)
{
return false;
}
return videoFiles.Count(file =>
{
var size = _diskProvider.GetFileSize(file);
var fileQuality = QualityParser.ParseQuality(file);
var sample = _detectSample.IsSample(series, GetQuality(folderInfo, fileQuality, series), file, size, folderInfo.SeasonNumber);
if (sample)
{
return false;
}
if (SceneChecker.IsSceneTitle(Path.GetFileName(file)))
{
return false;
}
return true;
}) == 1;
}
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
{
if (folderInfo != null &&
new QualityModelComparer(series.Profile).Compare(folderInfo.Quality,
fileQuality) > 0)
{
_logger.Debug("Using quality from folder: {0}", folderInfo.Quality);
return folderInfo.Quality;
}
return fileQuality;
}
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
@ -8,7 +7,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public class ImportResult public class ImportResult
{ {
public ImportDecision ImportDecision { get; private set; } public ImportDecision ImportDecision { get; private set; }
public List<String> Errors { get; private set; } public List<string> Errors { get; private set; }
public ImportResultType Result public ImportResultType Result
{ {
@ -28,7 +27,7 @@ public ImportResultType Result
} }
} }
public ImportResult(ImportDecision importDecision, params String[] errors) public ImportResult(ImportDecision importDecision, params string[] errors)
{ {
Ensure.That(importDecision, () => importDecision).IsNotNull(); Ensure.That(importDecision, () => importDecision).IsNotNull();

View File

@ -3,6 +3,7 @@
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
@ -20,14 +21,12 @@ public FreeSpaceSpecification(IDiskProvider diskProvider, IConfigService configS
_logger = logger; _logger = logger;
} }
public string RejectionReason { get { return "Not enough free space"; } } public Decision IsSatisfiedBy(LocalEpisode localEpisode)
public bool IsSatisfiedBy(LocalEpisode localEpisode)
{ {
if (_configService.SkipFreeSpaceCheckWhenImporting) if (_configService.SkipFreeSpaceCheckWhenImporting)
{ {
_logger.Debug("Skipping free space check when importing"); _logger.Debug("Skipping free space check when importing");
return true; return Decision.Accept();
} }
try try
@ -35,7 +34,7 @@ public bool IsSatisfiedBy(LocalEpisode localEpisode)
if (localEpisode.ExistingFile) if (localEpisode.ExistingFile)
{ {
_logger.Debug("Skipping free space check for existing episode"); _logger.Debug("Skipping free space check for existing episode");
return true; return Decision.Accept();
} }
var path = Directory.GetParent(localEpisode.Series.Path); var path = Directory.GetParent(localEpisode.Series.Path);
@ -44,13 +43,13 @@ public bool IsSatisfiedBy(LocalEpisode localEpisode)
if (!freeSpace.HasValue) if (!freeSpace.HasValue)
{ {
_logger.Debug("Free space check returned an invalid result for: {0}", path); _logger.Debug("Free space check returned an invalid result for: {0}", path);
return true; return Decision.Accept();
} }
if (freeSpace < localEpisode.Size + 100.Megabytes()) if (freeSpace < localEpisode.Size + 100.Megabytes())
{ {
_logger.Warn("Not enough free space ({0}) to import: {1} ({2})", freeSpace, localEpisode, localEpisode.Size); _logger.Warn("Not enough free space ({0}) to import: {1} ({2})", freeSpace, localEpisode, localEpisode.Size);
return false; return Decision.Reject("Not enough free space");
} }
} }
catch (DirectoryNotFoundException ex) catch (DirectoryNotFoundException ex)
@ -62,7 +61,7 @@ public bool IsSatisfiedBy(LocalEpisode localEpisode)
_logger.ErrorException("Unable to check free disk space while importing: " + localEpisode.Path, ex); _logger.ErrorException("Unable to check free disk space while importing: " + localEpisode.Path, ex);
} }
return true; return Decision.Accept();
} }
} }
} }

View File

@ -1,4 +1,5 @@
using NLog; using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
@ -12,17 +13,15 @@ public FullSeasonSpecification(Logger logger)
_logger = logger; _logger = logger;
} }
public string RejectionReason { get { return "Full season file"; } } public Decision IsSatisfiedBy(LocalEpisode localEpisode)
public bool IsSatisfiedBy(LocalEpisode localEpisode)
{ {
if (localEpisode.ParsedEpisodeInfo.FullSeason) if (localEpisode.ParsedEpisodeInfo.FullSeason)
{ {
_logger.Debug("Single episode file detected as containing all episodes in the season"); _logger.Debug("Single episode file detected as containing all episodes in the season");
return false; return Decision.Reject("Single episode file contains all episodes in seasons");
} }
return true; return Decision.Accept();
} }
} }
} }

View File

@ -0,0 +1,54 @@
using System;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
public class MatchesFolderSpecification : IImportDecisionEngineSpecification
{
private readonly Logger _logger;
public MatchesFolderSpecification(Logger logger)
{
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{
if (localEpisode.ExistingFile)
{
return Decision.Accept();
}
var folderInfo = Parser.Parser.ParseTitle(new FileInfo(localEpisode.Path).DirectoryName);
if (folderInfo == null)
{
return Decision.Accept();
}
if (folderInfo.FullSeason)
{
return Decision.Accept();
}
var unexpected = localEpisode.ParsedEpisodeInfo.EpisodeNumbers.Where(f => !folderInfo.EpisodeNumbers.Contains(f)).ToList();
if (unexpected.Any())
{
_logger.Debug("Unexpected episode number(s) in file: {0}", unexpected);
if (unexpected.Count == 1)
{
return Decision.Reject("Episode Number {0} was unexpected", unexpected.First());
}
return Decision.Reject("Episode Numbers {0} were unexpected", String.Join(", ", unexpected));
}
return Decision.Accept();
}
}
}

View File

@ -1,35 +1,41 @@
using NLog; using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{ {
public class NotSampleSpecification : IImportDecisionEngineSpecification public class NotSampleSpecification : IImportDecisionEngineSpecification
{ {
private readonly ISampleService _sampleService; private readonly IDetectSample _detectSample;
private readonly Logger _logger; private readonly Logger _logger;
public NotSampleSpecification(ISampleService sampleService, public NotSampleSpecification(IDetectSample detectSample,
Logger logger) Logger logger)
{ {
_sampleService = sampleService; _detectSample = detectSample;
_logger = logger; _logger = logger;
} }
public string RejectionReason { get { return "Sample"; } } public Decision IsSatisfiedBy(LocalEpisode localEpisode)
public bool IsSatisfiedBy(LocalEpisode localEpisode)
{ {
if (localEpisode.ExistingFile) if (localEpisode.ExistingFile)
{ {
_logger.Debug("Existing file, skipping sample check"); _logger.Debug("Existing file, skipping sample check");
return true; return Decision.Accept();
} }
return !_sampleService.IsSample(localEpisode.Series, var sample = _detectSample.IsSample(localEpisode.Series,
localEpisode.Quality, localEpisode.Quality,
localEpisode.Path, localEpisode.Path,
localEpisode.Size, localEpisode.Size,
localEpisode.SeasonNumber); localEpisode.SeasonNumber);
if (sample)
{
return Decision.Reject("Sample");
}
return Decision.Accept();
} }
} }
} }

View File

@ -4,6 +4,7 @@
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
@ -21,14 +22,12 @@ public NotUnpackingSpecification(IDiskProvider diskProvider, IConfigService conf
_logger = logger; _logger = logger;
} }
public string RejectionReason { get { return "File is still being unpacked"; } } public Decision IsSatisfiedBy(LocalEpisode localEpisode)
public bool IsSatisfiedBy(LocalEpisode localEpisode)
{ {
if (localEpisode.ExistingFile) if (localEpisode.ExistingFile)
{ {
_logger.Debug("{0} is in series folder, unpacking check", localEpisode.Path); _logger.Debug("{0} is in series folder, unpacking check", localEpisode.Path);
return true; return Decision.Accept();
} }
foreach (var workingFolder in _configService.DownloadClientWorkingFolders.Split('|')) foreach (var workingFolder in _configService.DownloadClientWorkingFolders.Split('|'))
@ -41,13 +40,13 @@ public bool IsSatisfiedBy(LocalEpisode localEpisode)
if (OsInfo.IsNotWindows) if (OsInfo.IsNotWindows)
{ {
_logger.Debug("{0} is still being unpacked", localEpisode.Path); _logger.Debug("{0} is still being unpacked", localEpisode.Path);
return false; return Decision.Reject("File is still being unpacked");
} }
if (_diskProvider.FileGetLastWrite(localEpisode.Path) > DateTime.UtcNow.AddMinutes(-5)) if (_diskProvider.FileGetLastWrite(localEpisode.Path) > DateTime.UtcNow.AddMinutes(-5))
{ {
_logger.Debug("{0} appears to be unpacking still", localEpisode.Path); _logger.Debug("{0} appears to be unpacking still", localEpisode.Path);
return false; return Decision.Reject("File is still being unpacked");
} }
} }
@ -55,7 +54,7 @@ public bool IsSatisfiedBy(LocalEpisode localEpisode)
} }
} }
return true; return Decision.Accept();
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
@ -14,18 +15,16 @@ public UpgradeSpecification(Logger logger)
_logger = logger; _logger = logger;
} }
public string RejectionReason { get { return "Not an upgrade for existing episode file(s)"; } } public Decision IsSatisfiedBy(LocalEpisode localEpisode)
public bool IsSatisfiedBy(LocalEpisode localEpisode)
{ {
var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile); var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile);
if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && qualityComparer.Compare(e.EpisodeFile.Value.Quality, localEpisode.Quality) > 0)) if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && qualityComparer.Compare(e.EpisodeFile.Value.Quality, localEpisode.Quality) > 0))
{ {
_logger.Debug("This file isn't an upgrade for all episodes. Skipping {0}", localEpisode.Path); _logger.Debug("This file isn't an upgrade for all episodes. Skipping {0}", localEpisode.Path);
return false; return Decision.Reject("Not an upgrade for existing episode file(s)");
} }
return true; return Decision.Accept();
} }
} }
} }

View File

@ -61,7 +61,7 @@ public void Handle(SeriesScannedEvent message)
{ {
try try
{ {
var localEpisode = _parsingService.GetLocalEpisode(possibleMetadataFile, message.Series, false); var localEpisode = _parsingService.GetLocalEpisode(possibleMetadataFile, message.Series);
if (localEpisode == null) if (localEpisode == null)
{ {

View File

@ -561,8 +561,9 @@
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" /> <Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportResultType.cs" /> <Compile Include="MediaFiles\EpisodeImport\ImportResultType.cs" />
<Compile Include="MediaFiles\EpisodeImport\ManualImportService.cs" /> <Compile Include="MediaFiles\EpisodeImport\ManualImportService.cs" />
<Compile Include="MediaFiles\EpisodeImport\SampleService.cs" /> <Compile Include="MediaFiles\EpisodeImport\DetectSample.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecification.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\MatchesFolderSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecification.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecification.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecification.cs" />

View File

@ -104,7 +104,7 @@ public static class Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes with single digit episode number (S01E1, S01E5E6, etc) //Episodes with single digit episode number (S01E1, S01E5E6, etc)
new Regex(@"^(?<title>.*?)(?:\W?S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]){1,2}(?<episode>\d{1}))+)+(\W+|_|$)(?!\\)", new Regex(@"^(?<title>.*?)(?:(?:_|-|\s|\.)S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]){1,2}(?<episode>\d{1}))+)+(\W+|_|$)(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Anime - Title Absolute Episode Number (e66) //Anime - Title Absolute Episode Number (e66)

View File

@ -13,7 +13,8 @@ namespace NzbDrone.Core.Parser
{ {
public interface IParsingService public interface IParsingService
{ {
LocalEpisode GetLocalEpisode(string filename, Series series, bool sceneSource); LocalEpisode GetLocalEpisode(string filename, Series series);
LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
Series GetSeries(string title); Series GetSeries(string title);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId = 0, SearchCriteriaBase searchCriteria = null); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId = 0, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 seriesId, IEnumerable<Int32> episodeIds); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 seriesId, IEnumerable<Int32> episodeIds);
@ -39,9 +40,25 @@ public ParsingService(IEpisodeService episodeService,
_logger = logger; _logger = logger;
} }
public LocalEpisode GetLocalEpisode(string filename, Series series, bool sceneSource) public LocalEpisode GetLocalEpisode(string filename, Series series)
{ {
var parsedEpisodeInfo = Parser.ParsePath(filename); return GetLocalEpisode(filename, series, null, false);
}
public LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource)
{
ParsedEpisodeInfo parsedEpisodeInfo;
if (folderInfo != null)
{
parsedEpisodeInfo = folderInfo.JsonClone();
parsedEpisodeInfo.Quality = QualityParser.ParseQuality(Path.GetFileName(filename));
}
else
{
parsedEpisodeInfo = Parser.ParsePath(filename);
}
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode) if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
{ {

View File

@ -1,4 +1,6 @@
namespace NzbDrone.Core.Parser using System;
namespace NzbDrone.Core.Parser
{ {
public static class SceneChecker public static class SceneChecker
{ {
@ -10,10 +12,14 @@ public static bool IsSceneTitle(string title)
if (title.Contains(" ")) return false; if (title.Contains(" ")) return false;
var parsedTitle = Parser.ParseTitle(title); var parsedTitle = Parser.ParseTitle(title);
if (parsedTitle == null
|| parsedTitle.ReleaseGroup == null if (parsedTitle == null ||
|| parsedTitle.Quality.Quality == Qualities.Quality.Unknown parsedTitle.ReleaseGroup == null ||
|| string.IsNullOrWhiteSpace(parsedTitle.SeriesTitle)) return false; parsedTitle.Quality.Quality == Qualities.Quality.Unknown ||
String.IsNullOrWhiteSpace(parsedTitle.SeriesTitle))
{
return false;
}
return true; return true;
} }

View File

@ -91,7 +91,6 @@
<Compile Include="LoggingTest.cs" /> <Compile Include="LoggingTest.cs" />
<Compile Include="MockerExtensions.cs" /> <Compile Include="MockerExtensions.cs" />
<Compile Include="NzbDroneRunner.cs" /> <Compile Include="NzbDroneRunner.cs" />
<Compile Include="ObjectExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReflectionExtensions.cs" /> <Compile Include="ReflectionExtensions.cs" />
<Compile Include="StringExtensions.cs" /> <Compile Include="StringExtensions.cs" />