New: Removed libcurl http fallback since mono 5.16+ doesn't need it. Also bumped minimum mono version check to 5.16 (5.20 is the best choice atm)

This commit is contained in:
Taloth Saldono 2019-08-24 12:17:28 +02:00
parent 72902c8984
commit b3e84f407a
26 changed files with 208 additions and 469 deletions

4
.gitmodules vendored
View File

@ -1,4 +0,0 @@
[submodule "src/ExternalModules/CurlSharp"]
path = src/ExternalModules/CurlSharp
url = https://github.com/Sonarr/CurlSharp.git
branch = master

View File

@ -194,9 +194,6 @@ PackageMono()
echo "Adding Sonarr.Core.dll.config (for dllmap)" echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
echo "Adding CurlSharp.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $outputFolderLinux
# Below we deal with some mono incompatibilities with windows-only dotnet core/standard libs # Below we deal with some mono incompatibilities with windows-only dotnet core/standard libs
# See: https://github.com/mono/mono/blob/master/tools/nuget-hash-extractor/download.sh # See: https://github.com/mono/mono/blob/master/tools/nuget-hash-extractor/download.sh
# That list defines assemblies that are prohibited from being loaded from the appdir, instead loading from mono GAC. # That list defines assemblies that are prohibited from being loaded from the appdir, instead loading from mono GAC.
@ -308,12 +305,6 @@ PackageTests()
echo "Adding Sonarr.Core.dll.config (for dllmap)" echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolder cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolder
echo "Adding CurlSharp.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder
echo "Copying CurlSharp libraries"
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
ProgressEnd 'Creating Test Package' ProgressEnd 'Creating Test Package'
} }

View File

@ -16,7 +16,7 @@ Architecture: all
Provides: nzbdrone Provides: nzbdrone
Conflicts: nzbdrone Conflicts: nzbdrone
Replaces: nzbdrone Replaces: nzbdrone
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52) | libmediainfo0 (>= 0.7.52), mono-runtime (>= 5.4), libmono-system-runtime-interopservices-runtimeinformation4.0-cil, libmono-system-net-http4.0-cil, ${cli:Depends}, ${misc:Depends} Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52) | libmediainfo0 (>= 0.7.52), mono-runtime (>= 5.4), libmono-system-runtime-interopservices-runtimeinformation4.0-cil (>= 4.0.0~alpha1), libmono-system-net-http4.0-cil (>= 4.0.0~alpha1), ${cli:Depends}, ${misc:Depends}
Recommends: libmediainfo0v5 (>= 18.03) | libmediainfo0 (>= 18.03), libcurl4 | libcurl3 Recommends: libmediainfo0v5 (>= 18.03) | libmediainfo0 (>= 18.03)
Suggests: sqlite3 (>= 3.7), mediainfo (>= 0.7.52) Suggests: sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
Description: Internet PVR Description: Internet PVR

View File

@ -3,9 +3,7 @@
# Uncomment this to turn on verbose mode. # Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1 #export DH_VERBOSE=1
# Note: System.Native is a dependency of System.Runtime.InteropServices.RuntimeInformation used by SharpRaven, EXCLUDE_MODULEREFS = crypt32 httpapi
# but SharpRaven doesn't use any functions that need System.Native
EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
%: %:
dh $@ --with=systemd --with=cli dh $@ --with=systemd --with=cli

View File

@ -1,4 +1,2 @@
ignores msbuild ignores msbuild
ignores libmediainfo0v5 ignores libmediainfo0v5
ignores libcurl3
ignores libcurl4

View File

@ -154,12 +154,26 @@ class Health extends Component {
const internalLink = getInternalLink(item.source); const internalLink = getInternalLink(item.source);
const testLink = getTestLink(item.source, this.props); const testLink = getTestLink(item.source, this.props);
let kind = kinds.WARNING;
switch (item.type.toLowerCase()) {
case 'error':
kind = kinds.DANGER;
break;
default:
case 'warning':
kind = kinds.WARNING;
break;
case 'notice':
kind = kinds.INFO;
break;
}
return ( return (
<TableRow key={`health${item.message}`}> <TableRow key={`health${item.message}`}>
<TableRowCell> <TableRowCell>
<Icon <Icon
name={icons.DANGER} name={icons.DANGER}
kind={item.type.toLowerCase() === 'error' ? kinds.DANGER : kinds.WARNING} kind={kind}
title={titleCase(item.type)} title={titleCase(item.type)}
/> />
</TableRowCell> </TableRowCell>

View File

@ -20,8 +20,10 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Configuration Condition="'$(Configuration)'==''">Release</Configuration>
<!-- Centralize intermediate and default outputs --> <!-- Centralize intermediate and default outputs -->
<IntermediateOutputPath>$(SonarrRootDir)_temp\obj\$(Configuration)\$(MSBuildProjectName)\</IntermediateOutputPath> <BaseIntermediateOutputPath>$(SonarrRootDir)_temp\obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(SonarrRootDir)_temp\obj\$(MSBuildProjectName)\$(Configuration)\</IntermediateOutputPath>
<OutputPath>$(SonarrRootDir)_temp\bin\$(Configuration)\$(MSBuildProjectName)\</OutputPath> <OutputPath>$(SonarrRootDir)_temp\bin\$(Configuration)\$(MSBuildProjectName)\</OutputPath>
<!-- Output to _output and _tests respectively --> <!-- Output to _output and _tests respectively -->
@ -30,6 +32,7 @@
<OutputPath Condition="'$(SonarrOutputType)'=='Update'">$(SonarrRootDir)_output\Sonarr.Update\</OutputPath> <OutputPath Condition="'$(SonarrOutputType)'=='Update'">$(SonarrRootDir)_output\Sonarr.Update\</OutputPath>
<!-- Paths relative to project file for better readability --> <!-- Paths relative to project file for better readability -->
<BaseIntermediateOutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(BaseIntermediateOutputPath)'))</BaseIntermediateOutputPath>
<IntermediateOutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)'))</IntermediateOutputPath> <IntermediateOutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)'))</IntermediateOutputPath>
<OutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(OutputPath)'))</OutputPath> <OutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(OutputPath)'))</OutputPath>

@ -1 +0,0 @@
Subproject commit cfdbbbd9c6b9612c2756245049a8234ce87dc576

View File

@ -22,7 +22,6 @@ namespace NzbDrone.Common.Test.Http
{ {
[IntegrationTest] [IntegrationTest]
[TestFixture(typeof(ManagedHttpDispatcher))] [TestFixture(typeof(ManagedHttpDispatcher))]
[TestFixture(typeof(CurlHttpDispatcher))]
public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispatcher : IHttpDispatcher public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispatcher : IHttpDispatcher
{ {
private string[] _httpBinHosts; private string[] _httpBinHosts;

View File

@ -11,9 +11,4 @@
<ItemGroup> <ItemGroup>
<Reference Include="System.ServiceProcess" /> <Reference Include="System.ServiceProcess" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="..\ExternalModules\CurlSharp\libs\i386\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<dllmap dll="libcurl.dll" target="libcurl.so.4" />
<dllmap os="osx" dll="libcurl.dll" target="libcurl.4.dylib"/>
<!--<dllmap os="freebsd" dll="libcurl.dll" target="libcurl.so.4" />-->
<!--<dllmap os="solaris" dll="libcurl.dll" target="libcurl.so.4" />-->
</configuration>

View File

@ -1,335 +0,0 @@
using System;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using CurlSharp;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
namespace NzbDrone.Common.Http.Dispatchers
{
public class CurlHttpDispatcher : IHttpDispatcher
{
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";
private static readonly string _caBundleFilePath;
static CurlHttpDispatcher()
{
if (Assembly.GetExecutingAssembly().Location.IsNotNullOrWhiteSpace())
{
_caBundleFilePath = Path.Combine(Assembly.GetExecutingAssembly().Location, "..", _caBundleFileName);
}
else
{
_caBundleFilePath = _caBundleFileName;
}
}
public CurlHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, IUserAgentBuilder userAgentBuilder, Logger logger)
{
_proxySettingsProvider = proxySettingsProvider;
_userAgentBuilder = userAgentBuilder;
_logger = logger;
}
public bool CheckAvailability()
{
try
{
return CurlGlobalHandle.Instance.Initialize();
}
catch (Exception ex)
{
_logger.Trace(ex, "Initializing curl failed");
return false;
}
}
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
{
if (!CheckAvailability())
{
throw new ApplicationException("Curl failed to initialize.");
}
lock (CurlGlobalHandle.Instance)
{
Stream responseStream = new MemoryStream();
Stream headerStream = new MemoryStream();
using (var curlEasy = new CurlEasy())
{
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)
{
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));
}
}
}
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);
}
}
}
private void AddProxy(CurlEasy curlEasy, HttpRequest request)
{
var proxySettings = _proxySettingsProvider.GetProxySettings(request);
if (proxySettings != null)
{
switch (proxySettings.Type)
{
case ProxyType.Http:
curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Http);
curlEasy.SetOpt(CurlOption.ProxyAuth, CurlHttpAuth.Basic);
curlEasy.SetOpt(CurlOption.ProxyUserPwd, proxySettings.Username + ":" + proxySettings.Password.ToString());
break;
case ProxyType.Socks4:
curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks4);
curlEasy.SetOpt(CurlOption.ProxyUsername, proxySettings.Username);
curlEasy.SetOpt(CurlOption.ProxyPassword, proxySettings.Password);
break;
case ProxyType.Socks5:
curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks5);
curlEasy.SetOpt(CurlOption.ProxyUsername, proxySettings.Username);
curlEasy.SetOpt(CurlOption.ProxyPassword, proxySettings.Password);
break;
}
curlEasy.SetOpt(CurlOption.Proxy, proxySettings.Host + ":" + proxySettings.Port.ToString());
}
}
private CurlSlist SerializeHeaders(HttpRequest request)
{
if (!request.Headers.ContainsKey("Accept-Encoding"))
{
request.Headers.Add("Accept-Encoding", "gzip");
}
if (request.Headers.ContentType == null)
{
request.Headers.ContentType = string.Empty;
}
var curlHeaders = new CurlSlist();
foreach (var header in request.Headers)
{
curlHeaders.Append(header.Key + ": " + header.Value.ToString());
}
return curlHeaders;
}
private WebHeaderCollection ProcessHeaderStream(HttpRequest request, CookieContainer cookies, Stream headerStream)
{
headerStream.Position = 0;
var headerData = headerStream.ToBytes();
var headerString = Encoding.ASCII.GetString(headerData);
var webHeaderCollection = new WebHeaderCollection();
// following a redirect we could have two sets of headers, so only process the last one
foreach (var header in headerString.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
{
if (!header.Contains(":")) break;
webHeaderCollection.Add(header);
}
var setCookie = webHeaderCollection.Get("Set-Cookie");
if (setCookie != null && setCookie.Length > 0 && cookies != null)
{
try
{
cookies.SetCookies((Uri)request.Url, FixSetCookieHeader(setCookie));
}
catch (CookieException ex)
{
_logger.Debug("Rejected cookie {0}: {1}", ex.InnerException.Message, setCookie);
}
}
return webHeaderCollection;
}
private string FixSetCookieHeader(string setCookie)
{
// fix up the date if it was malformed
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";
DateTime dt;
if (DateTime.TryParseExact(match.Groups[2].Value, longFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt) ||
DateTime.TryParseExact(match.Groups[2].Value, shortFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt) ||
DateTime.TryParse(match.Groups[2].Value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt))
return match.Groups[1].Value + dt.ToUniversalTime().ToString(longFormat, CultureInfo.InvariantCulture) + " GMT";
else
return match.Value;
});
return setCookieClean;
}
private byte[] ProcessResponseStream(HttpRequest request, Stream responseStream, WebHeaderCollection webHeaderCollection)
{
responseStream.Position = 0;
if (responseStream.Length != 0)
{
var encoding = webHeaderCollection["Content-Encoding"];
if (encoding != null)
{
if (encoding.IndexOf("gzip") != -1)
{
responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
webHeaderCollection.Remove("Content-Encoding");
}
else if (encoding.IndexOf("deflate") != -1)
{
responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);
webHeaderCollection.Remove("Content-Encoding");
}
}
}
return responseStream.ToBytes();
}
}
internal sealed class CurlGlobalHandle : SafeHandle
{
public static readonly CurlGlobalHandle Instance = new CurlGlobalHandle();
private bool _initialized;
private bool _available;
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
private CurlGlobalHandle()
: base(IntPtr.Zero, true)
{
}
public bool Initialize()
{
lock (CurlGlobalHandle.Instance)
{
if (_initialized)
return _available;
_initialized = true;
_available = Curl.GlobalInit(CurlInitFlag.All) == CurlCode.Ok;
return _available;
}
}
protected override bool ReleaseHandle()
{
if (_initialized && _available)
{
Curl.GlobalCleanup();
_available = false;
}
return true;
}
public override bool IsInvalid => !_initialized || !_available;
}
}

View File

@ -1,56 +0,0 @@
using System;
using System.Net;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Http.Dispatchers
{
public class FallbackHttpDispatcher : IHttpDispatcher
{
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, 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 (PlatformInfo.IsMono && request.Url.Scheme == "https")
{
if (!_curlTLSFallbackCache.Find(request.Url.Host))
{
try
{
return _managedDispatcher.GetResponse(request, cookies);
}
catch (TlsFailureException)
{
_logger.Debug("https request failed in tls error for {0}, trying curl fallback.", request.Url.Host);
_curlTLSFallbackCache.Set(request.Url.Host, true);
}
}
if (_curlDispatcher.CheckAvailability())
{
return _curlDispatcher.GetResponse(request, cookies);
}
_logger.Trace("Curl not available, using default WebClient.");
}
return _managedDispatcher.GetResponse(request, cookies);
}
}
}

View File

@ -9,7 +9,7 @@ namespace NzbDrone.Common.Http
public class TlsFailureException : WebException public class TlsFailureException : WebException
{ {
public TlsFailureException(WebRequest request, WebException innerException) 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) : base("Failed to establish secure https connection to '" + request.RequestUri + "'.", innerException, WebExceptionStatus.SecureChannelFailure, innerException.Response)
{ {
} }

View File

@ -12,7 +12,6 @@
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ExternalModules\CurlSharp\CurlSharp\CurlSharp.csproj" />
<ProjectReference Include="..\LogentriesNLog\LogentriesNLog.csproj" /> <ProjectReference Include="..\LogentriesNLog\LogentriesNLog.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -23,10 +23,9 @@ namespace NzbDrone.Core.Test.Framework
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>())); Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>())); Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<ManagedHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<IPlatformInfo>(), TestLogger)); Mocker.SetConstant<IHttpDispatcher>(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<IHttpProvider>(new HttpProvider(TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<FallbackHttpDispatcher>(), Mocker.Resolve<UserAgentBuilder>(), TestLogger)); Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.Resolve<UserAgentBuilder>(), TestLogger));
Mocker.SetConstant<ISonarrCloudRequestBuilder>(new SonarrCloudRequestBuilder()); Mocker.SetConstant<ISonarrCloudRequestBuilder>(new SonarrCloudRequestBuilder());
} }
} }

View File

@ -0,0 +1,57 @@
using System;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class DotnetVersionCheckFixture : CoreTest<DotnetVersionCheck>
{
private void GivenOutput(string version)
{
WindowsOnly();
Mocker.GetMock<IPlatformInfo>()
.SetupGet(s => s.Version)
.Returns(new Version(version));
}
[TestCase("4.7.2")]
[TestCase("4.8")]
public void should_return_ok(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeOk();
}
[TestCase("4.6.2")]
[TestCase("4.7")]
[TestCase("4.7.1")]
public void should_return_notice(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeNotice();
}
public void should_return_warning(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeWarning();
}
[TestCase("4.5")]
[TestCase("4.5.2")]
[TestCase("4.6.1")]
public void should_return_error(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeError();
}
}
}

View File

@ -11,6 +11,16 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
result.Type.Should().Be(HealthCheckResult.Ok); result.Type.Should().Be(HealthCheckResult.Ok);
} }
public static void ShouldBeNotice(this Core.HealthCheck.HealthCheck result, string message = null)
{
result.Type.Should().Be(HealthCheckResult.Notice);
if (message.IsNotNullOrWhiteSpace())
{
result.Message.Should().Contain(message);
}
}
public static void ShouldBeWarning(this Core.HealthCheck.HealthCheck result, string message = null) public static void ShouldBeWarning(this Core.HealthCheck.HealthCheck result, string message = null)
{ {
result.Type.Should().Be(HealthCheckResult.Warning); result.Type.Should().Be(HealthCheckResult.Warning);

View File

@ -18,14 +18,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(new Version(version)); .Returns(new Version(version));
} }
[TestCase("5.18")]
[TestCase("4.6")] [TestCase("5.20")]
[TestCase("4.4.2")]
[TestCase("4.6")]
[TestCase("4.8")]
[TestCase("5.0")]
[TestCase("5.2")]
[TestCase("5.4")]
public void should_return_ok(string version) public void should_return_ok(string version)
{ {
GivenOutput(version); GivenOutput(version);
@ -33,6 +27,23 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Subject.Check().ShouldBeOk(); Subject.Check().ShouldBeOk();
} }
[TestCase("5.16")]
public void should_return_notice(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeNotice();
}
[TestCase("5.4")]
[TestCase("5.8")]
public void should_return_warning(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeWarning();
}
[TestCase("2.10.2")] [TestCase("2.10.2")]
[TestCase("2.10.8.1")] [TestCase("2.10.8.1")]
[TestCase("3.0.0.1")] [TestCase("3.0.0.1")]
@ -44,14 +55,6 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[TestCase("3.10")] [TestCase("3.10")]
[TestCase("4.0.0.0")] [TestCase("4.0.0.0")]
[TestCase("4.2")] [TestCase("4.2")]
public void should_return_warning(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeWarning();
}
[TestCase("4.4.0")] [TestCase("4.4.0")]
[TestCase("4.4.1")] [TestCase("4.4.1")]
public void should_return_error(string version) public void should_return_error(string version)

View File

@ -0,0 +1,52 @@
using System;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class DotnetVersionCheck : HealthCheckBase
{
private readonly IPlatformInfo _platformInfo;
private readonly Logger _logger;
public DotnetVersionCheck(IPlatformInfo platformInfo, Logger logger)
{
_platformInfo = platformInfo;
_logger = logger;
}
public override HealthCheck Check()
{
if (!PlatformInfo.IsDotNet)
{
return new HealthCheck(GetType());
}
var dotnetVersion = _platformInfo.Version;
// Target .Net version, which would allow us to increase our target framework
var targetVersion = new Version("4.7.2");
if (dotnetVersion >= targetVersion)
{
_logger.Debug("Dotnet version is {0} or better: {1}", targetVersion, dotnetVersion);
return new HealthCheck(GetType());
}
// Supported .net version but below our desired target
var stableVersion = new Version("4.6.2");
if (dotnetVersion >= stableVersion)
{
_logger.Debug("Dotnet version is {0} or better: {1}", stableVersion, dotnetVersion);
return new HealthCheck(GetType(), HealthCheckResult.Notice,
$"Currently installed .Net Framework {dotnetVersion} is supported but we recommend upgrading to at least {targetVersion}.",
"#currently-installed-net-framework-is-supported-but-upgrading-is-recommended");
}
return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed .Net Framework {dotnetVersion} is old and unsupported. Please upgrade the .Net Framework to at least {targetVersion}.",
"#currently-installed-net-framework-is-old-and-unsupported");
}
public override bool CheckOnSchedule => false;
}
}

View File

@ -2,7 +2,9 @@
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using NLog; using NLog;
using NLog.Fluent;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions;
namespace NzbDrone.Core.HealthCheck.Checks namespace NzbDrone.Core.HealthCheck.Checks
{ {
@ -26,11 +28,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
var monoVersion = _platformInfo.Version; var monoVersion = _platformInfo.Version;
if (monoVersion >= new Version("5.0.0") && Environment.GetEnvironmentVariable("MONO_TLS_PROVIDER") == "legacy") if (monoVersion >= new Version("5.8.0") && Environment.GetEnvironmentVariable("MONO_TLS_PROVIDER") == "legacy")
{ {
// Mono 5.0 still has issues in combination with libmediainfo, so disabling this check for now. _logger.Debug()
//_logger.Debug("Mono version 5.0.0 or higher and legacy TLS provider is selected, recommending user to switch to btls."); .Message("Mono version {0} and legacy TLS provider is selected, recommending user to switch to btls.", monoVersion)
//return new HealthCheck(GetType(), HealthCheckResult.Warning, "Sonarr now supports Mono 5.x with btls enabled, consider removing MONO_TLS_PROVIDER=legacy option"); .WriteSentryDebug("LegacyTlsProvider", monoVersion.ToString())
.Write();
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Sonarr Mono 4.x tls workaround still enabled, consider removing MONO_TLS_PROVIDER=legacy environment option");
} }
return new HealthCheck(GetType()); return new HealthCheck(GetType());

View File

@ -24,19 +24,47 @@ namespace NzbDrone.Core.HealthCheck.Checks
var monoVersion = _platformInfo.Version; var monoVersion = _platformInfo.Version;
// Known buggy Mono versions
if (monoVersion == new Version("4.4.0") || monoVersion == new Version("4.4.1")) if (monoVersion == new Version("4.4.0") || monoVersion == new Version("4.4.1"))
{ {
_logger.Debug("Mono version {0}", monoVersion); _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"); return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed Mono version {monoVersion} has a bug that causes issues connecting to indexers/download clients. You should upgrade to a higher version",
"#currently-installed-mono-version-is-old-and-unsupported");
} }
if (monoVersion >= new Version("4.4.2")) // Currently best stable Mono version (5.18 gets us .net 4.7.2 support)
var bestVersion = new Version("5.20");
var targetVersion = new Version("5.18");
if (monoVersion >= targetVersion)
{ {
_logger.Debug("Mono version is 4.4.2 or better: {0}", monoVersion); _logger.Debug("Mono version is {0} or better: {1}", targetVersion, monoVersion);
return new HealthCheck(GetType()); 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."); // Stable Mono versions
var stableVersion = new Version("5.16");
if (monoVersion >= stableVersion)
{
_logger.Debug("Mono version is {0} or better: {1}", stableVersion, monoVersion);
return new HealthCheck(GetType(), HealthCheckResult.Notice,
$"Currently installed Mono version {monoVersion} is supported but upgrading to {bestVersion} is recommended.",
"#currently-installed-mono-version-is-supported-but-upgrading-is-recommended");
}
// Old but supported Mono versions, there are known bugs
var supportedVersion = new Version("5.4");
if (monoVersion >= supportedVersion)
{
_logger.Debug("Mono version is {0} or better: {1}", supportedVersion, monoVersion);
return new HealthCheck(GetType(), HealthCheckResult.Warning,
$"Currently installed Mono version {monoVersion} is supported but has some known issues. Please upgrade Mono to version {bestVersion}.",
"#currently-installed-mono-version-is-supported-but-upgrading-is-recommended");
}
return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed Mono version {monoVersion} is old and unsupported. Please upgrade Mono to version {bestVersion}.",
"#currently-installed-mono-version-is-old-and-unsupported");
} }
public override bool CheckOnSchedule => false; public override bool CheckOnSchedule => false;

View File

@ -46,7 +46,8 @@ namespace NzbDrone.Core.HealthCheck
public enum HealthCheckResult public enum HealthCheckResult
{ {
Ok = 0, Ok = 0,
Warning = 1, Notice = 1,
Error = 2 Warning = 2,
Error = 3
} }
} }

View File

@ -31,7 +31,6 @@ namespace NzbDrone.Host
AutoRegisterImplementations<NzbDronePersistentConnection>(); AutoRegisterImplementations<NzbDronePersistentConnection>();
Container.Register<INancyBootstrapper, SonarrBootstrapper>(); Container.Register<INancyBootstrapper, SonarrBootstrapper>();
Container.Register<IHttpDispatcher, FallbackHttpDispatcher>();
} }
} }
} }

View File

@ -10,7 +10,7 @@ namespace NzbDrone.Update
private UpdateContainerBuilder(IStartupContext startupContext, List<string> assemblies) private UpdateContainerBuilder(IStartupContext startupContext, List<string> assemblies)
: base(startupContext, assemblies) : base(startupContext, assemblies)
{ {
Container.Register<IHttpDispatcher, FallbackHttpDispatcher>();
} }
public static IContainer Build(IStartupContext startupContext) public static IContainer Build(IStartupContext startupContext)

View File

@ -91,8 +91,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LogentriesNLog", "Logentrie
{90D6E9FC-7B88-4E1B-B018-8FA742274558} = {90D6E9FC-7B88-4E1B-B018-8FA742274558} {90D6E9FC-7B88-4E1B-B018-8FA742274558} = {90D6E9FC-7B88-4E1B-B018-8FA742274558}
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurlSharp", "ExternalModules\CurlSharp\CurlSharp\CurlSharp.csproj", "{74420A79-CC16-442C-8B1E-7C1B913844F0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sonarr.Api.V3", "Sonarr.Api.V3\Sonarr.Api.V3.csproj", "{7140FF1F-79BE-492F-9188-B21A050BF708}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sonarr.Api.V3", "Sonarr.Api.V3\Sonarr.Api.V3.csproj", "{7140FF1F-79BE-492F-9188-B21A050BF708}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sonarr.Http", "Sonarr.Http\Sonarr.Http.csproj", "{5370BFF7-1BD7-46BC-AF06-7D9EA5CDA1D6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sonarr.Http", "Sonarr.Http\Sonarr.Http.csproj", "{5370BFF7-1BD7-46BC-AF06-7D9EA5CDA1D6}"
@ -268,12 +266,6 @@ Global
{9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Mono|x86.Build.0 = Release|x86 {9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Mono|x86.Build.0 = Release|x86
{9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Release|x86.ActiveCfg = Release|x86 {9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Release|x86.ActiveCfg = Release|x86
{9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Release|x86.Build.0 = Release|x86 {9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Release|x86.Build.0 = Release|x86
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Debug|x86.ActiveCfg = Debug|Any CPU
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Debug|x86.Build.0 = Debug|Any CPU
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Mono|x86.ActiveCfg = Release|Any CPU
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Mono|x86.Build.0 = Release|Any CPU
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Release|x86.ActiveCfg = Release|Any CPU
{74420A79-CC16-442C-8B1E-7C1B913844F0}.Release|x86.Build.0 = Release|Any CPU
{7140FF1F-79BE-492F-9188-B21A050BF708}.Debug|x86.ActiveCfg = Debug|x86 {7140FF1F-79BE-492F-9188-B21A050BF708}.Debug|x86.ActiveCfg = Debug|x86
{7140FF1F-79BE-492F-9188-B21A050BF708}.Debug|x86.Build.0 = Debug|x86 {7140FF1F-79BE-492F-9188-B21A050BF708}.Debug|x86.Build.0 = Debug|x86
{7140FF1F-79BE-492F-9188-B21A050BF708}.Mono|x86.ActiveCfg = Release|x86 {7140FF1F-79BE-492F-9188-B21A050BF708}.Mono|x86.ActiveCfg = Release|x86
@ -322,7 +314,6 @@ Global
{411A9E0E-FDC6-4E25-828A-0C2CD1CD96F8} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} {411A9E0E-FDC6-4E25-828A-0C2CD1CD96F8} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}
{90D6E9FC-7B88-4E1B-B018-8FA742274558} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} {90D6E9FC-7B88-4E1B-B018-8FA742274558} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}
{9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} {9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}
{74420A79-CC16-442C-8B1E-7C1B913844F0} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA}
{C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5} = {57A04B72-8088-4F75-A582-1158CF8291F7} {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5} = {57A04B72-8088-4F75-A582-1158CF8291F7}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution