1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-14 15:44:53 -04:00

Compare commits

...

64 Commits

Author SHA1 Message Date
Taloth Saldono
4c460f6836 WIP: Added testcases for a bunch of Parsed titles. Added support for At & Percent conversion. 2017-07-02 21:29:01 +02:00
Taloth Saldono
b03f434329 Disable Nyaa forcibly. 2017-07-02 21:27:59 +02:00
Chris Dyson
68290b0385 New: Added 'Series Title, The' renaming option
Closes #371
2017-07-02 21:21:46 +02:00
Taloth Saldono
773274f3cc Update Nyaa Pantsu apiPath to the actual /feed/torznab url. 2017-07-01 12:43:30 +02:00
Taloth Saldono
a33d8968ed New: Added Nyaa Pantsu as Torznab preset for Anime. 2017-07-01 00:08:01 +02:00
Taloth Saldono
e41baccd02 New: Added AnimeTosho as Newznab and Torznab presets. 2017-06-30 23:40:16 +02:00
Taloth Saldono
fef3423019 Fixed error in NzbGet KeepHistory check and updated tests. 2017-06-30 22:01:31 +02:00
Taloth Saldono
11926d8b2d Fixed: Support for Mono 5.x with the newer BoringTLS provider. 2017-06-30 21:33:41 +02:00
Mark McDowall
4189bc6f76 Webhook improvements
New: Include Path/Relative Path for on download Webhooks
New: IsUpgrade flag for on download Webhooks
2017-06-28 16:11:05 -07:00
Mark McDowall
601b54c244 Reorder HttpMethods to match RestSharp 2017-06-28 16:11:05 -07:00
Taloth Saldono
905ab18336 Fixed: Subtitle extensions should be case-insensitive. 2017-06-27 16:22:07 +02:00
Taloth Saldono
f46a576df5 Check if NzbGet KeepHistory value is set too high instead of only checking for 0. 2017-06-27 16:22:07 +02:00
Mark McDowall
60231fbcae Fixed: Show rounded age in minimum age rejection message
Fixes #2003
2017-06-26 22:41:22 -07:00
Mark McDowall
63860e783e Fixed: Background logo when URL base is used
Fixes #2002
2017-06-26 22:29:52 -07:00
Mark McDowall
cf8b9df5ad Fixed: Ignore case when importing extra files 2017-06-26 22:25:10 -07:00
Taloth Saldono
ff6841e410 Fixed: Added permanent Health Check warning to ensure Drone Factory is no longer used. 2017-06-21 19:35:32 +02:00
Taloth Saldono
ab49afe116 Don't log error on the shutdown the command execution pipeline.
closes #1961
2017-06-20 17:35:37 +02:00
Taloth Saldono
afcf136c4f Fixed regression in pending icon. 2017-06-20 17:23:01 +02:00
Taloth Saldono
d1b85444d0 Fixed DetectSampleFixture. 2017-06-19 23:48:47 +02:00
Taloth Saldono
63e0eba02f Fixed: releases with unknown seeders show on the UI as - instead of 0 to be easier to distinguish. 2017-06-19 23:30:43 +02:00
Taloth Saldono
475c99d492 Tweaked Newznab/Torznab handling of attr without value. 2017-06-19 23:30:43 +02:00
Taloth Saldono
23552c3267 Tweaked SingleInstancePolicy not to cancel startup if AppData is overridden is set. 2017-06-19 23:30:43 +02:00
Mark McDowall
a49e37239e Log responses from qbit 2017-06-18 22:21:33 -07:00
Mark McDowall
65b936ed94 Fixed: Time left cell for pending items in queue 2017-06-18 22:15:44 -07:00
Mark McDowall
359ef04861 Fixed: Grab/Delete buttons for pending releases in queue 2017-06-18 22:10:52 -07:00
Mark McDowall
8cf028e071 Fixed: Improve sample rejection message when MediaInfo is not available
Closes #1967
2017-06-18 21:26:18 -07:00
Mark McDowall
eea3419849 New: Download client and ID for custom scripts 2017-06-18 21:17:33 -07:00
Taloth Saldono
62bc63312d Fixed: Changed Authentication cookie to prevent conflicts with other apps. (invalidates existing logins)
Closes #1962
2017-06-18 00:18:23 +02:00
Taloth Saldono
6a6d415625 Fixed: Pending releases from blocked indexers should not be grabbed.
ref #1961
2017-06-18 00:07:16 +02:00
Taloth Saldono
1fbe82ae47 Prevent back-off escalation during grace period. 2017-06-17 23:42:04 +02:00
Taloth Saldono
87f3cc9014 Fixed: Regression prevented indexers from being re-enabled after a successful Test.
ref #1961
2017-06-17 23:41:38 +02:00
Taloth Saldono
ab07a40931 New: Added Omgwtfnzbs UHD category.
closes #1977
2017-06-17 20:55:18 +02:00
Taloth Saldono
10f292b225 Removed superfluous try catches so that DownloadClient backoff logic gets triggered. 2017-06-17 20:53:26 +02:00
Mark McDowall
de5ce23989 Fixed: Redirect calls missing URL Base 2017-06-10 16:47:12 -07:00
Mark McDowall
d0e226e269 Fixed: Logging full error message to database 2017-06-10 16:25:14 -07:00
Mark McDowall
d7cb5090fc Fixed: Twitter oAuth callback URL 2017-06-05 20:22:37 -07:00
Mark McDowall
416e9abca5 Fixed: Error message when adding a Plex server without a TV library 2017-06-04 21:10:56 -07:00
Mark McDowall
6dcb7768a9 Fixed broken test and add a couple more for ProcessDownloadDecisions 2017-06-04 00:52:34 -07:00
Mark McDowall
baf83b4c71 Fixed: Error when processing manual import decisions
Fixes #1590
2017-06-03 21:10:56 -07:00
Mark McDowall
285288db1a Additional logging when an import decision cannot be made 2017-06-03 21:10:56 -07:00
Jeremy Ryan
ec3dd982f6 Update EpisodeFileEditorLayoutTemplate.hbs 2017-06-03 21:10:20 -07:00
Drew Freyling
410aa467b8 include css files in minification 2017-06-03 21:08:35 -07:00
Mark McDowall
0c89a4ae8f Store releases when download client is unavailable
New: Retry releases when download client was unavailable
Closes #949
2017-06-03 20:41:32 -07:00
Taloth Saldono
a1edbafa8a Removed ugly UUID= VolumeLabel from mounts. 2017-05-27 22:26:41 +02:00
Taloth Saldono
ef5a400c68 Added missing ACC audio format. 2017-05-27 22:19:55 +02:00
Taloth Saldono
8cc02a9d9c Fixed: Minimum seeding check causing exception when release was pushed via api instead of by indexer. 2017-05-27 22:02:30 +02:00
Taloth Saldono
e83e852e0d Added ability for HealthChecks to run on specific events. 2017-05-27 20:45:01 +02:00
Taloth Saldono
4e10d30cf6 Added Status refreshes to Download Monitoring Service and allow DownloadService to report success (but not failure). 2017-05-27 20:44:58 +02:00
Taloth Saldono
f335cc1af8 Fixed: Prevent Download Client from being queried every minute if it failed repeatedly similar to Indexer temporarily disabled logic. 2017-05-27 20:44:55 +02:00
Taloth Saldono
f4bea5512c Refactored IndexerStatusService into Thingy Provider architecture. 2017-05-27 14:59:37 +02:00
Taloth Saldono
9f8091e4d7 Fixed: Added wildcard to BTN season searches to pick up 'Season x - Episodes 1-10' formats.
Closes #1946
2017-05-26 15:50:34 +02:00
Mark McDowall
5aa02eb15c Cleanup/fix EpisodeMonitoredService
Fixed: Unmonitor episodes when the season is unmonitored when adding the series
Fixes #1852
2017-05-23 22:04:56 -07:00
Mark McDowall
6bbe4ce066 Fixed: Ensure an API Key is set when starting Sonarr
Closes #1514
2017-05-22 21:54:18 -07:00
Mark McDowall
563b5ef017 Fixed: Don't use invalid scene mappings. Fixes #1627 2017-05-22 21:39:13 -07:00
Mark McDowall
b9df5634bf New: Link to more information on RSS sync interval
Closes #1918
2017-05-22 20:55:30 -07:00
Mark McDowall
755575d107 Fixed: Follow 301 redirects when fetching torrents
Closes #1929
2017-05-22 18:09:59 -07:00
Taloth Saldono
8eaab46488 Fixed up some errors and do the guid cache fix on the module instead of backend coz that would cause other issues. 2017-05-21 22:01:03 +02:00
Taloth Saldono
4fbc481780 Fixed: Processing of mixed newznab/torznab api such as the experimental animetosho api.
Ref #1384
2017-05-21 21:10:54 +02:00
Mark McDowall
a41b5723d4 New: Ability to set minimum seeders on a per indexer basis 2017-05-19 12:08:30 -07:00
Mark McDowall
c2b66cf524 Fixed: Deleting an episode file from the UI that was already deleted from disk
Fixes #1782
2017-05-19 12:07:57 -07:00
Mark McDowall
0d782e1cac Consistent formatting for MediaInfo in various locations
Fixed: Stream details for MP3 and EAC3 in Kodi metadata
Closes #1534
2017-05-19 11:18:50 -07:00
Mark McDowall
edf549d0fd Fixed: Improved message when a conflicting slug is added 2017-05-17 21:42:57 -07:00
Mark McDowall
cf7ce9804f Fixed: Ignore file quality matching release quality for unknown quality releases 2017-05-17 21:15:21 -07:00
Mark McDowall
e8d01daf03 Fixed: Ignore file quality matching release quality for season packs 2017-05-15 23:58:07 -07:00
217 changed files with 3963 additions and 1492 deletions

View File

@@ -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());
});

View File

@@ -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',

View File

@@ -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",

View File

@@ -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)

View File

@@ -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);
}

View 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;
}
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -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" />

View File

@@ -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
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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.

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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>

View File

@@ -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]

View File

@@ -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()

View File

@@ -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))));

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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()
{

View File

@@ -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);

View File

@@ -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()
{

View File

@@ -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>()))

View File

@@ -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);
}
}
}

View File

@@ -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");

View File

@@ -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()
{

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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]

View File

@@ -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();

View File

@@ -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>

View File

@@ -16,4 +16,4 @@ namespace NzbDrone.Core.Test.OrganizerTests
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -14,4 +14,4 @@ namespace NzbDrone.Core.Test.ThingiProviderTests
Subject.Validate().IsValid.Should().BeTrue();
}
}
}
}

View File

@@ -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());
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>()

View File

@@ -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
{

View File

@@ -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;
}

View File

@@ -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())
{

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View 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%';");
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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)
{
}
}
}

View File

@@ -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);

View File

@@ -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}");
}
}

View File

@@ -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);
}
}

View File

@@ -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>();

View File

@@ -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)

View File

@@ -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");
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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");
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,9 @@
using NzbDrone.Core.ThingiProvider.Status;
namespace NzbDrone.Core.Download
{
public class DownloadClientStatus : ProviderStatusBase
{
}
}

View 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)
{
}
}
}

View 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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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; }

View File

@@ -0,0 +1,9 @@
namespace NzbDrone.Core.Download.Pending
{
public enum PendingReleaseReason
{
Delay = 0,
DownloadClientUnavailable = 1,
Fallback = 2
}
}

View File

@@ -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());

View File

@@ -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;
}
}
}

View File

@@ -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");

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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;
}
}
}

View File

@@ -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";
}

View File

@@ -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",

View 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