mirror of
https://github.com/Radarr/Radarr.git
synced 2025-04-24 06:27:08 -04:00
Fixed: Automation/Integration/Unit Tests
This commit is contained in:
parent
944f420270
commit
7f221c7834
123 changed files with 1384 additions and 1191 deletions
21
.gitattributes
vendored
21
.gitattributes
vendored
|
@ -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
4
.gitmodules
vendored
|
@ -1,4 +0,0 @@
|
|||
[submodule "src/ExternalModules/CurlSharp"]
|
||||
path = src/ExternalModules/CurlSharp
|
||||
url = https://github.com/Sonarr/CurlSharp.git
|
||||
branch = master
|
|
@ -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:
|
||||
|
|
4
build.sh
4
build.sh
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,9 +11,4 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\ExternalModules\CurlSharp\libs\i386\*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
src/NzbDrone.Common/Instrumentation/InitializeLogger.cs
Normal file
28
src/NzbDrone.Common/Instrumentation/InitializeLogger.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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("");
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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" });
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ namespace NzbDrone.Core.Datastore
|
|||
|
||||
public Version Version => _database.Version;
|
||||
|
||||
public int Migration => _database.Migration;
|
||||
|
||||
public void Vacuum()
|
||||
{
|
||||
_database.Vacuum();
|
||||
|
|
|
@ -24,6 +24,8 @@ namespace NzbDrone.Core.Datastore
|
|||
|
||||
public Version Version => _database.Version;
|
||||
|
||||
public int Migration => _database.Migration;
|
||||
|
||||
public void Vacuum()
|
||||
{
|
||||
_database.Vacuum();
|
||||
|
|
|
@ -57,7 +57,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
|||
SQLiteConnection.ClearAllPools();
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
processor.Dispose();
|
||||
|
||||
sw.Stop();
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
52
src/NzbDrone.Core/HealthCheck/Checks/DotnetVersionCheck.cs
Normal file
52
src/NzbDrone.Core/HealthCheck/Checks/DotnetVersionCheck.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -46,7 +46,8 @@ namespace NzbDrone.Core.HealthCheck
|
|||
public enum HealthCheckResult
|
||||
{
|
||||
Ok = 0,
|
||||
Warning = 1,
|
||||
Error = 2
|
||||
Notice = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
42
src/NzbDrone.Core/Instrumentation/ReconfigureSentry.cs
Normal file
42
src/NzbDrone.Core/Instrumentation/ReconfigureSentry.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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})"
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
46
src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs
Normal file
46
src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
24
src/NzbDrone.Host/AccessControl/RemoteAccessException.cs
Normal file
24
src/NzbDrone.Host/AccessControl/RemoteAccessException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,5 +7,6 @@
|
|||
InstallService,
|
||||
UninstallService,
|
||||
Service,
|
||||
RegisterUrl
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -31,7 +31,6 @@ namespace Radarr.Host
|
|||
AutoRegisterImplementations<NzbDronePersistentConnection>();
|
||||
|
||||
Container.Register<INancyBootstrapper, RadarrBootstrapper>();
|
||||
Container.Register<IHttpDispatcher, FallbackHttpDispatcher>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Api.Indexers;
|
||||
using Radarr.Api.V2.Indexers;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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++)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Api.DownloadClient;
|
||||
using Radarr.Api.V2.DownloadClient;
|
||||
using RestSharp;
|
||||
|
||||
namespace NzbDrone.Integration.Test.Client
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using NzbDrone.Api.Indexers;
|
||||
using Radarr.Api.V2.Indexers;
|
||||
using RestSharp;
|
||||
|
||||
namespace NzbDrone.Integration.Test.Client
|
||||
|
|
24
src/NzbDrone.Integration.Test/Client/LogsClient.cs
Normal file
24
src/NzbDrone.Integration.Test/Client/LogsClient.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Api.Notifications;
|
||||
using Radarr.Api.V2.Notifications;
|
||||
using RestSharp;
|
||||
|
||||
namespace NzbDrone.Integration.Test.Client
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue