New: Platform Updates, Socket Closure Workaround

This commit is contained in:
Qstick 2018-04-21 00:59:31 -04:00
parent df068e9f0a
commit e100759e71
80 changed files with 1555 additions and 693 deletions

View file

@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
@ -23,7 +23,7 @@ public GzipCompressionPipeline(Logger logger)
_logger = logger; _logger = logger;
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case. // 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) public void Register(IPipelines pipelines)

View file

@ -14,7 +14,7 @@ public class CacheableSpecification : ICacheableSpecification
{ {
public bool IsCacheable(NancyContext context) public bool IsCacheable(NancyContext context)
{ {
if (!RuntimeInfoBase.IsProduction) if (!RuntimeInfo.IsProduction)
{ {
return false; return false;
} }
@ -46,4 +46,4 @@ public bool IsCacheable(NancyContext context)
return true; return true;
} }
} }
} }

View file

@ -79,7 +79,7 @@ protected override Stream GetContentStream(string filePath)
private string GetIndexText() private string GetIndexText()
{ {
if (RuntimeInfoBase.IsProduction && _generatedContent != null) if (RuntimeInfo.IsProduction && _generatedContent != null)
{ {
return _generatedContent; return _generatedContent;
} }
@ -111,7 +111,7 @@ private string GetIndexText()
text = text.Replace("APP_BRANCH", _configFileProvider.Branch.ToLower()); text = text.Replace("APP_BRANCH", _configFileProvider.Branch.ToLower());
text = text.Replace("APP_ANALYTICS", _analyticsService.IsEnabled.ToString().ToLowerInvariant()); text = text.Replace("APP_ANALYTICS", _analyticsService.IsEnabled.ToString().ToLowerInvariant());
text = text.Replace("URL_BASE", URL_BASE); 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; _generatedContent = text;

View file

@ -67,7 +67,7 @@ protected override Stream GetContentStream(string filePath)
private string GetLoginText() private string GetLoginText()
{ {
if (RuntimeInfoBase.IsProduction && _generatedContent != null) if (RuntimeInfo.IsProduction && _generatedContent != null)
{ {
return _generatedContent; return _generatedContent;
} }

View file

@ -21,7 +21,7 @@ protected StaticResourceMapperBase(IDiskProvider diskProvider, Logger logger)
_diskProvider = diskProvider; _diskProvider = diskProvider;
_logger = logger; _logger = logger;
if (!RuntimeInfoBase.IsProduction) if (!RuntimeInfo.IsProduction)
{ {
_caseSensitive = StringComparison.OrdinalIgnoreCase; _caseSensitive = StringComparison.OrdinalIgnoreCase;
} }

View file

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using Nancy.Bootstrapper; using Nancy.Bootstrapper;
using Nancy.Diagnostics; using Nancy.Diagnostics;
using NLog; using NLog;
@ -24,9 +24,9 @@ public NancyBootstrapper(TinyIoCContainer tinyIoCContainer)
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) 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); DiagnosticsHook.Disable(pipelines);
} }

View file

@ -1,4 +1,4 @@
using Nancy; using Nancy;
using Nancy.Routing; using Nancy.Routing;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -13,6 +13,8 @@ public class SystemModule : NzbDroneApiModule
{ {
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
private readonly IPlatformInfo _platformInfo;
private readonly IOsInfo _osInfo;
private readonly IRouteCacheProvider _routeCacheProvider; private readonly IRouteCacheProvider _routeCacheProvider;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
private readonly IMainDatabase _database; private readonly IMainDatabase _database;
@ -20,14 +22,17 @@ public class SystemModule : NzbDroneApiModule
public SystemModule(IAppFolderInfo appFolderInfo, public SystemModule(IAppFolderInfo appFolderInfo,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
IPlatformInfo platformInfo,
IOsInfo osInfo,
IRouteCacheProvider routeCacheProvider, IRouteCacheProvider routeCacheProvider,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
IMainDatabase database, IMainDatabase database,
ILifecycleService lifecycleService) ILifecycleService lifecycleService) : base("system")
: base("system")
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_runtimeInfo = runtimeInfo; _runtimeInfo = runtimeInfo;
_platformInfo = platformInfo;
_osInfo = osInfo;
_routeCacheProvider = routeCacheProvider; _routeCacheProvider = routeCacheProvider;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
_database = database; _database = database;
@ -41,27 +46,29 @@ public SystemModule(IAppFolderInfo appFolderInfo,
private Response GetStatus() private Response GetStatus()
{ {
return new return new
{ {
Version = BuildInfo.Version.ToString(), Version = BuildInfo.Version.ToString(),
BuildTime = BuildInfo.BuildDateTime, BuildTime = BuildInfo.BuildDateTime,
IsDebug = BuildInfo.IsDebug, IsDebug = BuildInfo.IsDebug,
IsProduction = RuntimeInfoBase.IsProduction, IsProduction = RuntimeInfo.IsProduction,
IsAdmin = _runtimeInfo.IsAdmin, IsAdmin = _runtimeInfo.IsAdmin,
IsUserInteractive = RuntimeInfoBase.IsUserInteractive, IsUserInteractive = RuntimeInfo.IsUserInteractive,
StartupPath = _appFolderInfo.StartUpFolder, StartupPath = _appFolderInfo.StartUpFolder,
AppData = _appFolderInfo.GetAppDataPath(), AppData = _appFolderInfo.GetAppDataPath(),
OsVersion = OsInfo.Version.ToString(), OsName = _osInfo.Name,
IsMonoRuntime = OsInfo.IsMonoRuntime, OsVersion = _osInfo.Version,
IsMono = OsInfo.IsNotWindows, IsMonoRuntime = PlatformInfo.IsMono,
IsLinux = OsInfo.IsLinux, IsMono = PlatformInfo.IsMono,
IsOsx = OsInfo.IsOsx, IsLinux = OsInfo.IsLinux,
IsWindows = OsInfo.IsWindows, IsOsx = OsInfo.IsOsx,
Branch = _configFileProvider.Branch, IsWindows = OsInfo.IsWindows,
Authentication = _configFileProvider.AuthenticationMethod, Branch = _configFileProvider.Branch,
SqliteVersion = _database.Version, Authentication = _configFileProvider.AuthenticationMethod,
UrlBase = _configFileProvider.UrlBase, SqliteVersion = _database.Version,
RuntimeVersion = _runtimeInfo.RuntimeVersion UrlBase = _configFileProvider.UrlBase,
}.AsResponse(); RuntimeVersion = _platformInfo.Version,
RuntimeName = PlatformInfo.Platform
}.AsResponse();
} }
private Response GetRoutes() private Response GetRoutes()

View file

@ -29,7 +29,7 @@ public void ApplicationPath_should_not_be_empty()
[Test] [Test]
public void IsProduction_should_return_false_when_run_within_nunit() 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] [Test]

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -9,6 +9,7 @@
using NLog; using NLog;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.Http.Proxy;
@ -30,6 +31,12 @@ public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispat
[SetUp] [SetUp]
public void 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<ICacheManager>(Mocker.Resolve<CacheManager>());
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>()); Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>()); Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
@ -48,7 +55,7 @@ public void SetUp()
[Test] [Test]
public void should_execute_simple_get() 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); var response = Subject.Execute(request);
@ -58,7 +65,7 @@ public void should_execute_simple_get()
[Test] [Test]
public void should_execute_https_get() 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); var response = Subject.Execute(request);
@ -68,7 +75,7 @@ public void should_execute_https_get()
[Test] [Test]
public void should_execute_typed_get() public void should_execute_typed_get()
{ {
var request = new HttpRequest(string.Format("http://{0}/get?test=1", _httpBinHost)); var request = new HttpRequest($"http://{_httpBinHost}/get?test=1");
var response = Subject.Get<HttpBinResource>(request); var response = Subject.Get<HttpBinResource>(request);
@ -81,7 +88,7 @@ public void should_execute_simple_post()
{ {
var message = "{ my: 1 }"; var message = "{ my: 1 }";
var request = new HttpRequest(string.Format("http://{0}/post", _httpBinHost)); var request = new HttpRequest($"http://{_httpBinHost}/post");
request.SetContent(message); request.SetContent(message);
var response = Subject.Post<HttpBinResource>(request); var response = Subject.Post<HttpBinResource>(request);
@ -92,7 +99,7 @@ public void should_execute_simple_post()
[TestCase("gzip")] [TestCase("gzip")]
public void should_execute_get_using_gzip(string compression) 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); var response = Subject.Get<HttpBinResource>(request);
@ -108,7 +115,7 @@ public void should_execute_get_using_gzip(string compression)
[TestCase(HttpStatusCode.BadGateway)] [TestCase(HttpStatusCode.BadGateway)]
public void should_throw_on_unsuccessful_status_codes(int statusCode) 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)); var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
@ -120,7 +127,7 @@ public void should_throw_on_unsuccessful_status_codes(int statusCode)
[Test] [Test]
public void should_not_follow_redirects_when_not_in_production() 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); Subject.Get(request);
@ -130,7 +137,7 @@ public void should_not_follow_redirects_when_not_in_production()
[Test] [Test]
public void should_follow_redirects() 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; request.AllowAutoRedirect = true;
var response = Subject.Get(request); var response = Subject.Get(request);
@ -183,7 +190,7 @@ public void should_throw_on_too_many_redirects()
[Test] [Test]
public void should_send_user_agent() 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); var response = Subject.Get<HttpBinResource>(request);
@ -197,7 +204,7 @@ public void should_send_user_agent()
[TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")] [TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")]
public void should_send_headers(string header, string value) 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); request.Headers.Add(header, value);
var response = Subject.Get<HttpBinResource>(request); var response = Subject.Get<HttpBinResource>(request);
@ -220,7 +227,7 @@ public void should_not_download_file_with_error()
[Test] [Test]
public void should_send_cookie() 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"; request.Cookies["my"] = "cookie";
var response = Subject.Get<HttpBinResource>(request); var response = Subject.Get<HttpBinResource>(request);
@ -237,7 +244,7 @@ public void GivenOldCookie()
var oldRequest = new HttpRequest("http://eu.httpbin.org/get"); var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
oldRequest.Cookies["my"] = "cookie"; 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); oldClient.Should().NotBeSameAs(Subject);
@ -330,7 +337,7 @@ public void should_delete_request_cookie()
[Test] [Test]
public void should_not_store_response_cookie() 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.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = false; requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie.Should().BeFalse(); requestSet.StoreResponseCookie.Should().BeFalse();
@ -349,7 +356,7 @@ public void should_not_store_response_cookie()
[Test] [Test]
public void should_store_response_cookie() 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.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = false; requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie = true; requestSet.StoreResponseCookie = true;
@ -528,7 +535,7 @@ public void should_not_send_old_cookie()
[Test] [Test]
public void should_throw_on_http429_too_many_requests() 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)); Assert.Throws<TooManyRequestsException>(() => Subject.Get(request));
@ -548,7 +555,7 @@ public void should_call_interceptor()
.Setup(v => v.PostResponse(It.IsAny<HttpResponse>())) .Setup(v => v.PostResponse(It.IsAny<HttpResponse>()))
.Returns<HttpResponse>(r => r); .Returns<HttpResponse>(r => r);
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost)); var request = new HttpRequest($"http://{_httpBinHost}/get");
Subject.Get(request); Subject.Get(request);
@ -570,7 +577,7 @@ public void should_parse_malformed_cloudflare_cookie(string culture)
{ {
// the date is bad in the below - should be 13-Jul-2026 // 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"; 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) .AddQueryParam("Set-Cookie", malformedCookie)
.Build(); .Build();
@ -579,7 +586,7 @@ public void should_parse_malformed_cloudflare_cookie(string culture)
var responseSet = Subject.Get(requestSet); 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); var response = Subject.Get<HttpBinResource>(request);
@ -603,7 +610,8 @@ public void should_reject_malformed_domain_cookie(string malformedCookie)
{ {
try 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); var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false; requestSet.AllowAutoRedirect = false;
@ -611,7 +619,7 @@ public void should_reject_malformed_domain_cookie(string malformedCookie)
var responseSet = Subject.Get(requestSet); 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); var response = Subject.Get<HttpBinResource>(request);

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

View file

@ -91,6 +91,7 @@
<Compile Include="Http\HttpRequestBuilderFixture.cs" /> <Compile Include="Http\HttpRequestBuilderFixture.cs" />
<Compile Include="Http\HttpRequestFixture.cs" /> <Compile Include="Http\HttpRequestFixture.cs" />
<Compile Include="Http\HttpUriFixture.cs" /> <Compile Include="Http\HttpUriFixture.cs" />
<Compile Include="Http\UserAgentBuilderFixture.cs" />
<Compile Include="InstrumentationTests\CleanseLogMessageFixture.cs" /> <Compile Include="InstrumentationTests\CleanseLogMessageFixture.cs" />
<Compile Include="LevenshteinDistanceFixture.cs" /> <Compile Include="LevenshteinDistanceFixture.cs" />
<Compile Include="OsPathFixture.cs" /> <Compile Include="OsPathFixture.cs" />

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -13,12 +13,15 @@ public abstract class ContainerBuilderBase
{ {
private readonly List<Type> _loadedTypes; 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>(); _loadedTypes = new List<Type>();
assemblies.Add(OsInfo.IsWindows ? "NzbDrone.Windows" : "NzbDrone.Mono");
assemblies.Add("NzbDrone.Common");
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{ {
_loadedTypes.AddRange(Assembly.Load(assembly).GetTypes()); _loadedTypes.AddRange(Assembly.Load(assembly).GetTypes());

View file

@ -17,6 +17,19 @@ public abstract class DiskProviderBase : IDiskProvider
{ {
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProviderBase)); 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 long? GetAvailableSpace(string path);
public abstract void InheritFolderPermissions(string filename); public abstract void InheritFolderPermissions(string filename);
public abstract void SetPermissions(string path, string mask, string user, string group); public abstract void SetPermissions(string path, string mask, string user, string group);
@ -87,7 +100,7 @@ public bool FolderExists(string path)
public bool FileExists(string path) public bool FileExists(string path)
{ {
Ensure.That(path, () => path).IsValidPath(); Ensure.That(path, () => path).IsValidPath();
return FileExists(path, OsInfo.PathStringComparison); return FileExists(path, PathStringComparison);
} }
public bool FileExists(string path, StringComparison stringComparison) public bool FileExists(string path, StringComparison stringComparison)

View file

@ -101,12 +101,12 @@ public static Param<string> IsValidPath(this Param<string> param)
if (param.Value.IsPathValid()) return param; 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));
} }
} }
} }

View file

@ -0,0 +1,9 @@
namespace NzbDrone.Common.EnvironmentInfo
{
public interface IOperatingSystemVersionInfo
{
string Version { get; }
string Name { get; }
string FullName { get; }
}
}

View file

@ -0,0 +1,9 @@
namespace NzbDrone.Common.EnvironmentInfo
{
public interface IOsVersionAdapter
{
bool Enabled { get; }
OsVersionModel Read();
}
}

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

View file

@ -1,14 +1,13 @@
namespace NzbDrone.Common.EnvironmentInfo namespace NzbDrone.Common.EnvironmentInfo
{ {
public interface IRuntimeInfo public interface IRuntimeInfo
{ {
bool IsUserInteractive { get; } bool IsUserInteractive { get; }
bool IsAdmin { get; } bool IsAdmin { get; }
bool IsWindowsService { get; } bool IsWindowsService { get; }
bool IsConsole { get; } bool IsWindowsTray { get; }
bool IsRunning { get; set; } bool IsExiting { get; set; }
bool RestartPending { get; set; } bool RestartPending { get; set; }
string ExecutingApplication { get; } string ExecutingApplication { get; }
string RuntimeVersion { get; }
} }
} }

View file

@ -1,87 +1,95 @@
using System; using System;
using System.Diagnostics; using System.Collections.Generic;
using System.Globalization; using System.IO;
using System.Runtime.InteropServices; using System.Linq;
using NLog;
namespace NzbDrone.Common.EnvironmentInfo 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() static OsInfo()
{ {
var platform = (int)Environment.OSVersion.Platform; var platform = Environment.OSVersion.Platform;
Version = Environment.OSVersion.Version; switch (platform)
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)
{ {
Os = Os.Windows; case PlatformID.Win32NT:
PathStringComparison = StringComparison.OrdinalIgnoreCase; {
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 else
{ {
Os = IsOsx ? Os.Osx : Os.Linux; Name = Os.ToString();
FullName = Name;
PathStringComparison = StringComparison.Ordinal;
} }
} }
}
public static Version Version { get; private set; } public interface IOsInfo
public static bool IsMonoRuntime { get; private set; } {
public static bool IsNotWindows { get; private set; } string Version { get; }
public static bool IsLinux { get; private set; } string Name { get; }
public static bool IsOsx { get; private set; } string FullName { get; }
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 enum Os public enum Os
@ -90,4 +98,4 @@ public enum Os
Linux, Linux,
Osx Osx
} }
} }

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

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
@ -9,11 +9,11 @@
namespace NzbDrone.Common.EnvironmentInfo namespace NzbDrone.Common.EnvironmentInfo
{ {
public abstract class RuntimeInfoBase : IRuntimeInfo public class RuntimeInfo : IRuntimeInfo
{ {
private readonly Logger _logger; private readonly Logger _logger;
public RuntimeInfoBase(IServiceProvider serviceProvider, Logger logger) public RuntimeInfo(IServiceProvider serviceProvider, Logger logger)
{ {
_logger = logger; _logger = logger;
@ -28,10 +28,11 @@ public RuntimeInfoBase(IServiceProvider serviceProvider, Logger logger)
if (entry != null) if (entry != null)
{ {
ExecutingApplication = entry.Location; ExecutingApplication = entry.Location;
IsWindowsTray = entry.ManifestModule.Name == $"{ProcessProvider.NZB_DRONE_PROCESS_NAME}.exe";
} }
} }
static RuntimeInfoBase() static RuntimeInfo()
{ {
IsProduction = InternalIsProduction(); IsProduction = InternalIsProduction();
} }
@ -59,31 +60,18 @@ public bool IsAdmin
public bool IsWindowsService { get; private set; } public bool IsWindowsService { get; private set; }
public bool IsConsole public bool IsExiting { get; set; }
{
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 RestartPending { 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; }
public static bool IsProduction { get; private set; }
private static bool InternalIsProduction() private static bool InternalIsProduction()
{ {
if (BuildInfo.IsDebug || Debugger.IsAttached) return false; 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 try
{ {
@ -99,21 +87,23 @@ private static bool InternalIsProduction()
} }
try try
{ {
var currentAssmeblyLocation = typeof(RuntimeInfoBase).Assembly.Location; var currentAssmeblyLocation = typeof(RuntimeInfo).Assembly.Location;
if(currentAssmeblyLocation.ToLower().Contains("_output"))return false; if (currentAssmeblyLocation.ToLower().Contains("_output")) return false;
} }
catch catch
{ {
} }
string lowerCurrentDir = Directory.GetCurrentDirectory().ToLower(); var lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
if (lowerCurrentDir.Contains("teamcity")) return false; if (lowerCurrentDir.Contains("teamcity")) return false;
if (lowerCurrentDir.Contains("_output")) return false; if (lowerCurrentDir.Contains("_output")) return false;
return true; return true;
} }
public bool IsWindowsTray { get; private set; }
} }
} }

View file

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

View file

@ -1,7 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -47,7 +48,7 @@ public static bool PathEquals(this string firstPath, string secondPath, StringCo
{ {
if (!comparison.HasValue) if (!comparison.HasValue)
{ {
comparison = OsInfo.PathStringComparison; comparison = DiskProviderBase.PathStringComparison;
} }
if (firstPath.Equals(secondPath, comparison.Value)) return true; if (firstPath.Equals(secondPath, comparison.Value)) return true;
@ -93,7 +94,7 @@ public static bool IsParentPath(this string parentPath, string childPath)
while (child.Parent != null) while (child.Parent != null)
{ {
if (child.Parent.FullName.Equals(parent.FullName, OsInfo.PathStringComparison)) if (child.Parent.FullName.Equals(parent.FullName, DiskProviderBase.PathStringComparison))
{ {
return true; return true;
} }
@ -275,4 +276,4 @@ public static string GetNlogConfigPath(this IAppFolderInfo appFolderInfo)
return Path.Combine(appFolderInfo.StartUpFolder, NLOG_CONFIG_FILE); return Path.Combine(appFolderInfo.StartUpFolder, NLOG_CONFIG_FILE);
} }
} }
} }

View file

@ -21,6 +21,7 @@ public class CurlHttpDispatcher : IHttpDispatcher
private static readonly Regex ExpiryDate = new Regex(@"(expires=)([^;]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex ExpiryDate = new Regex(@"(expires=)([^;]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private readonly IHttpProxySettingsProvider _proxySettingsProvider; private readonly IHttpProxySettingsProvider _proxySettingsProvider;
private readonly IUserAgentBuilder _userAgentBuilder;
private readonly Logger _logger; private readonly Logger _logger;
private const string _caBundleFileName = "curl-ca-bundle.crt"; private const string _caBundleFileName = "curl-ca-bundle.crt";
@ -37,10 +38,11 @@ static CurlHttpDispatcher()
_caBundleFilePath = _caBundleFileName; _caBundleFilePath = _caBundleFileName;
} }
} }
public CurlHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, Logger logger) public CurlHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, IUserAgentBuilder userAgentBuilder, Logger logger)
{ {
_proxySettingsProvider = proxySettingsProvider; _proxySettingsProvider = proxySettingsProvider;
_userAgentBuilder = userAgentBuilder;
_logger = logger; _logger = logger;
} }
@ -68,94 +70,93 @@ public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
{ {
using (Stream responseStream = new MemoryStream()) using (Stream responseStream = new MemoryStream())
using (Stream headerStream = 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; responseStream.Write(b, 0, s * n);
curlEasy.WriteFunction = (b, s, n, o) => 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); switch (result)
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) case CurlCode.SslCaCert:
{ case (CurlCode)77:
case CurlCode.SslCaCert: 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));
case (CurlCode)77: default:
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)); throw new WebException(string.Format("Curl Error {0} for Url {1}", 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 @@ private WebHeaderCollection ProcessHeaderStream(HttpRequest request, CookieConta
private string FixSetCookieHeader(string setCookie) private string FixSetCookieHeader(string setCookie)
{ {
// fix up the date if it was malformed // 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 shortFormat = "ddd, dd-MMM-yy HH:mm:ss";
string longFormat = "ddd, dd-MMM-yyyy HH:mm:ss"; string longFormat = "ddd, dd-MMM-yyyy HH:mm:ss";
@ -260,7 +261,6 @@ private string FixSetCookieHeader(string setCookie)
private byte[] ProcessResponseStream(HttpRequest request, Stream responseStream, WebHeaderCollection webHeaderCollection) private byte[] ProcessResponseStream(HttpRequest request, Stream responseStream, WebHeaderCollection webHeaderCollection)
{ {
byte[] bytes = null;
responseStream.Position = 0; responseStream.Position = 0;
if (responseStream.Length != 0) if (responseStream.Length != 0)
@ -270,27 +270,20 @@ private byte[] ProcessResponseStream(HttpRequest request, Stream responseStream,
{ {
if (encoding.IndexOf("gzip") != -1) if (encoding.IndexOf("gzip") != -1)
{ {
using (var zipStream = new GZipStream(responseStream, CompressionMode.Decompress)) responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
{
bytes = zipStream.ToBytes();
}
webHeaderCollection.Remove("Content-Encoding"); webHeaderCollection.Remove("Content-Encoding");
} }
else if (encoding.IndexOf("deflate") != -1) else if (encoding.IndexOf("deflate") != -1)
{ {
using (var deflateStream = new DeflateStream(responseStream, CompressionMode.Decompress)) responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);
{
bytes = deflateStream.ToBytes();
}
webHeaderCollection.Remove("Content-Encoding"); webHeaderCollection.Remove("Content-Encoding");
} }
} }
} }
if (bytes == null) bytes = responseStream.ToBytes(); return responseStream.ToBytes();
return bytes;
} }
} }

View file

@ -10,21 +10,23 @@ public class FallbackHttpDispatcher : IHttpDispatcher
{ {
private readonly ManagedHttpDispatcher _managedDispatcher; private readonly ManagedHttpDispatcher _managedDispatcher;
private readonly CurlHttpDispatcher _curlDispatcher; private readonly CurlHttpDispatcher _curlDispatcher;
private readonly IPlatformInfo _platformInfo;
private readonly Logger _logger; private readonly Logger _logger;
private readonly ICached<bool> _curlTLSFallbackCache; 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; _managedDispatcher = managedDispatcher;
_curlDispatcher = curlDispatcher; _curlDispatcher = curlDispatcher;
_platformInfo = platformInfo;
_curlTLSFallbackCache = cacheManager.GetCache<bool>(GetType(), "curlTLSFallback"); _curlTLSFallbackCache = cacheManager.GetCache<bool>(GetType(), "curlTLSFallback");
_logger = logger; _logger = logger;
} }
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) 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)) if (!_curlTLSFallbackCache.Find(request.Url.Host))
{ {

View file

@ -2,9 +2,13 @@
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Net; using System.Net;
using System.Reflection;
using NLog;
using NLog.Fluent;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy; using NzbDrone.Common.Http.Proxy;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Security; using NzbDrone.Common.Security;
namespace NzbDrone.Common.Http.Dispatchers namespace NzbDrone.Common.Http.Dispatchers
@ -13,55 +17,59 @@ public class ManagedHttpDispatcher : IHttpDispatcher
{ {
private readonly IHttpProxySettingsProvider _proxySettingsProvider; private readonly IHttpProxySettingsProvider _proxySettingsProvider;
private readonly ICreateManagedWebProxy _createManagedWebProxy; 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; _proxySettingsProvider = proxySettingsProvider;
_createManagedWebProxy = createManagedWebProxy; _createManagedWebProxy = createManagedWebProxy;
_userAgentBuilder = userAgentBuilder;
_platformInfo = platformInfo;
_logger = logger;
} }
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
{ {
HttpWebResponse httpWebResponse = null; var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
HttpWebRequest webRequest = null;
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 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) if (request.ContentData != null)
{ {
webRequest.ContentLength = request.ContentData.Length; webRequest.ContentLength = request.ContentData.Length;
@ -71,34 +79,57 @@ public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
} }
} }
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);
} }
else if (e.ToString().Contains("TLS Support not"))
httpWebResponse = (HttpWebResponse) e.Response; {
throw new TlsFailureException(webRequest, e);
if (httpWebResponse == null) }
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; 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(); data = responseStream.ToBytes();
if (OsInfo.IsMonoRuntime && httpWebResponse.ContentEncoding == "gzip") if (PlatformInfo.IsMono && httpWebResponse.ContentEncoding == "gzip")
{ {
using (var compressedStream = new MemoryStream(data)) using (var compressedStream = new MemoryStream(data))
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress)) using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
@ -111,17 +142,14 @@ public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
httpWebResponse.Headers.Remove("Content-Encoding"); 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, return new HttpResponse(request, new HttpHeader(httpWebResponse.Headers), data, httpWebResponse.StatusCode);
httpWebResponse.StatusCode);
}
finally
{
webRequest = null;
(httpWebResponse as IDisposable)?.Dispose();
httpWebResponse = null;
}
} }
protected virtual void AddProxy(HttpWebRequest webRequest, HttpRequest request) protected virtual void AddProxy(HttpWebRequest webRequest, HttpRequest request)
@ -181,5 +209,35 @@ protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader h
} }
} }
} }
// 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();
}
}
}
} }
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -31,12 +31,19 @@ public class HttpClient : IHttpClient
private readonly ICached<CookieContainer> _cookieContainerCache; private readonly ICached<CookieContainer> _cookieContainerCache;
private readonly List<IHttpRequestInterceptor> _requestInterceptors; private readonly List<IHttpRequestInterceptor> _requestInterceptors;
private readonly IHttpDispatcher _httpDispatcher; 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(); _requestInterceptors = requestInterceptors.ToList();
_rateLimitService = rateLimitService; _rateLimitService = rateLimitService;
_httpDispatcher = httpDispatcher; _httpDispatcher = httpDispatcher;
_userAgentBuilder = userAgentBuilder;
_logger = logger; _logger = logger;
ServicePointManager.DefaultConnectionLimit = 12; ServicePointManager.DefaultConnectionLimit = 12;
@ -71,7 +78,7 @@ public HttpResponse Execute(HttpRequest request)
while (response.HasHttpRedirect); 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"]); _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 @@ private CookieContainer InitializeRequestCookies(HttpRequest request)
if (!request.IgnorePersistentCookies) if (!request.IgnorePersistentCookies)
{ {
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url); var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
sourceContainer.Add(persistentCookies); sourceContainer.Add(persistentCookies);
} }
if (request.Cookies.Count != 0) if (request.Cookies.Count != 0)
{ {
foreach (var pair in request.Cookies) 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. cookie = new Cookie(pair.Key, "", "/")
// See https://gist.github.com/ta264/7822b1424f72e5b4c961 {
Expires = DateTime.Now.AddHours(1) 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); sourceContainer.Add((Uri)request.Url, cookie);
@ -178,7 +196,6 @@ private void PrepareRequestCookies(HttpRequest request, CookieContainer cookieCo
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer()); var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url); var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
var existingCookies = cookieContainer.GetCookies((Uri)request.Url); var existingCookies = cookieContainer.GetCookies((Uri)request.Url);
cookieContainer.Add(persistentCookies); cookieContainer.Add(persistentCookies);
cookieContainer.Add(existingCookies); cookieContainer.Add(existingCookies);
}*/ }*/
@ -226,13 +243,11 @@ public void DownloadFile(string url, string fileName)
_logger.Debug("Downloading [{0}] to [{1}]", url, fileName); _logger.Debug("Downloading [{0}] to [{1}]", url, fileName);
var stopWatch = Stopwatch.StartNew(); var stopWatch = Stopwatch.StartNew();
using (var webClient = new GZipWebClient()) var webClient = new GZipWebClient();
{ webClient.Headers.Add(HttpRequestHeader.UserAgent, _userAgentBuilder.GetUserAgent());
webClient.Headers.Add(HttpRequestHeader.UserAgent, UserAgentBuilder.UserAgent); webClient.DownloadFile(url, fileName);
webClient.DownloadFile(url, fileName); stopWatch.Stop();
stopWatch.Stop(); _logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
_logger.Debug("Downloading Completed. took {0:0}s", stopWatch.Elapsed.Seconds);
}
} }
catch (WebException e) catch (WebException e)
{ {
@ -255,6 +270,7 @@ public HttpResponse Get(HttpRequest request)
public HttpResponse<T> Get<T>(HttpRequest request) where T : new() public HttpResponse<T> Get<T>(HttpRequest request) where T : new()
{ {
var response = Get(request); var response = Get(request);
CheckResponseContentType(response);
return new HttpResponse<T>(response); return new HttpResponse<T>(response);
} }
@ -273,7 +289,16 @@ public HttpResponse Post(HttpRequest request)
public HttpResponse<T> Post<T>(HttpRequest request) where T : new() public HttpResponse<T> Post<T>(HttpRequest request) where T : new()
{ {
var response = Post(request); var response = Post(request);
CheckResponseContentType(response);
return new HttpResponse<T>(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);
}
}
} }
} }

View file

@ -7,13 +7,19 @@ public class HttpException : Exception
public HttpRequest Request { get; private set; } public HttpRequest Request { get; private set; }
public HttpResponse Response { get; private set; } public HttpResponse Response { get; private set; }
public HttpException(HttpRequest request, HttpResponse response) public HttpException(HttpRequest request, HttpResponse response, string message)
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url)) : base(message)
{ {
Request = request; Request = request;
Response = response; 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) public HttpException(HttpResponse response)
: this(response.Request, response) : this(response.Request, response)
{ {
@ -30,4 +36,4 @@ public override string ToString()
return base.ToString(); return base.ToString();
} }
} }
} }

View file

@ -17,7 +17,7 @@ public HttpRequest(string url, HttpAccept httpAccept = null)
IgnorePersistentCookies = false; IgnorePersistentCookies = false;
Cookies = new Dictionary<string, string>(); Cookies = new Dictionary<string, string>();
if (!RuntimeInfoBase.IsProduction) if (!RuntimeInfo.IsProduction)
{ {
AllowAutoRedirect = false; AllowAutoRedirect = false;
} }

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

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

View file

@ -2,19 +2,39 @@
namespace NzbDrone.Common.Http namespace NzbDrone.Common.Http
{ {
public static class UserAgentBuilder public interface IUserAgentBuilder
{ {
public static string UserAgent { get; private set; } string GetUserAgent(bool simplified = false);
public static string UserAgentSimplified { get; private set; } }
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})", if (simplified)
BuildInfo.Version, {
OsInfo.Os, OsInfo.Version.ToString(2)); return _userAgentSimplified;
}
UserAgentSimplified = string.Format("Radarr/{0}", return _userAgent;
BuildInfo.Version.ToString(2)); }
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)}";
} }
} }
} }

View file

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

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -35,7 +35,7 @@ private static void HandleAppDomainException(object sender, UnhandledExceptionEv
return; return;
} }
if (OsInfo.IsMonoRuntime) if (PlatformInfo.IsMono)
{ {
if (exception is TypeInitializationException && exception.InnerException is DllNotFoundException || if (exception is TypeInitializationException && exception.InnerException is DllNotFoundException ||
exception is DllNotFoundException) exception is DllNotFoundException)
@ -51,4 +51,4 @@ private static void HandleAppDomainException(object sender, UnhandledExceptionEv
Logger.Fatal(exception, "EPIC FAIL: " + exception.Message); Logger.Fatal(exception, "EPIC FAIL: " + exception.Message);
} }
} }
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using LogentriesNLog; using LogentriesNLog;
@ -48,7 +48,7 @@ public static void Register(IStartupContext startupContext, bool updateApp, bool
} }
else else
{ {
if (inConsole && (OsInfo.IsNotWindows || RuntimeInfoBase.IsUserInteractive)) if (inConsole && (OsInfo.IsNotWindows || RuntimeInfo.IsUserInteractive))
{ {
RegisterConsole(); RegisterConsole();
} }
@ -152,16 +152,6 @@ private static void RegisterUpdateFile(IAppFolderInfo appFolderInfo)
LogManager.Configuration.LoggingRules.Add(loggingRule); 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) public static Logger GetLogger(Type obj)
{ {
return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", "")); return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", ""));

View file

@ -94,6 +94,10 @@
<Compile Include="Disk\RelativeFileSystemModel.cs" /> <Compile Include="Disk\RelativeFileSystemModel.cs" />
<Compile Include="Disk\FileSystemModel.cs" /> <Compile Include="Disk\FileSystemModel.cs" />
<Compile Include="Disk\FileSystemResult.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="Extensions\DictionaryExtensions.cs" />
<Compile Include="Disk\GdiPlusInterop.cs" /> <Compile Include="Disk\GdiPlusInterop.cs" />
<Compile Include="Disk\OsPath.cs" /> <Compile Include="Disk\OsPath.cs" />
@ -125,13 +129,12 @@
<Compile Include="EnvironmentInfo\BuildInfo.cs" /> <Compile Include="EnvironmentInfo\BuildInfo.cs" />
<Compile Include="EnvironmentInfo\OsInfo.cs" /> <Compile Include="EnvironmentInfo\OsInfo.cs" />
<Compile Include="EnvironmentInfo\IRuntimeInfo.cs" /> <Compile Include="EnvironmentInfo\IRuntimeInfo.cs" />
<Compile Include="EnvironmentInfo\RuntimeInfoBase.cs" /> <Compile Include="EnvironmentInfo\RuntimeInfo.cs" />
<Compile Include="EnvironmentInfo\StartupContext.cs" /> <Compile Include="EnvironmentInfo\StartupContext.cs" />
<Compile Include="Exceptions\NotParentException.cs" /> <Compile Include="Exceptions\NotParentException.cs" />
<Compile Include="Exceptions\NzbDroneException.cs" /> <Compile Include="Exceptions\NzbDroneException.cs" />
<Compile Include="Exceptron\Configuration\ExceptronConfiguration.cs" /> <Compile Include="Exceptron\Configuration\ExceptronConfiguration.cs" />
<Compile Include="Exceptron\ExceptionData.cs" /> <Compile Include="Exceptron\ExceptionData.cs" />
<Compile Include="Exceptron\ExceptionExtentions.cs" />
<Compile Include="Exceptron\ExceptionSeverity.cs" /> <Compile Include="Exceptron\ExceptionSeverity.cs" />
<Compile Include="Exceptron\ExceptronApiException.cs" /> <Compile Include="Exceptron\ExceptronApiException.cs" />
<Compile Include="Exceptron\ExceptronClient.cs" /> <Compile Include="Exceptron\ExceptronClient.cs" />
@ -190,11 +193,12 @@
<Compile Include="Http\HttpRequestBuilder.cs" /> <Compile Include="Http\HttpRequestBuilder.cs" />
<Compile Include="Http\HttpRequestBuilderFactory.cs" /> <Compile Include="Http\HttpRequestBuilderFactory.cs" />
<Compile Include="Http\Proxy\ProxyType.cs" /> <Compile Include="Http\Proxy\ProxyType.cs" />
<Compile Include="Http\TlsFailureException.cs" />
<Compile Include="Http\TooManyRequestsException.cs" /> <Compile Include="Http\TooManyRequestsException.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" /> <Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Http\UnexpectedHtmlContentException.cs" />
<Compile Include="Http\UserAgentBuilder.cs" /> <Compile Include="Http\UserAgentBuilder.cs" />
<Compile Include="Instrumentation\CleanseLogMessage.cs" /> <Compile Include="Instrumentation\CleanseLogMessage.cs" />
<Compile Include="Instrumentation\ExceptronTarget.cs" />
<Compile Include="Instrumentation\Extensions\LoggerProgressExtensions.cs" /> <Compile Include="Instrumentation\Extensions\LoggerProgressExtensions.cs" />
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" /> <Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
<Compile Include="Instrumentation\LogEventExtensions.cs" /> <Compile Include="Instrumentation\LogEventExtensions.cs" />

View file

@ -108,7 +108,7 @@ public void OpenDefaultBrowser(string url)
public Process Start(string path, string args = null, StringDictionary environmentVariables = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null) 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); args = GetMonoArgs(path, args);
path = "mono"; path = "mono";
@ -192,7 +192,7 @@ public Process Start(string path, string args = null, StringDictionary environme
public Process SpawnNewProcess(string path, string args = null, StringDictionary environmentVariables = null) 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); args = GetMonoArgs(path, args);
path = "mono"; path = "mono";

View file

@ -1,9 +1,9 @@
using System.Collections.Specialized;
using System.Security.AccessControl;
using Moq; using Moq;
using System;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Cloud; using NzbDrone.Common.Cloud;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.TPL; using NzbDrone.Common.TPL;
@ -24,12 +24,16 @@ public abstract class CoreTest : TestBase
{ {
protected void UseRealHttp() 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<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.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<NLog.Logger>())); 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>(), 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()); Mocker.SetConstant<IRadarrCloudRequestBuilder>(new RadarrCloudRequestBuilder());
} }

View file

@ -1,4 +1,5 @@
using NUnit.Framework; using System;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.HealthCheck.Checks; using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
@ -8,17 +9,12 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[TestFixture] [TestFixture]
public class MonoVersionCheckFixture : CoreTest<MonoVersionCheck> public class MonoVersionCheckFixture : CoreTest<MonoVersionCheck>
{ {
[SetUp]
public void Setup()
{
MonoOnly();
}
private void GivenOutput(string version) private void GivenOutput(string version)
{ {
Mocker.GetMock<IRuntimeInfo>() MonoOnly();
.SetupGet(s => s.RuntimeVersion) Mocker.GetMock<IPlatformInfo>()
.Returns(string.Format("{0} (tarball Wed Sep 25 16:35:44 CDT 2013)", version)); .SetupGet(s => s.Version)
.Returns(new Version(version));
} }
[TestCase("3.10")] [TestCase("3.10")]

View file

@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Update; using NzbDrone.Core.Update;
@ -10,6 +11,12 @@ namespace NzbDrone.Core.Test.UpdateTests
{ {
public class UpdatePackageProviderFixture : CoreTest<UpdatePackageProvider> public class UpdatePackageProviderFixture : CoreTest<UpdatePackageProvider>
{ {
[SetUp]
public void Setup()
{
Mocker.GetMock<IPlatformInfo>().SetupGet(c => c.Version).Returns(new Version("9.9.9"));
}
[Test] [Test]
public void no_update_when_version_higher() public void no_update_when_version_higher()
{ {

View file

@ -17,6 +17,6 @@ public AnalyticsService(IConfigFileProvider configFileProvider)
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
} }
public bool IsEnabled => _configFileProvider.AnalyticsEnabled && RuntimeInfoBase.IsProduction; public bool IsEnabled => _configFileProvider.AnalyticsEnabled && RuntimeInfo.IsProduction;
} }
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
@ -373,7 +374,7 @@ public string ChownGroup
public int FirstDayOfWeek public int FirstDayOfWeek
{ {
get { return GetValueInt("FirstDayOfWeek", (int)OsInfo.FirstDayOfWeek); } get { return GetValueInt("FirstDayOfWeek", (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek); }
set { SetValue("FirstDayOfWeek", value); } set { SetValue("FirstDayOfWeek", value); }
} }

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -9,50 +8,43 @@ namespace NzbDrone.Core.HealthCheck.Checks
{ {
public class MonoVersionCheck : HealthCheckBase public class MonoVersionCheck : HealthCheckBase
{ {
private readonly IRuntimeInfo _runtimeInfo; private readonly IPlatformInfo _platformInfo;
private readonly Logger _logger; 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; _logger = logger;
} }
public override HealthCheck Check() public override HealthCheck Check()
{ {
if (OsInfo.IsWindows) if (!PlatformInfo.IsMono)
{ {
return new HealthCheck(GetType()); return new HealthCheck(GetType());
} }
var versionString = _runtimeInfo.RuntimeVersion; var monoVersion = _platformInfo.Version;
var versionMatch = VersionRegex.Match(versionString);
if (versionMatch.Success) if (monoVersion == new Version("3.4.0") && HasMonoBug18599())
{ {
var version = new Version(versionMatch.Groups["version"].Value); _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");
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());
}
} }
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; public override bool CheckOnConfigChange => false;
@ -70,7 +62,8 @@ private bool HasMonoBug18599()
return false; return false;
} }
var fieldInfo = numberFormatterType.GetField("userFormatProvider", BindingFlags.Static | BindingFlags.NonPublic); var fieldInfo = numberFormatterType.GetField("userFormatProvider",
BindingFlags.Static | BindingFlags.NonPublic);
if (fieldInfo == null) if (fieldInfo == null)
{ {

View file

@ -1,18 +1,14 @@
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Lifecycle namespace NzbDrone.Core.Lifecycle
{ {
public class ApplicationShutdownRequested : IEvent public class ApplicationShutdownRequested : IEvent
{ {
public bool Restarting { get; set; } public bool Restarting { get; }
public ApplicationShutdownRequested() public ApplicationShutdownRequested(bool restarting = false)
{
}
public ApplicationShutdownRequested(bool restarting)
{ {
Restarting = restarting; Restarting = restarting;
} }
} }
} }

View file

@ -189,7 +189,6 @@ private bool ChangeFileDateToUtcAirDate(string filePath, DateTime airDateUtc)
catch (Exception ex) catch (Exception ex)
{ {
ex.ExceptronIgnoreOnMono();
_logger.Warn(ex, "Unable to set date of file [" + filePath + "]"); _logger.Warn(ex, "Unable to set date of file [" + filePath + "]");
} }
} }

View file

@ -7,12 +7,10 @@ public static class RestClientFactory
{ {
public static RestClient BuildClient(string baseUrl) public static RestClient BuildClient(string baseUrl)
{ {
var restClient = new RestClient(baseUrl); var restClient = new RestClient(baseUrl)
{
restClient.UserAgent = string.Format("Radarr/{0} (RestSharp/{1}; {2}/{3})", UserAgent = $"Radarr/{BuildInfo.Version} ({OsInfo.Os})"
BuildInfo.Version, };
restClient.GetType().Assembly.GetName().Version,
OsInfo.Os, OsInfo.Version.ToString(2));
return restClient; return restClient;
} }

View file

@ -1,4 +1,3 @@
using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
@ -14,16 +13,11 @@ public class CheckUpdateService : ICheckUpdateService
private readonly IUpdatePackageProvider _updatePackageProvider; private readonly IUpdatePackageProvider _updatePackageProvider;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
private readonly Logger _logger;
public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, public CheckUpdateService(IUpdatePackageProvider updatePackageProvider,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider)
Logger logger)
{ {
_updatePackageProvider = updatePackageProvider; _updatePackageProvider = updatePackageProvider;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
_logger = logger;
} }
public UpdatePackage AvailableUpdate() public UpdatePackage AvailableUpdate()
@ -31,4 +25,4 @@ public UpdatePackage AvailableUpdate()
return _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version); return _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version);
} }
} }
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Common.Cloud; using NzbDrone.Common.Cloud;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -15,11 +15,13 @@ public interface IUpdatePackageProvider
public class UpdatePackageProvider : IUpdatePackageProvider public class UpdatePackageProvider : IUpdatePackageProvider
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IPlatformInfo _platformInfo;
private readonly IHttpRequestBuilderFactory _requestBuilder; private readonly IHttpRequestBuilderFactory _requestBuilder;
public UpdatePackageProvider(IHttpClient httpClient, IRadarrCloudRequestBuilder requestBuilder) public UpdatePackageProvider(IHttpClient httpClient, IRadarrCloudRequestBuilder requestBuilder, IPlatformInfo platformInfo)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_platformInfo = platformInfo;
_requestBuilder = requestBuilder.Services; _requestBuilder = requestBuilder.Services;
} }
@ -29,6 +31,7 @@ public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
.Resource("/update/{branch}") .Resource("/update/{branch}")
.AddQueryParam("version", currentVersion) .AddQueryParam("version", currentVersion)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant()) .AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("runtimeVer", _platformInfo.Version)
.SetSegment("branch", branch) .SetSegment("branch", branch)
.Build(); .Build();
@ -45,6 +48,7 @@ public List<UpdatePackage> GetRecentUpdates(string branch, Version currentVersio
.Resource("/update/{branch}/changes") .Resource("/update/{branch}/changes")
.AddQueryParam("version", currentVersion) .AddQueryParam("version", currentVersion)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant()) .AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("runtimeVer", _platformInfo.Version)
.SetSegment("branch", branch) .SetSegment("branch", branch)
.Build(); .Build();
@ -53,4 +57,4 @@ public List<UpdatePackage> GetRecentUpdates(string branch, Version currentVersio
return updates.Resource; return updates.Resource;
} }
} }
} }

View file

@ -20,6 +20,7 @@ public class UrlAclAdapter : IUrlAclAdapter
private readonly INetshProvider _netshProvider; private readonly INetshProvider _netshProvider;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
private readonly IOsInfo _osInfo;
private readonly Logger _logger; private readonly Logger _logger;
public List<string> Urls public List<string> Urls
@ -30,7 +31,7 @@ public List<string> Urls
} }
} }
private List<UrlAcl> InternalUrls { get; set; } private List<UrlAcl> InternalUrls { get; }
private List<UrlAcl> RegisteredUrls { get; set; } private List<UrlAcl> RegisteredUrls { get; set; }
private static readonly Regex UrlAclRegex = new Regex(@"(?<scheme>https?)\:\/\/(?<address>.+?)\:(?<port>\d+)/(?<urlbase>.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex UrlAclRegex = new Regex(@"(?<scheme>https?)\:\/\/(?<address>.+?)\:(?<port>\d+)/(?<urlbase>.+)?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@ -38,19 +39,26 @@ public List<string> Urls
public UrlAclAdapter(INetshProvider netshProvider, public UrlAclAdapter(INetshProvider netshProvider,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
IOsInfo osInfo,
Logger logger) Logger logger)
{ {
_netshProvider = netshProvider; _netshProvider = netshProvider;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
_runtimeInfo = runtimeInfo; _runtimeInfo = runtimeInfo;
_osInfo = osInfo;
_logger = logger; _logger = logger;
InternalUrls = new List<UrlAcl>(); InternalUrls = new List<UrlAcl>();
RegisteredUrls = GetRegisteredUrls(); RegisteredUrls = new List<UrlAcl>();
} }
public void ConfigureUrls() public void ConfigureUrls()
{ {
if (RegisteredUrls.Empty())
{
GetRegisteredUrls();
}
var localHostHttpUrls = BuildUrlAcls("http", "localhost", _configFileProvider.Port); var localHostHttpUrls = BuildUrlAcls("http", "localhost", _configFileProvider.Port);
var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, _configFileProvider.Port); var interfaceHttpUrls = BuildUrlAcls("http", _configFileProvider.BindAddress, _configFileProvider.Port);
@ -105,7 +113,8 @@ public void ConfigureUrls()
private void RefreshRegistration() 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) foreach (var urlAcl in InternalUrls)
{ {
@ -124,19 +133,24 @@ private bool IsRegistered(UrlAcl urlAcl)
c.UrlBase == urlAcl.UrlBase); c.UrlBase == urlAcl.UrlBase);
} }
private List<UrlAcl> GetRegisteredUrls() private void GetRegisteredUrls()
{ {
if (OsInfo.IsNotWindows) if (OsInfo.IsNotWindows)
{ {
return new List<UrlAcl>(); return;
}
if (RegisteredUrls.Any())
{
return;
} }
var arguments = string.Format("http show urlacl"); var arguments = string.Format("http show urlacl");
var output = _netshProvider.Run(arguments); 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); var match = UrlAclRegex.Match(line.Content);

View file

@ -58,7 +58,7 @@ public void Start()
//_cancelHandler = new CancelHandler(); //_cancelHandler = new CancelHandler();
} }
_runtimeInfo.IsRunning = true; _runtimeInfo.IsExiting = false;
DbFactory.RegisterDatabase(_container); DbFactory.RegisterDatabase(_container);
_hostController.StartServer(); _hostController.StartServer();
@ -87,7 +87,7 @@ private void Shutdown()
_logger.Info("Attempting to stop application."); _logger.Info("Attempting to stop application.");
_hostController.StopServer(); _hostController.StopServer();
_logger.Info("Application has finished stop routine."); _logger.Info("Application has finished stop routine.");
_runtimeInfo.IsRunning = false; _runtimeInfo.IsExiting = true;
} }
public void Handle(ApplicationShutdownRequested message) public void Handle(ApplicationShutdownRequested message)

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Nancy.Bootstrapper; using Nancy.Bootstrapper;
using NzbDrone.Api; using NzbDrone.Api;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition;
@ -15,26 +15,15 @@ public static IContainer BuildContainer(StartupContext args)
var assemblies = new List<string> var assemblies = new List<string>
{ {
"Radarr.Host", "Radarr.Host",
"NzbDrone.Common",
"NzbDrone.Core", "NzbDrone.Core",
"NzbDrone.Api", "NzbDrone.Api",
"NzbDrone.SignalR" "NzbDrone.SignalR"
}; };
if (OsInfo.IsWindows) return new MainAppContainerBuilder(args, assemblies).Container;
{
assemblies.Add("NzbDrone.Windows");
}
else
{
assemblies.Add("NzbDrone.Mono");
}
return new MainAppContainerBuilder(args, assemblies.ToArray()).Container;
} }
private MainAppContainerBuilder(StartupContext args, string[] assemblies) private MainAppContainerBuilder(StartupContext args, List<string> assemblies)
: base(args, assemblies) : base(args, assemblies)
{ {
AutoRegisterImplementations<NzbDronePersistentConnection>(); AutoRegisterImplementations<NzbDronePersistentConnection>();
@ -43,4 +32,4 @@ private MainAppContainerBuilder(StartupContext args, string[] assemblies)
Container.Register<IHttpDispatcher, FallbackHttpDispatcher>(); Container.Register<IHttpDispatcher, FallbackHttpDispatcher>();
} }
} }
} }

View file

@ -1,5 +1,6 @@
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
namespace Radarr.Host namespace Radarr.Host
{ {
@ -8,14 +9,16 @@ public class Router
private readonly INzbDroneServiceFactory _nzbDroneServiceFactory; private readonly INzbDroneServiceFactory _nzbDroneServiceFactory;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IConsoleService _consoleService; private readonly IConsoleService _consoleService;
private readonly IRuntimeInfo _runtimeInfo;
private readonly Logger _logger; private readonly Logger _logger;
public Router(INzbDroneServiceFactory nzbDroneServiceFactory, IServiceProvider serviceProvider, public Router(INzbDroneServiceFactory nzbDroneServiceFactory, IServiceProvider serviceProvider,
IConsoleService consoleService, Logger logger) IConsoleService consoleService, IRuntimeInfo runtimeInfo, Logger logger)
{ {
_nzbDroneServiceFactory = nzbDroneServiceFactory; _nzbDroneServiceFactory = nzbDroneServiceFactory;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_consoleService = consoleService; _consoleService = consoleService;
_runtimeInfo = runtimeInfo;
_logger = logger; _logger = logger;
} }
@ -34,7 +37,7 @@ public void Route(ApplicationModes applicationModes)
case ApplicationModes.Interactive: case ApplicationModes.Interactive:
{ {
_logger.Debug("Console selected"); _logger.Debug(_runtimeInfo.IsWindowsTray ? "Tray selected" : "Console selected");
_nzbDroneServiceFactory.Start(); _nzbDroneServiceFactory.Start();
break; break;
} }

View file

@ -1,4 +1,4 @@
using System.Threading; using System.Threading;
using NLog; using NLog;
using NLog.Common; using NLog.Common;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -28,7 +28,7 @@ public SpinService(IRuntimeInfo runtimeInfo, IProcessProvider processProvider, I
public void Spin() public void Spin()
{ {
while (_runtimeInfo.IsRunning) while (!_runtimeInfo.IsExiting)
{ {
Thread.Sleep(1000); Thread.Sleep(1000);
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,4 @@
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"

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

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ProductBuildVersion</key>
<string>16C68</string>
<key>ProductCopyright</key>
<string>1983-2016 Apple Inc.</string>
<key>ProductName</key>
<string>Mac OS X</string>
<key>ProductUserVisibleVersion</key>
<string>10.0.0</string>
<key>ProductVersion</key>
<string>10.0.0</string>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
majorversion="6"
minorversion="0"
productversion="6.0.2"
buildphase="hotfix"
buildnumber="8451"
smallfixnumber="7"
builddate="2016/12/20"
buildtime="05:11:44"

View file

@ -81,10 +81,26 @@
<ItemGroup> <ItemGroup>
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" /> <Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
<Compile Include="DiskProviderTests\FreeSpaceFixture.cs" /> <Compile Include="DiskProviderTests\FreeSpaceFixture.cs" />
<Compile Include="EnvironmentInfo\MonoPlatformInfoFixture.cs" />
<Compile Include="EnvironmentInfo\ReleaseFileVersionAdapterFixture.cs" />
<Compile Include="EnvironmentInfo\VersionAdapters\MacOsVersionAdapterFixture.cs" />
<Compile Include="EnvironmentInfo\VersionAdapters\ReleaseFileVersionAdapterFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
<None Include="Files\linux\lsb-release">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Files\linux\os-release">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Files\macOS\SystemVersion.plist">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Files\synology\VERSION">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -0,0 +1,46 @@
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Mono.EnvironmentInfo
{
public class MonoPlatformInfo : PlatformInfo
{
private static readonly Regex VersionRegex = new Regex(@"(?<=\W|^)(?<version>\d+\.\d+(\.\d+)?(\.\d+)?)(?=\W)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public override Version Version { get; }
public MonoPlatformInfo(Logger logger)
{
var runTimeVersion = new Version();
try
{
var type = Type.GetType("Mono.Runtime");
if (type != null)
{
var displayNameMethod = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayNameMethod != null)
{
var displayName = displayNameMethod.Invoke(null, null).ToString();
var versionMatch = VersionRegex.Match(displayName);
if (versionMatch.Success)
{
runTimeVersion = new Version(versionMatch.Groups["version"].Value);
}
}
}
}
catch (Exception ex)
{
logger.Error(ex, "Unable to get mono version");
}
Version = runTimeVersion;
}
}
}

View file

@ -1,45 +0,0 @@
using System;
using System.Reflection;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Mono.EnvironmentInfo
{
public class MonoRuntimeProvider : RuntimeInfoBase
{
private readonly Logger _logger;
public MonoRuntimeProvider(Common.IServiceProvider serviceProvider, Logger logger)
:base(serviceProvider, logger)
{
_logger = logger;
}
public override string RuntimeVersion
{
get
{
try
{
var type = Type.GetType("Mono.Runtime");
if (type != null)
{
var displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayName != null)
{
return displayName.Invoke(null, null).ToString();
}
}
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to get mono version: " + ex.Message);
}
return string.Empty;
}
}
}
}

View file

@ -0,0 +1,52 @@
using System.IO;
using System.Linq;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Mono.EnvironmentInfo.VersionAdapters
{
public class IssueFileVersionAdapter : IOsVersionAdapter
{
private readonly IDiskProvider _diskProvider;
public IssueFileVersionAdapter(IDiskProvider diskProvider)
{
_diskProvider = diskProvider;
}
public OsVersionModel Read()
{
if (!_diskProvider.FolderExists("/etc/"))
{
return null;
}
var issueFile = _diskProvider.GetFiles("/etc/", SearchOption.TopDirectoryOnly).SingleOrDefault(c => c.EndsWith("/issue"));
if (issueFile == null)
{
return null;
}
var fileContent = _diskProvider.ReadAllText(issueFile);
// Ubuntu 14.04.5 LTS \n \l
// Ubuntu 16.04.1 LTS \n \l
// Fedora/Centos
// Kernel \r on an \m (\l)
// Arch Linux \r (\l)
// Debian GNU/Linux 8 \n \l
if (fileContent.Contains("Arch Linux"))
{
return new OsVersionModel("Arch", "1.0", "Arch Linux");
}
return null;
}
public bool Enabled => OsInfo.IsLinux;
}
}

View file

@ -0,0 +1,69 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Mono.EnvironmentInfo.VersionAdapters
{
public class MacOsVersionAdapter : IOsVersionAdapter
{
private static readonly Regex DarwinVersionRegex = new Regex("<string>(?<version>10\\.\\d{1,2}\\.?\\d{0,2}?)<\\/string>",
RegexOptions.Compiled |
RegexOptions.IgnoreCase
);
private const string PLIST_DIR = "/System/Library/CoreServices/";
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public MacOsVersionAdapter(IDiskProvider diskProvider, Logger logger)
{
_diskProvider = diskProvider;
_logger = logger;
}
public OsVersionModel Read()
{
var version = "10.0";
if (!_diskProvider.FolderExists(PLIST_DIR))
{
_logger.Debug("Directory {0} doesn't exist", PLIST_DIR);
return null;
}
var allFiles = _diskProvider.GetFiles(PLIST_DIR, SearchOption.TopDirectoryOnly);
var versionFile = allFiles.SingleOrDefault(c =>
c.EndsWith("SystemVersion.plist") ||
c.EndsWith("ServerVersion.plist")
);
if (string.IsNullOrWhiteSpace(versionFile))
{
_logger.Debug("Couldn't find version plist file in {0}", PLIST_DIR);
return null;
}
var text = _diskProvider.ReadAllText(versionFile);
var match = DarwinVersionRegex.Match(text);
if (match.Success)
{
version = match.Groups["version"].Value;
}
var name = versionFile.Contains("Server") ? "macOS Server" : "macOS";
return new OsVersionModel(name, version);
}
public bool Enabled => OsInfo.IsOsx;
}
}

View file

@ -0,0 +1,79 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Mono.EnvironmentInfo.VersionAdapters
{
public class ReleaseFileVersionAdapter : IOsVersionAdapter
{
private readonly IDiskProvider _diskProvider;
public ReleaseFileVersionAdapter(IDiskProvider diskProvider)
{
_diskProvider = diskProvider;
}
public OsVersionModel Read()
{
if (!_diskProvider.FolderExists("/etc/"))
{
return null;
}
var releaseFiles = _diskProvider.GetFiles("/etc/", SearchOption.TopDirectoryOnly).Where(c => c.EndsWith("release")).ToList();
var name = "Linux";
var fullName = "";
var version = "";
bool success = false;
foreach (var releaseFile in releaseFiles)
{
var fileContent = _diskProvider.ReadAllText(releaseFile);
var lines = Regex.Split(fileContent, "\r\n|\r|\n"); ;
foreach (var line in lines)
{
var parts = line.Split('=');
if (parts.Length >= 2)
{
var key = parts[0];
var value = parts[1];
if (!string.IsNullOrWhiteSpace(value))
{
switch (key)
{
case "ID":
success = true;
name = value;
break;
case "PRETTY_NAME":
success = true;
fullName = value;
break;
case "VERSION_ID":
success = true;
version = value;
break;
}
}
}
}
}
if (!success)
{
return null;
}
return new OsVersionModel(name, version, fullName);
}
public bool Enabled => OsInfo.IsLinux;
}
}

View file

@ -0,0 +1,78 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Mono.EnvironmentInfo.VersionAdapters
{
public class SynologyVersionAdapter : IOsVersionAdapter
{
private readonly IDiskProvider _diskProvider;
private const string NAME = "DSM";
private const string FULL_NAME = "Synology DSM";
public SynologyVersionAdapter(IDiskProvider diskProvider)
{
_diskProvider = diskProvider;
}
public OsVersionModel Read()
{
if (!_diskProvider.FolderExists("/etc.defaults/"))
{
return null;
}
var versionFile = _diskProvider.GetFiles("/etc.defaults/", SearchOption.TopDirectoryOnly).SingleOrDefault(c => c.EndsWith("VERSION"));
if (versionFile == null)
{
return null;
}
var version = "";
var major = "";
var minor = "0";
var fileContent = _diskProvider.ReadAllText(versionFile);
var lines = Regex.Split(fileContent, "\r\n|\r|\n"); ;
foreach (var line in lines)
{
var parts = line.Split('=');
if (parts.Length >= 2)
{
var key = parts[0];
var value = parts[1].Trim('"');
if (!string.IsNullOrWhiteSpace(value))
{
switch (key)
{
case "productversion":
version = value;
break;
case "majorversion":
major = value;
break;
case "minorversion":
minor = value;
break;
}
}
}
}
if (string.IsNullOrWhiteSpace(version) && !string.IsNullOrWhiteSpace(major))
{
version = $"{major}.{minor}";
}
return new OsVersionModel(NAME, version, $"{FULL_NAME} {version}");
}
public bool Enabled => OsInfo.IsLinux;
}
}

View file

@ -74,9 +74,13 @@
<Compile Include="Disk\DiskProvider.cs" /> <Compile Include="Disk\DiskProvider.cs" />
<Compile Include="Disk\FindDriveType.cs" /> <Compile Include="Disk\FindDriveType.cs" />
<Compile Include="Disk\LinuxPermissionsException.cs" /> <Compile Include="Disk\LinuxPermissionsException.cs" />
<Compile Include="EnvironmentInfo\MonoRuntimeProvider.cs" /> <Compile Include="EnvironmentInfo\MonoPlatformInfo.cs" />
<Compile Include="Disk\ProcMount.cs" /> <Compile Include="Disk\ProcMount.cs" />
<Compile Include="Disk\ProcMountProvider.cs" /> <Compile Include="Disk\ProcMountProvider.cs" />
<Compile Include="EnvironmentInfo\VersionAdapters\IssueFileVersionAdapter.cs" />
<Compile Include="EnvironmentInfo\VersionAdapters\MacOsVersionAdapter.cs" />
<Compile Include="EnvironmentInfo\VersionAdapters\ReleaseFileVersionAdapter.cs" />
<Compile Include="EnvironmentInfo\VersionAdapters\SynologyVersionAdapter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Disk\SymbolicLinkResolver.cs" /> <Compile Include="Disk\SymbolicLinkResolver.cs" />
</ItemGroup> </ItemGroup>

View file

@ -1,5 +1,3 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -151,7 +149,7 @@ private static void AddTheAutoMockingContainerExtensionToTheContainer(IUnityCont
private Mock<T> TheRegisteredMockForThisType<T>(Type type) where T : class private Mock<T> TheRegisteredMockForThisType<T>(Type type) where T : class
{ {
return (Mock<T>)_registeredMocks.Where(x => x.Key == type).First().Value; return (Mock<T>)_registeredMocks.First(x => x.Key == type).Value;
} }
private void CreateANewMockAndRegisterIt<T>(Type type, MockBehavior behavior) where T : class private void CreateANewMockAndRegisterIt<T>(Type type, MockBehavior behavior) where T : class
@ -190,4 +188,4 @@ private void RegisterPlatformLibrary(IUnityContainer container)
#endregion #endregion
} }
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using FluentAssertions; using FluentAssertions;
@ -133,7 +133,7 @@ protected void WindowsOnly()
protected void MonoOnly() protected void MonoOnly()
{ {
if (OsInfo.IsWindows) if (!PlatformInfo.IsMono)
{ {
throw new IgnoreException("mono specific test"); throw new IgnoreException("mono specific test");
} }

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http.Dispatchers; using NzbDrone.Common.Http.Dispatchers;
@ -7,7 +7,7 @@ namespace NzbDrone.Update
{ {
public class UpdateContainerBuilder : ContainerBuilderBase public class UpdateContainerBuilder : ContainerBuilderBase
{ {
private UpdateContainerBuilder(IStartupContext startupContext, string[] assemblies) private UpdateContainerBuilder(IStartupContext startupContext, List<string> assemblies)
: base(startupContext, assemblies) : base(startupContext, assemblies)
{ {
Container.Register<IHttpDispatcher, FallbackHttpDispatcher>(); Container.Register<IHttpDispatcher, FallbackHttpDispatcher>();
@ -17,22 +17,10 @@ public static IContainer Build(IStartupContext startupContext)
{ {
var assemblies = new List<string> var assemblies = new List<string>
{ {
"Radarr.Update", "Radarr.Update"
"NzbDrone.Common"
}; };
if (OsInfo.IsWindows) return new UpdateContainerBuilder(startupContext, assemblies).Container;
{
assemblies.Add("NzbDrone.Windows");
}
else
{
assemblies.Add("NzbDrone.Mono");
}
return new UpdateContainerBuilder(startupContext, assemblies.ToArray()).Container;
} }
} }
} }

View file

@ -24,7 +24,7 @@ public AppType GetAppType()
{ {
if (OsInfo.IsNotWindows) if (OsInfo.IsNotWindows)
{ {
//Tehcnically its the console, but its been renamed for mono (Linux/OS X) //Tehcnically it is the console, but it has been renamed for mono (Linux/OS X)
return AppType.Normal; return AppType.Normal;
} }
@ -42,4 +42,4 @@ public AppType GetAppType()
return AppType.Normal; return AppType.Normal;
} }
} }
} }

View file

@ -0,0 +1,19 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Test.Common;
using NzbDrone.Windows.EnvironmentInfo;
namespace NzbDrone.Windows.Test.EnvironmentInfo
{
[TestFixture]
[Platform("Win")]
public class DotNetPlatformInfoFixture : TestBase<DotNetPlatformInfo>
{
[Test]
public void should_get_framework_version()
{
Subject.Version.Major.Should().Be(4);
Subject.Version.Minor.Should().BeOneOf(0, 5, 6, 7, 8);
}
}
}

View file

@ -0,0 +1,23 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Test.Common;
using NzbDrone.Windows.EnvironmentInfo;
namespace NzbDrone.Windows.Test.EnvironmentInfo
{
[TestFixture]
[Platform("Win")]
public class WindowsVersionInfoFixture : TestBase<WindowsVersionInfo>
{
[Test]
public void should_get_windows_version()
{
var info = Subject.Read();
info.Version.Should().NotBeNullOrWhiteSpace();
info.Name.Should().Contain("Windows");
info.FullName.Should().Contain("Windows");
info.FullName.Should().Contain("NT");
info.FullName.Should().Contain(info.Version);
}
}
}

View file

@ -73,6 +73,8 @@
<ItemGroup> <ItemGroup>
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" /> <Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
<Compile Include="DiskProviderTests\FreeSpaceFixture.cs" /> <Compile Include="DiskProviderTests\FreeSpaceFixture.cs" />
<Compile Include="EnvironmentInfo\DotNetPlatformInfoFixture.cs" />
<Compile Include="EnvironmentInfo\WindowsVersionInfoFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -0,0 +1,86 @@
using System;
using Microsoft.Win32;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Windows.EnvironmentInfo
{
public class DotNetPlatformInfo : PlatformInfo
{
private readonly Logger _logger;
public DotNetPlatformInfo(Logger logger)
{
_logger = logger;
var version = GetFrameworkVersion();
Environment.SetEnvironmentVariable("RUNTIME_VERSION", version.ToString());
Version = version;
}
public override Version Version { get; }
private Version GetFrameworkVersion()
{
try
{
const string subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";
using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(subkey))
{
if (ndpKey == null)
{
return new Version(4, 0);
}
var releaseKey = (int)ndpKey.GetValue("Release");
if (releaseKey >= 528040)
{
return new Version(4, 8, 0);
}
if (releaseKey >= 461808)
{
return new Version(4, 7, 2);
}
if (releaseKey >= 461308)
{
return new Version(4, 7, 1);
}
if (releaseKey >= 460798)
{
return new Version(4, 7);
}
if (releaseKey >= 394802)
{
return new Version(4, 6, 2);
}
if (releaseKey >= 394254)
{
return new Version(4, 6, 1);
}
if (releaseKey >= 393295)
{
return new Version(4, 6);
}
if (releaseKey >= 379893)
{
return new Version(4, 5, 2);
}
if (releaseKey >= 378675)
{
return new Version(4, 5, 1);
}
if (releaseKey >= 378389)
{
return new Version(4, 5);
}
}
}
catch (Exception e)
{
_logger.Error(e, "Couldnt get .NET framework version");
}
return new Version(4, 0);
}
}
}

View file

@ -1,16 +0,0 @@
using System;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Windows.EnvironmentInfo
{
public class DotNetRuntimeProvider : RuntimeInfoBase
{
public DotNetRuntimeProvider(Common.IServiceProvider serviceProvider, Logger logger)
: base(serviceProvider, logger)
{
}
public override string RuntimeVersion => Environment.Version.ToString();
}
}

View file

@ -0,0 +1,49 @@
using System;
using Microsoft.Win32;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Windows.EnvironmentInfo
{
public class WindowsVersionInfo : IOsVersionAdapter
{
private readonly Logger _logger;
public bool Enabled => OsInfo.IsWindows;
public WindowsVersionInfo(Logger logger)
{
_logger = logger;
}
public OsVersionModel Read()
{
var windowsServer = IsServer();
var osName = windowsServer ? "Windows Server" : "Windows";
return new OsVersionModel(osName, Environment.OSVersion.Version.ToString(), Environment.OSVersion.VersionString);
}
private bool IsServer()
{
try
{
const string subkey = @"Software\Microsoft\Windows NT\CurrentVersion";
var openSubKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(subkey);
if (openSubKey != null)
{
var productName = openSubKey.GetValue("ProductName").ToString();
if (productName.ToLower().Contains("server"))
{
return true;
}
}
}
catch (Exception e)
{
_logger.Error(e, "Couldn't detect if running Windows Server");
}
return false;
}
}
}

View file

@ -67,7 +67,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Disk\DiskProvider.cs" /> <Compile Include="Disk\DiskProvider.cs" />
<Compile Include="EnvironmentInfo\DotNetRuntimeProvider.cs" /> <Compile Include="EnvironmentInfo\DotNetPlatformInfo.cs" />
<Compile Include="EnvironmentInfo\WindowsVersionInfo.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>