Merge branch 'develop'
This commit is contained in:
commit
4dd6d122a0
|
@ -12,5 +12,6 @@ namespace NzbDrone.Api.Commands
|
|||
public DateTime StateChangeTime { get; set; }
|
||||
public Boolean SendUpdatesToClient { get; set; }
|
||||
public CommandStatus State { get; set; }
|
||||
public DateTime? LastExecutionTime { get; set; }
|
||||
}
|
||||
}
|
|
@ -16,6 +16,15 @@ namespace NzbDrone.Api.Config
|
|||
.SetValidator(rootFolderValidator)
|
||||
.SetValidator(pathExistsValidator)
|
||||
.When(c => !String.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder));
|
||||
|
||||
SharedValidator.RuleFor(c => c.BlacklistGracePeriod)
|
||||
.InclusiveBetween(1, 24);
|
||||
|
||||
SharedValidator.RuleFor(c => c.BlacklistRetryInterval)
|
||||
.InclusiveBetween(5, 120);
|
||||
|
||||
SharedValidator.RuleFor(c => c.BlacklistRetryLimit)
|
||||
.InclusiveBetween(0, 10);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,5 +12,8 @@ namespace NzbDrone.Api.Config
|
|||
public Boolean AutoRedownloadFailed { get; set; }
|
||||
public Boolean RemoveFailedDownloads { get; set; }
|
||||
public Boolean EnableFailedDownloadHandling { get; set; }
|
||||
public Int32 BlacklistGracePeriod { get; set; }
|
||||
public Int32 BlacklistRetryInterval { get; set; }
|
||||
public Int32 BlacklistRetryLimit { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ namespace NzbDrone.Api.Config
|
|||
public IndexerConfigModule(IConfigService configService)
|
||||
: base(configService)
|
||||
{
|
||||
SharedValidator.RuleFor(c => c.RssSyncInterval).InclusiveBetween(10, 120);
|
||||
SharedValidator.RuleFor(c => c.RssSyncInterval)
|
||||
.InclusiveBetween(10, 120)
|
||||
.When(c => c.RssSyncInterval > 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Nancy.Responses;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using Nancy.ModelBinding;
|
||||
using NzbDrone.Api.Mapping;
|
||||
|
@ -39,6 +40,7 @@ namespace NzbDrone.Api.Config
|
|||
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
|
||||
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
|
||||
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
|
||||
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
|
||||
}
|
||||
|
||||
private void UpdateNamingConfig(NamingConfigResource resource)
|
||||
|
@ -72,7 +74,6 @@ namespace NzbDrone.Api.Config
|
|||
|
||||
private JsonResponse<NamingSampleResource> GetExamples(NamingConfigResource config)
|
||||
{
|
||||
//TODO: Validate that the format is valid
|
||||
var nameSpec = config.InjectTo<NamingConfig>();
|
||||
var sampleResource = new NamingSampleResource();
|
||||
|
||||
|
@ -92,6 +93,14 @@ namespace NzbDrone.Api.Config
|
|||
? "Invalid format"
|
||||
: dailyEpisodeSampleResult.Filename;
|
||||
|
||||
sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace()
|
||||
? "Invalid format"
|
||||
: _filenameSampleService.GetSeriesFolderSample(nameSpec);
|
||||
|
||||
sampleResource.SeasonFolderExample = nameSpec.SeasonFolderFormat.IsNullOrWhiteSpace()
|
||||
? "Invalid format"
|
||||
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
|
||||
|
||||
return sampleResource.AsResponse();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,5 +5,7 @@
|
|||
public string SingleEpisodeExample { get; set; }
|
||||
public string MultiEpisodeExample { get; set; }
|
||||
public string DailyEpisodeExample { get; set; }
|
||||
public string SeriesFolderExample { get; set; }
|
||||
public string SeasonFolderExample { get; set; }
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ using NzbDrone.Core.Messaging.Events;
|
|||
namespace NzbDrone.Api.Health
|
||||
{
|
||||
public class HealthModule : NzbDroneRestModuleWithSignalR<HealthResource, HealthCheck>,
|
||||
IHandle<TriggerHealthCheckEvent>
|
||||
IHandle<HealthCheckCompleteEvent>
|
||||
{
|
||||
private readonly IHealthCheckService _healthCheckService;
|
||||
|
||||
|
@ -21,10 +21,10 @@ namespace NzbDrone.Api.Health
|
|||
|
||||
private List<HealthResource> GetHealth()
|
||||
{
|
||||
return ToListResource(_healthCheckService.PerformHealthCheck);
|
||||
return ToListResource(_healthCheckService.Results);
|
||||
}
|
||||
|
||||
public void Handle(TriggerHealthCheckEvent message)
|
||||
public void Handle(HealthCheckCompleteEvent message)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Sync);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.HealthCheck;
|
||||
|
||||
|
||||
namespace NzbDrone.Api.Health
|
||||
{
|
||||
public class HealthResource : RestResource
|
||||
{
|
||||
public HealthCheckResultType Type { get; set; }
|
||||
public HealthCheckResult Type { get; set; }
|
||||
public String Message { get; set; }
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Api.Indexers
|
||||
{
|
||||
|
@ -11,6 +10,7 @@ namespace NzbDrone.Api.Indexers
|
|||
{
|
||||
public QualityModel Quality { get; set; }
|
||||
public Int32 Age { get; set; }
|
||||
public Double AgeHours { get; set; }
|
||||
public Int64 Size { get; set; }
|
||||
public String Indexer { get; set; }
|
||||
public String ReleaseGroup { get; set; }
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Nancy;
|
||||
using Nancy.Responses;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Api.MediaCovers
|
||||
{
|
||||
public class MediaCoverModule : NzbDroneApiModule
|
||||
{
|
||||
private const string MEDIA_COVER_ROUTE = @"/(?<seriesId>\d+)/(?<filename>(.+)\.(jpg|png|gif))";
|
||||
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
|
||||
public MediaCoverModule(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider) : base("MediaCover")
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_diskProvider = diskProvider;
|
||||
|
||||
Get[MEDIA_COVER_ROUTE] = options => GetMediaCover(options.seriesId, options.filename);
|
||||
}
|
||||
|
||||
private Response GetMediaCover(int seriesId, string filename)
|
||||
{
|
||||
var filePath = Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover", seriesId.ToString(), filename);
|
||||
|
||||
if (!_diskProvider.FileExists(filePath))
|
||||
return new NotFoundResponse();
|
||||
|
||||
return new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -136,10 +136,11 @@
|
|||
<Compile Include="Frontend\Mappers\IMapHttpRequestsToDisk.cs" />
|
||||
<Compile Include="Frontend\Mappers\StaticResourceMapperBase.cs" />
|
||||
<Compile Include="Frontend\StaticResourceModule.cs" />
|
||||
<Compile Include="Health\HistoryResource.cs" />
|
||||
<Compile Include="Health\HealthResource.cs" />
|
||||
<Compile Include="Health\HealthModule.cs" />
|
||||
<Compile Include="History\HistoryResource.cs" />
|
||||
<Compile Include="History\HistoryModule.cs" />
|
||||
<Compile Include="MediaCovers\MediaCoverModule.cs" />
|
||||
<Compile Include="Metadata\MetadataResource.cs" />
|
||||
<Compile Include="Metadata\MetadataModule.cs" />
|
||||
<Compile Include="Notifications\NotificationSchemaModule.cs" />
|
||||
|
|
|
@ -13,6 +13,7 @@ using NzbDrone.Api.Validation;
|
|||
using NzbDrone.Api.Mapping;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
|
||||
namespace NzbDrone.Api.Series
|
||||
{
|
||||
|
@ -27,11 +28,13 @@ namespace NzbDrone.Api.Series
|
|||
private readonly ICommandExecutor _commandExecutor;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly ISeriesStatisticsService _seriesStatisticsService;
|
||||
private readonly ISceneMappingService _sceneMappingService;
|
||||
private readonly IMapCoversToLocal _coverMapper;
|
||||
|
||||
public SeriesModule(ICommandExecutor commandExecutor,
|
||||
ISeriesService seriesService,
|
||||
ISeriesStatisticsService seriesStatisticsService,
|
||||
ISceneMappingService sceneMappingService,
|
||||
IMapCoversToLocal coverMapper,
|
||||
RootFolderValidator rootFolderValidator,
|
||||
PathExistsValidator pathExistsValidator,
|
||||
|
@ -44,6 +47,8 @@ namespace NzbDrone.Api.Series
|
|||
_commandExecutor = commandExecutor;
|
||||
_seriesService = seriesService;
|
||||
_seriesStatisticsService = seriesStatisticsService;
|
||||
_sceneMappingService = sceneMappingService;
|
||||
|
||||
_coverMapper = coverMapper;
|
||||
|
||||
GetResourceAll = AllSeries;
|
||||
|
@ -58,7 +63,6 @@ namespace NzbDrone.Api.Series
|
|||
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||
.IsValidPath()
|
||||
.SetValidator(rootFolderValidator)
|
||||
.SetValidator(pathExistsValidator)
|
||||
.SetValidator(seriesPathValidator)
|
||||
.SetValidator(droneFactoryValidator);
|
||||
|
||||
|
@ -68,6 +72,21 @@ namespace NzbDrone.Api.Series
|
|||
PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator);
|
||||
}
|
||||
|
||||
private void PopulateAlternativeTitles(List<SeriesResource> resources)
|
||||
{
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
PopulateAlternativeTitles(resource);
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateAlternativeTitles(SeriesResource resource)
|
||||
{
|
||||
var mapping = _sceneMappingService.FindByTvdbid(resource.TvdbId);
|
||||
if (mapping == null) return;
|
||||
resource.AlternativeTitles = mapping.Select(x => x.Title).Distinct().ToList();
|
||||
}
|
||||
|
||||
private SeriesResource GetSeries(int id)
|
||||
{
|
||||
var series = _seriesService.GetSeries(id);
|
||||
|
@ -81,6 +100,7 @@ namespace NzbDrone.Api.Series
|
|||
var resource = series.InjectTo<SeriesResource>();
|
||||
MapCoversToLocal(resource);
|
||||
FetchAndLinkSeriesStatistics(resource);
|
||||
PopulateAlternativeTitles(resource);
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
@ -92,6 +112,7 @@ namespace NzbDrone.Api.Series
|
|||
|
||||
MapCoversToLocal(seriesResources.ToArray());
|
||||
LinkSeriesStatistics(seriesResources, seriesStats);
|
||||
PopulateAlternativeTitles(seriesResources);
|
||||
|
||||
return seriesResources;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace NzbDrone.Api.Series
|
|||
|
||||
//View Only
|
||||
public String Title { get; set; }
|
||||
public List<String> AlternativeTitles { get; set; }
|
||||
|
||||
public Int32 SeasonCount
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@ using NzbDrone.Test.Common;
|
|||
namespace NzbDrone.Common.Test.CacheTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class CachedManagerFixture : TestBase<ICacheManger>
|
||||
public class CachedManagerFixture : TestBase<ICacheManager>
|
||||
{
|
||||
[Test]
|
||||
public void should_return_proper_type_of_cache()
|
||||
|
|
|
@ -4,7 +4,7 @@ using NzbDrone.Common.EnsureThat;
|
|||
|
||||
namespace NzbDrone.Common.Cache
|
||||
{
|
||||
public interface ICacheManger
|
||||
public interface ICacheManager
|
||||
{
|
||||
ICached<T> GetCache<T>(Type host, string name);
|
||||
ICached<T> GetCache<T>(Type host);
|
||||
|
@ -12,11 +12,11 @@ namespace NzbDrone.Common.Cache
|
|||
ICollection<ICached> Caches { get; }
|
||||
}
|
||||
|
||||
public class CacheManger : ICacheManger
|
||||
public class CacheManager : ICacheManager
|
||||
{
|
||||
private readonly ICached<ICached> _cache;
|
||||
|
||||
public CacheManger()
|
||||
public CacheManager()
|
||||
{
|
||||
_cache = new Cached<ICached>();
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Common
|
||||
{
|
||||
|
@ -12,5 +11,17 @@ namespace NzbDrone.Common
|
|||
TValue value;
|
||||
return dictionary.TryGetValue(key, out value) ? value : defaultValue;
|
||||
}
|
||||
|
||||
public static Dictionary<T1, T2> Merge<T1, T2>(this Dictionary<T1, T2> first, Dictionary<T1, T2> second)
|
||||
{
|
||||
if (first == null) throw new ArgumentNullException("first");
|
||||
if (second == null) throw new ArgumentNullException("second");
|
||||
|
||||
var merged = new Dictionary<T1, T2>();
|
||||
first.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
|
||||
second.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
|
||||
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.Blacklisting
|
|||
{
|
||||
Subject.Insert(_blacklist);
|
||||
|
||||
Subject.Blacklisted(_blacklist.SeriesId, _blacklist.SourceTitle.ToUpperInvariant()).Should().BeTrue();
|
||||
Subject.Blacklisted(_blacklist.SeriesId, _blacklist.SourceTitle.ToUpperInvariant()).Should().HaveCount(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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
|
||||
{
|
||||
|
@ -26,6 +26,8 @@ namespace NzbDrone.Core.Test.Blacklisting
|
|||
DownloadClient = "SabnzbdClient",
|
||||
DownloadClientId = "Sabnzbd_nzo_2dfh73k"
|
||||
};
|
||||
|
||||
_event.Data.Add("publishedDate", DateTime.UtcNow.ToString("s") + "Z");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using FluentAssertions;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -25,8 +26,8 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
|
|||
|
||||
mappings.Should().NotBeEmpty();
|
||||
|
||||
mappings.Should().NotContain(c => string.IsNullOrWhiteSpace(c.SearchTerm));
|
||||
mappings.Should().NotContain(c => string.IsNullOrWhiteSpace(c.ParseTerm));
|
||||
mappings.Should().NotContain(c => String.IsNullOrWhiteSpace(c.SearchTerm));
|
||||
mappings.Should().NotContain(c => String.IsNullOrWhiteSpace(c.Title));
|
||||
mappings.Should().NotContain(c => c.TvdbId == 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -76,6 +76,16 @@ namespace NzbDrone.Core.Test.Download
|
|||
.Returns(_failed);
|
||||
}
|
||||
|
||||
private void GivenGracePeriod(int hours)
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(s => s.BlacklistGracePeriod).Returns(hours);
|
||||
}
|
||||
|
||||
private void GivenRetryLimit(int count)
|
||||
{
|
||||
Mocker.GetMock<IConfigService>().SetupGet(s => s.BlacklistRetryLimit).Returns(count);
|
||||
}
|
||||
|
||||
private void VerifyNoFailedDownloads()
|
||||
{
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
|
@ -270,5 +280,91 @@ namespace NzbDrone.Core.Test.Download
|
|||
|
||||
VerifyNoFailedDownloads();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_if_ageHours_is_not_set()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
|
||||
var historyGrabbed = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
|
||||
|
||||
GivenGrabbedHistory(historyGrabbed);
|
||||
GivenNoFailedHistory();
|
||||
|
||||
Subject.Execute(new CheckForFailedDownloadCommand());
|
||||
|
||||
VerifyFailedDownloads();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_if_age_is_greater_than_grace_period()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
|
||||
var historyGrabbed = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
|
||||
historyGrabbed.First().Data.Add("ageHours", "48");
|
||||
|
||||
GivenGrabbedHistory(historyGrabbed);
|
||||
GivenNoFailedHistory();
|
||||
|
||||
Subject.Execute(new CheckForFailedDownloadCommand());
|
||||
|
||||
VerifyFailedDownloads();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_process_if_retry_count_is_greater_than_grace_period()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
|
||||
var historyGrabbed = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
|
||||
historyGrabbed.First().Data.Add("ageHours", "48");
|
||||
|
||||
GivenGrabbedHistory(historyGrabbed);
|
||||
GivenNoFailedHistory();
|
||||
GivenGracePeriod(6);
|
||||
|
||||
Subject.Execute(new CheckForFailedDownloadCommand());
|
||||
|
||||
VerifyFailedDownloads();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_process_if_age_is_less_than_grace_period()
|
||||
{
|
||||
GivenFailedDownloadClientHistory();
|
||||
|
||||
var historyGrabbed = Builder<History.History>.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
|
||||
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
|
||||
historyGrabbed.First().Data.Add("ageHours", "1");
|
||||
|
||||
GivenGrabbedHistory(historyGrabbed);
|
||||
GivenNoFailedHistory();
|
||||
GivenGracePeriod(6);
|
||||
GivenRetryLimit(1);
|
||||
|
||||
Subject.Execute(new CheckForFailedDownloadCommand());
|
||||
|
||||
VerifyNoFailedDownloads();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_when_download_client_returns()
|
||||
public void should_return_ok_when_download_client_returns()
|
||||
{
|
||||
var downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||
|
||||
|
@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||
.Setup(s => s.GetDownloadClient())
|
||||
.Returns(downloadClient.Object);
|
||||
|
||||
Subject.Check().Should().BeNull();
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,11 +57,11 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_when_no_issues_found()
|
||||
public void should_return_ok_when_no_issues_found()
|
||||
{
|
||||
GivenDroneFactoryFolder(true);
|
||||
|
||||
Subject.Check().Should().BeNull();
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,19 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||
{
|
||||
public static class HealthCheckFixtureExtensions
|
||||
{
|
||||
public static void ShouldBeOk(this Core.HealthCheck.HealthCheck result)
|
||||
{
|
||||
result.Type.Should().Be(HealthCheckResult.Ok);
|
||||
}
|
||||
|
||||
public static void ShouldBeWarning(this Core.HealthCheck.HealthCheck result)
|
||||
{
|
||||
result.Type.Should().Be(HealthCheckResultType.Warning);
|
||||
result.Type.Should().Be(HealthCheckResult.Warning);
|
||||
}
|
||||
|
||||
public static void ShouldBeError(this Core.HealthCheck.HealthCheck result)
|
||||
{
|
||||
result.Type.Should().Be(HealthCheckResultType.Error);
|
||||
result.Type.Should().Be(HealthCheckResult.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_when_multiple_indexers_are_enabled()
|
||||
public void should_return_ok_when_multiple_indexers_are_enabled()
|
||||
{
|
||||
var indexer1 = Mocker.GetMock<IIndexer>();
|
||||
indexer1.SetupGet(s => s.SupportsSearching).Returns(true);
|
||||
|
@ -51,11 +51,11 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||
.Setup(s => s.GetAvailableProviders())
|
||||
.Returns(new List<IIndexer> { indexer1.Object, indexer2.Object });
|
||||
|
||||
Subject.Check().Should().BeNull();
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_when_indexer_supports_searching()
|
||||
public void should_return_ok_when_indexer_supports_searching()
|
||||
{
|
||||
var indexer1 = Mocker.GetMock<IIndexer>();
|
||||
indexer1.SetupGet(s => s.SupportsSearching).Returns(true);
|
||||
|
@ -64,7 +64,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||
.Setup(s => s.GetAvailableProviders())
|
||||
.Returns(new List<IIndexer> { indexer1.Object });
|
||||
|
||||
Subject.Check().Should().BeNull();
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,35 +48,35 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_when_mono_3_2()
|
||||
public void should_return_ok_when_mono_3_2()
|
||||
{
|
||||
GivenOutput("3.2.0.1");
|
||||
|
||||
Subject.Check().Should().BeNull();
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_when_mono_4_0()
|
||||
public void should_return_ok_when_mono_4_0()
|
||||
{
|
||||
GivenOutput("4.0.0.0");
|
||||
|
||||
Subject.Check().Should().BeNull();
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_when_mono_3_2_7()
|
||||
public void should_return_ok_when_mono_3_2_7()
|
||||
{
|
||||
GivenOutput("3.2.7");
|
||||
|
||||
Subject.Check().Should().BeNull();
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_when_mono_3_2_1()
|
||||
public void should_return_ok_when_mono_3_2_1()
|
||||
{
|
||||
GivenOutput("3.2.1");
|
||||
|
||||
Subject.Check().Should().BeNull();
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,15 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
|||
ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_treat_daily_episode_a_special()
|
||||
{
|
||||
GivenRuntime(600);
|
||||
_series.SeriesType = SeriesTypes.Daily;
|
||||
_localEpisode.Episodes[0].SeasonNumber = 0;
|
||||
ShouldBeFalse();
|
||||
}
|
||||
|
||||
private void ShouldBeTrue()
|
||||
{
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
|
|
|
@ -33,79 +33,6 @@ namespace NzbDrone.Core.Test.NotificationTests
|
|||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSectionKeys_should_return_single_section_key_when_only_one_show_section()
|
||||
{
|
||||
var response = "<MediaContainer size=\"1\" mediaTagPrefix=\"/system/bundle/media/flags/\" mediaTagVersion=\"1329809559\" title1=\"Plex Library\" identifier=\"com.plexapp.plugins.library\"><Directory refreshing=\"0\" key=\"5\" type=\"show\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory></MediaContainer>";
|
||||
Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
|
||||
|
||||
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
|
||||
.Returns(stream);
|
||||
|
||||
var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
|
||||
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSectionKeys_should_return_single_section_key_when_only_one_show_section_with_other_sections()
|
||||
{
|
||||
|
||||
|
||||
var response = "<MediaContainer size=\"1\" mediaTagPrefix=\"/system/bundle/media/flags/\" mediaTagVersion=\"1329809559\" title1=\"Plex Library\" identifier=\"com.plexapp.plugins.library\"><Directory refreshing=\"0\" key=\"5\" type=\"show\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory><Directory refreshing=\"0\" key=\"7\" type=\"movie\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory></MediaContainer>";
|
||||
Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
|
||||
|
||||
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
|
||||
.Returns(stream);
|
||||
|
||||
|
||||
var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
|
||||
|
||||
|
||||
result.Should().HaveCount(1);
|
||||
result.First().Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSectionKeys_should_return_multiple_section_keys_when_there_are_multiple_show_sections()
|
||||
{
|
||||
|
||||
|
||||
var response = "<MediaContainer size=\"1\" mediaTagPrefix=\"/system/bundle/media/flags/\" mediaTagVersion=\"1329809559\" title1=\"Plex Library\" identifier=\"com.plexapp.plugins.library\"><Directory refreshing=\"0\" key=\"5\" type=\"show\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory><Directory refreshing=\"0\" key=\"6\" type=\"show\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory></MediaContainer>";
|
||||
Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
|
||||
|
||||
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
|
||||
.Returns(stream);
|
||||
|
||||
|
||||
var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
|
||||
|
||||
|
||||
result.Should().HaveCount(2);
|
||||
result.First().Should().Be(5);
|
||||
result.Last().Should().Be(6);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateSection_should_update_section()
|
||||
{
|
||||
|
||||
|
||||
var response = "";
|
||||
Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
|
||||
|
||||
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadString("http://localhost:32400/library/sections/5/refresh"))
|
||||
.Returns(response);
|
||||
|
||||
|
||||
Mocker.Resolve<PlexService>().UpdateSection(new PlexServerSettings { Host = "localhost", Port = 32400 }, 5);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Notify_should_send_notification()
|
||||
{
|
||||
|
|
|
@ -191,6 +191,7 @@
|
|||
<Compile Include="ParserTests\NormalizeTitleFixture.cs" />
|
||||
<Compile Include="ParserTests\CrapParserFixture.cs" />
|
||||
<Compile Include="ParserTests\DailyEpisodeParserFixture.cs" />
|
||||
<Compile Include="ParserTests\HashedReleaseFixture.cs" />
|
||||
<Compile Include="ParserTests\SingleEpisodeParserFixture.cs" />
|
||||
<Compile Include="ParserTests\PathParserFixture.cs" />
|
||||
<Compile Include="ParserTests\MultiEpisodeParserFixture.cs" />
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||
[TestCase("ce39afb7da6cf7c04eba3090f0a309f609883862")]
|
||||
[TestCase("THIS SHOULD NEVER PARSE")]
|
||||
[TestCase("Vh1FvU3bJXw6zs8EEUX4bMo5vbbMdHghxHirc.mkv")]
|
||||
[TestCase("0e895c37245186812cb08aab1529cf8ee389dd05.mkv")]
|
||||
public void should_not_parse_crap(string title)
|
||||
{
|
||||
Parser.Parser.ParseTitle(title).Should().BeNull();
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.ParserTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class HashedReleaseFixture : CoreTest
|
||||
{
|
||||
public static object[] HashedReleaseParserCases =
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
@"C:\Test\Some.Hashed.Release.S01E01.720p.WEB-DL.AAC2.0.H.264-Mercury\0e895c3724.mkv".AsOsAgnostic(),
|
||||
"somehashedrelease",
|
||||
"WEBDL-720p",
|
||||
"Mercury"
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
@"C:\Test\0e895c3724\Some.Hashed.Release.S01E01.720p.WEB-DL.AAC2.0.H.264-Mercury.mkv".AsOsAgnostic(),
|
||||
"somehashedrelease",
|
||||
"WEBDL-720p",
|
||||
"Mercury"
|
||||
}
|
||||
};
|
||||
|
||||
[Test, TestCaseSource("HashedReleaseParserCases")]
|
||||
public void should_properly_parse_hashed_releases(string path, string title, string quality, string releaseGroup)
|
||||
{
|
||||
var result = Parser.Parser.ParsePath(path);
|
||||
result.SeriesTitle.Should().Be(title);
|
||||
result.Quality.ToString().Should().Be(quality);
|
||||
result.ReleaseGroup.Should().Be(releaseGroup);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.ParserTests
|
||||
|
@ -20,6 +17,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||
[TestCase("The Office - S01E01 - Pilot [HTDV-480p]", "DRONE")]
|
||||
[TestCase("The Office - S01E01 - Pilot [HTDV-720p]", "DRONE")]
|
||||
[TestCase("The Office - S01E01 - Pilot [HTDV-1080p]", "DRONE")]
|
||||
[TestCase("The.Walking.Dead.S04E13.720p.WEB-DL.AAC2.0.H.264-Cyphanix", "Cyphanix")]
|
||||
public void should_parse_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
|
|
|
@ -82,6 +82,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
|||
[TestCase("24.S01E01", "24", 1, 1)]
|
||||
[TestCase("Homeland - 2x12 - The Choice [HDTV-1080p].mkv", "Homeland", 2, 12)]
|
||||
[TestCase("Homeland - 2x4 - New Car Smell [HDTV-1080p].mkv", "Homeland", 2, 4)]
|
||||
[TestCase("Top Gear - 06x11 - 2005.08.07", "Top Gear", 6, 11)]
|
||||
public void should_parse_single_episode(string postTitle, string title, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Qualities;
|
||||
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 Int32 SeriesId { get; set; }
|
||||
public List<Int32> EpisodeIds { get; set; }
|
||||
public String SourceTitle { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public DateTime? PublishedDate { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
|
@ -8,7 +6,7 @@ namespace NzbDrone.Core.Blacklisting
|
|||
{
|
||||
public interface IBlacklistRepository : IBasicRepository<Blacklist>
|
||||
{
|
||||
bool Blacklisted(int seriesId, string sourceTitle);
|
||||
List<Blacklist> Blacklisted(int seriesId, string sourceTitle);
|
||||
List<Blacklist> BlacklistedBySeries(int seriesId);
|
||||
}
|
||||
|
||||
|
@ -19,11 +17,10 @@ namespace NzbDrone.Core.Blacklisting
|
|||
{
|
||||
}
|
||||
|
||||
public bool Blacklisted(int seriesId, string sourceTitle)
|
||||
public List<Blacklist> Blacklisted(int seriesId, string sourceTitle)
|
||||
{
|
||||
return Query.Where(e => e.SeriesId == seriesId)
|
||||
.AndWhere(e => e.SourceTitle.Contains(sourceTitle))
|
||||
.Any();
|
||||
.AndWhere(e => e.SourceTitle.Contains(sourceTitle));
|
||||
}
|
||||
|
||||
public List<Blacklist> BlacklistedBySeries(int seriesId)
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
@ -9,25 +13,31 @@ namespace NzbDrone.Core.Blacklisting
|
|||
{
|
||||
public interface IBlacklistService
|
||||
{
|
||||
bool Blacklisted(int seriesId,string sourceTitle);
|
||||
bool Blacklisted(int seriesId,string sourceTitle, DateTime publishedDate);
|
||||
PagingSpec<Blacklist> Paged(PagingSpec<Blacklist> pagingSpec);
|
||||
void Delete(int id);
|
||||
}
|
||||
|
||||
public class BlacklistService : IBlacklistService, IExecute<ClearBlacklistCommand>, IHandle<DownloadFailedEvent>, IHandle<SeriesDeletedEvent>
|
||||
public class BlacklistService : IBlacklistService,
|
||||
IExecute<ClearBlacklistCommand>,
|
||||
IHandle<DownloadFailedEvent>,
|
||||
IHandle<SeriesDeletedEvent>
|
||||
{
|
||||
private readonly IBlacklistRepository _blacklistRepository;
|
||||
private readonly IRedownloadFailedDownloads _redownloadFailedDownloadService;
|
||||
|
||||
public BlacklistService(IBlacklistRepository blacklistRepository, IRedownloadFailedDownloads redownloadFailedDownloadService)
|
||||
public BlacklistService(IBlacklistRepository blacklistRepository,
|
||||
IRedownloadFailedDownloads redownloadFailedDownloadService)
|
||||
{
|
||||
_blacklistRepository = blacklistRepository;
|
||||
_redownloadFailedDownloadService = redownloadFailedDownloadService;
|
||||
}
|
||||
|
||||
public bool Blacklisted(int seriesId, string sourceTitle)
|
||||
public bool Blacklisted(int seriesId, string sourceTitle, DateTime publishedDate)
|
||||
{
|
||||
return _blacklistRepository.Blacklisted(seriesId,sourceTitle);
|
||||
var blacklisted = _blacklistRepository.Blacklisted(seriesId, sourceTitle);
|
||||
|
||||
return blacklisted.Any(item => HasSamePublishedDate(item, publishedDate));
|
||||
}
|
||||
|
||||
public PagingSpec<Blacklist> Paged(PagingSpec<Blacklist> pagingSpec)
|
||||
|
@ -40,6 +50,14 @@ namespace NzbDrone.Core.Blacklisting
|
|||
_blacklistRepository.Delete(id);
|
||||
}
|
||||
|
||||
private bool HasSamePublishedDate(Blacklist item, DateTime publishedDate)
|
||||
{
|
||||
if (!item.PublishedDate.HasValue) return true;
|
||||
|
||||
return item.PublishedDate.Value.AddDays(-2) <= publishedDate &&
|
||||
item.PublishedDate.Value.AddDays(2) >= publishedDate;
|
||||
}
|
||||
|
||||
public void Execute(ClearBlacklistCommand message)
|
||||
{
|
||||
_blacklistRepository.Purge();
|
||||
|
@ -53,7 +71,8 @@ namespace NzbDrone.Core.Blacklisting
|
|||
EpisodeIds = message.EpisodeIds,
|
||||
SourceTitle = message.SourceTitle,
|
||||
Quality = message.Quality,
|
||||
Date = DateTime.UtcNow
|
||||
Date = DateTime.UtcNow,
|
||||
PublishedDate = DateTime.Parse(message.Data.GetValueOrDefault("publishedDate", null))
|
||||
};
|
||||
|
||||
_blacklistRepository.Insert(blacklist);
|
||||
|
|
|
@ -46,9 +46,9 @@ namespace NzbDrone.Core.Configuration
|
|||
|
||||
private readonly string _configFile;
|
||||
|
||||
public ConfigFileProvider(IAppFolderInfo appFolderInfo, ICacheManger cacheManger, IEventAggregator eventAggregator)
|
||||
public ConfigFileProvider(IAppFolderInfo appFolderInfo, ICacheManager cacheManager, IEventAggregator eventAggregator)
|
||||
{
|
||||
_cache = cacheManger.GetCache<string>(GetType());
|
||||
_cache = cacheManager.GetCache<string>(GetType());
|
||||
_eventAggregator = eventAggregator;
|
||||
_configFile = appFolderInfo.GetConfigPath();
|
||||
}
|
||||
|
|
|
@ -130,6 +130,27 @@ namespace NzbDrone.Core.Configuration
|
|||
set { SetValue("RemoveFailedDownloads", value); }
|
||||
}
|
||||
|
||||
public Int32 BlacklistGracePeriod
|
||||
{
|
||||
get { return GetValueInt("BlacklistGracePeriod", 2); }
|
||||
|
||||
set { SetValue("BlacklistGracePeriod", value); }
|
||||
}
|
||||
|
||||
public Int32 BlacklistRetryInterval
|
||||
{
|
||||
get { return GetValueInt("BlacklistRetryInterval", 60); }
|
||||
|
||||
set { SetValue("BlacklistRetryInterval", value); }
|
||||
}
|
||||
|
||||
public Int32 BlacklistRetryLimit
|
||||
{
|
||||
get { return GetValueInt("BlacklistRetryLimit", 1); }
|
||||
|
||||
set { SetValue("BlacklistRetryLimit", value); }
|
||||
}
|
||||
|
||||
public Boolean EnableFailedDownloadHandling
|
||||
{
|
||||
get { return GetValueBoolean("EnableFailedDownloadHandling", true); }
|
||||
|
|
|
@ -19,6 +19,10 @@ namespace NzbDrone.Core.Configuration
|
|||
Boolean AutoRedownloadFailed { get; set; }
|
||||
Boolean RemoveFailedDownloads { get; set; }
|
||||
Boolean EnableFailedDownloadHandling { get; set; }
|
||||
Int32 BlacklistGracePeriod { get; set; }
|
||||
Int32 BlacklistRetryInterval { get; set; }
|
||||
Int32 BlacklistRetryLimit { get; set; }
|
||||
|
||||
|
||||
//Media Management
|
||||
Boolean AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
||||
|
|
|
@ -5,7 +5,8 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
|||
{
|
||||
public class SceneMapping : ModelBase
|
||||
{
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
public string ParseTerm { get; set; }
|
||||
|
||||
[JsonProperty("searchTitle")]
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.DataAugmentation.Scene
|
||||
{
|
||||
public interface ISceneMappingRepository : IBasicRepository<SceneMapping>
|
||||
{
|
||||
|
||||
List<SceneMapping> FindByTvdbid(int tvdbId);
|
||||
}
|
||||
|
||||
public class SceneMappingRepository : BasicRepository<SceneMapping>, ISceneMappingRepository
|
||||
|
@ -16,5 +17,9 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
|||
{
|
||||
}
|
||||
|
||||
public List<SceneMapping> FindByTvdbid(int tvdbId)
|
||||
{
|
||||
return Query.Where(x => x.TvdbId == tvdbId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using NzbDrone.Core.Lifecycle;
|
|||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.DataAugmentation.Scene
|
||||
{
|
||||
|
@ -13,6 +14,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
|||
{
|
||||
string GetSceneName(int tvdbId);
|
||||
Nullable<int> GetTvDbId(string cleanName);
|
||||
List<SceneMapping> FindByTvdbid(int tvdbId);
|
||||
}
|
||||
|
||||
public class SceneMappingService : ISceneMappingService,
|
||||
|
@ -24,14 +26,16 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
|||
private readonly Logger _logger;
|
||||
private readonly ICached<SceneMapping> _getSceneNameCache;
|
||||
private readonly ICached<SceneMapping> _gettvdbIdCache;
|
||||
private readonly ICached<List<SceneMapping>> _findbytvdbIdCache;
|
||||
|
||||
public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, ICacheManger cacheManger, Logger logger)
|
||||
public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_repository = repository;
|
||||
_sceneMappingProxy = sceneMappingProxy;
|
||||
|
||||
_getSceneNameCache = cacheManger.GetCache<SceneMapping>(GetType(), "scene_name");
|
||||
_gettvdbIdCache = cacheManger.GetCache<SceneMapping>(GetType(), "tvdb_id");
|
||||
_getSceneNameCache = cacheManager.GetCache<SceneMapping>(GetType(), "scene_name");
|
||||
_gettvdbIdCache = cacheManager.GetCache<SceneMapping>(GetType(), "tvdb_id");
|
||||
_findbytvdbIdCache = cacheManager.GetCache<List<SceneMapping>>(GetType(), "find_tvdb_id");
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -54,6 +58,11 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
|||
return mapping.TvdbId;
|
||||
}
|
||||
|
||||
public List<SceneMapping> FindByTvdbid(int tvdbId)
|
||||
{
|
||||
return _findbytvdbIdCache.Find(tvdbId.ToString());
|
||||
}
|
||||
|
||||
private void UpdateMappings()
|
||||
{
|
||||
_logger.Info("Updating Scene mapping");
|
||||
|
@ -68,7 +77,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
|||
|
||||
foreach (var sceneMapping in mappings)
|
||||
{
|
||||
sceneMapping.ParseTerm = sceneMapping.ParseTerm.CleanSeriesTitle();
|
||||
sceneMapping.ParseTerm = sceneMapping.Title.CleanSeriesTitle();
|
||||
}
|
||||
|
||||
_repository.InsertMany(mappings);
|
||||
|
@ -92,12 +101,17 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
|||
|
||||
_gettvdbIdCache.Clear();
|
||||
_getSceneNameCache.Clear();
|
||||
_findbytvdbIdCache.Clear();
|
||||
|
||||
foreach (var sceneMapping in mappings)
|
||||
{
|
||||
_getSceneNameCache.Set(sceneMapping.TvdbId.ToString(), sceneMapping);
|
||||
_gettvdbIdCache.Set(sceneMapping.ParseTerm.CleanSeriesTitle(), sceneMapping);
|
||||
}
|
||||
foreach (var sceneMapping in mappings.GroupBy(x => x.TvdbId))
|
||||
{
|
||||
_findbytvdbIdCache.Set(sceneMapping.Key.ToString(), sceneMapping.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,14 +18,14 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
|||
|
||||
public XemService(IEpisodeService episodeService,
|
||||
IXemProxy xemProxy,
|
||||
ISeriesService seriesService, ICacheManger cacheManger, Logger logger)
|
||||
ISeriesService seriesService, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_episodeService = episodeService;
|
||||
_xemProxy = xemProxy;
|
||||
_seriesService = seriesService;
|
||||
_logger = logger;
|
||||
_logger = logger;
|
||||
_cache = cacheManger.GetCache<bool>(GetType());
|
||||
_cache = cacheManager.GetCache<bool>(GetType());
|
||||
}
|
||||
|
||||
private void PerformUpdate(Series series)
|
||||
|
|
|
@ -78,12 +78,6 @@ namespace NzbDrone.Core.Datastore
|
|||
return dataMapper;
|
||||
});
|
||||
|
||||
|
||||
if (migrationType == MigrationType.Main)
|
||||
{
|
||||
db.Vacuum();
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(47)]
|
||||
public class add_temporary_blacklist_columns : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Blacklist").AddColumn("PublishedDate").AsDateTime().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FluentMigrator;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(48)]
|
||||
public class add_title_to_scenemappings : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("SceneMappings").AddColumn("Title").AsString().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
|||
}
|
||||
}
|
||||
|
||||
public virtual bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (!_configService.EnableFailedDownloadHandling)
|
||||
{
|
||||
|
@ -35,7 +35,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
|||
return true;
|
||||
}
|
||||
|
||||
if (_blacklistService.Blacklisted(subject.Series.Id, subject.Release.Title))
|
||||
if (_blacklistService.Blacklisted(subject.Series.Id, subject.Release.Title, subject.Release.PublishDate))
|
||||
{
|
||||
_logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title);
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RetrySpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RetrySpecification(IHistoryService historyService, IConfigService configService, Logger logger)
|
||||
{
|
||||
_historyService = historyService;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Release has been retried too many times";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (!_configService.EnableFailedDownloadHandling)
|
||||
{
|
||||
_logger.Debug("Failed Download Handling is not enabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
var history = _historyService.FindBySourceTitle(subject.Release.Title);
|
||||
|
||||
if (history.Count(h => h.EventType == HistoryEventType.Grabbed &&
|
||||
HasSamePublishedDate(h, subject.Release.PublishDate)) >
|
||||
_configService.BlacklistRetryLimit)
|
||||
{
|
||||
_logger.Debug("Release has been attempted more times than allowed, rejecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HasSamePublishedDate(History.History item, DateTime publishedDate)
|
||||
{
|
||||
DateTime itemsPublishedDate;
|
||||
|
||||
if (!DateTime.TryParse(item.Data.GetValueOrDefault("PublishedDate", null), out itemsPublishedDate)) return true;
|
||||
|
||||
return itemsPublishedDate.AddDays(-2) <= publishedDate && itemsPublishedDate.AddDays(2) >= publishedDate;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,6 +58,11 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
|||
{
|
||||
}
|
||||
|
||||
public override void RetryDownload(string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Test()
|
||||
{
|
||||
PerformTest(Settings.Folder);
|
||||
|
|
|
@ -138,6 +138,11 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
|||
_proxy.RemoveFromHistory(id, Settings);
|
||||
}
|
||||
|
||||
public override void RetryDownload(string id)
|
||||
{
|
||||
_proxy.RetryDownload(id, Settings);
|
||||
}
|
||||
|
||||
public override void Test()
|
||||
{
|
||||
_proxy.GetVersion(Settings);
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
|||
List<NzbgetHistoryItem> GetHistory(NzbgetSettings settings);
|
||||
VersionResponse GetVersion(NzbgetSettings settings);
|
||||
void RemoveFromHistory(string id, NzbgetSettings settings);
|
||||
void RetryDownload(string id, NzbgetSettings settings);
|
||||
}
|
||||
|
||||
public class NzbgetProxy : INzbgetProxy
|
||||
|
@ -98,6 +99,23 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
|||
}
|
||||
}
|
||||
|
||||
public void RetryDownload(string id, NzbgetSettings settings)
|
||||
{
|
||||
var history = GetHistory(settings);
|
||||
var item = history.SingleOrDefault(h => h.Parameters.SingleOrDefault(p => p.Name == "drone") != null);
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
_logger.Warn("Unable to return item to queue, Unknown ID: {0}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EditQueue("HistoryReturn", 0, "", item.Id, settings))
|
||||
{
|
||||
_logger.Warn("Failed to return item to queue from history, {0} [{1}]", item.Name, item.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private bool EditQueue(string command, int offset, string editText, int id, NzbgetSettings settings)
|
||||
{
|
||||
var parameters = new object[] { command, offset, editText, id };
|
||||
|
|
|
@ -80,6 +80,11 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
|||
{
|
||||
}
|
||||
|
||||
public override void RetryDownload(string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Test()
|
||||
{
|
||||
PerformTest(Settings.Folder);
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Download.Clients.Sabnzbd.Responses;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
@ -18,20 +15,20 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||
{
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly ISabnzbdProxy _sabnzbdProxy;
|
||||
private readonly ISabnzbdProxy _proxy;
|
||||
private readonly ICached<IEnumerable<QueueItem>> _queueCache;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public Sabnzbd(IHttpProvider httpProvider,
|
||||
ICacheManger cacheManger,
|
||||
ICacheManager cacheManager,
|
||||
IParsingService parsingService,
|
||||
ISabnzbdProxy sabnzbdProxy,
|
||||
ISabnzbdProxy proxy,
|
||||
Logger logger)
|
||||
{
|
||||
_httpProvider = httpProvider;
|
||||
_parsingService = parsingService;
|
||||
_sabnzbdProxy = sabnzbdProxy;
|
||||
_queueCache = cacheManger.GetCache<IEnumerable<QueueItem>>(GetType(), "queue");
|
||||
_proxy = proxy;
|
||||
_queueCache = cacheManager.GetCache<IEnumerable<QueueItem>>(GetType(), "queue");
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -45,7 +42,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||
using (var nzb = _httpProvider.DownloadStream(url))
|
||||
{
|
||||
_logger.Info("Adding report [{0}] to the queue.", title);
|
||||
var response = _sabnzbdProxy.DownloadNzb(nzb, title, category, priority, Settings);
|
||||
var response = _proxy.DownloadNzb(nzb, title, category, priority, Settings);
|
||||
|
||||
if (response != null && response.Ids.Any())
|
||||
{
|
||||
|
@ -64,7 +61,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||
|
||||
try
|
||||
{
|
||||
sabQueue = _sabnzbdProxy.GetQueue(0, 0, Settings);
|
||||
sabQueue = _proxy.GetQueue(0, 0, Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
|
@ -105,7 +102,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||
|
||||
try
|
||||
{
|
||||
sabHistory = _sabnzbdProxy.GetHistory(start, limit, Settings);
|
||||
sabHistory = _proxy.GetHistory(start, limit, Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
|
@ -135,17 +132,22 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||
|
||||
public override void RemoveFromQueue(string id)
|
||||
{
|
||||
_sabnzbdProxy.RemoveFrom("queue", id, Settings);
|
||||
_proxy.RemoveFrom("queue", id, Settings);
|
||||
}
|
||||
|
||||
public override void RemoveFromHistory(string id)
|
||||
{
|
||||
_sabnzbdProxy.RemoveFrom("history", id, Settings);
|
||||
_proxy.RemoveFrom("history", id, Settings);
|
||||
}
|
||||
|
||||
public override void RetryDownload(string id)
|
||||
{
|
||||
_proxy.RetryDownload(id, Settings);
|
||||
}
|
||||
|
||||
public override void Test()
|
||||
{
|
||||
_sabnzbdProxy.GetCategories(Settings);
|
||||
_proxy.GetCategories(Settings);
|
||||
}
|
||||
|
||||
public void Execute(TestSabnzbdCommand message)
|
||||
|
@ -153,7 +155,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||
var settings = new SabnzbdSettings();
|
||||
settings.InjectFrom(message);
|
||||
|
||||
_sabnzbdProxy.GetCategories(settings);
|
||||
_proxy.GetCategories(settings);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||
SabnzbdCategoryResponse GetCategories(SabnzbdSettings settings);
|
||||
SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings);
|
||||
SabnzbdHistory GetHistory(int start, int limit, SabnzbdSettings settings);
|
||||
void RetryDownload(string id, SabnzbdSettings settings);
|
||||
}
|
||||
|
||||
public class SabnzbdProxy : ISabnzbdProxy
|
||||
|
@ -111,6 +112,14 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
|||
return Json.Deserialize<SabnzbdHistory>(JObject.Parse(response).SelectToken("history").ToString());
|
||||
}
|
||||
|
||||
public void RetryDownload(string id, SabnzbdSettings settings)
|
||||
{
|
||||
var request = new RestRequest();
|
||||
var action = String.Format("mode=retry&value={0}", id);
|
||||
|
||||
ProcessRequest(request, action, settings);
|
||||
}
|
||||
|
||||
private IRestClient BuildClient(string action, SabnzbdSettings settings)
|
||||
{
|
||||
var protocol = settings.UseSsl ? "https" : "http";
|
||||
|
|
|
@ -43,6 +43,7 @@ namespace NzbDrone.Core.Download
|
|||
public abstract IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 10);
|
||||
public abstract void RemoveFromQueue(string id);
|
||||
public abstract void RemoveFromHistory(string id);
|
||||
public abstract void RetryDownload(string id);
|
||||
public abstract void Test();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class DownloadFailedEvent : IEvent
|
||||
{
|
||||
public DownloadFailedEvent()
|
||||
{
|
||||
Data = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public Int32 SeriesId { get; set; }
|
||||
public List<Int32> EpisodeIds { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
|
@ -15,5 +19,6 @@ namespace NzbDrone.Core.Download
|
|||
public String DownloadClient { get; set; }
|
||||
public String DownloadClientId { get; set; }
|
||||
public String Message { get; set; }
|
||||
public Dictionary<string, string> Data { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class FailedDownload
|
||||
{
|
||||
public HistoryItem DownloadClientHistoryItem { get; set; }
|
||||
public DateTime LastRetry { get; set; }
|
||||
public Int32 RetryCount { get; set; }
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.History;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
@ -23,6 +24,8 @@ namespace NzbDrone.Core.Download
|
|||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly ICached<FailedDownload> _failedDownloads;
|
||||
|
||||
private static string DOWNLOAD_CLIENT = "downloadClient";
|
||||
private static string DOWNLOAD_CLIENT_ID = "downloadClientId";
|
||||
|
||||
|
@ -30,6 +33,7 @@ namespace NzbDrone.Core.Download
|
|||
IHistoryService historyService,
|
||||
IEventAggregator eventAggregator,
|
||||
IConfigService configService,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
{
|
||||
_downloadClientProvider = downloadClientProvider;
|
||||
|
@ -37,6 +41,8 @@ namespace NzbDrone.Core.Download
|
|||
_eventAggregator = eventAggregator;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
|
||||
_failedDownloads = cacheManager.GetCache<FailedDownload>(GetType());
|
||||
}
|
||||
|
||||
public void MarkAsFailed(int historyId)
|
||||
|
@ -127,6 +133,12 @@ namespace NzbDrone.Core.Download
|
|||
continue;
|
||||
}
|
||||
|
||||
if (FailedDownloadForRecentRelease(failedItem, historyItems))
|
||||
{
|
||||
_logger.Debug("Recent release Failed, do not blacklist");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (failedHistory.Any(h => failedLocal.Id.Equals(h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID))))
|
||||
{
|
||||
_logger.Debug("Already added to history as failed");
|
||||
|
@ -152,19 +164,21 @@ namespace NzbDrone.Core.Download
|
|||
private void PublishDownloadFailedEvent(List<History.History> historyItems, string message)
|
||||
{
|
||||
var historyItem = historyItems.First();
|
||||
string downloadClient;
|
||||
string downloadClientId;
|
||||
|
||||
_eventAggregator.PublishEvent(new DownloadFailedEvent
|
||||
{
|
||||
SeriesId = historyItem.SeriesId,
|
||||
EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
|
||||
Quality = historyItem.Quality,
|
||||
SourceTitle = historyItem.SourceTitle,
|
||||
DownloadClient = historyItem.Data.GetValueOrDefault(DOWNLOAD_CLIENT),
|
||||
DownloadClientId = historyItem.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID),
|
||||
Message = message
|
||||
});
|
||||
var downloadFailedEvent = new DownloadFailedEvent
|
||||
{
|
||||
SeriesId = historyItem.SeriesId,
|
||||
EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
|
||||
Quality = historyItem.Quality,
|
||||
SourceTitle = historyItem.SourceTitle,
|
||||
DownloadClient = historyItem.Data.GetValueOrDefault(DOWNLOAD_CLIENT),
|
||||
DownloadClientId = historyItem.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID),
|
||||
Message = message
|
||||
};
|
||||
|
||||
downloadFailedEvent.Data = downloadFailedEvent.Data.Merge(historyItem.Data);
|
||||
|
||||
_eventAggregator.PublishEvent(downloadFailedEvent);
|
||||
}
|
||||
|
||||
private IDownloadClient GetDownloadClient()
|
||||
|
@ -179,6 +193,56 @@ namespace NzbDrone.Core.Download
|
|||
return downloadClient;
|
||||
}
|
||||
|
||||
private bool FailedDownloadForRecentRelease(HistoryItem failedDownloadHistoryItem, List<History.History> matchingHistoryItems)
|
||||
{
|
||||
double ageHours;
|
||||
|
||||
if (!Double.TryParse(matchingHistoryItems.First().Data.GetValueOrDefault("ageHours"), out ageHours))
|
||||
{
|
||||
_logger.Debug("Unable to determine age of failed download");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ageHours > _configService.BlacklistGracePeriod)
|
||||
{
|
||||
_logger.Debug("Failed download is older than the grace period");
|
||||
return false;
|
||||
}
|
||||
|
||||
var tracked = _failedDownloads.Get(failedDownloadHistoryItem.Id, () => new FailedDownload
|
||||
{
|
||||
DownloadClientHistoryItem = failedDownloadHistoryItem,
|
||||
LastRetry = DateTime.UtcNow
|
||||
}
|
||||
);
|
||||
|
||||
if (tracked.RetryCount >= _configService.BlacklistRetryLimit)
|
||||
{
|
||||
_logger.Debug("Retry limit reached");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tracked.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow)
|
||||
{
|
||||
_logger.Debug("Retrying failed release");
|
||||
tracked.LastRetry = DateTime.UtcNow;
|
||||
tracked.RetryCount++;
|
||||
|
||||
try
|
||||
{
|
||||
GetDownloadClient().RetryDownload(failedDownloadHistoryItem.Id);
|
||||
}
|
||||
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
_logger.Debug("Retrying failed downloads is not supported by your download client");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Execute(CheckForFailedDownloadCommand message)
|
||||
{
|
||||
if (!_configService.EnableFailedDownloadHandling)
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download
|
|||
IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0);
|
||||
void RemoveFromQueue(string id);
|
||||
void RemoveFromHistory(string id);
|
||||
void RetryDownload(string id);
|
||||
void Test();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,10 +40,7 @@ namespace NzbDrone.Core.Download
|
|||
{
|
||||
_logger.Debug("Failed download only contains one episode, searching again");
|
||||
|
||||
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand
|
||||
{
|
||||
EpisodeIds = episodeIds.ToList()
|
||||
});
|
||||
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(episodeIds));
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -66,10 +63,7 @@ namespace NzbDrone.Core.Download
|
|||
|
||||
_logger.Debug("Failed download contains multiple episodes, probably a double episode, searching again");
|
||||
|
||||
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand
|
||||
{
|
||||
EpisodeIds = episodeIds.ToList()
|
||||
});
|
||||
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(episodeIds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ using NzbDrone.Core.Download;
|
|||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class DownloadClientCheck : IProvideHealthCheck
|
||||
public class DownloadClientCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||
|
||||
|
@ -12,13 +12,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
_downloadClientProvider = downloadClientProvider;
|
||||
}
|
||||
|
||||
public HealthCheck Check()
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var downloadClient = _downloadClientProvider.GetDownloadClient();
|
||||
|
||||
if (downloadClient == null)
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Warning, "No download client is available");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, "No download client is available");
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -27,10 +27,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Error, "Unable to communicate with download client");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, "Unable to communicate with download client");
|
||||
}
|
||||
|
||||
return null;
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ using NzbDrone.Core.Configuration;
|
|||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class DroneFactoryCheck : IProvideHealthCheck
|
||||
public class DroneFactoryCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
|
@ -17,18 +17,18 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
_diskProvider = diskProvider;
|
||||
}
|
||||
|
||||
public HealthCheck Check()
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var droneFactoryFolder = _configService.DownloadedEpisodesFolder;
|
||||
|
||||
if (droneFactoryFolder.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Warning, "Drone factory folder is not configured");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Drone factory folder is not configured");
|
||||
}
|
||||
|
||||
if (!_diskProvider.FolderExists(droneFactoryFolder))
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Error, "Drone factory folder does not exist");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, "Drone factory folder does not exist");
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -39,12 +39,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Error, "Unable to write to drone factory folder");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, "Unable to write to drone factory folder");
|
||||
}
|
||||
|
||||
//Todo: Unable to import one or more files/folders from
|
||||
|
||||
return null;
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ using NzbDrone.Core.Indexers;
|
|||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class IndexerCheck : IProvideHealthCheck
|
||||
public class IndexerCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
|
||||
|
@ -12,21 +12,21 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
_indexerFactory = indexerFactory;
|
||||
}
|
||||
|
||||
public HealthCheck Check()
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
var enabled = _indexerFactory.GetAvailableProviders();
|
||||
|
||||
if (!enabled.Any())
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Error, "No indexers are enabled");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, "No indexers are enabled");
|
||||
}
|
||||
|
||||
if (enabled.All(i => i.SupportsSearching == false))
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Warning, "Enabled indexers do not support searching");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Enabled indexers do not support searching");
|
||||
}
|
||||
|
||||
return null;
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ using NzbDrone.Common.Processes;
|
|||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class MonoVersionCheck : IProvideHealthCheck
|
||||
public class MonoVersionCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IProcessProvider _processProvider;
|
||||
private readonly Logger _logger;
|
||||
|
@ -18,11 +18,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public HealthCheck Check()
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
if (!OsInfo.IsMono)
|
||||
{
|
||||
return null;
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
var output = _processProvider.StartAndCapture("mono", "--version");
|
||||
|
@ -38,12 +38,28 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
if (version >= new Version(3, 2))
|
||||
{
|
||||
_logger.Debug("mono version is 3.2 or better: {0}", version.ToString());
|
||||
return null;
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new HealthCheck(HealthCheckResultType.Warning, "mono version is less than 3.2, upgrade for improved stability");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, "mono version is less than 3.2, upgrade for improved stability");
|
||||
}
|
||||
|
||||
public override bool CheckOnConfigChange
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CheckOnSchedule
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ using NzbDrone.Core.Update;
|
|||
|
||||
namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class UpdateCheck : IProvideHealthCheck
|
||||
public class UpdateCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
|
@ -20,7 +20,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
}
|
||||
|
||||
|
||||
public HealthCheck Check()
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Error,
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error,
|
||||
"Unable to update, running from write-protected folder");
|
||||
}
|
||||
}
|
||||
|
@ -41,11 +41,19 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
|||
{
|
||||
if (_checkUpdateService.AvailableUpdate() != null)
|
||||
{
|
||||
return new HealthCheck(HealthCheckResultType.Warning, "New update is available");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, "New update is available");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
public override bool CheckOnConfigChange
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,27 @@ namespace NzbDrone.Core.HealthCheck
|
|||
{
|
||||
public class HealthCheck : ModelBase
|
||||
{
|
||||
public HealthCheckResultType Type { get; set; }
|
||||
public Type Source { get; set; }
|
||||
public HealthCheckResult Type { get; set; }
|
||||
public String Message { get; set; }
|
||||
|
||||
public HealthCheck(HealthCheckResultType type, string message)
|
||||
public HealthCheck(Type source)
|
||||
{
|
||||
Source = source;
|
||||
Type = HealthCheckResult.Ok;
|
||||
}
|
||||
|
||||
public HealthCheck(Type source, HealthCheckResult type, string message)
|
||||
{
|
||||
Source = source;
|
||||
Type = type;
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
public enum HealthCheckResultType
|
||||
public enum HealthCheckResult
|
||||
{
|
||||
Ok = 0,
|
||||
Warning = 1,
|
||||
Error = 2
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck
|
||||
{
|
||||
public abstract class HealthCheckBase : IProvideHealthCheck
|
||||
{
|
||||
public abstract HealthCheck Check();
|
||||
|
||||
public virtual bool CheckOnStartup
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool CheckOnConfigChange
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool CheckOnSchedule
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace NzbDrone.Core.HealthCheck
|
||||
{
|
||||
public class TriggerHealthCheckEvent : IEvent
|
||||
public class HealthCheckCompleteEvent : IEvent
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider.Events;
|
||||
|
@ -12,56 +16,86 @@ namespace NzbDrone.Core.HealthCheck
|
|||
{
|
||||
public interface IHealthCheckService
|
||||
{
|
||||
List<HealthCheck> PerformHealthCheck();
|
||||
List<HealthCheck> Results();
|
||||
}
|
||||
|
||||
public class HealthCheckService : IHealthCheckService,
|
||||
IExecute<CheckHealthCommand>,
|
||||
IHandleAsync<ApplicationStartedEvent>,
|
||||
IHandleAsync<ConfigSavedEvent>,
|
||||
IHandleAsync<ProviderUpdatedEvent<IIndexer>>,
|
||||
IHandleAsync<ProviderUpdatedEvent<IDownloadClient>>
|
||||
{
|
||||
private readonly IEnumerable<IProvideHealthCheck> _healthChecks;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks, IEventAggregator eventAggregator, Logger logger)
|
||||
private readonly ICached<HealthCheck> _healthCheckResults;
|
||||
|
||||
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
{
|
||||
_healthChecks = healthChecks;
|
||||
_eventAggregator = eventAggregator;
|
||||
_cacheManager = cacheManager;
|
||||
_logger = logger;
|
||||
|
||||
_healthCheckResults = _cacheManager.GetCache<HealthCheck>(GetType());
|
||||
}
|
||||
|
||||
public List<HealthCheck> PerformHealthCheck()
|
||||
public List<HealthCheck> Results()
|
||||
{
|
||||
_logger.Trace("Checking health");
|
||||
var result = _healthChecks.Select(c => c.Check()).Where(c => c != null).ToList();
|
||||
|
||||
return result;
|
||||
return _healthCheckResults.Values.ToList();
|
||||
}
|
||||
|
||||
public void Execute(CheckHealthCommand message)
|
||||
private void PerformHealthCheck(Func<IProvideHealthCheck, bool> predicate)
|
||||
{
|
||||
//Until we have stored health checks we should just trigger the complete event
|
||||
//and let the clients check in
|
||||
//Multiple connected clients means we're going to compute the health check multiple times
|
||||
//Multiple checks feels a bit ugly, but means the most up to date information goes to the client
|
||||
_eventAggregator.PublishEvent(new TriggerHealthCheckEvent());
|
||||
var results = _healthChecks.Where(predicate)
|
||||
.Select(c => c.Check())
|
||||
.ToList();
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result.Type == HealthCheckResult.Ok)
|
||||
{
|
||||
_healthCheckResults.Remove(result.Source.Name);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_healthCheckResults.Set(result.Source.Name, result);
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new HealthCheckCompleteEvent());
|
||||
}
|
||||
|
||||
public void HandleAsync(ConfigSavedEvent message)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new TriggerHealthCheckEvent());
|
||||
PerformHealthCheck(c => c.CheckOnConfigChange);
|
||||
}
|
||||
|
||||
public void HandleAsync(ProviderUpdatedEvent<IIndexer> message)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new TriggerHealthCheckEvent());
|
||||
PerformHealthCheck(c => c.CheckOnConfigChange);
|
||||
}
|
||||
|
||||
public void HandleAsync(ProviderUpdatedEvent<IDownloadClient> message)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new TriggerHealthCheckEvent());
|
||||
PerformHealthCheck(c => c.CheckOnConfigChange);
|
||||
}
|
||||
|
||||
public void HandleAsync(ApplicationStartedEvent message)
|
||||
{
|
||||
PerformHealthCheck(c => c.CheckOnStartup);
|
||||
}
|
||||
|
||||
public void Execute(CheckHealthCommand message)
|
||||
{
|
||||
PerformHealthCheck(c => c.CheckOnSchedule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
namespace NzbDrone.Core.HealthCheck
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck
|
||||
{
|
||||
public interface IProvideHealthCheck
|
||||
{
|
||||
HealthCheck Check();
|
||||
Boolean CheckOnStartup { get; }
|
||||
Boolean CheckOnConfigChange { get; }
|
||||
Boolean CheckOnSchedule { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Extentions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
@ -18,6 +17,7 @@ namespace NzbDrone.Core.History
|
|||
List<History> Failed();
|
||||
List<History> Grabbed();
|
||||
History MostRecentForEpisode(int episodeId);
|
||||
List<History> FindBySourceTitle(string sourceTitle);
|
||||
}
|
||||
|
||||
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
|
||||
|
@ -69,6 +69,16 @@ namespace NzbDrone.Core.History
|
|||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<History> FindBySourceTitle(string sourceTitle)
|
||||
{
|
||||
return Query.Where(h => h.SourceTitle.Contains(sourceTitle));
|
||||
}
|
||||
|
||||
public List<History> AllForEpisode(int episodeId)
|
||||
{
|
||||
return Query.Where(h => h.EpisodeId == episodeId);
|
||||
}
|
||||
|
||||
protected override SortBuilder<History> GetPagedQuery(QueryBuilder<History> query, PagingSpec<History> pagingSpec)
|
||||
{
|
||||
var baseQuery = query.Join<History, Series>(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id)
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace NzbDrone.Core.History
|
|||
List<History> Grabbed();
|
||||
History MostRecentForEpisode(int episodeId);
|
||||
History Get(int id);
|
||||
List<History> FindBySourceTitle(string sourceTitle);
|
||||
}
|
||||
|
||||
public class HistoryService : IHistoryService, IHandle<EpisodeGrabbedEvent>, IHandle<EpisodeImportedEvent>, IHandle<DownloadFailedEvent>
|
||||
|
@ -71,6 +72,11 @@ namespace NzbDrone.Core.History
|
|||
return _historyRepository.Get(id);
|
||||
}
|
||||
|
||||
public List<History> FindBySourceTitle(string sourceTitle)
|
||||
{
|
||||
return _historyRepository.FindBySourceTitle(sourceTitle);
|
||||
}
|
||||
|
||||
public void Purge()
|
||||
{
|
||||
_historyRepository.Purge();
|
||||
|
@ -107,6 +113,8 @@ namespace NzbDrone.Core.History
|
|||
history.Data.Add("NzbInfoUrl", message.Episode.Release.InfoUrl);
|
||||
history.Data.Add("ReleaseGroup", message.Episode.ParsedEpisodeInfo.ReleaseGroup);
|
||||
history.Data.Add("Age", message.Episode.Release.Age.ToString());
|
||||
history.Data.Add("AgeHours", message.Episode.Release.AgeHours.ToString());
|
||||
history.Data.Add("PublishedDate", message.Episode.Release.PublishDate.ToString("s") + "Z");
|
||||
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(message.DownloadClientId))
|
||||
|
|
|
@ -4,6 +4,7 @@ using NLog;
|
|||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping
|
||||
{
|
||||
|
@ -11,11 +12,13 @@ namespace NzbDrone.Core.Housekeeping
|
|||
{
|
||||
private readonly IEnumerable<IHousekeepingTask> _housekeepers;
|
||||
private readonly Logger _logger;
|
||||
private readonly IDatabase _mainDb;
|
||||
|
||||
public HousekeepingService(IEnumerable<IHousekeepingTask> housekeepers, Logger logger)
|
||||
public HousekeepingService(IEnumerable<IHousekeepingTask> housekeepers, Logger logger, IDatabase mainDb)
|
||||
{
|
||||
_housekeepers = housekeepers;
|
||||
_logger = logger;
|
||||
_mainDb = mainDb;
|
||||
}
|
||||
|
||||
private void Clean()
|
||||
|
@ -33,6 +36,10 @@ namespace NzbDrone.Core.Housekeeping
|
|||
_logger.ErrorException("Error running housekeeping task: " + housekeeper.GetType().FullName, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Vacuuming the log db isn't needed since that's done hourly at the TrimLogCommand.
|
||||
_logger.Debug("Compressing main database after housekeeping");
|
||||
_mainDb.Vacuum();
|
||||
}
|
||||
|
||||
public void Execute(HousekeepingCommand message)
|
||||
|
|
|
@ -14,5 +14,14 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public EpisodeSearchCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public EpisodeSearchCommand(List<int> episodeIds)
|
||||
{
|
||||
EpisodeIds = episodeIds;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,28 +6,57 @@ using NzbDrone.Core.Datastore;
|
|||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Queue;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class MissingEpisodeSearchService : IExecute<EpisodeSearchCommand>, IExecute<MissingEpisodeSearchCommand>
|
||||
public interface IEpisodeSearchService
|
||||
{
|
||||
void MissingEpisodesAiredAfter(DateTime dateTime);
|
||||
}
|
||||
|
||||
public class MissingEpisodeSearchService : IEpisodeSearchService, IExecute<EpisodeSearchCommand>, IExecute<MissingEpisodeSearchCommand>
|
||||
{
|
||||
private readonly ISearchForNzb _nzbSearchService;
|
||||
private readonly IDownloadApprovedReports _downloadApprovedReports;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IQueueService _queueService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MissingEpisodeSearchService(ISearchForNzb nzbSearchService,
|
||||
IDownloadApprovedReports downloadApprovedReports,
|
||||
IEpisodeService episodeService,
|
||||
IQueueService queueService,
|
||||
Logger logger)
|
||||
{
|
||||
_nzbSearchService = nzbSearchService;
|
||||
_downloadApprovedReports = downloadApprovedReports;
|
||||
_episodeService = episodeService;
|
||||
_queueService = queueService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void MissingEpisodesAiredAfter(DateTime dateTime)
|
||||
{
|
||||
var missing = _episodeService.EpisodesBetweenDates(dateTime, DateTime.UtcNow)
|
||||
.Where(e => !e.HasFile &&
|
||||
!_queueService.GetQueue().Select(q => q.Episode.Id).Contains(e.Id))
|
||||
.ToList();
|
||||
|
||||
var downloadedCount = 0;
|
||||
_logger.Info("Searching for {0} missing episodes since last RSS Sync", missing.Count);
|
||||
|
||||
foreach (var episode in missing)
|
||||
{
|
||||
var decisions = _nzbSearchService.EpisodeSearch(episode);
|
||||
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
|
||||
downloadedCount += downloaded.Count;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Completed search for {0} episodes. {1} reports downloaded.", missing.Count, downloadedCount);
|
||||
}
|
||||
|
||||
public void Execute(EpisodeSearchCommand message)
|
||||
{
|
||||
foreach (var episodeId in message.EpisodeIds)
|
||||
|
@ -53,24 +82,24 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
FilterExpression = v => v.Monitored == true && v.Series.Monitored == true
|
||||
}).Records.ToList();
|
||||
|
||||
_logger.ProgressInfo("Performing missing search for {0} episodes", episodes.Count);
|
||||
var missing = episodes.Where(e => !_queueService.GetQueue().Select(q => q.Episode.Id).Contains(e.Id)).ToList();
|
||||
|
||||
_logger.ProgressInfo("Performing missing search for {0} episodes", missing.Count);
|
||||
var downloadedCount = 0;
|
||||
|
||||
//Limit requests to indexers at 100 per minute
|
||||
using (var rateGate = new RateGate(100, TimeSpan.FromSeconds(60)))
|
||||
{
|
||||
foreach (var episode in episodes)
|
||||
foreach (var episode in missing)
|
||||
{
|
||||
rateGate.WaitToProceed();
|
||||
var decisions = _nzbSearchService.EpisodeSearch(episode);
|
||||
var downloaded = _downloadApprovedReports.DownloadApproved(decisions);
|
||||
downloadedCount += downloaded.Count;
|
||||
|
||||
_logger.ProgressInfo("Episode search completed. {0} reports downloaded.", downloaded.Count);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Completed missing search for {0} episodes. {1} reports downloaded.", episodes.Count, downloadedCount);
|
||||
_logger.ProgressInfo("Completed missing search for {0} episodes. {1} reports downloaded.", missing.Count, downloadedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,5 +14,14 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public MissingEpisodeSearchCommand()
|
||||
{
|
||||
}
|
||||
|
||||
public MissingEpisodeSearchCommand(List<int> episodeIds)
|
||||
{
|
||||
EpisodeIds = episodeIds;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
|
@ -17,16 +19,19 @@ namespace NzbDrone.Core.Indexers
|
|||
private readonly IFetchAndParseRss _rssFetcherAndParser;
|
||||
private readonly IMakeDownloadDecision _downloadDecisionMaker;
|
||||
private readonly IDownloadApprovedReports _downloadApprovedReports;
|
||||
private readonly IEpisodeSearchService _episodeSearchService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RssSyncService(IFetchAndParseRss rssFetcherAndParser,
|
||||
IMakeDownloadDecision downloadDecisionMaker,
|
||||
IDownloadApprovedReports downloadApprovedReports,
|
||||
IEpisodeSearchService episodeSearchService,
|
||||
Logger logger)
|
||||
{
|
||||
_rssFetcherAndParser = rssFetcherAndParser;
|
||||
_downloadDecisionMaker = downloadDecisionMaker;
|
||||
_downloadApprovedReports = downloadApprovedReports;
|
||||
_episodeSearchService = episodeSearchService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -45,6 +50,12 @@ namespace NzbDrone.Core.Indexers
|
|||
public void Execute(RssSyncCommand message)
|
||||
{
|
||||
Sync();
|
||||
|
||||
if (message.LastExecutionTime.HasValue && DateTime.UtcNow.Subtract(message.LastExecutionTime.Value).TotalHours > 3)
|
||||
{
|
||||
_logger.Info("RSS Sync hasn't run since: {0}. Searching for any missing episodes since then.", message.LastExecutionTime.Value);
|
||||
_episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace NzbDrone.Core.Jobs
|
|||
|
||||
try
|
||||
{
|
||||
_commandExecutor.PublishCommand(task.TypeName);
|
||||
_commandExecutor.PublishCommand(task.TypeName, task.LastExecution);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -50,10 +50,10 @@ namespace NzbDrone.Core.Jobs
|
|||
{
|
||||
new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName},
|
||||
new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFailedDownloadCommand).FullName},
|
||||
new ScheduledTask{ Interval = 5, TypeName = typeof(CheckHealthCommand).FullName},
|
||||
new ScheduledTask{ Interval = 1*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
|
||||
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
|
||||
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
||||
new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName},
|
||||
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName},
|
||||
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace NzbDrone.Core.MediaFiles.Commands
|
|||
{
|
||||
public class RescanSeriesCommand : Command
|
||||
{
|
||||
public int SeriesId { get; set; }
|
||||
public int? SeriesId { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient
|
||||
{
|
||||
|
|
|
@ -111,9 +111,21 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
public void Execute(RescanSeriesCommand message)
|
||||
{
|
||||
var series = _seriesService.GetSeries(message.SeriesId);
|
||||
if (message.SeriesId.HasValue)
|
||||
{
|
||||
var series = _seriesService.GetSeries(message.SeriesId.Value);
|
||||
Scan(series);
|
||||
}
|
||||
|
||||
Scan(series);
|
||||
else
|
||||
{
|
||||
var allSeries = _seriesService.GetAllSeries();
|
||||
|
||||
foreach (var series in allSeries)
|
||||
{
|
||||
Scan(series);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
|
||||
public bool IsSample(Series series, QualityModel quality, string path, long size, int seasonNumber)
|
||||
{
|
||||
if (seasonNumber == 0)
|
||||
if (seasonNumber == 0 && series.SeriesType == SeriesTypes.Standard)
|
||||
{
|
||||
_logger.Debug("Special, skipping sample check");
|
||||
return false;
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace NzbDrone.Core.Messaging.Commands
|
|||
public string Message { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
public DateTime? LastExecutionTime { get; set; }
|
||||
|
||||
protected Command()
|
||||
{
|
||||
|
|
|
@ -49,8 +49,15 @@ namespace NzbDrone.Core.Messaging.Commands
|
|||
}
|
||||
|
||||
public void PublishCommand(string commandTypeName)
|
||||
{
|
||||
PublishCommand(commandTypeName, null);
|
||||
}
|
||||
|
||||
public void PublishCommand(string commandTypeName, DateTime? lastExecutionTime)
|
||||
{
|
||||
dynamic command = GetCommand(commandTypeName);
|
||||
command.LastExecutionTime = lastExecutionTime;
|
||||
|
||||
PublishCommand(command);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Messaging.Commands
|
||||
{
|
||||
public interface ICommandExecutor
|
||||
{
|
||||
void PublishCommand<TCommand>(TCommand command) where TCommand : Command;
|
||||
void PublishCommand(string commandTypeName);
|
||||
void PublishCommand(string commandTypeName, DateTime? lastEecutionTime);
|
||||
Command PublishCommandAsync<TCommand>(TCommand command) where TCommand : Command;
|
||||
Command PublishCommandAsync(string commandTypeName);
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ namespace NzbDrone.Core.Messaging.Commands.Tracking
|
|||
{
|
||||
private readonly ICached<Command> _cache;
|
||||
|
||||
public CommandTrackingService(ICacheManger cacheManger)
|
||||
public CommandTrackingService(ICacheManager cacheManager)
|
||||
{
|
||||
_cache = cacheManger.GetCache<Command>(GetType());
|
||||
_cache = cacheManager.GetCache<Command>(GetType());
|
||||
}
|
||||
|
||||
public Command GetById(int id)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex
|
||||
{
|
||||
public class PlexError
|
||||
{
|
||||
public String Error { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex
|
||||
{
|
||||
public class PlexException : NzbDroneException
|
||||
{
|
||||
public PlexException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public PlexException(string message, params object[] args) : base(message, args)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex
|
||||
{
|
||||
public class PlexSection
|
||||
{
|
||||
public Int32 Id { get; set; }
|
||||
public String Path { get; set; }
|
||||
}
|
||||
|
||||
public class PlexDirectory
|
||||
{
|
||||
public String Type { get; set; }
|
||||
|
||||
[JsonProperty("_children")]
|
||||
public List<PlexSection> Sections { get; set; }
|
||||
}
|
||||
|
||||
public class PlexMediaContainer
|
||||
{
|
||||
[JsonProperty("_children")]
|
||||
public List<PlexDirectory> Directories { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using RestSharp;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex
|
||||
{
|
||||
public interface IPlexServerProxy
|
||||
{
|
||||
List<PlexSection> GetTvSections(PlexServerSettings settings);
|
||||
void Update(int sectionId, PlexServerSettings settings);
|
||||
}
|
||||
|
||||
public class PlexServerProxy : IPlexServerProxy
|
||||
{
|
||||
private readonly ICached<String> _authCache;
|
||||
|
||||
public PlexServerProxy(ICacheManager cacheManager)
|
||||
{
|
||||
_authCache = cacheManager.GetCache<String>(GetType(), "authCache");
|
||||
}
|
||||
|
||||
public List<PlexSection> GetTvSections(PlexServerSettings settings)
|
||||
{
|
||||
var request = GetPlexServerRequest("library/sections", Method.GET, settings);
|
||||
var client = GetPlexServerClient(settings);
|
||||
|
||||
var response = client.Execute(request);
|
||||
|
||||
return Json.Deserialize<PlexMediaContainer>(response.Content)
|
||||
.Directories
|
||||
.Where(d => d.Type == "show")
|
||||
.SelectMany(d => d.Sections)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public void Update(int sectionId, PlexServerSettings settings)
|
||||
{
|
||||
var resource = String.Format("library/sections/{0}/refresh", sectionId);
|
||||
var request = GetPlexServerRequest(resource, Method.GET, settings);
|
||||
var client = GetPlexServerClient(settings);
|
||||
|
||||
var response = client.Execute(request);
|
||||
}
|
||||
|
||||
private String Authenticate(string username, string password)
|
||||
{
|
||||
var request = GetMyPlexRequest("users/sign_in.json", Method.POST);
|
||||
var client = GetMyPlexClient(username, password);
|
||||
|
||||
var response = client.Execute(request);
|
||||
CheckForError(response.Content);
|
||||
|
||||
var user = Json.Deserialize<PlexUser>(JObject.Parse(response.Content).SelectToken("user").ToString());
|
||||
|
||||
_authCache.Set(username, user.AuthenticationToken);
|
||||
|
||||
return user.AuthenticationToken;
|
||||
}
|
||||
|
||||
private RestClient GetMyPlexClient(string username, string password)
|
||||
{
|
||||
var client = new RestClient("https://my.plexapp.com");
|
||||
client.Authenticator = new HttpBasicAuthenticator(username, password);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private RestRequest GetMyPlexRequest(string resource, Method method)
|
||||
{
|
||||
var request = new RestRequest(resource, method);
|
||||
request.AddHeader("X-Plex-Platform", "Windows");
|
||||
request.AddHeader("X-Plex-Platform-Version", "7");
|
||||
request.AddHeader("X-Plex-Provides", "player");
|
||||
request.AddHeader("X-Plex-Client-Identifier", "AB6CCCC7-5CF5-4523-826A-B969E0FFD8A0");
|
||||
request.AddHeader("X-Plex-Product", "PlexWMC");
|
||||
request.AddHeader("X-Plex-Version", "0");
|
||||
|
||||
return request;
|
||||
|
||||
}
|
||||
|
||||
private RestClient GetPlexServerClient(PlexServerSettings settings)
|
||||
{
|
||||
return new RestClient(String.Format("http://{0}:{1}", settings.Host, settings.Port));
|
||||
}
|
||||
|
||||
private RestRequest GetPlexServerRequest(string resource, Method method, PlexServerSettings settings)
|
||||
{
|
||||
var request = new RestRequest(resource, method);
|
||||
request.AddHeader("Accept", "application/json");
|
||||
|
||||
if (!settings.Username.IsNullOrWhiteSpace())
|
||||
{
|
||||
request.AddParameter("X-Plex-Token", GetAuthenticationToken(settings.Username, settings.Password));
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private string GetAuthenticationToken(string username, string password)
|
||||
{
|
||||
return _authCache.Get(username, () => Authenticate(username, password));
|
||||
}
|
||||
|
||||
private void CheckForError(string response)
|
||||
{
|
||||
var error = Json.Deserialize<PlexError>(response);
|
||||
|
||||
if (error != null && !error.Error.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new PlexException(error.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
[FieldDefinition(2, Label = "Username")]
|
||||
public String Username { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Password")]
|
||||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
|
||||
public String Password { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Update Library", Type = FieldType.Checkbox)]
|
||||
|
|
|
@ -18,11 +18,13 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
public class PlexService : IPlexService, IExecute<TestPlexClientCommand>, IExecute<TestPlexServerCommand>
|
||||
{
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly IPlexServerProxy _plexServerProxy;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public PlexService(IHttpProvider httpProvider, Logger logger)
|
||||
public PlexService(IHttpProvider httpProvider, IPlexServerProxy plexServerProxy, Logger logger)
|
||||
{
|
||||
_httpProvider = httpProvider;
|
||||
_plexServerProxy = plexServerProxy;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -45,7 +47,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
{
|
||||
_logger.Debug("Sending Update Request to Plex Server");
|
||||
var sections = GetSectionKeys(settings);
|
||||
sections.ForEach(s => UpdateSection(settings, s));
|
||||
sections.ForEach(s => UpdateSection(s, settings));
|
||||
}
|
||||
|
||||
catch(Exception ex)
|
||||
|
@ -55,26 +57,21 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
}
|
||||
}
|
||||
|
||||
public List<int> GetSectionKeys(PlexServerSettings settings)
|
||||
private List<int> GetSectionKeys(PlexServerSettings settings)
|
||||
{
|
||||
_logger.Debug("Getting sections from Plex host: {0}", settings.Host);
|
||||
var url = String.Format("http://{0}:{1}/library/sections", settings.Host, settings.Port);
|
||||
var xmlStream = _httpProvider.DownloadStream(url, GetCredentials(settings));
|
||||
var xDoc = XDocument.Load(xmlStream);
|
||||
var mediaContainer = xDoc.Descendants("MediaContainer").FirstOrDefault();
|
||||
var directories = mediaContainer.Descendants("Directory").Where(x => x.Attribute("type").Value == "show");
|
||||
|
||||
return directories.Select(d => Int32.Parse(d.Attribute("key").Value)).ToList();
|
||||
return _plexServerProxy.GetTvSections(settings).Select(s => s.Id).ToList();
|
||||
}
|
||||
|
||||
public void UpdateSection(PlexServerSettings settings, int key)
|
||||
private void UpdateSection(int key, PlexServerSettings settings)
|
||||
{
|
||||
_logger.Debug("Updating Plex host: {0}, Section: {1}", settings.Host, key);
|
||||
var url = String.Format("http://{0}:{1}/library/sections/{2}/refresh", settings.Host, settings.Port, key);
|
||||
_httpProvider.DownloadString(url, GetCredentials(settings));
|
||||
|
||||
_plexServerProxy.Update(key, settings);
|
||||
}
|
||||
|
||||
public string SendCommand(string host, int port, string command, string username, string password)
|
||||
private string SendCommand(string host, int port, string command, string username, string password)
|
||||
{
|
||||
var url = String.Format("http://{0}:{1}/xbmcCmds/xbmcHttp?command={2}", host, port, command);
|
||||
|
||||
|
@ -86,13 +83,6 @@ namespace NzbDrone.Core.Notifications.Plex
|
|||
return _httpProvider.DownloadString(url);
|
||||
}
|
||||
|
||||
private NetworkCredential GetCredentials(PlexServerSettings settings)
|
||||
{
|
||||
if (settings.Username.IsNullOrWhiteSpace()) return null;
|
||||
|
||||
return new NetworkCredential(settings.Username, settings.Password);
|
||||
}
|
||||
|
||||
public void Execute(TestPlexClientCommand message)
|
||||
{
|
||||
_logger.Debug("Sending Test Notifcation to Plex Client: {0}", message.Host);
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex
|
||||
{
|
||||
public class PlexUser
|
||||
{
|
||||
[JsonProperty("authentication_token")]
|
||||
public String AuthenticationToken { get; set; }
|
||||
}
|
||||
}
|
|
@ -192,6 +192,8 @@
|
|||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Datastore\Migration\046_fix_nzb_su_url.cs" />
|
||||
<Compile Include="Datastore\Migration\047_add_published_date_blacklist_column.cs" />
|
||||
<Compile Include="Datastore\Migration\048_add_title_to_scenemappings.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
||||
|
@ -209,6 +211,7 @@
|
|||
<Compile Include="Datastore\ModelNotFoundException.cs" />
|
||||
<Compile Include="Datastore\PagingSpec.cs" />
|
||||
<Compile Include="Datastore\TableMapping.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RetrySpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\BlacklistSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\DownloadDecision.cs" />
|
||||
<Compile Include="DecisionEngine\IRejectWithReason.cs" />
|
||||
|
@ -258,6 +261,7 @@
|
|||
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdQueueTimeConverter.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\SabnzbdProxy.cs" />
|
||||
<Compile Include="Download\CheckForFailedDownloadCommand.cs" />
|
||||
<Compile Include="Download\FailedDownload.cs" />
|
||||
<Compile Include="Download\HistoryItem.cs" />
|
||||
<Compile Include="Download\DownloadFailedEvent.cs" />
|
||||
<Compile Include="Download\DownloadApprovedReports.cs" />
|
||||
|
@ -277,7 +281,8 @@
|
|||
<Compile Include="HealthCheck\Checks\IndexerCheck.cs" />
|
||||
<Compile Include="HealthCheck\Checks\UpdateCheck.cs" />
|
||||
<Compile Include="HealthCheck\HealthCheck.cs" />
|
||||
<Compile Include="HealthCheck\TriggerHealthCheckEvent.cs" />
|
||||
<Compile Include="HealthCheck\HealthCheckBase.cs" />
|
||||
<Compile Include="HealthCheck\HealthCheckCompleteEvent.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodes.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItems.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFiles.cs" />
|
||||
|
@ -373,6 +378,11 @@
|
|||
<Compile Include="Notifications\NotificationFactory.cs" />
|
||||
<Compile Include="Notifications\NotificationService.cs" />
|
||||
<Compile Include="Notifications\DownloadMessage.cs" />
|
||||
<Compile Include="Notifications\Plex\PlexError.cs" />
|
||||
<Compile Include="Notifications\Plex\PlexException.cs" />
|
||||
<Compile Include="Notifications\Plex\PlexSection.cs" />
|
||||
<Compile Include="Notifications\Plex\PlexServerProxy.cs" />
|
||||
<Compile Include="Notifications\Plex\PlexUser.cs" />
|
||||
<Compile Include="Notifications\PushBullet\PushBullet.cs" />
|
||||
<Compile Include="Notifications\PushBullet\PushBulletProxy.cs" />
|
||||
<Compile Include="Notifications\PushBullet\PushBulletSettings.cs" />
|
||||
|
|
|
@ -18,6 +18,8 @@ namespace NzbDrone.Core.Organizer
|
|||
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||
string GetSeriesFolder(string seriesTitle);
|
||||
string GetSeriesFolder(string seriesTitle, NamingConfig namingConfig);
|
||||
string GetSeasonFolder(string seriesTitle, int seasonNumber, NamingConfig namingConfig);
|
||||
}
|
||||
|
||||
public class FileNameBuilder : IBuildFileNames
|
||||
|
@ -48,12 +50,12 @@ namespace NzbDrone.Core.Organizer
|
|||
|
||||
public FileNameBuilder(INamingConfigService namingConfigService,
|
||||
IQualityDefinitionService qualityDefinitionService,
|
||||
ICacheManger cacheManger,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
{
|
||||
_namingConfigService = namingConfigService;
|
||||
_qualityDefinitionService = qualityDefinitionService;
|
||||
_patternCache = cacheManger.GetCache<EpisodeFormat>(GetType());
|
||||
_patternCache = cacheManager.GetCache<EpisodeFormat>(GetType());
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -171,11 +173,7 @@ namespace NzbDrone.Core.Organizer
|
|||
else
|
||||
{
|
||||
var nameSpec = _namingConfigService.GetConfig();
|
||||
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
|
||||
tokenValues.Add("{Series Title}", series.Title);
|
||||
|
||||
seasonFolder = ReplaceSeasonTokens(nameSpec.SeasonFolderFormat, seasonNumber);
|
||||
seasonFolder = ReplaceTokens(seasonFolder, tokenValues);
|
||||
seasonFolder = GetSeasonFolder(series.Title, seasonNumber, nameSpec);
|
||||
}
|
||||
|
||||
seasonFolder = CleanFilename(seasonFolder);
|
||||
|
@ -233,14 +231,29 @@ namespace NzbDrone.Core.Organizer
|
|||
}
|
||||
|
||||
public string GetSeriesFolder(string seriesTitle)
|
||||
{
|
||||
var namingConfig = _namingConfigService.GetConfig();
|
||||
|
||||
return GetSeriesFolder(seriesTitle, namingConfig);
|
||||
}
|
||||
|
||||
public string GetSeriesFolder(string seriesTitle, NamingConfig namingConfig)
|
||||
{
|
||||
seriesTitle = CleanFilename(seriesTitle);
|
||||
|
||||
var nameSpec = _namingConfigService.GetConfig();
|
||||
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
|
||||
tokenValues.Add("{Series Title}", seriesTitle);
|
||||
|
||||
return ReplaceTokens(nameSpec.SeriesFolderFormat, tokenValues);
|
||||
return ReplaceTokens(namingConfig.SeriesFolderFormat, tokenValues);
|
||||
}
|
||||
|
||||
public string GetSeasonFolder(string seriesTitle, int seasonNumber, NamingConfig namingConfig)
|
||||
{
|
||||
var tokenValues = new Dictionary<string, string>(FilenameBuilderTokenEqualityComparer.Instance);
|
||||
tokenValues.Add("{Series Title}", seriesTitle);
|
||||
|
||||
var seasonFolder = ReplaceSeasonTokens(namingConfig.SeasonFolderFormat, seasonNumber);
|
||||
return ReplaceTokens(seasonFolder, tokenValues);
|
||||
}
|
||||
|
||||
public static string CleanFilename(string name)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Validators;
|
||||
|
||||
|
@ -6,6 +7,9 @@ namespace NzbDrone.Core.Organizer
|
|||
{
|
||||
public static class FileNameValidation
|
||||
{
|
||||
private static readonly Regex SeasonFolderRegex = new Regex(@"(\{season(\:\d+)?\})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static IRuleBuilderOptions<T, string> ValidEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
|
@ -23,6 +27,12 @@ namespace NzbDrone.Core.Organizer
|
|||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.SeriesTitleRegex)).WithMessage("Must contain series title");
|
||||
}
|
||||
|
||||
public static IRuleBuilderOptions<T, string> ValidSeasonFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number");
|
||||
}
|
||||
}
|
||||
|
||||
public class ValidDailyEpisodeFormatValidator : PropertyValidator
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
|
@ -14,6 +11,8 @@ namespace NzbDrone.Core.Organizer
|
|||
SampleResult GetStandardSample(NamingConfig nameSpec);
|
||||
SampleResult GetMultiEpisodeSample(NamingConfig nameSpec);
|
||||
SampleResult GetDailySample(NamingConfig nameSpec);
|
||||
String GetSeriesFolderSample(NamingConfig nameSpec);
|
||||
String GetSeasonFolderSample(NamingConfig nameSpec);
|
||||
}
|
||||
|
||||
public class FilenameSampleService : IFilenameSampleService
|
||||
|
@ -123,6 +122,16 @@ namespace NzbDrone.Core.Organizer
|
|||
return result;
|
||||
}
|
||||
|
||||
public string GetSeriesFolderSample(NamingConfig nameSpec)
|
||||
{
|
||||
return _buildFileNames.GetSeriesFolder(_standardSeries.Title, nameSpec);
|
||||
}
|
||||
|
||||
public string GetSeasonFolderSample(NamingConfig nameSpec)
|
||||
{
|
||||
return _buildFileNames.GetSeasonFolder(_standardSeries.Title, _episode1.SeasonNumber, nameSpec);
|
||||
}
|
||||
|
||||
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace NzbDrone.Core.Parser.Model
|
|||
|
||||
public DateTime PublishDate { get; set; }
|
||||
|
||||
public int Age
|
||||
public Int32 Age
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -28,6 +28,21 @@ namespace NzbDrone.Core.Parser.Model
|
|||
}
|
||||
}
|
||||
|
||||
public Double AgeHours
|
||||
{
|
||||
get
|
||||
{
|
||||
return DateTime.UtcNow.Subtract(PublishDate).TotalHours;
|
||||
}
|
||||
|
||||
//This prevents manually downloading a release from blowing up in mono
|
||||
//TODO: Is there a better way?
|
||||
private set
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public int TvRageId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
@ -16,10 +17,6 @@ namespace NzbDrone.Core.Parser
|
|||
|
||||
private static readonly Regex[] ReportTitleRegex = new[]
|
||||
{
|
||||
//Episodes with airdate
|
||||
new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Anime - Absolute Episode Number + Title + Season+Episode
|
||||
//Todo: This currently breaks series that start with numbers
|
||||
// new Regex(@"^(?:(?<absoluteepisode>\d{2,3})(?:_|-|\s|\.)+)+(?<title>.+?)(?:\W|_)+(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)",
|
||||
|
@ -46,15 +43,15 @@ namespace NzbDrone.Core.Parser
|
|||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc)
|
||||
new Regex(@"^(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))+)",
|
||||
new Regex(@"^(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))+(?![\da-z]))",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
|
||||
new Regex(@"^(?<title>.+?)(?:(\W|_)+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))+)\W?(?!\\)",
|
||||
new Regex(@"^(?<title>.+?)(?:(\W|_)+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))*)\W?(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
|
||||
new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2,3}(?!\d+)))+)",
|
||||
new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:[ex]|\W[ex]){1,2}(?<episode>\d{2,3}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2,3}(?!\d+)))*)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Episodes with single digit episode number (S01E1, S01E5E6, etc)
|
||||
|
@ -81,6 +78,10 @@ namespace NzbDrone.Core.Parser
|
|||
new Regex(@"^(?<title>.+?)\W(?:S|Season)\W?(?<season>\d{1,2}(?!\d+))(\W+|_|$)(?<extras>EXTRAS|SUBPACK)?(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Episodes with airdate
|
||||
new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Supports 1103/1113 naming
|
||||
new Regex(@"^(?<title>.+?)?(?:\W(?<season>(?<!\d+|\(|\[|e|x)\d{2})(?<episode>(?<!e|x)\d{2}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
@ -91,7 +92,7 @@ namespace NzbDrone.Core.Parser
|
|||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc)
|
||||
new Regex(@"^(?<title>.+?)(?:(\W|_)+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{4}(?!\d+|i|p)))+)\W?(?!\\)",
|
||||
new Regex(@"^(?<title>.+?)(?:(\W|_)+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{4}(?!\d+|i|p)))+(?![\da-z]))\W?(?!\\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
//Anime - Title Absolute Episode Number
|
||||
|
@ -126,6 +127,12 @@ namespace NzbDrone.Core.Parser
|
|||
|
||||
var result = ParseTitle(fileInfo.Name);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
Logger.Debug("Attempting to parse episode info using directory path. {0}", fileInfo.Directory.Name);
|
||||
result = ParseTitle(fileInfo.Directory.Name + fileInfo.Extension);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
Logger.Debug("Attempting to parse episode info using full path. {0}", fileInfo.FullName);
|
||||
|
@ -138,8 +145,6 @@ namespace NzbDrone.Core.Parser
|
|||
return null;
|
||||
}
|
||||
|
||||
result.ReleaseGroup = ParseReleaseGroup(fileInfo.Name.Replace(fileInfo.Extension, ""));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -158,6 +163,7 @@ namespace NzbDrone.Core.Parser
|
|||
|
||||
if (match.Count != 0)
|
||||
{
|
||||
Console.WriteLine(regex);
|
||||
Logger.Trace(regex);
|
||||
try
|
||||
{
|
||||
|
@ -239,6 +245,12 @@ namespace NzbDrone.Core.Parser
|
|||
const string defaultReleaseGroup = "DRONE";
|
||||
|
||||
title = title.Trim();
|
||||
|
||||
if (!title.ContainsInvalidPathChars() && MediaFiles.MediaFileExtensions.Extensions.Contains(Path.GetExtension(title).ToLower()))
|
||||
{
|
||||
title = Path.GetFileNameWithoutExtension(title).Trim();
|
||||
}
|
||||
|
||||
var index = title.LastIndexOf('-');
|
||||
|
||||
if (index < 0)
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace NzbDrone.Core.SeriesStats
|
|||
SeriesId,
|
||||
SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount,
|
||||
SUM(CASE WHEN EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount,
|
||||
MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString
|
||||
MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 OR Monitored = 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString
|
||||
FROM Episodes";
|
||||
}
|
||||
|
||||
|
|
|
@ -196,8 +196,10 @@ namespace NzbDrone.Core.Tv
|
|||
|
||||
public List<Series> UpdateSeries(List<Series> series)
|
||||
{
|
||||
_logger.Debug("Updating {0} series", series.Count);
|
||||
foreach (var s in series)
|
||||
{
|
||||
_logger.Trace("Updating: {0}", s.Title);
|
||||
if (!s.RootFolderPath.IsNullOrWhiteSpace())
|
||||
{
|
||||
var folderName = new DirectoryInfo(s.Path).Name;
|
||||
|
@ -210,8 +212,7 @@ namespace NzbDrone.Core.Tv
|
|||
_logger.Trace("Not changing path for: {0}", s.Title);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Updating {0} series", series.Count);
|
||||
|
||||
_seriesRepository.UpdateMany(series);
|
||||
_logger.Debug("{0} series updated", series.Count);
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue