mirror of https://github.com/lidarr/Lidarr
Episode import uses specs and moves before import now
This commit is contained in:
parent
9ed5a06504
commit
aeb8ee06f6
|
@ -199,7 +199,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_unknown_series_rejection_if_series_is_unknow()
|
public void should_return_unknown_series_rejection_if_series_is_unknown()
|
||||||
{
|
{
|
||||||
GivenSpecifications(_pass1, _pass2, _pass3);
|
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
|
||||||
.Setup(e => e.BuildFilePath(It.IsAny<Series>(), fakeEpisode.First().SeasonNumber, filename, ".avi"))
|
.Setup(e => e.BuildFilePath(It.IsAny<Series>(), fakeEpisode.First().SeasonNumber, filename, ".avi"))
|
||||||
.Returns(fi);
|
.Returns(fi);
|
||||||
|
|
||||||
var result = Subject.MoveEpisodeFile(file, false);
|
var result = Subject.MoveEpisodeFile(file);
|
||||||
|
|
||||||
result.Should().BeNull();
|
result.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
|
||||||
.Setup(s => s.FileExists(currentFilename))
|
.Setup(s => s.FileExists(currentFilename))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
var result = Subject.MoveEpisodeFile(file, true);
|
var result = Subject.MoveEpisodeFile(file);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ namespace NzbDrone.Core.Test.MediaFileTests
|
||||||
.Setup(e => e.BuildFilePath(It.IsAny<Series>(), fakeEpisode.First().SeasonNumber, filename, ".mkv"))
|
.Setup(e => e.BuildFilePath(It.IsAny<Series>(), fakeEpisode.First().SeasonNumber, filename, ".mkv"))
|
||||||
.Returns(fi);
|
.Returns(fi);
|
||||||
|
|
||||||
var result = Subject.MoveEpisodeFile(file, true);
|
var result = Subject.MoveEpisodeFile(file);
|
||||||
|
|
||||||
result.Should().BeNull();
|
result.Should().BeNull();
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ImportDecisionMakerFixture : CoreTest<ImportDecisionMaker>
|
||||||
|
{
|
||||||
|
private List<String> _videoFiles;
|
||||||
|
private LocalEpisode _localEpisode;
|
||||||
|
private Series _series;
|
||||||
|
|
||||||
|
private Mock<IImportDecisionEngineSpecification> _pass1;
|
||||||
|
private Mock<IImportDecisionEngineSpecification> _pass2;
|
||||||
|
private Mock<IImportDecisionEngineSpecification> _pass3;
|
||||||
|
|
||||||
|
private Mock<IImportDecisionEngineSpecification> _fail1;
|
||||||
|
private Mock<IImportDecisionEngineSpecification> _fail2;
|
||||||
|
private Mock<IImportDecisionEngineSpecification> _fail3;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_pass1 = new Mock<IImportDecisionEngineSpecification>();
|
||||||
|
_pass2 = new Mock<IImportDecisionEngineSpecification>();
|
||||||
|
_pass3 = new Mock<IImportDecisionEngineSpecification>();
|
||||||
|
|
||||||
|
_fail1 = new Mock<IImportDecisionEngineSpecification>();
|
||||||
|
_fail2 = new Mock<IImportDecisionEngineSpecification>();
|
||||||
|
_fail3 = new Mock<IImportDecisionEngineSpecification>();
|
||||||
|
|
||||||
|
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(true);
|
||||||
|
_pass1.Setup(c => c.RejectionReason).Returns("_pass1");
|
||||||
|
|
||||||
|
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>())).Returns(true);
|
||||||
|
_pass2.Setup(c => c.RejectionReason).Returns("_pass2");
|
||||||
|
|
||||||
|
_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> { "The.Office.S03E115.DVDRip.XviD-OSiTV" };
|
||||||
|
_series = new Series();
|
||||||
|
_localEpisode = new LocalEpisode { Series = _series, Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi" };
|
||||||
|
|
||||||
|
Mocker.GetMock<IParsingService>().Setup(c => c.GetEpisodes(It.IsAny<String>(), It.IsAny<Series>()))
|
||||||
|
.Returns(_localEpisode);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenSpecifications(params Mock<IImportDecisionEngineSpecification>[] mocks)
|
||||||
|
{
|
||||||
|
Mocker.SetConstant<IEnumerable<IRejectWithReason>>(mocks.Select(c => c.Object));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_call_all_specifications()
|
||||||
|
{
|
||||||
|
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||||
|
|
||||||
|
Subject.GetImportDecisions(_videoFiles, new Series());
|
||||||
|
|
||||||
|
_fail1.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once());
|
||||||
|
_fail2.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once());
|
||||||
|
_fail3.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once());
|
||||||
|
_pass1.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once());
|
||||||
|
_pass2.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once());
|
||||||
|
_pass3.Verify(c => c.IsSatisfiedBy(_localEpisode), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_rejected_if_single_specs_fail()
|
||||||
|
{
|
||||||
|
GivenSpecifications(_fail1);
|
||||||
|
|
||||||
|
var result = Subject.GetImportDecisions(_videoFiles, new Series());
|
||||||
|
|
||||||
|
result.Single().Approved.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_rejected_if_one_of_specs_fail()
|
||||||
|
{
|
||||||
|
GivenSpecifications(_pass1, _fail1, _pass2, _pass3);
|
||||||
|
|
||||||
|
var result = Subject.GetImportDecisions(_videoFiles, new Series());
|
||||||
|
|
||||||
|
result.Single().Approved.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_pass_if_all_specs_pass()
|
||||||
|
{
|
||||||
|
GivenSpecifications(_pass1, _pass2, _pass3);
|
||||||
|
|
||||||
|
var result = Subject.GetImportDecisions(_videoFiles, new Series());
|
||||||
|
|
||||||
|
result.Single().Approved.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_have_same_number_of_rejections_as_specs_that_failed()
|
||||||
|
{
|
||||||
|
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
|
||||||
|
|
||||||
|
var result = Subject.GetImportDecisions(_videoFiles, new Series());
|
||||||
|
result.Single().Rejections.Should().HaveCount(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void failed_parse_shouldnt_blowup_the_process()
|
||||||
|
{
|
||||||
|
GivenSpecifications(_pass1);
|
||||||
|
|
||||||
|
Mocker.GetMock<IParsingService>().Setup(c => c.GetEpisodes(It.IsAny<String>(), It.IsAny<Series>()))
|
||||||
|
.Throws<TestException>();
|
||||||
|
|
||||||
|
_videoFiles = new List<String>
|
||||||
|
{
|
||||||
|
"The.Office.S03E115.DVDRip.XviD-OSiTV",
|
||||||
|
"The.Office.S03E115.DVDRip.XviD-OSiTV",
|
||||||
|
"The.Office.S03E115.DVDRip.XviD-OSiTV"
|
||||||
|
};
|
||||||
|
|
||||||
|
Subject.GetImportDecisions(_videoFiles, new Series());
|
||||||
|
|
||||||
|
Mocker.GetMock<IParsingService>()
|
||||||
|
.Verify(c => c.GetEpisodes(It.IsAny<String>(), It.IsAny<Series>()), Times.Exactly(_videoFiles.Count));
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedErrors(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class NotAlreadyImportedSpecificationFixture : CoreTest<NotAlreadyImportedSpecification>
|
||||||
|
{
|
||||||
|
private LocalEpisode _localEpisode;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_localEpisode = new LocalEpisode
|
||||||
|
{
|
||||||
|
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_path_is_already_in_episodeFiles()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Setup(s => s.Exists(_localEpisode.Path))
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_new_file()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Setup(s => s.Exists(_localEpisode.Path))
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Providers;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class NotSampleSpecificationFixture : CoreTest<NotSampleSpecification>
|
||||||
|
{
|
||||||
|
private Series _series;
|
||||||
|
private LocalEpisode _localEpisode;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_series = Builder<Series>.CreateNew()
|
||||||
|
.With(s => s.SeriesType = SeriesTypes.Standard)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var episodes = Builder<Episode>.CreateListOfSize(1)
|
||||||
|
.All()
|
||||||
|
.With(e => e.SeasonNumber = 1)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_localEpisode = new LocalEpisode
|
||||||
|
{
|
||||||
|
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
|
||||||
|
Episodes = episodes,
|
||||||
|
Series = _series
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithDailySeries()
|
||||||
|
{
|
||||||
|
_series.SeriesType = SeriesTypes.Daily;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithSeasonZero()
|
||||||
|
{
|
||||||
|
_localEpisode.Episodes[0].SeasonNumber = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithFileSize(long size)
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(s => s.GetFileSize(It.IsAny<String>()))
|
||||||
|
.Returns(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithLength(int minutes)
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IVideoFileInfoReader>()
|
||||||
|
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
||||||
|
.Returns(new TimeSpan(0, 0, minutes, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_series_is_daily()
|
||||||
|
{
|
||||||
|
WithDailySeries();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_season_zero()
|
||||||
|
{
|
||||||
|
WithSeasonZero();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_undersize_and_under_length()
|
||||||
|
{
|
||||||
|
WithFileSize(10.Megabytes());
|
||||||
|
WithLength(1);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_undersize()
|
||||||
|
{
|
||||||
|
WithFileSize(10.Megabytes());
|
||||||
|
WithLength(10);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_under_length()
|
||||||
|
{
|
||||||
|
WithFileSize(100.Megabytes());
|
||||||
|
WithLength(1);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_over_size_and_length()
|
||||||
|
{
|
||||||
|
WithFileSize(100.Megabytes());
|
||||||
|
WithLength(10);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Marr.Data;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Providers;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.MediaFileTests.EpisodeImportTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class UpgradeSpecificationFixture : CoreTest<UpgradeSpecification>
|
||||||
|
{
|
||||||
|
private Series _series;
|
||||||
|
private LocalEpisode _localEpisode;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_series = Builder<Series>.CreateNew()
|
||||||
|
.With(s => s.SeriesType = SeriesTypes.Standard)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_localEpisode = new LocalEpisode
|
||||||
|
{
|
||||||
|
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
|
||||||
|
Quality = new QualityModel(Quality.HDTV720p, false)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_no_existing_episodeFile()
|
||||||
|
{
|
||||||
|
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
|
||||||
|
.All()
|
||||||
|
.With(e => e.EpisodeFileId = 0)
|
||||||
|
.With(e => e.EpisodeFile = null)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_no_existing_episodeFile_for_multi_episodes()
|
||||||
|
{
|
||||||
|
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(e => e.EpisodeFileId = 0)
|
||||||
|
.With(e => e.EpisodeFile = null)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_upgrade_for_existing_episodeFile()
|
||||||
|
{
|
||||||
|
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
|
||||||
|
.All()
|
||||||
|
.With(e => e.EpisodeFileId = 1)
|
||||||
|
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
|
||||||
|
new EpisodeFile
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.SDTV, false)
|
||||||
|
}))
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_upgrade_for_existing_episodeFile_for_multi_episodes()
|
||||||
|
{
|
||||||
|
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(e => e.EpisodeFileId = 1)
|
||||||
|
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
|
||||||
|
new EpisodeFile
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.SDTV, false)
|
||||||
|
}))
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_not_an_upgrade_for_existing_episodeFile()
|
||||||
|
{
|
||||||
|
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
|
||||||
|
.All()
|
||||||
|
.With(e => e.EpisodeFileId = 1)
|
||||||
|
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
|
||||||
|
new EpisodeFile
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.Bluray720p, false)
|
||||||
|
}))
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_not_an_upgrade_for_existing_episodeFile_for_multi_episodes()
|
||||||
|
{
|
||||||
|
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(2)
|
||||||
|
.All()
|
||||||
|
.With(e => e.EpisodeFileId = 1)
|
||||||
|
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
|
||||||
|
new EpisodeFile
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.Bluray720p, false)
|
||||||
|
}))
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_not_an_upgrade_for_one_existing_episodeFile_for_multi_episode()
|
||||||
|
{
|
||||||
|
_localEpisode.Episodes = Builder<Episode>.CreateListOfSize(2)
|
||||||
|
.TheFirst(1)
|
||||||
|
.With(e => e.EpisodeFileId = 1)
|
||||||
|
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
|
||||||
|
new EpisodeFile
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.SDTV, false)
|
||||||
|
}))
|
||||||
|
.TheNext(1)
|
||||||
|
.With(e => e.EpisodeFileId = 2)
|
||||||
|
.With(e => e.EpisodeFile = new LazyLoaded<EpisodeFile>(
|
||||||
|
new EpisodeFile
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.Bluray720p, false)
|
||||||
|
}))
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -148,6 +148,10 @@
|
||||||
<Compile Include="JobTests\TestJobs.cs" />
|
<Compile Include="JobTests\TestJobs.cs" />
|
||||||
<Compile Include="MediaCoverTests\CoverExistsSpecificationFixture.cs" />
|
<Compile Include="MediaCoverTests\CoverExistsSpecificationFixture.cs" />
|
||||||
<Compile Include="MediaCoverTests\MediaCoverServiceFixture.cs" />
|
<Compile Include="MediaCoverTests\MediaCoverServiceFixture.cs" />
|
||||||
|
<Compile Include="MediaFileTests\EpisodeImportTests\UpgradeSpecificationFixture.cs" />
|
||||||
|
<Compile Include="MediaFileTests\EpisodeImportTests\NotSampleSpecificationFixture.cs" />
|
||||||
|
<Compile Include="MediaFileTests\EpisodeImportTests\ImportDecisionMakerFixture.cs" />
|
||||||
|
<Compile Include="MediaFileTests\EpisodeImportTests\NotAlreadyImportedSpecificationFixture.cs" />
|
||||||
<Compile Include="MediaFileTests\MediaFileTableCleanupServiceFixture.cs" />
|
<Compile Include="MediaFileTests\MediaFileTableCleanupServiceFixture.cs" />
|
||||||
<Compile Include="MediaFileTests\MediaFileRepositoryFixture.cs" />
|
<Compile Include="MediaFileTests\MediaFileRepositoryFixture.cs" />
|
||||||
<Compile Include="MediaFileTests\EpisodeFileMoverFixture.cs" />
|
<Compile Include="MediaFileTests\EpisodeFileMoverFixture.cs" />
|
||||||
|
@ -200,7 +204,6 @@
|
||||||
<Compile Include="DecisionEngineTests\AcceptableSizeSpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\AcceptableSizeSpecificationFixture.cs" />
|
||||||
<Compile Include="Qualities\QualitySizeServiceFixture.cs" />
|
<Compile Include="Qualities\QualitySizeServiceFixture.cs" />
|
||||||
<Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest_GetEpisodesByParseResult.cs" />
|
<Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest_GetEpisodesByParseResult.cs" />
|
||||||
<Compile Include="ProviderTests\DiskScanProviderTests\ImportFileFixture.cs" />
|
|
||||||
<Compile Include="FluentTest.cs" />
|
<Compile Include="FluentTest.cs" />
|
||||||
<Compile Include="InstrumentationTests\DatabaseTargetFixture.cs" />
|
<Compile Include="InstrumentationTests\DatabaseTargetFixture.cs" />
|
||||||
<Compile Include="OrganizerTests\GetNewFilenameFixture.cs" />
|
<Compile Include="OrganizerTests\GetNewFilenameFixture.cs" />
|
||||||
|
|
|
@ -1,303 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using FizzWare.NBuilder;
|
|
||||||
using FluentAssertions;
|
|
||||||
using Moq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Common;
|
|
||||||
using NzbDrone.Core.MediaFiles;
|
|
||||||
using NzbDrone.Core.Parser;
|
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
using NzbDrone.Core.Qualities;
|
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
using NzbDrone.Core.Providers;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
|
|
||||||
{
|
|
||||||
|
|
||||||
public class ImportFileFixture : CoreTest<DiskScanService>
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
private long _fileSize = 80.Megabytes();
|
|
||||||
private Series _fakeSeries;
|
|
||||||
private Episode[] _fakeEpisodes;
|
|
||||||
private Episode _fakeEpisode;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_fakeSeries = Builder<Series>
|
|
||||||
.CreateNew()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_fakeEpisode = Builder<Episode>
|
|
||||||
.CreateNew()
|
|
||||||
.With(c => c.EpisodeFileId = 0)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
|
|
||||||
_fakeEpisodes = Builder<Episode>.CreateListOfSize(2)
|
|
||||||
.All()
|
|
||||||
.With(c => c.SeasonNumber = 3)
|
|
||||||
.With(c => c.EpisodeFileId = 1)
|
|
||||||
.With(e => e.EpisodeFile = new EpisodeFile())
|
|
||||||
.BuildList().ToArray();
|
|
||||||
|
|
||||||
GivenNewFile();
|
|
||||||
|
|
||||||
GivenVideoDuration(TimeSpan.FromMinutes(20));
|
|
||||||
|
|
||||||
GivenFileSize(_fileSize);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenFileSize(long size)
|
|
||||||
{
|
|
||||||
_fileSize = size;
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(d => d.GetFileSize(It.IsAny<String>()))
|
|
||||||
.Returns(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenVideoDuration(TimeSpan duration)
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IVideoFileInfoReader>()
|
|
||||||
.Setup(d => d.GetRunTime(It.IsAny<String>()))
|
|
||||||
.Returns(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void GivenEpisodes(Episode[] episodes, QualityModel quality)
|
|
||||||
{
|
|
||||||
foreach (var episode in episodes)
|
|
||||||
{
|
|
||||||
if (episode.EpisodeFile == null)
|
|
||||||
{
|
|
||||||
episode.EpisodeFileId = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
episode.EpisodeFileId = episode.EpisodeFile.Value.Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
|
||||||
.Setup(c => c.GetEpisodes(It.IsAny<string>(), It.IsAny<Series>()))
|
|
||||||
.Returns(new LocalEpisode
|
|
||||||
{
|
|
||||||
Episodes = episodes.ToList(),
|
|
||||||
Quality = quality
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenNewFile()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IMediaFileService>()
|
|
||||||
.Setup(p => p.Exists(It.IsAny<String>()))
|
|
||||||
.Returns(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void import_new_file_should_succeed()
|
|
||||||
{
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel());
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
VerifyFileImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void import_new_file_with_same_quality_should_succeed()
|
|
||||||
{
|
|
||||||
_fakeEpisode.EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.SDTV) };
|
|
||||||
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.SDTV));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
VerifyFileImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void import_new_file_with_better_quality_should_succeed()
|
|
||||||
{
|
|
||||||
_fakeEpisode.EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.SDTV) };
|
|
||||||
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.HDTV1080p));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
VerifyFileImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void import_new_file_episode_has_better_quality_should_skip()
|
|
||||||
{
|
|
||||||
_fakeEpisode.EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p), Id = 1 };
|
|
||||||
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.SDTV));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
VerifySkipImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void import_unparsable_file_should_skip()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IParsingService>()
|
|
||||||
.Setup(c => c.GetEpisodes(It.IsAny<string>(), It.IsAny<Series>()))
|
|
||||||
.Returns<LocalEpisode>(null);
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
|
|
||||||
VerifySkipImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void import_existing_file_should_skip()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IMediaFileService>()
|
|
||||||
.Setup(p => p.Exists(It.IsAny<String>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
VerifySkipImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void import_file_with_no_episode_in_db_should_skip()
|
|
||||||
{
|
|
||||||
GivenEpisodes(new Episode[0], new QualityModel());
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
VerifySkipImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void import_new_multi_part_file_episode_with_better_quality_than_existing()
|
|
||||||
{
|
|
||||||
_fakeEpisodes[0].EpisodeFile = new EpisodeFile();
|
|
||||||
_fakeEpisodes[1].EpisodeFile = new EpisodeFile();
|
|
||||||
|
|
||||||
_fakeEpisodes[0].EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.SDTV) };
|
|
||||||
_fakeEpisodes[1].EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.SDTV) };
|
|
||||||
|
|
||||||
GivenEpisodes(_fakeEpisodes, new QualityModel(Quality.HDTV1080p));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
|
|
||||||
VerifyFileImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void skip_import_new_multi_part_file_episode_existing_has_better_quality()
|
|
||||||
{
|
|
||||||
_fakeEpisodes[0].EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p), Id = 1 };
|
|
||||||
_fakeEpisodes[1].EpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV1080p), Id = 1 };
|
|
||||||
|
|
||||||
GivenEpisodes(_fakeEpisodes, new QualityModel(Quality.SDTV));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
|
|
||||||
VerifySkipImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_skip_if_file_size_is_under_70MB_and_runTime_under_3_minutes()
|
|
||||||
{
|
|
||||||
GivenFileSize(50.Megabytes());
|
|
||||||
GivenVideoDuration(TimeSpan.FromMinutes(1));
|
|
||||||
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.HDTV1080p));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
VerifySkipImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_import_if_file_size_is_under_70MB_but_runTime_over_3_minutes()
|
|
||||||
{
|
|
||||||
GivenFileSize(50.Megabytes());
|
|
||||||
GivenVideoDuration(TimeSpan.FromMinutes(20));
|
|
||||||
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.HDTV1080p));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
VerifyFileImport(result);
|
|
||||||
Mocker.GetMock<IDiskProvider>().Verify(p => p.DeleteFile(It.IsAny<string>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_import_if_file_size_is_over_70MB_but_runTime_under_3_minutes()
|
|
||||||
{
|
|
||||||
GivenFileSize(100.Megabytes());
|
|
||||||
GivenVideoDuration(TimeSpan.FromMinutes(1));
|
|
||||||
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.HDTV1080p));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
VerifyFileImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_import_special_even_if_file_size_is_under_70MB_and_runTime_under_3_minutes()
|
|
||||||
{
|
|
||||||
GivenFileSize(10.Megabytes());
|
|
||||||
GivenVideoDuration(TimeSpan.FromMinutes(1));
|
|
||||||
|
|
||||||
_fakeEpisode.SeasonNumber = 0;
|
|
||||||
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.HDTV1080p));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
VerifyFileImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_skip_if_daily_series_with_file_size_is_under_70MB_and_runTime_under_3_minutes()
|
|
||||||
{
|
|
||||||
GivenFileSize(10.Megabytes());
|
|
||||||
GivenVideoDuration(TimeSpan.FromMinutes(1));
|
|
||||||
|
|
||||||
_fakeEpisode.SeasonNumber = 0;
|
|
||||||
_fakeSeries.SeriesType = SeriesTypes.Daily;
|
|
||||||
|
|
||||||
GivenEpisodes(new[] { _fakeEpisode }, new QualityModel(Quality.HDTV1080p));
|
|
||||||
|
|
||||||
var result = Subject.ImportFile(_fakeSeries, "file.ext");
|
|
||||||
|
|
||||||
VerifySkipImport(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFileImport(EpisodeFile result)
|
|
||||||
{
|
|
||||||
result.Should().NotBeNull();
|
|
||||||
result.SeriesId.Should().Be(_fakeSeries.Id);
|
|
||||||
result.Size.Should().Be(_fileSize);
|
|
||||||
result.DateAdded.Should().HaveDay(DateTime.UtcNow.Day);
|
|
||||||
|
|
||||||
Mocker.GetMock<IMediaFileService>().Verify(c => c.Add(result), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifySkipImport(EpisodeFile result)
|
|
||||||
{
|
|
||||||
result.Should().BeNull();
|
|
||||||
Mocker.GetMock<IMediaFileService>().Verify(p => p.Add(It.IsAny<EpisodeFile>()), Times.Never());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
@ -6,6 +7,7 @@ using NUnit.Framework;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
@ -26,7 +28,6 @@ namespace NzbDrone.Core.Test.ProviderTests.PostDownloadProviderTests
|
||||||
{
|
{
|
||||||
_fakeEpisodeFile = Builder<EpisodeFile>.CreateNew().Build();
|
_fakeEpisodeFile = Builder<EpisodeFile>.CreateNew().Build();
|
||||||
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskScanService>().Setup(c => c.GetVideoFiles(It.IsAny<string>(), It.IsAny<bool>()))
|
Mocker.GetMock<IDiskScanService>().Setup(c => c.GetVideoFiles(It.IsAny<string>(), It.IsAny<bool>()))
|
||||||
.Returns(_videoFiles);
|
.Returns(_videoFiles);
|
||||||
|
|
||||||
|
@ -37,24 +38,6 @@ namespace NzbDrone.Core.Test.ProviderTests.PostDownloadProviderTests
|
||||||
.Returns("c:\\drop\\");
|
.Returns("c:\\drop\\");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithOldWrite()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(c => c.GetLastFolderWrite(It.IsAny<String>()))
|
|
||||||
.Returns(DateTime.Now.AddDays(-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithRecentFolderWrite()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(c => c.GetLastFolderWrite(It.IsAny<String>()))
|
|
||||||
.Returns(DateTime.UtcNow);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(c => c.GetLastFileWrite(It.IsAny<String>()))
|
|
||||||
.Returns(DateTime.UtcNow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_import_file()
|
public void should_import_file()
|
||||||
{
|
{
|
||||||
|
@ -66,65 +49,14 @@ namespace NzbDrone.Core.Test.ProviderTests.PostDownloadProviderTests
|
||||||
[Test]
|
[Test]
|
||||||
public void should_search_for_series_using_folder_name()
|
public void should_search_for_series_using_folder_name()
|
||||||
{
|
{
|
||||||
WithOldWrite();
|
|
||||||
|
|
||||||
Subject.ProcessDownloadedEpisodesFolder();
|
Subject.ProcessDownloadedEpisodesFolder();
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>().Verify(c => c.GetSeries("foldername"), Times.Once());
|
Mocker.GetMock<IParsingService>().Verify(c => c.GetSeries("foldername"), Times.Once());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void all_imported_files_should_be_moved()
|
public void should_skip_if_file_is_in_use_by_another_process()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDiskScanService>().Setup(c => c.ImportFile(It.IsAny<Series>(), It.IsAny<string>()))
|
|
||||||
.Returns(_fakeEpisodeFile);
|
|
||||||
|
|
||||||
Subject.ProcessDownloadedEpisodesFolder();
|
|
||||||
|
|
||||||
Mocker.GetMock<IMoveEpisodeFiles>().Verify(c => c.MoveEpisodeFile(_fakeEpisodeFile, true), Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_trigger_import_event_on_import()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskScanService>().Setup(c => c.ImportFile(It.IsAny<Series>(), It.IsAny<string>()))
|
|
||||||
.Returns(_fakeEpisodeFile);
|
|
||||||
|
|
||||||
Subject.ProcessDownloadedEpisodesFolder();
|
|
||||||
|
|
||||||
VerifyEventPublished<EpisodeImportedEvent>();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_attempt_move_if_nothing_is_imported()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskScanService>().Setup(c => c.ImportFile(It.IsAny<Series>(), It.IsAny<string>()))
|
|
||||||
.Returns<EpisodeFile>(null);
|
|
||||||
|
|
||||||
Subject.ProcessDownloadedEpisodesFolder();
|
|
||||||
|
|
||||||
Mocker.GetMock<IMoveEpisodeFiles>().Verify(c => c.MoveEpisodeFile(It.IsAny<EpisodeFile>(), It.IsAny<bool>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_publish_import_event_if_nothing_is_imported()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IDiskScanService>().Setup(c => c.ImportFile(It.IsAny<Series>(), It.IsAny<string>()))
|
|
||||||
.Returns<EpisodeFile>(null);
|
|
||||||
|
|
||||||
Subject.ProcessDownloadedEpisodesFolder();
|
|
||||||
|
|
||||||
|
|
||||||
VerifyEventNotPublished<EpisodeImportedEvent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_skip_if_folder_is_in_use_by_another_process()
|
|
||||||
{
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<FileInfo>()))
|
Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<FileInfo>()))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
|
@ -134,13 +66,13 @@ namespace NzbDrone.Core.Test.ProviderTests.PostDownloadProviderTests
|
||||||
|
|
||||||
private void VerifyNoImport()
|
private void VerifyNoImport()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDiskScanService>().Verify(c => c.ImportFile(It.IsAny<Series>(), It.IsAny<string>()),
|
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true),
|
||||||
Times.Never());
|
Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VerifyImport()
|
private void VerifyImport()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDiskScanService>().Verify(c => c.ImportFile(It.IsAny<Series>(), It.IsAny<string>()),
|
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true),
|
||||||
Times.Once());
|
Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.Messaging;
|
using NzbDrone.Common.Messaging;
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
using NzbDrone.Core.MediaFiles.Commands;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Providers;
|
using NzbDrone.Core.Providers;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
@ -15,7 +16,6 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
public interface IDiskScanService
|
public interface IDiskScanService
|
||||||
{
|
{
|
||||||
EpisodeFile ImportFile(Series series, string filePath);
|
|
||||||
string[] GetVideoFiles(string path, bool allDirectories = true);
|
string[] GetVideoFiles(string path, bool allDirectories = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,19 +25,20 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
private static readonly string[] MediaExtensions = new[] { ".mkv", ".avi", ".wmv", ".mp4", ".mpg", ".mpeg", ".xvid", ".flv", ".mov", ".rm", ".rmvb", ".divx", ".dvr-ms", ".ts", ".ogm", ".m4v", ".strm" };
|
private static readonly string[] MediaExtensions = new[] { ".mkv", ".avi", ".wmv", ".mp4", ".mpg", ".mpeg", ".xvid", ".flv", ".mov", ".rm", ".rmvb", ".divx", ".dvr-ms", ".ts", ".ogm", ".m4v", ".strm" };
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
private readonly IMediaFileService _mediaFileService;
|
private readonly IMakeImportDecision _importDecisionMaker;
|
||||||
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||||
private readonly IParsingService _parsingService;
|
|
||||||
private readonly IMessageAggregator _messageAggregator;
|
private readonly IMessageAggregator _messageAggregator;
|
||||||
|
|
||||||
public DiskScanService(IDiskProvider diskProvider, ISeriesService seriesService, IMediaFileService mediaFileService, IVideoFileInfoReader videoFileInfoReader,
|
public DiskScanService(IDiskProvider diskProvider,
|
||||||
IParsingService parsingService, IMessageAggregator messageAggregator)
|
ISeriesService seriesService,
|
||||||
|
IMakeImportDecision importDecisionMaker,
|
||||||
|
IImportApprovedEpisodes importApprovedEpisodes,
|
||||||
|
IMessageAggregator messageAggregator)
|
||||||
{
|
{
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_seriesService = seriesService;
|
_seriesService = seriesService;
|
||||||
_mediaFileService = mediaFileService;
|
_importDecisionMaker = importDecisionMaker;
|
||||||
_videoFileInfoReader = videoFileInfoReader;
|
_importApprovedEpisodes = importApprovedEpisodes;
|
||||||
_parsingService = parsingService;
|
|
||||||
_messageAggregator = messageAggregator;
|
_messageAggregator = messageAggregator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,71 +54,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
var mediaFileList = GetVideoFiles(series.Path);
|
var mediaFileList = GetVideoFiles(series.Path);
|
||||||
|
|
||||||
foreach (var filePath in mediaFileList)
|
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, series);
|
||||||
{
|
_importApprovedEpisodes.Import(decisions);
|
||||||
try
|
|
||||||
{
|
|
||||||
ImportFile(series, filePath);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.ErrorException("Couldn't import file " + filePath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Todo: Find the "best" episode file for all found episodes and import that one
|
|
||||||
//Todo: Move the episode linking to here, instead of import (or rename import)
|
|
||||||
}
|
|
||||||
|
|
||||||
public EpisodeFile ImportFile(Series series, string filePath)
|
|
||||||
{
|
|
||||||
Logger.Trace("Importing file to database [{0}]", filePath);
|
|
||||||
|
|
||||||
if (_mediaFileService.Exists(filePath))
|
|
||||||
{
|
|
||||||
Logger.Trace("[{0}] already exists in the database. skipping.", filePath);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parsedEpisode = _parsingService.GetEpisodes(filePath, series);
|
|
||||||
|
|
||||||
if (parsedEpisode == null || !parsedEpisode.Episodes.Any())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var size = _diskProvider.GetFileSize(filePath);
|
|
||||||
|
|
||||||
if (series.SeriesType == SeriesTypes.Daily || parsedEpisode.SeasonNumber > 0)
|
|
||||||
{
|
|
||||||
var runTime = _videoFileInfoReader.GetRunTime(filePath);
|
|
||||||
if (size < Constants.IgnoreFileSize && runTime.TotalMinutes < 3)
|
|
||||||
{
|
|
||||||
Logger.Trace("[{0}] appears to be a sample. skipping.", filePath);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && e.EpisodeFile.Value.Quality > parsedEpisode.Quality))
|
|
||||||
{
|
|
||||||
Logger.Trace("This file isn't an upgrade for all episodes. Skipping {0}", filePath);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var episodeFile = new EpisodeFile();
|
|
||||||
episodeFile.DateAdded = DateTime.UtcNow;
|
|
||||||
episodeFile.SeriesId = series.Id;
|
|
||||||
episodeFile.Path = filePath.CleanPath();
|
|
||||||
episodeFile.Size = size;
|
|
||||||
episodeFile.Quality = parsedEpisode.Quality;
|
|
||||||
episodeFile.SeasonNumber = parsedEpisode.SeasonNumber;
|
|
||||||
episodeFile.SceneName = Path.GetFileNameWithoutExtension(filePath.CleanPath());
|
|
||||||
episodeFile.Episodes = parsedEpisode.Episodes;
|
|
||||||
|
|
||||||
//Todo: We shouldn't actually import the file until we confirm its the only one we want.
|
|
||||||
//Todo: Separate episodeFile creation from importing (pass file to import to import)
|
|
||||||
_mediaFileService.Add(episodeFile);
|
|
||||||
return episodeFile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetVideoFiles(string path, bool allDirectories = true)
|
public string[] GetVideoFiles(string path, bool allDirectories = true)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.Messaging;
|
using NzbDrone.Common.Messaging;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
using NzbDrone.Core.MediaFiles.Commands;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
@ -19,7 +20,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
private readonly IMoveEpisodeFiles _episodeFileMover;
|
private readonly IMoveEpisodeFiles _episodeFileMover;
|
||||||
private readonly IParsingService _parsingService;
|
private readonly IParsingService _parsingService;
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IMessageAggregator _messageAggregator;
|
private readonly IMakeImportDecision _importDecisionMaker;
|
||||||
|
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public DownloadedEpisodesImportService(IDiskProvider diskProvider,
|
public DownloadedEpisodesImportService(IDiskProvider diskProvider,
|
||||||
|
@ -28,7 +30,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
IMoveEpisodeFiles episodeFileMover,
|
IMoveEpisodeFiles episodeFileMover,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IMessageAggregator messageAggregator,
|
IMakeImportDecision importDecisionMaker,
|
||||||
|
IImportApprovedEpisodes importApprovedEpisodes,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
|
@ -37,7 +40,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
_episodeFileMover = episodeFileMover;
|
_episodeFileMover = episodeFileMover;
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_messageAggregator = messageAggregator;
|
_importDecisionMaker = importDecisionMaker;
|
||||||
|
_importApprovedEpisodes = importApprovedEpisodes;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +96,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProcessSubFolder(DirectoryInfo subfolderInfo)
|
private void ProcessSubFolder(DirectoryInfo subfolderInfo)
|
||||||
{
|
{
|
||||||
var series = _parsingService.GetSeries(subfolderInfo.Name);
|
var series = _parsingService.GetSeries(subfolderInfo.Name);
|
||||||
|
|
||||||
|
@ -102,12 +106,9 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var files = _diskScanService.GetVideoFiles(subfolderInfo.FullName);
|
var videoFiles = _diskScanService.GetVideoFiles(subfolderInfo.FullName);
|
||||||
|
|
||||||
foreach (var file in files)
|
ProcessFiles(videoFiles, series);
|
||||||
{
|
|
||||||
ProcessVideoFile(file, series);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessVideoFile(string videoFile, Series series)
|
private void ProcessVideoFile(string videoFile, Series series)
|
||||||
|
@ -118,13 +119,13 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeFile = _diskScanService.ImportFile(series, videoFile);
|
ProcessFiles(new [] { videoFile }, series);
|
||||||
|
|
||||||
if (episodeFile != null)
|
|
||||||
{
|
|
||||||
_episodeFileMover.MoveEpisodeFile(episodeFile, true);
|
|
||||||
_messageAggregator.PublishEvent(new EpisodeImportedEvent(episodeFile));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ProcessFiles(IEnumerable<string> videoFiles, Series series)
|
||||||
|
{
|
||||||
|
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles, series);
|
||||||
|
_importApprovedEpisodes.Import(decisions, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(DownloadedEpisodesScanCommand message)
|
public void Execute(DownloadedEpisodesScanCommand message)
|
||||||
|
|
|
@ -6,13 +6,15 @@ using NzbDrone.Common;
|
||||||
using NzbDrone.Common.Messaging;
|
using NzbDrone.Common.Messaging;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles
|
namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
public interface IMoveEpisodeFiles
|
public interface IMoveEpisodeFiles
|
||||||
{
|
{
|
||||||
EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, bool newDownload = false);
|
EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile);
|
||||||
|
EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MoveEpisodeFiles : IMoveEpisodeFiles
|
public class MoveEpisodeFiles : IMoveEpisodeFiles
|
||||||
|
@ -36,55 +38,68 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, bool newDownload = false)
|
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
if (episodeFile == null)
|
if (episodeFile == null)
|
||||||
throw new ArgumentNullException("episodeFile");
|
throw new ArgumentNullException("episodeFile");
|
||||||
|
|
||||||
var series = _seriesRepository.Get(episodeFile.SeriesId);
|
var series = _seriesRepository.Get(episodeFile.SeriesId);
|
||||||
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
|
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
|
||||||
string newFileName = _buildFileNames.BuildFilename(episodes, series, episodeFile);
|
var newFileName = _buildFileNames.BuildFilename(episodes, series, episodeFile);
|
||||||
var newFile = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
|
var destinationFilename = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
|
||||||
|
|
||||||
//Only rename if existing and new filenames don't match
|
episodeFile = MoveFile(episodeFile, destinationFilename);
|
||||||
if (DiskProvider.PathEquals(episodeFile.Path, newFile))
|
|
||||||
{
|
_mediaFileService.Update(episodeFile);
|
||||||
_logger.Debug("Skipping file rename, source and destination are the same: {0}", episodeFile.Path);
|
|
||||||
return null;
|
return episodeFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode)
|
||||||
|
{
|
||||||
|
var newFileName = _buildFileNames.BuildFilename(localEpisode.Episodes, localEpisode.Series, episodeFile);
|
||||||
|
var destinationFilename = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
|
||||||
|
episodeFile = MoveFile(episodeFile, destinationFilename);
|
||||||
|
|
||||||
|
//TODO: This just re-parses the source path (which is how we got localEpisode to begin with)
|
||||||
|
var parsedEpisodeInfo = Parser.Parser.ParsePath(localEpisode.Path);
|
||||||
|
_messageAggregator.PublishEvent(new EpisodeDownloadedEvent(parsedEpisodeInfo, localEpisode.Series));
|
||||||
|
|
||||||
|
return episodeFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EpisodeFile MoveFile(EpisodeFile episodeFile, string destinationFilename)
|
||||||
|
{
|
||||||
if (!_diskProvider.FileExists(episodeFile.Path))
|
if (!_diskProvider.FileExists(episodeFile.Path))
|
||||||
{
|
{
|
||||||
_logger.Error("Episode file path does not exist, {0}", episodeFile.Path);
|
_logger.Error("Episode file path does not exist, {0}", episodeFile.Path);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_diskProvider.CreateFolder(new FileInfo(newFile).DirectoryName);
|
//Only rename if existing and new filenames don't match
|
||||||
|
if (DiskProvider.PathEquals(episodeFile.Path, destinationFilename))
|
||||||
|
{
|
||||||
|
_logger.Debug("Skipping file rename, source and destination are the same: {0}", episodeFile.Path);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Debug("Moving [{0}] > [{1}]", episodeFile.Path, newFile);
|
_diskProvider.CreateFolder(new FileInfo(destinationFilename).DirectoryName);
|
||||||
_diskProvider.MoveFile(episodeFile.Path, newFile);
|
|
||||||
|
_logger.Debug("Moving [{0}] > [{1}]", episodeFile.Path, destinationFilename);
|
||||||
|
_diskProvider.MoveFile(episodeFile.Path, destinationFilename);
|
||||||
|
|
||||||
//Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes, the move worked, which is more important.
|
//Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes, the move worked, which is more important.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_diskProvider.InheritFolderPermissions(newFile);
|
_diskProvider.InheritFolderPermissions(destinationFilename);
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
catch (UnauthorizedAccessException ex)
|
||||||
{
|
{
|
||||||
_logger.Debug("Unable to apply folder permissions to: ", newFile);
|
_logger.Debug("Unable to apply folder permissions to: ", destinationFilename);
|
||||||
_logger.TraceException(ex.Message, ex);
|
_logger.TraceException(ex.Message, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeFile.Path = newFile;
|
episodeFile.Path = destinationFilename;
|
||||||
_mediaFileService.Update(episodeFile);
|
|
||||||
|
|
||||||
var parsedEpisodeInfo = Parser.Parser.ParsePath(episodeFile.Path);
|
|
||||||
parsedEpisodeInfo.Quality = episodeFile.Quality;
|
|
||||||
|
|
||||||
if (newDownload)
|
|
||||||
{
|
|
||||||
_messageAggregator.PublishEvent(new EpisodeDownloadedEvent(parsedEpisodeInfo, series));
|
|
||||||
}
|
|
||||||
|
|
||||||
return episodeFile;
|
return episodeFile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
{
|
||||||
|
public interface IImportDecisionEngineSpecification : IRejectWithReason
|
||||||
|
{
|
||||||
|
bool IsSatisfiedBy(LocalEpisode localEpisode);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
{
|
||||||
|
public interface IImportApprovedEpisodes
|
||||||
|
{
|
||||||
|
List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownloads = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportApprovedEpisodes : IImportApprovedEpisodes
|
||||||
|
{
|
||||||
|
private readonly IMoveEpisodeFiles _episodeFileMover;
|
||||||
|
private readonly MediaFileService _mediaFileService;
|
||||||
|
private readonly DiskProvider _diskProvider;
|
||||||
|
private readonly IMessageAggregator _messageAggregator;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public ImportApprovedEpisodes(IMoveEpisodeFiles episodeFileMover,
|
||||||
|
MediaFileService mediaFileService,
|
||||||
|
DiskProvider diskProvider,
|
||||||
|
IMessageAggregator messageAggregator,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_episodeFileMover = episodeFileMover;
|
||||||
|
_mediaFileService = mediaFileService;
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_messageAggregator = messageAggregator;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownload = false)
|
||||||
|
{
|
||||||
|
var qualifiedReports = GetQualifiedReports(decisions);
|
||||||
|
var imported = new List<ImportDecision>();
|
||||||
|
|
||||||
|
foreach (var report in qualifiedReports)
|
||||||
|
{
|
||||||
|
var localEpisode = report.LocalEpisode;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (imported.SelectMany(r => r.LocalEpisode.Episodes)
|
||||||
|
.Select(e => e.Id)
|
||||||
|
.ToList()
|
||||||
|
.Intersect(localEpisode.Episodes.Select(e => e.Id))
|
||||||
|
.Any())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var episodeFile = new EpisodeFile();
|
||||||
|
episodeFile.DateAdded = DateTime.UtcNow;
|
||||||
|
episodeFile.SeriesId = localEpisode.Series.Id;
|
||||||
|
episodeFile.Path = localEpisode.Path.CleanPath();
|
||||||
|
episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path);
|
||||||
|
episodeFile.Quality = localEpisode.Quality;
|
||||||
|
episodeFile.SeasonNumber = localEpisode.SeasonNumber;
|
||||||
|
episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanPath());
|
||||||
|
episodeFile.Episodes = localEpisode.Episodes;
|
||||||
|
|
||||||
|
if (newDownload)
|
||||||
|
{
|
||||||
|
episodeFile = _episodeFileMover.MoveEpisodeFile(episodeFile, localEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mediaFileService.Add(episodeFile);
|
||||||
|
_messageAggregator.PublishEvent(new EpisodeImportedEvent(episodeFile));
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.WarnException("Couldn't add report to download queue. " + localEpisode, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return imported;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ImportDecision> GetQualifiedReports(List<ImportDecision> decisions)
|
||||||
|
{
|
||||||
|
return decisions.Where(c => c.Approved)
|
||||||
|
.OrderByDescending(c => c.LocalEpisode.Quality)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
{
|
||||||
|
public class ImportDecision
|
||||||
|
{
|
||||||
|
public LocalEpisode LocalEpisode { get; private set; }
|
||||||
|
public IEnumerable<string> Rejections { get; private set; }
|
||||||
|
|
||||||
|
public bool Approved
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return !Rejections.Any();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImportDecision(LocalEpisode localEpisode, params string[] rejections)
|
||||||
|
{
|
||||||
|
LocalEpisode = localEpisode;
|
||||||
|
Rejections = rejections.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
|
{
|
||||||
|
public interface IMakeImportDecision
|
||||||
|
{
|
||||||
|
List<ImportDecision> GetImportDecisions(IEnumerable<String> videoFiles, Series series);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportDecisionMaker : IMakeImportDecision
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<IRejectWithReason> _specifications;
|
||||||
|
private readonly IParsingService _parsingService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public ImportDecisionMaker(IEnumerable<IRejectWithReason> specifications, IParsingService parsingService, Logger logger)
|
||||||
|
{
|
||||||
|
_specifications = specifications;
|
||||||
|
_parsingService = parsingService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ImportDecision> GetImportDecisions(IEnumerable<String> videoFiles, Series series)
|
||||||
|
{
|
||||||
|
return GetDecisions(videoFiles, series).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<ImportDecision> GetDecisions(IEnumerable<String> videoFiles, Series series)
|
||||||
|
{
|
||||||
|
foreach (var file in videoFiles)
|
||||||
|
{
|
||||||
|
ImportDecision decision = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parsedEpisode = _parsingService.GetEpisodes(file, series);
|
||||||
|
|
||||||
|
if (parsedEpisode != null)
|
||||||
|
{
|
||||||
|
decision = GetDecision(parsedEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parsedEpisode = new LocalEpisode();
|
||||||
|
parsedEpisode.Path = file;
|
||||||
|
|
||||||
|
decision = new ImportDecision(parsedEpisode, "Unable to parse file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Couldn't process report.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decision != null)
|
||||||
|
{
|
||||||
|
yield return decision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportDecision GetDecision(LocalEpisode localEpisode)
|
||||||
|
{
|
||||||
|
var reasons = _specifications.Select(c => EvaluateSpec(c, localEpisode))
|
||||||
|
.Where(c => !string.IsNullOrWhiteSpace(c));
|
||||||
|
|
||||||
|
return new ImportDecision(localEpisode, reasons.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EvaluateSpec(IRejectWithReason spec, LocalEpisode localEpisode)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(spec.RejectionReason))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("[Need Rejection Text]");
|
||||||
|
}
|
||||||
|
|
||||||
|
var generalSpecification = spec as IImportDecisionEngineSpecification;
|
||||||
|
if (generalSpecification != null && !generalSpecification.IsSatisfiedBy(localEpisode))
|
||||||
|
{
|
||||||
|
return spec.RejectionReason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//e.Data.Add("report", remoteEpisode.Report.ToJson());
|
||||||
|
//e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
|
||||||
|
_logger.ErrorException("Couldn't evaluate decision on " + localEpisode.Path, e);
|
||||||
|
return string.Format("{0}: {1}", spec.GetType().Name, e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
|
{
|
||||||
|
public class NotAlreadyImportedSpecification : IImportDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly IMediaFileService _mediaFileService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public NotAlreadyImportedSpecification(IMediaFileService mediaFileService, Logger logger)
|
||||||
|
{
|
||||||
|
_mediaFileService = mediaFileService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RejectionReason { get { return "Is Sample"; } }
|
||||||
|
|
||||||
|
public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
||||||
|
{
|
||||||
|
if (_mediaFileService.Exists(localEpisode.Path))
|
||||||
|
{
|
||||||
|
_logger.Trace("[{0}] already exists in the database. skipping.", localEpisode.Path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Providers;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
|
{
|
||||||
|
public class NotSampleSpecification : IImportDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public NotSampleSpecification(IDiskProvider diskProvider, IVideoFileInfoReader videoFileInfoReader, Logger logger)
|
||||||
|
{
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_videoFileInfoReader = videoFileInfoReader;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RejectionReason { get { return "Sample"; } }
|
||||||
|
|
||||||
|
public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
||||||
|
{
|
||||||
|
if (localEpisode.Series.SeriesType == SeriesTypes.Daily)
|
||||||
|
{
|
||||||
|
_logger.Trace("Daily Series, skipping sample check");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localEpisode.SeasonNumber == 0)
|
||||||
|
{
|
||||||
|
_logger.Trace("Special, skipping sample check");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = _diskProvider.GetFileSize(localEpisode.Path);
|
||||||
|
var runTime = _videoFileInfoReader.GetRunTime(localEpisode.Path);
|
||||||
|
|
||||||
|
if (size < Constants.IgnoreFileSize && runTime.TotalMinutes < 3)
|
||||||
|
{
|
||||||
|
_logger.Trace("[{0}] appears to be a sample.", localEpisode.Path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
|
{
|
||||||
|
public class UpgradeSpecification : IImportDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public UpgradeSpecification(Logger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RejectionReason { get { return "Is Sample"; } }
|
||||||
|
|
||||||
|
public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
||||||
|
{
|
||||||
|
if (localEpisode.Episodes.Any(e => e.EpisodeFileId != 0 && e.EpisodeFile.Value.Quality > localEpisode.Quality))
|
||||||
|
{
|
||||||
|
_logger.Trace("This file isn't an upgrade for all episodes. Skipping {0}", localEpisode.Path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -274,6 +274,13 @@
|
||||||
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\DiskScanCommand.cs" />
|
<Compile Include="MediaFiles\Commands\DiskScanCommand.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\ImportDecision.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\IImportDecisionEngineSpecification.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\ImportApprovedEpisodes.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotAlreadyImportedSpecification.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecification.cs" />
|
||||||
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecification.cs" />
|
||||||
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
|
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
|
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
|
||||||
<Compile Include="Download\EpisodeGrabbedEvent.cs" />
|
<Compile Include="Download\EpisodeGrabbedEvent.cs" />
|
||||||
|
|
|
@ -6,11 +6,14 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
{
|
{
|
||||||
public class LocalEpisode
|
public class LocalEpisode
|
||||||
{
|
{
|
||||||
//public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
public Series Series { get; set; }
|
||||||
|
|
||||||
public List<Episode> Episodes { get; set; }
|
public List<Episode> Episodes { get; set; }
|
||||||
|
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
|
|
||||||
public int SeasonNumber { get { return Episodes.Select(c => c.SeasonNumber).Distinct().Single(); } }
|
public int SeasonNumber { get { return Episodes.Select(c => c.SeasonNumber).Distinct().Single(); } }
|
||||||
|
|
||||||
|
public string Path { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,7 +26,6 @@ namespace NzbDrone.Core.Parser
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public LocalEpisode GetEpisodes(string fileName, Series series)
|
public LocalEpisode GetEpisodes(string fileName, Series series)
|
||||||
{
|
{
|
||||||
var parsedEpisodeInfo = Parser.ParseTitle(fileName);
|
var parsedEpisodeInfo = Parser.ParseTitle(fileName);
|
||||||
|
@ -45,8 +44,10 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
return new LocalEpisode
|
return new LocalEpisode
|
||||||
{
|
{
|
||||||
|
Series = series,
|
||||||
Quality = parsedEpisodeInfo.Quality,
|
Quality = parsedEpisodeInfo.Quality,
|
||||||
Episodes = episodes,
|
Episodes = episodes,
|
||||||
|
Path = fileName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue