mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
19 Commits
v4.4.4.706
...
v0.2.0.145
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
080d0a9b04 | ||
|
|
1b58ae7d47 | ||
|
|
a5e2e5777c | ||
|
|
ddc2b42923 | ||
|
|
3ac3737de9 | ||
|
|
01ad015b14 | ||
|
|
3d57d5aba7 | ||
|
|
d65fe3a530 | ||
|
|
e100759e71 | ||
|
|
df068e9f0a | ||
|
|
c27f08738a | ||
|
|
61629a527c | ||
|
|
5291f42905 | ||
|
|
0ce5857094 | ||
|
|
443078a7e4 | ||
|
|
dbdda0da13 | ||
|
|
b5e1b83de3 | ||
|
|
8e43f5c4ae | ||
|
|
e6d3954e79 |
@@ -154,9 +154,9 @@ workflows:
|
||||
- unit_tests:
|
||||
requires:
|
||||
- build
|
||||
#- integration_tests:
|
||||
# requires:
|
||||
# - build
|
||||
- integration_tests:
|
||||
requires:
|
||||
- build
|
||||
- publish_artifacts:
|
||||
requires:
|
||||
- build
|
||||
|
||||
9900
package-lock.json
generated
9900
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Radarr",
|
||||
"version": "2.0.0",
|
||||
"name": "radarr",
|
||||
"version": "1.0.0",
|
||||
"description": "Radarr",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
@@ -23,7 +23,7 @@ namespace NzbDrone.Api.Extensions.Pipelines
|
||||
_logger = logger;
|
||||
|
||||
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
|
||||
_writeGZipStream = OsInfo.IsMonoRuntime ? WriteGZipStreamMono : (Action<Action<Stream>, Stream>)WriteGZipStream;
|
||||
_writeGZipStream = PlatformInfo.IsMono ? WriteGZipStreamMono : (Action<Action<Stream>, Stream>)WriteGZipStream;
|
||||
}
|
||||
|
||||
public void Register(IPipelines pipelines)
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Api.Frontend
|
||||
{
|
||||
public bool IsCacheable(NancyContext context)
|
||||
{
|
||||
if (!RuntimeInfoBase.IsProduction)
|
||||
if (!RuntimeInfo.IsProduction)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -46,4 +46,4 @@ namespace NzbDrone.Api.Frontend
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
|
||||
private string GetIndexText()
|
||||
{
|
||||
if (RuntimeInfoBase.IsProduction && _generatedContent != null)
|
||||
if (RuntimeInfo.IsProduction && _generatedContent != null)
|
||||
{
|
||||
return _generatedContent;
|
||||
}
|
||||
@@ -111,7 +111,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
text = text.Replace("APP_BRANCH", _configFileProvider.Branch.ToLower());
|
||||
text = text.Replace("APP_ANALYTICS", _analyticsService.IsEnabled.ToString().ToLowerInvariant());
|
||||
text = text.Replace("URL_BASE", URL_BASE);
|
||||
text = text.Replace("PRODUCTION", RuntimeInfoBase.IsProduction.ToString().ToLowerInvariant());
|
||||
text = text.Replace("PRODUCTION", RuntimeInfo.IsProduction.ToString().ToLowerInvariant());
|
||||
|
||||
_generatedContent = text;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
|
||||
private string GetLoginText()
|
||||
{
|
||||
if (RuntimeInfoBase.IsProduction && _generatedContent != null)
|
||||
if (RuntimeInfo.IsProduction && _generatedContent != null)
|
||||
{
|
||||
return _generatedContent;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
_diskProvider = diskProvider;
|
||||
_logger = logger;
|
||||
|
||||
if (!RuntimeInfoBase.IsProduction)
|
||||
if (!RuntimeInfo.IsProduction)
|
||||
{
|
||||
_caseSensitive = StringComparison.OrdinalIgnoreCase;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Nancy.Bootstrapper;
|
||||
using Nancy.Diagnostics;
|
||||
using NLog;
|
||||
@@ -24,9 +24,9 @@ namespace NzbDrone.Api
|
||||
|
||||
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
|
||||
{
|
||||
Logger.Info("Starting NzbDrone API");
|
||||
Logger.Info("Starting Web Server");
|
||||
|
||||
if (RuntimeInfoBase.IsProduction)
|
||||
if (RuntimeInfo.IsProduction)
|
||||
{
|
||||
DiagnosticsHook.Disable(pipelines);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Nancy;
|
||||
using Nancy;
|
||||
using Nancy.Routing;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -13,6 +13,8 @@ namespace NzbDrone.Api.System
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IPlatformInfo _platformInfo;
|
||||
private readonly IOsInfo _osInfo;
|
||||
private readonly IRouteCacheProvider _routeCacheProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IMainDatabase _database;
|
||||
@@ -20,14 +22,17 @@ namespace NzbDrone.Api.System
|
||||
|
||||
public SystemModule(IAppFolderInfo appFolderInfo,
|
||||
IRuntimeInfo runtimeInfo,
|
||||
IPlatformInfo platformInfo,
|
||||
IOsInfo osInfo,
|
||||
IRouteCacheProvider routeCacheProvider,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IMainDatabase database,
|
||||
ILifecycleService lifecycleService)
|
||||
: base("system")
|
||||
ILifecycleService lifecycleService) : base("system")
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_platformInfo = platformInfo;
|
||||
_osInfo = osInfo;
|
||||
_routeCacheProvider = routeCacheProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
_database = database;
|
||||
@@ -41,27 +46,29 @@ namespace NzbDrone.Api.System
|
||||
private Response GetStatus()
|
||||
{
|
||||
return new
|
||||
{
|
||||
Version = BuildInfo.Version.ToString(),
|
||||
BuildTime = BuildInfo.BuildDateTime,
|
||||
IsDebug = BuildInfo.IsDebug,
|
||||
IsProduction = RuntimeInfoBase.IsProduction,
|
||||
IsAdmin = _runtimeInfo.IsAdmin,
|
||||
IsUserInteractive = RuntimeInfoBase.IsUserInteractive,
|
||||
StartupPath = _appFolderInfo.StartUpFolder,
|
||||
AppData = _appFolderInfo.GetAppDataPath(),
|
||||
OsVersion = OsInfo.Version.ToString(),
|
||||
IsMonoRuntime = OsInfo.IsMonoRuntime,
|
||||
IsMono = OsInfo.IsNotWindows,
|
||||
IsLinux = OsInfo.IsLinux,
|
||||
IsOsx = OsInfo.IsOsx,
|
||||
IsWindows = OsInfo.IsWindows,
|
||||
Branch = _configFileProvider.Branch,
|
||||
Authentication = _configFileProvider.AuthenticationMethod,
|
||||
SqliteVersion = _database.Version,
|
||||
UrlBase = _configFileProvider.UrlBase,
|
||||
RuntimeVersion = _runtimeInfo.RuntimeVersion
|
||||
}.AsResponse();
|
||||
{
|
||||
Version = BuildInfo.Version.ToString(),
|
||||
BuildTime = BuildInfo.BuildDateTime,
|
||||
IsDebug = BuildInfo.IsDebug,
|
||||
IsProduction = RuntimeInfo.IsProduction,
|
||||
IsAdmin = _runtimeInfo.IsAdmin,
|
||||
IsUserInteractive = RuntimeInfo.IsUserInteractive,
|
||||
StartupPath = _appFolderInfo.StartUpFolder,
|
||||
AppData = _appFolderInfo.GetAppDataPath(),
|
||||
OsName = _osInfo.Name,
|
||||
OsVersion = _osInfo.Version,
|
||||
IsMonoRuntime = PlatformInfo.IsMono,
|
||||
IsMono = PlatformInfo.IsMono,
|
||||
IsLinux = OsInfo.IsLinux,
|
||||
IsOsx = OsInfo.IsOsx,
|
||||
IsWindows = OsInfo.IsWindows,
|
||||
Branch = _configFileProvider.Branch,
|
||||
Authentication = _configFileProvider.AuthenticationMethod,
|
||||
SqliteVersion = _database.Version,
|
||||
UrlBase = _configFileProvider.UrlBase,
|
||||
RuntimeVersion = _platformInfo.Version,
|
||||
RuntimeName = PlatformInfo.Platform
|
||||
}.AsResponse();
|
||||
}
|
||||
|
||||
private Response GetRoutes()
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace NzbDrone.Api.Wanted
|
||||
{
|
||||
protected readonly IMovieService _movieService;
|
||||
|
||||
public MovieMissingModule(IMovieService movieService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
public MovieMissingModule(IMovieService movieService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(movieService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing")
|
||||
{
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Common.Test
|
||||
[Test]
|
||||
public void IsProduction_should_return_false_when_run_within_nunit()
|
||||
{
|
||||
RuntimeInfoBase.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName + " Folder is " + Directory.GetCurrentDirectory());
|
||||
RuntimeInfo.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName + " Folder is " + Directory.GetCurrentDirectory());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -9,6 +9,7 @@ using Moq;
|
||||
using NLog;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Dispatchers;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
@@ -30,6 +31,12 @@ namespace NzbDrone.Common.Test.Http
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Mocker.GetMock<IPlatformInfo>().Setup(c => c.Version).Returns(new Version("1.0.0"));
|
||||
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
|
||||
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
|
||||
|
||||
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
|
||||
|
||||
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
|
||||
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
|
||||
@@ -48,7 +55,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_execute_simple_get()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Execute(request);
|
||||
|
||||
@@ -58,7 +65,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_execute_https_get()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("https://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Execute(request);
|
||||
|
||||
@@ -68,11 +75,12 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_execute_typed_get()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get?test=1");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
response.Resource.Url.Should().Be(request.Url.FullUri);
|
||||
response.Resource.Url.EndsWith("/get?test=1");
|
||||
response.Resource.Args.Should().Contain("test", "1");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -80,7 +88,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
var message = "{ my: 1 }";
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/post", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/post");
|
||||
request.SetContent(message);
|
||||
|
||||
var response = Subject.Post<HttpBinResource>(request);
|
||||
@@ -91,7 +99,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("gzip")]
|
||||
public void should_execute_get_using_gzip(string compression)
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/{1}", _httpBinHost, compression));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/{compression}");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -107,7 +115,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase(HttpStatusCode.BadGateway)]
|
||||
public void should_throw_on_unsuccessful_status_codes(int statusCode)
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/status/{1}", _httpBinHost, statusCode));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/status/{statusCode}");
|
||||
|
||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
|
||||
@@ -119,7 +127,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_not_follow_redirects_when_not_in_production()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/redirect/1", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
|
||||
|
||||
Subject.Get(request);
|
||||
|
||||
@@ -129,7 +137,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_follow_redirects()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/redirect/1", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
|
||||
request.AllowAutoRedirect = true;
|
||||
|
||||
var response = Subject.Get(request);
|
||||
@@ -182,7 +190,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_send_user_agent()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -196,7 +204,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")]
|
||||
public void should_send_headers(string header, string value)
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
request.Headers.Add(header, value);
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
@@ -219,7 +227,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_send_cookie()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
request.Cookies["my"] = "cookie";
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
@@ -236,7 +244,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
|
||||
oldRequest.Cookies["my"] = "cookie";
|
||||
|
||||
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.Resolve<Logger>());
|
||||
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.GetMock<IUserAgentBuilder>().Object, Mocker.Resolve<Logger>());
|
||||
|
||||
oldClient.Should().NotBeSameAs(Subject);
|
||||
|
||||
@@ -329,7 +337,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_not_store_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost));
|
||||
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreRequestCookie = false;
|
||||
requestSet.StoreResponseCookie.Should().BeFalse();
|
||||
@@ -348,7 +356,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_store_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost));
|
||||
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreRequestCookie = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
@@ -514,7 +522,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
public void should_not_send_old_cookie()
|
||||
{
|
||||
GivenOldCookie();
|
||||
|
||||
|
||||
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
|
||||
requestCookies.IgnorePersistentCookies = true;
|
||||
requestCookies.StoreRequestCookie = false;
|
||||
@@ -527,7 +535,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_throw_on_http429_too_many_requests()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/status/429", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/status/429");
|
||||
|
||||
Assert.Throws<TooManyRequestsException>(() => Subject.Get(request));
|
||||
|
||||
@@ -547,7 +555,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
.Setup(v => v.PostResponse(It.IsAny<HttpResponse>()))
|
||||
.Returns<HttpResponse>(r => r);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
Subject.Get(request);
|
||||
|
||||
@@ -569,7 +577,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
// the date is bad in the below - should be 13-Jul-2026
|
||||
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
|
||||
var requestSet = new HttpRequestBuilder(string.Format("http://{0}/response-headers", _httpBinHost))
|
||||
var requestSet = new HttpRequestBuilder($"http://{_httpBinHost}/response-headers")
|
||||
.AddQueryParam("Set-Cookie", malformedCookie)
|
||||
.Build();
|
||||
|
||||
@@ -578,7 +586,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -602,7 +610,8 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = string.Format("http://{0}/response-headers?Set-Cookie={1}", _httpBinHost, Uri.EscapeUriString(malformedCookie));
|
||||
string url =
|
||||
$"http://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}";
|
||||
|
||||
var requestSet = new HttpRequest(url);
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
@@ -610,7 +619,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -626,6 +635,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
public class HttpBinResource
|
||||
{
|
||||
public Dictionary<string, object> Args { get; set; }
|
||||
public Dictionary<string, object> Headers { get; set; }
|
||||
public string Origin { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
30
src/NzbDrone.Common.Test/Http/UserAgentBuilderFixture.cs
Normal file
30
src/NzbDrone.Common.Test/Http/UserAgentBuilderFixture.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
[TestFixture]
|
||||
public class UserAgentBuilderFixture : TestBase<UserAgentBuilder>
|
||||
{
|
||||
[Test]
|
||||
public void should_get_user_agent_if_os_version_is_null()
|
||||
{
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Version).Returns((string)null);
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Name).Returns("TestOS");
|
||||
|
||||
Subject.GetUserAgent(false).Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_use_os_family_if_name_is_null()
|
||||
{
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Version).Returns((string)null);
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Name).Returns((string)null);
|
||||
|
||||
Subject.GetUserAgent(false).Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,7 @@
|
||||
<Compile Include="Http\HttpRequestBuilderFixture.cs" />
|
||||
<Compile Include="Http\HttpRequestFixture.cs" />
|
||||
<Compile Include="Http\HttpUriFixture.cs" />
|
||||
<Compile Include="Http\UserAgentBuilderFixture.cs" />
|
||||
<Compile Include="InstrumentationTests\CleanseLogMessageFixture.cs" />
|
||||
<Compile Include="LevenshteinDistanceFixture.cs" />
|
||||
<Compile Include="OsPathFixture.cs" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -13,12 +13,15 @@ namespace NzbDrone.Common.Composition
|
||||
{
|
||||
private readonly List<Type> _loadedTypes;
|
||||
|
||||
public IContainer Container { get; private set; }
|
||||
protected IContainer Container { get; }
|
||||
|
||||
protected ContainerBuilderBase(IStartupContext args, params string[] assemblies)
|
||||
protected ContainerBuilderBase(IStartupContext args, List<string> assemblies)
|
||||
{
|
||||
_loadedTypes = new List<Type>();
|
||||
|
||||
assemblies.Add(OsInfo.IsWindows ? "NzbDrone.Windows" : "NzbDrone.Mono");
|
||||
assemblies.Add("NzbDrone.Common");
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
_loadedTypes.AddRange(Assembly.Load(assembly).GetTypes());
|
||||
|
||||
@@ -17,6 +17,19 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProviderBase));
|
||||
|
||||
public static StringComparison PathStringComparison
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
return StringComparison.OrdinalIgnoreCase;
|
||||
}
|
||||
|
||||
return StringComparison.Ordinal;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract long? GetAvailableSpace(string path);
|
||||
public abstract void InheritFolderPermissions(string filename);
|
||||
public abstract void SetPermissions(string path, string mask, string user, string group);
|
||||
@@ -87,7 +100,7 @@ namespace NzbDrone.Common.Disk
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
return FileExists(path, OsInfo.PathStringComparison);
|
||||
return FileExists(path, PathStringComparison);
|
||||
}
|
||||
|
||||
public bool FileExists(string path, StringComparison stringComparison)
|
||||
|
||||
@@ -101,12 +101,12 @@ namespace NzbDrone.Common.EnsureThat
|
||||
|
||||
if (param.Value.IsPathValid()) return param;
|
||||
|
||||
if (OsInfo.IsNotWindows)
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid *nix path. paths must start with /", param.Value));
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. paths must be a full path eg. C:\\Windows", param.Value));
|
||||
}
|
||||
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. paths must be a full path eg. C:\\Windows", param.Value));
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid *nix path. paths must start with /", param.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public interface IOperatingSystemVersionInfo
|
||||
{
|
||||
string Version { get; }
|
||||
string Name { get; }
|
||||
string FullName { get; }
|
||||
}
|
||||
}
|
||||
9
src/NzbDrone.Common/EnvironmentInfo/IOsVersionAdapter.cs
Normal file
9
src/NzbDrone.Common/EnvironmentInfo/IOsVersionAdapter.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
|
||||
public interface IOsVersionAdapter
|
||||
{
|
||||
bool Enabled { get; }
|
||||
OsVersionModel Read();
|
||||
}
|
||||
}
|
||||
50
src/NzbDrone.Common/EnvironmentInfo/IPlatformInfo.cs
Normal file
50
src/NzbDrone.Common/EnvironmentInfo/IPlatformInfo.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
|
||||
public enum PlatformType
|
||||
{
|
||||
DotNet = 0,
|
||||
Mono = 1
|
||||
}
|
||||
|
||||
public interface IPlatformInfo
|
||||
{
|
||||
Version Version { get; }
|
||||
}
|
||||
|
||||
public abstract class PlatformInfo : IPlatformInfo
|
||||
{
|
||||
static PlatformInfo()
|
||||
{
|
||||
if (Type.GetType("Mono.Runtime") != null)
|
||||
{
|
||||
Platform = PlatformType.Mono;
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform = PlatformType.DotNet;
|
||||
}
|
||||
}
|
||||
|
||||
public static PlatformType Platform { get; }
|
||||
public static bool IsMono => Platform == PlatformType.Mono;
|
||||
public static bool IsDotNet => Platform == PlatformType.DotNet;
|
||||
|
||||
public static string PlatformName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDotNet)
|
||||
{
|
||||
return ".NET";
|
||||
}
|
||||
|
||||
return "Mono";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Version Version { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public interface IRuntimeInfo
|
||||
{
|
||||
bool IsUserInteractive { get; }
|
||||
bool IsAdmin { get; }
|
||||
bool IsWindowsService { get; }
|
||||
bool IsConsole { get; }
|
||||
bool IsRunning { get; set; }
|
||||
bool IsWindowsTray { get; }
|
||||
bool IsExiting { get; set; }
|
||||
bool RestartPending { get; set; }
|
||||
string ExecutingApplication { get; }
|
||||
string RuntimeVersion { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,95 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public static class OsInfo
|
||||
public class OsInfo : IOsInfo
|
||||
{
|
||||
public static Os Os { get; }
|
||||
|
||||
public static bool IsNotWindows => !IsWindows;
|
||||
public static bool IsLinux => Os == Os.Linux;
|
||||
public static bool IsOsx => Os == Os.Osx;
|
||||
public static bool IsWindows => Os == Os.Windows;
|
||||
|
||||
public string Version { get; }
|
||||
public string Name { get; }
|
||||
public string FullName { get; }
|
||||
|
||||
static OsInfo()
|
||||
{
|
||||
var platform = (int)Environment.OSVersion.Platform;
|
||||
var platform = Environment.OSVersion.Platform;
|
||||
|
||||
Version = Environment.OSVersion.Version;
|
||||
|
||||
IsMonoRuntime = Type.GetType("Mono.Runtime") != null;
|
||||
IsNotWindows = (platform == 4) || (platform == 6) || (platform == 128);
|
||||
IsOsx = IsRunningOnMac();
|
||||
IsLinux = IsNotWindows && !IsOsx;
|
||||
IsWindows = !IsNotWindows;
|
||||
|
||||
FirstDayOfWeek = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
|
||||
|
||||
if (IsWindows)
|
||||
switch (platform)
|
||||
{
|
||||
Os = Os.Windows;
|
||||
PathStringComparison = StringComparison.OrdinalIgnoreCase;
|
||||
case PlatformID.Win32NT:
|
||||
{
|
||||
Os = Os.Windows;
|
||||
break;
|
||||
}
|
||||
case PlatformID.MacOSX:
|
||||
case PlatformID.Unix:
|
||||
{
|
||||
// Sometimes Mac OS reports itself as Unix
|
||||
if (Directory.Exists("/System/Library/CoreServices/") &&
|
||||
(File.Exists("/System/Library/CoreServices/SystemVersion.plist") ||
|
||||
File.Exists("/System/Library/CoreServices/ServerVersion.plist"))
|
||||
)
|
||||
{
|
||||
Os = Os.Osx;
|
||||
}
|
||||
else
|
||||
{
|
||||
Os = Os.Linux;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OsInfo(IEnumerable<IOsVersionAdapter> versionAdapters, Logger logger)
|
||||
{
|
||||
OsVersionModel osInfo = null;
|
||||
|
||||
foreach (var osVersionAdapter in versionAdapters.Where(c => c.Enabled))
|
||||
{
|
||||
try
|
||||
{
|
||||
osInfo = osVersionAdapter.Read();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "Couldn't get OS Version info");
|
||||
}
|
||||
|
||||
if (osInfo != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (osInfo != null)
|
||||
{
|
||||
Name = osInfo.Name;
|
||||
Version = osInfo.Version;
|
||||
FullName = osInfo.FullName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Os = IsOsx ? Os.Osx : Os.Linux;
|
||||
|
||||
PathStringComparison = StringComparison.Ordinal;
|
||||
Name = Os.ToString();
|
||||
FullName = Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Version Version { get; private set; }
|
||||
public static bool IsMonoRuntime { get; private set; }
|
||||
public static bool IsNotWindows { get; private set; }
|
||||
public static bool IsLinux { get; private set; }
|
||||
public static bool IsOsx { get; private set; }
|
||||
public static bool IsWindows { get; private set; }
|
||||
public static Os Os { get; private set; }
|
||||
public static DayOfWeek FirstDayOfWeek { get; private set; }
|
||||
public static StringComparison PathStringComparison { get; private set; }
|
||||
|
||||
//Borrowed from: https://github.com/jpobst/Pinta/blob/master/Pinta.Core/Managers/SystemManager.cs
|
||||
//From Managed.Windows.Forms/XplatUI
|
||||
[DllImport("libc")]
|
||||
static extern int uname(IntPtr buf);
|
||||
|
||||
[DebuggerStepThrough]
|
||||
static bool IsRunningOnMac()
|
||||
{
|
||||
var buf = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
buf = Marshal.AllocHGlobal(8192);
|
||||
// This is a hacktastic way of getting sysname from uname ()
|
||||
if (uname(buf) == 0)
|
||||
{
|
||||
var os = Marshal.PtrToStringAnsi(buf);
|
||||
|
||||
if (os == "Darwin")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (buf != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(buf);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public interface IOsInfo
|
||||
{
|
||||
string Version { get; }
|
||||
string Name { get; }
|
||||
string FullName { get; }
|
||||
}
|
||||
|
||||
public enum Os
|
||||
@@ -90,4 +98,4 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
Linux,
|
||||
Osx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
src/NzbDrone.Common/EnvironmentInfo/OsVersionModel.cs
Normal file
29
src/NzbDrone.Common/EnvironmentInfo/OsVersionModel.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public class OsVersionModel
|
||||
{
|
||||
|
||||
|
||||
public OsVersionModel(string name, string version, string fullName = null)
|
||||
{
|
||||
Name = Trim(name);
|
||||
Version = Trim(version);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fullName))
|
||||
{
|
||||
fullName = $"{Name} {Version}";
|
||||
}
|
||||
|
||||
FullName = Trim(fullName);
|
||||
}
|
||||
|
||||
private static string Trim(string source)
|
||||
{
|
||||
return source.Trim().Trim('"', '\'');
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string FullName { get; }
|
||||
public string Version { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
@@ -9,11 +9,11 @@ using NzbDrone.Common.Processes;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public abstract class RuntimeInfoBase : IRuntimeInfo
|
||||
public class RuntimeInfo : IRuntimeInfo
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RuntimeInfoBase(IServiceProvider serviceProvider, Logger logger)
|
||||
public RuntimeInfo(IServiceProvider serviceProvider, Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
@@ -28,10 +28,11 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
if (entry != null)
|
||||
{
|
||||
ExecutingApplication = entry.Location;
|
||||
IsWindowsTray = entry.ManifestModule.Name == $"{ProcessProvider.NZB_DRONE_PROCESS_NAME}.exe";
|
||||
}
|
||||
}
|
||||
|
||||
static RuntimeInfoBase()
|
||||
static RuntimeInfo()
|
||||
{
|
||||
IsProduction = InternalIsProduction();
|
||||
}
|
||||
@@ -59,31 +60,18 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
public bool IsWindowsService { get; private set; }
|
||||
|
||||
public bool IsConsole
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
return IsUserInteractive && Process.GetCurrentProcess().ProcessName.Equals(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRunning { get; set; }
|
||||
public bool IsExiting { get; set; }
|
||||
public bool RestartPending { get; set; }
|
||||
public string ExecutingApplication { get; private set; }
|
||||
public string ExecutingApplication { get; }
|
||||
|
||||
public abstract string RuntimeVersion { get; }
|
||||
|
||||
public static bool IsProduction { get; private set; }
|
||||
public static bool IsProduction { get; }
|
||||
|
||||
private static bool InternalIsProduction()
|
||||
{
|
||||
if (BuildInfo.IsDebug || Debugger.IsAttached) return false;
|
||||
if (BuildInfo.Version.Revision > 10000) return false; //Official builds will never have such a high revision
|
||||
|
||||
//Official builds will never have such a high revision
|
||||
if (BuildInfo.Version.Revision > 10000) return false;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -99,21 +87,23 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var currentAssmeblyLocation = typeof(RuntimeInfoBase).Assembly.Location;
|
||||
if(currentAssmeblyLocation.ToLower().Contains("_output"))return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentAssmeblyLocation = typeof(RuntimeInfo).Assembly.Location;
|
||||
if (currentAssmeblyLocation.ToLower().Contains("_output")) return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
string lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
|
||||
var lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
|
||||
if (lowerCurrentDir.Contains("teamcity")) return false;
|
||||
if (lowerCurrentDir.Contains("_output")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsWindowsTray { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
public static class ExceptionExtentions
|
||||
{
|
||||
private const string IGNORE_FLAG = "exceptron_ignore";
|
||||
|
||||
public static Exception ExceptronIgnoreOnMono(this Exception exception)
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
exception.ExceptronIgnore();
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static Exception ExceptronIgnore(this Exception exception)
|
||||
{
|
||||
exception.Data.Add(IGNORE_FLAG, true);
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static bool ExceptronShouldIgnore(this Exception exception)
|
||||
{
|
||||
return exception.Data.Contains(IGNORE_FLAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
@@ -47,7 +48,7 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
if (!comparison.HasValue)
|
||||
{
|
||||
comparison = OsInfo.PathStringComparison;
|
||||
comparison = DiskProviderBase.PathStringComparison;
|
||||
}
|
||||
|
||||
if (firstPath.Equals(secondPath, comparison.Value)) return true;
|
||||
@@ -93,7 +94,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
while (child.Parent != null)
|
||||
{
|
||||
if (child.Parent.FullName.Equals(parent.FullName, OsInfo.PathStringComparison))
|
||||
if (child.Parent.FullName.Equals(parent.FullName, DiskProviderBase.PathStringComparison))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -275,4 +276,4 @@ namespace NzbDrone.Common.Extensions
|
||||
return Path.Combine(appFolderInfo.StartUpFolder, NLOG_CONFIG_FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
private static readonly Regex ExpiryDate = new Regex(@"(expires=)([^;]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
|
||||
private readonly IUserAgentBuilder _userAgentBuilder;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private const string _caBundleFileName = "curl-ca-bundle.crt";
|
||||
@@ -37,10 +38,11 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
_caBundleFilePath = _caBundleFileName;
|
||||
}
|
||||
}
|
||||
|
||||
public CurlHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, Logger logger)
|
||||
|
||||
public CurlHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, IUserAgentBuilder userAgentBuilder, Logger logger)
|
||||
{
|
||||
_proxySettingsProvider = proxySettingsProvider;
|
||||
_userAgentBuilder = userAgentBuilder;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -68,94 +70,93 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
using (Stream responseStream = new MemoryStream())
|
||||
using (Stream headerStream = new MemoryStream())
|
||||
|
||||
using (var curlEasy = new CurlEasy())
|
||||
{
|
||||
using (var curlEasy = new CurlEasy())
|
||||
curlEasy.AutoReferer = false;
|
||||
curlEasy.WriteFunction = (b, s, n, o) =>
|
||||
{
|
||||
curlEasy.AutoReferer = false;
|
||||
curlEasy.WriteFunction = (b, s, n, o) =>
|
||||
responseStream.Write(b, 0, s * n);
|
||||
return s * n;
|
||||
};
|
||||
curlEasy.HeaderFunction = (b, s, n, o) =>
|
||||
{
|
||||
headerStream.Write(b, 0, s * n);
|
||||
return s * n;
|
||||
};
|
||||
|
||||
AddProxy(curlEasy, request);
|
||||
|
||||
curlEasy.Url = request.Url.FullUri;
|
||||
|
||||
switch (request.Method)
|
||||
{
|
||||
case HttpMethod.GET:
|
||||
curlEasy.HttpGet = true;
|
||||
break;
|
||||
|
||||
case HttpMethod.POST:
|
||||
curlEasy.Post = true;
|
||||
break;
|
||||
|
||||
case HttpMethod.PUT:
|
||||
curlEasy.Put = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"HttpCurl method {request.Method} not supported");
|
||||
}
|
||||
curlEasy.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
|
||||
curlEasy.FollowLocation = false;
|
||||
|
||||
if (request.RequestTimeout != TimeSpan.Zero)
|
||||
{
|
||||
curlEasy.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalSeconds);
|
||||
}
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
curlEasy.CaInfo = _caBundleFilePath;
|
||||
}
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
curlEasy.Cookie = cookies.GetCookieHeader((Uri)request.Url);
|
||||
}
|
||||
|
||||
if (request.ContentData != null)
|
||||
{
|
||||
curlEasy.PostFieldSize = request.ContentData.Length;
|
||||
curlEasy.SetOpt(CurlOption.CopyPostFields, new string(Array.ConvertAll(request.ContentData, v => (char)v)));
|
||||
}
|
||||
|
||||
// Yes, we have to keep a ref to the object to prevent corrupting the unmanaged state
|
||||
using (var httpRequestHeaders = SerializeHeaders(request))
|
||||
{
|
||||
curlEasy.HttpHeader = httpRequestHeaders;
|
||||
|
||||
var result = curlEasy.Perform();
|
||||
|
||||
if (result != CurlCode.Ok)
|
||||
{
|
||||
responseStream.Write(b, 0, s * n);
|
||||
return s * n;
|
||||
};
|
||||
curlEasy.HeaderFunction = (b, s, n, o) =>
|
||||
{
|
||||
headerStream.Write(b, 0, s * n);
|
||||
return s * n;
|
||||
};
|
||||
|
||||
AddProxy(curlEasy, request);
|
||||
|
||||
curlEasy.Url = request.Url.FullUri;
|
||||
|
||||
switch (request.Method)
|
||||
{
|
||||
case HttpMethod.GET:
|
||||
curlEasy.HttpGet = true;
|
||||
break;
|
||||
|
||||
case HttpMethod.POST:
|
||||
curlEasy.Post = true;
|
||||
break;
|
||||
|
||||
case HttpMethod.PUT:
|
||||
curlEasy.Put = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(string.Format("HttpCurl method {0} not supported", request.Method));
|
||||
}
|
||||
curlEasy.FollowLocation = false;
|
||||
curlEasy.UserAgent = request.UseSimplifiedUserAgent ? UserAgentBuilder.UserAgentSimplified : UserAgentBuilder.UserAgent; ;
|
||||
|
||||
if (request.RequestTimeout != TimeSpan.Zero)
|
||||
{
|
||||
curlEasy.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalSeconds);
|
||||
}
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
curlEasy.CaInfo = _caBundleFilePath;
|
||||
}
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
curlEasy.Cookie = cookies.GetCookieHeader((Uri)request.Url);
|
||||
}
|
||||
|
||||
if (request.ContentData != null)
|
||||
{
|
||||
curlEasy.PostFieldSize = request.ContentData.Length;
|
||||
curlEasy.SetOpt(CurlOption.CopyPostFields, new string(Array.ConvertAll(request.ContentData, v => (char)v)));
|
||||
}
|
||||
|
||||
// Yes, we have to keep a ref to the object to prevent corrupting the unmanaged state
|
||||
using (var httpRequestHeaders = SerializeHeaders(request))
|
||||
{
|
||||
curlEasy.HttpHeader = httpRequestHeaders;
|
||||
|
||||
var result = curlEasy.Perform();
|
||||
|
||||
if (result != CurlCode.Ok)
|
||||
switch (result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case CurlCode.SslCaCert:
|
||||
case (CurlCode)77:
|
||||
throw new WebException(string.Format("Curl Error {0} for Url {1}, issues with your operating system SSL Root Certificate Bundle (ca-bundle).", result, curlEasy.Url));
|
||||
default:
|
||||
throw new WebException(string.Format("Curl Error {0} for Url {1}", result, curlEasy.Url));
|
||||
|
||||
}
|
||||
case CurlCode.SslCaCert:
|
||||
case (CurlCode)77:
|
||||
throw new WebException(string.Format("Curl Error {0} for Url {1}, issues with your operating system SSL Root Certificate Bundle (ca-bundle).", result, curlEasy.Url));
|
||||
default:
|
||||
throw new WebException(string.Format("Curl Error {0} for Url {1}", result, curlEasy.Url));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var webHeaderCollection = ProcessHeaderStream(request, cookies, headerStream);
|
||||
var responseData = ProcessResponseStream(request, responseStream, webHeaderCollection);
|
||||
|
||||
var httpHeader = new HttpHeader(webHeaderCollection);
|
||||
|
||||
return new HttpResponse(request, httpHeader, responseData, (HttpStatusCode)curlEasy.ResponseCode);
|
||||
}
|
||||
|
||||
var webHeaderCollection = ProcessHeaderStream(request, cookies, headerStream);
|
||||
var responseData = ProcessResponseStream(request, responseStream, webHeaderCollection);
|
||||
|
||||
var httpHeader = new HttpHeader(webHeaderCollection);
|
||||
|
||||
return new HttpResponse(request, httpHeader, responseData, (HttpStatusCode)curlEasy.ResponseCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +244,7 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
private string FixSetCookieHeader(string setCookie)
|
||||
{
|
||||
// fix up the date if it was malformed
|
||||
var setCookieClean = ExpiryDate.Replace(setCookie, delegate(Match match)
|
||||
var setCookieClean = ExpiryDate.Replace(setCookie, delegate (Match match)
|
||||
{
|
||||
string shortFormat = "ddd, dd-MMM-yy HH:mm:ss";
|
||||
string longFormat = "ddd, dd-MMM-yyyy HH:mm:ss";
|
||||
@@ -260,7 +261,6 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
|
||||
private byte[] ProcessResponseStream(HttpRequest request, Stream responseStream, WebHeaderCollection webHeaderCollection)
|
||||
{
|
||||
byte[] bytes = null;
|
||||
responseStream.Position = 0;
|
||||
|
||||
if (responseStream.Length != 0)
|
||||
@@ -270,27 +270,20 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
if (encoding.IndexOf("gzip") != -1)
|
||||
{
|
||||
using (var zipStream = new GZipStream(responseStream, CompressionMode.Decompress))
|
||||
{
|
||||
bytes = zipStream.ToBytes();
|
||||
}
|
||||
responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
|
||||
|
||||
webHeaderCollection.Remove("Content-Encoding");
|
||||
}
|
||||
else if (encoding.IndexOf("deflate") != -1)
|
||||
{
|
||||
using (var deflateStream = new DeflateStream(responseStream, CompressionMode.Decompress))
|
||||
{
|
||||
bytes = deflateStream.ToBytes();
|
||||
}
|
||||
responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);
|
||||
|
||||
webHeaderCollection.Remove("Content-Encoding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes == null) bytes = responseStream.ToBytes();
|
||||
return bytes;
|
||||
return responseStream.ToBytes();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,21 +10,23 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
private readonly ManagedHttpDispatcher _managedDispatcher;
|
||||
private readonly CurlHttpDispatcher _curlDispatcher;
|
||||
private readonly IPlatformInfo _platformInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly ICached<bool> _curlTLSFallbackCache;
|
||||
|
||||
public FallbackHttpDispatcher(ManagedHttpDispatcher managedDispatcher, CurlHttpDispatcher curlDispatcher, ICacheManager cacheManager, Logger logger)
|
||||
public FallbackHttpDispatcher(ManagedHttpDispatcher managedDispatcher, CurlHttpDispatcher curlDispatcher, ICacheManager cacheManager, IPlatformInfo platformInfo, Logger logger)
|
||||
{
|
||||
_managedDispatcher = managedDispatcher;
|
||||
_curlDispatcher = curlDispatcher;
|
||||
_platformInfo = platformInfo;
|
||||
_curlTLSFallbackCache = cacheManager.GetCache<bool>(GetType(), "curlTLSFallback");
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
|
||||
{
|
||||
if (OsInfo.IsMonoRuntime && request.Url.Scheme == "https")
|
||||
if (PlatformInfo.IsMono && request.Url.Scheme == "https")
|
||||
{
|
||||
if (!_curlTLSFallbackCache.Find(request.Url.Host))
|
||||
{
|
||||
|
||||
@@ -2,9 +2,13 @@ using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using NLog;
|
||||
using NLog.Fluent;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Common.Security;
|
||||
|
||||
namespace NzbDrone.Common.Http.Dispatchers
|
||||
@@ -13,55 +17,59 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
{
|
||||
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
|
||||
private readonly ICreateManagedWebProxy _createManagedWebProxy;
|
||||
private readonly IUserAgentBuilder _userAgentBuilder;
|
||||
private readonly IPlatformInfo _platformInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy)
|
||||
public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy, IUserAgentBuilder userAgentBuilder, IPlatformInfo platformInfo, Logger logger)
|
||||
{
|
||||
_proxySettingsProvider = proxySettingsProvider;
|
||||
_createManagedWebProxy = createManagedWebProxy;
|
||||
_userAgentBuilder = userAgentBuilder;
|
||||
_platformInfo = platformInfo;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
|
||||
{
|
||||
HttpWebResponse httpWebResponse = null;
|
||||
HttpWebRequest webRequest = null;
|
||||
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
|
||||
|
||||
if (PlatformInfo.IsMono)
|
||||
{
|
||||
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
|
||||
webRequest.AutomaticDecompression = DecompressionMethods.None;
|
||||
webRequest.Headers.Add("Accept-Encoding", "gzip");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Deflate is not a standard and could break depending on implementation.
|
||||
// we should just stick with the more compatible Gzip
|
||||
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
|
||||
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
}
|
||||
|
||||
webRequest.Method = request.Method.ToString();
|
||||
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
|
||||
webRequest.KeepAlive = request.ConnectionKeepAlive;
|
||||
webRequest.AllowAutoRedirect = false;
|
||||
webRequest.CookieContainer = cookies;
|
||||
|
||||
if (request.RequestTimeout != TimeSpan.Zero)
|
||||
{
|
||||
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
|
||||
}
|
||||
|
||||
AddProxy(webRequest, request);
|
||||
|
||||
if (request.Headers != null)
|
||||
{
|
||||
AddRequestHeaders(webRequest, request.Headers);
|
||||
}
|
||||
|
||||
HttpWebResponse httpWebResponse;
|
||||
|
||||
try
|
||||
{
|
||||
webRequest = (HttpWebRequest) WebRequest.Create((Uri) request.Url);
|
||||
|
||||
if (OsInfo.IsMonoRuntime)
|
||||
{
|
||||
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
|
||||
webRequest.AutomaticDecompression = DecompressionMethods.None;
|
||||
webRequest.Headers.Add("Accept-Encoding", "gzip");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Deflate is not a standard and could break depending on implementation.
|
||||
// we should just stick with the more compatible Gzip
|
||||
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
|
||||
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
}
|
||||
|
||||
webRequest.Method = request.Method.ToString();
|
||||
webRequest.UserAgent = request.UseSimplifiedUserAgent
|
||||
? UserAgentBuilder.UserAgentSimplified
|
||||
: UserAgentBuilder.UserAgent;
|
||||
webRequest.KeepAlive = request.ConnectionKeepAlive;
|
||||
webRequest.AllowAutoRedirect = false;
|
||||
webRequest.CookieContainer = cookies;
|
||||
|
||||
if (request.RequestTimeout != TimeSpan.Zero)
|
||||
{
|
||||
webRequest.Timeout = (int) Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
|
||||
}
|
||||
|
||||
AddProxy(webRequest, request);
|
||||
|
||||
if (request.Headers != null)
|
||||
{
|
||||
AddRequestHeaders(webRequest, request.Headers);
|
||||
}
|
||||
|
||||
if (request.ContentData != null)
|
||||
{
|
||||
webRequest.ContentLength = request.ContentData.Length;
|
||||
@@ -71,34 +79,57 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
httpWebResponse = (HttpWebResponse)webRequest.GetResponse();
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
if (e.Status == WebExceptionStatus.SecureChannelFailure && OsInfo.IsWindows)
|
||||
{
|
||||
httpWebResponse = (HttpWebResponse) webRequest.GetResponse();
|
||||
SecurityProtocolPolicy.DisableTls12();
|
||||
}
|
||||
catch (WebException e)
|
||||
|
||||
httpWebResponse = (HttpWebResponse)e.Response;
|
||||
|
||||
if (httpWebResponse == null)
|
||||
{
|
||||
if (e.Status == WebExceptionStatus.SecureChannelFailure && OsInfo.IsWindows)
|
||||
// Workaround for mono not closing connections properly in certain situations.
|
||||
AbortWebRequest(webRequest);
|
||||
|
||||
// The default messages for WebException on mono are pretty horrible.
|
||||
if (e.Status == WebExceptionStatus.NameResolutionFailure)
|
||||
{
|
||||
SecurityProtocolPolicy.DisableTls12();
|
||||
throw new WebException($"DNS Name Resolution Failure: '{webRequest.RequestUri.Host}'", e.Status);
|
||||
}
|
||||
|
||||
httpWebResponse = (HttpWebResponse) e.Response;
|
||||
|
||||
if (httpWebResponse == null)
|
||||
else if (e.ToString().Contains("TLS Support not"))
|
||||
{
|
||||
throw new TlsFailureException(webRequest, e);
|
||||
}
|
||||
else if (e.ToString().Contains("The authentication or decryption has failed."))
|
||||
{
|
||||
throw new TlsFailureException(webRequest, e);
|
||||
}
|
||||
else if (OsInfo.IsNotWindows)
|
||||
{
|
||||
throw new WebException($"{e.Message}: '{webRequest.RequestUri}'", e, e.Status, e.Response);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] data = null;
|
||||
byte[] data = null;
|
||||
|
||||
using (var responseStream = httpWebResponse.GetResponseStream())
|
||||
using (var responseStream = httpWebResponse.GetResponseStream())
|
||||
{
|
||||
if (responseStream != null && responseStream != Stream.Null)
|
||||
{
|
||||
if (responseStream != null)
|
||||
try
|
||||
{
|
||||
data = responseStream.ToBytes();
|
||||
|
||||
if (OsInfo.IsMonoRuntime && httpWebResponse.ContentEncoding == "gzip")
|
||||
if (PlatformInfo.IsMono && httpWebResponse.ContentEncoding == "gzip")
|
||||
{
|
||||
using (var compressedStream = new MemoryStream(data))
|
||||
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
|
||||
@@ -111,17 +142,14 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
httpWebResponse.Headers.Remove("Content-Encoding");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, httpWebResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), data,
|
||||
httpWebResponse.StatusCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
webRequest = null;
|
||||
(httpWebResponse as IDisposable)?.Dispose();
|
||||
httpWebResponse = null;
|
||||
}
|
||||
return new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), data, httpWebResponse.StatusCode);
|
||||
}
|
||||
|
||||
protected virtual void AddProxy(HttpWebRequest webRequest, HttpRequest request)
|
||||
@@ -181,5 +209,35 @@ namespace NzbDrone.Common.Http.Dispatchers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for mono not closing connections properly on timeouts
|
||||
private void AbortWebRequest(HttpWebRequest webRequest)
|
||||
{
|
||||
// First affected version was mono 5.16
|
||||
if (OsInfo.IsNotWindows && _platformInfo.Version >= new Version(5, 16))
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentOperationInfo = webRequest.GetType().GetField("currentOperation", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var currentOperation = currentOperationInfo.GetValue(webRequest);
|
||||
|
||||
if (currentOperation != null)
|
||||
{
|
||||
var responseStreamInfo = currentOperation.GetType().GetField("responseStream", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var responseStream = responseStreamInfo.GetValue(currentOperation) as Stream;
|
||||
// Note that responseStream will likely be null once mono fixes it.
|
||||
responseStream?.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// This can fail randomly on future mono versions that have been changed/fixed. Log to sentry and ignore.
|
||||
_logger.Trace()
|
||||
.Exception(ex)
|
||||
.Message("Unable to dispose responseStream on mono {0}", _platformInfo.Version)
|
||||
.Write();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -31,12 +31,19 @@ namespace NzbDrone.Common.Http
|
||||
private readonly ICached<CookieContainer> _cookieContainerCache;
|
||||
private readonly List<IHttpRequestInterceptor> _requestInterceptors;
|
||||
private readonly IHttpDispatcher _httpDispatcher;
|
||||
private readonly IUserAgentBuilder _userAgentBuilder;
|
||||
|
||||
public HttpClient(IEnumerable<IHttpRequestInterceptor> requestInterceptors, ICacheManager cacheManager, IRateLimitService rateLimitService, IHttpDispatcher httpDispatcher, Logger logger)
|
||||
public HttpClient(IEnumerable<IHttpRequestInterceptor> requestInterceptors,
|
||||
ICacheManager cacheManager,
|
||||
IRateLimitService rateLimitService,
|
||||
IHttpDispatcher httpDispatcher,
|
||||
IUserAgentBuilder userAgentBuilder,
|
||||
Logger logger)
|
||||
{
|
||||
_requestInterceptors = requestInterceptors.ToList();
|
||||
_rateLimitService = rateLimitService;
|
||||
_httpDispatcher = httpDispatcher;
|
||||
_userAgentBuilder = userAgentBuilder;
|
||||
_logger = logger;
|
||||
|
||||
ServicePointManager.DefaultConnectionLimit = 12;
|
||||
@@ -71,7 +78,7 @@ namespace NzbDrone.Common.Http
|
||||
while (response.HasHttpRedirect);
|
||||
}
|
||||
|
||||
if (response.HasHttpRedirect && !RuntimeInfoBase.IsProduction)
|
||||
if (response.HasHttpRedirect && !RuntimeInfo.IsProduction)
|
||||
{
|
||||
_logger.Error("Server requested a redirect to [{0}] while in developer mode. Update the request URL to avoid this redirect.", response.Headers["Location"]);
|
||||
}
|
||||
@@ -143,19 +150,30 @@ namespace NzbDrone.Common.Http
|
||||
if (!request.IgnorePersistentCookies)
|
||||
{
|
||||
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
|
||||
sourceContainer.Add(persistentCookies);
|
||||
sourceContainer.Add(persistentCookies);
|
||||
}
|
||||
|
||||
if (request.Cookies.Count != 0)
|
||||
{
|
||||
foreach (var pair in request.Cookies)
|
||||
{
|
||||
var cookie = new Cookie(pair.Key, pair.Value, "/")
|
||||
Cookie cookie;
|
||||
if (pair.Value == null)
|
||||
{
|
||||
// Use Now rather than UtcNow to work around Mono cookie expiry bug.
|
||||
// See https://gist.github.com/ta264/7822b1424f72e5b4c961
|
||||
Expires = DateTime.Now.AddHours(1)
|
||||
};
|
||||
cookie = new Cookie(pair.Key, "", "/")
|
||||
{
|
||||
Expires = DateTime.Now.AddDays(-1)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
cookie = new Cookie(pair.Key, pair.Value, "/")
|
||||
{
|
||||
// Use Now rather than UtcNow to work around Mono cookie expiry bug.
|
||||
// See https://gist.github.com/ta264/7822b1424f72e5b4c961
|
||||
Expires = DateTime.Now.AddHours(1)
|
||||
};
|
||||
}
|
||||
|
||||
sourceContainer.Add((Uri)request.Url, cookie);
|
||||
|
||||
@@ -178,7 +196,6 @@ namespace NzbDrone.Common.Http
|
||||
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
|
||||
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
|
||||
var existingCookies = cookieContainer.GetCookies((Uri)request.Url);
|
||||
|
||||
cookieContainer.Add(persistentCookies);
|
||||
cookieContainer.Add(existingCookies);
|
||||
}*/
|
||||
@@ -226,13 +243,11 @@ namespace NzbDrone.Common.Http
|
||||
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
|
||||
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
using (var webClient = new GZipWebClient())
|
||||
{
|
||||
webClient.Headers.Add(HttpRequestHeader.UserAgent, UserAgentBuilder.UserAgent);
|
||||
webClient.DownloadFile(url, fileName);
|
||||
stopWatch.Stop();
|
||||
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
|
||||
}
|
||||
var webClient = new GZipWebClient();
|
||||
webClient.Headers.Add(HttpRequestHeader.UserAgent, _userAgentBuilder.GetUserAgent());
|
||||
webClient.DownloadFile(url, fileName);
|
||||
stopWatch.Stop();
|
||||
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
@@ -255,6 +270,7 @@ namespace NzbDrone.Common.Http
|
||||
public HttpResponse<T> Get<T>(HttpRequest request) where T : new()
|
||||
{
|
||||
var response = Get(request);
|
||||
CheckResponseContentType(response);
|
||||
return new HttpResponse<T>(response);
|
||||
}
|
||||
|
||||
@@ -273,7 +289,16 @@ namespace NzbDrone.Common.Http
|
||||
public HttpResponse<T> Post<T>(HttpRequest request) where T : new()
|
||||
{
|
||||
var response = Post(request);
|
||||
CheckResponseContentType(response);
|
||||
return new HttpResponse<T>(response);
|
||||
}
|
||||
|
||||
private void CheckResponseContentType(HttpResponse response)
|
||||
{
|
||||
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
|
||||
{
|
||||
throw new UnexpectedHtmlContentException(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,19 @@ namespace NzbDrone.Common.Http
|
||||
public HttpRequest Request { get; private set; }
|
||||
public HttpResponse Response { get; private set; }
|
||||
|
||||
public HttpException(HttpRequest request, HttpResponse response)
|
||||
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url))
|
||||
public HttpException(HttpRequest request, HttpResponse response, string message)
|
||||
: base(message)
|
||||
{
|
||||
Request = request;
|
||||
Response = response;
|
||||
}
|
||||
|
||||
public HttpException(HttpRequest request, HttpResponse response)
|
||||
: this(request, response, string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public HttpException(HttpResponse response)
|
||||
: this(response.Request, response)
|
||||
{
|
||||
@@ -30,4 +36,4 @@ namespace NzbDrone.Common.Http
|
||||
return base.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Common.Http
|
||||
IgnorePersistentCookies = false;
|
||||
Cookies = new Dictionary<string, string>();
|
||||
|
||||
if (!RuntimeInfoBase.IsProduction)
|
||||
if (!RuntimeInfo.IsProduction)
|
||||
{
|
||||
AllowAutoRedirect = false;
|
||||
}
|
||||
|
||||
18
src/NzbDrone.Common/Http/TlsFailureException.cs
Normal file
18
src/NzbDrone.Common/Http/TlsFailureException.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class TlsFailureException : WebException
|
||||
{
|
||||
public TlsFailureException(WebRequest request, WebException innerException)
|
||||
: base("Failed to establish secure https connection to '" + request.RequestUri + "', libcurl fallback might be unavailable.", innerException, WebExceptionStatus.SecureChannelFailure, innerException.Response)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
14
src/NzbDrone.Common/Http/UnexpectedHtmlContentException.cs
Normal file
14
src/NzbDrone.Common/Http/UnexpectedHtmlContentException.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public class UnexpectedHtmlContentException : HttpException
|
||||
{
|
||||
public UnexpectedHtmlContentException(HttpResponse response)
|
||||
: base(response.Request, response, $"Site responded with browser content instead of api data. This disruption may be temporary, please try again later. [{response.Request.Url}]")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,39 @@ using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public static class UserAgentBuilder
|
||||
public interface IUserAgentBuilder
|
||||
{
|
||||
public static string UserAgent { get; private set; }
|
||||
public static string UserAgentSimplified { get; private set; }
|
||||
string GetUserAgent(bool simplified = false);
|
||||
}
|
||||
|
||||
static UserAgentBuilder()
|
||||
public class UserAgentBuilder : IUserAgentBuilder
|
||||
{
|
||||
private readonly string _userAgentSimplified;
|
||||
private readonly string _userAgent;
|
||||
|
||||
public string GetUserAgent(bool simplified)
|
||||
{
|
||||
UserAgent = string.Format("Radarr/{0} ({1} {2})",
|
||||
BuildInfo.Version,
|
||||
OsInfo.Os, OsInfo.Version.ToString(2));
|
||||
if (simplified)
|
||||
{
|
||||
return _userAgentSimplified;
|
||||
}
|
||||
|
||||
UserAgentSimplified = string.Format("Radarr/{0}",
|
||||
BuildInfo.Version.ToString(2));
|
||||
return _userAgent;
|
||||
}
|
||||
|
||||
public UserAgentBuilder(IOsInfo osInfo)
|
||||
{
|
||||
var osName = OsInfo.Os.ToString();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(osInfo.Name))
|
||||
{
|
||||
osName = osInfo.Name.ToLower();
|
||||
}
|
||||
|
||||
var osVersion = osInfo.Version?.ToLower();
|
||||
|
||||
_userAgent = $"Radarr/{BuildInfo.Version} ({osName} {osVersion})";
|
||||
_userAgentSimplified = $"Radarr/{BuildInfo.Version.ToString(2)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NLog.Common;
|
||||
using NLog.Layouts;
|
||||
using NLog.Targets;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Exceptron;
|
||||
using NzbDrone.Common.Exceptron.Configuration;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="NLog"/> target for exceptron. Allows you to automatically report all
|
||||
/// exceptions logged to Nlog/>
|
||||
/// </summary>
|
||||
[Target("Exceptron")]
|
||||
public class ExceptronTarget : Target
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ExceptronClient"/> instance that Nlog Target uses to report the exceptions.
|
||||
/// </summary>
|
||||
public IExceptronClient ExceptronClient { get; internal set; }
|
||||
|
||||
protected override void InitializeTarget()
|
||||
{
|
||||
var config = new ExceptronConfiguration
|
||||
{
|
||||
ApiKey = "d64e0a72845d495abc625af3a27cf5f5",
|
||||
IncludeMachineName = true,
|
||||
};
|
||||
|
||||
if (RuntimeInfoBase.IsProduction)
|
||||
{
|
||||
config.ApiKey = "82c0f66dd2d64d1480cc88b551c9bdd8";
|
||||
}
|
||||
|
||||
ExceptronClient = new ExceptronClient(config, BuildInfo.Version);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// String that identifies the active user
|
||||
/// </summary>
|
||||
public Layout UserId { get; set; }
|
||||
|
||||
protected override void Write(LogEventInfo logEvent)
|
||||
{
|
||||
if (logEvent == null || logEvent.Exception == null || logEvent.Exception.ExceptronShouldIgnore()) return;
|
||||
|
||||
try
|
||||
{
|
||||
var exceptionData = new ExceptionData
|
||||
{
|
||||
Exception = logEvent.Exception,
|
||||
Component = logEvent.LoggerName,
|
||||
Message = logEvent.FormattedMessage,
|
||||
};
|
||||
|
||||
if (UserId != null)
|
||||
{
|
||||
exceptionData.UserId = UserId.Render(logEvent);
|
||||
}
|
||||
|
||||
if (logEvent.Level <= LogLevel.Info)
|
||||
{
|
||||
exceptionData.Severity = ExceptionSeverity.None;
|
||||
}
|
||||
else if (logEvent.Level <= LogLevel.Warn)
|
||||
{
|
||||
exceptionData.Severity = ExceptionSeverity.Warning;
|
||||
}
|
||||
else if (logEvent.Level <= LogLevel.Error)
|
||||
{
|
||||
exceptionData.Severity = ExceptionSeverity.Error;
|
||||
}
|
||||
else if (logEvent.Level <= LogLevel.Fatal)
|
||||
{
|
||||
exceptionData.Severity = ExceptionSeverity.Fatal;
|
||||
}
|
||||
|
||||
ExceptronClient.SubmitException(exceptionData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
InternalLogger.Warn("Unable to report exception. {0}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
return;
|
||||
}
|
||||
|
||||
if (OsInfo.IsMonoRuntime)
|
||||
if (PlatformInfo.IsMono)
|
||||
{
|
||||
if (exception is TypeInitializationException && exception.InnerException is DllNotFoundException ||
|
||||
exception is DllNotFoundException)
|
||||
@@ -51,4 +51,4 @@ namespace NzbDrone.Common.Instrumentation
|
||||
Logger.Fatal(exception, "EPIC FAIL: " + exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using LogentriesNLog;
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inConsole && (OsInfo.IsNotWindows || RuntimeInfoBase.IsUserInteractive))
|
||||
if (inConsole && (OsInfo.IsNotWindows || RuntimeInfo.IsUserInteractive))
|
||||
{
|
||||
RegisterConsole();
|
||||
}
|
||||
@@ -152,16 +152,6 @@ namespace NzbDrone.Common.Instrumentation
|
||||
LogManager.Configuration.LoggingRules.Add(loggingRule);
|
||||
}
|
||||
|
||||
private static void RegisterExceptron()
|
||||
{
|
||||
var exceptronTarget = new ExceptronTarget();
|
||||
var rule = new LoggingRule("*", LogLevel.Warn, exceptronTarget);
|
||||
|
||||
LogManager.Configuration.AddTarget("ExceptronTarget", exceptronTarget);
|
||||
LogManager.Configuration.LoggingRules.Add(rule);
|
||||
}
|
||||
|
||||
|
||||
public static Logger GetLogger(Type obj)
|
||||
{
|
||||
return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", ""));
|
||||
|
||||
@@ -94,6 +94,10 @@
|
||||
<Compile Include="Disk\RelativeFileSystemModel.cs" />
|
||||
<Compile Include="Disk\FileSystemModel.cs" />
|
||||
<Compile Include="Disk\FileSystemResult.cs" />
|
||||
<Compile Include="EnvironmentInfo\IOperatingSystemVersionInfo.cs" />
|
||||
<Compile Include="EnvironmentInfo\IOsVersionAdapter.cs" />
|
||||
<Compile Include="EnvironmentInfo\IPlatformInfo.cs" />
|
||||
<Compile Include="EnvironmentInfo\OsVersionModel.cs" />
|
||||
<Compile Include="Extensions\DictionaryExtensions.cs" />
|
||||
<Compile Include="Disk\GdiPlusInterop.cs" />
|
||||
<Compile Include="Disk\OsPath.cs" />
|
||||
@@ -125,13 +129,12 @@
|
||||
<Compile Include="EnvironmentInfo\BuildInfo.cs" />
|
||||
<Compile Include="EnvironmentInfo\OsInfo.cs" />
|
||||
<Compile Include="EnvironmentInfo\IRuntimeInfo.cs" />
|
||||
<Compile Include="EnvironmentInfo\RuntimeInfoBase.cs" />
|
||||
<Compile Include="EnvironmentInfo\RuntimeInfo.cs" />
|
||||
<Compile Include="EnvironmentInfo\StartupContext.cs" />
|
||||
<Compile Include="Exceptions\NotParentException.cs" />
|
||||
<Compile Include="Exceptions\NzbDroneException.cs" />
|
||||
<Compile Include="Exceptron\Configuration\ExceptronConfiguration.cs" />
|
||||
<Compile Include="Exceptron\ExceptionData.cs" />
|
||||
<Compile Include="Exceptron\ExceptionExtentions.cs" />
|
||||
<Compile Include="Exceptron\ExceptionSeverity.cs" />
|
||||
<Compile Include="Exceptron\ExceptronApiException.cs" />
|
||||
<Compile Include="Exceptron\ExceptronClient.cs" />
|
||||
@@ -190,11 +193,12 @@
|
||||
<Compile Include="Http\HttpRequestBuilder.cs" />
|
||||
<Compile Include="Http\HttpRequestBuilderFactory.cs" />
|
||||
<Compile Include="Http\Proxy\ProxyType.cs" />
|
||||
<Compile Include="Http\TlsFailureException.cs" />
|
||||
<Compile Include="Http\TooManyRequestsException.cs" />
|
||||
<Compile Include="Extensions\IEnumerableExtensions.cs" />
|
||||
<Compile Include="Http\UnexpectedHtmlContentException.cs" />
|
||||
<Compile Include="Http\UserAgentBuilder.cs" />
|
||||
<Compile Include="Instrumentation\CleanseLogMessage.cs" />
|
||||
<Compile Include="Instrumentation\ExceptronTarget.cs" />
|
||||
<Compile Include="Instrumentation\Extensions\LoggerProgressExtensions.cs" />
|
||||
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
|
||||
<Compile Include="Instrumentation\LogEventExtensions.cs" />
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace NzbDrone.Common.Processes
|
||||
|
||||
public Process Start(string path, string args = null, StringDictionary environmentVariables = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null)
|
||||
{
|
||||
if (OsInfo.IsMonoRuntime && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (PlatformInfo.IsMono && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
args = GetMonoArgs(path, args);
|
||||
path = "mono";
|
||||
@@ -192,7 +192,7 @@ namespace NzbDrone.Common.Processes
|
||||
|
||||
public Process SpawnNewProcess(string path, string args = null, StringDictionary environmentVariables = null)
|
||||
{
|
||||
if (OsInfo.IsMonoRuntime && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (PlatformInfo.IsMono && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
args = GetMonoArgs(path, args);
|
||||
path = "mono";
|
||||
|
||||
@@ -9,6 +9,7 @@ using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients.QBittorrent;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
{
|
||||
@@ -37,8 +38,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), new Byte[0]));
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(new QBittorrentPreferences());
|
||||
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(new QBittorrentPreferences() { DhtEnabled = true });
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxySelector>()
|
||||
.Setup(s => s.GetProxy(It.IsAny<QBittorrentSettings>(), It.IsAny<bool>()))
|
||||
.Returns(Mocker.GetMock<IQBittorrentProxy>().Object);
|
||||
}
|
||||
|
||||
protected void GivenRedirectToMagnet()
|
||||
@@ -154,7 +159,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyPaused(item);
|
||||
item.RemainingTime.Should().NotBe(TimeSpan.Zero);
|
||||
item.RemainingTime.Should().NotHaveValue();
|
||||
}
|
||||
|
||||
[TestCase("pausedUP")]
|
||||
@@ -162,6 +167,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
[TestCase("uploading")]
|
||||
[TestCase("stalledUP")]
|
||||
[TestCase("checkingUP")]
|
||||
[TestCase("forcedUP")]
|
||||
public void completed_item_should_have_required_properties(string state)
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
@@ -184,6 +190,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
|
||||
[TestCase("queuedDL")]
|
||||
[TestCase("checkingDL")]
|
||||
[TestCase("metaDL")]
|
||||
public void queued_item_should_have_required_properties(string state)
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
@@ -201,7 +208,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyQueued(item);
|
||||
item.RemainingTime.Should().NotBe(TimeSpan.Zero);
|
||||
item.RemainingTime.Should().NotHaveValue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -243,7 +250,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
|
||||
var item = Subject.GetItems().Single();
|
||||
VerifyWarning(item);
|
||||
item.RemainingTime.Should().NotBe(TimeSpan.Zero);
|
||||
item.RemainingTime.Should().NotHaveValue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -271,6 +278,35 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
id.Should().Be(expectedHash);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_refuse_magnet_if_no_trackers_provided_and_dht_is_disabled()
|
||||
{
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(new QBittorrentPreferences() { DhtEnabled = false });
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
remoteMovie.Release.DownloadUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR";
|
||||
|
||||
Assert.Throws<ReleaseDownloadException>(() => Subject.Download(remoteMovie));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_accept_magnet_if_trackers_provided_and_dht_is_disabled()
|
||||
{
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(s => s.GetConfig(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(new QBittorrentPreferences() { DhtEnabled = false });
|
||||
|
||||
var remoteMovie = CreateRemoteMovie();
|
||||
remoteMovie.Release.DownloadUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp://abc";
|
||||
|
||||
Assert.DoesNotThrow(() => Subject.Download(remoteMovie));
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Verify(s => s.AddTorrentFromUrl(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_set_top_priority()
|
||||
{
|
||||
@@ -494,5 +530,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
var item = Subject.GetItems().Single();
|
||||
item.Category.Should().Be(category);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_should_force_api_version_check()
|
||||
{
|
||||
// Set TestConnection up to fail quick
|
||||
Mocker.GetMock<IQBittorrentProxy>()
|
||||
.Setup(v => v.GetApiVersion(It.IsAny<QBittorrentSettings>()))
|
||||
.Returns(new Version(1, 0));
|
||||
|
||||
Subject.Test();
|
||||
|
||||
Mocker.GetMock<IQBittorrentProxySelector>()
|
||||
.Verify(v => v.GetProxy(It.IsAny<QBittorrentSettings>(), true), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Security.AccessControl;
|
||||
using Moq;
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Dispatchers;
|
||||
using NzbDrone.Common.TPL;
|
||||
@@ -24,12 +24,16 @@ namespace NzbDrone.Core.Test.Framework
|
||||
{
|
||||
protected void UseRealHttp()
|
||||
{
|
||||
Mocker.GetMock<IPlatformInfo>().SetupGet(c => c.Version).Returns(new Version("3.0.0"));
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Version).Returns("1.0.0");
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Name).Returns("TestOS");
|
||||
|
||||
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
|
||||
Mocker.SetConstant<ManagedHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>()));
|
||||
Mocker.SetConstant<CurlHttpDispatcher>(new CurlHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<NLog.Logger>()));
|
||||
Mocker.SetConstant<ManagedHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<IPlatformInfo>(), TestLogger));
|
||||
Mocker.SetConstant<CurlHttpDispatcher>(new CurlHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<UserAgentBuilder>(), TestLogger));
|
||||
Mocker.SetConstant<IHttpProvider>(new HttpProvider(TestLogger));
|
||||
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<FallbackHttpDispatcher>(), TestLogger));
|
||||
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<FallbackHttpDispatcher>(), Mocker.Resolve<UserAgentBuilder>(), TestLogger));
|
||||
Mocker.SetConstant<IRadarrCloudRequestBuilder>(new RadarrCloudRequestBuilder());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.HealthCheck.Checks;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
@@ -8,17 +9,12 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
[TestFixture]
|
||||
public class MonoVersionCheckFixture : CoreTest<MonoVersionCheck>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
MonoOnly();
|
||||
}
|
||||
|
||||
private void GivenOutput(string version)
|
||||
{
|
||||
Mocker.GetMock<IRuntimeInfo>()
|
||||
.SetupGet(s => s.RuntimeVersion)
|
||||
.Returns(string.Format("{0} (tarball Wed Sep 25 16:35:44 CDT 2013)", version));
|
||||
MonoOnly();
|
||||
Mocker.GetMock<IPlatformInfo>()
|
||||
.SetupGet(s => s.Version)
|
||||
.Returns(new Version(version));
|
||||
}
|
||||
|
||||
[TestCase("3.10")]
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||
}
|
||||
|
||||
[TestCase("Prometheus", "Prometheus")]
|
||||
[TestCase("The Man from U.N.C.L.E.", "The Man from U.N.C.L.E.")]
|
||||
// TODO: TMDB does not like when we clean '.'
|
||||
// [TestCase("The Man from U.N.C.L.E.", "The Man from U.N.C.L.E.")]
|
||||
[TestCase("imdb:tt2527336", "Star Wars: The Last Jedi")]
|
||||
[TestCase("imdb:tt2798920", "Annihilation")]
|
||||
public void successful_search(string title, string expected)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Movies.AlternativeTitles;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.MovieTests.AlternativeTitleServiceTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlternativeTitleFixture : CoreTest
|
||||
{
|
||||
private AlternativeTitle CreateFakeTitle(SourceType source, int votes)
|
||||
{
|
||||
return Builder<AlternativeTitle>.CreateNew().With(t => t.SourceType = source).With(t => t.Votes = votes)
|
||||
.Build();
|
||||
}
|
||||
|
||||
[TestCase(SourceType.TMDB, -1, true)]
|
||||
[TestCase(SourceType.TMDB, 1000, true)]
|
||||
[TestCase(SourceType.Mappings, 0, false)]
|
||||
[TestCase(SourceType.Mappings, 4, true)]
|
||||
[TestCase(SourceType.Mappings, -1, false)]
|
||||
[TestCase(SourceType.Indexer, 0, true)]
|
||||
[TestCase(SourceType.User, 0, true)]
|
||||
public void should_be_trusted(SourceType source, int votes, bool trusted)
|
||||
{
|
||||
var fakeTitle = CreateFakeTitle(source, votes);
|
||||
|
||||
fakeTitle.IsTrusted().Should().Be(trusted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Movies.AlternativeTitles;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.MovieTests.AlternativeTitleServiceTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlternativeTitleServiceFixture : CoreTest<AlternativeTitleService>
|
||||
{
|
||||
private AlternativeTitle _title1;
|
||||
private AlternativeTitle _title2;
|
||||
private AlternativeTitle _title3;
|
||||
|
||||
private Movie _movie;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var titles = Builder<AlternativeTitle>.CreateListOfSize(3).All().With(t => t.MovieId = 0).Build();
|
||||
_title1 = titles[0];
|
||||
_title2 = titles[1];
|
||||
_title3 = titles[2];
|
||||
_movie = Builder<Movie>.CreateNew().With(m => m.CleanTitle = "myothertitle").With(m => m.Id = 1).Build();
|
||||
}
|
||||
|
||||
private void GivenExistingTitles(params AlternativeTitle[] titles)
|
||||
{
|
||||
Mocker.GetMock<IAlternativeTitleRepository>().Setup(r => r.FindByMovieId(_movie.Id))
|
||||
.Returns(titles.ToList());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_update_insert_remove_titles()
|
||||
{
|
||||
var titles = new List<AlternativeTitle> {_title2, _title3};
|
||||
var updates = new List<AlternativeTitle> {_title2};
|
||||
var deletes = new List<AlternativeTitle> {_title1};
|
||||
var inserts = new List<AlternativeTitle> {_title3};
|
||||
GivenExistingTitles(_title1, _title2);
|
||||
|
||||
Subject.UpdateTitles(titles, _movie);
|
||||
|
||||
Mocker.GetMock<IAlternativeTitleRepository>().Verify(r => r.InsertMany(inserts), Times.Once());
|
||||
Mocker.GetMock<IAlternativeTitleRepository>().Verify(r => r.UpdateMany(updates), Times.Once());
|
||||
Mocker.GetMock<IAlternativeTitleRepository>().Verify(r => r.DeleteMany(deletes), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_insert_duplicates()
|
||||
{
|
||||
GivenExistingTitles();
|
||||
var titles = new List<AlternativeTitle> {_title1, _title1};
|
||||
var inserts = new List<AlternativeTitle>{ _title1 };
|
||||
|
||||
Subject.UpdateTitles(titles, _movie);
|
||||
|
||||
Mocker.GetMock<IAlternativeTitleRepository>().Verify(r => r.InsertMany(inserts), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_insert_main_title()
|
||||
{
|
||||
GivenExistingTitles();
|
||||
var titles = new List<AlternativeTitle>{_title1};
|
||||
var movie = Builder<Movie>.CreateNew().With(m => m.CleanTitle = _title1.CleanTitle).Build();
|
||||
|
||||
Subject.UpdateTitles(titles, movie);
|
||||
|
||||
Mocker.GetMock<IAlternativeTitleRepository>().Verify(r => r.InsertMany(new List<AlternativeTitle>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_update_movie_id()
|
||||
{
|
||||
GivenExistingTitles();
|
||||
var titles = new List<AlternativeTitle> {_title1, _title2};
|
||||
|
||||
Subject.UpdateTitles(titles, _movie);
|
||||
|
||||
_title1.MovieId.Should().Be(_movie.Id);
|
||||
_title2.MovieId.Should().Be(_movie.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_update_with_correct_id()
|
||||
{
|
||||
var existingTitle = Builder<AlternativeTitle>.CreateNew().With(t => t.Id = 2).Build();
|
||||
GivenExistingTitles(existingTitle);
|
||||
var updateTitle = existingTitle.JsonClone();
|
||||
updateTitle.Id = 0;
|
||||
|
||||
Subject.UpdateTitles(new List<AlternativeTitle> {updateTitle}, _movie);
|
||||
|
||||
Mocker.GetMock<IAlternativeTitleRepository>().Verify(r => r.UpdateMany(It.Is<IList<AlternativeTitle>>(list => list.First().Id == existingTitle.Id)), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,6 +296,8 @@
|
||||
<Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" />
|
||||
<Compile Include="MetadataSource\SearchMovieComparerFixture.cs" />
|
||||
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
|
||||
<Compile Include="MovieTests\AlternativeTitleServiceTests\AlternativeTitleFixture.cs" />
|
||||
<Compile Include="MovieTests\AlternativeTitleServiceTests\AlternativeTitleServiceFixture.cs" />
|
||||
<Compile Include="NetImport\CouchPotato\CouchPotatoParserFixture.cs" />
|
||||
<Compile Include="NetImport\RSSImportFixture.cs" />
|
||||
<Compile Include="NetImport\RSSImportParserFixture.cs" />
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Castle.2009.S01E14.Mandarin.HDTV.XviD-LOL", Language.Mandarin)]
|
||||
[TestCase("Castle.2009.S01E14.Korean.HDTV.XviD-LOL", Language.Korean)]
|
||||
[TestCase("Castle.2009.S01E14.Russian.HDTV.XviD-LOL", Language.Russian)]
|
||||
[TestCase("Castle.2009.S01E14.Ukrainian.HDTV.XviD-LOL", Language.Ukrainian)]
|
||||
[TestCase("Castle.2009.S01E14.Ukr.HDTV.XviD-LOL", Language.Ukrainian)]
|
||||
[TestCase("Castle.2009.S01E14.Polish.HDTV.XviD-LOL", Language.Polish)]
|
||||
[TestCase("Castle.2009.S01E14.Vietnamese.HDTV.XviD-LOL", Language.Vietnamese)]
|
||||
[TestCase("Castle.2009.S01E14.Swedish.HDTV.XviD-LOL", Language.Swedish)]
|
||||
@@ -68,6 +70,10 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("2 Broke Girls - S01E01 - Pilot.sub", Language.Unknown)]
|
||||
[TestCase("2 Broke Girls - S01E01 - Pilot.eng.forced.sub", Language.English)]
|
||||
[TestCase("2 Broke Girls - S01E01 - Pilot-eng-forced.sub", Language.English)]
|
||||
[TestCase("2_Eng.srt", Language.English)]
|
||||
[TestCase("3_English.srt", Language.English)]
|
||||
[TestCase("Title.2000.1080p.BluRay.H264.AAC-RARBG.idx", Language.Unknown)]
|
||||
[TestCase("Title.2000.1080p.BluRay.H264.AAC-RARBG.sub", Language.Unknown)]
|
||||
public void should_parse_subtitle_language(string fileName, Language language)
|
||||
{
|
||||
var result = LanguageParser.ParseSubtitleLanguage(fileName);
|
||||
|
||||
@@ -251,6 +251,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
|
||||
[TestCase("Contract.to.Kill.2016.REMUX.2160p.BluRay.AVC.DTS-HD.MA.5.1-iFT")]
|
||||
[TestCase("27.Dresses.2008.REMUX.2160p.Bluray.AVC.DTS-HR.MA.5.1-LEGi0N")]
|
||||
[TestCase("Los Vengadores (2012) [UHDRemux HDR HEVC 2160p][Dolby Atmos TrueHD 7 1 Eng DTS 5 1 Esp]")]
|
||||
public void should_parse_remux2160p_quality(string title)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.BLURAY, false, Resolution.R2160P, Modifier.REMUX);
|
||||
|
||||
@@ -29,6 +29,16 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("The.Middle.720p.HEVC.x265-MeGusta-Pre", "MeGusta")]
|
||||
[TestCase("Haunted.Hayride.2018.720p.WEBRip.DDP5.1.x264-NTb-postbot", "NTb")]
|
||||
[TestCase("Haunted.Hayride.2018.720p.WEBRip.DDP5.1.x264-NTb-xpost", "NTb")]
|
||||
[TestCase("Men.in.Black.International.2019.BluRay.1080p.AVC.DTS-HD.MA5.1-CHDBits-RakuvArrow", "CHDBits")]
|
||||
[TestCase("Aladdin.2019.1080p.BluRay.x264-SPARKS-WhiteRev", "SPARKS")]
|
||||
[TestCase("Elvis.Presley.The.Searcher.2018.1080p.BluRay.x264-HANDJOB-BUYMORE", "HANDJOB")]
|
||||
[TestCase("Kill.Bill.Vol.2.2004.1080p.BluRay.DTS.x264-CyTSuNee-AsRequested", "CyTSuNee")]
|
||||
[TestCase("The.Good.Doctor.S02E17.Breakdown.1080p.AMZN.WEB-DL.DDP5.1.H.264-SiGMA-AlternativeToRequested", "SiGMA")]
|
||||
[TestCase("Mandy.2018.NORDiC.1080p.BluRay.x264-EGEN-GEROV", "EGEN")]
|
||||
[TestCase("TheEqualizer.2.2018.1080p.BluRay.DTS.X264-CMRG-Z0iDS3N", "CMRG")]
|
||||
[TestCase("Ghosthouse.1988.720p.BluRay.x264-SADPANDA-Chamele0n", "SADPANDA")]
|
||||
[TestCase("The.Walking.Dead.S08E08.1080p.BluRay.x264-ROVERS-4P", "ROVERS")]
|
||||
[TestCase("Stranger.Things.S01E02.720p.BluRay.X264-REWARD-4Planet", "REWARD")]
|
||||
//[TestCase("", "")]
|
||||
public void should_parse_release_group(string title, string expected)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Update;
|
||||
@@ -10,6 +11,12 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
{
|
||||
public class UpdatePackageProviderFixture : CoreTest<UpdatePackageProvider>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Mocker.GetMock<IPlatformInfo>().SetupGet(c => c.Version).Returns(new Version("9.9.9"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void no_update_when_version_higher()
|
||||
{
|
||||
|
||||
@@ -17,6 +17,6 @@ namespace NzbDrone.Core.Analytics
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
public bool IsEnabled => _configFileProvider.AnalyticsEnabled && RuntimeInfoBase.IsProduction;
|
||||
public bool IsEnabled => _configFileProvider.AnalyticsEnabled && RuntimeInfo.IsProduction;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
@@ -373,7 +374,7 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public int FirstDayOfWeek
|
||||
{
|
||||
get { return GetValueInt("FirstDayOfWeek", (int)OsInfo.FirstDayOfWeek); }
|
||||
get { return GetValueInt("FirstDayOfWeek", (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek); }
|
||||
|
||||
set { SetValue("FirstDayOfWeek", value); }
|
||||
}
|
||||
|
||||
@@ -48,9 +48,25 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
|
||||
public string GetVersion(DelugeSettings settings)
|
||||
{
|
||||
var response = ProcessRequest<string>(settings, "daemon.info");
|
||||
try
|
||||
{
|
||||
var response = ProcessRequest<string>(settings, "daemon.info");
|
||||
|
||||
return response;
|
||||
return response;
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
if (ex.Message.Contains("Unknown method"))
|
||||
{
|
||||
// Deluge v2 beta replaced 'daemon.info' with 'daemon.get_version'.
|
||||
// It may return or become official, for now we just retry with the get_version api.
|
||||
var response = ProcessRequest<string>(settings, "daemon.get_version");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetConfig(DelugeSettings settings)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
public class QBittorrent : TorrentClientBase<QBittorrentSettings>
|
||||
{
|
||||
private readonly IQBittorrentProxy _proxy;
|
||||
private readonly IQBittorrentProxySelector _proxySelector;
|
||||
|
||||
public QBittorrent(IQBittorrentProxy proxy,
|
||||
public QBittorrent(IQBittorrentProxySelector proxySelector,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
@@ -30,16 +30,23 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_proxySelector = proxySelector;
|
||||
}
|
||||
|
||||
private IQBittorrentProxy Proxy => _proxySelector.GetProxy(Settings);
|
||||
|
||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentFromUrl(magnetLink, Settings);
|
||||
if (!Proxy.GetConfig(Settings).DhtEnabled && !magnetLink.Contains("&tr="))
|
||||
{
|
||||
throw new NotSupportedException("Magnet Links without trackers not supported if DHT is disabled");
|
||||
}
|
||||
|
||||
Proxy.AddTorrentFromUrl(magnetLink, Settings);
|
||||
|
||||
if (Settings.MovieCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash.ToLower(), Settings.MovieCategory, Settings);
|
||||
Proxy.SetTorrentLabel(hash.ToLower(), Settings.MovieCategory, Settings);
|
||||
}
|
||||
|
||||
var isRecentMovie = remoteMovie.Movie.IsRecentMovie;
|
||||
@@ -47,7 +54,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
if (isRecentMovie && Settings.RecentMoviePriority == (int)QBittorrentPriority.First ||
|
||||
!isRecentMovie && Settings.OlderMoviePriority == (int)QBittorrentPriority.First)
|
||||
{
|
||||
_proxy.MoveTorrentToTopInQueue(hash.ToLower(), Settings);
|
||||
Proxy.MoveTorrentToTopInQueue(hash.ToLower(), Settings);
|
||||
}
|
||||
|
||||
SetInitialState(hash.ToLower());
|
||||
@@ -57,13 +64,13 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, Byte[] fileContent)
|
||||
{
|
||||
_proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||
Proxy.AddTorrentFromFile(filename, fileContent, Settings);
|
||||
|
||||
try
|
||||
{
|
||||
if (Settings.MovieCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_proxy.SetTorrentLabel(hash.ToLower(), Settings.MovieCategory, Settings);
|
||||
Proxy.SetTorrentLabel(hash.ToLower(), Settings.MovieCategory, Settings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -78,7 +85,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
if (isRecentMovie && Settings.RecentMoviePriority == (int)QBittorrentPriority.First ||
|
||||
!isRecentMovie && Settings.OlderMoviePriority == (int)QBittorrentPriority.First)
|
||||
{
|
||||
_proxy.MoveTorrentToTopInQueue(hash.ToLower(), Settings);
|
||||
Proxy.MoveTorrentToTopInQueue(hash.ToLower(), Settings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -100,8 +107,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
try
|
||||
{
|
||||
config = _proxy.GetConfig(Settings);
|
||||
torrents = _proxy.GetTorrents(Settings);
|
||||
config = Proxy.GetConfig(Settings);
|
||||
torrents = Proxy.GetTorrents(Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
@@ -113,17 +120,17 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
var item = new DownloadClientItem();
|
||||
item.DownloadId = torrent.Hash.ToUpper();
|
||||
item.Category = torrent.Category.IsNotNullOrWhiteSpace() ? torrent.Category : torrent.Label;
|
||||
item.Title = torrent.Name;
|
||||
item.TotalSize = torrent.Size;
|
||||
item.DownloadClient = Definition.Name;
|
||||
item.RemainingSize = (long)(torrent.Size * (1.0 - torrent.Progress));
|
||||
item.RemainingTime = GetRemainingTime(torrent);
|
||||
|
||||
item.OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.SavePath));
|
||||
|
||||
var item = new DownloadClientItem()
|
||||
{
|
||||
DownloadId = torrent.Hash.ToUpper(),
|
||||
Category = torrent.Category.IsNotNullOrWhiteSpace() ? torrent.Category : torrent.Label,
|
||||
Title = torrent.Name,
|
||||
TotalSize = torrent.Size,
|
||||
DownloadClient = Definition.Name,
|
||||
RemainingSize = (long)(torrent.Size * (1.0 - torrent.Progress)),
|
||||
RemainingTime = GetRemainingTime(torrent),
|
||||
OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(torrent.SavePath)),
|
||||
};
|
||||
// Avoid removing torrents that haven't reached the global max ratio.
|
||||
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
|
||||
item.CanMoveFiles = item.CanBeRemoved = (!config.MaxRatioEnabled || config.MaxRatio <= torrent.Ratio) && torrent.State == "pausedUP";
|
||||
@@ -154,7 +161,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
case "stalledUP": // torrent is being seeded, but no connection were made
|
||||
case "queuedUP": // queuing is enabled and torrent is queued for upload
|
||||
case "checkingUP": // torrent has finished downloading and is being checked
|
||||
case "forcedUP": // torrent is beeing seeded by force
|
||||
case "forcedUP": // torrent has finished downloading and is being forcibly seeded
|
||||
item.Status = DownloadItemStatus.Completed;
|
||||
item.RemainingTime = TimeSpan.Zero; // qBittorrent sends eta=8640000 for completed torrents
|
||||
break;
|
||||
@@ -164,13 +171,26 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
item.Message = "The download is stalled with no connections";
|
||||
break;
|
||||
|
||||
case "metaDL": // torrent magnet is being downloaded
|
||||
if (config.DhtEnabled)
|
||||
{
|
||||
item.Status = DownloadItemStatus.Queued;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Status = DownloadItemStatus.Warning;
|
||||
item.Message = "qBittorrent cannot resolve magnet link with DHT disabled";
|
||||
}
|
||||
break;
|
||||
|
||||
case "moving": // torrent is being moved from a folder
|
||||
case "downloading": // torrent is being downloaded and data is being transfered
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
break;
|
||||
|
||||
default: // new status in API? default to downloading
|
||||
item.Message = "Unknown download state: " + torrent.State;
|
||||
_logger.Warn(item.Message);
|
||||
_logger.Info(item.Message);
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
break;
|
||||
}
|
||||
@@ -183,12 +203,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
public override void RemoveItem(string hash, bool deleteData)
|
||||
{
|
||||
_proxy.RemoveTorrent(hash.ToLower(), deleteData, Settings);
|
||||
Proxy.RemoveTorrent(hash.ToLower(), deleteData, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientStatus GetStatus()
|
||||
{
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
var config = Proxy.GetConfig(Settings);
|
||||
|
||||
var destDir = new OsPath(config.SavePath);
|
||||
|
||||
@@ -211,8 +231,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
try
|
||||
{
|
||||
var version = _proxy.GetVersion(Settings);
|
||||
if (version < 5)
|
||||
var version = _proxySelector.GetProxy(Settings, true).GetApiVersion(Settings);
|
||||
if (version < Version.Parse("1.5"))
|
||||
{
|
||||
// API version 5 introduced the "save_path" property in /query/torrents
|
||||
return new NzbDroneValidationFailure("Host", "Unsupported client version")
|
||||
@@ -220,7 +240,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
DetailedDescription = "Please upgrade to qBittorrent version 3.2.4 or higher."
|
||||
};
|
||||
}
|
||||
else if (version < 6)
|
||||
else if (version < Version.Parse("1.6"))
|
||||
{
|
||||
// API version 6 introduced support for labels
|
||||
if (Settings.MovieCategory.IsNotNullOrWhiteSpace())
|
||||
@@ -234,7 +254,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
else if (Settings.MovieCategory.IsNullOrWhiteSpace())
|
||||
{
|
||||
// warn if labels are supported, but category is not provided
|
||||
return new NzbDroneValidationFailure("TvCategory", "Category is recommended")
|
||||
return new NzbDroneValidationFailure("MovieCategory", "Category is recommended")
|
||||
{
|
||||
IsWarning = true,
|
||||
DetailedDescription = "Radarr will not attempt to import completed downloads without a category."
|
||||
@@ -242,7 +262,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
}
|
||||
|
||||
// Complain if qBittorrent is configured to remove torrents on max ratio
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
var config = Proxy.GetConfig(Settings);
|
||||
if (config.MaxRatioEnabled && config.RemoveOnMaxRatio)
|
||||
{
|
||||
return new NzbDroneValidationFailure(String.Empty, "qBittorrent is configured to remove torrents when they reach their Share Ratio Limit")
|
||||
@@ -292,7 +312,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
try
|
||||
{
|
||||
var config = _proxy.GetConfig(Settings);
|
||||
var config = Proxy.GetConfig(Settings);
|
||||
|
||||
if (!config.QueueingEnabled)
|
||||
{
|
||||
@@ -319,7 +339,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.GetTorrents(Settings);
|
||||
Proxy.GetTorrents(Settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -337,13 +357,13 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
switch ((QBittorrentState)Settings.InitialState)
|
||||
{
|
||||
case QBittorrentState.ForceStart:
|
||||
_proxy.SetForceStart(hash, true, Settings);
|
||||
Proxy.SetForceStart(hash, true, Settings);
|
||||
break;
|
||||
case QBittorrentState.Start:
|
||||
_proxy.ResumeTorrent(hash, Settings);
|
||||
Proxy.ResumeTorrent(hash, Settings);
|
||||
break;
|
||||
case QBittorrentState.Pause:
|
||||
_proxy.PauseTorrent(hash, Settings);
|
||||
Proxy.PauseTorrent(hash, Settings);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -360,6 +380,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
return null;
|
||||
}
|
||||
|
||||
// qBittorrent sends eta=8640000 if unknown such as queued
|
||||
if (torrent.Eta == 8640000)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return TimeSpan.FromSeconds((int) torrent.Eta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
[JsonProperty(PropertyName = "queueing_enabled")]
|
||||
public bool QueueingEnabled { get; set; } = true;
|
||||
|
||||
[JsonProperty(PropertyName = "dht")]
|
||||
public bool DhtEnabled { get; set; } // DHT enabled (needed for more peers and magnet downloads)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
|
||||
using NzbDrone.Common.Http;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
public interface IQBittorrentProxy
|
||||
{
|
||||
bool IsApiSupported(QBittorrentSettings settings);
|
||||
Version GetApiVersion(QBittorrentSettings settings);
|
||||
string GetVersion(QBittorrentSettings settings);
|
||||
QBittorrentPreferences GetConfig(QBittorrentSettings settings);
|
||||
List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings);
|
||||
|
||||
void AddTorrentFromUrl(string torrentUrl, QBittorrentSettings settings);
|
||||
void AddTorrentFromFile(string fileName, Byte[] fileContent, QBittorrentSettings settings);
|
||||
|
||||
void RemoveTorrent(string hash, Boolean removeData, QBittorrentSettings settings);
|
||||
void SetTorrentLabel(string hash, string label, QBittorrentSettings settings);
|
||||
void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings);
|
||||
void PauseTorrent(string hash, QBittorrentSettings settings);
|
||||
void ResumeTorrent(string hash, QBittorrentSettings settings);
|
||||
void SetForceStart(string hash, bool enabled, QBittorrentSettings settings);
|
||||
}
|
||||
|
||||
public interface IQBittorrentProxySelector
|
||||
{
|
||||
IQBittorrentProxy GetProxy(QBittorrentSettings settings, bool force = false);
|
||||
}
|
||||
|
||||
public class QBittorrentProxySelector : IQBittorrentProxySelector
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly ICached<IQBittorrentProxy> _proxyCache;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private readonly IQBittorrentProxy _proxyV1;
|
||||
private readonly IQBittorrentProxy _proxyV2;
|
||||
|
||||
public QBittorrentProxySelector(QBittorrentProxyV1 proxyV1,
|
||||
QBittorrentProxyV2 proxyV2,
|
||||
IHttpClient httpClient,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_proxyCache = cacheManager.GetCache<IQBittorrentProxy>(GetType());
|
||||
_logger = logger;
|
||||
|
||||
_proxyV1 = proxyV1;
|
||||
_proxyV2 = proxyV2;
|
||||
}
|
||||
|
||||
public IQBittorrentProxy GetProxy(QBittorrentSettings settings, bool force)
|
||||
{
|
||||
var proxyKey = $"{settings.Host}_{settings.Port}";
|
||||
|
||||
if (force)
|
||||
{
|
||||
_proxyCache.Remove(proxyKey);
|
||||
}
|
||||
|
||||
return _proxyCache.Get(proxyKey, () => FetchProxy(settings), TimeSpan.FromMinutes(10.0));
|
||||
}
|
||||
|
||||
private IQBittorrentProxy FetchProxy(QBittorrentSettings settings)
|
||||
{
|
||||
if (_proxyV2.IsApiSupported(settings))
|
||||
{
|
||||
_logger.Trace("Using qbitTorrent API v2");
|
||||
return _proxyV2;
|
||||
}
|
||||
|
||||
if (_proxyV1.IsApiSupported(settings))
|
||||
{
|
||||
_logger.Trace("Using qbitTorrent API v1");
|
||||
return _proxyV1;
|
||||
}
|
||||
|
||||
throw new DownloadClientException("Unable to determine qBittorrent API version");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,41 +11,68 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
// API https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-Documentation
|
||||
|
||||
public interface IQBittorrentProxy
|
||||
{
|
||||
int GetVersion(QBittorrentSettings settings);
|
||||
QBittorrentPreferences GetConfig(QBittorrentSettings settings);
|
||||
List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings);
|
||||
|
||||
void AddTorrentFromUrl(string torrentUrl, QBittorrentSettings settings);
|
||||
void AddTorrentFromFile(string fileName, Byte[] fileContent, QBittorrentSettings settings);
|
||||
|
||||
void RemoveTorrent(string hash, Boolean removeData, QBittorrentSettings settings);
|
||||
void SetTorrentLabel(string hash, string label, QBittorrentSettings settings);
|
||||
void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings);
|
||||
void PauseTorrent(string hash, QBittorrentSettings settings);
|
||||
void ResumeTorrent(string hash, QBittorrentSettings settings);
|
||||
void SetForceStart(string hash, bool enabled, QBittorrentSettings settings);
|
||||
}
|
||||
|
||||
public class QBittorrentProxy : IQBittorrentProxy
|
||||
public class QBittorrentProxyV1 : IQBittorrentProxy
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
private readonly ICached<Dictionary<string, string>> _authCookieCache;
|
||||
|
||||
public QBittorrentProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger)
|
||||
public QBittorrentProxyV1(IHttpClient httpClient, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
|
||||
_authCookieCache = cacheManager.GetCache<Dictionary<string, string>>(GetType(), "authCookies");
|
||||
|
||||
}
|
||||
|
||||
public int GetVersion(QBittorrentSettings settings)
|
||||
public bool IsApiSupported(QBittorrentSettings settings)
|
||||
{
|
||||
// We can do the api test without having to authenticate since v4.1 will return 404 on the request.
|
||||
var request = BuildRequest(settings).Resource("/version/api");
|
||||
var response = ProcessRequest<int>(request, settings);
|
||||
request.SuppressHttpError = true;
|
||||
|
||||
try
|
||||
{
|
||||
var response = _httpClient.Execute(request.Build());
|
||||
|
||||
// Version request will return 404 if it doesn't exist.
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.HasHttpError)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", new HttpException(response));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Version GetApiVersion(QBittorrentSettings settings)
|
||||
{
|
||||
// Version request does not require authentication and will return 404 if it doesn't exist.
|
||||
var request = BuildRequest(settings).Resource("/version/api");
|
||||
var response = Version.Parse("1." + ProcessRequest(request, settings));
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public string GetVersion(QBittorrentSettings settings)
|
||||
{
|
||||
// Version request does not require authentication.
|
||||
var request = BuildRequest(settings).Resource("/version/qbittorrent");
|
||||
var response = ProcessRequest(request, settings).TrimStart('v');
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -60,10 +87,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
|
||||
public List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/query/torrents")
|
||||
.AddQueryParam("label", settings.MovieCategory)
|
||||
.AddQueryParam("category", settings.MovieCategory);
|
||||
|
||||
var request = BuildRequest(settings).Resource("/query/torrents");
|
||||
if (settings.MovieCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddQueryParam("label", settings.MovieCategory);
|
||||
request.AddQueryParam("category", settings.MovieCategory);
|
||||
}
|
||||
var response = ProcessRequest<List<QBittorrentTorrent>>(request, settings);
|
||||
|
||||
return response;
|
||||
@@ -80,6 +109,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
request.AddFormParameter("category", settings.MovieCategory);
|
||||
}
|
||||
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
|
||||
var result = ProcessRequest(request, settings);
|
||||
|
||||
// Note: Older qbit versions returned nothing, so we can't do != "Ok." here.
|
||||
@@ -100,6 +134,11 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
request.AddFormParameter("category", settings.MovieCategory);
|
||||
}
|
||||
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", "true");
|
||||
}
|
||||
|
||||
var result = ProcessRequest(request, settings);
|
||||
|
||||
// Note: Current qbit versions return nothing, so we can't do != "Ok." here.
|
||||
@@ -112,9 +151,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
public void RemoveTorrent(string hash, Boolean removeData, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource(removeData ? "/command/deletePerm" : "/command/delete")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
@@ -128,9 +167,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
ProcessRequest(setCategoryRequest, settings);
|
||||
}
|
||||
catch(DownloadClientException ex)
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// if setCategory fails due to method not being found, then try older setLabel command for qbittorent < v.3.3.5
|
||||
// if setCategory fails due to method not being found, then try older setLabel command for qBittorrent < v.3.3.5
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
var setLabelRequest = BuildRequest(settings).Resource("/command/setLabel")
|
||||
@@ -141,14 +180,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
ProcessRequest(setLabelRequest, settings);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/topPrio")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
try
|
||||
{
|
||||
ProcessRequest(request, settings);
|
||||
@@ -156,7 +195,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// qBittorrent rejects all Prio commands with 403: Forbidden if Options -> BitTorrent -> Torrent Queueing is not enabled
|
||||
#warning FIXME: so wouldn't the reauthenticate logic trigger on Forbidden?
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
return;
|
||||
@@ -170,9 +208,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
public void PauseTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/command/pause")
|
||||
.Post()
|
||||
.AddFormParameter("hash", hash);
|
||||
|
||||
.Post()
|
||||
.AddFormParameter("hash", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
@@ -181,7 +218,6 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
var request = BuildRequest(settings).Resource("/command/resume")
|
||||
.Post()
|
||||
.AddFormParameter("hash", hash);
|
||||
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
@@ -190,17 +226,17 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
var request = BuildRequest(settings).Resource("/command/setForceStart")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash)
|
||||
.AddFormParameter("value", enabled ? "true": "false");
|
||||
|
||||
.AddFormParameter("value", enabled ? "true" : "false");
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(QBittorrentSettings settings)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port);
|
||||
requestBuilder.LogResponseContent = true;
|
||||
requestBuilder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password);
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
NetworkCredential = new NetworkCredential(settings.Username, settings.Password)
|
||||
};
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
@@ -265,10 +301,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
_authCookieCache.Remove(authKey);
|
||||
|
||||
var authLoginRequest = BuildRequest(settings).Resource("/login")
|
||||
.Post()
|
||||
.AddFormParameter("username", settings.Username ?? string.Empty)
|
||||
.AddFormParameter("password", settings.Password ?? string.Empty)
|
||||
.Build();
|
||||
.Post()
|
||||
.AddFormParameter("username", settings.Username ?? string.Empty)
|
||||
.AddFormParameter("password", settings.Password ?? string.Empty)
|
||||
.Build();
|
||||
|
||||
HttpResponse response;
|
||||
try
|
||||
@@ -287,7 +323,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
|
||||
@@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||
{
|
||||
// API https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation
|
||||
|
||||
public class QBittorrentProxyV2 : IQBittorrentProxy
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
private readonly ICached<Dictionary<string, string>> _authCookieCache;
|
||||
|
||||
public QBittorrentProxyV2(IHttpClient httpClient, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
|
||||
_authCookieCache = cacheManager.GetCache<Dictionary<string, string>>(GetType(), "authCookies");
|
||||
}
|
||||
|
||||
public bool IsApiSupported(QBittorrentSettings settings)
|
||||
{
|
||||
// We can do the api test without having to authenticate since v3.2.0-v4.0.4 will return 404 on the request.
|
||||
var request = BuildRequest(settings).Resource("/api/v2/app/webapiVersion");
|
||||
request.SuppressHttpError = true;
|
||||
|
||||
try
|
||||
{
|
||||
var response = _httpClient.Execute(request.Build());
|
||||
|
||||
// Version request will return 404 if it doesn't exist.
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.HasHttpError)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", new HttpException(response));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Version GetApiVersion(QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/app/webapiVersion");
|
||||
var response = Version.Parse(ProcessRequest(request, settings));
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public string GetVersion(QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/app/version");
|
||||
var response = ProcessRequest(request, settings).TrimStart('v');
|
||||
|
||||
// eg "4.2alpha"
|
||||
return response;
|
||||
}
|
||||
|
||||
public QBittorrentPreferences GetConfig(QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/app/preferences");
|
||||
var response = ProcessRequest<QBittorrentPreferences>(request, settings);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public List<QBittorrentTorrent> GetTorrents(QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/info");
|
||||
if (settings.MovieCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddQueryParam("category", settings.MovieCategory);
|
||||
}
|
||||
var response = ProcessRequest<List<QBittorrentTorrent>>(request, settings);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public void AddTorrentFromUrl(string torrentUrl, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/add")
|
||||
.Post()
|
||||
.AddFormParameter("urls", torrentUrl);
|
||||
if (settings.MovieCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", settings.MovieCategory);
|
||||
}
|
||||
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", true);
|
||||
}
|
||||
|
||||
var result = ProcessRequest(request, settings);
|
||||
|
||||
// Note: Older qbit versions returned nothing, so we can't do != "Ok." here.
|
||||
if (result == "Fails.")
|
||||
{
|
||||
throw new DownloadClientException("Download client failed to add torrent by url");
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTorrentFromFile(string fileName, Byte[] fileContent, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/add")
|
||||
.Post()
|
||||
.AddFormUpload("torrents", fileName, fileContent);
|
||||
|
||||
if (settings.MovieCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddFormParameter("category", settings.MovieCategory);
|
||||
}
|
||||
|
||||
if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
|
||||
{
|
||||
request.AddFormParameter("paused", "true");
|
||||
}
|
||||
|
||||
var result = ProcessRequest(request, settings);
|
||||
|
||||
// Note: Current qbit versions return nothing, so we can't do != "Ok." here.
|
||||
if (result == "Fails.")
|
||||
{
|
||||
throw new DownloadClientException("Download client failed to add torrent");
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTorrent(string hash, Boolean removeData, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/delete")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
|
||||
if (removeData)
|
||||
{
|
||||
request.AddFormParameter("deleteFiles", "true");
|
||||
}
|
||||
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void SetTorrentLabel(string hash, string label, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/setCategory")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash)
|
||||
.AddFormParameter("category", label);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/topPrio")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
|
||||
try
|
||||
{
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
// qBittorrent rejects all Prio commands with 409: Conflict if Options -> BitTorrent -> Torrent Queueing is not enabled
|
||||
if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void PauseTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/pause")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void ResumeTorrent(string hash, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/resume")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash);
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
|
||||
{
|
||||
var request = BuildRequest(settings).Resource("/api/v2/torrents/setForceStart")
|
||||
.Post()
|
||||
.AddFormParameter("hashes", hash)
|
||||
.AddFormParameter("value", enabled ? "true" : "false");
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(QBittorrentSettings settings)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(settings.UseSsl, settings.Host, settings.Port)
|
||||
{
|
||||
LogResponseContent = true,
|
||||
NetworkCredential = new NetworkCredential(settings.Username, settings.Password)
|
||||
};
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private TResult ProcessRequest<TResult>(HttpRequestBuilder requestBuilder, QBittorrentSettings settings)
|
||||
where TResult : new()
|
||||
{
|
||||
var responseContent = ProcessRequest(requestBuilder, settings);
|
||||
|
||||
return Json.Deserialize<TResult>(responseContent);
|
||||
}
|
||||
|
||||
private string ProcessRequest(HttpRequestBuilder requestBuilder, QBittorrentSettings settings)
|
||||
{
|
||||
AuthenticateClient(requestBuilder, settings);
|
||||
|
||||
var request = requestBuilder.Build();
|
||||
request.LogResponseContent = true;
|
||||
|
||||
HttpResponse response;
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_logger.Debug("Authentication required, logging in.");
|
||||
|
||||
AuthenticateClient(requestBuilder, settings, true);
|
||||
|
||||
request = requestBuilder.Build();
|
||||
|
||||
response = _httpClient.Execute(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", ex);
|
||||
}
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, please check your settings.", ex);
|
||||
}
|
||||
|
||||
return response.Content;
|
||||
}
|
||||
|
||||
private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSettings settings, bool reauthenticate = false)
|
||||
{
|
||||
if (settings.Username.IsNullOrWhiteSpace() || settings.Password.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (reauthenticate)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var authKey = string.Format("{0}:{1}", requestBuilder.BaseUrl, settings.Password);
|
||||
|
||||
var cookies = _authCookieCache.Find(authKey);
|
||||
|
||||
if (cookies == null || reauthenticate)
|
||||
{
|
||||
_authCookieCache.Remove(authKey);
|
||||
|
||||
var authLoginRequest = BuildRequest(settings).Resource("/api/v2/auth/login")
|
||||
.Post()
|
||||
.AddFormParameter("username", settings.Username ?? string.Empty)
|
||||
.AddFormParameter("password", settings.Password ?? string.Empty)
|
||||
.Build();
|
||||
|
||||
HttpResponse response;
|
||||
try
|
||||
{
|
||||
response = _httpClient.Execute(authLoginRequest);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.Debug("qbitTorrent authentication failed.");
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
|
||||
}
|
||||
|
||||
throw new DownloadClientException("Failed to connect to qBittorrent, please check your settings.", ex);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
throw new DownloadClientUnavailableException("Failed to connect to qBittorrent, please check your settings.", ex);
|
||||
}
|
||||
|
||||
if (response.Content != "Ok.") // returns "Fails." on bad login
|
||||
{
|
||||
_logger.Debug("qbitTorrent authentication failed.");
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.");
|
||||
}
|
||||
|
||||
_logger.Debug("qBittorrent authentication succeeded.");
|
||||
|
||||
cookies = response.GetCookies();
|
||||
|
||||
_authCookieCache.Set(authKey, cookies);
|
||||
}
|
||||
|
||||
requestBuilder.SetCookies(cookies);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,11 +93,11 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
foreach (RTorrentTorrent torrent in torrents)
|
||||
{
|
||||
// Don't concern ourselves with categories other than specified
|
||||
if (torrent.Category != Settings.MovieCategory) continue;
|
||||
if (Settings.MovieCategory.IsNotNullOrWhiteSpace() && torrent.Category != Settings.MovieCategory) continue;
|
||||
|
||||
if (torrent.Path.StartsWith("."))
|
||||
{
|
||||
throw new DownloadClientException("Download paths paths must be absolute. Please specify variable \"directory\" in rTorrent.");
|
||||
throw new DownloadClientException("Download paths must be absolute. Please specify variable \"directory\" in rTorrent.");
|
||||
}
|
||||
|
||||
var item = new DownloadClientItem();
|
||||
|
||||
@@ -66,15 +66,13 @@ namespace NzbDrone.Core.Extras
|
||||
var sourcePath = localMovie.Path;
|
||||
var sourceFolder = _diskProvider.GetParentFolder(sourcePath);
|
||||
var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath);
|
||||
var files = _diskProvider.GetFiles(sourceFolder, SearchOption.TopDirectoryOnly);
|
||||
var files = _diskProvider.GetFiles(sourceFolder, SearchOption.AllDirectories).OrderByDescending(d => d).ToArray();
|
||||
|
||||
var wantedExtensions = _configService.ExtraFileExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(e => e.Trim(' ', '.'))
|
||||
.ToList();
|
||||
|
||||
var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
foreach (var matchingFilename in matchingFilenames)
|
||||
foreach (var matchingFilename in files)
|
||||
{
|
||||
var matchingExtension = wantedExtensions.FirstOrDefault(e => matchingFilename.EndsWith(e));
|
||||
|
||||
|
||||
@@ -88,11 +88,22 @@ namespace NzbDrone.Core.Extras.Subtitles
|
||||
if (SubtitleFileExtensions.Extensions.Contains(Path.GetExtension(path)))
|
||||
{
|
||||
var language = LanguageParser.ParseSubtitleLanguage(path);
|
||||
var suffix = GetSuffix(language, 1, false);
|
||||
var subtitleFile = ImportFile(movie, movieFile, path, readOnly, extension, suffix);
|
||||
subtitleFile.Language = language;
|
||||
var subtitleFiles = _subtitleFileService.GetFilesByMovie(movie.Id);
|
||||
var existingSrtSubs = subtitleFiles.Where(m => m.MovieFileId == movieFile.Id)
|
||||
.Where(m => m.Language == language)
|
||||
.Where(m => m.Extension == extension);
|
||||
|
||||
_subtitleFileService.Upsert(subtitleFile);
|
||||
var suffix = GetSuffix(language, existingSrtSubs.Count() + 1, extension.EqualsIgnoreCase(".srt"));
|
||||
var subtitleFile = new SubtitleFile();
|
||||
|
||||
if ((extension.EqualsIgnoreCase(".srt") && language != Language.Unknown) ||
|
||||
!extension.EqualsIgnoreCase(".srt"))
|
||||
{
|
||||
subtitleFile = ImportFile(movie, movieFile, path, readOnly, extension, suffix);
|
||||
subtitleFile.Language = language;
|
||||
|
||||
_subtitleFileService.Upsert(subtitleFile);
|
||||
}
|
||||
|
||||
return subtitleFile;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
@@ -9,50 +8,43 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
{
|
||||
public class MonoVersionCheck : HealthCheckBase
|
||||
{
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IPlatformInfo _platformInfo;
|
||||
private readonly Logger _logger;
|
||||
private static readonly Regex VersionRegex = new Regex(@"(?<=\W|^)(?<version>\d+\.\d+(\.\d+)?(\.\d+)?)(?=\W)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public MonoVersionCheck(IRuntimeInfo runtimeInfo, Logger logger)
|
||||
public MonoVersionCheck(IPlatformInfo platformInfo, Logger logger)
|
||||
{
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_platformInfo = platformInfo;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override HealthCheck Check()
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
if (!PlatformInfo.IsMono)
|
||||
{
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
var versionString = _runtimeInfo.RuntimeVersion;
|
||||
var versionMatch = VersionRegex.Match(versionString);
|
||||
var monoVersion = _platformInfo.Version;
|
||||
|
||||
if (versionMatch.Success)
|
||||
if (monoVersion == new Version("3.4.0") && HasMonoBug18599())
|
||||
{
|
||||
var version = new Version(versionMatch.Groups["version"].Value);
|
||||
|
||||
if (version == new Version(3, 4, 0) && HasMonoBug18599())
|
||||
{
|
||||
_logger.Debug("mono version 3.4.0, checking for mono bug #18599 returned positive.");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, "your mono version 3.4.0 has a critical bug, you should upgrade to a higher version");
|
||||
}
|
||||
|
||||
if (version == new Version(4, 4, 0) || version == new Version(4, 4, 1))
|
||||
{
|
||||
_logger.Debug("mono version {0}", version);
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, $"your mono version {version} has a bug that causes issues connecting to indexers/download clients");
|
||||
}
|
||||
|
||||
if (version >= new Version(3, 10))
|
||||
{
|
||||
_logger.Debug("mono version is 3.10 or better: {0}", version.ToString());
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
_logger.Debug("Mono version 3.4.0, checking for Mono bug #18599 returned positive.");
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, "You are running an old and unsupported version of Mono with a known bug. You should upgrade to a higher version");
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, "mono version is less than 3.10, upgrade for improved stability");
|
||||
if (monoVersion == new Version("4.4.0") || monoVersion == new Version("4.4.1"))
|
||||
{
|
||||
_logger.Debug("Mono version {0}", monoVersion);
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Your Mono version {monoVersion} has a bug that causes issues connecting to indexers/download clients. You should upgrade to a higher version");
|
||||
}
|
||||
|
||||
if (monoVersion >= new Version("3.10"))
|
||||
{
|
||||
_logger.Debug("Mono version is 3.10 or better: {0}", monoVersion);
|
||||
return new HealthCheck(GetType());
|
||||
}
|
||||
|
||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, "You are running an old and unsupported version of Mono. Please upgrade Mono for improved stability.");
|
||||
}
|
||||
|
||||
public override bool CheckOnConfigChange => false;
|
||||
@@ -70,7 +62,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||
return false;
|
||||
}
|
||||
|
||||
var fieldInfo = numberFormatterType.GetField("userFormatProvider", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
var fieldInfo = numberFormatterType.GetField("userFormatProvider",
|
||||
BindingFlags.Static | BindingFlags.NonPublic);
|
||||
|
||||
if (fieldInfo == null)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -60,7 +61,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
requestBuilder.AddQueryParam("ranked", "0");
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("category", "movies");
|
||||
requestBuilder.AddQueryParam("category", string.Join(";", Settings.Categories.Distinct()));
|
||||
requestBuilder.AddQueryParam("limit", "100");
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||
requestBuilder.AddQueryParam("format", "json_extended");
|
||||
@@ -98,7 +99,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
requestBuilder.AddQueryParam("ranked", "0");
|
||||
}
|
||||
|
||||
requestBuilder.AddQueryParam("category", "movies");
|
||||
requestBuilder.AddQueryParam("category", string.Join(";", Settings.Categories.Distinct()));
|
||||
requestBuilder.AddQueryParam("limit", "100");
|
||||
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
|
||||
requestBuilder.AddQueryParam("format", "json_extended");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Parser;
|
||||
@@ -12,6 +12,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
public RarbgSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.BaseUrl).ValidRootUrl();
|
||||
RuleFor(c => c.Categories).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +25,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
BaseUrl = "https://torrentapi.org";
|
||||
RankedOnly = false;
|
||||
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
|
||||
Categories = new[] { 14, 48, 17, 44, 45, 47, 50, 51, 52, 42, 46 };
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "API URL", HelpText = "URL to Rarbg api, not the website.")]
|
||||
@@ -44,6 +46,9 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
[FieldDefinition(5, Type = FieldType.Tag, SelectOptions = typeof(IndexerFlags), Label = "Required Flags", HelpText = "What indexer flags are required?", HelpLink = "https://github.com/Radarr/Radarr/wiki/Indexer-Flags#1-required-flags", Advanced = true)]
|
||||
public IEnumerable<int> RequiredFlags { get; set; }
|
||||
|
||||
[FieldDefinition(6, Type = FieldType.Textbox, Label = "Categories", HelpText = "Comma Separated list, you can retrieve the ID by checking the URL behind the category on the website (i.e. Movie/x264/1080 = 44)", HelpLink = "https://rarbgmirror.org/torrents.php?category=movies", Advanced = true)]
|
||||
public IEnumerable<int> Categories { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Common.Messaging;
|
||||
|
||||
namespace NzbDrone.Core.Lifecycle
|
||||
{
|
||||
public class ApplicationShutdownRequested : IEvent
|
||||
{
|
||||
public bool Restarting { get; set; }
|
||||
public bool Restarting { get; }
|
||||
|
||||
public ApplicationShutdownRequested()
|
||||
{
|
||||
}
|
||||
|
||||
public ApplicationShutdownRequested(bool restarting)
|
||||
public ApplicationShutdownRequested(bool restarting = false)
|
||||
{
|
||||
Restarting = restarting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Specifications
|
||||
var qualityComparer = new QualityModelComparer(localMovie.Movie.Profile);
|
||||
if (localMovie.Movie.MovieFile != null && qualityComparer.Compare(localMovie.Movie.MovieFile.Quality, localMovie.Quality) > 0)
|
||||
{
|
||||
_logger.Debug("This file isn't an upgrade for all episodes. Skipping {0}", localMovie.Path);
|
||||
return Decision.Reject("Not an upgrade for existing episode file(s)");
|
||||
_logger.Debug("This file isn't an upgrade for movie. Skipping {0}", localMovie.Path);
|
||||
return Decision.Reject("Not an upgrade for existing movie file");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
@@ -189,7 +189,6 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.ExceptronIgnoreOnMono();
|
||||
_logger.Warn(ex, "Unable to set date of file [" + filePath + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,11 +86,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
// The dude abides, so should us, Lets be nice to TMDb
|
||||
// var allowed = int.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()); // get allowed
|
||||
// var reset = long.Parse(response.Headers.GetValues("X-RateLimit-Reset").First()); // get time when it resets
|
||||
var remaining = int.Parse(response.Headers.GetValues("X-RateLimit-Remaining").First());
|
||||
if (remaining <= 5)
|
||||
if (response.Headers.ContainsKey("X-RateLimit-Remaining"))
|
||||
{
|
||||
_logger.Trace("Waiting 5 seconds to get information for the next 35 movies");
|
||||
Thread.Sleep(5000);
|
||||
var remaining = int.Parse(response.Headers.GetValues("X-RateLimit-Remaining").First());
|
||||
if (remaining <= 5)
|
||||
{
|
||||
_logger.Trace("Waiting 5 seconds to get information for the next 35 movies");
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
}
|
||||
|
||||
var resource = response.Resource;
|
||||
@@ -333,11 +336,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||
// The dude abides, so should us, Lets be nice to TMDb
|
||||
// var allowed = int.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()); // get allowed
|
||||
// var reset = long.Parse(response.Headers.GetValues("X-RateLimit-Reset").First()); // get time when it resets
|
||||
var remaining = int.Parse(response.Headers.GetValues("X-RateLimit-Remaining").First());
|
||||
if (remaining <= 5)
|
||||
if (response.Headers.ContainsKey("X-RateLimit-Remaining"))
|
||||
{
|
||||
_logger.Trace("Waiting 5 seconds to get information for the next 35 movies");
|
||||
Thread.Sleep(5000);
|
||||
var remaining = int.Parse(response.Headers.GetValues("X-RateLimit-Remaining").First());
|
||||
if (remaining <= 5)
|
||||
{
|
||||
_logger.Trace("Waiting 5 seconds to get information for the next 35 movies");
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
}
|
||||
|
||||
var resources = response.Resource;
|
||||
|
||||
@@ -17,30 +17,30 @@ namespace NzbDrone.Core.Movies.AlternativeTitles
|
||||
public int VoteCount { get; set; }
|
||||
public Language Language { get; set; }
|
||||
public LazyLoaded<Movie> Movie { get; set; }
|
||||
|
||||
public AlternativeTitle()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AlternativeTitle(string title, SourceType sourceType = SourceType.TMDB, int sourceId = 0, Language language = Language.English)
|
||||
{
|
||||
Title = title;
|
||||
CleanTitle = title.CleanSeriesTitle();
|
||||
SourceType = sourceType;
|
||||
SourceId = sourceId;
|
||||
Language = language;
|
||||
|
||||
public AlternativeTitle()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool IsTrusted(int minVotes = 3)
|
||||
public AlternativeTitle(string title, SourceType sourceType = SourceType.TMDB, int sourceId = 0, Language language = Language.English)
|
||||
{
|
||||
Title = title;
|
||||
CleanTitle = title.CleanSeriesTitle();
|
||||
SourceType = sourceType;
|
||||
SourceId = sourceId;
|
||||
Language = language;
|
||||
}
|
||||
|
||||
public bool IsTrusted(int minVotes = 4)
|
||||
{
|
||||
switch (SourceType)
|
||||
{
|
||||
case SourceType.TMDB:
|
||||
case SourceType.Mappings:
|
||||
return Votes >= minVotes;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Marr.Data;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
@@ -3,6 +3,7 @@ using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Movies.Events;
|
||||
|
||||
namespace NzbDrone.Core.Movies.AlternativeTitles
|
||||
@@ -13,7 +14,7 @@ namespace NzbDrone.Core.Movies.AlternativeTitles
|
||||
AlternativeTitle AddAltTitle(AlternativeTitle title, Movie movie);
|
||||
List<AlternativeTitle> AddAltTitles(List<AlternativeTitle> titles, Movie movie);
|
||||
AlternativeTitle GetById(int id);
|
||||
void DeleteNotEnoughVotes(List<AlternativeTitle> mappingsTitles);
|
||||
List<AlternativeTitle> UpdateTitles(List<AlternativeTitle> titles, Movie movie);
|
||||
}
|
||||
|
||||
public class AlternativeTitleService : IAlternativeTitleService, IHandleAsync<MovieDeletedEvent>
|
||||
@@ -63,11 +64,28 @@ namespace NzbDrone.Core.Movies.AlternativeTitles
|
||||
_titleRepo.Delete(title);
|
||||
}
|
||||
|
||||
public void DeleteNotEnoughVotes(List<AlternativeTitle> mappingsTitles)
|
||||
public List<AlternativeTitle> UpdateTitles(List<AlternativeTitle> titles, Movie movie)
|
||||
{
|
||||
var toRemove = mappingsTitles.Where(t => t.SourceType == SourceType.Mappings && t.Votes < 4);
|
||||
var realT = _titleRepo.FindBySourceIds(toRemove.Select(t => t.SourceId).ToList());
|
||||
_titleRepo.DeleteMany(realT);
|
||||
int movieId = movie.Id;
|
||||
// First update the movie ids so we can correlate them later.
|
||||
titles.ForEach(t => t.MovieId = movieId);
|
||||
// Then make sure none of them are the same as the main title.
|
||||
titles = titles.Where(t => t.CleanTitle != movie.CleanTitle).ToList();
|
||||
// Then make sure they are all distinct titles
|
||||
titles = titles.DistinctBy(t => t.CleanTitle).ToList();
|
||||
|
||||
// Now find titles to delete, update and insert.
|
||||
var existingTitles = _titleRepo.FindByMovieId(movieId);
|
||||
|
||||
var insert = titles.Where(t => !existingTitles.Contains(t));
|
||||
var update = existingTitles.Where(t => titles.Contains(t));
|
||||
var delete = existingTitles.Where(t => !titles.Contains(t));
|
||||
|
||||
_titleRepo.DeleteMany(delete.ToList());
|
||||
_titleRepo.UpdateMany(update.ToList());
|
||||
_titleRepo.InsertMany(insert.ToList());
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
public void HandleAsync(MovieDeletedEvent message)
|
||||
|
||||
@@ -108,26 +108,16 @@ namespace NzbDrone.Core.Movies
|
||||
_logger.Warn(e, "Couldn't update movie path for " + movie.Path);
|
||||
}
|
||||
|
||||
movieInfo.AlternativeTitles = movieInfo.AlternativeTitles.Where(t => t.CleanTitle != movie.CleanTitle)
|
||||
.DistinctBy(t => t.CleanTitle)
|
||||
.ExceptBy(t => t.CleanTitle, movie.AlternativeTitles, t => t.CleanTitle, EqualityComparer<string>.Default).ToList();
|
||||
|
||||
try
|
||||
{
|
||||
movie.AlternativeTitles.AddRange(_titleService.AddAltTitles(movieInfo.AlternativeTitles, movie));
|
||||
|
||||
var mappings = _apiClient.AlternativeTitlesAndYearForMovie(movieInfo.TmdbId);
|
||||
var mappingsTitles = mappings.Item1;
|
||||
|
||||
_titleService.DeleteNotEnoughVotes(mappingsTitles);
|
||||
mappingsTitles = mappingsTitles.Where(t => t.IsTrusted()).ToList();
|
||||
|
||||
mappingsTitles = mappingsTitles.ExceptBy(t => t.CleanTitle, movie.AlternativeTitles,
|
||||
t => t.CleanTitle, EqualityComparer<string>.Default).ToList();
|
||||
|
||||
|
||||
mappingsTitles = mappingsTitles.Where(t => t.Votes > 3).ToList();
|
||||
|
||||
movie.AlternativeTitles.AddRange(_titleService.AddAltTitles(mappingsTitles, movie));
|
||||
movieInfo.AlternativeTitles.AddRange(mappingsTitles);
|
||||
|
||||
movie.AlternativeTitles = _titleService.UpdateTitles(movieInfo.AlternativeTitles, movie);
|
||||
|
||||
if (mappings.Item2 != null)
|
||||
{
|
||||
|
||||
@@ -150,6 +150,9 @@
|
||||
<Compile Include="DecisionEngine\Specifications\CustomFormatAllowedByProfileSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\MaximumSizeSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RequiredIndexerFlagsSpecification.cs" />
|
||||
<Compile Include="Download\Clients\DownloadClientUnavailableException.cs" />
|
||||
<Compile Include="Download\Clients\QBittorrent\QBittorrentProxySelector.cs" />
|
||||
<Compile Include="Download\Clients\QBittorrent\QBittorrentProxyV2.cs" />
|
||||
<Compile Include="Download\InvalidNzbException.cs" />
|
||||
<Compile Include="Download\NzbValidationService.cs" />
|
||||
<Compile Include="Extras\Metadata\Consumers\Xbmc\XbmcNfoDetector.cs" />
|
||||
@@ -525,7 +528,7 @@
|
||||
<Compile Include="Download\Clients\rTorrent\RTorrentDirectoryValidator.cs" />
|
||||
<Compile Include="Download\Clients\QBittorrent\QBittorrent.cs" />
|
||||
<Compile Include="Download\Clients\QBittorrent\QBittorrentPriority.cs" />
|
||||
<Compile Include="Download\Clients\QBittorrent\QBittorrentProxy.cs" />
|
||||
<Compile Include="Download\Clients\QBittorrent\QBittorrentProxyV1.cs" />
|
||||
<Compile Include="Download\Clients\QBittorrent\QBittorrentSettings.cs" />
|
||||
<Compile Include="Download\Clients\QBittorrent\QBittorrentTorrent.cs" />
|
||||
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdPriorityTypeConverter.cs" />
|
||||
@@ -1336,4 +1339,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -31,6 +31,7 @@ namespace NzbDrone.Core.Parser
|
||||
new IsoLanguage("hu", "hun", Language.Hungarian),
|
||||
new IsoLanguage("he", "heb", Language.Hebrew),
|
||||
new IsoLanguage("cs", "ces", Language.Czech),
|
||||
new IsoLanguage("ua", "ukr", Language.Ukrainian),
|
||||
new IsoLanguage("an", "any", Language.Any)
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace NzbDrone.Core.Parser
|
||||
Hungarian = 22,
|
||||
Hebrew = 23,
|
||||
Czech = 24,
|
||||
Ukrainian = 25,
|
||||
Any = -1,
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,10 @@ namespace NzbDrone.Core.Parser
|
||||
{
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(LanguageParser));
|
||||
|
||||
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_|^)(?<italian>\b(?:ita|italian)\b)|(?<german>german\b|videomann)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)(?:FR|VOSTFR|VO|VFF|VFQ|VF2|TRUEFRENCH)(?:\W|_))|(?<russian>\brus\b)|(?<dutch>nl\W?subs?)|(?<hungarian>\b(?:HUNDUB|HUN)\b)|(?<hebrew>\bHebDub\b)|(?<czech>\b(?:CZ|SK)\b)",
|
||||
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_|^)(?<italian>\b(?:ita|italian)\b)|(?<german>german\b|videomann)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)(?:FR|VOSTFR|VO|VFF|VFQ|VF2|TRUEFRENCH)(?:\W|_))|(?<russian>\brus\b)|(?<dutch>nl\W?subs?)|(?<hungarian>\b(?:HUNDUB|HUN)\b)|(?<hebrew>\bHebDub\b)|(?<czech>\b(?:CZ|SK)\b)|(?<ukrainian>\bukr\b)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex SubtitleLanguageRegex = new Regex(".+?[-_. ](?<iso_code>[a-z]{2,3})(?:[-_. ]forced)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex RarbgSubtitleLanguageRegex = new Regex("^[0-9]{1,2}_(?<iso_code>[A-Za-z]{2,3}).*$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public static List<Language> ParseLanguages(string title)
|
||||
{
|
||||
var lowerTitle = title.ToLower();
|
||||
@@ -86,6 +85,9 @@ namespace NzbDrone.Core.Parser
|
||||
if (lowerTitle.Contains("czech"))
|
||||
languages.Add( Language.Czech);
|
||||
|
||||
if (lowerTitle.Contains("ukrainian"))
|
||||
languages.Add(Language.Ukrainian);
|
||||
|
||||
var match = LanguageRegex.Match(title);
|
||||
|
||||
if (match.Groups["italian"].Captures.Cast<Capture>().Any())
|
||||
@@ -118,6 +120,8 @@ namespace NzbDrone.Core.Parser
|
||||
if (match.Groups["czech"].Success)
|
||||
languages.Add( Language.Czech);
|
||||
|
||||
if (match.Groups["ukrainian"].Success)
|
||||
languages.Add( Language.Ukrainian);
|
||||
|
||||
return languages.DistinctBy(l => (int)l).ToList();
|
||||
}
|
||||
@@ -148,13 +152,19 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
var simpleFilename = Path.GetFileNameWithoutExtension(fileName);
|
||||
var languageMatch = SubtitleLanguageRegex.Match(simpleFilename);
|
||||
|
||||
if (!languageMatch.Success)
|
||||
{
|
||||
languageMatch = RarbgSubtitleLanguageRegex.Match(simpleFilename);
|
||||
}
|
||||
|
||||
if (languageMatch.Success)
|
||||
{
|
||||
var isoCode = languageMatch.Groups["iso_code"].Value;
|
||||
var isoLanguage = IsoLanguages.Find(isoCode);
|
||||
var isoLanguage = IsoLanguages.Find(isoCode.ToLower());
|
||||
|
||||
return isoLanguage?.Language ?? Language.Unknown;
|
||||
Logger.Debug("Parsed language: {0}", isoLanguage?.Language ?? Language.Unknown);
|
||||
return isoLanguage?.Language ?? Language.Unknown;
|
||||
}
|
||||
#if !LIBRARY
|
||||
Logger.Debug("Unable to parse langauge from subtitle file: {0}", fileName);
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex SixDigitAirDateRegex = new Regex(@"(?<=[_.-])(?<airdate>(?<!\d)(?<airyear>[1-9]\d{1})(?<airmonth>[0-1][0-9])(?<airday>[0-3][0-9]))(?=[_.-])",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex CleanReleaseGroupRegex = new Regex(@"^(.*?[-._ ](S\d+E\d+)[-._ ])|(-(RP|1|NZBGeek|Obfuscated|sample|Pre|postbot|xpost))+$",
|
||||
private static readonly Regex CleanReleaseGroupRegex = new Regex(@"^(.*?[-._ ](S\d+E\d+)[-._ ])|(-(RP|1|NZBGeek|Obfuscated|sample|Pre|postbot|xpost|Rakuv[a-z0-9]*|WhiteRev|BUYMORE|AsRequested|AlternativeToRequested|GEROV|Z0iDS3N|Chamele0n|4P|4Planet))+$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex CleanTorrentSuffixRegex = new Regex(@"\[(?:ettv|rartv|rarbg|cttv)\]$",
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b(?<hcsub>(\w+SUBS?)\b)|(?<hc>(HC|SUBBED))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
private static readonly Regex RemuxRegex = new Regex(@"\b(?<remux>(BD)?Remux)\b",
|
||||
private static readonly Regex RemuxRegex = new Regex(@"\b(?<remux>(BD|UHD)?Remux)\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex BRDISKRegex = new Regex(@"\b(COMPLETE|ISO|BDISO|BD25|BD50|BR.?DISK)\b",
|
||||
|
||||
@@ -7,12 +7,10 @@ namespace NzbDrone.Core.Rest
|
||||
{
|
||||
public static RestClient BuildClient(string baseUrl)
|
||||
{
|
||||
var restClient = new RestClient(baseUrl);
|
||||
|
||||
restClient.UserAgent = string.Format("Radarr/{0} (RestSharp/{1}; {2}/{3})",
|
||||
BuildInfo.Version,
|
||||
restClient.GetType().Assembly.GetName().Version,
|
||||
OsInfo.Os, OsInfo.Version.ToString(2));
|
||||
var restClient = new RestClient(baseUrl)
|
||||
{
|
||||
UserAgent = $"Radarr/{BuildInfo.Version} ({OsInfo.Os})"
|
||||
};
|
||||
|
||||
return restClient;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
@@ -14,16 +13,11 @@ namespace NzbDrone.Core.Update
|
||||
private readonly IUpdatePackageProvider _updatePackageProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
private readonly Logger _logger;
|
||||
|
||||
|
||||
public CheckUpdateService(IUpdatePackageProvider updatePackageProvider,
|
||||
IConfigFileProvider configFileProvider,
|
||||
Logger logger)
|
||||
IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_updatePackageProvider = updatePackageProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public UpdatePackage AvailableUpdate()
|
||||
@@ -31,4 +25,4 @@ namespace NzbDrone.Core.Update
|
||||
return _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -15,11 +15,13 @@ namespace NzbDrone.Core.Update
|
||||
public class UpdatePackageProvider : IUpdatePackageProvider
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IPlatformInfo _platformInfo;
|
||||
private readonly IHttpRequestBuilderFactory _requestBuilder;
|
||||
|
||||
public UpdatePackageProvider(IHttpClient httpClient, IRadarrCloudRequestBuilder requestBuilder)
|
||||
public UpdatePackageProvider(IHttpClient httpClient, IRadarrCloudRequestBuilder requestBuilder, IPlatformInfo platformInfo)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_platformInfo = platformInfo;
|
||||
_requestBuilder = requestBuilder.Services;
|
||||
}
|
||||
|
||||
@@ -29,6 +31,7 @@ namespace NzbDrone.Core.Update
|
||||
.Resource("/update/{branch}")
|
||||
.AddQueryParam("version", currentVersion)
|
||||
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("runtimeVer", _platformInfo.Version)
|
||||
.SetSegment("branch", branch)
|
||||
.Build();
|
||||
|
||||
@@ -45,6 +48,7 @@ namespace NzbDrone.Core.Update
|
||||
.Resource("/update/{branch}/changes")
|
||||
.AddQueryParam("version", currentVersion)
|
||||
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("runtimeVer", _platformInfo.Version)
|
||||
.SetSegment("branch", branch)
|
||||
.Build();
|
||||
|
||||
@@ -53,4 +57,4 @@ namespace NzbDrone.Core.Update
|
||||
return updates.Resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Radarr.Host.AccessControl
|
||||
private readonly INetshProvider _netshProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IOsInfo _osInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public List<string> Urls
|
||||
@@ -30,7 +31,7 @@ namespace Radarr.Host.AccessControl
|
||||
}
|
||||
}
|
||||
|
||||
private List<UrlAcl> InternalUrls { get; set; }
|
||||
private List<UrlAcl> InternalUrls { get; }
|
||||
private List<UrlAcl> RegisteredUrls { get; set; }
|
||||
|
||||
private static readonly Regex UrlAclRegex = new Regex(@"(?<scheme>https?)\:\/\/(?<address>.+?)\:(?<port>\d+)/(?<urlbase>.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
@@ -38,19 +39,26 @@ namespace Radarr.Host.AccessControl
|
||||
public UrlAclAdapter(INetshProvider netshProvider,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IRuntimeInfo runtimeInfo,
|
||||
IOsInfo osInfo,
|
||||
Logger logger)
|
||||
{
|
||||
_netshProvider = netshProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_osInfo = osInfo;
|
||||
_logger = logger;
|
||||
|
||||
InternalUrls = new List<UrlAcl>();
|
||||
RegisteredUrls = GetRegisteredUrls();
|
||||
RegisteredUrls = new List<UrlAcl>();
|
||||
}
|
||||
|
||||
public void ConfigureUrls()
|
||||
{
|
||||
if (RegisteredUrls.Empty())
|
||||
{
|
||||
GetRegisteredUrls();
|
||||
}
|
||||
|
||||
var localHostHttpUrls = BuildUrlAcls("http", "localhost", _configFileProvider.Port);
|
||||
var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, _configFileProvider.Port);
|
||||
|
||||
@@ -105,7 +113,8 @@ namespace Radarr.Host.AccessControl
|
||||
|
||||
private void RefreshRegistration()
|
||||
{
|
||||
if (OsInfo.Version.Major < 6) return;
|
||||
var osVersion = new Version(_osInfo.Version);
|
||||
if (osVersion.Major < 6) return;
|
||||
|
||||
foreach (var urlAcl in InternalUrls)
|
||||
{
|
||||
@@ -124,19 +133,24 @@ namespace Radarr.Host.AccessControl
|
||||
c.UrlBase == urlAcl.UrlBase);
|
||||
}
|
||||
|
||||
private List<UrlAcl> GetRegisteredUrls()
|
||||
private void GetRegisteredUrls()
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
return new List<UrlAcl>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (RegisteredUrls.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var arguments = string.Format("http show urlacl");
|
||||
var output = _netshProvider.Run(arguments);
|
||||
|
||||
if (output == null || !output.Standard.Any()) return new List<UrlAcl>();
|
||||
if (output == null || !output.Standard.Any()) return;
|
||||
|
||||
return output.Standard.Select(line =>
|
||||
RegisteredUrls = output.Standard.Select(line =>
|
||||
{
|
||||
var match = UrlAclRegex.Match(line.Content);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Radarr.Host
|
||||
//_cancelHandler = new CancelHandler();
|
||||
}
|
||||
|
||||
_runtimeInfo.IsRunning = true;
|
||||
_runtimeInfo.IsExiting = false;
|
||||
DbFactory.RegisterDatabase(_container);
|
||||
_hostController.StartServer();
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Radarr.Host
|
||||
_logger.Info("Attempting to stop application.");
|
||||
_hostController.StopServer();
|
||||
_logger.Info("Application has finished stop routine.");
|
||||
_runtimeInfo.IsRunning = false;
|
||||
_runtimeInfo.IsExiting = true;
|
||||
}
|
||||
|
||||
public void Handle(ApplicationShutdownRequested message)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Nancy.Bootstrapper;
|
||||
using NzbDrone.Api;
|
||||
using NzbDrone.Common.Composition;
|
||||
@@ -15,26 +15,15 @@ namespace Radarr.Host
|
||||
var assemblies = new List<string>
|
||||
{
|
||||
"Radarr.Host",
|
||||
"NzbDrone.Common",
|
||||
"NzbDrone.Core",
|
||||
"NzbDrone.Api",
|
||||
"NzbDrone.SignalR"
|
||||
};
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
assemblies.Add("NzbDrone.Windows");
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
assemblies.Add("NzbDrone.Mono");
|
||||
}
|
||||
|
||||
return new MainAppContainerBuilder(args, assemblies.ToArray()).Container;
|
||||
return new MainAppContainerBuilder(args, assemblies).Container;
|
||||
}
|
||||
|
||||
private MainAppContainerBuilder(StartupContext args, string[] assemblies)
|
||||
private MainAppContainerBuilder(StartupContext args, List<string> assemblies)
|
||||
: base(args, assemblies)
|
||||
{
|
||||
AutoRegisterImplementations<NzbDronePersistentConnection>();
|
||||
@@ -43,4 +32,4 @@ namespace Radarr.Host
|
||||
Container.Register<IHttpDispatcher, FallbackHttpDispatcher>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace Radarr.Host
|
||||
{
|
||||
@@ -8,14 +9,16 @@ namespace Radarr.Host
|
||||
private readonly INzbDroneServiceFactory _nzbDroneServiceFactory;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IConsoleService _consoleService;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public Router(INzbDroneServiceFactory nzbDroneServiceFactory, IServiceProvider serviceProvider,
|
||||
IConsoleService consoleService, Logger logger)
|
||||
IConsoleService consoleService, IRuntimeInfo runtimeInfo, Logger logger)
|
||||
{
|
||||
_nzbDroneServiceFactory = nzbDroneServiceFactory;
|
||||
_serviceProvider = serviceProvider;
|
||||
_consoleService = consoleService;
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -34,7 +37,7 @@ namespace Radarr.Host
|
||||
|
||||
case ApplicationModes.Interactive:
|
||||
{
|
||||
_logger.Debug("Console selected");
|
||||
_logger.Debug(_runtimeInfo.IsWindowsTray ? "Tray selected" : "Console selected");
|
||||
_nzbDroneServiceFactory.Start();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using NLog;
|
||||
using NLog.Common;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -28,7 +28,7 @@ namespace Radarr.Host
|
||||
|
||||
public void Spin()
|
||||
{
|
||||
while (_runtimeInfo.IsRunning)
|
||||
while (!_runtimeInfo.IsExiting)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using NzbDrone.Api.Commands;
|
||||
namespace NzbDrone.Integration.Test.ApiTests
|
||||
{
|
||||
[TestFixture]
|
||||
[Ignore("Not ready to be used on this branch")]
|
||||
public class CommandFixture : IntegrationTest
|
||||
{
|
||||
[Test]
|
||||
@@ -16,4 +15,4 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||
response.Id.Should().NotBe(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,41 +3,11 @@ using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Integration.Test.ApiTests
|
||||
namespace NzbDrone.Integration.Test.ApiTests.WantedTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class WantedFixture : IntegrationTest
|
||||
public class CutoffUnmetFixture : IntegrationTest
|
||||
{
|
||||
[Test, Order(0)]
|
||||
public void missing_should_be_empty()
|
||||
{
|
||||
EnsureNoMovie(680, "Pulp Fiction");
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc");
|
||||
|
||||
result.Records.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void missing_should_have_monitored_items()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", true);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc");
|
||||
|
||||
result.Records.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void missing_should_have_movie()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", true);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc");
|
||||
|
||||
result.Records.First().Title.Should().Be("Pulp Fiction");
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void cutoff_should_have_monitored_items()
|
||||
{
|
||||
@@ -45,21 +15,11 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||
var movie = EnsureMovie(680, "Pulp Fiction", true);
|
||||
EnsureMovieFile(movie, Quality.SDTV);
|
||||
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc");
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "true");
|
||||
|
||||
result.Records.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void missing_should_not_have_unmonitored_items()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", false);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc");
|
||||
|
||||
result.Records.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void cutoff_should_not_have_unmonitored_items()
|
||||
{
|
||||
@@ -67,7 +27,19 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||
var movie = EnsureMovie(680, "Pulp Fiction", false);
|
||||
EnsureMovieFile(movie, Quality.SDTV);
|
||||
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc");
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "true");
|
||||
|
||||
result.Records.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void cutoff_should_not_have_released_items()
|
||||
{
|
||||
EnsureProfileCutoff(1, Quality.HDTV720p);
|
||||
var movie = EnsureMovie(680, "Pulp Fiction", true);
|
||||
EnsureMovieFile(movie, Quality.SDTV);
|
||||
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "status", "inCinemas");
|
||||
|
||||
result.Records.Should().BeEmpty();
|
||||
}
|
||||
@@ -84,16 +56,6 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||
result.Records.First().Title.Should().Be("Pulp Fiction");
|
||||
}
|
||||
|
||||
[Test, Order(2)]
|
||||
public void missing_should_have_unmonitored_items()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", false);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "false");
|
||||
|
||||
result.Records.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(2)]
|
||||
public void cutoff_should_have_unmonitored_items()
|
||||
{
|
||||
@@ -105,5 +67,17 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||
|
||||
result.Records.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(2)]
|
||||
public void cutoff_should_have_released_items()
|
||||
{
|
||||
EnsureProfileCutoff(1, Quality.HDTV720p);
|
||||
var movie = EnsureMovie(680, "Pulp Fiction", false);
|
||||
EnsureMovieFile(movie, Quality.SDTV);
|
||||
|
||||
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "status", "released");
|
||||
|
||||
result.Records.Should().NotBeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Integration.Test.ApiTests.WantedTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class MissingFixture : IntegrationTest
|
||||
{
|
||||
[Test, Order(0)]
|
||||
public void missing_should_be_empty()
|
||||
{
|
||||
EnsureNoMovie(680, "Pulp Fiction");
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc");
|
||||
|
||||
result.Records.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void missing_should_have_monitored_items()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", true);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "true");
|
||||
|
||||
result.Records.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void missing_should_have_movie()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", true);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc");
|
||||
|
||||
result.Records.First().Title.Should().Be("Pulp Fiction");
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void missing_should_not_have_unmonitored_items()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", false);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "true");
|
||||
|
||||
result.Records.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(1)]
|
||||
public void missing_should_not_have_released_items()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", false);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "status", "inCinemas");
|
||||
|
||||
result.Records.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(2)]
|
||||
public void missing_should_have_unmonitored_items()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", false);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "false");
|
||||
|
||||
result.Records.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Test, Order(2)]
|
||||
public void missing_should_have_released_items()
|
||||
{
|
||||
EnsureMovie(680, "Pulp Fiction", false);
|
||||
|
||||
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "status", "released");
|
||||
|
||||
result.Records.Should().NotBeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,6 +235,8 @@ namespace NzbDrone.Integration.Test
|
||||
}
|
||||
}
|
||||
|
||||
Commands.WaitAll();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -254,9 +256,13 @@ namespace NzbDrone.Integration.Test
|
||||
|
||||
if (result.MovieFile == null)
|
||||
{
|
||||
var path = Path.Combine(MovieRootFolder, movie.Title, string.Format("{0} - {1}.mkv", movie.Title, quality.Name));
|
||||
var path = Path.Combine(MovieRootFolder, movie.Title, string.Format("{0} ({1}) - {2}.strm", movie.Title, movie.Year, quality.Name));
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
var sourcePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "ApiTests", "Files", "H264_sample.mp4");
|
||||
|
||||
//File.Copy(sourcePath, path);
|
||||
File.WriteAllText(path, "Fake Movie");
|
||||
|
||||
Commands.PostAndWait(new CommandResource { Name = "refreshmovie", Body = new RefreshMovieCommand(movie.Id) });
|
||||
|
||||
@@ -113,7 +113,8 @@
|
||||
<Compile Include="ApiTests\MovieFileFixture.cs" />
|
||||
<Compile Include="ApiTests\FileSystemFixture.cs" />
|
||||
<Compile Include="ApiTests\MovieLookupFixture.cs" />
|
||||
<Compile Include="ApiTests\WantedFixture.cs" />
|
||||
<Compile Include="ApiTests\WantedTests\CutoffUnmetFixture.cs" />
|
||||
<Compile Include="ApiTests\WantedTests\MissingFixture.cs" />
|
||||
<Compile Include="Client\ClientBase.cs" />
|
||||
<Compile Include="Client\IndexerClient.cs" />
|
||||
<Compile Include="Client\DownloadClientClient.cs" />
|
||||
@@ -180,20 +181,20 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent Condition="('$(OS)' == 'Windows_NT')">
|
||||
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Mono.*" "$(TargetDir)"
|
||||
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)"
|
||||
<PostBuildEvent Condition="('$(OS)' == 'Windows_NT')">
|
||||
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Mono.*" "$(TargetDir)"
|
||||
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)"
|
||||
</PostBuildEvent>
|
||||
<PostBuildEvent Condition="('$(OS)' != 'Windows_NT')">
|
||||
cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir)
|
||||
cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir)
|
||||
<PostBuildEvent Condition="('$(OS)' != 'Windows_NT')">
|
||||
cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir)
|
||||
cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir)
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Mono.EnvironmentInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Mono.Test.EnvironmentInfo
|
||||
{
|
||||
[TestFixture]
|
||||
[Platform("Mono")]
|
||||
public class MonoPlatformInfoFixture : TestBase<MonoPlatformInfo>
|
||||
{
|
||||
[Test]
|
||||
public void should_get_framework_version()
|
||||
{
|
||||
Subject.Version.Major.Should().BeOneOf(4, 5);
|
||||
if (Subject.Version.Major == 4)
|
||||
{
|
||||
Subject.Version.Minor.Should().BeOneOf(0, 5, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Mono.Disk;
|
||||
using NzbDrone.Mono.EnvironmentInfo.VersionAdapters;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Mono.Test.EnvironmentInfo
|
||||
{
|
||||
[TestFixture]
|
||||
[Platform("Mono")]
|
||||
public class ReleaseFileVersionAdapterFixture : TestBase<ReleaseFileVersionAdapter>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Mocker.SetConstant<IDiskProvider>(Mocker.Resolve<DiskProvider>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_version_info()
|
||||
{
|
||||
var info = Subject.Read();
|
||||
info.FullName.Should().NotBeNullOrWhiteSpace();
|
||||
info.Name.Should().NotBeNullOrWhiteSpace();
|
||||
info.Version.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Mono.EnvironmentInfo.VersionAdapters;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
||||
{
|
||||
[TestFixture]
|
||||
public class MacOsVersionAdapterFixture : TestBase<MacOsVersionAdapter>
|
||||
{
|
||||
[TestCase("10.8.0")]
|
||||
[TestCase("10.8")]
|
||||
[TestCase("10.8.1")]
|
||||
[TestCase("10.11.20")]
|
||||
public void should_get_version_info(string versionString)
|
||||
{
|
||||
var fileContent = File.ReadAllText(GetTestPath("Files/macOS/SystemVersion.plist")).Replace("10.0.0", versionString);
|
||||
|
||||
const string plistPath = "/System/Library/CoreServices/SystemVersion.plist";
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.FolderExists("/System/Library/CoreServices/")).Returns(true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.GetFiles("/System/Library/CoreServices/", SearchOption.TopDirectoryOnly))
|
||||
.Returns(new[] { plistPath });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.ReadAllText(plistPath))
|
||||
.Returns(fileContent);
|
||||
|
||||
var versionName = Subject.Read();
|
||||
versionName.Version.Should().Be(versionString);
|
||||
versionName.Name.Should().Be("macOS");
|
||||
versionName.FullName.Should().Be("macOS " + versionString);
|
||||
}
|
||||
|
||||
|
||||
[TestCase]
|
||||
public void should_detect_server()
|
||||
{
|
||||
var fileContent = File.ReadAllText(GetTestPath("Files/macOS/SystemVersion.plist"));
|
||||
|
||||
const string plistPath = "/System/Library/CoreServices/ServerVersion.plist";
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.FolderExists("/System/Library/CoreServices/")).Returns(true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.GetFiles("/System/Library/CoreServices/", SearchOption.TopDirectoryOnly))
|
||||
.Returns(new[] { plistPath });
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.ReadAllText(plistPath))
|
||||
.Returns(fileContent);
|
||||
|
||||
var versionName = Subject.Read();
|
||||
versionName.Name.Should().Be("macOS Server");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void should_return_null_if_folder_doesnt_exist()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.FolderExists("/System/Library/CoreServices/")).Returns(false);
|
||||
|
||||
Subject.Read().Should().BeNull();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly), Times.Never());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Mono.Disk;
|
||||
using NzbDrone.Mono.EnvironmentInfo.VersionAdapters;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Test.Common.Categories;
|
||||
|
||||
namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters
|
||||
{
|
||||
[TestFixture]
|
||||
public class ReleaseFileVersionAdapterFixture : TestBase<ReleaseFileVersionAdapter>
|
||||
{
|
||||
[Test]
|
||||
[IntegrationTest]
|
||||
[Platform("Mono")]
|
||||
public void should_get_version_info_from_actual_linux()
|
||||
{
|
||||
Mocker.SetConstant<IDiskProvider>(Mocker.Resolve<DiskProvider>());
|
||||
var info = Subject.Read();
|
||||
info.FullName.Should().NotBeNullOrWhiteSpace();
|
||||
info.Name.Should().NotBeNullOrWhiteSpace();
|
||||
info.Version.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_if_etc_doestn_exist()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists("/etc/")).Returns(false);
|
||||
Subject.Read().Should().BeNull();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly), Times.Never());
|
||||
|
||||
Subject.Read().Should().BeNull();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_return_null_if_release_file_doestn_exist()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists("/etc/")).Returns(true);
|
||||
Subject.Read().Should().BeNull();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly)).Returns(new string[0]);
|
||||
|
||||
Subject.Read().Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_detect_version()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists("/etc/")).Returns(true);
|
||||
Subject.Read().Should().BeNull();
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.GetFiles(It.IsAny<string>(), SearchOption.TopDirectoryOnly)).Returns(new[]
|
||||
{
|
||||
"/etc/lsb-release",
|
||||
"/etc/os-release"
|
||||
});
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.ReadAllText("/etc/lsb-release"))
|
||||
.Returns(File.ReadAllText(GetTestPath("Files/linux/lsb-release")));
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(c => c.ReadAllText("/etc/os-release"))
|
||||
.Returns(File.ReadAllText(GetTestPath("Files/linux/os-release")));
|
||||
|
||||
var version = Subject.Read();
|
||||
version.Should().NotBeNull();
|
||||
version.Name.Should().Be("ubuntu");
|
||||
version.Version.Should().Be("14.04");
|
||||
version.FullName.Should().Be("Ubuntu 14.04.5 LTS");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/NzbDrone.Mono.Test/Files/linux/lsb-release
Normal file
4
src/NzbDrone.Mono.Test/Files/linux/lsb-release
Normal file
@@ -0,0 +1,4 @@
|
||||
DISTRIB_ID=Ubuntu
|
||||
DISTRIB_RELEASE=14.04
|
||||
DISTRIB_CODENAME=trusty
|
||||
DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"
|
||||
9
src/NzbDrone.Mono.Test/Files/linux/os-release
Normal file
9
src/NzbDrone.Mono.Test/Files/linux/os-release
Normal file
@@ -0,0 +1,9 @@
|
||||
NAME="Ubuntu"
|
||||
VERSION="14.04.5 LTS, Trusty Tahr"
|
||||
ID=ubuntu
|
||||
ID_LIKE=debian
|
||||
PRETTY_NAME="Ubuntu 14.04.5 LTS"
|
||||
VERSION_ID="14.04"
|
||||
HOME_URL="http://www.ubuntu.com/"
|
||||
SUPPORT_URL="http://help.ubuntu.com/"
|
||||
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user