mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-14 15:44:53 -04:00
Compare commits
64 Commits
skyhook-no
...
normalized
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c460f6836 | ||
|
|
b03f434329 | ||
|
|
68290b0385 | ||
|
|
773274f3cc | ||
|
|
a33d8968ed | ||
|
|
e41baccd02 | ||
|
|
fef3423019 | ||
|
|
11926d8b2d | ||
|
|
4189bc6f76 | ||
|
|
601b54c244 | ||
|
|
905ab18336 | ||
|
|
f46a576df5 | ||
|
|
60231fbcae | ||
|
|
63860e783e | ||
|
|
cf8b9df5ad | ||
|
|
ff6841e410 | ||
|
|
ab49afe116 | ||
|
|
afcf136c4f | ||
|
|
d1b85444d0 | ||
|
|
63e0eba02f | ||
|
|
475c99d492 | ||
|
|
23552c3267 | ||
|
|
a49e37239e | ||
|
|
65b936ed94 | ||
|
|
359ef04861 | ||
|
|
8cf028e071 | ||
|
|
eea3419849 | ||
|
|
62bc63312d | ||
|
|
6a6d415625 | ||
|
|
1fbe82ae47 | ||
|
|
87f3cc9014 | ||
|
|
ab07a40931 | ||
|
|
10f292b225 | ||
|
|
de5ce23989 | ||
|
|
d0e226e269 | ||
|
|
d7cb5090fc | ||
|
|
416e9abca5 | ||
|
|
6dcb7768a9 | ||
|
|
baf83b4c71 | ||
|
|
285288db1a | ||
|
|
ec3dd982f6 | ||
|
|
410aa467b8 | ||
|
|
0c89a4ae8f | ||
|
|
a1edbafa8a | ||
|
|
ef5a400c68 | ||
|
|
8cc02a9d9c | ||
|
|
e83e852e0d | ||
|
|
4e10d30cf6 | ||
|
|
f335cc1af8 | ||
|
|
f4bea5512c | ||
|
|
9f8091e4d7 | ||
|
|
5aa02eb15c | ||
|
|
6bbe4ce066 | ||
|
|
563b5ef017 | ||
|
|
b9df5634bf | ||
|
|
755575d107 | ||
|
|
8eaab46488 | ||
|
|
4fbc481780 | ||
|
|
a41b5723d4 | ||
|
|
c2b66cf524 | ||
|
|
0d782e1cac | ||
|
|
edf549d0fd | ||
|
|
cf7ce9804f | ||
|
|
e8d01daf03 |
@@ -25,7 +25,7 @@ gulp.task('copyHtml', function () {
|
||||
});
|
||||
|
||||
gulp.task('copyContent', function () {
|
||||
return gulp.src([paths.src.content + '**/*.*', '!**/*.less'])
|
||||
return gulp.src([paths.src.content + '**/*.*', '!**/*.less', '!**/*.css'])
|
||||
.pipe(gulp.dest(paths.dest.content))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
@@ -16,6 +16,10 @@ gulp.task('less', function() {
|
||||
paths.src.content + 'bootstrap.less',
|
||||
paths.src.content + 'theme.less',
|
||||
paths.src.content + 'overrides.less',
|
||||
paths.src.content + 'bootstrap.toggle-switch.css',
|
||||
paths.src.content + 'fullcalendar.css',
|
||||
paths.src.content + 'Messenger/messenger.css',
|
||||
paths.src.content + 'Messenger/messenger.flat.css',
|
||||
paths.src.root + 'Series/series.less',
|
||||
paths.src.root + 'Activity/activity.less',
|
||||
paths.src.root + 'AddSeries/addSeries.less',
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace NzbDrone.Api.Authentication
|
||||
new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase, Encoding.ASCII.GetBytes(_configService.HmacSalt)))
|
||||
);
|
||||
|
||||
FormsAuthentication.FormsAuthenticationCookieName = "_ncfa_sonarr";
|
||||
|
||||
FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration
|
||||
{
|
||||
RedirectUrl = _configFileProvider.UrlBase + "/login",
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.SignalR;
|
||||
using HttpStatusCode = System.Net.HttpStatusCode;
|
||||
|
||||
namespace NzbDrone.Api.EpisodeFiles
|
||||
{
|
||||
@@ -18,27 +15,21 @@ namespace NzbDrone.Api.EpisodeFiles
|
||||
IHandle<EpisodeFileAddedEvent>
|
||||
{
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IRecycleBinProvider _recycleBinProvider;
|
||||
private readonly IDeleteMediaFiles _mediaFileDeletionService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public EpisodeFileModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
IMediaFileService mediaFileService,
|
||||
IDiskProvider diskProvider,
|
||||
IRecycleBinProvider recycleBinProvider,
|
||||
IDeleteMediaFiles mediaFileDeletionService,
|
||||
ISeriesService seriesService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
Logger logger)
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_mediaFileService = mediaFileService;
|
||||
_diskProvider = diskProvider;
|
||||
_recycleBinProvider = recycleBinProvider;
|
||||
_mediaFileDeletionService = mediaFileDeletionService;
|
||||
_seriesService = seriesService;
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_logger = logger;
|
||||
GetResourceById = GetEpisodeFile;
|
||||
GetResourceAll = GetEpisodeFiles;
|
||||
UpdateResource = SetQuality;
|
||||
@@ -77,13 +68,15 @@ namespace NzbDrone.Api.EpisodeFiles
|
||||
private void DeleteEpisodeFile(int id)
|
||||
{
|
||||
var episodeFile = _mediaFileService.Get(id);
|
||||
var series = _seriesService.GetSeries(episodeFile.SeriesId);
|
||||
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||
var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
|
||||
|
||||
_logger.Info("Deleting episode file: {0}", fullPath);
|
||||
_recycleBinProvider.DeleteFile(fullPath, subfolder);
|
||||
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
|
||||
if (episodeFile == null)
|
||||
{
|
||||
throw new NzbDroneClientException(HttpStatusCode.NotFound, "Episode file not found");
|
||||
}
|
||||
|
||||
var series = _seriesService.GetSeries(episodeFile.SeriesId);
|
||||
|
||||
_mediaFileDeletionService.DeleteEpisodeFile(series, episodeFile);
|
||||
}
|
||||
|
||||
public void Handle(EpisodeFileAddedEvent message)
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Api.Episodes
|
||||
{
|
||||
public abstract class EpisodeModuleWithSignalR : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>,
|
||||
IHandle<EpisodeGrabbedEvent>,
|
||||
IHandle<EpisodeDownloadedEvent>
|
||||
IHandle<EpisodeImportedEvent>
|
||||
{
|
||||
protected readonly IEpisodeService _episodeService;
|
||||
protected readonly ISeriesService _seriesService;
|
||||
@@ -115,9 +115,14 @@ namespace NzbDrone.Api.Episodes
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(EpisodeDownloadedEvent message)
|
||||
public void Handle(EpisodeImportedEvent message)
|
||||
{
|
||||
foreach (var episode in message.Episode.Episodes)
|
||||
if (!message.NewDownload)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var episode in message.EpisodeInfo.Episodes)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Updated, episode.Id);
|
||||
}
|
||||
|
||||
46
src/NzbDrone.Api/Extensions/Pipelines/UrlBasePipeline.cs
Normal file
46
src/NzbDrone.Api/Extensions/Pipelines/UrlBasePipeline.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Nancy;
|
||||
using Nancy.Bootstrapper;
|
||||
using Nancy.Responses;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Extensions.Pipelines
|
||||
{
|
||||
public class UrlBasePipeline : IRegisterNancyPipeline
|
||||
{
|
||||
private readonly string _urlBase;
|
||||
|
||||
public UrlBasePipeline(IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_urlBase = configFileProvider.UrlBase;
|
||||
}
|
||||
|
||||
public int Order => 99;
|
||||
|
||||
public void Register(IPipelines pipelines)
|
||||
{
|
||||
if (_urlBase.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
pipelines.BeforeRequest.AddItemToStartOfPipeline((Func<NancyContext, Response>) Handle);
|
||||
}
|
||||
}
|
||||
|
||||
private Response Handle(NancyContext context)
|
||||
{
|
||||
var basePath = context.Request.Url.BasePath;
|
||||
|
||||
if (basePath.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new RedirectResponse($"{_urlBase}{context.Request.Path}{context.Request.Url.Query}");
|
||||
}
|
||||
|
||||
if (_urlBase != basePath)
|
||||
{
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Nancy;
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
private readonly IAnalyticsService _analyticsService;
|
||||
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
|
||||
private readonly string _indexPath;
|
||||
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static string API_KEY;
|
||||
private static string URL_BASE;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Nancy.Responses;
|
||||
@@ -38,20 +38,6 @@ namespace NzbDrone.Api.Frontend
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
//Redirect to the subfolder if the request went to the base URL
|
||||
if (path.Equals("/"))
|
||||
{
|
||||
var urlBase = _configFileProvider.UrlBase;
|
||||
|
||||
if (!string.IsNullOrEmpty(urlBase))
|
||||
{
|
||||
if (Request.Url.BasePath != urlBase)
|
||||
{
|
||||
return new RedirectResponse(urlBase + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
|
||||
|
||||
if (mapper != null)
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Api.Indexers
|
||||
|
||||
private Response DownloadRelease(ReleaseResource release)
|
||||
{
|
||||
var remoteEpisode = _remoteEpisodeCache.Find(release.Guid);
|
||||
var remoteEpisode = _remoteEpisodeCache.Find(GetCacheKey(release));
|
||||
|
||||
if (remoteEpisode == null)
|
||||
{
|
||||
@@ -68,7 +68,7 @@ namespace NzbDrone.Api.Indexers
|
||||
}
|
||||
catch (ReleaseDownloadException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, ex.Message);
|
||||
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Getting release from indexer failed");
|
||||
}
|
||||
|
||||
@@ -113,8 +113,14 @@ namespace NzbDrone.Api.Indexers
|
||||
|
||||
protected override ReleaseResource MapDecision(DownloadDecision decision, int initialWeight)
|
||||
{
|
||||
_remoteEpisodeCache.Set(decision.RemoteEpisode.Release.Guid, decision.RemoteEpisode, TimeSpan.FromMinutes(30));
|
||||
return base.MapDecision(decision, initialWeight);
|
||||
var resource = base.MapDecision(decision, initialWeight);
|
||||
_remoteEpisodeCache.Set(GetCacheKey(resource), decision.RemoteEpisode, TimeSpan.FromMinutes(30));
|
||||
return resource;
|
||||
}
|
||||
|
||||
private string GetCacheKey(ReleaseResource resource)
|
||||
{
|
||||
return string.Concat(resource.IndexerId, "_", resource.Guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,7 @@
|
||||
<Compile Include="Commands\CommandResource.cs" />
|
||||
<Compile Include="Extensions\AccessControlHeaders.cs" />
|
||||
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
|
||||
<Compile Include="Extensions\Pipelines\UrlBasePipeline.cs" />
|
||||
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />
|
||||
<Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" />
|
||||
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
namespace NzbDrone.Common.Http
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public enum HttpMethod
|
||||
{
|
||||
GET,
|
||||
PUT,
|
||||
POST,
|
||||
HEAD,
|
||||
PUT,
|
||||
DELETE,
|
||||
HEAD,
|
||||
OPTIONS,
|
||||
PATCH,
|
||||
OPTIONS
|
||||
MERGE
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,11 @@ namespace NzbDrone.Common.Reflection
|
||||
return (T)attribute;
|
||||
}
|
||||
|
||||
public static T[] GetAttributes<T>(this MemberInfo member) where T : Attribute
|
||||
{
|
||||
return member.GetCustomAttributes(typeof(T), false).OfType<T>().ToArray();
|
||||
}
|
||||
|
||||
public static Type FindTypeByName(this Assembly assembly, string name)
|
||||
{
|
||||
return assembly.GetTypes().SingleOrDefault(c => c.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
|
||||
@@ -70,4 +75,4 @@ namespace NzbDrone.Common.Reflection
|
||||
return type.GetCustomAttributes(typeof(TAttribute), true).Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
||||
namespace NzbDrone.Common.Security
|
||||
@@ -14,6 +15,12 @@ namespace NzbDrone.Common.Security
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
// This was never meant to be used on mono, and will cause issues with mono 5 and higher if btls is enabled.
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: In v3 we should drop support for SSL3 because its very insecure. Only leaving it enabled because some people might rely on it.
|
||||
|
||||
@@ -5,6 +5,7 @@ using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.DataAugmentation.Xem;
|
||||
using NzbDrone.Core.DataAugmentation.Xem.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -98,7 +99,6 @@ namespace NzbDrone.Core.Test.DataAugmentation.SceneNumbering
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_not_fetch_scenenumbering_if_not_listed()
|
||||
{
|
||||
@@ -308,5 +308,19 @@ namespace NzbDrone.Core.Test.DataAugmentation.SceneNumbering
|
||||
episode.SceneSeasonNumber.Should().NotHaveValue();
|
||||
episode.SceneEpisodeNumber.Should().NotHaveValue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_mapping_when_scene_information_is_all_zero()
|
||||
{
|
||||
GivenTvdbMappings();
|
||||
|
||||
AddTvdbMapping(0, 0, 0, 8, 3, 1); // 3x01 -> 3x01
|
||||
AddTvdbMapping(0, 0, 0, 9, 3, 2); // 3x02 -> 3x02
|
||||
|
||||
Subject.Handle(new SeriesUpdatedEvent(_series));
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Verify(v => v.UpdateEpisodes(It.Is<List<Episode>>(e => e.Any(c => c.SceneAbsoluteEpisodeNumber == 0 && c.SceneSeasonNumber == 0 && c.SceneEpisodeNumber == 0))), Times.Never());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
{
|
||||
[TestFixture]
|
||||
|
||||
public class BlockedIndexerSpecificationFixture : CoreTest<BlockedIndexerSpecification>
|
||||
{
|
||||
private RemoteEpisode _remoteEpisode;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_remoteEpisode = new RemoteEpisode
|
||||
{
|
||||
Release = new ReleaseInfo { IndexerId = 1 }
|
||||
};
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Setup(v => v.GetBlockedProviders())
|
||||
.Returns(new List<IndexerStatus>());
|
||||
}
|
||||
|
||||
private void WithBlockedIndexer()
|
||||
{
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Setup(v => v.GetBlockedProviders())
|
||||
.Returns(new List<IndexerStatus> { new IndexerStatus { ProviderId = 1, DisabledTill = DateTime.UtcNow } });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_no_blocked_indexer()
|
||||
{
|
||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_false_if_blocked_indexer()
|
||||
{
|
||||
WithBlockedIndexer();
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
|
||||
Subject.Type.Should().Be(RejectionType.Temporary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications.Search;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.TorrentRss;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.DecisionEngineTests.Search
|
||||
{
|
||||
[TestFixture]
|
||||
public class TorrentSeedingSpecificationFixture : TestBase<TorrentSeedingSpecification>
|
||||
{
|
||||
private Series _series;
|
||||
private RemoteEpisode _remoteEpisode;
|
||||
private IndexerDefinition _indexerDefinition;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>.CreateNew().With(s => s.Id = 1).Build();
|
||||
|
||||
_remoteEpisode = new RemoteEpisode
|
||||
{
|
||||
Series = _series,
|
||||
Release = new TorrentInfo
|
||||
{
|
||||
IndexerId = 1,
|
||||
Title = "Series.Title.S01.720p.BluRay.X264-RlsGrp",
|
||||
Seeders = 0
|
||||
}
|
||||
};
|
||||
|
||||
_indexerDefinition = new IndexerDefinition
|
||||
{
|
||||
Settings = new TorrentRssIndexerSettings { MinimumSeeders = 5 }
|
||||
};
|
||||
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(v => v.Get(1))
|
||||
.Returns(_indexerDefinition);
|
||||
|
||||
}
|
||||
|
||||
private void GivenReleaseSeeders(int? seeders)
|
||||
{
|
||||
(_remoteEpisode.Release as TorrentInfo).Seeders = seeders;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_not_torrent()
|
||||
{
|
||||
_remoteEpisode.Release = new ReleaseInfo
|
||||
{
|
||||
IndexerId = 1,
|
||||
Title = "Series.Title.S01.720p.BluRay.X264-RlsGrp"
|
||||
};
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_indexer_not_specified()
|
||||
{
|
||||
_remoteEpisode.Release.IndexerId = 0;
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_indexer_no_longer_exists()
|
||||
{
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(v => v.Get(It.IsAny<int>()))
|
||||
.Callback<int>(i => { throw new ModelNotFoundException(typeof(IndexerDefinition), i); });
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_true_if_seeds_unknown()
|
||||
{
|
||||
GivenReleaseSeeders(null);
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void should_return_true_if_seeds_above_or_equal_to_limit(int seeders)
|
||||
{
|
||||
GivenReleaseSeeders(seeders);
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase(0)]
|
||||
[TestCase(4)]
|
||||
public void should_return_false_if_seeds_belove_limit(int seeders)
|
||||
{
|
||||
GivenReleaseSeeders(seeders);
|
||||
|
||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Profiles;
|
||||
using NzbDrone.Core.Qualities;
|
||||
@@ -35,7 +37,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
.Build();
|
||||
}
|
||||
|
||||
private RemoteEpisode GetRemoteEpisode(List<Episode> episodes, QualityModel quality)
|
||||
private RemoteEpisode GetRemoteEpisode(List<Episode> episodes, QualityModel quality, DownloadProtocol downloadProtocol = DownloadProtocol.Usenet)
|
||||
{
|
||||
var remoteEpisode = new RemoteEpisode();
|
||||
remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo();
|
||||
@@ -45,6 +47,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
remoteEpisode.Episodes.AddRange(episodes);
|
||||
|
||||
remoteEpisode.Release = new ReleaseInfo();
|
||||
remoteEpisode.Release.DownloadProtocol = downloadProtocol;
|
||||
remoteEpisode.Release.PublishDate = DateTime.UtcNow;
|
||||
|
||||
remoteEpisode.Series = Builder<Series>.CreateNew()
|
||||
@@ -192,7 +195,6 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteEpisode>()), Times.Never());
|
||||
@@ -209,7 +211,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.Add(It.IsAny<DownloadDecision>()), Times.Never());
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.Add(It.IsAny<DownloadDecision>(), It.IsAny<PendingReleaseReason>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -223,7 +225,43 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
|
||||
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.Add(It.IsAny<DownloadDecision>()), Times.Exactly(2));
|
||||
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.Add(It.IsAny<DownloadDecision>(), It.IsAny<PendingReleaseReason>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_add_to_failed_if_already_failed_for_that_protocol()
|
||||
{
|
||||
var episodes = new List<Episode> { GetEpisode(1) };
|
||||
var remoteEpisode = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p));
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode));
|
||||
|
||||
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteEpisode>()))
|
||||
.Throws(new DownloadClientUnavailableException("Download client failed"));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteEpisode>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_add_to_failed_if_failed_for_a_different_protocol()
|
||||
{
|
||||
var episodes = new List<Episode> { GetEpisode(1) };
|
||||
var remoteEpisode = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p), DownloadProtocol.Usenet);
|
||||
var remoteEpisode2 = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p), DownloadProtocol.Torrent);
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||
|
||||
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.Is<RemoteEpisode>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)))
|
||||
.Throws(new DownloadClientUnavailableException("Download client failed"));
|
||||
|
||||
Subject.ProcessDecisions(decisions);
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteEpisode>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)), Times.Once());
|
||||
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteEpisode>(r => r.Release.DownloadProtocol == DownloadProtocol.Torrent)), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download
|
||||
{
|
||||
public class DownloadClientStatusServiceFixture : CoreTest<DownloadClientStatusService>
|
||||
{
|
||||
private DateTime _epoch;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_epoch = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private DownloadClientStatus WithStatus(DownloadClientStatus status)
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Setup(v => v.FindByProviderId(1))
|
||||
.Returns(status);
|
||||
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Setup(v => v.All())
|
||||
.Returns(new[] { status });
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private void VerifyUpdate()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<DownloadClientStatus>()), Times.Once());
|
||||
}
|
||||
|
||||
private void VerifyNoUpdate()
|
||||
{
|
||||
Mocker.GetMock<IDownloadClientStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<DownloadClientStatus>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_consider_blocked_within_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(4),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_consider_blocked_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_escalate_further_till_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
var origStatus = WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(4),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().BeNull();
|
||||
|
||||
origStatus.EscalationLevel.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_escalate_further_after_5_minutes_since_initial_failure()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
|
||||
status.EscalationLevel.Should().BeGreaterThan(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_escalate_beyond_3_hours()
|
||||
{
|
||||
WithStatus(new DownloadClientStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromMinutes(6),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(120),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
status.DisabledTill.Should().HaveValue();
|
||||
status.DisabledTill.Should().NotBeAfter(_epoch + TimeSpan.FromHours(3.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||
private NzbgetQueueItem _queued;
|
||||
private NzbgetHistoryItem _failed;
|
||||
private NzbgetHistoryItem _completed;
|
||||
private Dictionary<string, string> _configItems;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
@@ -80,13 +81,18 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||
DownloadRate = 7000000
|
||||
});
|
||||
|
||||
var configItems = new Dictionary<string, string>();
|
||||
configItems.Add("Category1.Name", "tv");
|
||||
configItems.Add("Category1.DestDir", @"/remote/mount/tv");
|
||||
|
||||
Mocker.GetMock<INzbgetProxy>()
|
||||
.Setup(v => v.GetVersion(It.IsAny<NzbgetSettings>()))
|
||||
.Returns("14.0");
|
||||
|
||||
_configItems = new Dictionary<string, string>();
|
||||
_configItems.Add("Category1.Name", "tv");
|
||||
_configItems.Add("Category1.DestDir", @"/remote/mount/tv");
|
||||
|
||||
Mocker.GetMock<INzbgetProxy>()
|
||||
.Setup(v => v.GetConfig(It.IsAny<NzbgetSettings>()))
|
||||
.Returns(configItems);
|
||||
.Returns(_configItems);
|
||||
}
|
||||
|
||||
protected void GivenFailedDownload()
|
||||
@@ -414,5 +420,18 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
|
||||
|
||||
error.IsValid.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("0", false)]
|
||||
[TestCase("1", true)]
|
||||
[TestCase(" 7", false)]
|
||||
[TestCase("5000000", false)]
|
||||
public void should_test_keephistory(string keephistory, bool expected)
|
||||
{
|
||||
_configItems["KeepHistory"] = keephistory;
|
||||
|
||||
var error = Subject.Test();
|
||||
|
||||
error.IsValid.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
[Test]
|
||||
public void should_add()
|
||||
{
|
||||
Subject.Add(_temporarilyRejected);
|
||||
Subject.Add(_temporarilyRejected, PendingReleaseReason.Delay);
|
||||
|
||||
VerifyInsert();
|
||||
}
|
||||
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
{
|
||||
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate);
|
||||
|
||||
Subject.Add(_temporarilyRejected);
|
||||
Subject.Add(_temporarilyRejected, PendingReleaseReason.Delay);
|
||||
|
||||
VerifyNoInsert();
|
||||
}
|
||||
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
{
|
||||
GivenHeldRelease(_release.Title + "-RP", _release.Indexer, _release.PublishDate);
|
||||
|
||||
Subject.Add(_temporarilyRejected);
|
||||
Subject.Add(_temporarilyRejected, PendingReleaseReason.Delay);
|
||||
|
||||
VerifyInsert();
|
||||
}
|
||||
@@ -132,7 +132,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
{
|
||||
GivenHeldRelease(_release.Title, "AnotherIndexer", _release.PublishDate);
|
||||
|
||||
Subject.Add(_temporarilyRejected);
|
||||
Subject.Add(_temporarilyRejected, PendingReleaseReason.Delay);
|
||||
|
||||
VerifyInsert();
|
||||
}
|
||||
@@ -142,7 +142,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
{
|
||||
GivenHeldRelease(_release.Title, _release.Indexer, _release.PublishDate.AddHours(1));
|
||||
|
||||
Subject.Add(_temporarilyRejected);
|
||||
Subject.Add(_temporarilyRejected, PendingReleaseReason.Delay);
|
||||
|
||||
VerifyInsert();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
public void should_not_ignore_pending_items_from_available_indexer()
|
||||
{
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Setup(v => v.GetBlockedIndexers())
|
||||
.Setup(v => v.GetBlockedProviders())
|
||||
.Returns(new List<IndexerStatus>());
|
||||
|
||||
GivenPendingRelease();
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
public void should_ignore_pending_items_from_unavailable_indexer()
|
||||
{
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Setup(v => v.GetBlockedIndexers())
|
||||
.Setup(v => v.GetBlockedProviders())
|
||||
.Returns(new List<IndexerStatus> { new IndexerStatus { ProviderId = 1, DisabledTill = DateTime.UtcNow.AddHours(2) } });
|
||||
|
||||
GivenPendingRelease();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:newznab="http://www.newznab.com/DTD/2010/feeds/attributes/" xmlns:torznab="http://torznab.com/schemas/2015/feed">
|
||||
<channel>
|
||||
<atom:link href="https://localhost/feed/rssx" rel="self" type="application/rss+xml" />
|
||||
<title>Anime Tosho</title>
|
||||
<link>https://localhost/</link>
|
||||
<description>Latest releases feed</description>
|
||||
<language>en-gb</language>
|
||||
<ttl>30</ttl>
|
||||
<lastBuildDate>Wed, 17 May 2017 20:36:06 +0000</lastBuildDate>
|
||||
<newznab:response offset="0"/>
|
||||
<item>
|
||||
<title>[finFAGs]_Frame_Arms_Girl_07_(1280x720_TV_AAC)_[1262B6F7].mkv</title>
|
||||
<pubDate>Wed, 17 May 2017 20:36:06 +0000</pubDate>
|
||||
<guid isPermaLink="true">https://localhost/view/123451</guid>
|
||||
<category>Anime</category>
|
||||
<description><![CDATA[<strong>Total Size</strong>: 301.8 MB<br />]]></description>
|
||||
<link>https://localhost/view/finfags-_frame_arms_girl_07_-1280x720_tv_aac-_-1262b6f7-mkv.123451</link>
|
||||
<comments>https://localhost/view/finfags-_frame_arms_girl_07_-1280x720_tv_aac-_-1262b6f7-mkv.123451</comments>
|
||||
<enclosure url="http://storage.localhost/torrents/123451.torrent" type="application/x-bittorrent" length="0" />
|
||||
<source url="http://www.tokyotosho.info/details.php?id=123451">TokyoTosho</source>
|
||||
|
||||
<newznab:attr name="category" value="5070" />
|
||||
<newznab:attr name="category" value="100001" />
|
||||
<newznab:attr name="files" value="1" />
|
||||
<newznab:attr name="size" value="316477946" />
|
||||
|
||||
<torznab:attr name="files" value="1" />
|
||||
<torznab:attr name="size" value="316477946" />
|
||||
<torznab:attr name="category" value="5070" />
|
||||
<torznab:attr name="category" value="100001" />
|
||||
<torznab:attr name="infohash" value="2d69a861bef5a9f2cdf791b7328e37b7953205e1" />
|
||||
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:VU2QYN66WU7FTPXSG3TFDRXW6KTEBPBF" />
|
||||
</item>
|
||||
<item>
|
||||
<title>[HorribleSubs] Frame Arms Girl - 07 [720p].mkv</title>
|
||||
<pubDate>Mon, 15 May 2017 19:15:56 +0000</pubDate>
|
||||
<guid isPermaLink="true">https://localhost/view/123452</guid>
|
||||
<category>Anime</category>
|
||||
<description><![CDATA[<strong>Total Size</strong>: 452.0 MB<br />]]></description>
|
||||
<link>https://localhost/view/horriblesubs-frame-arms-girl-07-720p-mkv.123452</link>
|
||||
<comments>https://localhost/view/horriblesubs-frame-arms-girl-07-720p-mkv.123452</comments>
|
||||
<enclosure url="http://storage.localhost/torrents/123452.torrent" type="application/x-bittorrent" length="0" />
|
||||
<enclosure url="http://storage.localhost/nzb/123452.nzb" type="application/x-nzb" length="0" />
|
||||
<source url="http://www.tokyotosho.info/details.php?id=123452">TokyoTosho</source>
|
||||
|
||||
<newznab:attr name="category" value="5070" />
|
||||
<newznab:attr name="category" value="100001" />
|
||||
<newznab:attr name="files" value="1" />
|
||||
<newznab:attr name="size" value="473987489" />
|
||||
|
||||
<torznab:attr name="files" value="1" />
|
||||
<torznab:attr name="size" value="473987489" />
|
||||
<torznab:attr name="category" value="5070" />
|
||||
<torznab:attr name="category" value="100001" />
|
||||
<torznab:attr name="infohash" value="bff4afebcd50c21949ed6a06323d2120c649bd82" />
|
||||
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:5QK77JL7LZVIMEGKJ5VVAMMR5EEQMMSN" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
@@ -26,7 +25,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
public void should_return_error_when_download_client_throws()
|
||||
{
|
||||
var downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||
downloadClient.Setup(s => s.Definition).Returns(new IndexerDefinition{Name = "Test"});
|
||||
downloadClient.Setup(s => s.Definition).Returns(new DownloadClientDefinition{Name = "Test"});
|
||||
|
||||
downloadClient.Setup(s => s.GetItems())
|
||||
.Throws<Exception>();
|
||||
@@ -36,8 +35,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(new IDownloadClient[] { downloadClient.Object });
|
||||
|
||||
Subject.Check().ShouldBeError();
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
.Returns(_indexers);
|
||||
|
||||
Mocker.GetMock<IIndexerStatusService>()
|
||||
.Setup(v => v.GetBlockedIndexers())
|
||||
.Setup(v => v.GetBlockedProviders())
|
||||
.Returns(_blockedIndexers);
|
||||
}
|
||||
|
||||
@@ -57,13 +57,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
{
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
[Test]
|
||||
public void should_not_return_error_when_indexer_failed_less_than_an_hour()
|
||||
{
|
||||
GivenIndexer(1, 0.1, 0.5);
|
||||
|
||||
Subject.Check().ShouldBeOk();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_warning_if_indexer_unavailable()
|
||||
|
||||
@@ -13,6 +13,7 @@ using NzbDrone.Core.Qualities;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Test.Qualities;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.HistoryTests
|
||||
@@ -81,7 +82,13 @@ namespace NzbDrone.Core.Test.HistoryTests
|
||||
Path = @"C:\Test\Unsorted\Series.s01e01.mkv"
|
||||
};
|
||||
|
||||
Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, true, "sab", "abcd"));
|
||||
var downloadClientItem = new DownloadClientItem
|
||||
{
|
||||
DownloadClient = "sab",
|
||||
DownloadId = "abcd"
|
||||
};
|
||||
|
||||
Subject.Handle(new EpisodeImportedEvent(localEpisode, episodeFile, new List<EpisodeFile>(), true, downloadClientItem));
|
||||
|
||||
Mocker.GetMock<IHistoryRepository>()
|
||||
.Verify(v => v.Insert(It.Is<History.History>(h => h.SourceTitle == Path.GetFileNameWithoutExtension(localEpisode.Path))));
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||
{
|
||||
[TestFixture]
|
||||
public class CleanupDownloadClientUnavailablePendingReleasesFixture : DbTest<CleanupDownloadClientUnavailablePendingReleases, PendingRelease>
|
||||
{
|
||||
[Test]
|
||||
public void should_delete_old_DownloadClientUnavailable_pending_items()
|
||||
{
|
||||
var pendingRelease = Builder<PendingRelease>.CreateNew()
|
||||
.With(h => h.Reason = PendingReleaseReason.DownloadClientUnavailable)
|
||||
.With(h => h.Added = DateTime.UtcNow.AddDays(-21))
|
||||
.With(h => h.ParsedEpisodeInfo = new ParsedEpisodeInfo())
|
||||
.With(h => h.Release = new ReleaseInfo())
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(pendingRelease);
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_old_Fallback_pending_items()
|
||||
{
|
||||
var pendingRelease = Builder<PendingRelease>.CreateNew()
|
||||
.With(h => h.Reason = PendingReleaseReason.Fallback)
|
||||
.With(h => h.Added = DateTime.UtcNow.AddDays(-21))
|
||||
.With(h => h.ParsedEpisodeInfo = new ParsedEpisodeInfo())
|
||||
.With(h => h.Release = new ReleaseInfo())
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(pendingRelease);
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_delete_old_Delay_pending_items()
|
||||
{
|
||||
var pendingRelease = Builder<PendingRelease>.CreateNew()
|
||||
.With(h => h.Reason = PendingReleaseReason.Delay)
|
||||
.With(h => h.Added = DateTime.UtcNow.AddDays(-21))
|
||||
.With(h => h.ParsedEpisodeInfo = new ParsedEpisodeInfo())
|
||||
.With(h => h.Release = new ReleaseInfo())
|
||||
.BuildNew();
|
||||
|
||||
Db.Insert(pendingRelease);
|
||||
Subject.Clean();
|
||||
AllStoredModels.Should().HaveCount(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
public class IndexerStatusServiceFixture : CoreTest<IndexerStatusService>
|
||||
{
|
||||
private DateTime _epoch;
|
||||
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
private void WithStatus(IndexerStatus status)
|
||||
{
|
||||
Mocker.GetMock<IIndexerStatusRepository>()
|
||||
.Setup(v => v.FindByIndexerId(1))
|
||||
.Setup(v => v.FindByProviderId(1))
|
||||
.Returns(status);
|
||||
|
||||
Mocker.GetMock<IIndexerStatusRepository>()
|
||||
@@ -29,25 +29,16 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
.Returns(new[] { status });
|
||||
}
|
||||
|
||||
private void VerifyUpdate(bool updated = true)
|
||||
private void VerifyUpdate()
|
||||
{
|
||||
Mocker.GetMock<IIndexerStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<IndexerStatus>()), Times.Exactly(updated ? 1 : 0));
|
||||
.Verify(v => v.Upsert(It.IsAny<IndexerStatus>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_start_backoff_on_first_failure()
|
||||
private void VerifyNoUpdate()
|
||||
{
|
||||
WithStatus(new IndexerStatus());
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedIndexers().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
status.DisabledTill.Should().HaveValue();
|
||||
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
|
||||
Mocker.GetMock<IIndexerStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<IndexerStatus>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -59,7 +50,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedIndexers().FirstOrDefault();
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().BeNull();
|
||||
}
|
||||
|
||||
@@ -70,22 +61,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||
|
||||
Subject.RecordSuccess(1);
|
||||
|
||||
VerifyUpdate(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_preserve_escalation_on_intermittent_success()
|
||||
{
|
||||
WithStatus(new IndexerStatus { MostRecentFailure = _epoch - TimeSpan.FromSeconds(4), EscalationLevel = 3 });
|
||||
|
||||
Subject.RecordSuccess(1);
|
||||
Subject.RecordSuccess(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedIndexers().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
status.DisabledTill.Should().HaveValue();
|
||||
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(15), 500);
|
||||
VerifyNoUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
@@ -63,6 +64,35 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
|
||||
releaseInfo.Size.Should().Be(1183105773);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_newznab_animetosho()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_animetosho.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(1);
|
||||
|
||||
releases.First().Should().BeOfType<ReleaseInfo>();
|
||||
var releaseInfo = releases.First() as ReleaseInfo;
|
||||
|
||||
releaseInfo.Title.Should().Be("[HorribleSubs] Frame Arms Girl - 07 [720p].mkv");
|
||||
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
|
||||
releaseInfo.DownloadUrl.Should().Be("http://storage.localhost/nzb/123452.nzb");
|
||||
releaseInfo.InfoUrl.Should().Be("https://localhost/view/horriblesubs-frame-arms-girl-07-720p-mkv.123452");
|
||||
releaseInfo.CommentUrl.Should().Be("https://localhost/view/horriblesubs-frame-arms-girl-07-720p-mkv.123452");
|
||||
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
releaseInfo.PublishDate.Should().Be(DateTime.Parse("Mon, 15 May 2017 19:15:56 +0000").ToUniversalTime());
|
||||
releaseInfo.Size.Should().Be(473987489);
|
||||
releaseInfo.TvdbId.Should().Be(0);
|
||||
releaseInfo.TvRageId.Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_pagesize_reported_by_caps()
|
||||
{
|
||||
|
||||
@@ -254,13 +254,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
|
||||
}
|
||||
|
||||
[TestCase("BitMeTv/BitMeTv.xml")]
|
||||
[TestCase("Fanzub/fanzub.xml")]
|
||||
[TestCase("IPTorrents/IPTorrents.xml")]
|
||||
[TestCase("Newznab/newznab_nzb_su.xml")]
|
||||
[TestCase("Nyaa/Nyaa.xml")]
|
||||
[TestCase("Omgwtfnzbs/Omgwtfnzbs.xml")]
|
||||
[TestCase("Torznab/torznab_hdaccess_net.xml")]
|
||||
[TestCase("Torznab/torznab_tpb.xml")]
|
||||
[TestCase("Torznab/torznab_animetosho.xml")]
|
||||
public void should_detect_recent_feed(string rssXmlFile)
|
||||
{
|
||||
GivenRecentFeedResponse(rssXmlFile);
|
||||
|
||||
@@ -97,6 +97,37 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
|
||||
releaseInfo.Peers.Should().Be(36724);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_recent_feed_from_torznab_animetosho()
|
||||
{
|
||||
var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_animetosho.xml");
|
||||
|
||||
Mocker.GetMock<IHttpClient>()
|
||||
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
|
||||
|
||||
var releases = Subject.FetchRecent();
|
||||
|
||||
releases.Should().HaveCount(2);
|
||||
|
||||
releases.First().Should().BeOfType<TorrentInfo>();
|
||||
var releaseInfo = releases.First() as TorrentInfo;
|
||||
|
||||
releaseInfo.Title.Should().Be("[finFAGs]_Frame_Arms_Girl_07_(1280x720_TV_AAC)_[1262B6F7].mkv");
|
||||
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
|
||||
releaseInfo.DownloadUrl.Should().Be("http://storage.localhost/torrents/123451.torrent");
|
||||
releaseInfo.InfoUrl.Should().Be("https://localhost/view/finfags-_frame_arms_girl_07_-1280x720_tv_aac-_-1262b6f7-mkv.123451");
|
||||
releaseInfo.CommentUrl.Should().Be("https://localhost/view/finfags-_frame_arms_girl_07_-1280x720_tv_aac-_-1262b6f7-mkv.123451");
|
||||
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
|
||||
releaseInfo.PublishDate.Should().Be(DateTime.Parse("Wed, 17 May 2017 20:36:06 +0000").ToUniversalTime());
|
||||
releaseInfo.Size.Should().Be(316477946);
|
||||
releaseInfo.TvdbId.Should().Be(0);
|
||||
releaseInfo.TvRageId.Should().Be(0);
|
||||
releaseInfo.InfoHash.Should().Be("2d69a861bef5a9f2cdf791b7328e37b7953205e1");
|
||||
releaseInfo.Seeders.Should().BeNull();
|
||||
releaseInfo.Peers.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_pagesize_reported_by_caps()
|
||||
{
|
||||
|
||||
@@ -164,11 +164,9 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
Mocker.GetMock<IDetectSample>()
|
||||
.Setup(s => s.IsSample(It.IsAny<Series>(),
|
||||
It.IsAny<QualityModel>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<long>(),
|
||||
It.IsAny<bool>()))
|
||||
.Returns(true);
|
||||
.Returns(DetectSampleResult.Sample);
|
||||
|
||||
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
|
||||
|
||||
@@ -236,11 +234,9 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
Mocker.GetMock<IDetectSample>()
|
||||
.Setup(s => s.IsSample(It.IsAny<Series>(),
|
||||
It.IsAny<QualityModel>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<long>(),
|
||||
It.IsAny<bool>()))
|
||||
.Returns(true);
|
||||
.Returns(DetectSampleResult.Sample);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetFiles(It.IsAny<string>(), SearchOption.AllDirectories))
|
||||
@@ -347,11 +343,9 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||
|
||||
Mocker.GetMock<IDetectSample>()
|
||||
.Setup(s => s.IsSample(It.IsAny<Series>(),
|
||||
It.IsAny<QualityModel>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<long>(),
|
||||
It.IsAny<bool>()))
|
||||
.Returns(true);
|
||||
.Returns(DetectSampleResult.Sample);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetFileSize(It.IsAny<string>()))
|
||||
|
||||
@@ -10,11 +10,12 @@ using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
{
|
||||
[TestFixture]
|
||||
public class SampleServiceFixture : CoreTest<DetectSample>
|
||||
public class DetectSampleFixture : CoreTest<DetectSample>
|
||||
{
|
||||
private Series _series;
|
||||
private LocalEpisode _localEpisode;
|
||||
@@ -42,11 +43,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
};
|
||||
}
|
||||
|
||||
private void GivenFileSize(long size)
|
||||
{
|
||||
_localEpisode.Size = size;
|
||||
}
|
||||
|
||||
private void GivenRuntime(int seconds)
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
@@ -58,7 +54,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
public void should_return_false_if_season_zero()
|
||||
{
|
||||
_localEpisode.Episodes[0].SeasonNumber = 0;
|
||||
ShouldBeFalse();
|
||||
ShouldBeNotSample();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -66,7 +62,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
{
|
||||
_localEpisode.Path = @"C:\Test\some.show.s01e01.flv";
|
||||
|
||||
ShouldBeFalse();
|
||||
ShouldBeNotSample();
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
@@ -76,7 +72,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
{
|
||||
_localEpisode.Path = @"C:\Test\some.show.s01e01.strm";
|
||||
|
||||
ShouldBeFalse();
|
||||
ShouldBeNotSample();
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(c => c.GetRunTime(It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
@@ -85,12 +81,9 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
public void should_use_runtime()
|
||||
{
|
||||
GivenRuntime(120);
|
||||
GivenFileSize(1000.Megabytes());
|
||||
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
_localEpisode.Quality,
|
||||
_localEpisode.Path,
|
||||
_localEpisode.Size,
|
||||
_localEpisode.IsSpecial);
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>().Verify(v => v.GetRunTime(It.IsAny<string>()), Times.Once());
|
||||
@@ -101,7 +94,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
{
|
||||
GivenRuntime(60);
|
||||
|
||||
ShouldBeTrue();
|
||||
ShouldBeSample();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -109,7 +102,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
{
|
||||
GivenRuntime(600);
|
||||
|
||||
ShouldBeFalse();
|
||||
ShouldBeNotSample();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -118,7 +111,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
_series.Runtime = 6;
|
||||
GivenRuntime(299);
|
||||
|
||||
ShouldBeFalse();
|
||||
ShouldBeNotSample();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -127,7 +120,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
_series.Runtime = 2;
|
||||
GivenRuntime(60);
|
||||
|
||||
ShouldBeFalse();
|
||||
ShouldBeNotSample();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -136,29 +129,21 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
_series.Runtime = 2;
|
||||
GivenRuntime(10);
|
||||
|
||||
ShouldBeTrue();
|
||||
ShouldBeSample();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_acceptable_size()
|
||||
public void should_return_indeterminate_if_mediainfo_result_is_null()
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(s => s.GetRunTime(It.IsAny<string>()))
|
||||
.Throws<DllNotFoundException>();
|
||||
.Returns((TimeSpan?)null);
|
||||
|
||||
GivenFileSize(1000.Megabytes());
|
||||
ShouldBeFalse();
|
||||
}
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
_localEpisode.Path,
|
||||
_localEpisode.IsSpecial).Should().Be(DetectSampleResult.Indeterminate);
|
||||
|
||||
[Test]
|
||||
public void should_fall_back_to_file_size_if_mediainfo_dll_not_found_undersize()
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(s => s.GetRunTime(It.IsAny<string>()))
|
||||
.Throws<DllNotFoundException>();
|
||||
|
||||
GivenFileSize(1.Megabytes());
|
||||
ShouldBeTrue();
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -167,7 +152,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
GivenRuntime(600);
|
||||
_series.SeriesType = SeriesTypes.Daily;
|
||||
_localEpisode.Episodes[0].SeasonNumber = 0;
|
||||
ShouldBeFalse();
|
||||
ShouldBeNotSample();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -176,25 +161,21 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
_series.SeriesType = SeriesTypes.Anime;
|
||||
_localEpisode.Episodes[0].SeasonNumber = 0;
|
||||
|
||||
ShouldBeFalse();
|
||||
ShouldBeNotSample();
|
||||
}
|
||||
|
||||
private void ShouldBeTrue()
|
||||
private void ShouldBeSample()
|
||||
{
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
_localEpisode.Quality,
|
||||
_localEpisode.Path,
|
||||
_localEpisode.Size,
|
||||
_localEpisode.IsSpecial).Should().BeTrue();
|
||||
}
|
||||
|
||||
private void ShouldBeFalse()
|
||||
{
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
_localEpisode.Quality,
|
||||
_localEpisode.Path,
|
||||
_localEpisode.Size,
|
||||
_localEpisode.IsSpecial).Should().BeFalse();
|
||||
_localEpisode.IsSpecial).Should().Be(DetectSampleResult.Sample);
|
||||
}
|
||||
|
||||
private void ShouldBeNotSample()
|
||||
{
|
||||
Subject.IsSample(_localEpisode.Series,
|
||||
_localEpisode.Path,
|
||||
_localEpisode.IsSpecial).Should().Be(DetectSampleResult.NotSample);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,8 +333,16 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
|
||||
GivenVideoFiles(videoFiles.ToList());
|
||||
|
||||
Mocker.GetMock<IDetectSample>()
|
||||
.Setup(s => s.IsSample(_series, It.IsAny<QualityModel>(), It.Is<string>(c => c.Contains("sample")), It.IsAny<long>(), It.IsAny<bool>()))
|
||||
.Returns(true);
|
||||
.Setup(s => s.IsSample(_series, It.IsAny<string>(), It.IsAny<bool>()))
|
||||
.Returns((Series s, string path, bool special) =>
|
||||
{
|
||||
if (path.Contains("sample"))
|
||||
{
|
||||
return DetectSampleResult.Sample;
|
||||
}
|
||||
|
||||
return DetectSampleResult.NotSample;
|
||||
});
|
||||
|
||||
var folderInfo = Parser.Parser.ParseTitle("Series.Title.S01E01");
|
||||
|
||||
|
||||
@@ -63,6 +63,35 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
|
||||
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_accepted_if_grabbed_history_is_for_a_season_pack()
|
||||
{
|
||||
var history = Builder<History.History>.CreateListOfSize(1)
|
||||
.All()
|
||||
.With(h => h.EventType = HistoryEventType.Grabbed)
|
||||
.With(h => h.Quality = _localEpisode.Quality)
|
||||
.With(h => h.SourceTitle = "Series.Title.S01.720p.HDTV.x264-RlsGroup")
|
||||
.BuildList();
|
||||
|
||||
GivenHistory(history);
|
||||
|
||||
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_accepted_if_grabbed_history_quality_is_unknown()
|
||||
{
|
||||
var history = Builder<History.History>.CreateListOfSize(1)
|
||||
.All()
|
||||
.With(h => h.EventType = HistoryEventType.Grabbed)
|
||||
.With(h => h.Quality = new QualityModel(Quality.Unknown))
|
||||
.BuildList();
|
||||
|
||||
GivenHistory(history);
|
||||
|
||||
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_accepted_if_grabbed_history_quality_matches()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.IO;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaFileDeletionService
|
||||
{
|
||||
[TestFixture]
|
||||
public class DeleteEpisodeFileFixture : CoreTest<Core.MediaFiles.MediaFileDeletionService>
|
||||
{
|
||||
private static readonly string RootFolder = @"C:\Test\TV";
|
||||
private Series _series;
|
||||
private EpisodeFile _episodeFile;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>.CreateNew()
|
||||
.With(s => s.Path = Path.Combine(RootFolder, "Series Title"))
|
||||
.Build();
|
||||
|
||||
_episodeFile = Builder<EpisodeFile>.CreateNew()
|
||||
.With(f => f.RelativePath = "Series Title - S01E01")
|
||||
.With(f => f.Path = Path.Combine(_series.Path, "Series Title - S01E01"))
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetParentFolder(_series.Path))
|
||||
.Returns(RootFolder);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetParentFolder(_episodeFile.Path))
|
||||
.Returns(_series.Path);
|
||||
}
|
||||
|
||||
private void GivenRootFolderExists()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.FolderExists(RootFolder))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void GivenRootFolderHasFolders()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.GetDirectories(RootFolder))
|
||||
.Returns(new[] { _series.Path });
|
||||
}
|
||||
|
||||
private void GivenSeriesFolderExists()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.FolderExists(_series.Path))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_throw_if_root_folder_does_not_exist()
|
||||
{
|
||||
Assert.Throws<NzbDroneClientException>(() => Subject.DeleteEpisodeFile(_series, _episodeFile));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_should_throw_if_root_folder_is_empty()
|
||||
{
|
||||
GivenRootFolderExists();
|
||||
Assert.Throws<NzbDroneClientException>(() => Subject.DeleteEpisodeFile(_series, _episodeFile));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_from_db_if_series_folder_does_not_exist()
|
||||
{
|
||||
GivenRootFolderExists();
|
||||
GivenRootFolderHasFolders();
|
||||
|
||||
Subject.DeleteEpisodeFile(_series, _episodeFile);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Delete(_episodeFile, DeleteMediaFileReason.Manual), Times.Once());
|
||||
Mocker.GetMock<IRecycleBinProvider>().Verify(v => v.DeleteFile(_episodeFile.Path, It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_from_db_if_episode_file_does_not_exist()
|
||||
{
|
||||
GivenRootFolderExists();
|
||||
GivenRootFolderHasFolders();
|
||||
GivenSeriesFolderExists();
|
||||
|
||||
Subject.DeleteEpisodeFile(_series, _episodeFile);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Delete(_episodeFile, DeleteMediaFileReason.Manual), Times.Once());
|
||||
Mocker.GetMock<IRecycleBinProvider>().Verify(v => v.DeleteFile(_episodeFile.Path, It.IsAny<string>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_delete_from_disk_and_db_if_episode_file_exists()
|
||||
{
|
||||
GivenRootFolderExists();
|
||||
GivenRootFolderHasFolders();
|
||||
GivenSeriesFolderExists();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.FileExists(_episodeFile.Path))
|
||||
.Returns(true);
|
||||
|
||||
Subject.DeleteEpisodeFile(_series, _episodeFile);
|
||||
|
||||
Mocker.GetMock<IRecycleBinProvider>().Verify(v => v.DeleteFile(_episodeFile.Path, "Series Title"), Times.Once());
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Delete(_episodeFile, DeleteMediaFileReason.Manual), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_handle_error_deleting_episode_file()
|
||||
{
|
||||
GivenRootFolderExists();
|
||||
GivenRootFolderHasFolders();
|
||||
GivenSeriesFolderExists();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.FileExists(_episodeFile.Path))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IRecycleBinProvider>()
|
||||
.Setup(s => s.DeleteFile(_episodeFile.Path, "Series Title"))
|
||||
.Throws(new IOException());
|
||||
|
||||
Assert.Throws<NzbDroneClientException>(() => Subject.DeleteEpisodeFile(_series, _episodeFile));
|
||||
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
Mocker.GetMock<IRecycleBinProvider>().Verify(v => v.DeleteFile(_episodeFile.Path, "Series Title"), Times.Once());
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Delete(_episodeFile, DeleteMediaFileReason.Manual), Times.Never());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FormattedAudioChannelsFixture
|
||||
public class FormatAudioChannelsFixture : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void should_subtract_one_from_AudioChannels_as_total_channels_if_LFE_in_AudioChannelPositionsText()
|
||||
@@ -17,7 +18,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
AudioChannelPositionsText = "Front: L C R, Side: L R, LFE"
|
||||
};
|
||||
|
||||
mediaInfoModel.FormattedAudioChannels.Should().Be(5.1m);
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -30,7 +31,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
AudioChannelPositionsText = "Front: L R"
|
||||
};
|
||||
|
||||
mediaInfoModel.FormattedAudioChannels.Should().Be(2);
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -44,7 +45,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
SchemaRevision = 2
|
||||
};
|
||||
|
||||
mediaInfoModel.FormattedAudioChannels.Should().Be(0);
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -58,7 +59,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
mediaInfoModel.FormattedAudioChannels.Should().Be(2);
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -72,7 +73,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
mediaInfoModel.FormattedAudioChannels.Should().Be(2);
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -86,7 +87,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
mediaInfoModel.FormattedAudioChannels.Should().Be(5.1m);
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -100,7 +101,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
mediaInfoModel.FormattedAudioChannels.Should().Be(7.1m);
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -114,7 +115,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
mediaInfoModel.FormattedAudioChannels.Should().Be(7.1m);
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FormatAudioCodecFixture : TestBase
|
||||
{
|
||||
[TestCase("AC-3", "AC3")]
|
||||
[TestCase("E-AC-3", "EAC3")]
|
||||
[TestCase("MPEG Audio", "MPEG Audio")]
|
||||
[TestCase("DTS", "DTS")]
|
||||
public void should_format_audio_format(string audioFormat, string expectedFormat)
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = audioFormat
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(expectedFormat);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = "MPEG Audio",
|
||||
AudioProfile = "Layer 3"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be("MP3");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_AudioFormat_by_default()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = "Other Audio Format"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(mediaInfoModel.AudioFormat);
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FormatVideoCodecFixture : TestBase
|
||||
{
|
||||
[TestCase("AVC", null, "x264")]
|
||||
[TestCase("AVC", "source.title.x264.720p-Sonarr", "x264")]
|
||||
[TestCase("AVC", "source.title.h264.720p-Sonarr", "h264")]
|
||||
[TestCase("V_MPEGH/ISO/HEVC", null, "x265")]
|
||||
[TestCase("V_MPEGH/ISO/HEVC", "source.title.x265.720p-Sonarr", "x265")]
|
||||
[TestCase("V_MPEGH/ISO/HEVC", "source.title.h265.720p-Sonarr", "h265")]
|
||||
[TestCase("MPEG-2 Video", null, "MPEG2")]
|
||||
public void should_format_video_codec_with_source_title(string videoCodec, string sceneName, string expectedFormat)
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
VideoCodec = videoCodec
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, sceneName).Should().Be(expectedFormat);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_VideoCodec_by_default()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
VideoCodec = "VideoCodec"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatVideoCodec(mediaInfoModel, null).Should().Be(mediaInfoModel.VideoCodec);
|
||||
ExceptionVerification.ExpectedErrors(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -30,11 +30,9 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
{
|
||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
Subject.GetRunTime(path).Seconds.Should().Be(10);
|
||||
|
||||
Subject.GetRunTime(path).Value.Seconds.Should().Be(10);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void get_info()
|
||||
{
|
||||
@@ -86,7 +84,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
info.VideoCodec.Should().Be("AVC");
|
||||
info.VideoFps.Should().Be(24);
|
||||
info.Width.Should().Be(480);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||
{
|
||||
series.Should().NotBeNull();
|
||||
series.Title.Should().NotBeNullOrWhiteSpace();
|
||||
series.CleanTitle.Should().Be(Parser.Parser.CleanSeriesTitle(series.Title));
|
||||
series.CleanTitle.Should().Be(Parser.NormalizeParsedTitle.CleanSeriesTitle(series.Title));
|
||||
series.SortTitle.Should().Be(SeriesTitleNormalizer.Normalize(series.Title, series.TvdbId));
|
||||
series.Overview.Should().NotBeNullOrWhiteSpace();
|
||||
series.AirTime.Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
@@ -161,9 +161,11 @@
|
||||
<Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" />
|
||||
<Compile Include="DecisionEngineTests\RssSync\DeletedEpisodeFileSpecificationFixture.cs" />
|
||||
<Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />
|
||||
<Compile Include="DecisionEngineTests\Search\TorrentSeedingSpecificationFixture.cs" />
|
||||
<Compile Include="DecisionEngineTests\Search\SeriesSpecificationFixture.cs" />
|
||||
<Compile Include="DecisionEngineTests\SameEpisodesSpecificationFixture.cs" />
|
||||
<Compile Include="DecisionEngineTests\RawDiskSpecificationFixture.cs" />
|
||||
<Compile Include="DecisionEngineTests\BlockedIndexerSpecificationFixture.cs" />
|
||||
<Compile Include="DecisionEngineTests\UpgradeDiskSpecificationFixture.cs" />
|
||||
<Compile Include="DiskSpace\DiskSpaceServiceFixture.cs" />
|
||||
<Compile Include="Download\CompletedDownloadServiceFixture.cs" />
|
||||
@@ -178,6 +180,7 @@
|
||||
<Compile Include="Download\DownloadClientTests\DownloadStationTests\SharedFolderResolverFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\DownloadStationTests\UsenetDownloadStationFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\HadoukenTests\HadoukenFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientStatusServiceFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\NzbgetTests\NzbgetFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\NzbVortexTests\NzbVortexFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\PneumaticProviderFixture.cs" />
|
||||
@@ -235,6 +238,7 @@
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedIndexerStatusFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupDownloadClientUnavailablePendingReleasesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupUnusedTagsFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleasesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasksFixture.cs" />
|
||||
@@ -280,7 +284,7 @@
|
||||
<Compile Include="MediaFiles\DownloadedEpisodesImportServiceFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeFileMovingServiceTests\MoveEpisodeFileFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMakerFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\SampleServiceFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\DetectSampleFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecificationFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\SameFileSpecificationFixture.cs" />
|
||||
@@ -290,8 +294,11 @@
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecificationFixture.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" />
|
||||
<Compile Include="MediaFiles\ImportApprovedEpisodesFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaFileDeletionService\DeleteEpisodeFileFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\FormattedAudioChannelsFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioCodecFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatVideoCodecFixture.cs" />
|
||||
<Compile Include="MediaFiles\MediaInfo\MediaInfoFormatterTests\FormatAudioChannelsFixture.cs" />
|
||||
<Compile Include="Messaging\Commands\CommandQueueManagerFixture.cs" />
|
||||
<Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" />
|
||||
<Compile Include="MetadataSource\SearchSeriesComparerFixture.cs" />
|
||||
@@ -301,6 +308,8 @@
|
||||
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
|
||||
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
|
||||
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
|
||||
<Compile Include="OrganizerTests\FileNameBuilderTests\TitleTheFixture.cs" />
|
||||
<Compile Include="OrganizerTests\NormalizeOfficialTitleFixture.cs" />
|
||||
<Compile Include="ParserTests\MiniSeriesEpisodeParserFixture.cs" />
|
||||
<Compile Include="Qualities\RevisionComparableFixture.cs" />
|
||||
<Compile Include="QueueTests\QueueServiceFixture.cs" />
|
||||
@@ -366,7 +375,8 @@
|
||||
<Compile Include="Qualities\QualityModelComparerFixture.cs" />
|
||||
<Compile Include="RootFolderTests\RootFolderServiceFixture.cs" />
|
||||
<Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" />
|
||||
<Compile Include="ThingiProvider\ProviderBaseFixture.cs" />
|
||||
<Compile Include="ThingiProviderTests\ProviderStatusServiceFixture.cs" />
|
||||
<Compile Include="ThingiProviderTests\ProviderBaseFixture.cs" />
|
||||
<Compile Include="ThingiProviderTests\NullConfigFixture.cs" />
|
||||
<Compile Include="TvTests\EpisodeServiceTests\FindEpisodeByTitleFixture.cs" />
|
||||
<Compile Include="TvTests\EpisodeServiceTests\HandleEpisodeFileDeletedFixture.cs" />
|
||||
@@ -430,6 +440,9 @@
|
||||
<Content Include="Files\Indexers\TorrentRss\LimeTorrents.xml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Files\Indexers\Torznab\torznab_animetosho.xml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="License.txt" />
|
||||
<None Include="Files\Indexers\BroadcastheNet\RecentFeed.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
|
||||
@@ -16,4 +16,4 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TitleTheFixture : CoreTest<FileNameBuilder>
|
||||
{
|
||||
private Series _series;
|
||||
private Episode _episode;
|
||||
private EpisodeFile _episodeFile;
|
||||
private NamingConfig _namingConfig;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = "South Park")
|
||||
.Build();
|
||||
|
||||
_episode = Builder<Episode>.CreateNew()
|
||||
.With(e => e.Title = "City Sushi")
|
||||
.With(e => e.SeasonNumber = 15)
|
||||
.With(e => e.EpisodeNumber = 6)
|
||||
.With(e => e.AbsoluteEpisodeNumber = 100)
|
||||
.Build();
|
||||
|
||||
_episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "SonarrTest" };
|
||||
|
||||
_namingConfig = NamingConfig.Default;
|
||||
_namingConfig.RenameEpisodes = true;
|
||||
|
||||
Mocker.GetMock<INamingConfigService>()
|
||||
.Setup(c => c.GetConfig()).Returns(_namingConfig);
|
||||
|
||||
Mocker.GetMock<IQualityDefinitionService>()
|
||||
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
|
||||
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
|
||||
}
|
||||
|
||||
[TestCase("The Mist", "Mist, The")]
|
||||
[TestCase("A Place to Call Home", "Place to Call Home, A")]
|
||||
[TestCase("An Adventure in Space and Time", "Adventure in Space and Time, An")]
|
||||
[TestCase("The Flash (2010)", "Flash, The (2010)")]
|
||||
[TestCase("A League Of Their Own (AU)", "League Of Their Own, A (AU)")]
|
||||
[TestCase("The Fixer (ZH) (2015)", "Fixer, The (ZH) (2015)")]
|
||||
[TestCase("The Sixth Sense 2 (Thai)", "Sixth Sense 2, The (Thai)")]
|
||||
[TestCase("The Amazing Race (Latin America)", "Amazing Race, The (Latin America)")]
|
||||
[TestCase("The Rat Pack (A&E)", "Rat Pack, The (A&E)")]
|
||||
[TestCase("The Climax: I (Almost) Got Away With It (2016)", "Climax- I (Almost) Got Away With It, The (2016)")]
|
||||
//[TestCase("", "")]
|
||||
public void should_get_expected_title_back(string title, string expected)
|
||||
{
|
||||
_series.Title = title;
|
||||
_namingConfig.StandardEpisodeFormat = "{Series TitleThe}";
|
||||
|
||||
Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("A")]
|
||||
[TestCase("Anne")]
|
||||
[TestCase("Theodore")]
|
||||
[TestCase("3%")]
|
||||
public void should_not_change_title(string title)
|
||||
{
|
||||
_series.Title = title;
|
||||
_namingConfig.StandardEpisodeFormat = "{Series TitleThe}";
|
||||
|
||||
Subject.BuildFileName(new List<Episode> { _episode }, _series, _episodeFile)
|
||||
.Should().Be(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.OrganizerTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class NormalizeOfficialTitleFixture : CoreTest
|
||||
{
|
||||
[TestCase("$#*! My Dad Says", "S#* My Dad Says")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_special_cases(string title, string expected)
|
||||
{
|
||||
// These need special handling on a case by case basis.
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("@midnight", "At midnight")]
|
||||
[TestCase("Murder @ 9", "Murder at 9")]
|
||||
[TestCase("T@gged", "Tagged")]
|
||||
[TestCase("PUCHIM@S", "PUCHIMAS")]
|
||||
[TestCase("extr@", "extra")]
|
||||
[TestCase("Live@Much", "Live at Much")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_at_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("3%", "3 Percent")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_percent_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Law & Order (UK)", "Law and Order UK")]
|
||||
[TestCase("Sun, Sea and A&E", "Sun Sea and A and E")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_and_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Code:Breaker", "Code Breaker")]
|
||||
[TestCase("Transformers: Prime", "Transformers Prime")]
|
||||
[TestCase("Mobile Suit Gundam UC RE:0096", "Mobile Suit Gundam UC RE 0096")]
|
||||
[TestCase("What the Bleep!?: Down the Rabbit Hole", "What the Bleep Down the Rabbit Hole")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_colon_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Sun, Sea and A&E", "Sun Sea and A and E")]
|
||||
[TestCase("The $25,000 Pyramid", "The 25000 Pyramid")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_comma_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
//[TestCase("The $100,000 Pyramid", "The 100000 Dollar Pyramid")]
|
||||
[TestCase("$25 Million Dollar Hoax", "25 Million Dollar Hoax")]
|
||||
[TestCase("Arli$$", "Arliss")]
|
||||
[TestCase("Country Buck$", "Country Bucks")]
|
||||
[TestCase("Tamara Ecclestone: Billion $$ Girl", "Tamara Ecclestone Billion Dollar Girl")]
|
||||
[TestCase("$#*! My Dad Says", "S#* My Dad Says")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_dollar_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Separation?!", "Separation")]
|
||||
[TestCase("Snog Marry Avoid?", "Snog Marry Avoid")]
|
||||
[TestCase("What the Bleep!?: Down the Rabbit Hole", "What the Bleep Down the Rabbit Hole")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_question_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Separation?!", "Separation")]
|
||||
[TestCase("What the Bleep!?: Down the Rabbit Hole", "What the Bleep Down the Rabbit Hole")]
|
||||
[TestCase("What's Happening!!", "Whats Happening")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_exclamation_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Bro'Town", "Bro Town")]
|
||||
[TestCase("'Til Death", "Til Death")]
|
||||
[TestCase("Those Who Can't", "Those Who Cant")]
|
||||
[TestCase("Paul O'Grady: For the Love of Dogs", "Paul O Grady For the Love of Dogs")]
|
||||
[TestCase("Bitchin' Rides", "Bitchin Rides")]
|
||||
[TestCase("Trust Me, I'm a Vet", "Trust Me Im a Vet")]
|
||||
[TestCase("You're the Worst", "Youre the Worst")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_quote_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Robotics;Notes", "Robotics Notes")]
|
||||
[TestCase("Myself; Yourself", "Myself Yourself")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_semicolon_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("Acquisitions Incorporated: The \"C\" Team", "Acquisitions Incorporated The C Team")]
|
||||
//[TestCase("", "")]
|
||||
public void should_scenify_doublequote_char(string title, string expected)
|
||||
{
|
||||
NormalizeOfficialTitle.ScenifyTitle(title).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,5 +134,23 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
{
|
||||
"Tokyo Ghoul A".CleanSeriesTitle().Should().Be("tokyoghoula");
|
||||
}
|
||||
|
||||
[TestCase("A 120% deal", "a120percentdeal")]
|
||||
[TestCase("The z0%e", "thez0e")]
|
||||
[TestCase("That f$%king mess", "thatfkingmess")]
|
||||
public void should_replace_percentage_character(string title, string normalizedTitle)
|
||||
{
|
||||
title.CleanSeriesTitle().Should().Be(normalizedTitle);
|
||||
}
|
||||
|
||||
[TestCase("@midnight", "atmidnight")]
|
||||
[TestCase("Murder @ 9", "murderat9")]
|
||||
[TestCase("T@gged", "tagged")]
|
||||
[TestCase("PUCHIM@S", "puchimas")]
|
||||
[TestCase("Live@Much", "liveamuch")] // liveatmuch
|
||||
public void should_replace_at_character(string title, string normalizedTitle)
|
||||
{
|
||||
title.CleanSeriesTitle().Should().Be(normalizedTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
|
||||
Subject.Validate().IsValid.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Indexers.Newznab;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.ThingiProvider
|
||||
namespace NzbDrone.Core.Test.ThingiProviderTests
|
||||
{
|
||||
|
||||
public class ProviderRepositoryFixture : DbTest<IndexerRepository, IndexerDefinition>
|
||||
{
|
||||
[Test]
|
||||
@@ -27,4 +26,4 @@ namespace NzbDrone.Core.Test.ThingiProvider
|
||||
storedSetting.ShouldBeEquivalentTo(newznabSettings, o=>o.IncludingAllRuntimeProperties());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NLog;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.ThingiProvider.Status;
|
||||
|
||||
namespace NzbDrone.Core.Test.ThingiProviderTests
|
||||
{
|
||||
public class MockProviderStatus : ProviderStatusBase
|
||||
{
|
||||
}
|
||||
|
||||
public interface IMockProvider : IProvider
|
||||
{
|
||||
}
|
||||
|
||||
public interface IMockProviderStatusRepository : IProviderStatusRepository<MockProviderStatus>
|
||||
{
|
||||
}
|
||||
|
||||
public class MockProviderStatusService : ProviderStatusServiceBase<IMockProvider, MockProviderStatus>
|
||||
{
|
||||
public MockProviderStatusService(IMockProviderStatusRepository providerStatusRepository, IEventAggregator eventAggregator, Logger logger)
|
||||
: base(providerStatusRepository, eventAggregator, logger)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class ProviderStatusServiceFixture : CoreTest<MockProviderStatusService>
|
||||
{
|
||||
private DateTime _epoch;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_epoch = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private void WithStatus(MockProviderStatus status)
|
||||
{
|
||||
Mocker.GetMock<IMockProviderStatusRepository>()
|
||||
.Setup(v => v.FindByProviderId(1))
|
||||
.Returns(status);
|
||||
|
||||
Mocker.GetMock<IMockProviderStatusRepository>()
|
||||
.Setup(v => v.All())
|
||||
.Returns(new[] { status });
|
||||
}
|
||||
|
||||
private void VerifyUpdate()
|
||||
{
|
||||
Mocker.GetMock<IMockProviderStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<MockProviderStatus>()), Times.Once());
|
||||
}
|
||||
|
||||
private void VerifyNoUpdate()
|
||||
{
|
||||
Mocker.GetMock<IMockProviderStatusRepository>()
|
||||
.Verify(v => v.Upsert(It.IsAny<MockProviderStatus>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_start_backoff_on_first_failure()
|
||||
{
|
||||
WithStatus(new MockProviderStatus());
|
||||
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
status.DisabledTill.Should().HaveValue();
|
||||
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_cancel_backoff_on_success()
|
||||
{
|
||||
WithStatus(new MockProviderStatus { EscalationLevel = 2 });
|
||||
|
||||
Subject.RecordSuccess(1);
|
||||
|
||||
VerifyUpdate();
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_store_update_if_already_okay()
|
||||
{
|
||||
WithStatus(new MockProviderStatus { EscalationLevel = 0 });
|
||||
|
||||
Subject.RecordSuccess(1);
|
||||
|
||||
VerifyNoUpdate();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_preserve_escalation_on_intermittent_success()
|
||||
{
|
||||
WithStatus(new MockProviderStatus
|
||||
{
|
||||
InitialFailure = _epoch - TimeSpan.FromSeconds(20),
|
||||
MostRecentFailure = _epoch - TimeSpan.FromSeconds(4),
|
||||
EscalationLevel = 3
|
||||
});
|
||||
|
||||
Subject.RecordSuccess(1);
|
||||
Subject.RecordSuccess(1);
|
||||
Subject.RecordFailure(1);
|
||||
|
||||
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||
status.Should().NotBeNull();
|
||||
status.DisabledTill.Should().HaveValue();
|
||||
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(15), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,46 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests
|
||||
.Verify(v => v.UpdateEpisodes(It.Is<List<Episode>>(l => l.All(e => !e.Monitored))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_should_not_monitor_episodes_if_season_is_not_monitored()
|
||||
{
|
||||
_series = Builder<Series>.CreateNew()
|
||||
.With(s => s.Seasons = Builder<Season>.CreateListOfSize(2)
|
||||
.TheFirst(1)
|
||||
.With(n => n.Monitored = true)
|
||||
.TheLast(1)
|
||||
.With(n => n.Monitored = false)
|
||||
.Build()
|
||||
.ToList())
|
||||
.Build();
|
||||
|
||||
var episodes = Builder<Episode>.CreateListOfSize(10)
|
||||
.All()
|
||||
.With(e => e.Monitored = true)
|
||||
.With(e => e.EpisodeFileId = 0)
|
||||
.With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-7))
|
||||
.TheFirst(5)
|
||||
.With(e => e.SeasonNumber = 1)
|
||||
.TheLast(5)
|
||||
.With(e => e.SeasonNumber = 2)
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Setup(s => s.GetEpisodeBySeries(It.IsAny<int>()))
|
||||
.Returns(episodes);
|
||||
|
||||
Subject.SetEpisodeMonitoredStatus(_series, new MonitoringOptions
|
||||
{
|
||||
IgnoreEpisodesWithFiles = true,
|
||||
IgnoreEpisodesWithoutFiles = false
|
||||
});
|
||||
|
||||
VerifyMonitored(e => e.SeasonNumber == 1);
|
||||
VerifyNotMonitored(e => e.SeasonNumber == 2);
|
||||
VerifySeasonMonitored(s => s.SeasonNumber == 1);
|
||||
VerifySeasonNotMonitored(s => s.SeasonNumber == 2);
|
||||
}
|
||||
|
||||
private void VerifyMonitored(Func<Episode, bool> predicate)
|
||||
{
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -142,7 +142,21 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public bool LaunchBrowser => GetValueBoolean("LaunchBrowser", true);
|
||||
|
||||
public string ApiKey => GetValue("ApiKey", GenerateApiKey());
|
||||
public string ApiKey
|
||||
{
|
||||
get
|
||||
{
|
||||
var apiKey = GetValue("ApiKey", GenerateApiKey());
|
||||
|
||||
if (apiKey.IsNullOrWhiteSpace())
|
||||
{
|
||||
apiKey = GenerateApiKey();
|
||||
SetValue("ApiKey", apiKey);
|
||||
}
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticationType AuthenticationMethod
|
||||
{
|
||||
|
||||
@@ -61,7 +61,15 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
||||
|
||||
if (episode == null)
|
||||
{
|
||||
_logger.Debug("Information hasn't been added to TheTVDB yet, skipping.");
|
||||
_logger.Debug("Information hasn't been added to TheTVDB yet, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mapping.Scene.Absolute == 0 &&
|
||||
mapping.Scene.Season == 0 &&
|
||||
mapping.Scene.Episode == 0)
|
||||
{
|
||||
_logger.Debug("Mapping for {0} S{1:00}E{2:00} is invalid, skipping", series, mapping.Tvdb.Season, mapping.Tvdb.Episode);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||
var id = seriesReader.GetInt32(0);
|
||||
var title = seriesReader.GetString(1);
|
||||
|
||||
var sortTitle = Parser.Parser.NormalizeTitle(title).ToLower();
|
||||
var sortTitle = Parser.NormalizeParsedTitle.NormalizeTitle(title).ToLower();
|
||||
|
||||
using (IDbCommand updateCmd = conn.CreateCommand())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(97)]
|
||||
public class add_reason_to_pending_releases : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("PendingReleases").AddColumn("Reason").AsInt32().WithDefaultValue(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(115)]
|
||||
public class add_downloadclient_status : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Create.TableForModel("DownloadClientStatus")
|
||||
.WithColumn("ProviderId").AsInt32().NotNullable().Unique()
|
||||
.WithColumn("InitialFailure").AsDateTime().Nullable()
|
||||
.WithColumn("MostRecentFailure").AsDateTime().Nullable()
|
||||
.WithColumn("EscalationLevel").AsInt32().NotNullable()
|
||||
.WithColumn("DisabledTill").AsDateTime().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/NzbDrone.Core/Datastore/Migration/116_disable_nyaa.cs
Normal file
14
src/NzbDrone.Core/Datastore/Migration/116_disable_nyaa.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(116)]
|
||||
public class disable_nyaa : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.Sql("UPDATE Indexers SET EnableRss = 0, EnableSearch = 0, Settings = Replace(Settings, 'https://nyaa.se', '') WHERE Implementation = 'Nyaa' AND Settings LIKE '%nyaa.se%';");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Datastore
|
||||
.Ignore(i => i.SupportsOnDownload)
|
||||
.Ignore(i => i.SupportsOnUpgrade)
|
||||
.Ignore(i => i.SupportsOnRename);
|
||||
|
||||
|
||||
Mapper.Entity<MetadataDefinition>().RegisterDefinition("Metadata");
|
||||
|
||||
Mapper.Entity<DownloadClientDefinition>().RegisterDefinition("DownloadClients")
|
||||
@@ -80,7 +80,7 @@ namespace NzbDrone.Core.Datastore
|
||||
.Ignore(f => f.Path)
|
||||
.Relationships.AutoMapICollectionOrComplexProperties()
|
||||
.For("Episodes")
|
||||
.LazyLoad(condition: parent => parent.Id > 0,
|
||||
.LazyLoad(condition: parent => parent.Id > 0,
|
||||
query: (db, parent) => db.Query<Episode>().Where(c => c.EpisodeFileId == parent.Id).ToList())
|
||||
.HasOne(file => file.Series, file => file.SeriesId);
|
||||
|
||||
@@ -116,6 +116,7 @@ namespace NzbDrone.Core.Datastore
|
||||
.Ignore(c => c.Message);
|
||||
|
||||
Mapper.Entity<IndexerStatus>().RegisterModel("IndexerStatus");
|
||||
Mapper.Entity<DownloadClientStatus>().RegisterModel("DownloadClientStatus");
|
||||
}
|
||||
|
||||
private static void RegisterMappers()
|
||||
@@ -171,4 +172,4 @@ namespace NzbDrone.Core.Datastore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class BlockedIndexerSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IIndexerStatusService _indexerStatusService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly ICachedDictionary<IndexerStatus> _blockedIndexerCache;
|
||||
|
||||
public BlockedIndexerSpecification(IIndexerStatusService indexerStatusService, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_indexerStatusService = indexerStatusService;
|
||||
_logger = logger;
|
||||
|
||||
_blockedIndexerCache = cacheManager.GetCacheDictionary(GetType(), "blocked", FetchBlockedIndexer, TimeSpan.FromSeconds(15));
|
||||
}
|
||||
|
||||
public SpecificationPriority Priority => SpecificationPriority.Database;
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var status = _blockedIndexerCache.Find(subject.Release.IndexerId.ToString());
|
||||
if (status != null)
|
||||
{
|
||||
return Decision.Reject($"Indexer {subject.Release.Indexer} is blocked till {status.DisabledTill} due to failures, cannot grab release.");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
private IDictionary<string, IndexerStatus> FetchBlockedIndexer()
|
||||
{
|
||||
return _indexerStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using NLog;
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -29,6 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
var age = subject.Release.AgeMinutes;
|
||||
var minimumAge = _configService.MinimumAge;
|
||||
var ageRounded = Math.Round(age, 1);
|
||||
|
||||
if (minimumAge == 0)
|
||||
{
|
||||
@@ -37,15 +39,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
}
|
||||
|
||||
|
||||
_logger.Debug("Checking if report meets minimum age requirements. {0}", age);
|
||||
_logger.Debug("Checking if report meets minimum age requirements. {0}", ageRounded);
|
||||
|
||||
if (age < minimumAge)
|
||||
{
|
||||
_logger.Debug("Only {0} minutes old, minimum age is {1} minutes", age, minimumAge);
|
||||
return Decision.Reject("Only {0} minutes old, minimum age is {1} minutes", age, minimumAge);
|
||||
_logger.Debug("Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
|
||||
return Decision.Reject("Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
|
||||
}
|
||||
|
||||
_logger.Debug("Release is {0} minutes old, greater than minimum age of {1} minutes", age, minimumAge);
|
||||
_logger.Debug("Release is {0} minutes old, greater than minimum age of {1} minutes", ageRounded, minimumAge);
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class TorrentSeedingSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public TorrentSeedingSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var torrentInfo = remoteEpisode.Release as TorrentInfo;
|
||||
|
||||
if (torrentInfo == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
if (torrentInfo.Seeders != null && torrentInfo.Seeders < 1)
|
||||
{
|
||||
_logger.Debug("Not enough seeders. ({0})", torrentInfo.Seeders);
|
||||
return Decision.Reject("Not enough seeders. ({0})", torrentInfo.Seeders);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class TorrentSeedingSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public TorrentSeedingSpecification(IIndexerFactory indexerFactory, Logger logger)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var torrentInfo = remoteEpisode.Release as TorrentInfo;
|
||||
|
||||
if (torrentInfo == null || torrentInfo.IndexerId == 0)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
IndexerDefinition indexer;
|
||||
try
|
||||
{
|
||||
indexer = _indexerFactory.Get(torrentInfo.IndexerId);
|
||||
}
|
||||
catch (ModelNotFoundException)
|
||||
{
|
||||
_logger.Debug("Indexer with id {0} does not exist, skipping seeders check", torrentInfo.IndexerId);
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
||||
|
||||
if (torrentIndexerSettings != null)
|
||||
{
|
||||
var minimumSeeders = torrentIndexerSettings.MinimumSeeders;
|
||||
|
||||
if (torrentInfo.Seeders.HasValue && torrentInfo.Seeders.Value < minimumSeeders)
|
||||
{
|
||||
_logger.Debug("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
|
||||
return Decision.Reject("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,21 +81,13 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
{
|
||||
IEnumerable<DelugeTorrent> torrents;
|
||||
|
||||
try
|
||||
if (!Settings.TvCategory.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (!Settings.TvCategory.IsNullOrWhiteSpace())
|
||||
{
|
||||
torrents = _proxy.GetTorrentsByLabel(Settings.TvCategory, Settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
torrents = _proxy.GetTorrents(Settings);
|
||||
}
|
||||
torrents = _proxy.GetTorrentsByLabel(Settings.TvCategory, Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
else
|
||||
{
|
||||
_logger.Error(ex, "Couldn't get list of torrents");
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
torrents = _proxy.GetTorrents(Settings);
|
||||
}
|
||||
|
||||
var items = new List<DownloadClientItem>();
|
||||
@@ -191,12 +183,13 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, ex.Message);
|
||||
|
||||
return new NzbDroneValidationFailure("Password", "Authentication failed");
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unble to test connection");
|
||||
switch (ex.Status)
|
||||
{
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
@@ -220,7 +213,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to test connection");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
|
||||
}
|
||||
|
||||
@@ -271,7 +264,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to get torrents");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to Deluge, please check your settings", ex);
|
||||
throw new DownloadClientUnavailableException("Unable to connect to Deluge, please check your settings", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,16 @@ namespace NzbDrone.Core.Download.Clients
|
||||
public DownloadClientException(string message, params object[] args)
|
||||
: base(string.Format(message, args))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DownloadClientException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DownloadClientException(string message, Exception innerException, params object[] args)
|
||||
: base(string.Format(message, args), innerException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DownloadClientException(string message, Exception innerException)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients
|
||||
{
|
||||
public class DownloadClientUnavailableException : DownloadClientException
|
||||
{
|
||||
public DownloadClientUnavailableException(string message, params object[] args)
|
||||
: base(string.Format(message, args))
|
||||
{
|
||||
}
|
||||
|
||||
public DownloadClientUnavailableException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DownloadClientUnavailableException(string message, Exception innerException, params object[] args)
|
||||
: base(string.Format(message, args), innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public DownloadClientUnavailableException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,20 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies
|
||||
DownloadStationSettings settings) where T : new()
|
||||
{
|
||||
var request = requestBuilder.Build();
|
||||
var response = _httpClient.Execute(request);
|
||||
HttpResponse response;
|
||||
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to Diskstation, please check your settings", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Unable to connect to Diskstation, please check your settings", ex);
|
||||
}
|
||||
|
||||
_logger.Debug("Trying to {0}", operation);
|
||||
|
||||
|
||||
@@ -320,12 +320,12 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex) // User could not have permission to access to downloadstation
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, ex.Message);
|
||||
return new NzbDroneValidationFailure(string.Empty, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Error testing Torrent Download Station");
|
||||
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
@@ -346,7 +346,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to connect to Torrent Download Station");
|
||||
|
||||
if (ex.Status == WebExceptionStatus.ConnectFailure)
|
||||
{
|
||||
@@ -359,7 +359,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Error testing Torrent Download Station");
|
||||
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,12 +234,12 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex) // User could not have permission to access to downloadstation
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, ex.Message);
|
||||
return new NzbDroneValidationFailure(string.Empty, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Error testing Usenet Download Station");
|
||||
return new NzbDroneValidationFailure(string.Empty, $"Unknown exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
@@ -260,7 +260,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to connect to Usenet Download Station");
|
||||
|
||||
if (ex.Status == WebExceptionStatus.ConnectFailure)
|
||||
{
|
||||
@@ -273,7 +273,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Error testing Torrent Download Station");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,17 +35,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
HadoukenTorrent[] torrents;
|
||||
|
||||
try
|
||||
{
|
||||
torrents = _proxy.GetTorrents(Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.ErrorException(ex.Message, ex);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
var torrents = _proxy.GetTorrents(Settings);
|
||||
|
||||
var items = new List<DownloadClientItem>();
|
||||
|
||||
|
||||
@@ -77,7 +77,21 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
||||
requestBuilder.Headers.Add("Accept-Encoding", "gzip,deflate");
|
||||
|
||||
var httpRequest = requestBuilder.Build();
|
||||
var response = _httpClient.Execute(httpRequest);
|
||||
HttpResponse response;
|
||||
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(httpRequest);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to Hadouken, please check your settings", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Unable to connect to Hadouken, please check your settings", ex);
|
||||
}
|
||||
|
||||
var result = Json.Deserialize<JsonRpcResponse<T>>(response.Content);
|
||||
|
||||
if (result.Error != null)
|
||||
|
||||
@@ -47,17 +47,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
List<NzbVortexQueueItem> vortexQueue;
|
||||
|
||||
try
|
||||
{
|
||||
vortexQueue = _proxy.GetQueue(30, Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Warn("Couldn't get download queue. {0}", ex.Message);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
var vortexQueue = _proxy.GetQueue(30, Settings);
|
||||
|
||||
var queueItems = new List<DownloadClientItem>();
|
||||
|
||||
@@ -168,7 +158,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to connect to NZBVortex");
|
||||
return new ValidationFailure("Host", "Unable to connect to NZBVortex");
|
||||
}
|
||||
|
||||
@@ -189,7 +179,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to connect to NZBVortex");
|
||||
return new ValidationFailure("Host", "Unable to connect to NZBVortex");
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to NZBVortex, please check your settings", ex);
|
||||
throw new DownloadClientUnavailableException("Unable to connect to NZBVortex, please check your settings", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -9,8 +10,8 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
{
|
||||
@@ -51,19 +52,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
|
||||
private IEnumerable<DownloadClientItem> GetQueue()
|
||||
{
|
||||
NzbgetGlobalStatus globalStatus;
|
||||
List<NzbgetQueueItem> queue;
|
||||
|
||||
try
|
||||
{
|
||||
globalStatus = _proxy.GetGlobalStatus(Settings);
|
||||
queue = _proxy.GetQueue(Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
var globalStatus = _proxy.GetGlobalStatus(Settings);
|
||||
var queue = _proxy.GetQueue(Settings);
|
||||
|
||||
var queueItems = new List<DownloadClientItem>();
|
||||
|
||||
@@ -119,17 +109,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
|
||||
private IEnumerable<DownloadClientItem> GetHistory()
|
||||
{
|
||||
List<NzbgetHistoryItem> history;
|
||||
|
||||
try
|
||||
{
|
||||
history = _proxy.GetHistory(Settings).Take(_configService.DownloadClientHistoryLimit).ToList();
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
var history = _proxy.GetHistory(Settings).Take(_configService.DownloadClientHistoryLimit).ToList();
|
||||
|
||||
var historyItems = new List<DownloadClientItem>();
|
||||
|
||||
@@ -289,7 +269,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
{
|
||||
return new ValidationFailure("Username", "Authentication failed");
|
||||
}
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to connect to NZBGet");
|
||||
return new ValidationFailure("Host", "Unable to connect to NZBGet");
|
||||
}
|
||||
|
||||
@@ -317,8 +297,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
{
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
|
||||
var keepHistory = config.GetValueOrDefault("KeepHistory");
|
||||
if (keepHistory == "0")
|
||||
var keepHistory = config.GetValueOrDefault("KeepHistory", "7");
|
||||
int value;
|
||||
if (!int.TryParse(keepHistory, NumberStyles.None, CultureInfo.InvariantCulture, out value) || value == 0)
|
||||
{
|
||||
return new NzbDroneValidationFailure(string.Empty, "NzbGet setting KeepHistory should be greater than 0")
|
||||
{
|
||||
@@ -326,6 +307,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
DetailedDescription = "NzbGet setting KeepHistory is set to 0. Which prevents Sonarr from seeing completed downloads."
|
||||
};
|
||||
}
|
||||
else if (value > 25000)
|
||||
{
|
||||
return new NzbDroneValidationFailure(string.Empty, "NzbGet setting KeepHistory should be less than 25000")
|
||||
{
|
||||
InfoLink = string.Format("http://{0}:{1}/", Settings.Host, Settings.Port),
|
||||
DetailedDescription = "NzbGet setting KeepHistory is set too high."
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -235,14 +235,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new DownloadClientException("Authentication failed for NzbGet, please check your settings", ex);
|
||||
throw new DownloadClientAuthenticationException("Authentication failed for NzbGet, please check your settings", ex);
|
||||
}
|
||||
|
||||
throw new DownloadClientException("Unable to connect to NzbGet. " + ex.Message, ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to NzbGet. " + ex.Message, ex);
|
||||
throw new DownloadClientUnavailableException("Unable to connect to NzbGet. " + ex.Message, ex);
|
||||
}
|
||||
|
||||
var result = Json.Deserialize<JsonRpcResponse<T>>(response.Content);
|
||||
|
||||
@@ -89,19 +89,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
QBittorrentPreferences config;
|
||||
List<QBittorrentTorrent> torrents;
|
||||
|
||||
try
|
||||
{
|
||||
config = _proxy.GetConfig(Settings);
|
||||
torrents = _proxy.GetTorrents(Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
var torrents = _proxy.GetTorrents(Settings);
|
||||
|
||||
var queueItems = new List<DownloadClientItem>();
|
||||
|
||||
@@ -241,7 +230,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, ex.Message);
|
||||
return new NzbDroneValidationFailure("Username", "Authentication failure")
|
||||
{
|
||||
DetailedDescription = "Please verify your username and password."
|
||||
@@ -249,7 +238,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to connect to qBittorrent");
|
||||
if (ex.Status == WebExceptionStatus.ConnectFailure)
|
||||
{
|
||||
return new NzbDroneValidationFailure("Host", "Unable to connect")
|
||||
@@ -261,7 +250,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to test qBittorrent");
|
||||
return new NzbDroneValidationFailure(String.Empty, "Unknown exception: " + ex.Message);
|
||||
}
|
||||
|
||||
@@ -296,7 +285,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to test qBittorrent");
|
||||
return new NzbDroneValidationFailure(String.Empty, "Unknown exception: " + ex.Message);
|
||||
}
|
||||
|
||||
@@ -311,7 +300,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to get torrents");
|
||||
return new NzbDroneValidationFailure(String.Empty, "Failed to get the list of torrents: " + ex.Message);
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
AuthenticateClient(requestBuilder, settings);
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
request.LogResponseContent = true;
|
||||
|
||||
HttpResponse response;
|
||||
try
|
||||
@@ -225,7 +226,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBitTorrent, please check your settings.", ex);
|
||||
throw new DownloadClientUnavailableException("Failed to connect to qBitTorrent, please check your settings.", ex);
|
||||
}
|
||||
|
||||
if (response.Content != "Ok.") // returns "Fails." on bad login
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Warn("Couldn't get download queue. {0}", ex.Message);
|
||||
_logger.Warn(ex, "Couldn't get download queue. {0}", ex.Message);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
|
||||
@@ -112,17 +112,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
|
||||
private IEnumerable<DownloadClientItem> GetHistory()
|
||||
{
|
||||
SabnzbdHistory sabHistory;
|
||||
|
||||
try
|
||||
{
|
||||
sabHistory = _proxy.GetHistory(0, _configService.DownloadClientHistoryLimit, Settings.TvCategory, Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
var sabHistory = _proxy.GetHistory(0, _configService.DownloadClientHistoryLimit, Settings.TvCategory, Settings);
|
||||
|
||||
var historyItems = new List<DownloadClientItem>();
|
||||
|
||||
@@ -188,7 +178,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
historyItems.Add(historyItem);
|
||||
}
|
||||
|
||||
@@ -388,7 +378,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, ex.Message);
|
||||
return new ValidationFailure("Host", "Unable to connect to SABnzbd");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to SABnzbd, please check your settings", ex);
|
||||
throw new DownloadClientUnavailableException("Unable to connect to SABnzbd, please check your settings", ex);
|
||||
}
|
||||
|
||||
CheckForError(response);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -33,17 +33,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
List<TransmissionTorrent> torrents;
|
||||
|
||||
try
|
||||
{
|
||||
torrents = _proxy.GetTorrents(Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
var torrents = _proxy.GetTorrents(Settings);
|
||||
|
||||
var items = new List<DownloadClientItem>();
|
||||
|
||||
@@ -205,27 +195,24 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, ex.Message);
|
||||
return new NzbDroneValidationFailure("Username", "Authentication failure")
|
||||
{
|
||||
DetailedDescription = string.Format("Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {0} by WhiteList limitations in the {0} configuration.", Name)
|
||||
};
|
||||
}
|
||||
catch (WebException ex)
|
||||
catch (DownloadClientUnavailableException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
if (ex.Status == WebExceptionStatus.ConnectFailure)
|
||||
_logger.Error(ex, ex.Message);
|
||||
|
||||
return new NzbDroneValidationFailure("Host", "Unable to connect")
|
||||
{
|
||||
return new NzbDroneValidationFailure("Host", "Unable to connect")
|
||||
{
|
||||
DetailedDescription = "Please verify the hostname and port."
|
||||
};
|
||||
}
|
||||
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
|
||||
DetailedDescription = "Please verify the hostname and port."
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to test");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
|
||||
}
|
||||
}
|
||||
@@ -240,7 +227,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to get torrents");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message);
|
||||
}
|
||||
|
||||
|
||||
@@ -238,54 +238,66 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
|
||||
public TransmissionResponse ProcessRequest(string action, object arguments, TransmissionSettings settings)
|
||||
{
|
||||
var requestBuilder = BuildRequest(settings);
|
||||
requestBuilder.Headers.ContentType = "application/json";
|
||||
requestBuilder.SuppressHttpError = true;
|
||||
|
||||
AuthenticateClient(requestBuilder, settings);
|
||||
|
||||
var request = requestBuilder.Post().Build();
|
||||
|
||||
var data = new Dictionary<string, object>();
|
||||
data.Add("method", action);
|
||||
|
||||
if (arguments != null)
|
||||
try
|
||||
{
|
||||
data.Add("arguments", arguments);
|
||||
}
|
||||
var requestBuilder = BuildRequest(settings);
|
||||
requestBuilder.Headers.ContentType = "application/json";
|
||||
requestBuilder.SuppressHttpError = true;
|
||||
|
||||
request.SetContent(data.ToJson());
|
||||
request.ContentSummary = string.Format("{0}(...)", action);
|
||||
AuthenticateClient(requestBuilder, settings);
|
||||
|
||||
var response = _httpClient.Execute(request);
|
||||
if (response.StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
AuthenticateClient(requestBuilder, settings, true);
|
||||
var request = requestBuilder.Post().Build();
|
||||
|
||||
request = requestBuilder.Post().Build();
|
||||
var data = new Dictionary<string, object>();
|
||||
data.Add("method", action);
|
||||
|
||||
if (arguments != null)
|
||||
{
|
||||
data.Add("arguments", arguments);
|
||||
}
|
||||
|
||||
request.SetContent(data.ToJson());
|
||||
request.ContentSummary = string.Format("{0}(...)", action);
|
||||
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("User authentication failed.");
|
||||
}
|
||||
var response = _httpClient.Execute(request);
|
||||
|
||||
var transmissionResponse = Json.Deserialize<TransmissionResponse>(response.Content);
|
||||
if (response.StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
AuthenticateClient(requestBuilder, settings, true);
|
||||
|
||||
if (transmissionResponse == null)
|
||||
{
|
||||
throw new TransmissionException("Unexpected response");
|
||||
}
|
||||
else if (transmissionResponse.Result != "success")
|
||||
{
|
||||
throw new TransmissionException(transmissionResponse.Result);
|
||||
}
|
||||
request = requestBuilder.Post().Build();
|
||||
|
||||
return transmissionResponse;
|
||||
request.SetContent(data.ToJson());
|
||||
request.ContentSummary = string.Format("{0}(...)", action);
|
||||
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
else if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("User authentication failed.");
|
||||
}
|
||||
|
||||
var transmissionResponse = Json.Deserialize<TransmissionResponse>(response.Content);
|
||||
|
||||
if (transmissionResponse == null)
|
||||
{
|
||||
throw new TransmissionException("Unexpected response");
|
||||
}
|
||||
else if (transmissionResponse.Result != "success")
|
||||
{
|
||||
throw new TransmissionException(transmissionResponse.Result);
|
||||
}
|
||||
|
||||
return transmissionResponse;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to Transmission, please check your settings", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Unable to connect to Transmission, please check your settings", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,69 +81,60 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
try
|
||||
var torrents = _proxy.GetTorrents(Settings);
|
||||
|
||||
_logger.Debug("Retrieved metadata of {0} torrents in client", torrents.Count);
|
||||
|
||||
var items = new List<DownloadClientItem>();
|
||||
foreach (RTorrentTorrent torrent in torrents)
|
||||
{
|
||||
var torrents = _proxy.GetTorrents(Settings);
|
||||
// Don't concern ourselves with categories other than specified
|
||||
if (torrent.Category != Settings.TvCategory) continue;
|
||||
|
||||
_logger.Debug("Retrieved metadata of {0} torrents in client", torrents.Count);
|
||||
|
||||
var items = new List<DownloadClientItem>();
|
||||
foreach (RTorrentTorrent torrent in torrents)
|
||||
if (torrent.Path.StartsWith("."))
|
||||
{
|
||||
// Don't concern ourselves with categories other than specified
|
||||
if (torrent.Category != Settings.TvCategory) continue;
|
||||
|
||||
if (torrent.Path.StartsWith("."))
|
||||
{
|
||||
throw new DownloadClientException("Download paths paths must be absolute. Please specify variable \"directory\" in rTorrent.");
|
||||
}
|
||||
|
||||
var item = new DownloadClientItem();
|
||||
item.DownloadClient = Definition.Name;
|
||||
item.Title = torrent.Name;
|
||||
item.DownloadId = torrent.Hash;
|
||||
item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.Path));
|
||||
item.TotalSize = torrent.TotalSize;
|
||||
item.RemainingSize = torrent.RemainingSize;
|
||||
item.Category = torrent.Category;
|
||||
|
||||
if (torrent.DownRate > 0)
|
||||
{
|
||||
var secondsLeft = torrent.RemainingSize / torrent.DownRate;
|
||||
item.RemainingTime = TimeSpan.FromSeconds(secondsLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.RemainingTime = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
if (torrent.IsFinished)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Completed;
|
||||
}
|
||||
else if (torrent.IsActive)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
}
|
||||
else if (!torrent.IsActive)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
}
|
||||
|
||||
// No stop ratio data is present, so do not delete
|
||||
item.CanMoveFiles = item.CanBeRemoved = false;
|
||||
|
||||
items.Add(item);
|
||||
throw new DownloadClientException("Download paths paths must be absolute. Please specify variable \"directory\" in rTorrent.");
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
var item = new DownloadClientItem();
|
||||
item.DownloadClient = Definition.Name;
|
||||
item.Title = torrent.Name;
|
||||
item.DownloadId = torrent.Hash;
|
||||
item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.Path));
|
||||
item.TotalSize = torrent.TotalSize;
|
||||
item.RemainingSize = torrent.RemainingSize;
|
||||
item.Category = torrent.Category;
|
||||
|
||||
if (torrent.DownRate > 0)
|
||||
{
|
||||
var secondsLeft = torrent.RemainingSize / torrent.DownRate;
|
||||
item.RemainingTime = TimeSpan.FromSeconds(secondsLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.RemainingTime = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
if (torrent.IsFinished)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Completed;
|
||||
}
|
||||
else if (torrent.IsActive)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
}
|
||||
else if (!torrent.IsActive)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
}
|
||||
|
||||
// No stop ratio data is present, so do not delete
|
||||
item.CanMoveFiles = item.CanBeRemoved = false;
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
@@ -189,7 +180,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to test rTorrent");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
|
||||
}
|
||||
|
||||
@@ -204,7 +195,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to get torrents");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using CookComputing.XmlRpc;
|
||||
@@ -54,8 +56,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
_logger.Debug("Executing remote method: system.client_version");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
var version = client.GetVersion();
|
||||
var version = ExecuteRequest(() => client.GetVersion());
|
||||
|
||||
return version;
|
||||
}
|
||||
@@ -65,20 +66,22 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
_logger.Debug("Executing remote method: d.multicall2");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var ret = client.TorrentMulticall("", "",
|
||||
"d.name=", // string
|
||||
"d.hash=", // string
|
||||
"d.base_path=", // string
|
||||
"d.custom1=", // string (label)
|
||||
"d.size_bytes=", // long
|
||||
"d.left_bytes=", // long
|
||||
"d.down.rate=", // long (in bytes / s)
|
||||
"d.ratio=", // long
|
||||
"d.is_open=", // long
|
||||
"d.is_active=", // long
|
||||
"d.complete="); //long
|
||||
var ret = ExecuteRequest(() => client.TorrentMulticall("", "",
|
||||
"d.name=", // string
|
||||
"d.hash=", // string
|
||||
"d.base_path=", // string
|
||||
"d.custom1=", // string (label)
|
||||
"d.size_bytes=", // long
|
||||
"d.left_bytes=", // long
|
||||
"d.down.rate=", // long (in bytes / s)
|
||||
"d.ratio=", // long
|
||||
"d.is_open=", // long
|
||||
"d.is_active=", // long
|
||||
"d.complete=") //long
|
||||
);
|
||||
|
||||
var items = new List<RTorrentTorrent>();
|
||||
|
||||
foreach (object[] torrent in ret)
|
||||
{
|
||||
var labelDecoded = System.Web.HttpUtility.UrlDecode((string) torrent[3]);
|
||||
@@ -107,8 +110,8 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
_logger.Debug("Executing remote method: load.normal");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var response = ExecuteRequest(() => client.LoadStart("", torrentUrl, GetCommands(label, priority, directory)));
|
||||
|
||||
var response = client.LoadStart("", torrentUrl, GetCommands(label, priority, directory));
|
||||
if (response != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl);
|
||||
@@ -120,8 +123,8 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
_logger.Debug("Executing remote method: load.raw");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var response = ExecuteRequest(() => client.LoadRawStart("", fileContent, GetCommands(label, priority, directory)));
|
||||
|
||||
var response = client.LoadRawStart("", fileContent, GetCommands(label, priority, directory));
|
||||
if (response != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not add torrent: {0}.", fileName);
|
||||
@@ -133,14 +136,39 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
_logger.Debug("Executing remote method: d.erase");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
var response = ExecuteRequest(() => client.Remove(hash));
|
||||
|
||||
var response = client.Remove(hash);
|
||||
if (response != 0)
|
||||
{
|
||||
throw new DownloadClientException("Could not remove torrent: {0}.", hash);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasHashTorrent(string hash, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.name");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
try
|
||||
{
|
||||
var name = ExecuteRequest(() => client.GetName(hash));
|
||||
|
||||
if (name.IsNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var metaTorrent = name == (hash + ".meta");
|
||||
|
||||
return !metaTorrent;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetCommands(string label, RTorrentPriority priority, string directory)
|
||||
{
|
||||
var result = new List<string>();
|
||||
@@ -163,25 +191,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public bool HasHashTorrent(string hash, RTorrentSettings settings)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.name");
|
||||
|
||||
var client = BuildClient(settings);
|
||||
|
||||
try
|
||||
{
|
||||
var name = client.GetName(hash);
|
||||
if (name.IsNullOrWhiteSpace()) return false;
|
||||
bool metaTorrent = name == (hash + ".meta");
|
||||
return !metaTorrent;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private IRTorrent BuildClient(RTorrentSettings settings)
|
||||
{
|
||||
var client = XmlRpcProxyGen.Create<IRTorrent>();
|
||||
@@ -201,5 +210,21 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private T ExecuteRequest<T>(Func<T> task)
|
||||
{
|
||||
try
|
||||
{
|
||||
return task();
|
||||
}
|
||||
catch (XmlRpcServerException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to rTorrent, please check your settings", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Unable to connect to rTorrent, please check your settings", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,42 +72,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
List<UTorrentTorrent> torrents;
|
||||
|
||||
try
|
||||
{
|
||||
var cacheKey = string.Format("{0}:{1}:{2}", Settings.Host, Settings.Port, Settings.TvCategory);
|
||||
var cache = _torrentCache.Find(cacheKey);
|
||||
|
||||
var response = _proxy.GetTorrents(cache == null ? null : cache.CacheID, Settings);
|
||||
|
||||
if (cache != null && response.Torrents == null)
|
||||
{
|
||||
var removedAndUpdated = new HashSet<string>(response.TorrentsChanged.Select(v => v.Hash).Concat(response.TorrentsRemoved));
|
||||
|
||||
torrents = cache.Torrents
|
||||
.Where(v => !removedAndUpdated.Contains(v.Hash))
|
||||
.Concat(response.TorrentsChanged)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
torrents = response.Torrents;
|
||||
}
|
||||
|
||||
cache = new UTorrentTorrentCache
|
||||
{
|
||||
CacheID = response.CacheNumber,
|
||||
Torrents = torrents
|
||||
};
|
||||
|
||||
_torrentCache.Set(cacheKey, cache, TimeSpan.FromMinutes(15));
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
var torrents = GetTorrents();
|
||||
|
||||
var queueItems = new List<DownloadClientItem>();
|
||||
|
||||
@@ -173,6 +138,40 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
private List<UTorrentTorrent> GetTorrents()
|
||||
{
|
||||
List<UTorrentTorrent> torrents;
|
||||
|
||||
var cacheKey = string.Format("{0}:{1}:{2}", Settings.Host, Settings.Port, Settings.TvCategory);
|
||||
var cache = _torrentCache.Find(cacheKey);
|
||||
|
||||
var response = _proxy.GetTorrents(cache == null ? null : cache.CacheID, Settings);
|
||||
|
||||
if (cache != null && response.Torrents == null)
|
||||
{
|
||||
var removedAndUpdated = new HashSet<string>(response.TorrentsChanged.Select(v => v.Hash).Concat(response.TorrentsRemoved));
|
||||
|
||||
torrents = cache.Torrents
|
||||
.Where(v => !removedAndUpdated.Contains(v.Hash))
|
||||
.Concat(response.TorrentsChanged)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
torrents = response.Torrents;
|
||||
}
|
||||
|
||||
cache = new UTorrentTorrentCache
|
||||
{
|
||||
CacheID = response.CacheNumber,
|
||||
Torrents = torrents
|
||||
};
|
||||
|
||||
_torrentCache.Set(cacheKey, cache, TimeSpan.FromMinutes(15));
|
||||
|
||||
return torrents;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
{
|
||||
_proxy.RemoveTorrent(downloadId, deleteData, Settings);
|
||||
@@ -232,7 +231,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, ex.Message);
|
||||
return new NzbDroneValidationFailure("Username", "Authentication failure")
|
||||
{
|
||||
DetailedDescription = "Please verify your username and password."
|
||||
@@ -240,7 +239,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Unable to connect to uTorrent");
|
||||
if (ex.Status == WebExceptionStatus.ConnectFailure)
|
||||
{
|
||||
return new NzbDroneValidationFailure("Host", "Unable to connect")
|
||||
@@ -252,7 +251,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to test uTorrent");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
|
||||
}
|
||||
|
||||
@@ -267,7 +266,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
_logger.Error(ex, "Failed to get torrents");
|
||||
return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message);
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to uTorrent, please check your settings", ex);
|
||||
throw new DownloadClientUnavailableException("Unable to connect to uTorrent, please check your settings", ex);
|
||||
}
|
||||
|
||||
cookies = response.GetCookies();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -9,17 +11,24 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IDownloadClientFactory : IProviderFactory<IDownloadClient, DownloadClientDefinition>
|
||||
{
|
||||
|
||||
List<IDownloadClient> DownloadHandlingEnabled(bool filterBlockedClients = true);
|
||||
}
|
||||
|
||||
public class DownloadClientFactory : ProviderFactory<IDownloadClient, DownloadClientDefinition>, IDownloadClientFactory
|
||||
{
|
||||
private readonly IDownloadClientRepository _providerRepository;
|
||||
private readonly IDownloadClientStatusService _downloadClientStatusService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadClientFactory(IDownloadClientRepository providerRepository, IEnumerable<IDownloadClient> providers, IContainer container, IEventAggregator eventAggregator, Logger logger)
|
||||
public DownloadClientFactory(IDownloadClientStatusService downloadClientStatusService,
|
||||
IDownloadClientRepository providerRepository,
|
||||
IEnumerable<IDownloadClient> providers,
|
||||
IContainer container,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
: base(providerRepository, providers, container, eventAggregator, logger)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_downloadClientStatusService = downloadClientStatusService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override List<DownloadClientDefinition> Active()
|
||||
@@ -33,5 +42,46 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
definition.Protocol = provider.Protocol;
|
||||
}
|
||||
|
||||
public List<IDownloadClient> DownloadHandlingEnabled(bool filterBlockedClients = true)
|
||||
{
|
||||
var enabledClients = GetAvailableProviders();
|
||||
|
||||
if (filterBlockedClients)
|
||||
{
|
||||
return FilterBlockedClients(enabledClients).ToList();
|
||||
}
|
||||
|
||||
return enabledClients.ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<IDownloadClient> FilterBlockedClients(IEnumerable<IDownloadClient> clients)
|
||||
{
|
||||
var blockedIndexers = _downloadClientStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v);
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
DownloadClientStatus downloadClientStatus;
|
||||
if (blockedIndexers.TryGetValue(client.Definition.Id, out downloadClientStatus))
|
||||
{
|
||||
_logger.Debug("Temporarily ignoring download client {0} till {1} due to recent failures.", client.Definition.Name, downloadClientStatus.DisabledTill.Value.ToLocalTime());
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return client;
|
||||
}
|
||||
}
|
||||
|
||||
public override ValidationResult Test(DownloadClientDefinition definition)
|
||||
{
|
||||
var result = base.Test(definition);
|
||||
|
||||
if ((result == null || result.IsValid) && definition.Id != 0)
|
||||
{
|
||||
_downloadClientStatusService.RecordSuccess(definition.Id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,19 +27,12 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
public IEnumerable<IDownloadClient> GetDownloadClients()
|
||||
{
|
||||
return _downloadClientFactory.GetAvailableProviders();//.Select(MapDownloadClient);
|
||||
return _downloadClientFactory.GetAvailableProviders();
|
||||
}
|
||||
|
||||
public IDownloadClient Get(int id)
|
||||
{
|
||||
return _downloadClientFactory.GetAvailableProviders().Single(d => d.Definition.Id == id);
|
||||
}
|
||||
|
||||
public IDownloadClient MapDownloadClient(IDownloadClient downloadClient)
|
||||
{
|
||||
_downloadClientFactory.SetProviderCharacteristics(downloadClient, (DownloadClientDefinition)downloadClient.Definition);
|
||||
|
||||
return downloadClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
src/NzbDrone.Core/Download/DownloadClientStatus.cs
Normal file
9
src/NzbDrone.Core/Download/DownloadClientStatus.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using NzbDrone.Core.ThingiProvider.Status;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class DownloadClientStatus : ProviderStatusBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
19
src/NzbDrone.Core/Download/DownloadClientStatusRepository.cs
Normal file
19
src/NzbDrone.Core/Download/DownloadClientStatusRepository.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider.Status;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IDownloadClientStatusRepository : IProviderStatusRepository<DownloadClientStatus>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class DownloadClientStatusRepository : ProviderStatusRepository<DownloadClientStatus>, IDownloadClientStatusRepository
|
||||
{
|
||||
public DownloadClientStatusRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/NzbDrone.Core/Download/DownloadClientStatusService.cs
Normal file
22
src/NzbDrone.Core/Download/DownloadClientStatusService.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ThingiProvider.Status;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IDownloadClientStatusService : IProviderStatusServiceBase<DownloadClientStatus>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class DownloadClientStatusService : ProviderStatusServiceBase<IDownloadClient, DownloadClientStatus>, IDownloadClientStatusService
|
||||
{
|
||||
public DownloadClientStatusService(IDownloadClientStatusRepository providerStatusRepository, IEventAggregator eventAggregator, Logger logger)
|
||||
: base(providerStatusRepository, eventAggregator, logger)
|
||||
{
|
||||
MinimumTimeSinceInitialFailure = TimeSpan.FromMinutes(5);
|
||||
MaximumEscalationLevel = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,18 +21,21 @@ namespace NzbDrone.Core.Download
|
||||
public class DownloadService : IDownloadService
|
||||
{
|
||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||
private readonly IDownloadClientStatusService _downloadClientStatusService;
|
||||
private readonly IIndexerStatusService _indexerStatusService;
|
||||
private readonly IRateLimitService _rateLimitService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadService(IProvideDownloadClient downloadClientProvider,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IRateLimitService rateLimitService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
IDownloadClientStatusService downloadClientStatusService,
|
||||
IIndexerStatusService indexerStatusService,
|
||||
IRateLimitService rateLimitService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
_downloadClientProvider = downloadClientProvider;
|
||||
_downloadClientStatusService = downloadClientStatusService;
|
||||
_indexerStatusService = indexerStatusService;
|
||||
_rateLimitService = rateLimitService;
|
||||
_eventAggregator = eventAggregator;
|
||||
@@ -64,6 +67,7 @@ namespace NzbDrone.Core.Download
|
||||
try
|
||||
{
|
||||
downloadClientId = downloadClient.Download(remoteEpisode);
|
||||
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
|
||||
_indexerStatusService.RecordSuccess(remoteEpisode.Release.IndexerId);
|
||||
}
|
||||
catch (ReleaseDownloadException ex)
|
||||
@@ -92,4 +96,4 @@ namespace NzbDrone.Core.Download
|
||||
_eventAggregator.PublishEvent(episodeGrabbedEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
public DateTime Added { get; set; }
|
||||
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
||||
public ReleaseInfo Release { get; set; }
|
||||
public PendingReleaseReason Reason { get; set; }
|
||||
|
||||
//Not persisted
|
||||
public RemoteEpisode RemoteEpisode { get; set; }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Core.Download.Pending
|
||||
{
|
||||
public enum PendingReleaseReason
|
||||
{
|
||||
Delay = 0,
|
||||
DownloadClientUnavailable = 1,
|
||||
Fallback = 2
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
{
|
||||
public interface IPendingReleaseService
|
||||
{
|
||||
void Add(DownloadDecision decision);
|
||||
|
||||
void Add(DownloadDecision decision, PendingReleaseReason reason);
|
||||
List<ReleaseInfo> GetPending();
|
||||
List<RemoteEpisode> GetPendingRemoteEpisodes(int seriesId);
|
||||
List<Queue.Queue> GetPendingQueue();
|
||||
@@ -67,7 +66,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
}
|
||||
|
||||
|
||||
public void Add(DownloadDecision decision)
|
||||
public void Add(DownloadDecision decision, PendingReleaseReason reason)
|
||||
{
|
||||
var alreadyPending = GetPendingReleases();
|
||||
|
||||
@@ -77,14 +76,32 @@ namespace NzbDrone.Core.Download.Pending
|
||||
.Intersect(episodeIds)
|
||||
.Any());
|
||||
|
||||
if (existingReports.Any(MatchingReleasePredicate(decision.RemoteEpisode.Release)))
|
||||
var matchingReports = existingReports.Where(MatchingReleasePredicate(decision.RemoteEpisode.Release)).ToList();
|
||||
|
||||
if (matchingReports.Any())
|
||||
{
|
||||
_logger.Debug("This release is already pending, not adding again");
|
||||
return;
|
||||
var sameReason = true;
|
||||
|
||||
foreach (var matchingReport in matchingReports)
|
||||
{
|
||||
if (matchingReport.Reason != reason)
|
||||
{
|
||||
_logger.Debug("This release is already pending with reason {0}, changing to {1}", matchingReport.Reason, reason);
|
||||
matchingReport.Reason = reason;
|
||||
_repository.Update(matchingReport);
|
||||
sameReason = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameReason)
|
||||
{
|
||||
_logger.Debug("This release is already pending with reason {0}, not adding again", reason);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Adding release to pending releases");
|
||||
Insert(decision);
|
||||
_logger.Debug("Adding release to pending releases with reason {0}", reason);
|
||||
Insert(decision, reason);
|
||||
}
|
||||
|
||||
public List<ReleaseInfo> GetPending()
|
||||
@@ -101,7 +118,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
|
||||
private List<ReleaseInfo> FilterBlockedIndexers(List<ReleaseInfo> releases)
|
||||
{
|
||||
var blockedIndexers = new HashSet<int>(_indexerStatusService.GetBlockedIndexers().Select(v => v.ProviderId));
|
||||
var blockedIndexers = new HashSet<int>(_indexerStatusService.GetBlockedProviders().Select(v => v.ProviderId));
|
||||
|
||||
return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList();
|
||||
}
|
||||
@@ -117,7 +134,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
|
||||
var nextRssSync = new Lazy<DateTime>(() => _taskManager.GetNextExecution(typeof(RssSyncCommand)));
|
||||
|
||||
foreach (var pendingRelease in GetPendingReleases())
|
||||
foreach (var pendingRelease in GetPendingReleases().Where(p => p.Reason != PendingReleaseReason.Fallback))
|
||||
{
|
||||
foreach (var episode in pendingRelease.RemoteEpisode.Episodes)
|
||||
{
|
||||
@@ -132,6 +149,13 @@ namespace NzbDrone.Core.Download.Pending
|
||||
ect = ect.AddMinutes(_configService.RssSyncInterval);
|
||||
}
|
||||
|
||||
var timeleft = ect.Subtract(DateTime.UtcNow);
|
||||
|
||||
if (timeleft.TotalSeconds < 0)
|
||||
{
|
||||
timeleft = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
var queue = new Queue.Queue
|
||||
{
|
||||
Id = GetQueueId(pendingRelease, episode),
|
||||
@@ -142,11 +166,12 @@ namespace NzbDrone.Core.Download.Pending
|
||||
Size = pendingRelease.RemoteEpisode.Release.Size,
|
||||
Sizeleft = pendingRelease.RemoteEpisode.Release.Size,
|
||||
RemoteEpisode = pendingRelease.RemoteEpisode,
|
||||
Timeleft = ect.Subtract(DateTime.UtcNow),
|
||||
Timeleft = timeleft,
|
||||
EstimatedCompletionTime = ect,
|
||||
Status = "Pending",
|
||||
Status = pendingRelease.Reason.ToString(),
|
||||
Protocol = pendingRelease.RemoteEpisode.Release.DownloadProtocol
|
||||
};
|
||||
|
||||
queued.Add(queue);
|
||||
}
|
||||
}
|
||||
@@ -224,7 +249,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||
};
|
||||
}
|
||||
|
||||
private void Insert(DownloadDecision decision)
|
||||
private void Insert(DownloadDecision decision, PendingReleaseReason reason)
|
||||
{
|
||||
_repository.Insert(new PendingRelease
|
||||
{
|
||||
@@ -232,7 +257,8 @@ namespace NzbDrone.Core.Download.Pending
|
||||
ParsedEpisodeInfo = decision.RemoteEpisode.ParsedEpisodeInfo,
|
||||
Release = decision.RemoteEpisode.Release,
|
||||
Title = decision.RemoteEpisode.Release.Title,
|
||||
Added = DateTime.UtcNow
|
||||
Added = DateTime.UtcNow,
|
||||
Reason = reason
|
||||
});
|
||||
|
||||
_eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent());
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
@@ -36,36 +39,33 @@ namespace NzbDrone.Core.Download
|
||||
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports);
|
||||
var grabbed = new List<DownloadDecision>();
|
||||
var pending = new List<DownloadDecision>();
|
||||
var failed = new List<DownloadDecision>();
|
||||
|
||||
var usenetFailed = false;
|
||||
var torrentFailed = false;
|
||||
|
||||
foreach (var report in prioritizedDecisions)
|
||||
{
|
||||
var remoteEpisode = report.RemoteEpisode;
|
||||
var downloadProtocol = report.RemoteEpisode.Release.DownloadProtocol;
|
||||
|
||||
var episodeIds = remoteEpisode.Episodes.Select(e => e.Id).ToList();
|
||||
|
||||
//Skip if already grabbed
|
||||
if (grabbed.SelectMany(r => r.RemoteEpisode.Episodes)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(episodeIds)
|
||||
.Any())
|
||||
// Skip if already grabbed
|
||||
if (IsEpisodeProcessed(grabbed, report))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (report.TemporarilyRejected)
|
||||
{
|
||||
_pendingReleaseService.Add(report);
|
||||
_pendingReleaseService.Add(report, PendingReleaseReason.Delay);
|
||||
pending.Add(report);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pending.SelectMany(r => r.RemoteEpisode.Episodes)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(episodeIds)
|
||||
.Any())
|
||||
if (downloadProtocol == DownloadProtocol.Usenet && usenetFailed ||
|
||||
downloadProtocol == DownloadProtocol.Torrent && torrentFailed)
|
||||
{
|
||||
failed.Add(report);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -74,14 +74,31 @@ namespace NzbDrone.Core.Download
|
||||
_downloadService.DownloadReport(remoteEpisode);
|
||||
grabbed.Add(report);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: support for store & forward
|
||||
//We'll need to differentiate between a download client error and an indexer error
|
||||
_logger.Warn(e, "Couldn't add report to download queue. " + remoteEpisode);
|
||||
if (ex is DownloadClientUnavailableException || ex is DownloadClientAuthenticationException)
|
||||
{
|
||||
_logger.Debug("Failed to send release to download client, storing until later");
|
||||
failed.Add(report);
|
||||
|
||||
if (downloadProtocol == DownloadProtocol.Usenet)
|
||||
{
|
||||
usenetFailed = true;
|
||||
}
|
||||
else if (downloadProtocol == DownloadProtocol.Torrent)
|
||||
{
|
||||
torrentFailed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn(ex, "Couldn't add report to download queue. " + remoteEpisode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pending.AddRange(ProcessFailedGrabs(grabbed, failed));
|
||||
|
||||
return new ProcessedDecisions(grabbed, pending, decisions.Where(d => d.Rejected).ToList());
|
||||
}
|
||||
|
||||
@@ -90,5 +107,50 @@ namespace NzbDrone.Core.Download
|
||||
//Process both approved and temporarily rejected
|
||||
return decisions.Where(c => (c.Approved || c.TemporarilyRejected) && c.RemoteEpisode.Episodes.Any()).ToList();
|
||||
}
|
||||
|
||||
private bool IsEpisodeProcessed(List<DownloadDecision> decisions, DownloadDecision report)
|
||||
{
|
||||
var episodeIds = report.RemoteEpisode.Episodes.Select(e => e.Id).ToList();
|
||||
|
||||
return decisions.SelectMany(r => r.RemoteEpisode.Episodes)
|
||||
.Select(e => e.Id)
|
||||
.ToList()
|
||||
.Intersect(episodeIds)
|
||||
.Any();
|
||||
}
|
||||
|
||||
private List<DownloadDecision> ProcessFailedGrabs(List<DownloadDecision> grabbed, List<DownloadDecision> failed)
|
||||
{
|
||||
var pending = new List<DownloadDecision>();
|
||||
var stored = new List<DownloadDecision>();
|
||||
|
||||
foreach (var report in failed)
|
||||
{
|
||||
// If a release was already grabbed with matching episodes we should store it as a fallback
|
||||
// and filter it out the next time it is processed incase a higher quality release failed to
|
||||
// add to the download client, but a lower quality release was sent to another client
|
||||
// If the release wasn't grabbed already, but was already stored, store it as a fallback,
|
||||
// otherwise store it as DownloadClientUnavailable.
|
||||
|
||||
if (IsEpisodeProcessed(grabbed, report))
|
||||
{
|
||||
_pendingReleaseService.Add(report, PendingReleaseReason.Fallback);
|
||||
pending.Add(report);
|
||||
}
|
||||
else if (IsEpisodeProcessed(stored, report))
|
||||
{
|
||||
_pendingReleaseService.Add(report, PendingReleaseReason.Fallback);
|
||||
pending.Add(report);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pendingReleaseService.Add(report, PendingReleaseReason.DownloadClientUnavailable);
|
||||
pending.Add(report);
|
||||
stored.Add(report);
|
||||
}
|
||||
}
|
||||
|
||||
return pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,9 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
var response = _httpClient.Get(request);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.SeeOther || response.StatusCode == HttpStatusCode.Found)
|
||||
if (response.StatusCode == HttpStatusCode.MovedPermanently ||
|
||||
response.StatusCode == HttpStatusCode.Found ||
|
||||
response.StatusCode == HttpStatusCode.SeeOther)
|
||||
{
|
||||
var locationHeader = response.Headers.GetSingleValue("Location");
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
IHandle<EpisodeGrabbedEvent>,
|
||||
IHandle<EpisodeImportedEvent>
|
||||
{
|
||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||
private readonly IDownloadClientStatusService _downloadClientStatusService;
|
||||
private readonly IDownloadClientFactory _downloadClientFactory;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IManageCommandQueue _manageCommandQueue;
|
||||
private readonly IConfigService _configService;
|
||||
@@ -25,16 +26,18 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
private readonly Logger _logger;
|
||||
private readonly Debouncer _refreshDebounce;
|
||||
|
||||
public DownloadMonitoringService(IProvideDownloadClient downloadClientProvider,
|
||||
IEventAggregator eventAggregator,
|
||||
IManageCommandQueue manageCommandQueue,
|
||||
IConfigService configService,
|
||||
IFailedDownloadService failedDownloadService,
|
||||
ICompletedDownloadService completedDownloadService,
|
||||
ITrackedDownloadService trackedDownloadService,
|
||||
Logger logger)
|
||||
public DownloadMonitoringService(IDownloadClientStatusService downloadClientStatusService,
|
||||
IDownloadClientFactory downloadClientFactory,
|
||||
IEventAggregator eventAggregator,
|
||||
IManageCommandQueue manageCommandQueue,
|
||||
IConfigService configService,
|
||||
IFailedDownloadService failedDownloadService,
|
||||
ICompletedDownloadService completedDownloadService,
|
||||
ITrackedDownloadService trackedDownloadService,
|
||||
Logger logger)
|
||||
{
|
||||
_downloadClientProvider = downloadClientProvider;
|
||||
_downloadClientStatusService = downloadClientStatusService;
|
||||
_downloadClientFactory = downloadClientFactory;
|
||||
_eventAggregator = eventAggregator;
|
||||
_manageCommandQueue = manageCommandQueue;
|
||||
_configService = configService;
|
||||
@@ -56,7 +59,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
_refreshDebounce.Pause();
|
||||
try
|
||||
{
|
||||
var downloadClients = _downloadClientProvider.GetDownloadClients();
|
||||
var downloadClients = _downloadClientFactory.DownloadHandlingEnabled();
|
||||
|
||||
var trackedDownloads = new List<TrackedDownload>();
|
||||
|
||||
@@ -84,9 +87,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
try
|
||||
{
|
||||
downloadClientHistory = downloadClient.GetItems().ToList();
|
||||
|
||||
_downloadClientStatusService.RecordSuccess(downloadClient.Definition.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_downloadClientStatusService.RecordFailure(downloadClient.Definition.Id);
|
||||
_logger.Warn(ex, "Unable to retrieve queue and history items from " + downloadClient.Definition.Name);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Extras
|
||||
.Select(e => e.Trim(' ', '.'))
|
||||
.ToList();
|
||||
|
||||
var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName));
|
||||
var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
foreach (var matchingFilename in matchingFilenames)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Extras.Metadata.Files;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
@@ -103,7 +104,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
return metadata;
|
||||
}
|
||||
|
||||
if (filename.Equals("tvshow.nfo", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (filename.Equals("tvshow.nfo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
metadata.Type = MetadataType.SeriesMetadata;
|
||||
return metadata;
|
||||
@@ -113,7 +114,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
|
||||
if (parseResult != null &&
|
||||
!parseResult.FullSeason &&
|
||||
Path.GetExtension(filename) == ".nfo")
|
||||
Path.GetExtension(filename).Equals(".nfo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
metadata.Type = MetadataType.EpisodeMetadata;
|
||||
return metadata;
|
||||
@@ -247,7 +248,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
var video = new XElement("video");
|
||||
video.Add(new XElement("aspect", (float)episodeFile.MediaInfo.Width / (float)episodeFile.MediaInfo.Height));
|
||||
video.Add(new XElement("bitrate", episodeFile.MediaInfo.VideoBitrate));
|
||||
video.Add(new XElement("codec", episodeFile.MediaInfo.VideoCodec));
|
||||
video.Add(new XElement("codec", MediaInfoFormatter.FormatVideoCodec(episodeFile.MediaInfo, episodeFile.SceneName)));
|
||||
video.Add(new XElement("framerate", episodeFile.MediaInfo.VideoFps));
|
||||
video.Add(new XElement("height", episodeFile.MediaInfo.Height));
|
||||
video.Add(new XElement("scantype", episodeFile.MediaInfo.ScanType));
|
||||
@@ -264,11 +265,11 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
var audio = new XElement("audio");
|
||||
audio.Add(new XElement("bitrate", episodeFile.MediaInfo.AudioBitrate));
|
||||
audio.Add(new XElement("channels", episodeFile.MediaInfo.AudioChannels));
|
||||
audio.Add(new XElement("codec", GetAudioCodec(episodeFile.MediaInfo.AudioFormat)));
|
||||
audio.Add(new XElement("codec", MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo)));
|
||||
audio.Add(new XElement("language", episodeFile.MediaInfo.AudioLanguages));
|
||||
streamDetails.Add(audio);
|
||||
|
||||
if (episodeFile.MediaInfo.Subtitles != null && episodeFile.MediaInfo.Subtitles.Length > 0)
|
||||
if (episodeFile.MediaInfo.Subtitles.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var subtitle = new XElement("subtitle");
|
||||
subtitle.Add(new XElement("language", episodeFile.MediaInfo.Subtitles));
|
||||
@@ -379,15 +380,5 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
|
||||
{
|
||||
return Path.ChangeExtension(episodeFilePath, "").Trim('.') + "-thumb.jpg";
|
||||
}
|
||||
|
||||
private string GetAudioCodec(string audioCodec)
|
||||
{
|
||||
if (audioCodec == "AC-3")
|
||||
{
|
||||
return "AC3";
|
||||
}
|
||||
|
||||
return audioCodec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
@@ -65,7 +66,7 @@ namespace NzbDrone.Core.Extras.Others
|
||||
public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly)
|
||||
{
|
||||
// If the extension is .nfo we need to change it to .nfo-orig
|
||||
if (Path.GetExtension(path).Equals(".nfo"))
|
||||
if (Path.GetExtension(path).Equals(".nfo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
extension += "-orig";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Extras.Subtitles
|
||||
{
|
||||
@@ -8,7 +9,7 @@ namespace NzbDrone.Core.Extras.Subtitles
|
||||
|
||||
static SubtitleFileExtensions()
|
||||
{
|
||||
_fileExtensions = new HashSet<string>
|
||||
_fileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".aqt",
|
||||
".ass",
|
||||
|
||||
16
src/NzbDrone.Core/HealthCheck/CheckOnAttribute.cs
Normal file
16
src/NzbDrone.Core/HealthCheck/CheckOnAttribute.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.HealthCheck
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class CheckOnAttribute: Attribute
|
||||
{
|
||||
public Type EventType { get; set; }
|
||||
|
||||
public CheckOnAttribute(Type eventType)
|
||||
{
|
||||
EventType = eventType;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user