Merge branch 'develop'
This commit is contained in:
commit
d56a774563
|
@ -1,7 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using Nancy;
|
||||||
using NzbDrone.Api.Mapping;
|
using Nancy.ModelBinding;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
|
|
||||||
namespace NzbDrone.Api.History
|
namespace NzbDrone.Api.History
|
||||||
|
@ -9,11 +12,15 @@ namespace NzbDrone.Api.History
|
||||||
public class HistoryModule : NzbDroneRestModule<HistoryResource>
|
public class HistoryModule : NzbDroneRestModule<HistoryResource>
|
||||||
{
|
{
|
||||||
private readonly IHistoryService _historyService;
|
private readonly IHistoryService _historyService;
|
||||||
|
private readonly IFailedDownloadService _failedDownloadService;
|
||||||
|
|
||||||
public HistoryModule(IHistoryService historyService)
|
public HistoryModule(IHistoryService historyService, IFailedDownloadService failedDownloadService)
|
||||||
{
|
{
|
||||||
_historyService = historyService;
|
_historyService = historyService;
|
||||||
|
_failedDownloadService = failedDownloadService;
|
||||||
GetResourcePaged = GetHistory;
|
GetResourcePaged = GetHistory;
|
||||||
|
|
||||||
|
Post["/failed"] = x => MarkAsFailed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PagingResource<HistoryResource> GetHistory(PagingResource<HistoryResource> pagingResource)
|
private PagingResource<HistoryResource> GetHistory(PagingResource<HistoryResource> pagingResource)
|
||||||
|
@ -36,5 +43,12 @@ namespace NzbDrone.Api.History
|
||||||
|
|
||||||
return ApplyToPage(_historyService.Paged, pagingSpec);
|
return ApplyToPage(_historyService.Paged, pagingSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response MarkAsFailed()
|
||||||
|
{
|
||||||
|
var id = (int)Request.Form.Id;
|
||||||
|
_failedDownloadService.MarkAsFailed(id);
|
||||||
|
return new Object().AsResponse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Api.Episodes;
|
||||||
using NzbDrone.Api.REST;
|
using NzbDrone.Api.REST;
|
||||||
|
using NzbDrone.Api.Series;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ namespace NzbDrone.Api.History
|
||||||
|
|
||||||
public Dictionary<string, string> Data { get; set; }
|
public Dictionary<string, string> Data { get; set; }
|
||||||
|
|
||||||
public Episode Episode { get; set; }
|
public EpisodeResource Episode { get; set; }
|
||||||
public Core.Tv.Series Series { get; set; }
|
public SeriesResource Series { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace NzbDrone.Api.Indexers
|
||||||
public Boolean SceneSource { get; set; }
|
public Boolean SceneSource { get; set; }
|
||||||
public Int32 SeasonNumber { get; set; }
|
public Int32 SeasonNumber { get; set; }
|
||||||
public Language Language { get; set; }
|
public Language Language { get; set; }
|
||||||
public DateTime? AirDate { get; set; }
|
public String AirDate { get; set; }
|
||||||
public String SeriesTitle { get; set; }
|
public String SeriesTitle { get; set; }
|
||||||
public int[] EpisodeNumbers { get; set; }
|
public int[] EpisodeNumbers { get; set; }
|
||||||
public Boolean Approved { get; set; }
|
public Boolean Approved { get; set; }
|
||||||
|
|
|
@ -22,7 +22,6 @@ namespace NzbDrone.Api.Update
|
||||||
_recentUpdateProvider = recentUpdateProvider;
|
_recentUpdateProvider = recentUpdateProvider;
|
||||||
_installUpdateService = installUpdateService;
|
_installUpdateService = installUpdateService;
|
||||||
GetResourceAll = GetRecentUpdates;
|
GetResourceAll = GetRecentUpdates;
|
||||||
Post["/"] = x=> InstallUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UpdateResource> GetRecentUpdates()
|
private List<UpdateResource> GetRecentUpdates()
|
||||||
|
@ -46,16 +45,6 @@ namespace NzbDrone.Api.Update
|
||||||
|
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response InstallUpdate()
|
|
||||||
{
|
|
||||||
var updateResource = Request.Body.FromJson<UpdateResource>();
|
|
||||||
|
|
||||||
var updatePackage = updateResource.InjectTo<UpdatePackage>();
|
|
||||||
_installUpdateService.InstallUpdate(updatePackage);
|
|
||||||
|
|
||||||
return updateResource.AsResponse();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateResource : RestResource
|
public class UpdateResource : RestResource
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Blacklisting;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Blacklisting
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class BlacklistRepositoryFixture : DbTest<BlacklistRepository, Blacklist>
|
||||||
|
{
|
||||||
|
private Blacklist _blacklist;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_blacklist = new Blacklist
|
||||||
|
{
|
||||||
|
SeriesId = 12345,
|
||||||
|
EpisodeIds = new List<int> {1},
|
||||||
|
Quality = new QualityModel(Quality.Bluray720p),
|
||||||
|
SourceTitle = "series.title.s01e01",
|
||||||
|
Date = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_be_able_to_write_to_database()
|
||||||
|
{
|
||||||
|
Subject.Insert(_blacklist);
|
||||||
|
Subject.All().Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_should_have_episode_ids()
|
||||||
|
{
|
||||||
|
Subject.Insert(_blacklist);
|
||||||
|
|
||||||
|
Subject.All().First().EpisodeIds.Should().Contain(_blacklist.EpisodeIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_check_for_blacklisted_title_case_insensative()
|
||||||
|
{
|
||||||
|
Subject.Insert(_blacklist);
|
||||||
|
|
||||||
|
Subject.Blacklisted(_blacklist.SourceTitle.ToUpperInvariant()).Should().BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Blacklisting;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Blacklisting
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class BlacklistServiceFixture : CoreTest<BlacklistService>
|
||||||
|
{
|
||||||
|
private DownloadFailedEvent _event;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_event = new DownloadFailedEvent
|
||||||
|
{
|
||||||
|
SeriesId = 12345,
|
||||||
|
EpisodeIds = new List<int> {1},
|
||||||
|
Quality = new QualityModel(Quality.Bluray720p),
|
||||||
|
SourceTitle = "series.title.s01e01",
|
||||||
|
DownloadClient = "SabnzbdClient",
|
||||||
|
DownloadClientId = "Sabnzbd_nzo_2dfh73k"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_trigger_redownload()
|
||||||
|
{
|
||||||
|
Subject.Handle(_event);
|
||||||
|
|
||||||
|
Mocker.GetMock<IRedownloadFailedDownloads>()
|
||||||
|
.Verify(v => v.Redownload(_event.SeriesId, _event.EpisodeIds), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_add_to_repository()
|
||||||
|
{
|
||||||
|
Subject.Handle(_event);
|
||||||
|
|
||||||
|
Mocker.GetMock<IBlacklistRepository>()
|
||||||
|
.Verify(v => v.Insert(It.Is<Blacklist>(b => b.EpisodeIds == _event.EpisodeIds)), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
|
using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.Clients.Sabnzbd;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
@ -15,9 +18,9 @@ using NzbDrone.Core.Test.Framework;
|
||||||
namespace NzbDrone.Core.Test.DecisionEngineTests
|
namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class UpgradeHistorySpecificationFixture : CoreTest<UpgradeHistorySpecification>
|
public class HistorySpecificationFixture : CoreTest<HistorySpecification>
|
||||||
{
|
{
|
||||||
private UpgradeHistorySpecification _upgradeHistory;
|
private HistorySpecification _upgradeHistory;
|
||||||
|
|
||||||
private RemoteEpisode _parseResultMulti;
|
private RemoteEpisode _parseResultMulti;
|
||||||
private RemoteEpisode _parseResultSingle;
|
private RemoteEpisode _parseResultSingle;
|
||||||
|
@ -29,7 +32,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
Mocker.Resolve<QualityUpgradableSpecification>();
|
Mocker.Resolve<QualityUpgradableSpecification>();
|
||||||
_upgradeHistory = Mocker.Resolve<UpgradeHistorySpecification>();
|
_upgradeHistory = Mocker.Resolve<HistorySpecification>();
|
||||||
|
|
||||||
var singleEpisodeList = new List<Episode> { new Episode { Id = 1, SeasonNumber = 12, EpisodeNumber = 3 } };
|
var singleEpisodeList = new List<Episode> { new Episode { Id = 1, SeasonNumber = 12, EpisodeNumber = 3 } };
|
||||||
var doubleEpisodeList = new List<Episode> {
|
var doubleEpisodeList = new List<Episode> {
|
||||||
|
@ -64,6 +67,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(1)).Returns(_notupgradableQuality);
|
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(1)).Returns(_notupgradableQuality);
|
||||||
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(2)).Returns(_notupgradableQuality);
|
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(2)).Returns(_notupgradableQuality);
|
||||||
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(3)).Returns<QualityModel>(null);
|
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(3)).Returns<QualityModel>(null);
|
||||||
|
|
||||||
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
|
.Setup(c => c.GetDownloadClient()).Returns(Mocker.GetMock<IDownloadClient>().Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithFirstReportUpgradable()
|
private void WithFirstReportUpgradable()
|
||||||
|
@ -76,6 +82,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(2)).Returns(_upgradableQuality);
|
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(2)).Returns(_upgradableQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GivenSabnzbdDownloadClient()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
|
.Setup(c => c.GetDownloadClient()).Returns(Mocker.Resolve<SabnzbdClient>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenMostRecentForEpisode(HistoryEventType eventType)
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IHistoryService>().Setup(s => s.MostRecentForEpisode(It.IsAny<int>()))
|
||||||
|
.Returns(new History.History { EventType = eventType });
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_be_upgradable_if_only_episode_is_upgradable()
|
public void should_be_upgradable_if_only_episode_is_upgradable()
|
||||||
|
@ -129,5 +146,40 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
{
|
{
|
||||||
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, new SeasonSearchCriteria()).Should().BeTrue();
|
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, new SeasonSearchCriteria()).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_using_sabnzbd_and_nothing_in_history()
|
||||||
|
{
|
||||||
|
GivenSabnzbdDownloadClient();
|
||||||
|
|
||||||
|
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_most_recent_in_history_is_grabbed()
|
||||||
|
{
|
||||||
|
GivenSabnzbdDownloadClient();
|
||||||
|
GivenMostRecentForEpisode(HistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_most_recent_in_history_is_failed()
|
||||||
|
{
|
||||||
|
GivenSabnzbdDownloadClient();
|
||||||
|
GivenMostRecentForEpisode(HistoryEventType.DownloadFailed);
|
||||||
|
|
||||||
|
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_most_recent_in_history_is_imported()
|
||||||
|
{
|
||||||
|
GivenSabnzbdDownloadClient();
|
||||||
|
GivenMostRecentForEpisode(HistoryEventType.DownloadFolderImported);
|
||||||
|
|
||||||
|
_upgradeHistory.IsSatisfiedBy(_parseResultMulti, null).Should().BeTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,324 +0,0 @@
|
||||||
/*using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using FluentAssertions;
|
|
||||||
using Moq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Common;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Download.Clients.Sabnzbd;
|
|
||||||
using NzbDrone.Core.Model;
|
|
||||||
using NzbDrone.Core.Qualities;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
|
|
||||||
public class QueueFixture : CoreTest
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
string sabHost = "192.168.5.55";
|
|
||||||
int sabPort = 2222;
|
|
||||||
string apikey = "5c770e3197e4fe763423ee7c392c25d1";
|
|
||||||
string username = "admin";
|
|
||||||
string password = "pass";
|
|
||||||
string cat = "tv";
|
|
||||||
|
|
||||||
var fakeConfig = Mocker.GetMock<IConfigService>();
|
|
||||||
fakeConfig.SetupGet(c => c.SabHost).Returns(sabHost);
|
|
||||||
fakeConfig.SetupGet(c => c.SabPort).Returns(sabPort);
|
|
||||||
fakeConfig.SetupGet(c => c.SabApiKey).Returns(apikey);
|
|
||||||
fakeConfig.SetupGet(c => c.SabUsername).Returns(username);
|
|
||||||
fakeConfig.SetupGet(c => c.SabPassword).Returns(password);
|
|
||||||
fakeConfig.SetupGet(c => c.SabTvCategory).Returns(cat);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithFullQueue()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHttpProvider>()
|
|
||||||
.Setup(
|
|
||||||
s =>
|
|
||||||
s.DownloadString(
|
|
||||||
"http://192.168.5.55:2222/api?mode=queue&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
|
|
||||||
.Returns(ReadAllText("Files","Queue.txt"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithEmptyQueue()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHttpProvider>()
|
|
||||||
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=queue&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
|
|
||||||
.Returns(ReadAllText("Files","QueueEmpty.txt"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithFailResponse()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHttpProvider>()
|
|
||||||
.Setup(s => s.DownloadString(It.IsAny<String>())).Returns(ReadAllText("Files","JsonError.txt"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithUnknownPriorityQueue()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHttpProvider>()
|
|
||||||
.Setup(
|
|
||||||
s =>
|
|
||||||
s.DownloadString(
|
|
||||||
"http://192.168.5.55:2222/api?mode=queue&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
|
|
||||||
.Returns(ReadAllText("Files", "QueueUnknownPriority.txt"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetQueue_should_return_an_empty_list_when_the_queue_is_empty()
|
|
||||||
{
|
|
||||||
WithEmptyQueue();
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().GetQueue();
|
|
||||||
|
|
||||||
result.Should().BeEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetQueue_should_throw_when_there_is_an_error_getting_the_queue()
|
|
||||||
{
|
|
||||||
WithFailResponse();
|
|
||||||
|
|
||||||
Assert.Throws<ApplicationException>(() => Mocker.Resolve<SabnzbdClient>().GetQueue(), "API Key Incorrect");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetQueue_should_return_a_list_with_items_when_the_queue_has_items()
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().GetQueue();
|
|
||||||
|
|
||||||
result.Should().HaveCount(7);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetQueue_should_return_a_list_with_items_even_when_priority_is_non_standard()
|
|
||||||
{
|
|
||||||
WithUnknownPriorityQueue();
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().GetQueue();
|
|
||||||
|
|
||||||
result.Should().HaveCount(7);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void is_in_queue_should_find_if_exact_episode_is_in_queue()
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
var parseResult = new RemoteEpisode
|
|
||||||
{
|
|
||||||
EpisodeTitle = "Title",
|
|
||||||
EpisodeNumbers = new List<int> { 5 },
|
|
||||||
SeasonNumber = 1,
|
|
||||||
Quality = new QualityModel { Quality = Quality.SDTV, Proper = false },
|
|
||||||
Series = new Series { Title = "30 Rock", CleanTitle = Parser.NormalizeTitle("30 Rock") },
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().IsInQueue(parseResult);
|
|
||||||
|
|
||||||
result.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void is_in_queue_should_find_if_exact_daily_episode_is_in_queue()
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
var parseResult = new RemoteEpisode
|
|
||||||
{
|
|
||||||
Quality = new QualityModel { Quality = Quality.Bluray720p, Proper = false },
|
|
||||||
AirDate = new DateTime(2011, 12, 01),
|
|
||||||
Series = new Series { Title = "The Dailyshow", CleanTitle = Parser.NormalizeTitle("The Dailyshow"), SeriesType = SeriesTypes.Daily },
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().IsInQueue(parseResult);
|
|
||||||
|
|
||||||
result.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void is_in_queue_should_find_if_exact_full_season_release_is_in_queue()
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
|
|
||||||
var parseResult = new RemoteEpisode
|
|
||||||
{
|
|
||||||
Quality = new QualityModel { Quality = Quality.Bluray720p, Proper = false },
|
|
||||||
FullSeason = true,
|
|
||||||
SeasonNumber = 5,
|
|
||||||
Series = new Series { Title = "My Name is earl", CleanTitle = Parser.NormalizeTitle("My Name is earl") },
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().IsInQueue(parseResult);
|
|
||||||
|
|
||||||
result.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static object[] DifferentEpisodeCases =
|
|
||||||
{
|
|
||||||
new object[] { 2, new[] { 5 }, "30 Rock", Quality.Bluray1080p, true }, //Same Series, Different Season, Episode
|
|
||||||
new object[] { 1, new[] { 6 }, "30 Rock", Quality.Bluray1080p, true }, //Same series, different episodes
|
|
||||||
new object[] { 1, new[] { 6, 7, 8 }, "30 Rock", Quality.Bluray1080p, true }, //Same series, different episodes
|
|
||||||
new object[] { 1, new[] { 6 }, "Some other show", Quality.Bluray1080p, true }, //Different series, same season, episode
|
|
||||||
new object[] { 1, new[] { 5 }, "Rock", Quality.Bluray1080p, true }, //Similar series, same season, episodes
|
|
||||||
new object[] { 1, new[] { 5 }, "30 Rock", Quality.Bluray720p, false }, //Same series, higher quality
|
|
||||||
new object[] { 1, new[] { 5 }, "30 Rock", Quality.HDTV720p, true } //Same series, higher quality
|
|
||||||
};
|
|
||||||
|
|
||||||
[Test, TestCaseSource("DifferentEpisodeCases")]
|
|
||||||
public void IsInQueue_should_not_find_diffrent_episode_queue(int season, int[] episodes, string title, Quality qualityType, bool proper)
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
var parseResult = new RemoteEpisode
|
|
||||||
{
|
|
||||||
EpisodeTitle = "Title",
|
|
||||||
EpisodeNumbers = new List<int>(episodes),
|
|
||||||
SeasonNumber = season,
|
|
||||||
Quality = new QualityModel { Quality = qualityType, Proper = proper },
|
|
||||||
Series = new Series { Title = title, CleanTitle = Parser.NormalizeTitle(title) },
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().IsInQueue(parseResult);
|
|
||||||
|
|
||||||
result.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static object[] LowerQualityCases =
|
|
||||||
{
|
|
||||||
new object[] { 1, new[] { 5 }, "30 Rock", Quality.SDTV, false }, //Same Series, lower quality
|
|
||||||
new object[] { 1, new[] { 5 }, "30 rocK", Quality.SDTV, false }, //Same Series, different casing
|
|
||||||
new object[] { 1, new[] { 5 }, "30 RocK", Quality.HDTV720p, false }, //Same Series, same quality
|
|
||||||
new object[] { 1, new[] { 5, 6 }, "30 RocK", Quality.HDTV720p, false }, //Same Series, same quality, one different episode
|
|
||||||
new object[] { 1, new[] { 5, 6 }, "30 RocK", Quality.HDTV720p, false }, //Same Series, same quality, one different episode
|
|
||||||
new object[] { 4, new[] { 8 }, "Parks and Recreation", Quality.WEBDL720p, false }, //Same Series, same quality
|
|
||||||
};
|
|
||||||
|
|
||||||
[Test, TestCaseSource("LowerQualityCases")]
|
|
||||||
public void IsInQueue_should_find_same_or_lower_quality_episode_queue(int season, int[] episodes, string title, Quality qualityType, bool proper)
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
var parseResult = new RemoteEpisode
|
|
||||||
{
|
|
||||||
EpisodeTitle = "Title",
|
|
||||||
EpisodeNumbers = new List<int>(episodes),
|
|
||||||
SeasonNumber = season,
|
|
||||||
Quality = new QualityModel { Quality = qualityType, Proper = proper },
|
|
||||||
Series = new Series { Title = title, CleanTitle = Parser.NormalizeTitle(title) },
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().IsInQueue(parseResult);
|
|
||||||
|
|
||||||
result.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static object[] DuplicateItemsCases =
|
|
||||||
{
|
|
||||||
new object[] { 5, new[] { 13 }, "The Big Bang Theory", Quality.SDTV, false }, //Same Series, lower quality
|
|
||||||
new object[] { 5, new[] { 13 }, "The Big Bang Theory", Quality.HDTV720p, false }, //Same Series, same quality
|
|
||||||
new object[] { 5, new[] { 13 }, "The Big Bang Theory", Quality.HDTV720p, true }, //Same Series, same quality
|
|
||||||
new object[] { 5, new[] { 13, 14 }, "The Big Bang Theory", Quality.HDTV720p, false } //Same Series, same quality, one diffrent episode
|
|
||||||
};
|
|
||||||
|
|
||||||
[Test, TestCaseSource("DuplicateItemsCases")]
|
|
||||||
public void IsInQueue_should_find_items_marked_as_duplicate(int season, int[] episodes, string title, Quality qualityType, bool proper)
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
var parseResult = new RemoteEpisode
|
|
||||||
{
|
|
||||||
EpisodeTitle = "Title",
|
|
||||||
EpisodeNumbers = new List<int>(episodes),
|
|
||||||
SeasonNumber = season,
|
|
||||||
Quality = new QualityModel { Quality = qualityType, Proper = proper },
|
|
||||||
Series = new Series { Title = title, CleanTitle = Parser.NormalizeTitle(title) },
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().IsInQueue(parseResult);
|
|
||||||
|
|
||||||
result.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static object[] DoubleEpisodeCases =
|
|
||||||
{
|
|
||||||
new object[] { 3, new[] { 14, 15 }, "My Name Is Earl", Quality.Bluray720p, false },
|
|
||||||
new object[] { 3, new[] { 15 }, "My Name Is Earl", Quality.DVD, false },
|
|
||||||
new object[] { 3, new[] { 14 }, "My Name Is Earl", Quality.HDTV720p, false },
|
|
||||||
new object[] { 3, new[] { 15, 16 }, "My Name Is Earl", Quality.SDTV, false }
|
|
||||||
};
|
|
||||||
|
|
||||||
[Test, TestCaseSource("DoubleEpisodeCases")]
|
|
||||||
public void IsInQueue_should_find_double_episodes_(int season, int[] episodes, string title, Quality qualityType, bool proper)
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
var parseResult = new RemoteEpisode
|
|
||||||
{
|
|
||||||
EpisodeTitle = "Title",
|
|
||||||
EpisodeNumbers = new List<int>(episodes),
|
|
||||||
SeasonNumber = season,
|
|
||||||
Quality = new QualityModel { Quality = qualityType, Proper = proper },
|
|
||||||
Series = new Series { Title = title, CleanTitle = Parser.NormalizeTitle(title) },
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().IsInQueue(parseResult);
|
|
||||||
|
|
||||||
result.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void IsInQueue_should_return_false_if_queue_is_empty()
|
|
||||||
{
|
|
||||||
WithEmptyQueue();
|
|
||||||
|
|
||||||
var parseResult = new RemoteEpisode
|
|
||||||
{
|
|
||||||
EpisodeTitle = "Title",
|
|
||||||
EpisodeNumbers = new List<int> { 1 },
|
|
||||||
SeasonNumber = 2,
|
|
||||||
Quality = new QualityModel { Quality = Quality.Bluray1080p, Proper = true },
|
|
||||||
Series = new Series { Title = "Test", CleanTitle = Parser.NormalizeTitle("Test") },
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().IsInQueue(parseResult);
|
|
||||||
|
|
||||||
result.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void GetQueue_should_parse_timeleft_with_hours_greater_than_24_hours()
|
|
||||||
{
|
|
||||||
WithFullQueue();
|
|
||||||
|
|
||||||
var result = Mocker.Resolve<SabnzbdClient>().GetQueue();
|
|
||||||
|
|
||||||
result.Should().NotBeEmpty();
|
|
||||||
var timeleft = result.First(q => q.Id == "SABnzbd_nzo_qv6ilb").Timeleft;
|
|
||||||
timeleft.Days.Should().Be(2);
|
|
||||||
timeleft.Hours.Should().Be(9);
|
|
||||||
timeleft.Minutes.Should().Be(27);
|
|
||||||
timeleft.Seconds.Should().Be(45);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TearDown]
|
|
||||||
public void TearDown()
|
|
||||||
{
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}*/
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
|
@ -46,30 +47,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithFailResponse()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHttpProvider>()
|
|
||||||
.Setup(s => s.DownloadString(It.IsAny<String>())).Returns("{ \"status\": false, \"error\": \"API Key Required\" }");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void add_url_should_format_request_properly()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHttpProvider>(MockBehavior.Strict)
|
|
||||||
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=addurl&name=http://www.nzbclub.com/nzb_download.aspx?mid=1950232&priority=0&pp=3&cat=tv&nzbname=My+Series+Name+-+5x2-5x3+-+My+title+%5bBluray720p%5d+%5bProper%5d&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
|
|
||||||
.Returns("{ \"status\": true }");
|
|
||||||
|
|
||||||
|
|
||||||
Subject.DownloadNzb(_remoteEpisode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void add_by_url_should_detect_and_handle_sab_errors()
|
|
||||||
{
|
|
||||||
WithFailResponse();
|
|
||||||
Assert.Throws<ApplicationException>(() => Subject.DownloadNzb(_remoteEpisode));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_be_able_to_get_categories_when_config_is_passed_in()
|
public void should_be_able_to_get_categories_when_config_is_passed_in()
|
||||||
{
|
{
|
||||||
|
@ -195,15 +172,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
|
||||||
result.Should().Be("0.6.9");
|
result.Should().Be("0.6.9");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_throw_when_WebException_is_thrown()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IHttpProvider>()
|
|
||||||
.Setup(s => s.DownloadString(It.IsAny<String>())).Throws(new WebException());
|
|
||||||
|
|
||||||
Assert.Throws<WebException>(() => Subject.DownloadNzb(_remoteEpisode));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void downloadNzb_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
|
public void downloadNzb_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
|
||||||
{
|
{
|
||||||
|
@ -211,16 +179,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
|
||||||
.SetupGet(s => s.SabRecentTvPriority)
|
.SetupGet(s => s.SabRecentTvPriority)
|
||||||
.Returns(SabPriorityType.High);
|
.Returns(SabPriorityType.High);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISabCommunicationProxy>()
|
||||||
Mocker.GetMock<IHttpProvider>()
|
.Setup(s => s.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), (int)SabPriorityType.High))
|
||||||
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=addurl&name=http://www.nzbclub.com/nzb_download.aspx?mid=1950232&priority=1&pp=3&cat=tv&nzbname=My+Series+Name+-+5x2-5x3+-+My+title+%5bBluray720p%5d+%5bProper%5d&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
|
.Returns("{ \"status\": \"true\", \"nzo_ids\": [ \"sab_id_goes_here\" ] }");
|
||||||
.Returns("{ \"status\": true }");
|
|
||||||
|
|
||||||
|
|
||||||
Subject.DownloadNzb(_remoteEpisode);
|
Subject.DownloadNzb(_remoteEpisode);
|
||||||
|
|
||||||
Mocker.GetMock<IHttpProvider>()
|
Mocker.GetMock<ISabCommunicationProxy>()
|
||||||
.Verify(v => v.DownloadString("http://192.168.5.55:2222/api?mode=addurl&name=http://www.nzbclub.com/nzb_download.aspx?mid=1950232&priority=1&pp=3&cat=tv&nzbname=My+Series+Name+-+5x2-5x3+-+My+title+%5bBluray720p%5d+%5bProper%5d&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"), Times.Once());
|
.Verify(v => v.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), (int)SabPriorityType.High), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,6 @@ namespace NzbDrone.Core.Test.Download
|
||||||
VerifyEventNotPublished<EpisodeGrabbedEvent>();
|
VerifyEventNotPublished<EpisodeGrabbedEvent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_attempt_download_if_client_isnt_configure()
|
public void should_not_attempt_download_if_client_isnt_configure()
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Download
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FailedDownloadServiceFixture : CoreTest<FailedDownloadService>
|
||||||
|
{
|
||||||
|
private List<HistoryItem> _completed;
|
||||||
|
private List<HistoryItem> _failed;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_completed = Builder<HistoryItem>.CreateListOfSize(5)
|
||||||
|
.All()
|
||||||
|
.With(h => h.Status = HistoryStatus.Completed)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_failed = Builder<HistoryItem>.CreateListOfSize(1)
|
||||||
|
.All()
|
||||||
|
.With(h => h.Status = HistoryStatus.Failed)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
|
.Setup(c => c.GetDownloadClient()).Returns(Mocker.GetMock<IDownloadClient>().Object);
|
||||||
|
|
||||||
|
Mocker.GetMock<IConfigService>()
|
||||||
|
.SetupGet(s => s.EnableFailedDownloadHandling)
|
||||||
|
.Returns(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenNoGrabbedHistory()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.Grabbed())
|
||||||
|
.Returns(new List<History.History>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenGrabbedHistory(List<History.History> history)
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.Grabbed())
|
||||||
|
.Returns(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenNoFailedHistory()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.Failed())
|
||||||
|
.Returns(new List<History.History>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenFailedHistory(List<History.History> failedHistory)
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(s => s.Failed())
|
||||||
|
.Returns(failedHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenFailedDownloadClientHistory()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDownloadClient>()
|
||||||
|
.Setup(s => s.GetHistory(0, 20))
|
||||||
|
.Returns(_failed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VerifyNoFailedDownloads()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IEventAggregator>()
|
||||||
|
.Verify(v => v.PublishEvent(It.IsAny<DownloadFailedEvent>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VerifyFailedDownloads(int count = 1)
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IEventAggregator>()
|
||||||
|
.Verify(v => v.PublishEvent(It.Is<DownloadFailedEvent>(d => d.EpisodeIds.Count == count)), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_if_no_download_client_history()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDownloadClient>()
|
||||||
|
.Setup(s => s.GetHistory(0, 20))
|
||||||
|
.Returns(new List<HistoryItem>());
|
||||||
|
|
||||||
|
Subject.Execute(new FailedDownloadCommand());
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Verify(s => s.BetweenDates(It.IsAny<DateTime>(), It.IsAny<DateTime>(), HistoryEventType.Grabbed),
|
||||||
|
Times.Never());
|
||||||
|
|
||||||
|
VerifyNoFailedDownloads();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_if_no_failed_items_in_download_client_history()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IDownloadClient>()
|
||||||
|
.Setup(s => s.GetHistory(0, 20))
|
||||||
|
.Returns(_completed);
|
||||||
|
|
||||||
|
Subject.Execute(new FailedDownloadCommand());
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Verify(s => s.BetweenDates(It.IsAny<DateTime>(), It.IsAny<DateTime>(), HistoryEventType.Grabbed),
|
||||||
|
Times.Never());
|
||||||
|
|
||||||
|
VerifyNoFailedDownloads();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_if_matching_history_is_not_found()
|
||||||
|
{
|
||||||
|
GivenNoGrabbedHistory();
|
||||||
|
GivenFailedDownloadClientHistory();
|
||||||
|
|
||||||
|
Subject.Execute(new FailedDownloadCommand());
|
||||||
|
|
||||||
|
VerifyNoFailedDownloads();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_process_if_already_added_to_history_as_failed()
|
||||||
|
{
|
||||||
|
GivenFailedDownloadClientHistory();
|
||||||
|
|
||||||
|
var history = Builder<History.History>.CreateListOfSize(1)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
GivenGrabbedHistory(history);
|
||||||
|
GivenFailedHistory(history);
|
||||||
|
|
||||||
|
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||||
|
history.First().Data.Add("downloadClientId", _failed.First().Id);
|
||||||
|
|
||||||
|
Subject.Execute(new FailedDownloadCommand());
|
||||||
|
|
||||||
|
VerifyNoFailedDownloads();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_process_if_not_already_in_failed_history()
|
||||||
|
{
|
||||||
|
GivenFailedDownloadClientHistory();
|
||||||
|
|
||||||
|
var history = Builder<History.History>.CreateListOfSize(1)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
GivenGrabbedHistory(history);
|
||||||
|
GivenNoFailedHistory();
|
||||||
|
|
||||||
|
history.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||||
|
history.First().Data.Add("downloadClientId", _failed.First().Id);
|
||||||
|
|
||||||
|
Subject.Execute(new FailedDownloadCommand());
|
||||||
|
|
||||||
|
VerifyFailedDownloads();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_have_multiple_episode_ids_when_multi_episode_release_fails()
|
||||||
|
{
|
||||||
|
GivenFailedDownloadClientHistory();
|
||||||
|
|
||||||
|
var history = Builder<History.History>.CreateListOfSize(2)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
GivenGrabbedHistory(history);
|
||||||
|
GivenNoFailedHistory();
|
||||||
|
|
||||||
|
history.ForEach(h =>
|
||||||
|
{
|
||||||
|
h.Data.Add("downloadClient", "SabnzbdClient");
|
||||||
|
h.Data.Add("downloadClientId", _failed.First().Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
Subject.Execute(new FailedDownloadCommand());
|
||||||
|
|
||||||
|
VerifyFailedDownloads(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_skip_if_enable_failed_download_handling_is_off()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IConfigService>()
|
||||||
|
.SetupGet(s => s.EnableFailedDownloadHandling)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
Subject.Execute(new FailedDownloadCommand());
|
||||||
|
|
||||||
|
VerifyNoFailedDownloads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
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;
|
using NzbDrone.Test.Common;
|
||||||
|
@ -36,106 +37,46 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
||||||
{
|
{
|
||||||
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
|
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi",
|
||||||
Episodes = episodes,
|
Episodes = episodes,
|
||||||
Series = _series
|
Series = _series,
|
||||||
|
Quality = new QualityModel(Quality.HDTV720p)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithDailySeries()
|
private void GivenFileSize(long size)
|
||||||
{
|
|
||||||
_series.SeriesType = SeriesTypes.Daily;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithSeasonZero()
|
|
||||||
{
|
|
||||||
_localEpisode.Episodes[0].SeasonNumber = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithFileSize(long size)
|
|
||||||
{
|
{
|
||||||
_localEpisode.Size = size;
|
_localEpisode.Size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithLength(int minutes)
|
private void GivenRuntime(int seconds)
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IVideoFileInfoReader>()
|
Mocker.GetMock<IVideoFileInfoReader>()
|
||||||
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
.Setup(s => s.GetRunTime(It.IsAny<String>()))
|
||||||
.Returns(new TimeSpan(0, 0, minutes, 0));
|
.Returns(new TimeSpan(0, 0, seconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_true_if_series_is_daily()
|
public void should_return_true_if_series_is_daily()
|
||||||
{
|
{
|
||||||
WithDailySeries();
|
_series.SeriesType = SeriesTypes.Daily;
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_true_if_season_zero()
|
public void should_return_true_if_season_zero()
|
||||||
{
|
{
|
||||||
WithSeasonZero();
|
_localEpisode.Episodes[0].SeasonNumber = 0;
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_false_if_undersize_and_under_length()
|
public void should_return_true_for_existing_file()
|
||||||
{
|
{
|
||||||
WithFileSize(10.Megabytes());
|
_localEpisode.ExistingFile = true;
|
||||||
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();
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_true_if_under_length()
|
public void should_return_true_for_flv()
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_not_check_lenght_if_file_is_large_enough()
|
|
||||||
{
|
|
||||||
WithFileSize(100.Megabytes());
|
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
|
||||||
|
|
||||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_log_error_if_run_time_is_0_and_under_sample_size()
|
|
||||||
{
|
|
||||||
WithFileSize(40.Megabytes());
|
|
||||||
WithLength(0);
|
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_skip_check_for_flv_file()
|
|
||||||
{
|
{
|
||||||
_localEpisode.Path = @"C:\Test\some.show.s01e01.flv";
|
_localEpisode.Path = @"C:\Test\some.show.s01e01.flv";
|
||||||
|
|
||||||
|
@ -143,5 +84,70 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
||||||
|
|
||||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
|
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_run_runtime_check_on_linux()
|
||||||
|
{
|
||||||
|
LinuxOnly();
|
||||||
|
GivenFileSize(1000.Megabytes());
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode);
|
||||||
|
|
||||||
|
Mocker.GetMock<IVideoFileInfoReader>().Verify(v => v.GetRunTime(It.IsAny<String>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_run_runtime_check_on_windows()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
GivenRuntime(120);
|
||||||
|
GivenFileSize(1000.Megabytes());
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode);
|
||||||
|
|
||||||
|
Mocker.GetMock<IVideoFileInfoReader>().Verify(v => v.GetRunTime(It.IsAny<String>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_runtime_is_less_than_minimum()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
GivenRuntime(60);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_true_if_runtime_greater_than_than_minimum()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
GivenRuntime(120);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_file_size_is_under_minimum()
|
||||||
|
{
|
||||||
|
LinuxOnly();
|
||||||
|
|
||||||
|
GivenRuntime(120);
|
||||||
|
GivenFileSize(20.Megabytes());
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_file_size_is_under_minimum_for_larger_limits()
|
||||||
|
{
|
||||||
|
LinuxOnly();
|
||||||
|
|
||||||
|
GivenRuntime(120);
|
||||||
|
GivenFileSize(120.Megabytes());
|
||||||
|
_localEpisode.Quality = new QualityModel(Quality.Bluray1080p);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,8 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_true_when_in_old_working_folder()
|
public void should_return_true_when_in_old_working_folder()
|
||||||
{
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
GivenInWorkingFolder();
|
GivenInWorkingFolder();
|
||||||
GivenLastWriteTimeUtc(DateTime.UtcNow.AddHours(-1));
|
GivenLastWriteTimeUtc(DateTime.UtcNow.AddHours(-1));
|
||||||
|
|
||||||
|
@ -68,5 +70,16 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_false_if_unopacking_on_linux()
|
||||||
|
{
|
||||||
|
LinuxOnly();
|
||||||
|
|
||||||
|
GivenInWorkingFolder();
|
||||||
|
GivenLastWriteTimeUtc(DateTime.UtcNow.AddDays(-5));
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,8 @@
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Blacklisting\BlacklistServiceFixture.cs" />
|
||||||
|
<Compile Include="Blacklisting\BlacklistRepositoryFixture.cs" />
|
||||||
<Compile Include="DataAugmentationFixture\Scene\SceneMappingProxyFixture.cs" />
|
<Compile Include="DataAugmentationFixture\Scene\SceneMappingProxyFixture.cs" />
|
||||||
<Compile Include="DataAugmentationFixture\Scene\SceneMappingServiceFixture.cs" />
|
<Compile Include="DataAugmentationFixture\Scene\SceneMappingServiceFixture.cs" />
|
||||||
<Compile Include="Datastore\BasicRepositoryFixture.cs" />
|
<Compile Include="Datastore\BasicRepositoryFixture.cs" />
|
||||||
|
@ -121,9 +123,9 @@
|
||||||
<Compile Include="Download\DownloadClientTests\NzbgetProviderTests\DownloadNzbFixture.cs" />
|
<Compile Include="Download\DownloadClientTests\NzbgetProviderTests\DownloadNzbFixture.cs" />
|
||||||
<Compile Include="Download\DownloadClientTests\NzbgetProviderTests\QueueFixture.cs" />
|
<Compile Include="Download\DownloadClientTests\NzbgetProviderTests\QueueFixture.cs" />
|
||||||
<Compile Include="Download\DownloadClientTests\PneumaticProviderFixture.cs" />
|
<Compile Include="Download\DownloadClientTests\PneumaticProviderFixture.cs" />
|
||||||
<Compile Include="Download\DownloadClientTests\SabProviderTests\QueueFixture.cs" />
|
|
||||||
<Compile Include="Download\DownloadClientTests\SabProviderTests\SabProviderFixture.cs" />
|
<Compile Include="Download\DownloadClientTests\SabProviderTests\SabProviderFixture.cs" />
|
||||||
<Compile Include="Download\DownloadServiceFixture.cs" />
|
<Compile Include="Download\DownloadServiceFixture.cs" />
|
||||||
|
<Compile Include="Download\FailedDownloadServiceFixture.cs" />
|
||||||
<Compile Include="Framework\CoreTest.cs" />
|
<Compile Include="Framework\CoreTest.cs" />
|
||||||
<Compile Include="Framework\DbTest.cs" />
|
<Compile Include="Framework\DbTest.cs" />
|
||||||
<Compile Include="Framework\NBuilderExtensions.cs" />
|
<Compile Include="Framework\NBuilderExtensions.cs" />
|
||||||
|
@ -167,7 +169,9 @@
|
||||||
<Compile Include="NotificationTests\Xbmc\Json\UpdateFixture.cs" />
|
<Compile Include="NotificationTests\Xbmc\Json\UpdateFixture.cs" />
|
||||||
<Compile Include="OrganizerTests\BuildFilePathFixture.cs" />
|
<Compile Include="OrganizerTests\BuildFilePathFixture.cs" />
|
||||||
<Compile Include="ParserTests\ParsingServiceTests\GetEpisodesFixture.cs" />
|
<Compile Include="ParserTests\ParsingServiceTests\GetEpisodesFixture.cs" />
|
||||||
|
<Compile Include="ParserTests\ParsingServiceTests\GetSeriesFixture.cs" />
|
||||||
<Compile Include="ParserTests\ParsingServiceTests\MapFixture.cs" />
|
<Compile Include="ParserTests\ParsingServiceTests\MapFixture.cs" />
|
||||||
|
<Compile Include="ParserTests\SeriesTitleInfoFixture.cs" />
|
||||||
<Compile Include="Providers\XemProxyFixture.cs" />
|
<Compile Include="Providers\XemProxyFixture.cs" />
|
||||||
<Compile Include="Qualities\QualitySizeRepositoryFixture.cs" />
|
<Compile Include="Qualities\QualitySizeRepositoryFixture.cs" />
|
||||||
<Compile Include="Qualities\QualityProfileRepositoryFixture.cs" />
|
<Compile Include="Qualities\QualityProfileRepositoryFixture.cs" />
|
||||||
|
@ -193,7 +197,7 @@
|
||||||
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesBetweenDatesFixture.cs" />
|
<Compile Include="TvTests\EpisodeRepositoryTests\EpisodesBetweenDatesFixture.cs" />
|
||||||
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
|
||||||
<Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
|
||||||
<Compile Include="DecisionEngineTests\UpgradeHistorySpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\HistorySpecificationFixture.cs" />
|
||||||
<Compile Include="DecisionEngineTests\UpgradeDiskSpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\UpgradeDiskSpecificationFixture.cs" />
|
||||||
<Compile Include="DecisionEngineTests\QualityUpgradeSpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\QualityUpgradeSpecificationFixture.cs" />
|
||||||
<Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceFixture.cs" />
|
<Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceFixture.cs" />
|
||||||
|
|
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||||
using NzbDrone.Common.Expansive;
|
using NzbDrone.Common.Expansive;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ParserTests
|
namespace NzbDrone.Core.Test.ParserTests
|
||||||
|
@ -21,6 +22,9 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
* [TestCase("Desparate Housewives - S07E22 - 7x23 - And Lots of Security.. [HDTV-720p].mkv", "Desparate Housewives", 7, new[] { 22, 23 }, 2)]
|
* [TestCase("Desparate Housewives - S07E22 - 7x23 - And Lots of Security.. [HDTV-720p].mkv", "Desparate Housewives", 7, new[] { 22, 23 }, 2)]
|
||||||
* [TestCase("S07E22 - 7x23 - And Lots of Security.. [HDTV-720p].mkv", "", 7, new[] { 22, 23 }, 2)]
|
* [TestCase("S07E22 - 7x23 - And Lots of Security.. [HDTV-720p].mkv", "", 7, new[] { 22, 23 }, 2)]
|
||||||
* (Game of Thrones s03 e - "Game of Thrones Season 3 Episode 10"
|
* (Game of Thrones s03 e - "Game of Thrones Season 3 Episode 10"
|
||||||
|
* The.Man.of.Steel.1994-05.33.hybrid.DreamGirl-Novus-HD
|
||||||
|
* Superman.-.The.Man.of.Steel.1994-06.34.hybrid.DreamGirl-Novus-HD
|
||||||
|
* Superman.-.The.Man.of.Steel.1994-05.33.hybrid.DreamGirl-Novus-HD
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[TestCase("Sonny.With.a.Chance.S02E15", "Sonny.With.a.Chance", 2, 15)]
|
[TestCase("Sonny.With.a.Chance.S02E15", "Sonny.With.a.Chance", 2, 15)]
|
||||||
|
@ -80,6 +84,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
[TestCase("(Game of Thrones s03 e - \"Game of Thrones Season 3 Episode 10\"", "Game of Thrones", 3, 10)]
|
[TestCase("(Game of Thrones s03 e - \"Game of Thrones Season 3 Episode 10\"", "Game of Thrones", 3, 10)]
|
||||||
[TestCase("House.Hunters.International.S05E607.720p.hdtv.x264", "House.Hunters.International", 5, 607)]
|
[TestCase("House.Hunters.International.S05E607.720p.hdtv.x264", "House.Hunters.International", 5, 607)]
|
||||||
[TestCase("Adventure.Time.With.Finn.And.Jake.S01E20.720p.BluRay.x264-DEiMOS", "Adventure.Time.With.Finn.And.Jake", 1, 20)]
|
[TestCase("Adventure.Time.With.Finn.And.Jake.S01E20.720p.BluRay.x264-DEiMOS", "Adventure.Time.With.Finn.And.Jake", 1, 20)]
|
||||||
|
[TestCase("Hostages.S01E04.2-45.PM.[HDTV-720p].mkv", "Hostages", 1, 4)]
|
||||||
public void ParseTitle_single(string postTitle, string title, int seasonNumber, int episodeNumber)
|
public void ParseTitle_single(string postTitle, string title, int seasonNumber, int episodeNumber)
|
||||||
{
|
{
|
||||||
var result = Parser.Parser.ParseTitle(postTitle);
|
var result = Parser.Parser.ParseTitle(postTitle);
|
||||||
|
@ -168,7 +173,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
var airDate = new DateTime(year, month, day);
|
var airDate = new DateTime(year, month, day);
|
||||||
result.Should().NotBeNull();
|
result.Should().NotBeNull();
|
||||||
result.SeriesTitle.Should().Be(title.CleanSeriesTitle());
|
result.SeriesTitle.Should().Be(title.CleanSeriesTitle());
|
||||||
result.AirDate.Should().Be(airDate);
|
result.AirDate.Should().Be(airDate.ToString(Episode.AIR_DATE_FORMAT));
|
||||||
result.EpisodeNumbers.Should().BeNull();
|
result.EpisodeNumbers.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,6 +235,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||||
[TestCase("The.Daily.Show", "dailyshow")]
|
[TestCase("The.Daily.Show", "dailyshow")]
|
||||||
[TestCase("Castle (2009)", "castle2009")]
|
[TestCase("Castle (2009)", "castle2009")]
|
||||||
[TestCase("Parenthood.2010", "parenthood2010")]
|
[TestCase("Parenthood.2010", "parenthood2010")]
|
||||||
|
[TestCase("Law_and_Order_SVU", "lawordersvu")]
|
||||||
public void series_name_normalize(string parsedSeriesName, string seriesName)
|
public void series_name_normalize(string parsedSeriesName, string seriesName)
|
||||||
{
|
{
|
||||||
var result = parsedSeriesName.CleanSeriesTitle();
|
var result = parsedSeriesName.CleanSeriesTitle();
|
||||||
|
|
|
@ -61,7 +61,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
|
|
||||||
private void GivenDailyParseResult()
|
private void GivenDailyParseResult()
|
||||||
{
|
{
|
||||||
_parsedEpisodeInfo.AirDate = DateTime.Today;
|
_parsedEpisodeInfo.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenSceneNumberingSeries()
|
private void GivenSceneNumberingSeries()
|
||||||
|
@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
Subject.Map(_parsedEpisodeInfo, _series.TvRageId);
|
Subject.Map(_parsedEpisodeInfo, _series.TvRageId);
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>()
|
Mocker.GetMock<IEpisodeService>()
|
||||||
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Once());
|
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<String>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -90,19 +90,19 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
|
Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>()
|
Mocker.GetMock<IEpisodeService>()
|
||||||
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Never());
|
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<String>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_fallback_to_daily_episode_lookup_when_search_criteria_episode_doesnt_match()
|
public void should_fallback_to_daily_episode_lookup_when_search_criteria_episode_doesnt_match()
|
||||||
{
|
{
|
||||||
GivenDailySeries();
|
GivenDailySeries();
|
||||||
_parsedEpisodeInfo.AirDate = DateTime.Today.AddDays(-5);
|
_parsedEpisodeInfo.AirDate = DateTime.Today.AddDays(-5).ToString(Episode.AIR_DATE_FORMAT); ;
|
||||||
|
|
||||||
Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
|
Subject.Map(_parsedEpisodeInfo, _series.TvRageId, _singleEpisodeSearchCriteria);
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>()
|
Mocker.GetMock<IEpisodeService>()
|
||||||
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<DateTime>()), Times.Once());
|
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<String>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class GetSeriesFixture : CoreTest<ParsingService>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_use_passed_in_title_when_it_cannot_be_parsed()
|
||||||
|
{
|
||||||
|
const string title = "30 Rock";
|
||||||
|
|
||||||
|
Subject.GetSeries(title);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISeriesService>()
|
||||||
|
.Verify(s => s.FindByTitle(title), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_use_parsed_series_title()
|
||||||
|
{
|
||||||
|
const string title = "30.Rock.S01E01.720p.hdtv";
|
||||||
|
|
||||||
|
Subject.GetSeries(title);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISeriesService>()
|
||||||
|
.Verify(s => s.FindByTitle(Parser.Parser.ParseTitle(title).SeriesTitle), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_fallback_to_title_without_year_and_year_when_title_lookup_fails()
|
||||||
|
{
|
||||||
|
const string title = "House.2004.S01E01.720p.hdtv";
|
||||||
|
var parsedEpisodeInfo = Parser.Parser.ParseTitle(title);
|
||||||
|
|
||||||
|
Subject.GetSeries(title);
|
||||||
|
|
||||||
|
Mocker.GetMock<ISeriesService>()
|
||||||
|
.Verify(s => s.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
|
||||||
|
parsedEpisodeInfo.SeriesTitleInfo.Year), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ParserTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SeriesTitleInfoFixture : CoreTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_have_year_zero_when_title_doesnt_have_a_year()
|
||||||
|
{
|
||||||
|
const string title = "House.S01E01.pilot.720p.hdtv";
|
||||||
|
|
||||||
|
var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
|
||||||
|
|
||||||
|
result.Year.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_have_same_title_for_title_and_title_without_year_when_title_doesnt_have_a_year()
|
||||||
|
{
|
||||||
|
const string title = "House.S01E01.pilot.720p.hdtv";
|
||||||
|
|
||||||
|
var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
|
||||||
|
|
||||||
|
result.Title.Should().Be(result.TitleWithoutYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_have_year_when_title_has_a_year()
|
||||||
|
{
|
||||||
|
const string title = "House.2004.S01E01.pilot.720p.hdtv";
|
||||||
|
|
||||||
|
var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
|
||||||
|
|
||||||
|
result.Year.Should().Be(2004);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_have_year_in_title_when_title_has_a_year()
|
||||||
|
{
|
||||||
|
const string title = "House.2004.S01E01.pilot.720p.hdtv";
|
||||||
|
|
||||||
|
var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
|
||||||
|
|
||||||
|
result.Title.Should().Be("house2004");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_title_without_year_should_not_contain_year()
|
||||||
|
{
|
||||||
|
const string title = "House.2004.S01E01.pilot.720p.hdtv";
|
||||||
|
|
||||||
|
var result = Parser.Parser.ParseTitle(title).SeriesTitleInfo;
|
||||||
|
|
||||||
|
result.TitleWithoutYear.Should().Be("house");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Blacklisting
|
||||||
|
{
|
||||||
|
public class Blacklist : ModelBase
|
||||||
|
{
|
||||||
|
public int SeriesId { get; set; }
|
||||||
|
public List<int> EpisodeIds { get; set; }
|
||||||
|
public string SourceTitle { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Blacklisting
|
||||||
|
{
|
||||||
|
public interface IBlacklistRepository : IBasicRepository<Blacklist>
|
||||||
|
{
|
||||||
|
bool Blacklisted(string sourceTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlacklistRepository : BasicRepository<Blacklist>, IBlacklistRepository
|
||||||
|
{
|
||||||
|
public BlacklistRepository(IDatabase database, IEventAggregator eventAggregator) :
|
||||||
|
base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Blacklisted(string sourceTitle)
|
||||||
|
{
|
||||||
|
return Query.Any(e => e.SourceTitle.Contains(sourceTitle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Blacklisting
|
||||||
|
{
|
||||||
|
public interface IBlacklistService
|
||||||
|
{
|
||||||
|
bool Blacklisted(string sourceTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlacklistService : IBlacklistService, IHandle<DownloadFailedEvent>
|
||||||
|
{
|
||||||
|
private readonly IBlacklistRepository _blacklistRepository;
|
||||||
|
private readonly IRedownloadFailedDownloads _redownloadFailedDownloadService;
|
||||||
|
|
||||||
|
public BlacklistService(IBlacklistRepository blacklistRepository, IRedownloadFailedDownloads redownloadFailedDownloadService)
|
||||||
|
{
|
||||||
|
_blacklistRepository = blacklistRepository;
|
||||||
|
_redownloadFailedDownloadService = redownloadFailedDownloadService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Blacklisted(string sourceTitle)
|
||||||
|
{
|
||||||
|
return _blacklistRepository.Blacklisted(sourceTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(DownloadFailedEvent message)
|
||||||
|
{
|
||||||
|
var blacklist = new Blacklist
|
||||||
|
{
|
||||||
|
SeriesId = message.SeriesId,
|
||||||
|
EpisodeIds = message.EpisodeIds,
|
||||||
|
SourceTitle = message.SourceTitle,
|
||||||
|
Quality = message.Quality,
|
||||||
|
Date = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
_blacklistRepository.Insert(blacklist);
|
||||||
|
|
||||||
|
_redownloadFailedDownloadService.Redownload(message.SeriesId, message.EpisodeIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -261,6 +261,27 @@ namespace NzbDrone.Core.Configuration
|
||||||
set { SetValue("AutoDownloadPropers", value); }
|
set { SetValue("AutoDownloadPropers", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean AutoRedownloadFailed
|
||||||
|
{
|
||||||
|
get { return GetValueBoolean("AutoRedownloadFailed", true); }
|
||||||
|
|
||||||
|
set { SetValue("AutoRedownloadFailed", value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean RemoveFailedDownloads
|
||||||
|
{
|
||||||
|
get { return GetValueBoolean("RemoveFailedDownloads", true); }
|
||||||
|
|
||||||
|
set { SetValue("RemoveFailedDownloads", value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean EnableFailedDownloadHandling
|
||||||
|
{
|
||||||
|
get { return GetValueBoolean("EnableFailedDownloadHandling", true); }
|
||||||
|
|
||||||
|
set { SetValue("EnableFailedDownloadHandling", value); }
|
||||||
|
}
|
||||||
|
|
||||||
public string DownloadClientWorkingFolders
|
public string DownloadClientWorkingFolders
|
||||||
{
|
{
|
||||||
get { return GetValue("DownloadClientWorkingFolders", "_UNPACK_|_FAILED_"); }
|
get { return GetValue("DownloadClientWorkingFolders", "_UNPACK_|_FAILED_"); }
|
||||||
|
|
|
@ -39,6 +39,9 @@ namespace NzbDrone.Core.Configuration
|
||||||
Int32 RssSyncInterval { get; set; }
|
Int32 RssSyncInterval { get; set; }
|
||||||
Boolean AutoDownloadPropers { get; set; }
|
Boolean AutoDownloadPropers { get; set; }
|
||||||
String DownloadClientWorkingFolders { get; set; }
|
String DownloadClientWorkingFolders { get; set; }
|
||||||
|
Boolean AutoRedownloadFailed { get; set; }
|
||||||
|
Boolean RemoveFailedDownloads { get; set; }
|
||||||
|
Boolean EnableFailedDownloadHandling { get; set; }
|
||||||
void SaveValues(Dictionary<string, object> configValues);
|
void SaveValues(Dictionary<string, object> configValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(28)]
|
||||||
|
public class add_blacklist_table : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Create.TableForModel("Blacklist")
|
||||||
|
.WithColumn("SeriesId").AsInt32()
|
||||||
|
.WithColumn("EpisodeIds").AsString()
|
||||||
|
.WithColumn("SourceTitle").AsString()
|
||||||
|
.WithColumn("Quality").AsString()
|
||||||
|
.WithColumn("Date").AsDateTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using Marr.Data;
|
using Marr.Data;
|
||||||
using Marr.Data.Mapping;
|
using Marr.Data.Mapping;
|
||||||
using NzbDrone.Common.Reflection;
|
using NzbDrone.Common.Reflection;
|
||||||
|
using NzbDrone.Core.Blacklisting;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.DataAugmentation.Scene;
|
using NzbDrone.Core.DataAugmentation.Scene;
|
||||||
using NzbDrone.Core.Datastore.Converters;
|
using NzbDrone.Core.Datastore.Converters;
|
||||||
|
@ -67,6 +68,8 @@ namespace NzbDrone.Core.Datastore
|
||||||
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
||||||
|
|
||||||
Mapper.Entity<SeriesStatistics>().MapResultSet();
|
Mapper.Entity<SeriesStatistics>().MapResultSet();
|
||||||
|
|
||||||
|
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterMappers()
|
private static void RegisterMappers()
|
||||||
|
@ -80,6 +83,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(Enum), new EnumIntConverter());
|
MapRepository.Instance.RegisterTypeConverter(typeof(Enum), new EnumIntConverter());
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(Quality), new QualityIntConverter());
|
MapRepository.Instance.RegisterTypeConverter(typeof(Quality), new QualityIntConverter());
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
|
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
|
||||||
|
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterProviderSettingConverter()
|
private static void RegisterProviderSettingConverter()
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Blacklisting;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
|
{
|
||||||
|
public class BlacklistSpecification : IDecisionEngineSpecification
|
||||||
|
{
|
||||||
|
private readonly IBlacklistService _blacklistService;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public BlacklistSpecification(IBlacklistService blacklistService, IConfigService configService, Logger logger)
|
||||||
|
{
|
||||||
|
_blacklistService = blacklistService;
|
||||||
|
_configService = configService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RejectionReason
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "Release is blacklisted";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||||
|
{
|
||||||
|
if (!_configService.EnableFailedDownloadHandling)
|
||||||
|
{
|
||||||
|
_logger.Trace("Failed Download Handling is not enabled");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_blacklistService.Blacklisted(subject.Release.Title))
|
||||||
|
{
|
||||||
|
_logger.Trace("{0} is blacklisted", subject.Release.Title);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,28 @@
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Download.Clients.Sabnzbd;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.MetadataSource.Trakt;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||||
{
|
{
|
||||||
public class UpgradeHistorySpecification : IDecisionEngineSpecification
|
public class HistorySpecification : IDecisionEngineSpecification
|
||||||
{
|
{
|
||||||
private readonly IHistoryService _historyService;
|
private readonly IHistoryService _historyService;
|
||||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||||
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public UpgradeHistorySpecification(IHistoryService historyService, QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger)
|
public HistorySpecification(IHistoryService historyService,
|
||||||
|
QualityUpgradableSpecification qualityUpgradableSpecification,
|
||||||
|
IProvideDownloadClient downloadClientProvider,
|
||||||
|
Logger logger)
|
||||||
{
|
{
|
||||||
_historyService = historyService;
|
_historyService = historyService;
|
||||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||||
|
_downloadClientProvider = downloadClientProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +42,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_downloadClientProvider.GetDownloadClient().GetType() == typeof (SabnzbdClient))
|
||||||
|
{
|
||||||
|
_logger.Trace("Performing history status check on report");
|
||||||
|
foreach (var episode in subject.Episodes)
|
||||||
|
{
|
||||||
|
_logger.Trace("Checking current status of episode [{0}] in history", episode.Id);
|
||||||
|
var mostRecent = _historyService.MostRecentForEpisode(episode.Id);
|
||||||
|
|
||||||
|
if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var episode in subject.Episodes)
|
foreach (var episode in subject.Episodes)
|
||||||
{
|
{
|
||||||
var bestQualityInHistory = _historyService.GetBestQualityInHistory(episode.Id);
|
var bestQualityInHistory = _historyService.GetBestQualityInHistory(episode.Id);
|
|
@ -34,9 +34,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||||
|
|
||||||
if (dailySearchSpec == null) return true;
|
if (dailySearchSpec == null) return true;
|
||||||
|
|
||||||
var episode = _episodeService.GetEpisode(dailySearchSpec.Series.Id, dailySearchSpec.Airtime);
|
var episode = _episodeService.GetEpisode(dailySearchSpec.Series.Id, dailySearchSpec.AirDate.ToString(Episode.AIR_DATE_FORMAT));
|
||||||
|
|
||||||
if (!remoteEpisode.ParsedEpisodeInfo.AirDate.HasValue || remoteEpisode.ParsedEpisodeInfo.AirDate.Value.ToString(Episode.AIR_DATE_FORMAT) != episode.AirDate)
|
if (!remoteEpisode.ParsedEpisodeInfo.IsDaily() || remoteEpisode.ParsedEpisodeInfo.AirDate != episode.AirDate)
|
||||||
{
|
{
|
||||||
_logger.Trace("Episode AirDate does not match searched episode number, skipping.");
|
_logger.Trace("Episode AirDate does not match searched episode number, skipping.");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
|
@ -22,7 +23,7 @@ namespace NzbDrone.Core.Download.Clients
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DownloadNzb(RemoteEpisode remoteEpisode)
|
public string DownloadNzb(RemoteEpisode remoteEpisode)
|
||||||
{
|
{
|
||||||
var url = remoteEpisode.Release.DownloadUrl;
|
var url = remoteEpisode.Release.DownloadUrl;
|
||||||
var title = remoteEpisode.Release.Title;
|
var title = remoteEpisode.Release.Title;
|
||||||
|
@ -34,8 +35,9 @@ namespace NzbDrone.Core.Download.Clients
|
||||||
|
|
||||||
_logger.Trace("Downloading NZB from: {0} to: {1}", url, filename);
|
_logger.Trace("Downloading NZB from: {0} to: {1}", url, filename);
|
||||||
_httpProvider.DownloadFile(url, filename);
|
_httpProvider.DownloadFile(url, filename);
|
||||||
|
|
||||||
_logger.Trace("NZB Download succeeded, saved to: {0}", filename);
|
_logger.Trace("NZB Download succeeded, saved to: {0}", filename);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsConfigured
|
public bool IsConfigured
|
||||||
|
@ -50,5 +52,18 @@ namespace NzbDrone.Core.Download.Clients
|
||||||
{
|
{
|
||||||
return new QueueItem[0];
|
return new QueueItem[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
|
||||||
|
{
|
||||||
|
return new HistoryItem[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFromQueue(string id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFromHistory(string id)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DownloadNzb(RemoteEpisode remoteEpisode)
|
public string DownloadNzb(RemoteEpisode remoteEpisode)
|
||||||
{
|
{
|
||||||
var url = remoteEpisode.Release.DownloadUrl;
|
var url = remoteEpisode.Release.DownloadUrl;
|
||||||
var title = remoteEpisode.Release.Title + ".nzb";
|
var title = remoteEpisode.Release.Title + ".nzb";
|
||||||
|
@ -46,6 +46,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
var success = Json.Deserialize<EnqueueResponse>(response).Result;
|
var success = Json.Deserialize<EnqueueResponse>(response).Result;
|
||||||
_logger.Debug("Queue Response: [{0}]", success);
|
_logger.Debug("Queue Response: [{0}]", success);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsConfigured
|
public bool IsConfigured
|
||||||
|
@ -90,6 +91,21 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
|
||||||
|
{
|
||||||
|
return new HistoryItem[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFromQueue(string id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFromHistory(string id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public virtual VersionModel GetVersion(string host = null, int port = 0, string username = null, string password = null)
|
public virtual VersionModel GetVersion(string host = null, int port = 0, string username = null, string password = null)
|
||||||
{
|
{
|
||||||
//Get saved values if any of these are defaults
|
//Get saved values if any of these are defaults
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace NzbDrone.Core.Download.Clients
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DownloadNzb(RemoteEpisode remoteEpisode)
|
public string DownloadNzb(RemoteEpisode remoteEpisode)
|
||||||
{
|
{
|
||||||
var url = remoteEpisode.Release.DownloadUrl;
|
var url = remoteEpisode.Release.DownloadUrl;
|
||||||
var title = remoteEpisode.Release.Title;
|
var title = remoteEpisode.Release.Title;
|
||||||
|
@ -41,8 +41,6 @@ namespace NzbDrone.Core.Download.Clients
|
||||||
//Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC)
|
//Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC)
|
||||||
var filename = Path.Combine(_configService.PneumaticFolder, title + ".nzb");
|
var filename = Path.Combine(_configService.PneumaticFolder, title + ".nzb");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
logger.Trace("Downloading NZB from: {0} to: {1}", url, filename);
|
logger.Trace("Downloading NZB from: {0} to: {1}", url, filename);
|
||||||
_httpProvider.DownloadFile(url, filename);
|
_httpProvider.DownloadFile(url, filename);
|
||||||
|
|
||||||
|
@ -50,6 +48,8 @@ namespace NzbDrone.Core.Download.Clients
|
||||||
|
|
||||||
var contents = String.Format("plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb={0}&nzbname={1}", filename, title);
|
var contents = String.Format("plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb={0}&nzbname={1}", filename, title);
|
||||||
_diskProvider.WriteAllText(Path.Combine(_configService.DownloadedEpisodesFolder, title + ".strm"), contents);
|
_diskProvider.WriteAllText(Path.Combine(_configService.DownloadedEpisodesFolder, title + ".strm"), contents);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsConfigured
|
public bool IsConfigured
|
||||||
|
@ -65,6 +65,19 @@ namespace NzbDrone.Core.Download.Clients
|
||||||
return new QueueItem[0];
|
return new QueueItem[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
|
||||||
|
{
|
||||||
|
return new HistoryItem[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFromQueue(string id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFromHistory(string id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool IsInQueue(RemoteEpisode newEpisode)
|
public virtual bool IsInQueue(RemoteEpisode newEpisode)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
|
{
|
||||||
|
public interface ISabCommunicationProxy
|
||||||
|
{
|
||||||
|
string DownloadNzb(Stream nzb, string name, string category, int priority);
|
||||||
|
void RemoveFrom(string source, string id);
|
||||||
|
string ProcessRequest(IRestRequest restRequest, string action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SabCommunicationProxy : ISabCommunicationProxy
|
||||||
|
{
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
|
||||||
|
public SabCommunicationProxy(IConfigService configService)
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DownloadNzb(Stream nzb, string title, string category, int priority)
|
||||||
|
{
|
||||||
|
var request = new RestRequest(Method.POST);
|
||||||
|
var action = String.Format("mode=addfile&cat={0}&priority={1}", category, priority);
|
||||||
|
|
||||||
|
request.AddFile("name", ReadFully(nzb), title, "application/x-nzb");
|
||||||
|
|
||||||
|
return ProcessRequest(request, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFrom(string source, string id)
|
||||||
|
{
|
||||||
|
var request = new RestRequest();
|
||||||
|
var action = String.Format("mode={0}&name=delete&del_files=1&value={1}", source, id);
|
||||||
|
|
||||||
|
ProcessRequest(request, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ProcessRequest(IRestRequest restRequest, string action)
|
||||||
|
{
|
||||||
|
var client = BuildClient(action);
|
||||||
|
var response = client.Execute(restRequest);
|
||||||
|
|
||||||
|
CheckForError(response);
|
||||||
|
|
||||||
|
return response.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRestClient BuildClient(string action)
|
||||||
|
{
|
||||||
|
var protocol = _configService.SabUseSsl ? "https" : "http";
|
||||||
|
|
||||||
|
var url = string.Format(@"{0}://{1}:{2}/api?{3}&apikey={4}&ma_username={5}&ma_password={6}&output=json",
|
||||||
|
protocol,
|
||||||
|
_configService.SabHost,
|
||||||
|
_configService.SabPort,
|
||||||
|
action,
|
||||||
|
_configService.SabApiKey,
|
||||||
|
_configService.SabUsername,
|
||||||
|
_configService.SabPassword);
|
||||||
|
|
||||||
|
return new RestClient(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckForError(IRestResponse response)
|
||||||
|
{
|
||||||
|
if (response.ResponseStatus != ResponseStatus.Completed)
|
||||||
|
{
|
||||||
|
throw new ApplicationException("Unable to connect to SABnzbd, please check your settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = Json.Deserialize<SabJsonError>(response.Content);
|
||||||
|
|
||||||
|
if (result.Status != null && result.Status.Equals("false", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
throw new ApplicationException(result.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Find a better home for this
|
||||||
|
private byte[] ReadFully(Stream input)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[16 * 1024];
|
||||||
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
int read;
|
||||||
|
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
ms.Write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
@ -13,48 +14,12 @@ using RestSharp;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
{
|
{
|
||||||
public class SabRequestBuilder
|
|
||||||
{
|
|
||||||
private readonly IConfigService _configService;
|
|
||||||
|
|
||||||
public SabRequestBuilder(IConfigService configService)
|
|
||||||
{
|
|
||||||
_configService = configService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IRestRequest AddToQueueRequest(RemoteEpisode remoteEpisode)
|
|
||||||
{
|
|
||||||
string cat = _configService.SabTvCategory;
|
|
||||||
int priority = (int)_configService.SabRecentTvPriority;
|
|
||||||
|
|
||||||
string name = remoteEpisode.Release.DownloadUrl.Replace("&", "%26");
|
|
||||||
string nzbName = HttpUtility.UrlEncode(remoteEpisode.Release.Title);
|
|
||||||
|
|
||||||
string action = string.Format("mode=addurl&name={0}&priority={1}&pp=3&cat={2}&nzbname={3}&output=json",
|
|
||||||
name, priority, cat, nzbName);
|
|
||||||
|
|
||||||
string request = GetSabRequest(action);
|
|
||||||
|
|
||||||
return new RestRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetSabRequest(string action)
|
|
||||||
{
|
|
||||||
return string.Format(@"http://{0}:{1}/api?{2}&apikey={3}&ma_username={4}&ma_password={5}",
|
|
||||||
_configService.SabHost,
|
|
||||||
_configService.SabPort,
|
|
||||||
action,
|
|
||||||
_configService.SabApiKey,
|
|
||||||
_configService.SabUsername,
|
|
||||||
_configService.SabPassword);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SabnzbdClient : IDownloadClient
|
public class SabnzbdClient : IDownloadClient
|
||||||
{
|
{
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IHttpProvider _httpProvider;
|
private readonly IHttpProvider _httpProvider;
|
||||||
private readonly IParsingService _parsingService;
|
private readonly IParsingService _parsingService;
|
||||||
|
private readonly ISabCommunicationProxy _sabCommunicationProxy;
|
||||||
private readonly ICached<IEnumerable<QueueItem>> _queueCache;
|
private readonly ICached<IEnumerable<QueueItem>> _queueCache;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
@ -62,39 +27,17 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
IHttpProvider httpProvider,
|
IHttpProvider httpProvider,
|
||||||
ICacheManger cacheManger,
|
ICacheManger cacheManger,
|
||||||
IParsingService parsingService,
|
IParsingService parsingService,
|
||||||
|
ISabCommunicationProxy sabCommunicationProxy,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_httpProvider = httpProvider;
|
_httpProvider = httpProvider;
|
||||||
_parsingService = parsingService;
|
_parsingService = parsingService;
|
||||||
|
_sabCommunicationProxy = sabCommunicationProxy;
|
||||||
_queueCache = cacheManger.GetCache<IEnumerable<QueueItem>>(GetType(), "queue");
|
_queueCache = cacheManger.GetCache<IEnumerable<QueueItem>>(GetType(), "queue");
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DownloadNzb(RemoteEpisode remoteEpisode)
|
|
||||||
{
|
|
||||||
var url = remoteEpisode.Release.DownloadUrl;
|
|
||||||
var title = remoteEpisode.Release.Title;
|
|
||||||
|
|
||||||
string cat = _configService.SabTvCategory;
|
|
||||||
int priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.SabRecentTvPriority : (int)_configService.SabOlderTvPriority;
|
|
||||||
|
|
||||||
string name = url.Replace("&", "%26");
|
|
||||||
string nzbName = HttpUtility.UrlEncode(title);
|
|
||||||
|
|
||||||
string action = string.Format("mode=addurl&name={0}&priority={1}&pp=3&cat={2}&nzbname={3}&output=json",
|
|
||||||
name, priority, cat, nzbName);
|
|
||||||
|
|
||||||
string request = GetSabRequest(action);
|
|
||||||
_logger.Info("Adding report [{0}] to the queue.", title);
|
|
||||||
|
|
||||||
var response = _httpProvider.DownloadString(request);
|
|
||||||
|
|
||||||
_logger.Debug("Queue Response: [{0}]", response);
|
|
||||||
|
|
||||||
CheckForError(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsConfigured
|
public bool IsConfigured
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -104,6 +47,24 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string DownloadNzb(RemoteEpisode remoteEpisode)
|
||||||
|
{
|
||||||
|
var url = remoteEpisode.Release.DownloadUrl;
|
||||||
|
var title = remoteEpisode.Release.Title;
|
||||||
|
var category = _configService.SabTvCategory;
|
||||||
|
var priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.SabRecentTvPriority : (int)_configService.SabOlderTvPriority;
|
||||||
|
|
||||||
|
using (var nzb = _httpProvider.DownloadStream(url))
|
||||||
|
{
|
||||||
|
_logger.Info("Adding report [{0}] to the queue.", title);
|
||||||
|
var response = Json.Deserialize<SabAddResponse>(_sabCommunicationProxy.DownloadNzb(nzb, title, category, priority));
|
||||||
|
|
||||||
|
_logger.Debug("Queue Response: [{0}]", response.Status);
|
||||||
|
|
||||||
|
return response.Ids.First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<QueueItem> GetQueue()
|
public IEnumerable<QueueItem> GetQueue()
|
||||||
{
|
{
|
||||||
return _queueCache.Get("queue", () =>
|
return _queueCache.Get("queue", () =>
|
||||||
|
@ -128,7 +89,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
queueItem.Timeleft = sabQueueItem.Timeleft;
|
queueItem.Timeleft = sabQueueItem.Timeleft;
|
||||||
queueItem.Status = sabQueueItem.Status;
|
queueItem.Status = sabQueueItem.Status;
|
||||||
|
|
||||||
var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title);
|
var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title.Replace("ENCRYPTED / ", ""));
|
||||||
if (parsedEpisodeInfo == null) continue;
|
if (parsedEpisodeInfo == null) continue;
|
||||||
|
|
||||||
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
|
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
|
||||||
|
@ -143,7 +104,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
}, TimeSpan.FromSeconds(10));
|
}, TimeSpan.FromSeconds(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual List<SabHistoryItem> GetHistory(int start = 0, int limit = 0)
|
public IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
|
||||||
{
|
{
|
||||||
string action = String.Format("mode=history&output=json&start={0}&limit={1}", start, limit);
|
string action = String.Format("mode=history&output=json&start={0}&limit={1}", start, limit);
|
||||||
string request = GetSabRequest(action);
|
string request = GetSabRequest(action);
|
||||||
|
@ -152,7 +113,34 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
CheckForError(response);
|
CheckForError(response);
|
||||||
|
|
||||||
var items = Json.Deserialize<SabHistory>(JObject.Parse(response).SelectToken("history").ToString()).Items;
|
var items = Json.Deserialize<SabHistory>(JObject.Parse(response).SelectToken("history").ToString()).Items;
|
||||||
return items ?? new List<SabHistoryItem>();
|
var historyItems = new List<HistoryItem>();
|
||||||
|
|
||||||
|
foreach (var sabHistoryItem in items)
|
||||||
|
{
|
||||||
|
var historyItem = new HistoryItem();
|
||||||
|
historyItem.Id = sabHistoryItem.Id;
|
||||||
|
historyItem.Title = sabHistoryItem.Title;
|
||||||
|
historyItem.Size = sabHistoryItem.Size;
|
||||||
|
historyItem.DownloadTime = sabHistoryItem.DownloadTime;
|
||||||
|
historyItem.Storage = sabHistoryItem.Storage;
|
||||||
|
historyItem.Category = sabHistoryItem.Category;
|
||||||
|
historyItem.Message = sabHistoryItem.FailMessage;
|
||||||
|
historyItem.Status = sabHistoryItem.Status == "Failed" ? HistoryStatus.Failed : HistoryStatus.Completed;
|
||||||
|
|
||||||
|
historyItems.Add(historyItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return historyItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFromQueue(string id)
|
||||||
|
{
|
||||||
|
_sabCommunicationProxy.RemoveFrom("queue", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFromHistory(string id)
|
||||||
|
{
|
||||||
|
_sabCommunicationProxy.RemoveFrom("history", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual SabCategoryModel GetCategories(string host = null, int port = 0, string apiKey = null, string username = null, string password = null)
|
public virtual SabCategoryModel GetCategories(string host = null, int port = 0, string apiKey = null, string username = null, string password = null)
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class DownloadFailedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Int32 SeriesId { get; set; }
|
||||||
|
public List<Int32> EpisodeIds { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public String SourceTitle { get; set; }
|
||||||
|
public String DownloadClient { get; set; }
|
||||||
|
public String DownloadClientId { get; set; }
|
||||||
|
public String Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using NLog;
|
using System;
|
||||||
|
using NLog;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
using NzbDrone.Core.Instrumentation;
|
using NzbDrone.Core.Instrumentation;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -40,10 +41,17 @@ namespace NzbDrone.Core.Download
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadClient.DownloadNzb(remoteEpisode);
|
var downloadClientId = downloadClient.DownloadNzb(remoteEpisode);
|
||||||
|
var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode);
|
||||||
|
|
||||||
|
if (!String.IsNullOrWhiteSpace(downloadClientId))
|
||||||
|
{
|
||||||
|
episodeGrabbedEvent.DownloadClient = downloadClient.GetType().Name;
|
||||||
|
episodeGrabbedEvent.DownloadClientId = downloadClientId;
|
||||||
|
}
|
||||||
|
|
||||||
_logger.ProgressInfo("Report sent to download client. {0}", downloadTitle);
|
_logger.ProgressInfo("Report sent to download client. {0}", downloadTitle);
|
||||||
_eventAggregator.PublishEvent(new EpisodeGrabbedEvent(remoteEpisode));
|
_eventAggregator.PublishEvent(episodeGrabbedEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using NzbDrone.Common.Messaging;
|
using System;
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
|
@ -6,6 +7,8 @@ namespace NzbDrone.Core.Download
|
||||||
public class EpisodeGrabbedEvent : IEvent
|
public class EpisodeGrabbedEvent : IEvent
|
||||||
{
|
{
|
||||||
public RemoteEpisode Episode { get; private set; }
|
public RemoteEpisode Episode { get; private set; }
|
||||||
|
public String DownloadClient { get; set; }
|
||||||
|
public String DownloadClientId { get; set; }
|
||||||
|
|
||||||
public EpisodeGrabbedEvent(RemoteEpisode episode)
|
public EpisodeGrabbedEvent(RemoteEpisode episode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class FailedDownloadCommand : Command
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public interface IFailedDownloadService
|
||||||
|
{
|
||||||
|
void MarkAsFailed(int historyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FailedDownloadService : IFailedDownloadService, IExecute<FailedDownloadCommand>
|
||||||
|
{
|
||||||
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||||
|
private readonly IHistoryService _historyService;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
private readonly IDownloadClient _downloadClient;
|
||||||
|
|
||||||
|
private static string DOWNLOAD_CLIENT = "downloadClient";
|
||||||
|
private static string DOWNLOAD_CLIENT_ID = "downloadClientId";
|
||||||
|
|
||||||
|
public FailedDownloadService(IProvideDownloadClient downloadClientProvider,
|
||||||
|
IHistoryService historyService,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
IConfigService configService,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_downloadClientProvider = downloadClientProvider;
|
||||||
|
_historyService = historyService;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_configService = configService;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
_downloadClient = _downloadClientProvider.GetDownloadClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkAsFailed(int historyId)
|
||||||
|
{
|
||||||
|
var item = _historyService.Get(historyId);
|
||||||
|
PublishDownloadFailedEvent(new List<History.History> {item}, "Manually marked as failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckForFailedDownloads()
|
||||||
|
{
|
||||||
|
if (!_configService.EnableFailedDownloadHandling)
|
||||||
|
{
|
||||||
|
_logger.Trace("Failed Download Handling is not enabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var grabbedHistory = _historyService.Grabbed();
|
||||||
|
var failedHistory = _historyService.Failed();
|
||||||
|
|
||||||
|
CheckQueue(grabbedHistory, failedHistory);
|
||||||
|
CheckHistory(grabbedHistory, failedHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckQueue(List<History.History> grabbedHistory, List<History.History> failedHistory)
|
||||||
|
{
|
||||||
|
var downloadClientQueue = _downloadClient.GetQueue().ToList();
|
||||||
|
var failedItems = downloadClientQueue.Where(q => q.Title.StartsWith("ENCRYPTED / ")).ToList();
|
||||||
|
|
||||||
|
if (!failedItems.Any())
|
||||||
|
{
|
||||||
|
_logger.Trace("Yay! No encrypted downloads");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var failedItem in failedItems)
|
||||||
|
{
|
||||||
|
var failedLocal = failedItem;
|
||||||
|
var historyItems = GetHistoryItems(grabbedHistory, failedLocal.Id);
|
||||||
|
|
||||||
|
if (!historyItems.Any())
|
||||||
|
{
|
||||||
|
_logger.Trace("Unable to find matching history item");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedHistory.Any(h => h.Data.ContainsKey(DOWNLOAD_CLIENT_ID) &&
|
||||||
|
h.Data[DOWNLOAD_CLIENT_ID].Equals(failedLocal.Id)))
|
||||||
|
{
|
||||||
|
_logger.Trace("Already added to history as failed");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PublishDownloadFailedEvent(historyItems, "Encypted download detected");
|
||||||
|
|
||||||
|
if (_configService.RemoveFailedDownloads)
|
||||||
|
{
|
||||||
|
_logger.Info("Removing encrypted download from queue: {0}", failedItem.Title.Replace("ENCRYPTED / ", ""));
|
||||||
|
_downloadClient.RemoveFromQueue(failedItem.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckHistory(List<History.History> grabbedHistory, List<History.History> failedHistory)
|
||||||
|
{
|
||||||
|
var downloadClientHistory = _downloadClient.GetHistory(0, 20).ToList();
|
||||||
|
var failedItems = downloadClientHistory.Where(h => h.Status == HistoryStatus.Failed).ToList();
|
||||||
|
|
||||||
|
if (!failedItems.Any())
|
||||||
|
{
|
||||||
|
_logger.Trace("Yay! No failed downloads");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var failedItem in failedItems)
|
||||||
|
{
|
||||||
|
var failedLocal = failedItem;
|
||||||
|
var historyItems = GetHistoryItems(grabbedHistory, failedLocal.Id);
|
||||||
|
|
||||||
|
if (!historyItems.Any())
|
||||||
|
{
|
||||||
|
_logger.Trace("Unable to find matching history item");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedHistory.Any(h => h.Data.ContainsKey(DOWNLOAD_CLIENT_ID) &&
|
||||||
|
h.Data[DOWNLOAD_CLIENT_ID].Equals(failedLocal.Id)))
|
||||||
|
{
|
||||||
|
_logger.Trace("Already added to history as failed");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PublishDownloadFailedEvent(historyItems, failedItem.Message);
|
||||||
|
|
||||||
|
if (_configService.RemoveFailedDownloads)
|
||||||
|
{
|
||||||
|
_logger.Info("Removing failed download from history: {0}", failedItem.Title);
|
||||||
|
_downloadClient.RemoveFromHistory(failedItem.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
|
||||||
|
{
|
||||||
|
return grabbedHistory.Where(h => h.Data.ContainsKey(DOWNLOAD_CLIENT) &&
|
||||||
|
h.Data[DOWNLOAD_CLIENT_ID].Equals(downloadClientId))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PublishDownloadFailedEvent(List<History.History> historyItems, string message)
|
||||||
|
{
|
||||||
|
var historyItem = historyItems.First();
|
||||||
|
_eventAggregator.PublishEvent(new DownloadFailedEvent
|
||||||
|
{
|
||||||
|
SeriesId = historyItem.SeriesId,
|
||||||
|
EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
|
||||||
|
Quality = historyItem.Quality,
|
||||||
|
SourceTitle = historyItem.SourceTitle,
|
||||||
|
DownloadClient = historyItem.Data[DOWNLOAD_CLIENT],
|
||||||
|
DownloadClientId = historyItem.Data[DOWNLOAD_CLIENT_ID],
|
||||||
|
Message = message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(FailedDownloadCommand message)
|
||||||
|
{
|
||||||
|
CheckForFailedDownloads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class HistoryItem
|
||||||
|
{
|
||||||
|
public String Id { get; set; }
|
||||||
|
public String Title { get; set; }
|
||||||
|
public String Size { get; set; }
|
||||||
|
public String Category { get; set; }
|
||||||
|
public Int32 DownloadTime { get; set; }
|
||||||
|
public String Storage { get; set; }
|
||||||
|
public String Message { get; set; }
|
||||||
|
public HistoryStatus Status { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HistoryStatus
|
||||||
|
{
|
||||||
|
Completed = 0,
|
||||||
|
Failed = 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,11 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public interface IDownloadClient
|
public interface IDownloadClient
|
||||||
{
|
{
|
||||||
void DownloadNzb(RemoteEpisode remoteEpisode);
|
string DownloadNzb(RemoteEpisode remoteEpisode);
|
||||||
bool IsConfigured { get; }
|
bool IsConfigured { get; }
|
||||||
IEnumerable<QueueItem> GetQueue();
|
IEnumerable<QueueItem> GetQueue();
|
||||||
|
IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0);
|
||||||
|
void RemoveFromQueue(string id);
|
||||||
|
void RemoveFromHistory(string id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.IndexerSearch;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public interface IRedownloadFailedDownloads
|
||||||
|
{
|
||||||
|
void Redownload(int seriesId, List<int> episodeIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RedownloadFailedDownloadService : IRedownloadFailedDownloads
|
||||||
|
{
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly IEpisodeService _episodeService;
|
||||||
|
private readonly ICommandExecutor _commandExecutor;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public RedownloadFailedDownloadService(IConfigService configService, IEpisodeService episodeService, ICommandExecutor commandExecutor, Logger logger)
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
_episodeService = episodeService;
|
||||||
|
_commandExecutor = commandExecutor;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Redownload(int seriesId, List<int> episodeIds)
|
||||||
|
{
|
||||||
|
if (!_configService.AutoRedownloadFailed)
|
||||||
|
{
|
||||||
|
_logger.Trace("Auto redownloading failed episodes is disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episodeIds.Count == 1)
|
||||||
|
{
|
||||||
|
_logger.Trace("Failed download only contains one episode, searching again");
|
||||||
|
|
||||||
|
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand
|
||||||
|
{
|
||||||
|
EpisodeIds = episodeIds.ToList()
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var seasonNumber = _episodeService.GetEpisode(episodeIds.First()).SeasonNumber;
|
||||||
|
var episodesInSeason = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
|
||||||
|
|
||||||
|
if (episodeIds.Count == episodesInSeason.Count)
|
||||||
|
{
|
||||||
|
_logger.Trace("Failed download was entire season, searching again");
|
||||||
|
|
||||||
|
_commandExecutor.PublishCommandAsync(new SeasonSearchCommand
|
||||||
|
{
|
||||||
|
SeriesId = seriesId,
|
||||||
|
SeasonNumber = seasonNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Trace("Failed download contains multiple episodes, probably a double episode, searching again");
|
||||||
|
|
||||||
|
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand
|
||||||
|
{
|
||||||
|
EpisodeIds = episodeIds.ToList()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,22 +17,18 @@ namespace NzbDrone.Core.History
|
||||||
public string SourceTitle { get; set; }
|
public string SourceTitle { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
public Episode Episode { get; set; }
|
public Episode Episode { get; set; }
|
||||||
public Series Series { get; set; }
|
public Series Series { get; set; }
|
||||||
|
|
||||||
public HistoryEventType EventType { get; set; }
|
public HistoryEventType EventType { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, string> Data { get; set; }
|
public Dictionary<string, string> Data { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public enum HistoryEventType
|
public enum HistoryEventType
|
||||||
{
|
{
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
Grabbed = 1,
|
Grabbed = 1,
|
||||||
SeriesFolderImported = 2,
|
SeriesFolderImported = 2,
|
||||||
DownloadFolderImported = 3
|
DownloadFolderImported = 3,
|
||||||
|
DownloadFailed = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using Marr.Data.QGen;
|
using Marr.Data.QGen;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -13,6 +12,10 @@ namespace NzbDrone.Core.History
|
||||||
{
|
{
|
||||||
void Trim();
|
void Trim();
|
||||||
List<QualityModel> GetBestQualityInHistory(int episodeId);
|
List<QualityModel> GetBestQualityInHistory(int episodeId);
|
||||||
|
List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType);
|
||||||
|
List<History> Failed();
|
||||||
|
List<History> Grabbed();
|
||||||
|
History MostRecentForEpisode(int episodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
|
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
|
||||||
|
@ -38,6 +41,32 @@ namespace NzbDrone.Core.History
|
||||||
return history.Select(h => h.Quality).ToList();
|
return history.Select(h => h.Quality).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType)
|
||||||
|
{
|
||||||
|
return Query.Join<History, Series>(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id)
|
||||||
|
.Join<History, Episode>(JoinType.Inner, h => h.Episode, (h, e) => h.EpisodeId == e.Id)
|
||||||
|
.Where(h => h.Date >= startDate)
|
||||||
|
.AndWhere(h => h.Date <= endDate)
|
||||||
|
.AndWhere(h => h.EventType == eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<History> Failed()
|
||||||
|
{
|
||||||
|
return Query.Where(h => h.EventType == HistoryEventType.DownloadFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<History> Grabbed()
|
||||||
|
{
|
||||||
|
return Query.Where(h => h.EventType == HistoryEventType.Grabbed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public History MostRecentForEpisode(int episodeId)
|
||||||
|
{
|
||||||
|
return Query.Where(h => h.EpisodeId == episodeId)
|
||||||
|
.OrderByDescending(h => h.Date)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
public override PagingSpec<History> GetPaged(PagingSpec<History> pagingSpec)
|
public override PagingSpec<History> GetPaged(PagingSpec<History> pagingSpec)
|
||||||
{
|
{
|
||||||
pagingSpec.Records = GetPagedQuery(pagingSpec).ToList();
|
pagingSpec.Records = GetPagedQuery(pagingSpec).ToList();
|
||||||
|
|
|
@ -18,9 +18,14 @@ namespace NzbDrone.Core.History
|
||||||
void Trim();
|
void Trim();
|
||||||
QualityModel GetBestQualityInHistory(int episodeId);
|
QualityModel GetBestQualityInHistory(int episodeId);
|
||||||
PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
|
PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
|
||||||
|
List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType);
|
||||||
|
List<History> Failed();
|
||||||
|
List<History> Grabbed();
|
||||||
|
History MostRecentForEpisode(int episodeId);
|
||||||
|
History Get(int id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HistoryService : IHistoryService, IHandle<EpisodeGrabbedEvent>, IHandle<EpisodeImportedEvent>
|
public class HistoryService : IHistoryService, IHandle<EpisodeGrabbedEvent>, IHandle<EpisodeImportedEvent>, IHandle<DownloadFailedEvent>
|
||||||
{
|
{
|
||||||
private readonly IHistoryRepository _historyRepository;
|
private readonly IHistoryRepository _historyRepository;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
@ -41,6 +46,31 @@ namespace NzbDrone.Core.History
|
||||||
return _historyRepository.GetPaged(pagingSpec);
|
return _historyRepository.GetPaged(pagingSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType)
|
||||||
|
{
|
||||||
|
return _historyRepository.BetweenDates(startDate, endDate, eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<History> Failed()
|
||||||
|
{
|
||||||
|
return _historyRepository.Failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<History> Grabbed()
|
||||||
|
{
|
||||||
|
return _historyRepository.Grabbed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public History MostRecentForEpisode(int episodeId)
|
||||||
|
{
|
||||||
|
return _historyRepository.MostRecentForEpisode(episodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public History Get(int id)
|
||||||
|
{
|
||||||
|
return _historyRepository.Get(id);
|
||||||
|
}
|
||||||
|
|
||||||
public void Purge()
|
public void Purge()
|
||||||
{
|
{
|
||||||
_historyRepository.Purge();
|
_historyRepository.Purge();
|
||||||
|
@ -51,7 +81,7 @@ namespace NzbDrone.Core.History
|
||||||
_historyRepository.Trim();
|
_historyRepository.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual QualityModel GetBestQualityInHistory(int episodeId)
|
public QualityModel GetBestQualityInHistory(int episodeId)
|
||||||
{
|
{
|
||||||
return _historyRepository.GetBestQualityInHistory(episodeId).OrderByDescending(q => q).FirstOrDefault();
|
return _historyRepository.GetBestQualityInHistory(episodeId).OrderByDescending(q => q).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
@ -75,19 +105,25 @@ namespace NzbDrone.Core.History
|
||||||
history.Data.Add("ReleaseGroup", message.Episode.Release.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.Episode.Release.ReleaseGroup);
|
||||||
history.Data.Add("Age", message.Episode.Release.Age.ToString());
|
history.Data.Add("Age", message.Episode.Release.Age.ToString());
|
||||||
|
|
||||||
|
if (!String.IsNullOrWhiteSpace(message.DownloadClientId))
|
||||||
|
{
|
||||||
|
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||||
|
history.Data.Add("DownloadClientId", message.DownloadClientId);
|
||||||
|
}
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(EpisodeImportedEvent message)
|
public void Handle(EpisodeImportedEvent message)
|
||||||
{
|
{
|
||||||
foreach (var episode in message.DroppedEpisode.Episodes)
|
foreach (var episode in message.EpisodeInfo.Episodes)
|
||||||
{
|
{
|
||||||
var history = new History
|
var history = new History
|
||||||
{
|
{
|
||||||
EventType = HistoryEventType.DownloadFolderImported,
|
EventType = HistoryEventType.DownloadFolderImported,
|
||||||
Date = DateTime.UtcNow,
|
Date = DateTime.UtcNow,
|
||||||
Quality = message.DroppedEpisode.Quality,
|
Quality = message.EpisodeInfo.Quality,
|
||||||
SourceTitle = message.ImportedEpisode.SceneName,
|
SourceTitle = message.ImportedEpisode.SceneName,
|
||||||
SeriesId = message.ImportedEpisode.SeriesId,
|
SeriesId = message.ImportedEpisode.SeriesId,
|
||||||
EpisodeId = episode.Id
|
EpisodeId = episode.Id
|
||||||
|
@ -95,11 +131,33 @@ namespace NzbDrone.Core.History
|
||||||
|
|
||||||
//Won't have a value since we publish this event before saving to DB.
|
//Won't have a value since we publish this event before saving to DB.
|
||||||
//history.Data.Add("FileId", message.ImportedEpisode.Id.ToString());
|
//history.Data.Add("FileId", message.ImportedEpisode.Id.ToString());
|
||||||
history.Data.Add("DroppedPath", message.DroppedEpisode.Path);
|
history.Data.Add("DroppedPath", message.EpisodeInfo.Path);
|
||||||
history.Data.Add("ImportedPath", message.ImportedEpisode.Path);
|
history.Data.Add("ImportedPath", message.ImportedEpisode.Path);
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(DownloadFailedEvent message)
|
||||||
|
{
|
||||||
|
foreach (var episodeId in message.EpisodeIds)
|
||||||
|
{
|
||||||
|
var history = new History
|
||||||
|
{
|
||||||
|
EventType = HistoryEventType.DownloadFailed,
|
||||||
|
Date = DateTime.UtcNow,
|
||||||
|
Quality = message.Quality,
|
||||||
|
SourceTitle = message.SourceTitle,
|
||||||
|
SeriesId = message.SeriesId,
|
||||||
|
EpisodeId = episodeId,
|
||||||
|
};
|
||||||
|
|
||||||
|
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||||
|
history.Data.Add("DownloadClientId", message.DownloadClientId);
|
||||||
|
history.Data.Add("Message", message.Message);
|
||||||
|
|
||||||
|
_historyRepository.Insert(history);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,11 +4,11 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||||
{
|
{
|
||||||
public class DailyEpisodeSearchCriteria : SearchCriteriaBase
|
public class DailyEpisodeSearchCriteria : SearchCriteriaBase
|
||||||
{
|
{
|
||||||
public DateTime Airtime { get; set; }
|
public DateTime AirDate { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("[{0} : {1}", SceneTitle, Airtime);
|
return string.Format("[{0} : {1}", SceneTitle, AirDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -98,7 +98,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
{
|
{
|
||||||
var airDate = DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture);
|
var airDate = DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture);
|
||||||
var searchSpec = Get<DailyEpisodeSearchCriteria>(series, new List<Episode>{ episode });
|
var searchSpec = Get<DailyEpisodeSearchCriteria>(series, new List<Episode>{ episode });
|
||||||
searchSpec.Airtime = airDate;
|
searchSpec.AirDate = airDate;
|
||||||
|
|
||||||
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.IndexerSearch
|
|
||||||
{
|
|
||||||
|
|
||||||
interface ISearchAndDownload
|
|
||||||
{
|
|
||||||
void SearchSingle(int seriesId, int seasonNumber, int episodeNumber);
|
|
||||||
void SearchDaily(int seriesId, DateTime airDate);
|
|
||||||
void SearchSeason(int seriesId, int seasonNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* public class SearchAndDownloadService : ISearchAndDownload
|
|
||||||
{
|
|
||||||
private readonly ISearchForNzb _searchService;
|
|
||||||
private readonly IMakeDownloadDecision _downloadDecisionMaker;
|
|
||||||
|
|
||||||
public SearchAndDownloadService(ISearchForNzb searchService, IMakeDownloadDecision downloadDecisionMaker)
|
|
||||||
{
|
|
||||||
_searchService = searchService;
|
|
||||||
_downloadDecisionMaker = downloadDecisionMaker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FetchSearchSingle(int seriesId, int seasonNumber, int episodeNumber)
|
|
||||||
{
|
|
||||||
var result = _searchService.SearchSingle(seriesId, seasonNumber, episodeNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SearchDaily(int seriesId, DateTime airDate)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SearchSeason(int seriesId, int seasonNumber)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
|
@ -87,7 +87,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
_logger.Debug("Searching for {0}", searchCriteria);
|
_logger.Debug("Searching for {0}", searchCriteria);
|
||||||
|
|
||||||
var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.Airtime);
|
var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.AirDate);
|
||||||
var result = Fetch(indexer, searchUrls);
|
var result = Fetch(indexer, searchUrls);
|
||||||
|
|
||||||
_logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count);
|
_logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count);
|
||||||
|
|
|
@ -7,6 +7,7 @@ using NzbDrone.Core.Configuration.Events;
|
||||||
using NzbDrone.Core.DataAugmentation;
|
using NzbDrone.Core.DataAugmentation;
|
||||||
using NzbDrone.Core.DataAugmentation.Scene;
|
using NzbDrone.Core.DataAugmentation.Scene;
|
||||||
using NzbDrone.Core.DataAugmentation.Xem;
|
using NzbDrone.Core.DataAugmentation.Xem;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Housekeeping;
|
using NzbDrone.Core.Housekeeping;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Instrumentation.Commands;
|
using NzbDrone.Core.Instrumentation.Commands;
|
||||||
|
@ -54,7 +55,8 @@ namespace NzbDrone.Core.Jobs
|
||||||
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
|
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName},
|
new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName}
|
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
|
||||||
|
new ScheduledTask{ Interval = 1, TypeName = typeof(FailedDownloadCommand).FullName}
|
||||||
};
|
};
|
||||||
|
|
||||||
var currentTasks = _scheduledTaskRepository.All();
|
var currentTasks = _scheduledTaskRepository.All();
|
||||||
|
|
|
@ -7,7 +7,6 @@ using NzbDrone.Common;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
using NzbDrone.Core.MediaFiles.Commands;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
|
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
@ -74,10 +73,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
if (importedFiles.Any())
|
if (importedFiles.Any())
|
||||||
{
|
{
|
||||||
if (_diskProvider.GetFolderSize(subFolder) < NotSampleSpecification.SampleSizeLimit)
|
_diskProvider.DeleteFolder(subFolder, true);
|
||||||
{
|
|
||||||
_diskProvider.DeleteFolder(subFolder, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Growl.Connector;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
@ -73,26 +75,38 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
_logger.Debug("Moving [{0}] > [{1}]", episodeFile.Path, destinationFilename);
|
_logger.Debug("Moving [{0}] > [{1}]", episodeFile.Path, destinationFilename);
|
||||||
_diskProvider.MoveFile(episodeFile.Path, destinationFilename);
|
_diskProvider.MoveFile(episodeFile.Path, destinationFilename);
|
||||||
|
|
||||||
_logger.Trace("Setting last write time on series folder: {0}", series.Path);
|
|
||||||
_diskProvider.SetFolderWriteTime(series.Path, episodeFile.DateAdded);
|
|
||||||
|
|
||||||
if (series.SeasonFolder)
|
|
||||||
{
|
|
||||||
var seasonFolder = Path.GetDirectoryName(destinationFilename);
|
|
||||||
|
|
||||||
_logger.Trace("Setting last write time on season folder: {0}", seasonFolder);
|
|
||||||
_diskProvider.SetFolderWriteTime(seasonFolder, episodeFile.DateAdded);
|
|
||||||
}
|
|
||||||
|
|
||||||
//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(destinationFilename);
|
_logger.Trace("Setting last write time on series folder: {0}", series.Path);
|
||||||
|
_diskProvider.SetFolderWriteTime(series.Path, episodeFile.DateAdded);
|
||||||
|
|
||||||
|
if (series.SeasonFolder)
|
||||||
|
{
|
||||||
|
var seasonFolder = Path.GetDirectoryName(destinationFilename);
|
||||||
|
|
||||||
|
_logger.Trace("Setting last write time on season folder: {0}", seasonFolder);
|
||||||
|
_diskProvider.SetFolderWriteTime(seasonFolder, episodeFile.DateAdded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException ex)
|
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Debug("Unable to apply folder permissions to: ", destinationFilename);
|
_logger.WarnException("Unable to set last write time", ex);
|
||||||
_logger.TraceException(ex.Message, ex);
|
}
|
||||||
|
|
||||||
|
//We should only run this on Windows
|
||||||
|
if (OsInfo.IsWindows)
|
||||||
|
{
|
||||||
|
//Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes, the move worked, which is more important.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_diskProvider.InheritFolderPermissions(destinationFilename);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException ex)
|
||||||
|
{
|
||||||
|
_logger.Debug("Unable to apply folder permissions to: ", destinationFilename);
|
||||||
|
_logger.TraceException(ex.Message, ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
var qualifiedImports = GetQualifiedImports(decisions);
|
var qualifiedImports = GetQualifiedImports(decisions);
|
||||||
var imported = new List<ImportDecision>();
|
var imported = new List<ImportDecision>();
|
||||||
|
|
||||||
foreach (var importDecision in qualifiedImports)
|
foreach (var importDecision in qualifiedImports.OrderByDescending(e => e.LocalEpisode.Size))
|
||||||
{
|
{
|
||||||
var localEpisode = importDecision.LocalEpisode;
|
var localEpisode = importDecision.LocalEpisode;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
|
@ -11,6 +14,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
{
|
{
|
||||||
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 };
|
||||||
|
|
||||||
public NotSampleSpecification(IVideoFileInfoReader videoFileInfoReader,
|
public NotSampleSpecification(IVideoFileInfoReader videoFileInfoReader,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
|
@ -31,6 +35,12 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
|
|
||||||
public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
public bool IsSatisfiedBy(LocalEpisode localEpisode)
|
||||||
{
|
{
|
||||||
|
if (localEpisode.ExistingFile)
|
||||||
|
{
|
||||||
|
_logger.Trace("Existing file, skipping sample check");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (localEpisode.Series.SeriesType == SeriesTypes.Daily)
|
if (localEpisode.Series.SeriesType == SeriesTypes.Daily)
|
||||||
{
|
{
|
||||||
_logger.Trace("Daily Series, skipping sample check");
|
_logger.Trace("Daily Series, skipping sample check");
|
||||||
|
@ -43,28 +53,49 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Path.GetExtension(localEpisode.Path).Equals(".flv", StringComparison.InvariantCultureIgnoreCase))
|
var extension = Path.GetExtension(localEpisode.Path);
|
||||||
|
|
||||||
|
if (extension != null && extension.Equals(".flv", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.Trace("Skipping smaple check for .flv file");
|
_logger.Trace("Skipping sample check for .flv file");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localEpisode.Size > SampleSizeLimit)
|
if (OsInfo.IsWindows)
|
||||||
{
|
{
|
||||||
|
var runTime = _videoFileInfoReader.GetRunTime(localEpisode.Path);
|
||||||
|
|
||||||
|
if (runTime.TotalMinutes.Equals(0))
|
||||||
|
{
|
||||||
|
_logger.Error("[{0}] has a runtime of 0, is it a valid video file?", localEpisode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runTime.TotalSeconds < 90)
|
||||||
|
{
|
||||||
|
_logger.Trace("[{0}] appears to be a sample. Size: {1} Runtime: {2}", localEpisode.Path, localEpisode.Size, runTime);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Trace("Runtime is over 2 minutes, skipping file size check");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var runTime = _videoFileInfoReader.GetRunTime(localEpisode.Path);
|
return CheckSize(localEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
if (runTime.TotalMinutes.Equals(0))
|
private bool CheckSize(LocalEpisode localEpisode)
|
||||||
|
{
|
||||||
|
if (_largeSampleSizeQualities.Contains(localEpisode.Quality.Quality))
|
||||||
{
|
{
|
||||||
_logger.Error("[{0}] has a runtime of 0, is it a valid video file?", localEpisode);
|
if (localEpisode.Size < SampleSizeLimit * 2)
|
||||||
return false;
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runTime.TotalMinutes < 3)
|
if (localEpisode.Size < SampleSizeLimit)
|
||||||
{
|
{
|
||||||
_logger.Trace("[{0}] appears to be a sample. Size: {1} Runtime: {2}", localEpisode.Path, localEpisode.Size, runTime);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
@ -34,6 +35,12 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
||||||
{
|
{
|
||||||
if (Directory.GetParent(localEpisode.Path).Name.StartsWith(workingFolder))
|
if (Directory.GetParent(localEpisode.Path).Name.StartsWith(workingFolder))
|
||||||
{
|
{
|
||||||
|
if (OsInfo.IsLinux)
|
||||||
|
{
|
||||||
|
_logger.Trace("{0} is still being unpacked", localEpisode.Path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (_diskProvider.GetLastFileWrite(localEpisode.Path) > DateTime.UtcNow.AddMinutes(-5))
|
if (_diskProvider.GetLastFileWrite(localEpisode.Path) > DateTime.UtcNow.AddMinutes(-5))
|
||||||
{
|
{
|
||||||
_logger.Trace("{0} appears to be unpacking still", localEpisode.Path);
|
_logger.Trace("{0} appears to be unpacking still", localEpisode.Path);
|
||||||
|
|
|
@ -5,12 +5,12 @@ namespace NzbDrone.Core.MediaFiles.Events
|
||||||
{
|
{
|
||||||
public class EpisodeImportedEvent : IEvent
|
public class EpisodeImportedEvent : IEvent
|
||||||
{
|
{
|
||||||
public LocalEpisode DroppedEpisode { get; private set; }
|
public LocalEpisode EpisodeInfo { get; private set; }
|
||||||
public EpisodeFile ImportedEpisode { get; private set; }
|
public EpisodeFile ImportedEpisode { get; private set; }
|
||||||
|
|
||||||
public EpisodeImportedEvent(LocalEpisode droppedEpisode, EpisodeFile importedEpisode)
|
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode)
|
||||||
{
|
{
|
||||||
DroppedEpisode = droppedEpisode;
|
EpisodeInfo = episodeInfo;
|
||||||
ImportedEpisode = importedEpisode;
|
ImportedEpisode = importedEpisode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,9 @@
|
||||||
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Annotations\FieldDefinitionAttribute.cs" />
|
<Compile Include="Annotations\FieldDefinitionAttribute.cs" />
|
||||||
|
<Compile Include="Blacklisting\Blacklist.cs" />
|
||||||
|
<Compile Include="Blacklisting\BlacklistRepository.cs" />
|
||||||
|
<Compile Include="Blacklisting\BlacklistService.cs" />
|
||||||
<Compile Include="Configuration\Config.cs" />
|
<Compile Include="Configuration\Config.cs" />
|
||||||
<Compile Include="Configuration\ConfigFileProvider.cs" />
|
<Compile Include="Configuration\ConfigFileProvider.cs" />
|
||||||
<Compile Include="Configuration\ConfigRepository.cs" />
|
<Compile Include="Configuration\ConfigRepository.cs" />
|
||||||
|
@ -181,6 +184,7 @@
|
||||||
<Compile Include="Datastore\Migration\027_fix_omgwtfnzbs.cs">
|
<Compile Include="Datastore\Migration\027_fix_omgwtfnzbs.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Datastore\Migration\028_add_blacklist_table.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
||||||
|
@ -200,6 +204,7 @@
|
||||||
<Compile Include="Datastore\PagingSpecExtensions.cs" />
|
<Compile Include="Datastore\PagingSpecExtensions.cs" />
|
||||||
<Compile Include="Datastore\RelationshipExtensions.cs" />
|
<Compile Include="Datastore\RelationshipExtensions.cs" />
|
||||||
<Compile Include="Datastore\TableMapping.cs" />
|
<Compile Include="Datastore\TableMapping.cs" />
|
||||||
|
<Compile Include="DecisionEngine\Specifications\BlacklistSpecification.cs" />
|
||||||
<Compile Include="DecisionEngine\Specifications\DownloadDecision.cs" />
|
<Compile Include="DecisionEngine\Specifications\DownloadDecision.cs" />
|
||||||
<Compile Include="DecisionEngine\IRejectWithReason.cs" />
|
<Compile Include="DecisionEngine\IRejectWithReason.cs" />
|
||||||
<Compile Include="DecisionEngine\IDecisionEngineSpecification.cs" />
|
<Compile Include="DecisionEngine\IDecisionEngineSpecification.cs" />
|
||||||
|
@ -218,17 +223,23 @@
|
||||||
<Compile Include="DecisionEngine\QualityUpgradableSpecification.cs" />
|
<Compile Include="DecisionEngine\QualityUpgradableSpecification.cs" />
|
||||||
<Compile Include="DecisionEngine\Specifications\Search\SingleEpisodeSearchMatchSpecification.cs" />
|
<Compile Include="DecisionEngine\Specifications\Search\SingleEpisodeSearchMatchSpecification.cs" />
|
||||||
<Compile Include="DecisionEngine\Specifications\UpgradeDiskSpecification.cs" />
|
<Compile Include="DecisionEngine\Specifications\UpgradeDiskSpecification.cs" />
|
||||||
<Compile Include="DecisionEngine\Specifications\RssSync\UpgradeHistorySpecification.cs" />
|
<Compile Include="DecisionEngine\Specifications\RssSync\HistorySpecification.cs" />
|
||||||
<Compile Include="DiskSpace\DiskSpace.cs" />
|
<Compile Include="DiskSpace\DiskSpace.cs" />
|
||||||
<Compile Include="DiskSpace\DiskSpaceService.cs" />
|
<Compile Include="DiskSpace\DiskSpaceService.cs" />
|
||||||
<Compile Include="Download\Clients\Sabnzbd\ConnectionInfoModel.cs" />
|
<Compile Include="Download\Clients\Sabnzbd\ConnectionInfoModel.cs" />
|
||||||
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdPriorityTypeConverter.cs" />
|
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdPriorityTypeConverter.cs" />
|
||||||
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdQueueTimeConverter.cs" />
|
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdQueueTimeConverter.cs" />
|
||||||
<Compile Include="Download\Clients\Sabnzbd\SabAutoConfigureService.cs" />
|
<Compile Include="Download\Clients\Sabnzbd\SabAutoConfigureService.cs" />
|
||||||
|
<Compile Include="Download\Clients\Sabnzbd\SabCommunicationProxy.cs" />
|
||||||
|
<Compile Include="Download\FailedDownloadCommand.cs" />
|
||||||
|
<Compile Include="Download\HistoryItem.cs" />
|
||||||
|
<Compile Include="Download\DownloadFailedEvent.cs" />
|
||||||
<Compile Include="Download\DownloadApprovedReports.cs" />
|
<Compile Include="Download\DownloadApprovedReports.cs" />
|
||||||
<Compile Include="Download\DownloadClientProvider.cs" />
|
<Compile Include="Download\DownloadClientProvider.cs" />
|
||||||
<Compile Include="Download\DownloadClientType.cs" />
|
<Compile Include="Download\DownloadClientType.cs" />
|
||||||
|
<Compile Include="Download\FailedDownloadService.cs" />
|
||||||
<Compile Include="Download\QueueItem.cs" />
|
<Compile Include="Download\QueueItem.cs" />
|
||||||
|
<Compile Include="Download\RedownloadFailedDownloadService.cs" />
|
||||||
<Compile Include="Exceptions\BadRequestException.cs" />
|
<Compile Include="Exceptions\BadRequestException.cs" />
|
||||||
<Compile Include="Exceptions\DownstreamException.cs" />
|
<Compile Include="Exceptions\DownstreamException.cs" />
|
||||||
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
||||||
|
@ -307,6 +318,8 @@
|
||||||
<Compile Include="Notifications\Xbmc\Model\VersionResult.cs" />
|
<Compile Include="Notifications\Xbmc\Model\VersionResult.cs" />
|
||||||
<Compile Include="Notifications\Xbmc\Model\XbmcJsonResult.cs" />
|
<Compile Include="Notifications\Xbmc\Model\XbmcJsonResult.cs" />
|
||||||
<Compile Include="Notifications\Xbmc\Model\XbmcVersion.cs" />
|
<Compile Include="Notifications\Xbmc\Model\XbmcVersion.cs" />
|
||||||
|
<Compile Include="Parser\InvalidDateException.cs" />
|
||||||
|
<Compile Include="Parser\Model\SeriesTitleInfo.cs" />
|
||||||
<Compile Include="ProgressMessaging\CommandUpdatedEvent.cs" />
|
<Compile Include="ProgressMessaging\CommandUpdatedEvent.cs" />
|
||||||
<Compile Include="ProgressMessaging\ProgressMessageTarget.cs" />
|
<Compile Include="ProgressMessaging\ProgressMessageTarget.cs" />
|
||||||
<Compile Include="Instrumentation\SetLoggingLevel.cs" />
|
<Compile Include="Instrumentation\SetLoggingLevel.cs" />
|
||||||
|
@ -347,7 +360,6 @@
|
||||||
<Compile Include="IndexerSearch\Definitions\SeasonSearchCriteria.cs" />
|
<Compile Include="IndexerSearch\Definitions\SeasonSearchCriteria.cs" />
|
||||||
<Compile Include="IndexerSearch\Definitions\SingleEpisodeSearchCriteria.cs" />
|
<Compile Include="IndexerSearch\Definitions\SingleEpisodeSearchCriteria.cs" />
|
||||||
<Compile Include="IndexerSearch\NzbSearchService.cs" />
|
<Compile Include="IndexerSearch\NzbSearchService.cs" />
|
||||||
<Compile Include="IndexerSearch\SearchAndDownloadService.cs" />
|
|
||||||
<Compile Include="Indexers\RssParserBase.cs" />
|
<Compile Include="Indexers\RssParserBase.cs" />
|
||||||
<Compile Include="Indexers\RssSyncService.cs" />
|
<Compile Include="Indexers\RssSyncService.cs" />
|
||||||
<Compile Include="Indexers\IndexerBase.cs" />
|
<Compile Include="Indexers\IndexerBase.cs" />
|
||||||
|
@ -570,6 +582,7 @@
|
||||||
<Compile Include="Tv\SeriesStatusType.cs" />
|
<Compile Include="Tv\SeriesStatusType.cs" />
|
||||||
<Compile Include="Tv\RefreshSeriesService.cs" />
|
<Compile Include="Tv\RefreshSeriesService.cs" />
|
||||||
<Compile Include="Update\Commands\ApplicationUpdateCommand.cs" />
|
<Compile Include="Update\Commands\ApplicationUpdateCommand.cs" />
|
||||||
|
<Compile Include="Update\Commands\InstallUpdateCommand.cs" />
|
||||||
<Compile Include="Update\InstallUpdateService.cs" />
|
<Compile Include="Update\InstallUpdateService.cs" />
|
||||||
<Compile Include="Update\RecentUpdateProvider.cs" />
|
<Compile Include="Update\RecentUpdateProvider.cs" />
|
||||||
<Compile Include="Update\UpdateChanges.cs" />
|
<Compile Include="Update\UpdateChanges.cs" />
|
||||||
|
@ -625,7 +638,6 @@
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Download\Clients\Sabnzbd\Api\" />
|
|
||||||
<Folder Include="Download\Clients\uTorrent\" />
|
<Folder Include="Download\Clients\uTorrent\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Parser
|
||||||
|
{
|
||||||
|
public class InvalidDateException : NzbDroneException
|
||||||
|
{
|
||||||
|
public InvalidDateException(string message, params object[] args) : base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidDateException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,10 +7,11 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
public class ParsedEpisodeInfo
|
public class ParsedEpisodeInfo
|
||||||
{
|
{
|
||||||
public string SeriesTitle { get; set; }
|
public string SeriesTitle { get; set; }
|
||||||
|
public SeriesTitleInfo SeriesTitleInfo { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
public int SeasonNumber { get; set; }
|
public int SeasonNumber { get; set; }
|
||||||
public int[] EpisodeNumbers { get; set; }
|
public int[] EpisodeNumbers { get; set; }
|
||||||
public DateTime? AirDate { get; set; }
|
public String AirDate { get; set; }
|
||||||
public Language Language { get; set; }
|
public Language Language { get; set; }
|
||||||
|
|
||||||
public bool FullSeason { get; set; }
|
public bool FullSeason { get; set; }
|
||||||
|
@ -19,9 +20,9 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
{
|
{
|
||||||
string episodeString = "[Unknown Episode]";
|
string episodeString = "[Unknown Episode]";
|
||||||
|
|
||||||
if (AirDate != null && EpisodeNumbers == null)
|
if (IsDaily() && EpisodeNumbers == null)
|
||||||
{
|
{
|
||||||
episodeString = string.Format("{0}", AirDate.Value.ToString("yyyy-MM-dd"));
|
episodeString = string.Format("{0}", AirDate);
|
||||||
}
|
}
|
||||||
else if (FullSeason)
|
else if (FullSeason)
|
||||||
{
|
{
|
||||||
|
@ -34,5 +35,10 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
|
|
||||||
return string.Format("{0} - {1} {2}", SeriesTitle, episodeString, Quality);
|
return string.Format("{0} - {1} {2}", SeriesTitle, episodeString, Quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsDaily()
|
||||||
|
{
|
||||||
|
return !String.IsNullOrWhiteSpace(AirDate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Parser.Model
|
||||||
|
{
|
||||||
|
public class SeriesTitleInfo
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string TitleWithoutYear { get; set; }
|
||||||
|
public int Year { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Parser
|
namespace NzbDrone.Core.Parser
|
||||||
{
|
{
|
||||||
|
@ -64,7 +65,7 @@ namespace NzbDrone.Core.Parser
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled)
|
RegexOptions.IgnoreCase | RegexOptions.Compiled)
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Regex NormalizeRegex = new Regex(@"((^|\W)(a|an|the|and|or|of)($|\W|_))|\W|_|(?:(?<=[^0-9]+)|\b)(?!(?:19\d{2}|20\d{2}))\d+(?=[^0-9ip]+|\b)",
|
private static readonly Regex NormalizeRegex = new Regex(@"((^|\W|_)(a|an|the|and|or|of)($|\W|_))|\W|_|(?:(?<=[^0-9]+)|\b)(?!(?:19\d{2}|20\d{2}))\d+(?=[^0-9ip]+|\b)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly Regex SimpleTitleRegex = new Regex(@"480[i|p]|720[i|p]|1080[i|p]|[x|h|x\s|h\s]264|DD\W?5\W1|\<|\>|\?|\*|\:|\|",
|
private static readonly Regex SimpleTitleRegex = new Regex(@"480[i|p]|720[i|p]|1080[i|p]|[x|h|x\s|h\s]264|DD\W?5\W1|\<|\>|\?|\*|\:|\|",
|
||||||
|
@ -75,6 +76,9 @@ namespace NzbDrone.Core.Parser
|
||||||
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>ita|italian)|(?<german>german\b)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)FR)(?:\W|_)",
|
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>ita|italian)|(?<german>german\b)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)FR)(?:\W|_)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static readonly Regex YearInTitleRegex = new Regex(@"^(?<title>.+?)(?:\W|_)?(?<year>\d{4})",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
public static ParsedEpisodeInfo ParsePath(string path)
|
public static ParsedEpisodeInfo ParsePath(string path)
|
||||||
{
|
{
|
||||||
var fileInfo = new FileInfo(path);
|
var fileInfo = new FileInfo(path);
|
||||||
|
@ -110,16 +114,20 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
if (match.Count != 0)
|
if (match.Count != 0)
|
||||||
{
|
{
|
||||||
var result = ParseMatchCollection(match);
|
try
|
||||||
if (result != null)
|
|
||||||
{
|
{
|
||||||
//Check if episode is in the future (most likely a parse error)
|
var result = ParseMatchCollection(match);
|
||||||
if (result.AirDate > DateTime.Now.AddDays(1).Date || result.AirDate < new DateTime(1970, 1, 1))
|
if (result != null)
|
||||||
break;
|
{
|
||||||
|
result.Language = ParseLanguage(title);
|
||||||
result.Language = ParseLanguage(title);
|
result.Quality = QualityParser.ParseQuality(title);
|
||||||
result.Quality = QualityParser.ParseQuality(title);
|
return result;
|
||||||
return result;
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidDateException ex)
|
||||||
|
{
|
||||||
|
Logger.TraceException(ex.Message, ex);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,6 +142,58 @@ namespace NzbDrone.Core.Parser
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ParseSeriesName(string title)
|
||||||
|
{
|
||||||
|
Logger.Trace("Parsing string '{0}'", title);
|
||||||
|
|
||||||
|
var parseResult = ParseTitle(title);
|
||||||
|
|
||||||
|
if (parseResult == null)
|
||||||
|
{
|
||||||
|
return CleanSeriesTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResult.SeriesTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CleanSeriesTitle(this string title)
|
||||||
|
{
|
||||||
|
long number = 0;
|
||||||
|
|
||||||
|
//If Title only contains numbers return it as is.
|
||||||
|
if (Int64.TryParse(title, out number))
|
||||||
|
return title;
|
||||||
|
|
||||||
|
return NormalizeRegex.Replace(title, String.Empty).ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CleanupEpisodeTitle(string title)
|
||||||
|
{
|
||||||
|
//this will remove (1),(2) from the end of multi part episodes.
|
||||||
|
return MultiPartCleanupRegex.Replace(title, string.Empty).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SeriesTitleInfo GetSeriesTitleInfo(string title)
|
||||||
|
{
|
||||||
|
var seriesTitleInfo = new SeriesTitleInfo();
|
||||||
|
seriesTitleInfo.Title = title;
|
||||||
|
|
||||||
|
var match = YearInTitleRegex.Match(title);
|
||||||
|
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
seriesTitleInfo.TitleWithoutYear = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
seriesTitleInfo.TitleWithoutYear = match.Groups["title"].Value;
|
||||||
|
seriesTitleInfo.Year = Convert.ToInt32(match.Groups["year"].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return seriesTitleInfo;
|
||||||
|
}
|
||||||
|
|
||||||
private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchCollection)
|
private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchCollection)
|
||||||
{
|
{
|
||||||
var seriesName = matchCollection[0].Groups["title"].Value.Replace('.', ' ');
|
var seriesName = matchCollection[0].Groups["title"].Value.Replace('.', ' ');
|
||||||
|
@ -163,10 +223,10 @@ namespace NzbDrone.Core.Parser
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
result = new ParsedEpisodeInfo
|
result = new ParsedEpisodeInfo
|
||||||
{
|
{
|
||||||
SeasonNumber = seasons.First(),
|
SeasonNumber = seasons.First(),
|
||||||
EpisodeNumbers = new int[0],
|
EpisodeNumbers = new int[0],
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (Match matchGroup in matchCollection)
|
foreach (Match matchGroup in matchCollection)
|
||||||
{
|
{
|
||||||
|
@ -212,33 +272,28 @@ namespace NzbDrone.Core.Parser
|
||||||
airmonth = tempDay;
|
airmonth = tempDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var airDate = new DateTime(airYear, airmonth, airday);
|
||||||
|
|
||||||
|
//Check if episode is in the future (most likely a parse error)
|
||||||
|
if (airDate > DateTime.Now.AddDays(1).Date || airDate < new DateTime(1970, 1, 1))
|
||||||
|
{
|
||||||
|
throw new InvalidDateException("Invalid date found: {0}", airDate);
|
||||||
|
}
|
||||||
|
|
||||||
result = new ParsedEpisodeInfo
|
result = new ParsedEpisodeInfo
|
||||||
{
|
{
|
||||||
AirDate = new DateTime(airYear, airmonth, airday).Date,
|
AirDate = airDate.ToString(Episode.AIR_DATE_FORMAT),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
result.SeriesTitle = CleanSeriesTitle(seriesName);
|
result.SeriesTitle = CleanSeriesTitle(seriesName);
|
||||||
|
result.SeriesTitleInfo = GetSeriesTitleInfo(result.SeriesTitle);
|
||||||
|
|
||||||
Logger.Trace("Episode Parsed. {0}", result);
|
Logger.Trace("Episode Parsed. {0}", result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ParseSeriesName(string title)
|
|
||||||
{
|
|
||||||
Logger.Trace("Parsing string '{0}'", title);
|
|
||||||
|
|
||||||
var parseResult = ParseTitle(title);
|
|
||||||
|
|
||||||
if (parseResult == null)
|
|
||||||
{
|
|
||||||
return CleanSeriesTitle(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseResult.SeriesTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Language ParseLanguage(string title)
|
private static Language ParseLanguage(string title)
|
||||||
{
|
{
|
||||||
var lowerTitle = title.ToLower();
|
var lowerTitle = title.ToLower();
|
||||||
|
@ -332,22 +387,5 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string CleanSeriesTitle(this string title)
|
|
||||||
{
|
|
||||||
long number = 0;
|
|
||||||
|
|
||||||
//If Title only contains numbers return it as is.
|
|
||||||
if (Int64.TryParse(title, out number))
|
|
||||||
return title;
|
|
||||||
|
|
||||||
return NormalizeRegex.Replace(title, String.Empty).ToLower();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string CleanupEpisodeTitle(string title)
|
|
||||||
{
|
|
||||||
//this will remove (1),(2) from the end of multi part episodes.
|
|
||||||
return MultiPartCleanupRegex.Replace(title, string.Empty).Trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -68,15 +68,22 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
public Series GetSeries(string title)
|
public Series GetSeries(string title)
|
||||||
{
|
{
|
||||||
var searchTitle = title;
|
|
||||||
var parsedEpisodeInfo = Parser.ParseTitle(title);
|
var parsedEpisodeInfo = Parser.ParseTitle(title);
|
||||||
|
|
||||||
if (parsedEpisodeInfo != null)
|
if (parsedEpisodeInfo == null)
|
||||||
{
|
{
|
||||||
searchTitle = parsedEpisodeInfo.SeriesTitle;
|
return _seriesService.FindByTitle(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _seriesService.FindByTitle(searchTitle);
|
var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
|
||||||
|
|
||||||
|
if (series == null)
|
||||||
|
{
|
||||||
|
series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
|
||||||
|
parsedEpisodeInfo.SeriesTitleInfo.Year);
|
||||||
|
}
|
||||||
|
|
||||||
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
||||||
|
@ -104,7 +111,7 @@ namespace NzbDrone.Core.Parser
|
||||||
{
|
{
|
||||||
var result = new List<Episode>();
|
var result = new List<Episode>();
|
||||||
|
|
||||||
if (parsedEpisodeInfo.AirDate.HasValue)
|
if (parsedEpisodeInfo.IsDaily())
|
||||||
{
|
{
|
||||||
if (series.SeriesType == SeriesTypes.Standard)
|
if (series.SeriesType == SeriesTypes.Standard)
|
||||||
{
|
{
|
||||||
|
@ -112,7 +119,7 @@ namespace NzbDrone.Core.Parser
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeInfo = GetDailyEpisode(series, parsedEpisodeInfo.AirDate.Value, searchCriteria);
|
var episodeInfo = GetDailyEpisode(series, parsedEpisodeInfo.AirDate, searchCriteria);
|
||||||
|
|
||||||
if (episodeInfo != null)
|
if (episodeInfo != null)
|
||||||
{
|
{
|
||||||
|
@ -223,14 +230,14 @@ namespace NzbDrone.Core.Parser
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Episode GetDailyEpisode(Series series, DateTime airDate, SearchCriteriaBase searchCriteria)
|
private Episode GetDailyEpisode(Series series, String airDate, SearchCriteriaBase searchCriteria)
|
||||||
{
|
{
|
||||||
Episode episodeInfo = null;
|
Episode episodeInfo = null;
|
||||||
|
|
||||||
if (searchCriteria != null)
|
if (searchCriteria != null)
|
||||||
{
|
{
|
||||||
episodeInfo = searchCriteria.Episodes.SingleOrDefault(
|
episodeInfo = searchCriteria.Episodes.SingleOrDefault(
|
||||||
e => e.AirDate == airDate.ToString(Episode.AIR_DATE_FORMAT));
|
e => e.AirDate == airDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (episodeInfo == null)
|
if (episodeInfo == null)
|
||||||
|
|
|
@ -11,8 +11,8 @@ namespace NzbDrone.Core.Tv
|
||||||
public interface IEpisodeRepository : IBasicRepository<Episode>
|
public interface IEpisodeRepository : IBasicRepository<Episode>
|
||||||
{
|
{
|
||||||
Episode Find(int seriesId, int season, int episodeNumber);
|
Episode Find(int seriesId, int season, int episodeNumber);
|
||||||
Episode Get(int seriesId, DateTime date);
|
Episode Get(int seriesId, String date);
|
||||||
Episode Find(int seriesId, DateTime date);
|
Episode Find(int seriesId, String date);
|
||||||
List<Episode> GetEpisodes(int seriesId);
|
List<Episode> GetEpisodes(int seriesId);
|
||||||
List<Episode> GetEpisodes(int seriesId, int seasonNumber);
|
List<Episode> GetEpisodes(int seriesId, int seasonNumber);
|
||||||
List<Episode> GetEpisodeByFileId(int fileId);
|
List<Episode> GetEpisodeByFileId(int fileId);
|
||||||
|
@ -39,14 +39,14 @@ namespace NzbDrone.Core.Tv
|
||||||
return Query.SingleOrDefault(s => s.SeriesId == seriesId && s.SeasonNumber == season && s.EpisodeNumber == episodeNumber);
|
return Query.SingleOrDefault(s => s.SeriesId == seriesId && s.SeasonNumber == season && s.EpisodeNumber == episodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Episode Get(int seriesId, DateTime date)
|
public Episode Get(int seriesId, String date)
|
||||||
{
|
{
|
||||||
return Query.Single(s => s.SeriesId == seriesId && s.AirDate == date.ToString(Episode.AIR_DATE_FORMAT));
|
return Query.Single(s => s.SeriesId == seriesId && s.AirDate == date);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Episode Find(int seriesId, DateTime date)
|
public Episode Find(int seriesId, String date)
|
||||||
{
|
{
|
||||||
return Query.SingleOrDefault(s => s.SeriesId == seriesId && s.AirDate == date.ToString(Episode.AIR_DATE_FORMAT));
|
return Query.SingleOrDefault(s => s.SeriesId == seriesId && s.AirDate == date);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Episode> GetEpisodes(int seriesId)
|
public List<Episode> GetEpisodes(int seriesId)
|
||||||
|
|
|
@ -14,8 +14,8 @@ namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
Episode GetEpisode(int id);
|
Episode GetEpisode(int id);
|
||||||
Episode FindEpisode(int seriesId, int seasonNumber, int episodeNumber, bool useScene = false);
|
Episode FindEpisode(int seriesId, int seasonNumber, int episodeNumber, bool useScene = false);
|
||||||
Episode GetEpisode(int seriesId, DateTime date);
|
Episode GetEpisode(int seriesId, String date);
|
||||||
Episode FindEpisode(int seriesId, DateTime date);
|
Episode FindEpisode(int seriesId, String date);
|
||||||
List<Episode> GetEpisodeBySeries(int seriesId);
|
List<Episode> GetEpisodeBySeries(int seriesId);
|
||||||
List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber);
|
List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber);
|
||||||
PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec);
|
PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec);
|
||||||
|
@ -62,12 +62,12 @@ namespace NzbDrone.Core.Tv
|
||||||
return _episodeRepository.Find(seriesId, seasonNumber, episodeNumber);
|
return _episodeRepository.Find(seriesId, seasonNumber, episodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Episode GetEpisode(int seriesId, DateTime date)
|
public Episode GetEpisode(int seriesId, String date)
|
||||||
{
|
{
|
||||||
return _episodeRepository.Get(seriesId, date);
|
return _episodeRepository.Get(seriesId, date);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Episode FindEpisode(int seriesId, DateTime date)
|
public Episode FindEpisode(int seriesId, String date)
|
||||||
{
|
{
|
||||||
return _episodeRepository.Find(seriesId, date);
|
return _episodeRepository.Find(seriesId, date);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
bool SeriesPathExists(string path);
|
bool SeriesPathExists(string path);
|
||||||
Series FindByTitle(string cleanTitle);
|
Series FindByTitle(string cleanTitle);
|
||||||
|
Series FindByTitle(string cleanTitle, int year);
|
||||||
Series FindByTvdbId(int tvdbId);
|
Series FindByTvdbId(int tvdbId);
|
||||||
Series FindByTvRageId(int tvRageId);
|
Series FindByTvRageId(int tvRageId);
|
||||||
void SetSeriesType(int seriesId, SeriesTypes seriesTypes);
|
void SetSeriesType(int seriesId, SeriesTypes seriesTypes);
|
||||||
|
@ -32,6 +33,12 @@ namespace NzbDrone.Core.Tv
|
||||||
return Query.SingleOrDefault(s => s.CleanTitle.Equals(cleanTitle, StringComparison.InvariantCultureIgnoreCase));
|
return Query.SingleOrDefault(s => s.CleanTitle.Equals(cleanTitle, StringComparison.InvariantCultureIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Series FindByTitle(string cleanTitle, int year)
|
||||||
|
{
|
||||||
|
return Query.SingleOrDefault(s => s.CleanTitle.Equals(cleanTitle, StringComparison.InvariantCultureIgnoreCase) &&
|
||||||
|
s.Year == year);
|
||||||
|
}
|
||||||
|
|
||||||
public Series FindByTvdbId(int tvdbId)
|
public Series FindByTvdbId(int tvdbId)
|
||||||
{
|
{
|
||||||
return Query.SingleOrDefault(s => s.TvdbId.Equals(tvdbId));
|
return Query.SingleOrDefault(s => s.TvdbId.Equals(tvdbId));
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace NzbDrone.Core.Tv
|
||||||
Series FindByTvdbId(int tvdbId);
|
Series FindByTvdbId(int tvdbId);
|
||||||
Series FindByTvRageId(int tvRageId);
|
Series FindByTvRageId(int tvRageId);
|
||||||
Series FindByTitle(string title);
|
Series FindByTitle(string title);
|
||||||
|
Series FindByTitle(string title, int year);
|
||||||
void SetSeriesType(int seriesId, SeriesTypes seriesTypes);
|
void SetSeriesType(int seriesId, SeriesTypes seriesTypes);
|
||||||
void DeleteSeries(int seriesId, bool deleteFiles);
|
void DeleteSeries(int seriesId, bool deleteFiles);
|
||||||
List<Series> GetAllSeries();
|
List<Series> GetAllSeries();
|
||||||
|
@ -100,6 +101,11 @@ namespace NzbDrone.Core.Tv
|
||||||
return _seriesRepository.FindByTitle(Parser.Parser.CleanSeriesTitle(title));
|
return _seriesRepository.FindByTitle(Parser.Parser.CleanSeriesTitle(title));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Series FindByTitle(string title, int year)
|
||||||
|
{
|
||||||
|
return _seriesRepository.FindByTitle(title, year);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetSeriesType(int seriesId, SeriesTypes seriesTypes)
|
public void SetSeriesType(int seriesId, SeriesTypes seriesTypes)
|
||||||
{
|
{
|
||||||
_seriesRepository.SetSeriesType(seriesId, seriesTypes);
|
_seriesRepository.SetSeriesType(seriesId, seriesTypes);
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Update.Commands
|
||||||
|
{
|
||||||
|
public class InstallUpdateCommand : Command
|
||||||
|
{
|
||||||
|
public UpdatePackage UpdatePackage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ namespace NzbDrone.Core.Update
|
||||||
void InstallUpdate(UpdatePackage updatePackage);
|
void InstallUpdate(UpdatePackage updatePackage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InstallUpdateService : IInstallUpdates, IExecute<ApplicationUpdateCommand>
|
public class InstallUpdateService : IInstallUpdates, IExecute<ApplicationUpdateCommand>, IExecute<InstallUpdateCommand>
|
||||||
{
|
{
|
||||||
private readonly ICheckUpdateService _checkUpdateService;
|
private readonly ICheckUpdateService _checkUpdateService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
@ -89,5 +89,10 @@ namespace NzbDrone.Core.Update
|
||||||
InstallUpdate(latestAvailable);
|
InstallUpdate(latestAvailable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Execute(InstallUpdateCommand message)
|
||||||
|
{
|
||||||
|
InstallUpdate(message.UpdatePackage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,10 @@ define(
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
QualityProfileCollection.fetch();
|
QualityProfileCollection.fetch();
|
||||||
RootFolderCollection.fetch();
|
RootFolderCollection.fetch()
|
||||||
|
.done(function () {
|
||||||
|
RootFolderCollection.synced = true;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
|
|
|
@ -37,7 +37,7 @@ define(
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
this.currentDirs.show(new LoadingView());
|
this.currentDirs.show(new LoadingView());
|
||||||
|
|
||||||
if (RootFolderCollection.any()) {
|
if (RootFolderCollection.synced) {
|
||||||
this._showCurrentDirs();
|
this._showCurrentDirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,10 @@ define(
|
||||||
icon = 'icon-nd-imported';
|
icon = 'icon-nd-imported';
|
||||||
toolTip = 'Episode downloaded successfully and picked up from download client';
|
toolTip = 'Episode downloaded successfully and picked up from download client';
|
||||||
break;
|
break;
|
||||||
|
case 'downloadFailed':
|
||||||
|
icon = 'icon-nd-download-failed';
|
||||||
|
toolTip = 'Episode download failed';
|
||||||
|
break;
|
||||||
default :
|
default :
|
||||||
icon = 'icon-question';
|
icon = 'icon-question';
|
||||||
toolTip = 'unknown event';
|
toolTip = 'unknown event';
|
||||||
|
|
|
@ -43,7 +43,7 @@ define(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
CommandCollection.bind('add sync', function () {
|
CommandCollection.bind('sync', function () {
|
||||||
var command = CommandCollection.findCommand(options.command);
|
var command = CommandCollection.findCommand(options.command);
|
||||||
if (command) {
|
if (command) {
|
||||||
self._bindToCommandModel.call(self, command, options);
|
self._bindToCommandModel.call(self, command, options);
|
||||||
|
|
|
@ -156,4 +156,9 @@
|
||||||
.icon-fatal:before {
|
.icon-fatal:before {
|
||||||
.icon(@remove-sign);
|
.icon(@remove-sign);
|
||||||
color : purple;
|
color : purple;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-nd-download-failed:before {
|
||||||
|
.icon(@cloud-download);
|
||||||
|
color: @errorText;
|
||||||
}
|
}
|
|
@ -68,6 +68,6 @@ define(
|
||||||
return this.title;
|
return this.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '{0} ({1})'.format(this.title, this.year);
|
return new Handlebars.SafeString('{0} <em>({1})</em>'.format(this.title, this.year));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,30 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'marionette'
|
'vent',
|
||||||
], function (Marionette) {
|
'marionette',
|
||||||
|
'jquery'
|
||||||
|
], function (vent, Marionette, $) {
|
||||||
|
|
||||||
return Marionette.ItemView.extend({
|
return Marionette.ItemView.extend({
|
||||||
template: 'History/Details/HistoryDetailsViewTemplate'
|
template: 'History/Details/HistoryDetailsViewTemplate',
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .x-mark-as-failed': '_markAsFailed'
|
||||||
|
},
|
||||||
|
|
||||||
|
_markAsFailed: function () {
|
||||||
|
var url = window.NzbDrone.ApiRoot + '/history/failed';
|
||||||
|
var data = {
|
||||||
|
id: this.model.get('id')
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'POST',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
Details
|
{{#if_eq eventType compare="grabbed"}}Grabbed{{/if_eq}}
|
||||||
|
{{#if_eq eventType compare="downloadFailed"}}Download Failed{{/if_eq}}
|
||||||
|
{{#if_eq eventType compare="downloadFolderImported"}}Episode Imported{{/if_eq}}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,24 +29,33 @@
|
||||||
|
|
||||||
{{#if nzbInfoUrl}}
|
{{#if nzbInfoUrl}}
|
||||||
<dt>Info</dt>
|
<dt>Info</dt>
|
||||||
<dd><a href="{{infoUrl}}">{{infoUrl}}o</a></dd>
|
<dd><a href="{{nzbInfoUrl}}">{{nzbInfoUrl}}</a></dd>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/with}}
|
{{/with}}
|
||||||
</dl>
|
</dl>
|
||||||
{{else}}
|
{{/if_eq}}
|
||||||
|
{{#if_eq eventType compare="downloadFailed"}}
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
{{#with data}}
|
||||||
|
<dt>Message</dt>
|
||||||
|
<dd>{{message}}</dd>
|
||||||
|
{{/with}}
|
||||||
|
</dl>
|
||||||
|
{{/if_eq}}
|
||||||
|
{{#if_eq eventType compare="downloadFolderImported"}}
|
||||||
{{#if data}}
|
{{#if data}}
|
||||||
{{#with data}}
|
{{#with data}}
|
||||||
<dl class="dl-horizontal">
|
<dl class="dl-horizontal">
|
||||||
{{#if droppedPath}}
|
{{#if droppedPath}}
|
||||||
<dt>Source:</dt>
|
<dt>Source:</dt>
|
||||||
<dd>{{droppedPath}}</dd>
|
<dd>{{droppedPath}}</dd>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if importedPath}}
|
{{#if importedPath}}
|
||||||
<dt>Imported To:</dt>
|
<dt>Imported To:</dt>
|
||||||
<dd>{{importedPath}}</dd>
|
<dd>{{importedPath}}</dd>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</dl>
|
</dl>
|
||||||
{{/with}}
|
{{/with}}
|
||||||
{{else}}
|
{{else}}
|
||||||
No details available
|
No details available
|
||||||
|
@ -52,6 +63,7 @@
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
{{#if_eq eventType compare="grabbed"}}<button class="btn btn-danger x-mark-as-failed">mark as failed</button>{{/if_eq}}
|
||||||
<button class="btn" data-dismiss="modal">close</button>
|
<button class="btn" data-dismiss="modal">close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
<i class="icon-remove x-remove" title="Remove"/>
|
|
||||||
<i class="icon-repeat x-redownload" title="Re-Download"/>
|
|
|
@ -21,7 +21,7 @@ define(
|
||||||
},
|
},
|
||||||
|
|
||||||
_showDetails: function () {
|
_showDetails: function () {
|
||||||
vent.trigger(vent.Commands.ShowHistoryDetails, { history: this.model });
|
vent.trigger(vent.Commands.ShowHistoryDetails, { model: this.model });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,11 +10,28 @@ define(
|
||||||
template: 'Settings/MediaManagement/FileManagement/FileManagementViewTemplate',
|
template: 'Settings/MediaManagement/FileManagement/FileManagementViewTemplate',
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
recyclingBin: '.x-path'
|
recyclingBin : '.x-path',
|
||||||
|
failedDownloadHandlingCheckbox: '.x-failed-download-handling',
|
||||||
|
failedDownloadOptions : '.x-failed-download-options'
|
||||||
|
},
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'change .x-failed-download-handling': '_setFailedDownloadOptionsVisibility'
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
this.ui.recyclingBin.autoComplete('/directories');
|
this.ui.recyclingBin.autoComplete('/directories');
|
||||||
|
},
|
||||||
|
|
||||||
|
_setFailedDownloadOptionsVisibility: function () {
|
||||||
|
var checked = this.ui.failedDownloadHandlingCheckbox.prop('checked');
|
||||||
|
if (checked) {
|
||||||
|
this.ui.failedDownloadOptions.slideDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this.ui.failedDownloadOptions.slideUp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,3 +52,69 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="advanced-setting">
|
||||||
|
<legend>Failed Download Handling</legend>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Enable</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox toggle well">
|
||||||
|
<input type="checkbox" name="enableFailedDownloadHandling" class="x-failed-download-handling"/>
|
||||||
|
<p>
|
||||||
|
<span>Yes</span>
|
||||||
|
<span>No</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="btn btn-primary slide-button"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="help-inline-checkbox">
|
||||||
|
<i class="icon-question-sign" title="Process failed downloads and blacklist the release"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="x-failed-download-options">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Redownload</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox toggle well">
|
||||||
|
<input type="checkbox" name="autoRedownloadFailed"/>
|
||||||
|
<p>
|
||||||
|
<span>Yes</span>
|
||||||
|
<span>No</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="btn btn-primary slide-button"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="help-inline-checkbox">
|
||||||
|
<i class="icon-question-sign" title="Automatically search for and attempt to download another release when a download fails?"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Remove</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox toggle well">
|
||||||
|
<input type="checkbox" name="removeFailedDownloads"/>
|
||||||
|
<p>
|
||||||
|
<span>Yes</span>
|
||||||
|
<span>No</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="btn btn-primary slide-button"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="help-inline-checkbox">
|
||||||
|
<i class="icon-question-sign" title="Automatically remove failed downloads from history and encrypted downloads from queue?"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
|
@ -17,7 +17,7 @@ define(
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'change .x-rename-episodes': '_setNamingOptionsVisibility'
|
'change .x-rename-episodes': '_setFailedDownloadOptionsVisibility'
|
||||||
},
|
},
|
||||||
|
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
|
@ -32,7 +32,7 @@ define(
|
||||||
this._updateSamples();
|
this._updateSamples();
|
||||||
},
|
},
|
||||||
|
|
||||||
_setNamingOptionsVisibility: function () {
|
_setFailedDownloadOptionsVisibility: function () {
|
||||||
var checked = this.ui.renameEpisodesCheckbox.prop('checked');
|
var checked = this.ui.renameEpisodesCheckbox.prop('checked');
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.ui.namingOptions.slideDown();
|
this.ui.namingOptions.slideDown();
|
||||||
|
|
|
@ -7,8 +7,9 @@ define(
|
||||||
'Series/Edit/EditSeriesView',
|
'Series/Edit/EditSeriesView',
|
||||||
'Series/Delete/DeleteSeriesView',
|
'Series/Delete/DeleteSeriesView',
|
||||||
'Episode/EpisodeDetailsLayout',
|
'Episode/EpisodeDetailsLayout',
|
||||||
'History/Details/HistoryDetailsView'
|
'History/Details/HistoryDetailsView',
|
||||||
], function (vent, AppLayout, Marionette, EditSeriesView, DeleteSeriesView, EpisodeDetailsLayout, HistoryDetailsView) {
|
'System/Logs/Table/Details/LogDetailsView'
|
||||||
|
], function (vent, AppLayout, Marionette, EditSeriesView, DeleteSeriesView, EpisodeDetailsLayout, HistoryDetailsView, LogDetailsView) {
|
||||||
|
|
||||||
return Marionette.AppRouter.extend({
|
return Marionette.AppRouter.extend({
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ define(
|
||||||
vent.on(vent.Commands.DeleteSeriesCommand, this._deleteSeries, this);
|
vent.on(vent.Commands.DeleteSeriesCommand, this._deleteSeries, this);
|
||||||
vent.on(vent.Commands.ShowEpisodeDetails, this._showEpisode, this);
|
vent.on(vent.Commands.ShowEpisodeDetails, this._showEpisode, this);
|
||||||
vent.on(vent.Commands.ShowHistoryDetails, this._showHistory, this);
|
vent.on(vent.Commands.ShowHistoryDetails, this._showHistory, this);
|
||||||
|
vent.on(vent.Commands.ShowLogDetails, this._showLogDetails, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
_closeModal: function () {
|
_closeModal: function () {
|
||||||
|
@ -40,7 +42,12 @@ define(
|
||||||
},
|
},
|
||||||
|
|
||||||
_showHistory: function (options) {
|
_showHistory: function (options) {
|
||||||
var view = new HistoryDetailsView({ model: options.history });
|
var view = new HistoryDetailsView({ model: options.model });
|
||||||
|
AppLayout.modalRegion.show(view);
|
||||||
|
},
|
||||||
|
|
||||||
|
_showLogDetails: function (options) {
|
||||||
|
var view = new LogDetailsView({ model: options.model });
|
||||||
AppLayout.modalRegion.show(view);
|
AppLayout.modalRegion.show(view);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -130,12 +130,12 @@ define(
|
||||||
filename: filename
|
filename: filename
|
||||||
});
|
});
|
||||||
|
|
||||||
this.listenToOnce(contentsModel, 'sync', this._showContents);
|
this.listenToOnce(contentsModel, 'sync', this._showDetails);
|
||||||
|
|
||||||
contentsModel.fetch({ dataType: 'text' });
|
contentsModel.fetch({ dataType: 'text' });
|
||||||
},
|
},
|
||||||
|
|
||||||
_showContents: function (model) {
|
_showDetails: function (model) {
|
||||||
this.contents.show(new ContentsView({ model: model }));
|
this.contents.show(new ContentsView({ model: model }));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ define(
|
||||||
className: 'log-file-row',
|
className: 'log-file-row',
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'click': '_showContents'
|
'click': '_showDetails'
|
||||||
},
|
},
|
||||||
|
|
||||||
_showContents: function () {
|
_showDetails: function () {
|
||||||
vent.trigger(vent.Commands.ShowLogFile, { model: this.model });
|
vent.trigger(vent.Commands.ShowLogFile, { model: this.model });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,4 +18,8 @@
|
||||||
|
|
||||||
.log-file-row {
|
.log-file-row {
|
||||||
.clickable;
|
.clickable;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-row {
|
||||||
|
.clickable;
|
||||||
}
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'vent',
|
||||||
|
'marionette'
|
||||||
|
], function (vent, Marionette) {
|
||||||
|
|
||||||
|
return Marionette.ItemView.extend({
|
||||||
|
template: 'System/Logs/Table/Details/LogDetailsViewTemplate'
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
<div class="log-details-modal">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
|
||||||
|
<h3>Details</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
Message
|
||||||
|
<pre>{{message}}</pre>
|
||||||
|
|
||||||
|
{{#if exception}}
|
||||||
|
<br/>
|
||||||
|
Exception
|
||||||
|
<pre>{{exception}}</pre>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn" data-dismiss="modal">close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'vent',
|
||||||
|
'backgrid'
|
||||||
|
], function (vent, Backgrid) {
|
||||||
|
|
||||||
|
return Backgrid.Row.extend({
|
||||||
|
className: 'log-row',
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click': '_showDetails'
|
||||||
|
},
|
||||||
|
|
||||||
|
_showDetails: function () {
|
||||||
|
vent.trigger(vent.Commands.ShowLogDetails, { model: this.model });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,11 +6,12 @@ define(
|
||||||
'backgrid',
|
'backgrid',
|
||||||
'System/Logs/Table/LogTimeCell',
|
'System/Logs/Table/LogTimeCell',
|
||||||
'System/Logs/Table/LogLevelCell',
|
'System/Logs/Table/LogLevelCell',
|
||||||
|
'System/Logs/Table/LogRow',
|
||||||
'Shared/Grid/Pager',
|
'Shared/Grid/Pager',
|
||||||
'System/Logs/LogsCollection',
|
'System/Logs/LogsCollection',
|
||||||
'Shared/Toolbar/ToolbarLayout',
|
'Shared/Toolbar/ToolbarLayout',
|
||||||
'Shared/LoadingView'
|
'Shared/LoadingView'
|
||||||
], function (vent, Marionette, Backgrid, LogTimeCell, LogLevelCell, GridPager, LogCollection, ToolbarLayout, LoadingView) {
|
], function (vent, Marionette, Backgrid, LogTimeCell, LogLevelCell, LogRow, GridPager, LogCollection, ToolbarLayout, LoadingView) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'System/Logs/Table/LogsTableLayoutTemplate',
|
template: 'System/Logs/Table/LogsTableLayoutTemplate',
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ define(
|
||||||
|
|
||||||
_showTable: function () {
|
_showTable: function () {
|
||||||
this.grid.show(new Backgrid.Grid({
|
this.grid.show(new Backgrid.Grid({
|
||||||
row : Backgrid.Row,
|
row : LogRow,
|
||||||
columns : this.columns,
|
columns : this.columns,
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
className : 'table table-hover'
|
className : 'table table-hover'
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'marionette'
|
'marionette',
|
||||||
], function (Marionette) {
|
'Commands/CommandController'
|
||||||
|
], function (Marionette, CommandController) {
|
||||||
return Marionette.ItemView.extend({
|
return Marionette.ItemView.extend({
|
||||||
template: 'System/Update/UpdateItemViewTemplate',
|
template: 'System/Update/UpdateItemViewTemplate',
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ define(
|
||||||
},
|
},
|
||||||
|
|
||||||
_installUpdate: function () {
|
_installUpdate: function () {
|
||||||
this.model.save();
|
CommandController.Execute('installUpdate', { updatePackage: this.model.toJSON() });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,7 @@ define(
|
||||||
CloseModalCommand : 'CloseModalCommand',
|
CloseModalCommand : 'CloseModalCommand',
|
||||||
ShowEpisodeDetails : 'ShowEpisodeDetails',
|
ShowEpisodeDetails : 'ShowEpisodeDetails',
|
||||||
ShowHistoryDetails : 'ShowHistoryDetails',
|
ShowHistoryDetails : 'ShowHistoryDetails',
|
||||||
|
ShowLogDetails : 'ShowLogDetails',
|
||||||
SaveSettings : 'saveSettings',
|
SaveSettings : 'saveSettings',
|
||||||
ShowLogFile : 'showLogFile'
|
ShowLogFile : 'showLogFile'
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue