Fixed: Automation/Integration/Unit Tests

This commit is contained in:
Qstick 2019-09-02 22:22:25 -04:00
parent 944f420270
commit 7f221c7834
123 changed files with 1384 additions and 1191 deletions

21
.gitattributes vendored
View File

@ -1,22 +1,9 @@
# Auto detect text files and perform LF normalization
*text eol=lf
# Explicitly set bash scripts to have unix endings
*.sh text eol=lf
# Custom for Visual Studio
*.cs diff=csharp
#*.sln merge=union
#*.csproj merge=union
#*.vbproj merge=union
#*.fsproj merge=union
#*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.sln merge=union

4
.gitmodules vendored
View File

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

View File

@ -238,17 +238,16 @@ stages:
buildType: 'current'
artifactName: WindowsTests
targetPath: $(testsFolder)
- bash: |
wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-9_all.deb
sudo dpkg -i repo-mediaarea_1.0-9_all.deb
sudo apt-get update
sudo apt-get install -y libmediainfo-dev libmediainfo0v5 mediainfo
displayName: Install mediainfo
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- bash: |
sudo apt install dos2unix
dos2unix ${TESTSFOLDER}/test.sh
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- bash: |
brew install dos2unix
dos2unix ${TESTSFOLDER}/test.sh
condition: and(succeeded(), eq(variables['osName'], 'Mac'))
- task: Bash@3
displayName: Run Tests
env:
@ -326,14 +325,6 @@ stages:
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
displayName: Move Package Contents
- bash: |
sudo apt install dos2unix
dos2unix ${TESTSFOLDER}/test.sh
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- bash: |
brew install dos2unix
dos2unix ${TESTSFOLDER}/test.sh
condition: and(succeeded(), eq(variables['osName'], 'Mac'))
- task: Bash@3
displayName: Run Integration Tests
inputs:

View File

@ -139,7 +139,7 @@ Build()
RunGulp()
{
ProgressStart 'yarn install'
yarn install
yarn install --frozen-lockfile
ProgressEnd 'yarn install'
LintUI
@ -254,8 +254,6 @@ PackageTests()
echo "Adding Radarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Radarr.Core.dll.config $testPackageFolder
echo "Copying CurlSharp libraries"
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $testPackageFolder

View File

@ -16,7 +16,9 @@ class About extends Component {
const {
version,
isMonoRuntime,
isDocker,
runtimeVersion,
migrationVersion,
appData,
startupPath,
mode,
@ -41,6 +43,19 @@ class About extends Component {
/>
}
{
isDocker &&
<DescriptionListItem
title="Docker"
data={'True'}
/>
}
<DescriptionListItem
title="DB Migration"
data={migrationVersion}
/>
<DescriptionListItem
title="AppData directory"
data={appData}
@ -77,6 +92,8 @@ About.propTypes = {
version: PropTypes.string.isRequired,
isMonoRuntime: PropTypes.bool.isRequired,
runtimeVersion: PropTypes.string.isRequired,
isDocker: PropTypes.bool.isRequired,
migrationVersion: PropTypes.number.isRequired,
appData: PropTypes.string.isRequired,
startupPath: PropTypes.string.isRequired,
mode: PropTypes.string.isRequired,

View File

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

View File

@ -1,6 +1,6 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import React, { Component } from 'react';
import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@ -26,7 +26,7 @@ class Updates extends Component {
generalSettingsError,
items,
isInstallingUpdate,
updateMechanism,
isDocker,
shortDateFormat,
onInstallLatestPress
} = this.props;
@ -37,12 +37,6 @@ class Updates extends Component {
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
const externalUpdaterMessages = {
external: 'Unable to update Radarr directly, Radarr is configured to use an external update mechanism',
apt: 'Unable to update Radarr directly, use apt to install the update',
docker: 'Unable to update Radarr directly, update the docker container to receive the update'
};
return (
<PageContent title="Updates">
<PageContentBodyConnector>
@ -58,29 +52,24 @@ class Updates extends Component {
{
hasUpdateToInstall &&
<div className={styles.messageContainer}>
<div className={styles.updateAvailable}>
{
updateMechanism === 'builtIn' || updateMechanism === 'script' ?
<SpinnerButton
className={styles.updateAvailable}
kind={kinds.PRIMARY}
isSpinning={isInstallingUpdate}
onPress={onInstallLatestPress}
>
Install Latest
</SpinnerButton> :
!isDocker &&
<SpinnerButton
className={styles.updateAvailable}
kind={kinds.PRIMARY}
isSpinning={isInstallingUpdate}
onPress={onInstallLatestPress}
>
Install Latest
</SpinnerButton>
}
<Fragment>
<Icon
name={icons.WARNING}
kind={kinds.WARNING}
size={30}
/>
<div className={styles.message}>
{externalUpdaterMessages[updateMechanism] || externalUpdaterMessages.external}
</div>
</Fragment>
{
isDocker &&
<div className={styles.upToDateMessage}>
An update is available. Please update your Docker image and re-create the container.
</div>
}
{
@ -209,6 +198,7 @@ Updates.propTypes = {
generalSettingsError: PropTypes.object,
items: PropTypes.array.isRequired,
isInstallingUpdate: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired,
updateMechanism: PropTypes.string,
shortDateFormat: PropTypes.string.isRequired,
onInstallLatestPress: PropTypes.func.isRequired

View File

@ -7,6 +7,7 @@ import { fetchUpdates } from 'Store/Actions/systemActions';
import { executeCommand } from 'Store/Actions/commandActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import * as commandNames from 'Commands/commandNames';
import Updates from './Updates';
@ -16,12 +17,14 @@ function createMapStateToProps() {
(state) => state.system.updates,
(state) => state.settings.general,
createUISettingsSelector(),
createSystemStatusSelector(),
createCommandExecutingSelector(commandNames.APPLICATION_UPDATE),
(
currentVersion,
updates,
generalSettings,
uiSettings,
systemStatus,
isInstallingUpdate
) => {
const {
@ -40,6 +43,7 @@ function createMapStateToProps() {
generalSettingsError: generalSettings.error,
items,
isInstallingUpdate,
isDocker: systemStatus.isDocker,
updateMechanism: generalSettings.item.updateMechanism,
shortDateFormat: uiSettings.shortDateFormat
};

View File

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

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

View File

@ -1,9 +1,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NzbDrone.Common.Disk;
using Nancy;
using Nancy.Responses;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using Radarr.Http;
@ -54,6 +55,8 @@ namespace NzbDrone.Api.Logs
private Response GetLogFileResponse(string filename)
{
LogManager.Flush();
var filePath = GetLogFilePath(filename);
if (!_diskProvider.FileExists(filePath))

View File

@ -34,7 +34,9 @@ namespace NzbDrone.Automation.Test
[OneTimeSetUp]
public void SmokeTestSetup()
{
driver = new FirefoxDriver();
var options = new FirefoxOptions();
options.AddArguments("--headless");
driver = new FirefoxDriver(options);
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
_runner.KillAll();
@ -45,7 +47,7 @@ namespace NzbDrone.Automation.Test
var page = new PageBase(driver);
page.WaitForNoSpinner();
driver.ExecuteScript("window.NzbDrone.NameViews = true;");
driver.ExecuteScript("window.Radarr.NameViews = true;");
GetPageErrors().Should().BeEmpty();
}

View File

@ -17,11 +17,11 @@ namespace NzbDrone.Automation.Test
}
[Test]
public void series_page()
public void movie_page()
{
page.SeriesNavIcon.Click();
page.MovieNavIcon.Click();
page.WaitForNoSpinner();
page.FindByClass("iv-series-index-seriesindexlayout").Should().NotBeNull();
page.Find(By.CssSelector("div[class*='MovieIndex']")).Should().NotBeNull();
}
[Test]
@ -30,7 +30,7 @@ namespace NzbDrone.Automation.Test
page.CalendarNavIcon.Click();
page.WaitForNoSpinner();
page.FindByClass("iv-calendar-calendarlayout").Should().NotBeNull();
page.Find(By.CssSelector("div[class*='CalendarPage']")).Should().NotBeNull();
}
[Test]
@ -39,16 +39,9 @@ namespace NzbDrone.Automation.Test
page.ActivityNavIcon.Click();
page.WaitForNoSpinner();
page.FindByClass("iv-activity-activitylayout").Should().NotBeNull();
}
[Test]
public void wanted_page()
{
page.WantedNavIcon.Click();
page.WaitForNoSpinner();
page.FindByClass("iv-wanted-missing-missinglayout").Should().NotBeNull();
page.Find(By.LinkText("Queue")).Should().NotBeNull();
page.Find(By.LinkText("History")).Should().NotBeNull();
page.Find(By.LinkText("Blacklist")).Should().NotBeNull();
}
[Test]
@ -57,20 +50,20 @@ namespace NzbDrone.Automation.Test
page.SystemNavIcon.Click();
page.WaitForNoSpinner();
page.FindByClass("iv-system-systemlayout").Should().NotBeNull();
page.Find(By.CssSelector("div[class*='Health']")).Should().NotBeNull();
}
[Test]
public void add_series_page()
public void add_movie_page()
{
page.SeriesNavIcon.Click();
page.MovieNavIcon.Click();
page.WaitForNoSpinner();
page.Find(By.LinkText("Add Series")).Click();
page.Find(By.LinkText("Add New")).Click();
page.WaitForNoSpinner();
page.FindByClass("iv-addseries-addserieslayout").Should().NotBeNull();
page.Find(By.CssSelector("input[class*='AddNewMovie/searchInput']")).Should().NotBeNull();
}
}
}

View File

@ -47,16 +47,14 @@ namespace NzbDrone.Automation.Test.PageModel
});
}
public IWebElement SeriesNavIcon => FindByClass("x-series-nav");
public IWebElement MovieNavIcon => Find(By.LinkText("Movies"));
public IWebElement CalendarNavIcon => FindByClass("x-calendar-nav");
public IWebElement CalendarNavIcon => Find(By.LinkText("Calendar"));
public IWebElement ActivityNavIcon => FindByClass("x-activity-nav");
public IWebElement ActivityNavIcon => Find(By.LinkText("Activity"));
public IWebElement WantedNavIcon => FindByClass("x-wanted-nav");
public IWebElement SettingNavIcon => Find(By.LinkText("Settings"));
public IWebElement SettingNavIcon => FindByClass("x-settings-nav");
public IWebElement SystemNavIcon => FindByClass("x-system-nav");
public IWebElement SystemNavIcon => Find(By.PartialLinkText("System"));
}
}

View File

@ -4,6 +4,7 @@
<Platforms>x86</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Selenium.Firefox.WebDriver" Version="0.24.0" />
<PackageReference Include="Selenium.Support" Version="3.141.0" />
</ItemGroup>
<ItemGroup>

View File

@ -248,12 +248,14 @@ namespace NzbDrone.Common.Test.DiskTests
}
[Test]
[Ignore("No longer behaving this way in a Windows 10 Feature Update")]
public void should_not_be_able_to_rename_open_hardlinks_with_fileshare_none()
{
Assert.Throws<IOException>(() => DoHardLinkRename(FileShare.None));
}
[Test]
[Ignore("No longer behaving this way in a Windows 10 Feature Update")]
public void should_not_be_able_to_rename_open_hardlinks_with_fileshare_write()
{
Assert.Throws<IOException>(() => DoHardLinkRename(FileShare.Read));

View File

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

View File

@ -9,22 +9,21 @@ using NzbDrone.Common.Model;
using NzbDrone.Common.Processes;
using NzbDrone.Test.Common;
using NzbDrone.Test.Dummy;
using System.Reflection;
namespace NzbDrone.Common.Test
{
[TestFixture]
public class ProcessProviderTests : TestBase<ProcessProvider>
public class ProcessProviderFixture : TestBase<ProcessProvider>
{
[SetUp]
public void Setup()
{
Process.GetProcessesByName(DummyApp.DUMMY_PROCCESS_NAME).ToList().ForEach(c =>
{
c.Kill();
c.WaitForExit();
});
{
c.Kill();
c.WaitForExit();
});
Process.GetProcessesByName(DummyApp.DUMMY_PROCCESS_NAME).Should().BeEmpty();
}
@ -42,7 +41,7 @@ namespace NzbDrone.Common.Test
{
TestLogger.Warn(ex, "{0} when killing process", ex.Message);
}
});
}
@ -65,18 +64,9 @@ namespace NzbDrone.Common.Test
}
[Test]
[Ignore("Shit appveyor")]
public void Should_be_able_to_start_process()
{
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
var rPath = Path.GetDirectoryName(path);
var root = Directory.GetParent(rPath).Parent.Parent.Parent;
var DummyAppDir = Path.Combine(root.FullName, "NzbDrone.Test.Dummy", "bin", "Release");
var process = Subject.Start(Path.Combine(DummyAppDir, DummyApp.DUMMY_PROCCESS_NAME + ".exe"));
{
var process = StartDummyProcess();
Subject.Exists(DummyApp.DUMMY_PROCCESS_NAME).Should()
.BeTrue("excepted one dummy process to be already running");
@ -88,6 +78,7 @@ namespace NzbDrone.Common.Test
}
[Test]
[Explicit]
public void Should_be_able_to_start_powershell()
{
WindowsOnly();
@ -137,7 +128,6 @@ namespace NzbDrone.Common.Test
[Test]
[Ignore("Shit appveyor")]
public void kill_all_should_kill_all_process_with_name()
{
var dummy1 = StartDummyProcess();
@ -151,7 +141,8 @@ namespace NzbDrone.Common.Test
private Process StartDummyProcess()
{
return Subject.Start(DummyApp.DUMMY_PROCCESS_NAME + ".exe");
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, DummyApp.DUMMY_PROCCESS_NAME + ".exe");
return Subject.Start(path);
}
[Test]
@ -161,4 +152,4 @@ namespace NzbDrone.Common.Test
ExceptionVerification.MarkInconclusive(typeof(Win32Exception));
}
}
}
}

View File

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

View File

@ -1,4 +1,5 @@
using System;
using System.Security.Principal;
using System.ServiceProcess;
using FluentAssertions;
using NUnit.Framework;
@ -61,6 +62,10 @@ namespace NzbDrone.Common.Test
[Test]
public void Service_should_be_installed_and_then_uninstalled()
{
if (!IsAnAdministrator())
{
Assert.Inconclusive("Can't run test without Administrator rights");
}
Subject.ServiceExist(TEMP_SERVICE_NAME).Should().BeFalse("Service already installed");
Subject.Install(TEMP_SERVICE_NAME);
@ -100,9 +105,13 @@ namespace NzbDrone.Common.Test
}
[Test]
[Ignore("Shit appveyor")]
public void should_throw_if_starting_a_running_serivce()
public void should_throw_if_starting_a_running_service()
{
if (!IsAnAdministrator())
{
Assert.Inconclusive("Can't run test without Administrator rights");
}
Subject.GetService(ALWAYS_INSTALLED_SERVICE).Status
.Should().NotBe(ServiceControllerStatus.Running);
@ -128,5 +137,10 @@ namespace NzbDrone.Common.Test
ExceptionVerification.ExpectedWarns(1);
}
private static bool IsAnAdministrator()
{
var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
}
}
}

View File

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

View File

@ -27,6 +27,8 @@ namespace NzbDrone.Common.EnvironmentInfo
Release = $"{Version}-{Branch}";
}
public static string AppName { get; } = "Radarr";
public static Version Version { get; }
public static String Branch { get; }
public static string Release { get; }

View File

@ -15,6 +15,9 @@ namespace NzbDrone.Common.EnvironmentInfo
public static bool IsOsx => Os == Os.Osx;
public static bool IsWindows => Os == Os.Windows;
// this needs to not be static so we can mock it
public bool IsDocker { get; }
public string Version { get; }
public string Name { get; }
public string FullName { get; }
@ -83,8 +86,10 @@ namespace NzbDrone.Common.EnvironmentInfo
FullName = Name;
}
Environment.SetEnvironmentVariable("OS_NAME", Name);
Environment.SetEnvironmentVariable("OS_VERSION", Version);
if (IsLinux && File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"))
{
IsDocker = true;
}
}
}
@ -93,6 +98,7 @@ namespace NzbDrone.Common.EnvironmentInfo
string Version { get; }
string Name { get; }
string FullName { get; }
bool IsDocker { get; }
}
public enum Os

View File

@ -35,7 +35,16 @@ namespace NzbDrone.Common.EnvironmentInfo
static RuntimeInfo()
{
IsProduction = InternalIsProduction();
var officialBuild = InternalIsOfficialBuild();
// An build running inside of the testing environment. (Analytics disabled)
IsTesting = InternalIsTesting();
// An official build running outside of the testing environment. (Analytics configurable)
IsProduction = !IsTesting && officialBuild;
// An unofficial build running outside of the testing environment. (Analytics enabled)
IsDevelopment = !IsTesting && !officialBuild && !InternalIsDebug();
}
public DateTime StartTime
@ -104,23 +113,21 @@ namespace NzbDrone.Common.EnvironmentInfo
public bool RestartPending { get; set; }
public string ExecutingApplication { get; }
public static bool IsTesting { get; }
public static bool IsProduction { get; }
public static bool IsDevelopment { get; }
private static bool InternalIsProduction()
private static bool InternalIsTesting()
{
if (BuildInfo.IsDebug || Debugger.IsAttached) return false;
//Official builds will never have such a high revision
if (BuildInfo.Version.Revision > 10000) return false;
try
{
var lowerProcessName = Process.GetCurrentProcess().ProcessName.ToLower();
if (lowerProcessName.Contains("vshost")) return false;
if (lowerProcessName.Contains("nunit")) return false;
if (lowerProcessName.Contains("jetbrain")) return false;
if (lowerProcessName.Contains("resharper")) return false;
if (lowerProcessName.Contains("vshost")) return true;
if (lowerProcessName.Contains("nunit")) return true;
if (lowerProcessName.Contains("jetbrain")) return true;
if (lowerProcessName.Contains("resharper")) return true;
}
catch
{
@ -130,7 +137,7 @@ namespace NzbDrone.Common.EnvironmentInfo
try
{
var currentAssemblyLocation = typeof(RuntimeInfo).Assembly.Location;
if (currentAssemblyLocation.ToLower().Contains("_output")) return false;
if (currentAssemblyLocation.ToLower().Contains("_output")) return true;
}
catch
{
@ -138,13 +145,28 @@ namespace NzbDrone.Common.EnvironmentInfo
}
var lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
if (lowerCurrentDir.Contains("teamcity")) return false;
if (lowerCurrentDir.Contains("buildagent")) return false;
if (lowerCurrentDir.Contains("_output")) return false;
if (lowerCurrentDir.Contains("vsts")) return true;
if (lowerCurrentDir.Contains("buildagent")) return true;
if (lowerCurrentDir.Contains("_output")) return true;
return false;
}
private static bool InternalIsDebug()
{
if (BuildInfo.IsDebug || Debugger.IsAttached) return true;
return false;
}
private static bool InternalIsOfficialBuild()
{
//Official builds will never have such a high revision
if (BuildInfo.Version.Major >= 10 || BuildInfo.Version.Revision > 10000) return false;
return true;
}
public bool IsWindowsTray { get; private set; }
}
}
}

View File

@ -1,342 +0,0 @@
using System;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using CurlSharp;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Proxy;
namespace NzbDrone.Common.Http.Dispatchers
{
public class CurlHttpDispatcher : IHttpDispatcher
{
private static readonly Regex ExpiryDate = new Regex(@"(expires=)([^;]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private readonly IHttpProxySettingsProvider _proxySettingsProvider;
private readonly IUserAgentBuilder _userAgentBuilder;
private readonly Logger _logger;
private const string _caBundleFileName = "curl-ca-bundle.crt";
private static readonly string _caBundleFilePath;
static CurlHttpDispatcher()
{
if (Assembly.GetExecutingAssembly().Location.IsNotNullOrWhiteSpace())
{
_caBundleFilePath = Path.Combine(Assembly.GetExecutingAssembly().Location, "..", _caBundleFileName);
}
else
{
_caBundleFilePath = _caBundleFileName;
}
}
public CurlHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, IUserAgentBuilder userAgentBuilder, Logger logger)
{
_proxySettingsProvider = proxySettingsProvider;
_userAgentBuilder = userAgentBuilder;
_logger = logger;
}
public bool CheckAvailability()
{
try
{
return CurlGlobalHandle.Instance.Initialize();
}
catch (Exception ex)
{
_logger.Trace(ex, "Initializing curl failed");
return false;
}
}
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
{
if (!CheckAvailability())
{
throw new ApplicationException("Curl failed to initialize.");
}
lock (CurlGlobalHandle.Instance)
{
Stream responseStream = new MemoryStream();
Stream headerStream = new MemoryStream();
using (var curlEasy = new CurlEasy())
{
curlEasy.AutoReferer = false;
curlEasy.WriteFunction = (b, s, n, o) =>
{
responseStream.Write(b, 0, s * n);
return s * n;
};
curlEasy.HeaderFunction = (b, s, n, o) =>
{
headerStream.Write(b, 0, s * n);
return s * n;
};
AddProxy(curlEasy, request);
curlEasy.Url = request.Url.FullUri;
switch (request.Method)
{
case HttpMethod.GET:
curlEasy.HttpGet = true;
break;
case HttpMethod.POST:
curlEasy.Post = true;
break;
case HttpMethod.PUT:
curlEasy.Put = true;
break;
default:
throw new NotSupportedException($"HttpCurl method {request.Method} not supported");
}
curlEasy.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
curlEasy.FollowLocation = false;
if (request.RequestTimeout != TimeSpan.Zero)
{
curlEasy.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalSeconds);
}
if (OsInfo.IsWindows)
{
curlEasy.CaInfo = _caBundleFilePath;
}
if (cookies != null)
{
curlEasy.Cookie = cookies.GetCookieHeader((Uri)request.Url);
}
if (request.ContentData != null)
{
curlEasy.PostFieldSize = request.ContentData.Length;
curlEasy.SetOpt(CurlOption.CopyPostFields, new string(Array.ConvertAll(request.ContentData, v => (char)v)));
}
// Yes, we have to keep a ref to the object to prevent corrupting the unmanaged state
using (var httpRequestHeaders = SerializeHeaders(request))
{
curlEasy.HttpHeader = httpRequestHeaders;
var result = curlEasy.Perform();
if (result != CurlCode.Ok)
{
switch (result)
{
case CurlCode.SslCaCert:
case (CurlCode)77:
throw new WebException(string.Format("Curl Error {0} for Url {1}, issues with your operating system SSL Root Certificate Bundle (ca-bundle).", result, curlEasy.Url));
default:
throw new WebException(string.Format("Curl Error {0} for Url {1}", result, curlEasy.Url));
}
}
}
var webHeaderCollection = ProcessHeaderStream(request, cookies, headerStream);
var responseData = ProcessResponseStream(request, responseStream, webHeaderCollection);
var httpHeader = new HttpHeader(webHeaderCollection);
return new HttpResponse(request, httpHeader, responseData, (HttpStatusCode)curlEasy.ResponseCode);
}
}
}
private void AddProxy(CurlEasy curlEasy, HttpRequest request)
{
var proxySettings = _proxySettingsProvider.GetProxySettings(request);
if (proxySettings != null)
{
switch (proxySettings.Type)
{
case ProxyType.Http:
curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Http);
curlEasy.SetOpt(CurlOption.ProxyAuth, CurlHttpAuth.Basic);
curlEasy.SetOpt(CurlOption.ProxyUserPwd, proxySettings.Username + ":" + proxySettings.Password.ToString());
break;
case ProxyType.Socks4:
curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks4);
curlEasy.SetOpt(CurlOption.ProxyUsername, proxySettings.Username);
curlEasy.SetOpt(CurlOption.ProxyPassword, proxySettings.Password);
break;
case ProxyType.Socks5:
curlEasy.SetOpt(CurlOption.ProxyType, CurlProxyType.Socks5);
curlEasy.SetOpt(CurlOption.ProxyUsername, proxySettings.Username);
curlEasy.SetOpt(CurlOption.ProxyPassword, proxySettings.Password);
break;
}
curlEasy.SetOpt(CurlOption.Proxy, proxySettings.Host + ":" + proxySettings.Port.ToString());
}
}
private CurlSlist SerializeHeaders(HttpRequest request)
{
if (!request.Headers.ContainsKey("Accept-Encoding"))
{
request.Headers.Add("Accept-Encoding", "gzip");
}
if (request.Headers.ContentType == null)
{
request.Headers.ContentType = string.Empty;
}
var curlHeaders = new CurlSlist();
foreach (var header in request.Headers)
{
curlHeaders.Append(header.Key + ": " + header.Value.ToString());
}
return curlHeaders;
}
private WebHeaderCollection ProcessHeaderStream(HttpRequest request, CookieContainer cookies, Stream headerStream)
{
headerStream.Position = 0;
var headerData = headerStream.ToBytes();
var headerString = Encoding.ASCII.GetString(headerData);
var webHeaderCollection = new WebHeaderCollection();
// following a redirect we could have two sets of headers, so only process the last one
foreach (var header in headerString.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
{
if (!header.Contains(":")) break;
webHeaderCollection.Add(header);
}
var setCookie = webHeaderCollection.Get("Set-Cookie");
if (setCookie != null && setCookie.Length > 0 && cookies != null)
{
try
{
cookies.SetCookies((Uri)request.Url, FixSetCookieHeader(setCookie));
}
catch (CookieException ex)
{
_logger.Debug("Rejected cookie {0}: {1}", ex.InnerException.Message, setCookie);
}
}
return webHeaderCollection;
}
private string FixSetCookieHeader(string setCookie)
{
// fix up the date if it was malformed
var setCookieClean = ExpiryDate.Replace(setCookie, delegate(Match match)
{
string shortFormat = "ddd, dd-MMM-yy HH:mm:ss";
string longFormat = "ddd, dd-MMM-yyyy HH:mm:ss";
DateTime dt;
if (DateTime.TryParseExact(match.Groups[2].Value, longFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt) ||
DateTime.TryParseExact(match.Groups[2].Value, shortFormat, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt) ||
DateTime.TryParse(match.Groups[2].Value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt))
return match.Groups[1].Value + dt.ToUniversalTime().ToString(longFormat, CultureInfo.InvariantCulture) + " GMT";
else
return match.Value;
});
return setCookieClean;
}
private byte[] ProcessResponseStream(HttpRequest request, Stream responseStream, WebHeaderCollection webHeaderCollection)
{
byte[] bytes = null;
responseStream.Position = 0;
if (responseStream.Length != 0)
{
var encoding = webHeaderCollection["Content-Encoding"];
if (encoding != null)
{
if (encoding.IndexOf("gzip") != -1)
{
using (var zipStream = new GZipStream(responseStream, CompressionMode.Decompress))
{
bytes = zipStream.ToBytes();
}
webHeaderCollection.Remove("Content-Encoding");
}
else if (encoding.IndexOf("deflate") != -1)
{
using (var deflateStream = new DeflateStream(responseStream, CompressionMode.Decompress))
{
bytes = deflateStream.ToBytes();
}
webHeaderCollection.Remove("Content-Encoding");
}
}
}
if (bytes == null) bytes = responseStream.ToBytes();
return bytes;
}
}
internal sealed class CurlGlobalHandle : SafeHandle
{
public static readonly CurlGlobalHandle Instance = new CurlGlobalHandle();
private bool _initialized;
private bool _available;
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
private CurlGlobalHandle()
: base(IntPtr.Zero, true)
{
}
public bool Initialize()
{
lock (CurlGlobalHandle.Instance)
{
if (_initialized)
return _available;
_initialized = true;
_available = Curl.GlobalInit(CurlInitFlag.All) == CurlCode.Ok;
return _available;
}
}
protected override bool ReleaseHandle()
{
if (_initialized && _available)
{
Curl.GlobalCleanup();
_available = false;
}
return true;
}
public override bool IsInvalid => !_initialized || !_available;
}
}

View File

@ -1,56 +0,0 @@
using System;
using System.Net;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Http.Dispatchers
{
public class FallbackHttpDispatcher : IHttpDispatcher
{
private readonly ManagedHttpDispatcher _managedDispatcher;
private readonly CurlHttpDispatcher _curlDispatcher;
private readonly IPlatformInfo _platformInfo;
private readonly Logger _logger;
private readonly ICached<bool> _curlTLSFallbackCache;
public FallbackHttpDispatcher(ManagedHttpDispatcher managedDispatcher, CurlHttpDispatcher curlDispatcher, ICacheManager cacheManager, IPlatformInfo platformInfo, Logger logger)
{
_managedDispatcher = managedDispatcher;
_curlDispatcher = curlDispatcher;
_platformInfo = platformInfo;
_curlTLSFallbackCache = cacheManager.GetCache<bool>(GetType(), "curlTLSFallback");
_logger = logger;
}
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
{
if (PlatformInfo.IsMono && request.Url.Scheme == "https")
{
if (!_curlTLSFallbackCache.Find(request.Url.Host))
{
try
{
return _managedDispatcher.GetResponse(request, cookies);
}
catch (TlsFailureException)
{
_logger.Debug("https request failed in tls error for {0}, trying curl fallback.", request.Url.Host);
_curlTLSFallbackCache.Set(request.Url.Host, true);
}
}
if (_curlDispatcher.CheckAvailability())
{
return _curlDispatcher.GetResponse(request, cookies);
}
_logger.Trace("Curl not available, using default WebClient.");
}
return _managedDispatcher.GetResponse(request, cookies);
}
}
}

View File

@ -24,7 +24,7 @@ namespace NzbDrone.Common.Http
public HttpProvider(Logger logger)
{
_logger = logger;
_userAgent = string.Format("Radarr {0}", BuildInfo.Version);
_userAgent = $"{BuildInfo.AppName}/{BuildInfo.Version.ToString(2)}";
ServicePointManager.Expect100Continue = false;
}

View File

@ -9,10 +9,10 @@ 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)
: base("Failed to establish secure https connection to '" + request.RequestUri + "'.", innerException, WebExceptionStatus.SecureChannelFailure, innerException.Response)
{
}
}
}
}

View File

@ -33,8 +33,8 @@ namespace NzbDrone.Common.Http
var osVersion = osInfo.Version?.ToLower();
_userAgent = $"Radarr/{BuildInfo.Version} ({osName} {osVersion})";
_userAgentSimplified = $"Radarr/{BuildInfo.Version.ToString(2)}";
_userAgent = $"{BuildInfo.AppName}/{BuildInfo.Version} ({osName} {osVersion})";
_userAgentSimplified = $"{BuildInfo.AppName}/{BuildInfo.Version.ToString(2)}";
}
}
}

View File

@ -0,0 +1,28 @@
using System.Linq;
using NLog;
using NLog.Fluent;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Instrumentation.Sentry;
namespace NzbDrone.Common.Instrumentation
{
public class InitializeLogger
{
private readonly IOsInfo _osInfo;
public InitializeLogger(IOsInfo osInfo)
{
_osInfo = osInfo;
}
public void Initialize()
{
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null)
{
sentryTarget.UpdateScope(_osInfo);
}
}
}
}

View File

@ -58,18 +58,6 @@ namespace NzbDrone.Common.Instrumentation
LogManager.ReconfigExistingLoggers();
}
public static void UnRegisterRemoteLoggers()
{
var sentryRules = LogManager.Configuration.LoggingRules.Where(r => r.Targets.Any(t => t.Name == "sentryTarget"));
foreach (var rules in sentryRules)
{
rules.Targets.Clear();
}
LogManager.ReconfigExistingLoggers();
}
private static void RegisterSentry(bool updateClient)
{
string dsn;

View File

@ -8,6 +8,7 @@ using NLog;
using NLog.Common;
using NLog.Targets;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using Sentry;
using Sentry.Protocol;
@ -61,6 +62,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{LogLevel.Warn, BreadcrumbLevel.Warning},
};
private readonly DateTime _startTime = DateTime.UtcNow;
private readonly IDisposable _sdk;
private bool _disposed;
@ -68,10 +70,8 @@ namespace NzbDrone.Common.Instrumentation.Sentry
private bool _unauthorized;
public bool FilterEvents { get; set; }
public string UpdateBranch { get; set; }
public Version DatabaseVersion { get; set; }
public int DatabaseMigration { get; set; }
public bool SentryEnabled { get; set; }
public SentryTarget(string dsn)
{
_sdk = SentrySdk.Init(o =>
@ -79,34 +79,81 @@ namespace NzbDrone.Common.Instrumentation.Sentry
o.Dsn = new Dsn(dsn);
o.AttachStacktrace = true;
o.MaxBreadcrumbs = 200;
o.SendDefaultPii = true;
o.SendDefaultPii = false;
o.Debug = false;
o.DiagnosticsLevel = SentryLevel.Debug;
o.Release = BuildInfo.Release;
if (PlatformInfo.IsMono)
{
// Mono 6.0 broke GzipStream.WriteAsync
// TODO: Check specific version
o.RequestBodyCompressionLevel = System.IO.Compression.CompressionLevel.NoCompression;
}
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
o.Environment = BuildInfo.Branch;
});
SentrySdk.ConfigureScope(scope =>
{
scope.User = new User {
Username = HashUtil.AnonymousToken()
};
scope.SetTag("osfamily", OsInfo.Os.ToString());
scope.SetTag("runtime", PlatformInfo.PlatformName);
scope.SetTag("culture", Thread.CurrentThread.CurrentCulture.Name);
scope.SetTag("branch", BuildInfo.Branch);
scope.SetTag("version", BuildInfo.Version.ToString());
scope.SetTag("production", RuntimeInfo.IsProduction.ToString());
});
InitializeScope();
_debounce = new SentryDebounce();
// initialize to true and reconfigure later
// Otherwise it will default to false and any errors occuring
// before config file gets read will not be filtered
FilterEvents = true;
SentryEnabled = true;
}
public void InitializeScope()
{
SentrySdk.ConfigureScope(scope =>
{
scope.User = new User
{
Id = HashUtil.AnonymousToken()
};
scope.Contexts.App.Name = BuildInfo.AppName;
scope.Contexts.App.Version = BuildInfo.Version.ToString();
scope.Contexts.App.StartTime = _startTime;
scope.Contexts.App.Hash = HashUtil.AnonymousToken();
scope.Contexts.App.Build = BuildInfo.Release; // Git commit cache?
scope.SetTag("culture", Thread.CurrentThread.CurrentCulture.Name);
scope.SetTag("branch", BuildInfo.Branch);
});
}
public void UpdateScope(IOsInfo osInfo)
{
SentrySdk.ConfigureScope(scope =>
{
scope.SetTag("is_docker", $"{osInfo.IsDocker}");
if (osInfo.Name != null && PlatformInfo.IsMono)
{
// Sentry auto-detection of non-Windows platforms isn't that accurate on certain devices.
scope.Contexts.OperatingSystem.Name = osInfo.Name.FirstCharToUpper();
scope.Contexts.OperatingSystem.RawDescription = osInfo.FullName;
scope.Contexts.OperatingSystem.Version = osInfo.Version.ToString();
}
});
}
public void UpdateScope(Version databaseVersion, int migration, string updateBranch, IPlatformInfo platformInfo)
{
SentrySdk.ConfigureScope(scope =>
{
scope.Environment = updateBranch;
scope.SetTag("runtime_version", $"{PlatformInfo.PlatformName} {platformInfo.Version}");
if (databaseVersion != default(Version))
{
scope.SetTag("sqlite_version", $"{databaseVersion}");
scope.SetTag("database_migration", $"{migration}");
}
});
}
private void OnError(Exception ex)
@ -191,7 +238,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
protected override void Write(LogEventInfo logEvent)
{
if (_unauthorized)
if (_unauthorized || !SentryEnabled)
{
return;
}
@ -227,27 +274,12 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{
Level = LoggingLevelMap[logEvent.Level],
Logger = logEvent.LoggerName,
Message = logEvent.FormattedMessage,
Environment = UpdateBranch
Message = logEvent.FormattedMessage
};
sentryEvent.SetExtras(extras);
sentryEvent.SetFingerprint(fingerPrint);
// this can't be in the constructor as at that point OsInfo won't have
// populated these values yet
var osName = Environment.GetEnvironmentVariable("OS_NAME");
var osVersion = Environment.GetEnvironmentVariable("OS_VERSION");
var isDocker = Environment.GetEnvironmentVariable("OS_IS_DOCKER");
var runTimeVersion = Environment.GetEnvironmentVariable("RUNTIME_VERSION");
sentryEvent.SetTag("os_name", osName);
sentryEvent.SetTag("os_version", $"{osName} {osVersion}");
sentryEvent.SetTag("is_docker", isDocker);
sentryEvent.SetTag("runtime_version", $"{PlatformInfo.PlatformName} {runTimeVersion}");
sentryEvent.SetTag("sqlite_version", $"{DatabaseVersion}");
sentryEvent.SetTag("database_migration", $"{DatabaseMigration}");
SentrySdk.CaptureEvent(sentryEvent);
}
catch (Exception e)

View File

@ -328,6 +328,8 @@ namespace NzbDrone.Common.Processes
var monoProcesses = Process.GetProcessesByName("mono")
.Union(Process.GetProcessesByName("mono-sgen"))
.Union(Process.GetProcessesByName("mono-sgen32"))
.Union(Process.GetProcessesByName("mono-sgen64"))
.Where(process =>
process.Modules.Cast<ProcessModule>()
.Any(module =>

View File

@ -11,9 +11,6 @@
<PackageReference Include="SharpZipLib" Version="1.2.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ExternalModules\CurlSharp\CurlSharp\CurlSharp.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Configuration.Install" />
<Reference Include="System.ServiceProcess" />

View File

@ -5,6 +5,7 @@ using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Instrumentation;
using Radarr.Host;
using Radarr.Host.AccessControl;
namespace NzbDrone.Console
{
@ -50,6 +51,13 @@ namespace NzbDrone.Console
Logger.Fatal(ex.Message + ". This can happen if another instance of Radarr is already running another application is using the same port (default: 7878) or the user has insufficient permissions");
Exit(ExitCodes.RecoverableFailure);
}
catch (RemoteAccessException ex)
{
System.Console.WriteLine("");
System.Console.WriteLine("");
Logger.Fatal(ex, "EPIC FAIL!");
Exit(ExitCodes.Normal);
}
catch (Exception ex)
{
System.Console.WriteLine("");

View File

@ -30,10 +30,9 @@ namespace NzbDrone.Core.Test.Framework
Mocker.SetConstant<IHttpProxySettingsProvider>(new HttpProxySettingsProvider(Mocker.Resolve<ConfigService>()));
Mocker.SetConstant<ICreateManagedWebProxy>(new ManagedWebProxyFactory(Mocker.Resolve<CacheManager>()));
Mocker.SetConstant<ManagedHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<IPlatformInfo>(), TestLogger));
Mocker.SetConstant<CurlHttpDispatcher>(new CurlHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<UserAgentBuilder>(), TestLogger));
Mocker.SetConstant<IHttpDispatcher>(new ManagedHttpDispatcher(Mocker.Resolve<IHttpProxySettingsProvider>(), Mocker.Resolve<ICreateManagedWebProxy>(), Mocker.Resolve<UserAgentBuilder>(), Mocker.Resolve<IPlatformInfo>(), TestLogger));
Mocker.SetConstant<IHttpProvider>(new HttpProvider(TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<FallbackHttpDispatcher>(), Mocker.Resolve<UserAgentBuilder>(), TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.Resolve<UserAgentBuilder>(), TestLogger));
Mocker.SetConstant<IRadarrCloudRequestBuilder>(new RadarrCloudRequestBuilder());
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using FluentMigrator.Runner;
@ -99,7 +100,6 @@ namespace NzbDrone.Core.Test.Framework
{
WithTempAsAppPath();
Mocker.SetConstant<IAnnouncer>(Mocker.Resolve<MigrationLogger>());
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
@ -116,22 +116,15 @@ namespace NzbDrone.Core.Test.Framework
[TearDown]
public void TearDown()
{
if (TestFolderInfo != null && Directory.Exists(TestFolderInfo.AppDataFolder))
// Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly)
GC.Collect();
GC.WaitForPendingFinalizers();
SQLiteConnection.ClearAllPools();
if (TestFolderInfo != null)
{
var files = Directory.GetFiles(TestFolderInfo.AppDataFolder);
foreach (var file in files)
{
try
{
File.Delete(file);
}
catch (Exception)
{
}
}
DeleteTempFolder(TestFolderInfo.AppDataFolder);
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,121 +1,224 @@
//using System;
//using System.Collections.Generic;
//using Moq;
//using NUnit.Framework;
//using NzbDrone.Common;
//using NzbDrone.Core.Messaging.Commands;
//using NzbDrone.Core.Messaging.Commands.Tracking;
//using NzbDrone.Core.Messaging.Events;
//using NzbDrone.Test.Common;
//
//namespace NzbDrone.Core.Test.Messaging.Commands
//{
// [TestFixture]
// public class CommandExecutorFixture : TestBase<CommandExecutor>
// {
// private Mock<IExecute<CommandA>> _executorA;
// private Mock<IExecute<CommandB>> _executorB;
//
// [SetUp]
// public void Setup()
// {
// _executorA = new Mock<IExecute<CommandA>>();
// _executorB = new Mock<IExecute<CommandB>>();
//
// Mocker.GetMock<IServiceFactory>()
// .Setup(c => c.Build(typeof(IExecute<CommandA>)))
// .Returns(_executorA.Object);
//
// Mocker.GetMock<IServiceFactory>()
// .Setup(c => c.Build(typeof(IExecute<CommandB>)))
// .Returns(_executorB.Object);
//
//
// Mocker.GetMock<ITrackCommands>()
// .Setup(c => c.FindExisting(It.IsAny<Command>()))
// .Returns<Command>(null);
// }
//
// [Test]
// public void should_publish_command_to_executor()
// {
// var commandA = new CommandA();
//
// Subject.Push(commandA);
//
// _executorA.Verify(c => c.Execute(commandA), Times.Once());
// }
//
// [Test]
// public void should_publish_command_by_with_optional_arg_using_name()
// {
// Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(Command)))
// .Returns(new List<Type> { typeof(CommandA), typeof(CommandB) });
//
// Subject.Push(typeof(CommandA).FullName);
// _executorA.Verify(c => c.Execute(It.IsAny<CommandA>()), Times.Once());
// }
//
//
// [Test]
// public void should_not_publish_to_incompatible_executor()
// {
// var commandA = new CommandA();
//
// Subject.Push(commandA);
//
// _executorA.Verify(c => c.Execute(commandA), Times.Once());
// _executorB.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
// }
//
// [Test]
// public void broken_executor_should_throw_the_exception()
// {
// var commandA = new CommandA();
//
// _executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
// .Throws(new NotImplementedException());
//
// Assert.Throws<NotImplementedException>(() => Subject.Push(commandA));
// }
//
//
// [Test]
// public void broken_executor_should_publish_executed_event()
// {
// var commandA = new CommandA();
//
// _executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
// .Throws(new NotImplementedException());
//
// Assert.Throws<NotImplementedException>(() => Subject.Push(commandA));
//
// VerifyEventPublished<CommandExecutedEvent>();
// }
//
// [Test]
// public void should_publish_executed_event_on_success()
// {
// var commandA = new CommandA();
// Subject.Push(commandA);
//
// VerifyEventPublished<CommandExecutedEvent>();
// }
// }
//
// public class CommandA : Command
// {
// public CommandA(int id = 0)
// {
// }
// }
//
// public class CommandB : Command
// {
//
// public CommandB()
// {
// }
// }
//
//}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Messaging.Commands
{
[TestFixture]
public class CommandExecutorFixture : TestBase<CommandExecutor>
{
private CommandQueue _commandQueue;
private Mock<IExecute<CommandA>> _executorA;
private Mock<IExecute<CommandB>> _executorB;
[SetUp]
public void Setup()
{
_executorA = new Mock<IExecute<CommandA>>();
_executorB = new Mock<IExecute<CommandB>>();
Mocker.GetMock<IServiceFactory>()
.Setup(c => c.Build(typeof(IExecute<CommandA>)))
.Returns(_executorA.Object);
Mocker.GetMock<IServiceFactory>()
.Setup(c => c.Build(typeof(IExecute<CommandB>)))
.Returns(_executorB.Object);
}
[TearDown]
public void TearDown()
{
Subject.Handle(new ApplicationShutdownRequested());
// Give the threads a bit of time to shut down.
Thread.Sleep(10);
}
private void GivenCommandQueue()
{
_commandQueue = new CommandQueue();
Mocker.GetMock<IManageCommandQueue>()
.Setup(s => s.Queue(It.IsAny<CancellationToken>()))
.Returns(_commandQueue.GetConsumingEnumerable);
}
private void QueueAndWaitForExecution(CommandModel commandModel, bool waitPublish = false)
{
var waitEventComplete = new ManualResetEventSlim();
var waitEventPublish = new ManualResetEventSlim();
Mocker.GetMock<IManageCommandQueue>()
.Setup(s => s.Complete(It.Is<CommandModel>(c => c == commandModel), It.IsAny<string>()))
.Callback(() => waitEventComplete.Set());
Mocker.GetMock<IManageCommandQueue>()
.Setup(s => s.Fail(It.Is<CommandModel>(c => c == commandModel), It.IsAny<string>(), It.IsAny<Exception>()))
.Callback(() => waitEventComplete.Set());
Mocker.GetMock<IEventAggregator>()
.Setup(s => s.PublishEvent<CommandExecutedEvent>(It.IsAny<CommandExecutedEvent>()))
.Callback(() => waitEventPublish.Set());
_commandQueue.Add(commandModel);
if (!waitEventComplete.Wait(2000))
{
Assert.Fail("Command did not Complete/Fail within 2 sec");
}
if (waitPublish && !waitEventPublish.Wait(500))
{
Assert.Fail("Command did not Publish within 500 msec");
}
}
[Test]
public void should_start_executor_threads()
{
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock<IManageCommandQueue>()
.Verify(v => v.Queue(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
}
[Test]
public void should_execute_on_executor()
{
GivenCommandQueue();
var commandA = new CommandA();
var commandModel = new CommandModel
{
Body = commandA
};
Subject.Handle(new ApplicationStartedEvent());
QueueAndWaitForExecution(commandModel);
_executorA.Verify(c => c.Execute(commandA), Times.Once());
}
[Test]
public void should_not_execute_on_incompatible_executor()
{
GivenCommandQueue();
var commandA = new CommandA();
var commandModel = new CommandModel
{
Body = commandA
};
Subject.Handle(new ApplicationStartedEvent());
QueueAndWaitForExecution(commandModel);
_executorA.Verify(c => c.Execute(commandA), Times.Once());
_executorB.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
}
[Test]
public void broken_executor_should_publish_executed_event()
{
GivenCommandQueue();
var commandA = new CommandA();
var commandModel = new CommandModel
{
Body = commandA
};
_executorA.Setup(s => s.Execute(It.IsAny<CommandA>()))
.Throws(new NotImplementedException());
Subject.Handle(new ApplicationStartedEvent());
QueueAndWaitForExecution(commandModel);
VerifyEventPublished<CommandExecutedEvent>();
ExceptionVerification.WaitForErrors(1, 500);
}
[Test]
public void should_publish_executed_event_on_success()
{
GivenCommandQueue();
var commandA = new CommandA();
var commandModel = new CommandModel
{
Body = commandA
};
Subject.Handle(new ApplicationStartedEvent());
QueueAndWaitForExecution(commandModel);
VerifyEventPublished<CommandExecutedEvent>();
}
[Test]
public void should_use_completion_message()
{
GivenCommandQueue();
var commandA = new CommandA();
var commandModel = new CommandModel
{
Body = commandA
};
Subject.Handle(new ApplicationStartedEvent());
QueueAndWaitForExecution(commandModel);
Mocker.GetMock<IManageCommandQueue>()
.Verify(s => s.Complete(It.Is<CommandModel>(c => c == commandModel), commandA.CompletionMessage), Times.Once());
}
[Test]
public void should_use_last_progress_message_if_completion_message_is_null()
{
GivenCommandQueue();
var commandB = new CommandB();
var commandModel = new CommandModel
{
Body = commandB,
Message = "Do work"
};
Subject.Handle(new ApplicationStartedEvent());
QueueAndWaitForExecution(commandModel);
Mocker.GetMock<IManageCommandQueue>()
.Verify(s => s.Complete(It.Is<CommandModel>(c => c == commandModel), commandModel.Message), Times.Once());
}
}
public class CommandA : Command
{
public CommandA(int id = 0)
{
}
}
public class CommandB : Command
{
public CommandB()
{
}
public override string CompletionMessage => null;
}
}

View File

@ -18,7 +18,8 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
}
[TestCase("Prometheus", "Prometheus")]
[TestCase("The Man from U.N.C.L.E.", "The Man from U.N.C.L.E.")]
// TODO: TMDB Doesn't like when we clean periods from this
// [TestCase("The Man from U.N.C.L.E.", "The Man from U.N.C.L.E.")]
[TestCase("imdb:tt2527336", "Star Wars: The Last Jedi")]
[TestCase("imdb:tt2798920", "Annihilation")]
public void successful_search(string title, string expected)

View File

@ -42,9 +42,9 @@ namespace NzbDrone.Core.Test.MovieTests
_movie.LastInfoSync = DateTime.UtcNow.AddDays(-1);
}
private void GivenMovieLastRefreshedHalfADayAgo()
private void GivenMovieLastRefreshedADayAgo()
{
_movie.LastInfoSync = DateTime.UtcNow.AddHours(-12);
_movie.LastInfoSync = DateTime.UtcNow.AddHours(-24);
}
private void GivenMovieLastRefreshedRecently()
@ -58,15 +58,15 @@ namespace NzbDrone.Core.Test.MovieTests
}
[Test]
public void should_return_true_if_in_cinemas_movie_last_refreshed_more_than_6_hours_ago()
public void should_return_true_if_in_cinemas_movie_last_refreshed_more_than_12_hours_ago()
{
GivenMovieLastRefreshedHalfADayAgo();
GivenMovieLastRefreshedADayAgo();
Subject.ShouldRefresh(_movie).Should().BeTrue();
}
[Test]
public void should_return_false_if_in_cinemas_movie_last_refreshed_less_than_6_hours_ago()
public void should_return_false_if_in_cinemas_movie_last_refreshed_less_than_12_hours_ago()
{
GivenMovieLastRefreshedRecently();

View File

@ -37,6 +37,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests
public void Setup()
{
Mocker.GetMock<IConfigService>().SetupGet(s => s.RecycleBin).Returns(RecycleBin);
Mocker.GetMock<IConfigService>().SetupGet(s => s.RecycleBinCleanupDays).Returns(7);
Mocker.GetMock<IDiskProvider>().Setup(s => s.GetDirectories(RecycleBin))
.Returns(new [] { @"C:\Test\RecycleBin\Folder1", @"C:\Test\RecycleBin\Folder2", @"C:\Test\RecycleBin\Folder3" });

View File

@ -15,6 +15,9 @@
<Link>Files\1024.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Reference Include="System.Data.SQLite">
<HintPath>..\Libraries\Sqlite\System.Data.SQLite.dll</HintPath>
</Reference>
<None Update="Files\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@ -87,6 +87,16 @@ namespace NzbDrone.Core.Test.UpdateTests
.Returns(true);
}
[Test]
public void should_not_update_if_inside_docker()
{
Mocker.GetMock<IOsInfo>().Setup(x => x.IsDocker).Returns(true);
Subject.Invoking(x => x.Execute(new ApplicationUpdateCommand()))
.Should().Throw<CommandFailedException>()
.WithMessage("Updating is disabled inside a docker container. Please update the container image.");
}
[Test]
public void should_delete_sandbox_before_update_if_folder_exists()
{

View File

@ -1,22 +1,40 @@
using NzbDrone.Common.EnvironmentInfo;
using System;
using System.Linq;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.History;
namespace NzbDrone.Core.Analytics
{
public interface IAnalyticsService
{
bool IsEnabled { get; }
bool InstallIsActive { get; }
}
public class AnalyticsService : IAnalyticsService
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IHistoryService _historyService;
public AnalyticsService(IConfigFileProvider configFileProvider)
public AnalyticsService(IHistoryService historyService, IConfigFileProvider configFileProvider)
{
_configFileProvider = configFileProvider;
_historyService = historyService;
}
public bool IsEnabled => _configFileProvider.AnalyticsEnabled;
public bool IsEnabled => _configFileProvider.AnalyticsEnabled && RuntimeInfo.IsProduction || RuntimeInfo.IsDevelopment;
public bool InstallIsActive
{
get
{
var lastRecord = _historyService.Paged(new PagingSpec<History.History>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending });
var monthAgo = DateTime.UtcNow.AddMonths(-1);
return lastRecord.Records.Any(v => v.Date > monthAgo);
}
}
}
}

View File

@ -34,6 +34,7 @@ namespace NzbDrone.Core.Configuration
bool AnalyticsEnabled { get; }
string LogLevel { get; }
string ConsoleLogLevel { get; }
bool FilterSentryEvents { get; }
string Branch { get; }
string ApiKey { get; }
string SslCertHash { get; }
@ -182,7 +183,7 @@ namespace NzbDrone.Core.Configuration
public string LogLevel => GetValue("LogLevel", "info");
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertHash => GetValue("SslCertHash", "");
public string UrlBase
@ -364,11 +365,6 @@ namespace NzbDrone.Core.Configuration
{
EnsureDefaultConfigFile();
DeleteOldValues();
if (!AnalyticsEnabled)
{
NzbDroneLogger.UnRegisterRemoteLoggers();
}
}
public void Execute(ResetApiKeyCommand message)

View File

@ -3,7 +3,7 @@ using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Datastore
{
public class CorruptDatabaseException : NzbDroneException
public class CorruptDatabaseException : RadarrStartupException
{
public CorruptDatabaseException(string message, params object[] args) : base(message, args)
{

View File

@ -9,6 +9,7 @@ namespace NzbDrone.Core.Datastore
{
IDataMapper GetDataMapper();
Version Version { get; }
int Migration { get; }
void Vacuum();
}
@ -42,6 +43,16 @@ namespace NzbDrone.Core.Datastore
}
}
public int Migration
{
get
{
var migration = _datamapperFactory()
.ExecuteScalar("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1").ToString();
return Convert.ToInt32(migration);
}
}
public void Vacuum()
{
try

View File

@ -6,6 +6,7 @@ using NLog;
using NzbDrone.Common.Composition;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Datastore.Migration.Framework;
@ -124,7 +125,11 @@ namespace NzbDrone.Core.Datastore
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-use-sonarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
}
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Lidarr/Lidarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
}
catch (Exception e)
{
throw new RadarrStartupException(e, "Error creating main database");
}
}
@ -154,6 +159,10 @@ namespace NzbDrone.Core.Datastore
_migrationController.Migrate(connectionString, migrationContext);
}
catch (Exception e)
{
throw new RadarrStartupException(e, "Error creating log database");
}
}
}
}

View File

@ -24,6 +24,8 @@ namespace NzbDrone.Core.Datastore
public Version Version => _database.Version;
public int Migration => _database.Migration;
public void Vacuum()
{
_database.Vacuum();

View File

@ -24,6 +24,8 @@ namespace NzbDrone.Core.Datastore
public Version Version => _database.Version;
public int Migration => _database.Migration;
public void Vacuum()
{
_database.Vacuum();

View File

@ -57,7 +57,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
SQLiteConnection.ClearAllPools();
throw;
}
processor.Dispose();
sw.Stop();

View File

@ -237,7 +237,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
}
catch (WebException ex)
{
_logger.Error(ex, "Unble to test connection");
_logger.Error(ex, "Unable to test connection");
switch (ex.Status)
{
case WebExceptionStatus.ConnectFailure:

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using NzbDrone.Common.EnvironmentInfo;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@ -107,7 +108,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
requestBuilder.AddQueryParam("limit", "100");
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
requestBuilder.AddQueryParam("format", "json_extended");
requestBuilder.AddQueryParam("app_id", "Radarr");
requestBuilder.AddQueryParam("app_id", BuildInfo.AppName);
yield return new IndexerRequest(requestBuilder.Build());
}

View File

@ -2,7 +2,9 @@
using System.Linq;
using NLog;
using NLog.Config;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Sentry;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Messaging.Events;
@ -40,6 +42,9 @@ namespace NzbDrone.Core.Instrumentation
SetMinimumLogLevel(rules, "appFileDebug", minimumLogLevel <= LogLevel.Debug ? LogLevel.Debug : LogLevel.Off);
SetMinimumLogLevel(rules, "appFileTrace", minimumLogLevel <= LogLevel.Trace ? LogLevel.Trace : LogLevel.Off);
//Sentry
ReconfigureSentry();
LogManager.ReconfigExistingLoggers();
}
@ -67,6 +72,16 @@ namespace NzbDrone.Core.Instrumentation
}
}
private void ReconfigureSentry()
{
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null)
{
sentryTarget.SentryEnabled = RuntimeInfo.IsProduction && _configFileProvider.AnalyticsEnabled || RuntimeInfo.IsDevelopment;
sentryTarget.FilterEvents = _configFileProvider.FilterSentryEvents;
}
}
private List<LogLevel> GetLogLevels()
{
return new List<LogLevel>

View File

@ -0,0 +1,42 @@
using System.Linq;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Sentry;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Instrumentation
{
public class ReconfigureSentry : IHandleAsync<ApplicationStartedEvent>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IPlatformInfo _platformInfo;
private readonly IMainDatabase _database;
public ReconfigureSentry(IConfigFileProvider configFileProvider,
IPlatformInfo platformInfo,
IMainDatabase database)
{
_configFileProvider = configFileProvider;
_platformInfo = platformInfo;
_database = database;
}
public void Reconfigure()
{
// Extended sentry config
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null)
{
sentryTarget.UpdateScope(_database.Version, _database.Migration, _configFileProvider.Branch, _platformInfo);
}
}
public void HandleAsync(ApplicationStartedEvent message)
{
Reconfigure();
}
}
}

View File

@ -4,6 +4,7 @@ using FluentValidation.Results;
using NLog;
using RestSharp;
using NzbDrone.Core.Rest;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.Notifications.Boxcar
{
@ -75,7 +76,7 @@ namespace NzbDrone.Core.Notifications.Boxcar
request.AddParameter("user_credentials", settings.Token);
request.AddParameter("notification[title]", title);
request.AddParameter("notification[long_message]", message);
request.AddParameter("notification[source_name]", "Radarr");
request.AddParameter("notification[source_name]", BuildInfo.AppName);
request.AddParameter("notification[icon_url]", "https://raw.githubusercontent.com/Radarr/Radarr/develop/Logo/64.png");
client.ExecuteAndValidate(request);

View File

@ -43,10 +43,10 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
var requestBuilder = new HttpRequestBuilder("https://plex.tv")
.Accept(HttpAccept.Json)
.AddQueryParam("X-Plex-Client-Identifier", clientIdentifier)
.AddQueryParam("X-Plex-Product", "Radarr")
.AddQueryParam("X-Plex-Product", BuildInfo.AppName)
.AddQueryParam("X-Plex-Platform", "Windows")
.AddQueryParam("X-Plex-Platform-Version", "7")
.AddQueryParam("X-Plex-Device-Name", "Radarr")
.AddQueryParam("X-Plex-Device-Name", BuildInfo.AppName)
.AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString());
return requestBuilder;

View File

@ -31,10 +31,10 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
var requestBuilder = new HttpRequestBuilder("https://plex.tv/api/v2/pins")
.Accept(HttpAccept.Json)
.AddQueryParam("X-Plex-Client-Identifier", clientIdentifier)
.AddQueryParam("X-Plex-Product", "Radarr")
.AddQueryParam("X-Plex-Product", BuildInfo.AppName)
.AddQueryParam("X-Plex-Platform", "Windows")
.AddQueryParam("X-Plex-Platform-Version", "7")
.AddQueryParam("X-Plex-Device-Name", "Radarr")
.AddQueryParam("X-Plex-Device-Name", BuildInfo.AppName)
.AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString())
.AddQueryParam("strong", true);
@ -57,7 +57,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
.AddQueryParam("clientID", clientIdentifier)
.AddQueryParam("forwardUrl", callbackUrl)
.AddQueryParam("code", pinCode)
.AddQueryParam("context[device][product]", "Radarr")
.AddQueryParam("context[device][product]", BuildInfo.AppName)
.AddQueryParam("context[device][platform]", "Windows")
.AddQueryParam("context[device][platformVersion]", "7")
.AddQueryParam("context[device][version]", BuildInfo.Version.ToString());

View File

@ -155,10 +155,10 @@ namespace NzbDrone.Core.Notifications.Plex.Server
var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host}:{settings.Port}")
.Accept(HttpAccept.Json)
.AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier)
.AddQueryParam("X-Plex-Product", "Radarr")
.AddQueryParam("X-Plex-Product", BuildInfo.AppName)
.AddQueryParam("X-Plex-Platform", "Windows")
.AddQueryParam("X-Plex-Platform-Version", "7")
.AddQueryParam("X-Plex-Device-Name", "Radarr")
.AddQueryParam("X-Plex-Device-Name", BuildInfo.AppName)
.AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString());
if (settings.AuthToken.IsNotNullOrWhiteSpace())

View File

@ -1,6 +1,7 @@
using System;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using Prowlin;
namespace NzbDrone.Core.Notifications.Prowl
@ -26,7 +27,7 @@ namespace NzbDrone.Core.Notifications.Prowl
{
var notification = new Prowlin.Notification
{
Application = "Radarr",
Application = BuildInfo.AppName,
Description = message,
Event = title,
Priority = priority,

View File

@ -9,7 +9,7 @@ namespace NzbDrone.Core.Rest
{
var restClient = new RestClient(baseUrl)
{
UserAgent = $"Radarr/{BuildInfo.Version} ({OsInfo.Os})"
UserAgent = $"{BuildInfo.AppName}/{BuildInfo.Version} ({OsInfo.Os})"
};

View File

@ -32,6 +32,7 @@ namespace NzbDrone.Core.Update
private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IBackupService _backupService;
private readonly IOsInfo _osInfo;
public InstallUpdateService(ICheckUpdateService checkUpdateService,
@ -46,6 +47,7 @@ namespace NzbDrone.Core.Update
IConfigFileProvider configFileProvider,
IRuntimeInfo runtimeInfo,
IBackupService backupService,
IOsInfo osInfo,
Logger logger)
{
if (configFileProvider == null)
@ -64,6 +66,7 @@ namespace NzbDrone.Core.Update
_configFileProvider = configFileProvider;
_runtimeInfo = runtimeInfo;
_backupService = backupService;
_osInfo = osInfo;
_logger = logger;
}
@ -209,6 +212,11 @@ namespace NzbDrone.Core.Update
return;
}
if (_osInfo.IsDocker)
{
throw new CommandFailedException("Updating is disabled inside a docker container. Please update the container image.");
}
if (OsInfo.IsNotWindows && !_configFileProvider.UpdateAutomatically && message.Trigger != CommandTrigger.Manual)
{
_logger.ProgressDebug("Auto-update not enabled, not installing available update.");

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Core.Analytics;
namespace NzbDrone.Core.Update
{
@ -15,14 +16,16 @@ namespace NzbDrone.Core.Update
public class UpdatePackageProvider : IUpdatePackageProvider
{
private readonly IHttpClient _httpClient;
private readonly IPlatformInfo _platformInfo;
private readonly IHttpRequestBuilderFactory _requestBuilder;
private readonly IPlatformInfo _platformInfo;
private readonly IAnalyticsService _analyticsService;
public UpdatePackageProvider(IHttpClient httpClient, IRadarrCloudRequestBuilder requestBuilder, IPlatformInfo platformInfo)
public UpdatePackageProvider(IHttpClient httpClient, IRadarrCloudRequestBuilder requestBuilder, IAnalyticsService analyticsService, IPlatformInfo platformInfo)
{
_httpClient = httpClient;
_platformInfo = platformInfo;
_analyticsService = analyticsService;
_requestBuilder = requestBuilder.Services;
_httpClient = httpClient;
}
public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
@ -32,10 +35,15 @@ namespace NzbDrone.Core.Update
.AddQueryParam("version", currentVersion)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("runtimeVer", _platformInfo.Version)
.SetSegment("branch", branch)
.Build();
.SetSegment("branch", branch);
var update = _httpClient.Get<UpdatePackageAvailable>(request).Resource;
if (_analyticsService.IsEnabled)
{
// Send if the system is active so we know which versions to deprecate/ignore
request.AddQueryParam("active", _analyticsService.InstallIsActive.ToString().ToLower());
}
var update = _httpClient.Get<UpdatePackageAvailable>(request.Build()).Resource;
if (!update.Available) return null;
@ -49,12 +57,17 @@ namespace NzbDrone.Core.Update
.AddQueryParam("version", currentVersion)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("runtimeVer", _platformInfo.Version)
.SetSegment("branch", branch)
.Build();
.SetSegment("branch", branch);
var updates = _httpClient.Get<List<UpdatePackage>>(request);
if (_analyticsService.IsEnabled)
{
// Send if the system is active so we know which versions to deprecate/ignore
request.AddQueryParam("active", _analyticsService.InstallIsActive.ToString().ToLower());
}
var updates = _httpClient.Get<List<UpdatePackage>>(request.Build());
return updates.Resource;
}
}
}
}

View File

@ -29,7 +29,6 @@ namespace NzbDrone.App.Test
Mocker.GetMock<IProcessProvider>()
.Setup(c => c.SpawnNewProcess("sc.exe", It.IsAny<string>(), null, true));
Mocker.GetMock<IRuntimeInfo>().SetupGet(c => c.IsUserInteractive).Returns(true);
Subject.Route(ApplicationModes.InstallService);

View File

@ -0,0 +1,46 @@
using NzbDrone.Common.EnvironmentInfo;
namespace Radarr.Host.AccessControl
{
public interface IRemoteAccessAdapter
{
void MakeAccessible(bool passive);
}
public class RemoteAccessAdapter : IRemoteAccessAdapter
{
private readonly IRuntimeInfo _runtimeInfo;
private readonly IUrlAclAdapter _urlAclAdapter;
private readonly IFirewallAdapter _firewallAdapter;
private readonly ISslAdapter _sslAdapter;
public RemoteAccessAdapter(IRuntimeInfo runtimeInfo,
IUrlAclAdapter urlAclAdapter,
IFirewallAdapter firewallAdapter,
ISslAdapter sslAdapter)
{
_runtimeInfo = runtimeInfo;
_urlAclAdapter = urlAclAdapter;
_firewallAdapter = firewallAdapter;
_sslAdapter = sslAdapter;
}
public void MakeAccessible(bool passive)
{
if (OsInfo.IsWindows)
{
if (_runtimeInfo.IsAdmin)
{
_firewallAdapter.MakeAccessible();
_sslAdapter.Register();
}
else if (!passive)
{
throw new RemoteAccessException("Failed to register URLs for Radarr. Radarr will not be accessible remotely");
}
}
_urlAclAdapter.ConfigureUrls();
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using NzbDrone.Common.Exceptions;
namespace Radarr.Host.AccessControl
{
public class RemoteAccessException : NzbDroneException
{
public RemoteAccessException(string message, params object[] args) : base(message, args)
{
}
public RemoteAccessException(string message) : base(message)
{
}
public RemoteAccessException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
{
}
public RemoteAccessException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@ -7,5 +7,6 @@
InstallService,
UninstallService,
Service,
RegisterUrl
}
}

View File

@ -29,6 +29,7 @@ namespace Radarr.Host
}
_container = MainAppContainerBuilder.BuildContainer(startupContext);
_container.Resolve<InitializeLogger>().Initialize();
_container.Resolve<IAppFolderFactory>().Register();
_container.Resolve<IProvidePidFile>().Write();
@ -109,11 +110,17 @@ namespace Radarr.Host
private static ApplicationModes GetApplicationMode(IStartupContext startupContext)
{
if (startupContext.Flags.Contains(StartupContext.HELP))
if (startupContext.Help)
{
return ApplicationModes.Help;
}
if (OsInfo.IsWindows && startupContext.RegisterUrl)
{
return ApplicationModes.RegisterUrl;
}
if (OsInfo.IsWindows && startupContext.InstallService)
{
return ApplicationModes.InstallService;
@ -138,6 +145,7 @@ namespace Radarr.Host
{
case ApplicationModes.InstallService:
case ApplicationModes.UninstallService:
case ApplicationModes.RegisterUrl:
case ApplicationModes.Help:
{
return true;

View File

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

View File

@ -8,41 +8,26 @@ namespace Radarr.Host.Owin
public class OwinHostController : IHostController
{
private readonly IOwinAppFactory _owinAppFactory;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IRemoteAccessAdapter _removeAccessAdapter;
private readonly IUrlAclAdapter _urlAclAdapter;
private readonly IFirewallAdapter _firewallAdapter;
private readonly ISslAdapter _sslAdapter;
private readonly Logger _logger;
private IDisposable _owinApp;
public OwinHostController(
IOwinAppFactory owinAppFactory,
IRuntimeInfo runtimeInfo,
IRemoteAccessAdapter removeAccessAdapter,
IUrlAclAdapter urlAclAdapter,
IFirewallAdapter firewallAdapter,
ISslAdapter sslAdapter,
Logger logger)
{
_owinAppFactory = owinAppFactory;
_runtimeInfo = runtimeInfo;
_removeAccessAdapter = removeAccessAdapter;
_urlAclAdapter = urlAclAdapter;
_firewallAdapter = firewallAdapter;
_sslAdapter = sslAdapter;
_logger = logger;
}
public void StartServer()
{
if (OsInfo.IsWindows)
{
if (_runtimeInfo.IsAdmin)
{
_firewallAdapter.MakeAccessible();
_sslAdapter.Register();
}
}
_urlAclAdapter.ConfigureUrls();
_removeAccessAdapter.MakeAccessible(true);
_logger.Info("Listening on the following URLs:");
foreach (var url in _urlAclAdapter.Urls)
@ -53,7 +38,6 @@ namespace Radarr.Host.Owin
_owinApp = _owinAppFactory.CreateApp(_urlAclAdapter.Urls);
}
public void StopServer()
{
if (_owinApp == null) return;
@ -63,8 +47,5 @@ namespace Radarr.Host.Owin
_owinApp = null;
_logger.Info("Host has stopped");
}
}
}

View File

@ -11,6 +11,7 @@ using NLog;
using NzbDrone.Core.Configuration;
using Radarr.Host.Owin.MiddleWare;
using Owin;
using NzbDrone.Common.EnvironmentInfo;
namespace Radarr.Host.Owin
{
@ -70,7 +71,7 @@ namespace Radarr.Host.Owin
private void BuildApp(IAppBuilder appBuilder)
{
appBuilder.Properties["host.AppName"] = "NzbDrone";
appBuilder.Properties["host.AppName"] = BuildInfo.AppName;
foreach (var middleWare in _owinMiddleWares.OrderBy(c => c.Order))
{

View File

@ -1,6 +1,11 @@
using System;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes;
using Radarr.Host.AccessControl;
using IServiceProvider = NzbDrone.Common.IServiceProvider;
namespace Radarr.Host
{
@ -10,15 +15,24 @@ namespace Radarr.Host
private readonly IServiceProvider _serviceProvider;
private readonly IConsoleService _consoleService;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IProcessProvider _processProvider;
private readonly IRemoteAccessAdapter _remoteAccessAdapter;
private readonly Logger _logger;
public Router(INzbDroneServiceFactory nzbDroneServiceFactory, IServiceProvider serviceProvider,
IConsoleService consoleService, IRuntimeInfo runtimeInfo, Logger logger)
public Router(INzbDroneServiceFactory nzbDroneServiceFactory,
IServiceProvider serviceProvider,
IConsoleService consoleService,
IRuntimeInfo runtimeInfo,
IProcessProvider processProvider,
IRemoteAccessAdapter remoteAccessAdapter,
Logger logger)
{
_nzbDroneServiceFactory = nzbDroneServiceFactory;
_serviceProvider = serviceProvider;
_consoleService = consoleService;
_runtimeInfo = runtimeInfo;
_processProvider = processProvider;
_remoteAccessAdapter = remoteAccessAdapter;
_logger = logger;
}
@ -50,8 +64,13 @@ namespace Radarr.Host
}
else
{
_remoteAccessAdapter.MakeAccessible(true);
_serviceProvider.Install(ServiceProvider.SERVICE_NAME);
_serviceProvider.Start(ServiceProvider.SERVICE_NAME);
_serviceProvider.SetPermissions(ServiceProvider.SERVICE_NAME);
// Start the service and exit.
// Ensures that there isn't an instance of Radarr already running that the service account cannot stop.
_processProvider.SpawnNewProcess("sc.exe", $"start {ServiceProvider.SERVICE_NAME}", null, true);
}
break;
}
@ -67,6 +86,13 @@ namespace Radarr.Host
_serviceProvider.Uninstall(ServiceProvider.SERVICE_NAME);
}
break;
}
case ApplicationModes.RegisterUrl:
{
_logger.Debug("Regiser URL selected");
_remoteAccessAdapter.MakeAccessible(false);
break;
}
default:
@ -76,7 +102,5 @@ namespace Radarr.Host
}
}
}
}
}
}

View File

@ -1,6 +1,6 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Api.Movies;
using Radarr.Api.V2.Movies;
namespace NzbDrone.Integration.Test.ApiTests
{
@ -15,7 +15,7 @@ namespace NzbDrone.Integration.Test.ApiTests
{
_movie = EnsureMovie(11, "The Blacklist");
Blacklist.Post(new Api.Blacklist.BlacklistResource
Blacklist.Post(new Radarr.Api.V2.Blacklist.BlacklistResource
{
MovieId = _movie.Id,
SourceTitle = "Blacklist.S01E01.Brought.To.You.By-BoomBoxHD"

View File

@ -1,6 +1,6 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Api.Movies;
using Radarr.Api.V2.Movies;
using NzbDrone.Integration.Test.Client;
using System;
using System.Collections.Generic;

View File

@ -1,6 +1,6 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Api.Commands;
using NzbDrone.Integration.Test.Client;
namespace NzbDrone.Integration.Test.ApiTests
{
@ -10,7 +10,7 @@ namespace NzbDrone.Integration.Test.ApiTests
[Test]
public void should_be_able_to_run_rss_sync()
{
var response = Commands.Post(new CommandResource { Name = "rsssync" });
var response = Commands.Post(new SimpleCommandResource { Name = "rsssync" });
response.Id.Should().NotBe(0);
}

View File

@ -1,7 +1,7 @@
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Api.DiskSpace;
using Radarr.Api.V2.DiskSpace;
using NzbDrone.Integration.Test.Client;
namespace NzbDrone.Integration.Test.ApiTests

View File

@ -16,8 +16,8 @@ namespace NzbDrone.Integration.Test.ApiTests
var schema = DownloadClients.Schema().First(v => v.Implementation == "UsenetBlackhole");
schema.Enable = true;
schema.Fields.First(v => v.Name == "WatchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
schema.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
schema.Fields.First(v => v.Name == "watchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
schema.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
DownloadClients.InvalidPost(schema);
}
@ -31,7 +31,7 @@ namespace NzbDrone.Integration.Test.ApiTests
schema.Enable = true;
schema.Name = "Test UsenetBlackhole";
schema.Fields.First(v => v.Name == "WatchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
schema.Fields.First(v => v.Name == "watchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
DownloadClients.InvalidPost(schema);
}
@ -45,7 +45,7 @@ namespace NzbDrone.Integration.Test.ApiTests
schema.Enable = true;
schema.Name = "Test UsenetBlackhole";
schema.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
schema.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
DownloadClients.InvalidPost(schema);
}
@ -59,8 +59,8 @@ namespace NzbDrone.Integration.Test.ApiTests
schema.Enable = true;
schema.Name = "Test UsenetBlackhole";
schema.Fields.First(v => v.Name == "WatchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
schema.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
schema.Fields.First(v => v.Name == "watchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
schema.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
var result = DownloadClients.Post(schema);
@ -99,7 +99,7 @@ namespace NzbDrone.Integration.Test.ApiTests
EnsureNoDownloadClient();
var client = EnsureDownloadClient();
client.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb2");
client.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb2");
var result = DownloadClients.Put(client);
result.Should().NotBeNull();

View File

@ -1,6 +1,7 @@
using FluentAssertions;
using NUnit.Framework;
using System.Linq;
using Radarr.Api.V2.Movies;
using NzbDrone.Test.Common;
namespace NzbDrone.Integration.Test.ApiTests
@ -10,11 +11,11 @@ namespace NzbDrone.Integration.Test.ApiTests
{
private void GivenExistingMovie()
{
foreach (var title in new[] { "90210", "Dexter" })
foreach (var title in new[] { "The Dark Knight", "Pulp Fiction" })
{
var newMovie = Movies.Lookup(title).First();
newMovie.ProfileId = 1;
newMovie.QualityProfileId = 1;
newMovie.Path = string.Format(@"C:\Test\{0}", title).AsOsAgnostic();
Movies.Post(newMovie);
@ -26,17 +27,18 @@ namespace NzbDrone.Integration.Test.ApiTests
{
GivenExistingMovie();
var movie = Movies.All();
var movies = Movies.All();
foreach (var s in movie)
var movieEditor = new MovieEditorResource
{
s.ProfileId = 2;
}
QualityProfileId = 2,
MovieIds = movies.Select(o => o.Id).ToList()
};
var result = Movies.Editor(movie);
var result = Movies.Editor(movieEditor);
result.Should().HaveCount(2);
result.TrueForAll(s => s.ProfileId == 2).Should().BeTrue();
result.TrueForAll(s => s.QualityProfileId == 2).Should().BeTrue();
}
}
}

View File

@ -18,7 +18,7 @@ namespace NzbDrone.Integration.Test.ApiTests
var movie = Movies.Lookup("imdb:tt0110912").Single();
movie.ProfileId = 1;
movie.QualityProfileId = 1;
movie.Path = Path.Combine(MovieRootFolder, movie.Title);
movie.Tags = new HashSet<int>();
movie.Tags.Add(tag.Id);
@ -48,7 +48,7 @@ namespace NzbDrone.Integration.Test.ApiTests
var movie = Movies.Lookup("imdb:tt0110912").Single();
movie.ProfileId = 1;
movie.QualityProfileId = 1;
Movies.InvalidPost(movie);
}
@ -60,14 +60,14 @@ namespace NzbDrone.Integration.Test.ApiTests
var movie = Movies.Lookup("imdb:tt0110912").Single();
movie.ProfileId = 1;
movie.QualityProfileId = 1;
movie.Path = Path.Combine(MovieRootFolder, movie.Title);
var result = Movies.Post(movie);
result.Should().NotBeNull();
result.Id.Should().NotBe(0);
result.ProfileId.Should().Be(1);
result.QualityProfileId.Should().Be(1);
result.Path.Should().Be(Path.Combine(MovieRootFolder, movie.Title));
}
@ -105,16 +105,16 @@ namespace NzbDrone.Integration.Test.ApiTests
var movie = EnsureMovie(680, "Pulp Fiction");
var profileId = 1;
if (movie.ProfileId == profileId)
if (movie.QualityProfileId == profileId)
{
profileId = 2;
}
movie.ProfileId = profileId;
movie.QualityProfileId = profileId;
var result = Movies.Put(movie);
Movies.Get(movie.Id).ProfileId.Should().Be(profileId);
Movies.Get(movie.Id).QualityProfileId.Should().Be(profileId);
}
[Test, Order(3)]

View File

@ -25,11 +25,11 @@ namespace NzbDrone.Integration.Test.ApiTests
public void should_be_able_to_update()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = false;
config.RenameMovies = false;
config.StandardMovieFormat = "{Movie Title}";
var result = NamingConfig.Put(config);
result.RenameEpisodes.Should().BeFalse();
result.RenameMovies.Should().BeFalse();
result.StandardMovieFormat.Should().Be(config.StandardMovieFormat);
}
@ -38,7 +38,7 @@ namespace NzbDrone.Integration.Test.ApiTests
public void should_get_bad_request_if_standard_format_is_empty()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.RenameMovies = true;
config.StandardMovieFormat = "";
var errors = NamingConfig.InvalidPut(config);
@ -49,7 +49,7 @@ namespace NzbDrone.Integration.Test.ApiTests
public void should_get_bad_request_if_standard_format_doesnt_contain_title()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.RenameMovies = true;
config.StandardMovieFormat = "{quality}";
var errors = NamingConfig.InvalidPut(config);
@ -60,7 +60,7 @@ namespace NzbDrone.Integration.Test.ApiTests
public void should_not_require_format_when_rename_episodes_is_false()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = false;
config.RenameMovies = false;
config.StandardMovieFormat = "";
var errors = NamingConfig.InvalidPut(config);
@ -71,7 +71,7 @@ namespace NzbDrone.Integration.Test.ApiTests
public void should_require_format_when_rename_episodes_is_true()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.RenameMovies = true;
config.StandardMovieFormat = "";
var errors = NamingConfig.InvalidPut(config);
@ -82,7 +82,7 @@ namespace NzbDrone.Integration.Test.ApiTests
public void should_get_bad_request_if_movie_folder_format_does_not_contain_movie_title()
{
var config = NamingConfig.GetSingle();
config.RenameEpisodes = true;
config.RenameMovies = true;
config.MovieFolderFormat = "This and That";
var errors = NamingConfig.InvalidPut(config);

View File

@ -34,7 +34,7 @@ namespace NzbDrone.Integration.Test.ApiTests
var xbmc = schema.Single(s => s.Implementation.Equals("Xbmc", StringComparison.InvariantCultureIgnoreCase));
xbmc.Name = "Test XBMC";
xbmc.Fields.Single(f => f.Name.Equals("Host")).Value = "localhost";
xbmc.Fields.Single(f => f.Name.Equals("host")).Value = "localhost";
var result = Notifications.Post(xbmc);
Notifications.Delete(result.Id);

View File

@ -1,6 +1,6 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Api.Indexers;
using Radarr.Api.V2.Indexers;
using System.Linq;
using System.Net;

View File

@ -1,7 +1,7 @@
using System;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Api.RootFolders;
using Radarr.Api.V2.RootFolders;
namespace NzbDrone.Integration.Test.ApiTests
{

View File

@ -1,83 +0,0 @@
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Integration.Test.ApiTests.WantedTests
{
[TestFixture]
public class CutoffUnmetFixture : IntegrationTest
{
[Test, Order(1)]
public void cutoff_should_have_monitored_items()
{
EnsureProfileCutoff(1, Quality.HDTV720p);
var movie = EnsureMovie(680, "Pulp Fiction", true);
EnsureMovieFile(movie, Quality.SDTV);
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "true");
result.Records.Should().NotBeEmpty();
}
[Test, Order(1)]
public void cutoff_should_not_have_unmonitored_items()
{
EnsureProfileCutoff(1, Quality.HDTV720p);
var movie = EnsureMovie(680, "Pulp Fiction", false);
EnsureMovieFile(movie, Quality.SDTV);
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "true");
result.Records.Should().BeEmpty();
}
[Test, Order(1)]
public void cutoff_should_not_have_released_items()
{
EnsureProfileCutoff(1, Quality.HDTV720p);
var movie = EnsureMovie(680, "Pulp Fiction", true);
EnsureMovieFile(movie, Quality.SDTV);
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "status", "inCinemas");
result.Records.Should().BeEmpty();
}
[Test, Order(1)]
public void cutoff_should_have_movie()
{
EnsureProfileCutoff(1, Quality.HDTV720p);
var movie = EnsureMovie(680, "Pulp Fiction", true);
EnsureMovieFile(movie, Quality.SDTV);
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc");
result.Records.First().Title.Should().Be("Pulp Fiction");
}
[Test, Order(2)]
public void cutoff_should_have_unmonitored_items()
{
EnsureProfileCutoff(1, Quality.HDTV720p);
var movie = EnsureMovie(680, "Pulp Fiction", false);
EnsureMovieFile(movie, Quality.SDTV);
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "false");
result.Records.Should().NotBeEmpty();
}
[Test, Order(2)]
public void cutoff_should_have_released_items()
{
EnsureProfileCutoff(1, Quality.HDTV720p);
var movie = EnsureMovie(680, "Pulp Fiction", false);
EnsureMovieFile(movie, Quality.SDTV);
var result = WantedCutoffUnmet.GetPaged(0, 15, "physicalRelease", "desc", "status", "released");
result.Records.Should().NotBeEmpty();
}
}
}

View File

@ -1,81 +0,0 @@
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Integration.Test.ApiTests.WantedTests
{
[TestFixture]
public class MissingFixture : IntegrationTest
{
[Test, Order(0)]
public void missing_should_be_empty()
{
EnsureNoMovie(680, "Pulp Fiction");
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc");
result.Records.Should().BeEmpty();
}
[Test, Order(1)]
public void missing_should_have_monitored_items()
{
EnsureMovie(680, "Pulp Fiction", true);
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "true");
result.Records.Should().NotBeEmpty();
}
[Test, Order(1)]
public void missing_should_have_movie()
{
EnsureMovie(680, "Pulp Fiction", true);
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc");
result.Records.First().Title.Should().Be("Pulp Fiction");
}
[Test, Order(1)]
public void missing_should_not_have_unmonitored_items()
{
EnsureMovie(680, "Pulp Fiction", false);
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "true");
result.Records.Should().BeEmpty();
}
[Test, Order(1)]
public void missing_should_not_have_released_items()
{
EnsureMovie(680, "Pulp Fiction", false);
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "status", "inCinemas");
result.Records.Should().BeEmpty();
}
[Test, Order(2)]
public void missing_should_have_unmonitored_items()
{
EnsureMovie(680, "Pulp Fiction", false);
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "monitored", "false");
result.Records.Should().NotBeEmpty();
}
[Test, Order(2)]
public void missing_should_have_released_items()
{
EnsureMovie(680, "Pulp Fiction", false);
var result = WantedMissing.GetPaged(0, 15, "physicalRelease", "desc", "status", "released");
result.Records.Should().NotBeEmpty();
}
}
}

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Net;
using FluentAssertions;
using NLog;
using NzbDrone.Api;
using Radarr.Http.REST;
using Radarr.Http;
using NzbDrone.Common.Serializer;
@ -40,7 +39,7 @@ namespace NzbDrone.Integration.Test.Client
return request;
}
public T Execute<T>(IRestRequest request, HttpStatusCode statusCode) where T : class, new()
public string Execute(IRestRequest request, HttpStatusCode statusCode)
{
_logger.Info("{0}: {1}", request.Method, _restClient.BuildUri(request));
@ -58,7 +57,14 @@ namespace NzbDrone.Integration.Test.Client
response.StatusCode.Should().Be(statusCode);
return Json.Deserialize<T>(response.Content);
return response.Content;
}
public T Execute<T>(IRestRequest request, HttpStatusCode statusCode) where T : class, new()
{
var content = Execute(request, statusCode);
return Json.Deserialize<T>(content);
}
private static void AssertDisableCache(IList<Parameter> headers)

View File

@ -1,23 +1,47 @@
using NzbDrone.Api.Commands;
using RestSharp;
using RestSharp;
using NzbDrone.Core.Messaging.Commands;
using FluentAssertions;
using System.Threading;
using NUnit.Framework;
using System.Linq;
using System;
using Radarr.Http.REST;
using Newtonsoft.Json;
namespace NzbDrone.Integration.Test.Client
{
public class CommandClient : ClientBase<CommandResource>
public class SimpleCommandResource : RestResource
{
public string Name { get; set; }
public string CommandName { get; set; }
public string Message { get; set; }
public CommandPriority Priority { get; set; }
public CommandStatus Status { get; set; }
public DateTime Queued { get; set; }
public DateTime? Started { get; set; }
public DateTime? Ended { get; set; }
public TimeSpan? Duration { get; set; }
public string Exception { get; set; }
public CommandTrigger Trigger { get; set; }
[JsonIgnore]
public Command Body { get; set; }
[JsonProperty("body")]
public Command BodyReadOnly { get { return Body; } }
}
public class CommandClient : ClientBase<SimpleCommandResource>
{
public CommandClient(IRestClient restClient, string apiKey)
: base(restClient, apiKey)
: base(restClient, apiKey, "command")
{
}
public CommandResource PostAndWait(CommandResource command)
public SimpleCommandResource PostAndWait<T>(T command) where T : Command, new()
{
var result = Post(command);
var request = BuildRequest();
request.AddBody(command);
var result = Post<SimpleCommandResource>(request);
result.Id.Should().NotBe(0);
for (var i = 0; i < 50; i++)

View File

@ -1,5 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Api.DownloadClient;
using Radarr.Api.V2.DownloadClient;
using RestSharp;
namespace NzbDrone.Integration.Test.Client

View File

@ -1,4 +1,4 @@
using NzbDrone.Api.Indexers;
using Radarr.Api.V2.Indexers;
using RestSharp;
namespace NzbDrone.Integration.Test.Client

View File

@ -0,0 +1,24 @@
using System;
using RestSharp;
namespace NzbDrone.Integration.Test.Client
{
public class LogsClient : ClientBase
{
public LogsClient(IRestClient restClient, string apiKey)
: base(restClient, apiKey, "log/file")
{
}
public string[] GetLogFileLines(string filename)
{
var request = BuildRequest(filename);
var content = Execute(request, System.Net.HttpStatusCode.OK);
var lines = content.Split('\n');
lines = Array.ConvertAll(lines, s => s.TrimEnd('\r'));
Array.Resize(ref lines, lines.Length - 1);
return lines;
}
}
}

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Net;
using NzbDrone.Api.Movies;
using Radarr.Api.V2.Movies;
using RestSharp;
namespace NzbDrone.Integration.Test.Client
@ -19,7 +19,7 @@ namespace NzbDrone.Integration.Test.Client
return Get<List<MovieResource>>(request);
}
public List<MovieResource> Editor(List<MovieResource> movie)
public List<MovieResource> Editor(MovieEditorResource movie)
{
var request = BuildRequest("editor");
request.AddJsonBody(movie);

View File

@ -1,5 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Api.Notifications;
using Radarr.Api.V2.Notifications;
using RestSharp;
namespace NzbDrone.Integration.Test.Client

View File

@ -1,4 +1,4 @@
using NzbDrone.Api.Indexers;
using Radarr.Api.V2.Indexers;
using RestSharp;
namespace NzbDrone.Integration.Test.Client

Some files were not shown because too many files have changed in this diff Show More