From 7f221c7834a870946ae3e2888e7dc98d03ea47db Mon Sep 17 00:00:00 2001 From: Qstick Date: Mon, 2 Sep 2019 22:22:25 -0400 Subject: [PATCH] Fixed: Automation/Integration/Unit Tests --- .gitattributes | 21 +- .gitmodules | 4 - azure-pipelines.yml | 23 +- build.sh | 4 +- frontend/src/System/Status/About/About.js | 17 + frontend/src/System/Status/Health/Health.js | 16 +- frontend/src/System/Updates/Updates.js | 48 +-- .../src/System/Updates/UpdatesConnector.js | 4 + src/Directory.Build.props | 5 +- src/ExternalModules/CurlSharp | 1 - src/NzbDrone.Api/Logs/LogFileModuleBase.cs | 5 +- .../AutomationTest.cs | 6 +- src/NzbDrone.Automation.Test/MainPagesTest.cs | 31 +- .../PageModel/PageBase.cs | 12 +- .../Radarr.Automation.Test.csproj | 1 + .../DiskTests/DiskProviderFixtureBase.cs | 2 + .../Http/HttpClientFixture.cs | 1 - .../ProcessProviderTests.cs | 33 +- .../Radarr.Common.Test.csproj | 5 - .../ServiceProviderTests.cs | 20 +- src/NzbDrone.Common/CurlSharp.dll.config | 7 - .../EnvironmentInfo/BuildInfo.cs | 2 + src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs | 10 +- .../EnvironmentInfo/RuntimeInfo.cs | 54 ++- .../Http/Dispatchers/CurlHttpDispatcher.cs | 342 ----------------- .../Dispatchers/FallbackHttpDispatcher.cs | 56 --- src/NzbDrone.Common/Http/HttpProvider.cs | 2 +- .../Http/TlsFailureException.cs | 4 +- src/NzbDrone.Common/Http/UserAgentBuilder.cs | 4 +- .../Instrumentation/InitializeLogger.cs | 28 ++ .../Instrumentation/NzbDroneLogger.cs | 12 - .../Instrumentation/Sentry/SentryTarget.cs | 104 ++++-- .../Processes/ProcessProvider.cs | 2 + src/NzbDrone.Common/Radarr.Common.csproj | 3 - src/NzbDrone.Console/ConsoleApp.cs | 8 + src/NzbDrone.Core.Test/Framework/CoreTest.cs | 5 +- src/NzbDrone.Core.Test/Framework/DbTest.cs | 25 +- .../Checks/DotnetVersionCheckFixture.cs | 57 +++ .../Checks/HealthCheckFixtureExtensions.cs | 10 + .../Checks/MonoVersionCheckFixture.cs | 36 +- .../Commands/CommandExecutorFixture.cs | 345 ++++++++++++------ .../SkyHook/SkyHookProxySearchFixture.cs | 3 +- .../MovieTests/ShouldRefreshMovieFixture.cs | 10 +- .../RecycleBinProviderTests/CleanupFixture.cs | 1 + .../Radarr.Core.Test.csproj | 3 + .../UpdateTests/UpdateServiceFixture.cs | 10 + .../Analytics/AnalyticsService.cs | 24 +- .../Configuration/ConfigFileProvider.cs | 8 +- .../Datastore/CorruptDatabaseException.cs | 2 +- src/NzbDrone.Core/Datastore/Database.cs | 11 + src/NzbDrone.Core/Datastore/DbFactory.cs | 11 +- src/NzbDrone.Core/Datastore/LogDatabase.cs | 2 + src/NzbDrone.Core/Datastore/MainDatabase.cs | 2 + .../Framework/MigrationController.cs | 3 +- .../Download/Clients/Deluge/Deluge.cs | 2 +- .../HealthCheck/Checks/DotnetVersionCheck.cs | 52 +++ .../HealthCheck/Checks/MonoTlsCheck.cs | 13 +- .../HealthCheck/Checks/MonoVersionCheck.cs | 36 +- src/NzbDrone.Core/HealthCheck/HealthCheck.cs | 5 +- .../Indexers/Rarbg/RarbgRequestGenerator.cs | 3 +- .../Instrumentation/ReconfigureLogging.cs | 15 + .../Instrumentation/ReconfigureSentry.cs | 42 +++ .../Notifications/Boxcar/BoxcarProxy.cs | 3 +- .../Notifications/Plex/PlexTv/PlexTvProxy.cs | 4 +- .../Plex/PlexTv/PlexTvService.cs | 6 +- .../Plex/Server/PlexServerProxy.cs | 4 +- .../Notifications/Prowl/ProwlService.cs | 3 +- src/NzbDrone.Core/Rest/RestClientFactory.cs | 2 +- .../Update/InstallUpdateService.cs | 8 + .../Update/UpdatePackageProvider.cs | 33 +- src/NzbDrone.Host.Test/RouterTest.cs | 1 - .../AccessControl/RemoteAccessAdapter.cs | 46 +++ .../AccessControl/RemoteAccessException.cs | 24 ++ src/NzbDrone.Host/ApplicationModes.cs | 1 + src/NzbDrone.Host/Bootstrap.cs | 10 +- src/NzbDrone.Host/MainAppContainerBuilder.cs | 1 - src/NzbDrone.Host/Owin/OwinHostController.cs | 27 +- src/NzbDrone.Host/Owin/OwinServiceProvider.cs | 3 +- src/NzbDrone.Host/Router.cs | 36 +- .../ApiTests/BlacklistFixture.cs | 4 +- .../ApiTests/CalendarFixture.cs | 2 +- .../ApiTests/CommandFixture.cs | 4 +- .../ApiTests/DiskSpaceFixture.cs | 2 +- .../ApiTests/DownloadClientFixture.cs | 14 +- .../ApiTests/MovieEditorFixture.cs | 18 +- .../ApiTests/MovieFixture.cs | 14 +- .../ApiTests/NamingConfigFixture.cs | 14 +- .../ApiTests/NotificationFixture.cs | 2 +- .../ApiTests/ReleaseFixture.cs | 2 +- .../ApiTests/RootFolderFixture.cs | 2 +- .../WantedTests/CutoffUnmetFixture.cs | 83 ----- .../ApiTests/WantedTests/MissingFixture.cs | 81 ---- .../Client/ClientBase.cs | 12 +- .../Client/CommandClient.cs | 36 +- .../Client/DownloadClientClient.cs | 2 +- .../Client/IndexerClient.cs | 2 +- .../Client/LogsClient.cs | 24 ++ .../Client/MovieClient.cs | 4 +- .../Client/NotificationClient.cs | 2 +- .../Client/ReleaseClient.cs | 2 +- .../HttpLogFixture.cs | 21 +- .../IntegrationTest.cs | 5 +- .../IntegrationTestBase.cs | 60 +-- .../Radarr.Integration.Test.csproj | 2 +- .../MonoPlatformInfoFixture.cs | 2 +- .../ReleaseFileVersionAdapterFixture.cs | 2 +- .../ReleaseFileVersionAdapterFixture.cs | 2 +- .../EnvironmentInfo/MonoPlatformInfo.cs | 1 - .../ExceptionVerification.cs | 98 +++-- src/NzbDrone.Test.Common/LoggingTest.cs | 47 ++- src/NzbDrone.Test.Common/NzbDroneRunner.cs | 66 ++-- src/NzbDrone.Test.Common/TestBase.cs | 41 ++- src/NzbDrone.Test.Common/TestLogOutput.cs | 9 + .../StartNzbDroneService.cs | 7 +- src/NzbDrone.Update/UpdateApp.cs | 2 +- src/NzbDrone.Update/UpdateContainerBuilder.cs | 2 +- .../DotNetPlatformInfoFixture.cs | 2 +- .../EnvironmentInfo/DotNetPlatformInfo.cs | 17 +- src/Radarr.Api.V2/Logs/LogFileModuleBase.cs | 5 +- .../Movies/MovieEditorResource.cs | 2 +- src/Radarr.Api.V2/System/SystemModule.cs | 1 + src/Radarr.sln | 9 - src/SharedLiveTemplates.xml | 1 - 123 files changed, 1384 insertions(+), 1191 deletions(-) delete mode 100644 .gitmodules delete mode 160000 src/ExternalModules/CurlSharp delete mode 100644 src/NzbDrone.Common/CurlSharp.dll.config delete mode 100644 src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs delete mode 100644 src/NzbDrone.Common/Http/Dispatchers/FallbackHttpDispatcher.cs create mode 100644 src/NzbDrone.Common/Instrumentation/InitializeLogger.cs create mode 100644 src/NzbDrone.Core.Test/HealthCheck/Checks/DotnetVersionCheckFixture.cs create mode 100644 src/NzbDrone.Core/HealthCheck/Checks/DotnetVersionCheck.cs create mode 100644 src/NzbDrone.Core/Instrumentation/ReconfigureSentry.cs create mode 100644 src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs create mode 100644 src/NzbDrone.Host/AccessControl/RemoteAccessException.cs delete mode 100644 src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs delete mode 100644 src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs create mode 100644 src/NzbDrone.Integration.Test/Client/LogsClient.cs create mode 100644 src/NzbDrone.Test.Common/TestLogOutput.cs delete mode 100644 src/SharedLiveTemplates.xml diff --git a/.gitattributes b/.gitattributes index cc34a3e65..d70e95089 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 \ No newline at end of file +*.sln merge=union diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index bc941f3dd..000000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "src/ExternalModules/CurlSharp"] - path = src/ExternalModules/CurlSharp - url = https://github.com/Sonarr/CurlSharp.git - branch = master diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6152d5533..bcbfef76b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -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: diff --git a/build.sh b/build.sh index ffbc62e2d..3e42a3b75 100755 --- a/build.sh +++ b/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 diff --git a/frontend/src/System/Status/About/About.js b/frontend/src/System/Status/About/About.js index 0965baff4..12ec88157 100644 --- a/frontend/src/System/Status/About/About.js +++ b/frontend/src/System/Status/About/About.js @@ -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 && + + } + + + diff --git a/frontend/src/System/Updates/Updates.js b/frontend/src/System/Updates/Updates.js index feac8fb6f..05623f64d 100644 --- a/frontend/src/System/Updates/Updates.js +++ b/frontend/src/System/Updates/Updates.js @@ -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 ( @@ -58,29 +52,24 @@ class Updates extends Component { { hasUpdateToInstall && -
+
{ - updateMechanism === 'builtIn' || updateMechanism === 'script' ? - - Install Latest - : + !isDocker && + + Install Latest + + } - - - -
- {externalUpdaterMessages[updateMechanism] || externalUpdaterMessages.external} -
-
+ { + isDocker && +
+ An update is available. Please update your Docker image and re-create the container. +
} { @@ -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 diff --git a/frontend/src/System/Updates/UpdatesConnector.js b/frontend/src/System/Updates/UpdatesConnector.js index 8836bbb94..4003eed54 100644 --- a/frontend/src/System/Updates/UpdatesConnector.js +++ b/frontend/src/System/Updates/UpdatesConnector.js @@ -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 }; diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c437d8012..87469695d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,8 +20,10 @@ + Release - $(RadarrRootDir)_temp\obj\$(Configuration)\$(MSBuildProjectName)\ + $(RadarrRootDir)_temp\obj\$(MSBuildProjectName)\ + $(RadarrRootDir)_temp\obj\$(MSBuildProjectName)\$(Configuration)\ $(RadarrRootDir)_temp\bin\$(Configuration)\$(MSBuildProjectName)\ @@ -30,6 +32,7 @@ $(RadarrRootDir)_output\Radarr.Update\ + $([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(BaseIntermediateOutputPath)')) $([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)')) $([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(OutputPath)')) diff --git a/src/ExternalModules/CurlSharp b/src/ExternalModules/CurlSharp deleted file mode 160000 index cfdbbbd9c..000000000 --- a/src/ExternalModules/CurlSharp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cfdbbbd9c6b9612c2756245049a8234ce87dc576 diff --git a/src/NzbDrone.Api/Logs/LogFileModuleBase.cs b/src/NzbDrone.Api/Logs/LogFileModuleBase.cs index e719bafdd..8e78848c5 100644 --- a/src/NzbDrone.Api/Logs/LogFileModuleBase.cs +++ b/src/NzbDrone.Api/Logs/LogFileModuleBase.cs @@ -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)) diff --git a/src/NzbDrone.Automation.Test/AutomationTest.cs b/src/NzbDrone.Automation.Test/AutomationTest.cs index 01c307b67..c3de80202 100644 --- a/src/NzbDrone.Automation.Test/AutomationTest.cs +++ b/src/NzbDrone.Automation.Test/AutomationTest.cs @@ -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(); } diff --git a/src/NzbDrone.Automation.Test/MainPagesTest.cs b/src/NzbDrone.Automation.Test/MainPagesTest.cs index bbf89690a..7737751ca 100644 --- a/src/NzbDrone.Automation.Test/MainPagesTest.cs +++ b/src/NzbDrone.Automation.Test/MainPagesTest.cs @@ -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(); } } } \ No newline at end of file diff --git a/src/NzbDrone.Automation.Test/PageModel/PageBase.cs b/src/NzbDrone.Automation.Test/PageModel/PageBase.cs index f835c39a6..c0db1ab50 100644 --- a/src/NzbDrone.Automation.Test/PageModel/PageBase.cs +++ b/src/NzbDrone.Automation.Test/PageModel/PageBase.cs @@ -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")); } } \ No newline at end of file diff --git a/src/NzbDrone.Automation.Test/Radarr.Automation.Test.csproj b/src/NzbDrone.Automation.Test/Radarr.Automation.Test.csproj index 08ec8b7e6..b75dddef3 100644 --- a/src/NzbDrone.Automation.Test/Radarr.Automation.Test.csproj +++ b/src/NzbDrone.Automation.Test/Radarr.Automation.Test.csproj @@ -4,6 +4,7 @@ x86 + diff --git a/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs b/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs index 61ad8f8e6..f02b07d7a 100644 --- a/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs +++ b/src/NzbDrone.Common.Test/DiskTests/DiskProviderFixtureBase.cs @@ -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(() => 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(() => DoHardLinkRename(FileShare.Read)); diff --git a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs index b072ff395..fef4ec4b6 100644 --- a/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs +++ b/src/NzbDrone.Common.Test/Http/HttpClientFixture.cs @@ -22,7 +22,6 @@ namespace NzbDrone.Common.Test.Http { [IntegrationTest] [TestFixture(typeof(ManagedHttpDispatcher))] - [TestFixture(typeof(CurlHttpDispatcher))] public class HttpClientFixture : TestBase where TDispatcher : IHttpDispatcher { private string[] _httpBinHosts; diff --git a/src/NzbDrone.Common.Test/ProcessProviderTests.cs b/src/NzbDrone.Common.Test/ProcessProviderTests.cs index 3bbe5a7d7..b4ea33b7c 100644 --- a/src/NzbDrone.Common.Test/ProcessProviderTests.cs +++ b/src/NzbDrone.Common.Test/ProcessProviderTests.cs @@ -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 + public class ProcessProviderFixture : TestBase { [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)); } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Common.Test/Radarr.Common.Test.csproj b/src/NzbDrone.Common.Test/Radarr.Common.Test.csproj index 5c5418813..33335d8ae 100644 --- a/src/NzbDrone.Common.Test/Radarr.Common.Test.csproj +++ b/src/NzbDrone.Common.Test/Radarr.Common.Test.csproj @@ -11,9 +11,4 @@ - - - PreserveNewest - - \ No newline at end of file diff --git a/src/NzbDrone.Common.Test/ServiceProviderTests.cs b/src/NzbDrone.Common.Test/ServiceProviderTests.cs index b9cf4e220..893cdf567 100644 --- a/src/NzbDrone.Common.Test/ServiceProviderTests.cs +++ b/src/NzbDrone.Common.Test/ServiceProviderTests.cs @@ -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); + } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Common/CurlSharp.dll.config b/src/NzbDrone.Common/CurlSharp.dll.config deleted file mode 100644 index eadeb9ad7..000000000 --- a/src/NzbDrone.Common/CurlSharp.dll.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/NzbDrone.Common/EnvironmentInfo/BuildInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/BuildInfo.cs index 6a25d5b20..091abf0e9 100644 --- a/src/NzbDrone.Common/EnvironmentInfo/BuildInfo.cs +++ b/src/NzbDrone.Common/EnvironmentInfo/BuildInfo.cs @@ -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; } diff --git a/src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs index 80993e8a6..36d760b2c 100644 --- a/src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs +++ b/src/NzbDrone.Common/EnvironmentInfo/OsInfo.cs @@ -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 diff --git a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs index e60e5b616..f9296800a 100644 --- a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs +++ b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs @@ -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; } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs deleted file mode 100644 index 45574c728..000000000 --- a/src/NzbDrone.Common/Http/Dispatchers/CurlHttpDispatcher.cs +++ /dev/null @@ -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; - } -} diff --git a/src/NzbDrone.Common/Http/Dispatchers/FallbackHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/FallbackHttpDispatcher.cs deleted file mode 100644 index 01b60e012..000000000 --- a/src/NzbDrone.Common/Http/Dispatchers/FallbackHttpDispatcher.cs +++ /dev/null @@ -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 _curlTLSFallbackCache; - - public FallbackHttpDispatcher(ManagedHttpDispatcher managedDispatcher, CurlHttpDispatcher curlDispatcher, ICacheManager cacheManager, IPlatformInfo platformInfo, Logger logger) - { - _managedDispatcher = managedDispatcher; - _curlDispatcher = curlDispatcher; - _platformInfo = platformInfo; - _curlTLSFallbackCache = cacheManager.GetCache(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); - } - } -} diff --git a/src/NzbDrone.Common/Http/HttpProvider.cs b/src/NzbDrone.Common/Http/HttpProvider.cs index 33a8ea45b..aec23cd76 100644 --- a/src/NzbDrone.Common/Http/HttpProvider.cs +++ b/src/NzbDrone.Common/Http/HttpProvider.cs @@ -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; } diff --git a/src/NzbDrone.Common/Http/TlsFailureException.cs b/src/NzbDrone.Common/Http/TlsFailureException.cs index c1dcdd991..a6178a58d 100644 --- a/src/NzbDrone.Common/Http/TlsFailureException.cs +++ b/src/NzbDrone.Common/Http/TlsFailureException.cs @@ -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) { } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Http/UserAgentBuilder.cs b/src/NzbDrone.Common/Http/UserAgentBuilder.cs index 9626cd9f7..b750cffe7 100644 --- a/src/NzbDrone.Common/Http/UserAgentBuilder.cs +++ b/src/NzbDrone.Common/Http/UserAgentBuilder.cs @@ -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)}"; } } } diff --git a/src/NzbDrone.Common/Instrumentation/InitializeLogger.cs b/src/NzbDrone.Common/Instrumentation/InitializeLogger.cs new file mode 100644 index 000000000..e962f5dbb --- /dev/null +++ b/src/NzbDrone.Common/Instrumentation/InitializeLogger.cs @@ -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().FirstOrDefault(); + if (sentryTarget != null) + { + sentryTarget.UpdateScope(_osInfo); + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs index 5829cacb7..f2ba59da4 100644 --- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs +++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs @@ -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; diff --git a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs index fb3d87c88..47085e41f 100644 --- a/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs +++ b/src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs @@ -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) diff --git a/src/NzbDrone.Common/Processes/ProcessProvider.cs b/src/NzbDrone.Common/Processes/ProcessProvider.cs index 2e52895b2..19a2c9a13 100644 --- a/src/NzbDrone.Common/Processes/ProcessProvider.cs +++ b/src/NzbDrone.Common/Processes/ProcessProvider.cs @@ -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() .Any(module => diff --git a/src/NzbDrone.Common/Radarr.Common.csproj b/src/NzbDrone.Common/Radarr.Common.csproj index ae6bae20a..e5b932a32 100644 --- a/src/NzbDrone.Common/Radarr.Common.csproj +++ b/src/NzbDrone.Common/Radarr.Common.csproj @@ -11,9 +11,6 @@ - - - diff --git a/src/NzbDrone.Console/ConsoleApp.cs b/src/NzbDrone.Console/ConsoleApp.cs index f5920f08c..69cfc6393 100644 --- a/src/NzbDrone.Console/ConsoleApp.cs +++ b/src/NzbDrone.Console/ConsoleApp.cs @@ -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(""); diff --git a/src/NzbDrone.Core.Test/Framework/CoreTest.cs b/src/NzbDrone.Core.Test/Framework/CoreTest.cs index 36efd648e..cd7e2f93b 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -30,10 +30,9 @@ namespace NzbDrone.Core.Test.Framework Mocker.SetConstant(new HttpProxySettingsProvider(Mocker.Resolve())); Mocker.SetConstant(new ManagedWebProxyFactory(Mocker.Resolve())); - Mocker.SetConstant(new ManagedHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); - Mocker.SetConstant(new CurlHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), TestLogger)); + Mocker.SetConstant(new ManagedHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); Mocker.SetConstant(new HttpProvider(TestLogger)); - Mocker.SetConstant(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); + Mocker.SetConstant(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); Mocker.SetConstant(new RadarrCloudRequestBuilder()); } diff --git a/src/NzbDrone.Core.Test/Framework/DbTest.cs b/src/NzbDrone.Core.Test/Framework/DbTest.cs index cd2232fa6..92772d4ee 100644 --- a/src/NzbDrone.Core.Test/Framework/DbTest.cs +++ b/src/NzbDrone.Core.Test/Framework/DbTest.cs @@ -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(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); @@ -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); } } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/DotnetVersionCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/DotnetVersionCheckFixture.cs new file mode 100644 index 000000000..fefe82c57 --- /dev/null +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/DotnetVersionCheckFixture.cs @@ -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 + { + private void GivenOutput(string version) + { + WindowsOnly(); + + Mocker.GetMock() + .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(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/HealthCheckFixtureExtensions.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/HealthCheckFixtureExtensions.cs index 5654fa388..7488ff08a 100644 --- a/src/NzbDrone.Core.Test/HealthCheck/Checks/HealthCheckFixtureExtensions.cs +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/HealthCheckFixtureExtensions.cs @@ -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); diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/MonoVersionCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/MonoVersionCheckFixture.cs index 21690a20b..b95063928 100644 --- a/src/NzbDrone.Core.Test/HealthCheck/Checks/MonoVersionCheckFixture.cs +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/MonoVersionCheckFixture.cs @@ -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) diff --git a/src/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs b/src/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs index 4a039e699..8acff9714 100644 --- a/src/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs +++ b/src/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs @@ -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 -// { -// private Mock> _executorA; -// private Mock> _executorB; -// -// [SetUp] -// public void Setup() -// { -// _executorA = new Mock>(); -// _executorB = new Mock>(); -// -// Mocker.GetMock() -// .Setup(c => c.Build(typeof(IExecute))) -// .Returns(_executorA.Object); -// -// Mocker.GetMock() -// .Setup(c => c.Build(typeof(IExecute))) -// .Returns(_executorB.Object); -// -// -// Mocker.GetMock() -// .Setup(c => c.FindExisting(It.IsAny())) -// .Returns(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().Setup(c => c.GetImplementations(typeof(Command))) -// .Returns(new List { typeof(CommandA), typeof(CommandB) }); -// -// Subject.Push(typeof(CommandA).FullName); -// _executorA.Verify(c => c.Execute(It.IsAny()), 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()), Times.Never()); -// } -// -// [Test] -// public void broken_executor_should_throw_the_exception() -// { -// var commandA = new CommandA(); -// -// _executorA.Setup(c => c.Execute(It.IsAny())) -// .Throws(new NotImplementedException()); -// -// Assert.Throws(() => Subject.Push(commandA)); -// } -// -// -// [Test] -// public void broken_executor_should_publish_executed_event() -// { -// var commandA = new CommandA(); -// -// _executorA.Setup(c => c.Execute(It.IsAny())) -// .Throws(new NotImplementedException()); -// -// Assert.Throws(() => Subject.Push(commandA)); -// -// VerifyEventPublished(); -// } -// -// [Test] -// public void should_publish_executed_event_on_success() -// { -// var commandA = new CommandA(); -// Subject.Push(commandA); -// -// VerifyEventPublished(); -// } -// } -// -// public class CommandA : Command -// { -// public CommandA(int id = 0) -// { -// } -// } -// -// public class CommandB : Command -// { -// -// public CommandB() -// { -// } -// } -// -//} \ No newline at end of file +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 + { + private CommandQueue _commandQueue; + private Mock> _executorA; + private Mock> _executorB; + + [SetUp] + public void Setup() + { + _executorA = new Mock>(); + _executorB = new Mock>(); + + Mocker.GetMock() + .Setup(c => c.Build(typeof(IExecute))) + .Returns(_executorA.Object); + + Mocker.GetMock() + .Setup(c => c.Build(typeof(IExecute))) + .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() + .Setup(s => s.Queue(It.IsAny())) + .Returns(_commandQueue.GetConsumingEnumerable); + } + + private void QueueAndWaitForExecution(CommandModel commandModel, bool waitPublish = false) + { + var waitEventComplete = new ManualResetEventSlim(); + var waitEventPublish = new ManualResetEventSlim(); + + Mocker.GetMock() + .Setup(s => s.Complete(It.Is(c => c == commandModel), It.IsAny())) + .Callback(() => waitEventComplete.Set()); + + Mocker.GetMock() + .Setup(s => s.Fail(It.Is(c => c == commandModel), It.IsAny(), It.IsAny())) + .Callback(() => waitEventComplete.Set()); + + Mocker.GetMock() + .Setup(s => s.PublishEvent(It.IsAny())) + .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() + .Verify(v => v.Queue(It.IsAny()), 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()), 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())) + .Throws(new NotImplementedException()); + + Subject.Handle(new ApplicationStartedEvent()); + + QueueAndWaitForExecution(commandModel); + + VerifyEventPublished(); + + 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(); + } + + [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() + .Verify(s => s.Complete(It.Is(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() + .Verify(s => s.Complete(It.Is(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; + } + +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs index e97716bac..6b047f50c 100644 --- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs @@ -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) diff --git a/src/NzbDrone.Core.Test/MovieTests/ShouldRefreshMovieFixture.cs b/src/NzbDrone.Core.Test/MovieTests/ShouldRefreshMovieFixture.cs index e8d9bc037..208515664 100644 --- a/src/NzbDrone.Core.Test/MovieTests/ShouldRefreshMovieFixture.cs +++ b/src/NzbDrone.Core.Test/MovieTests/ShouldRefreshMovieFixture.cs @@ -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(); diff --git a/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs b/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs index cdf7e0f47..5e336420b 100644 --- a/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs +++ b/src/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs @@ -37,6 +37,7 @@ namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests public void Setup() { Mocker.GetMock().SetupGet(s => s.RecycleBin).Returns(RecycleBin); + Mocker.GetMock().SetupGet(s => s.RecycleBinCleanupDays).Returns(7); Mocker.GetMock().Setup(s => s.GetDirectories(RecycleBin)) .Returns(new [] { @"C:\Test\RecycleBin\Folder1", @"C:\Test\RecycleBin\Folder2", @"C:\Test\RecycleBin\Folder3" }); diff --git a/src/NzbDrone.Core.Test/Radarr.Core.Test.csproj b/src/NzbDrone.Core.Test/Radarr.Core.Test.csproj index ca456f446..c537fe15e 100644 --- a/src/NzbDrone.Core.Test/Radarr.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/Radarr.Core.Test.csproj @@ -15,6 +15,9 @@ Files\1024.png PreserveNewest + + ..\Libraries\Sqlite\System.Data.SQLite.dll + PreserveNewest diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs index 8f26183d3..bd04a2b51 100644 --- a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs +++ b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs @@ -87,6 +87,16 @@ namespace NzbDrone.Core.Test.UpdateTests .Returns(true); } + [Test] + public void should_not_update_if_inside_docker() + { + Mocker.GetMock().Setup(x => x.IsDocker).Returns(true); + + Subject.Invoking(x => x.Execute(new ApplicationUpdateCommand())) + .Should().Throw() + .WithMessage("Updating is disabled inside a docker container. Please update the container image."); + } + [Test] public void should_delete_sandbox_before_update_if_folder_exists() { diff --git a/src/NzbDrone.Core/Analytics/AnalyticsService.cs b/src/NzbDrone.Core/Analytics/AnalyticsService.cs index dfd9d8980..94dedf497 100644 --- a/src/NzbDrone.Core/Analytics/AnalyticsService.cs +++ b/src/NzbDrone.Core/Analytics/AnalyticsService.cs @@ -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() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending }); + var monthAgo = DateTime.UtcNow.AddMonths(-1); + + return lastRecord.Records.Any(v => v.Date > monthAgo); + } + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index dddf3da82..c41936d61 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -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) diff --git a/src/NzbDrone.Core/Datastore/CorruptDatabaseException.cs b/src/NzbDrone.Core/Datastore/CorruptDatabaseException.cs index 1d8b6696d..4493ca1b5 100644 --- a/src/NzbDrone.Core/Datastore/CorruptDatabaseException.cs +++ b/src/NzbDrone.Core/Datastore/CorruptDatabaseException.cs @@ -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) { diff --git a/src/NzbDrone.Core/Datastore/Database.cs b/src/NzbDrone.Core/Datastore/Database.cs index 2a8417ed6..1ff4fba49 100644 --- a/src/NzbDrone.Core/Datastore/Database.cs +++ b/src/NzbDrone.Core/Datastore/Database.cs @@ -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 diff --git a/src/NzbDrone.Core/Datastore/DbFactory.cs b/src/NzbDrone.Core/Datastore/DbFactory.cs index fd41b8145..07e406595 100644 --- a/src/NzbDrone.Core/Datastore/DbFactory.cs +++ b/src/NzbDrone.Core/Datastore/DbFactory.cs @@ -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"); + } } } } diff --git a/src/NzbDrone.Core/Datastore/LogDatabase.cs b/src/NzbDrone.Core/Datastore/LogDatabase.cs index c454e9997..e1c9c3a20 100644 --- a/src/NzbDrone.Core/Datastore/LogDatabase.cs +++ b/src/NzbDrone.Core/Datastore/LogDatabase.cs @@ -24,6 +24,8 @@ namespace NzbDrone.Core.Datastore public Version Version => _database.Version; + public int Migration => _database.Migration; + public void Vacuum() { _database.Vacuum(); diff --git a/src/NzbDrone.Core/Datastore/MainDatabase.cs b/src/NzbDrone.Core/Datastore/MainDatabase.cs index 8ce09eaf2..869c3a6da 100644 --- a/src/NzbDrone.Core/Datastore/MainDatabase.cs +++ b/src/NzbDrone.Core/Datastore/MainDatabase.cs @@ -24,6 +24,8 @@ namespace NzbDrone.Core.Datastore public Version Version => _database.Version; + public int Migration => _database.Migration; + public void Vacuum() { _database.Vacuum(); diff --git a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs index 793725e9f..2e1a2d2ab 100644 --- a/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs +++ b/src/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs @@ -57,7 +57,8 @@ namespace NzbDrone.Core.Datastore.Migration.Framework SQLiteConnection.ClearAllPools(); throw; } - + + processor.Dispose(); sw.Stop(); diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs index 5d44be05f..28715fc12 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/Deluge.cs @@ -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: diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DotnetVersionCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DotnetVersionCheck.cs new file mode 100644 index 000000000..60787d808 --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/DotnetVersionCheck.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/HealthCheck/Checks/MonoTlsCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/MonoTlsCheck.cs index e4ce38beb..fffcc8c14 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/MonoTlsCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/MonoTlsCheck.cs @@ -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()); diff --git a/src/NzbDrone.Core/HealthCheck/Checks/MonoVersionCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/MonoVersionCheck.cs index faf2152d7..0bdc8466c 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/MonoVersionCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/MonoVersionCheck.cs @@ -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; diff --git a/src/NzbDrone.Core/HealthCheck/HealthCheck.cs b/src/NzbDrone.Core/HealthCheck/HealthCheck.cs index c02ead7a8..c410878be 100644 --- a/src/NzbDrone.Core/HealthCheck/HealthCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/HealthCheck.cs @@ -46,7 +46,8 @@ namespace NzbDrone.Core.HealthCheck public enum HealthCheckResult { Ok = 0, - Warning = 1, - Error = 2 + Notice = 1, + Warning = 2, + Error = 3 } } diff --git a/src/NzbDrone.Core/Indexers/Rarbg/RarbgRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Rarbg/RarbgRequestGenerator.cs index 2df0b3825..79e750c28 100644 --- a/src/NzbDrone.Core/Indexers/Rarbg/RarbgRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Rarbg/RarbgRequestGenerator.cs @@ -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()); } diff --git a/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs b/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs index a79ba682a..27a7a9143 100644 --- a/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs +++ b/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs @@ -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().FirstOrDefault(); + if (sentryTarget != null) + { + sentryTarget.SentryEnabled = RuntimeInfo.IsProduction && _configFileProvider.AnalyticsEnabled || RuntimeInfo.IsDevelopment; + sentryTarget.FilterEvents = _configFileProvider.FilterSentryEvents; + } + } + private List GetLogLevels() { return new List diff --git a/src/NzbDrone.Core/Instrumentation/ReconfigureSentry.cs b/src/NzbDrone.Core/Instrumentation/ReconfigureSentry.cs new file mode 100644 index 000000000..034127e81 --- /dev/null +++ b/src/NzbDrone.Core/Instrumentation/ReconfigureSentry.cs @@ -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 + { + 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().FirstOrDefault(); + if (sentryTarget != null) + { + sentryTarget.UpdateScope(_database.Version, _database.Migration, _configFileProvider.Branch, _platformInfo); + } + } + + public void HandleAsync(ApplicationStartedEvent message) + { + Reconfigure(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs b/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs index 8d26a883b..bf0e848f7 100644 --- a/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs +++ b/src/NzbDrone.Core/Notifications/Boxcar/BoxcarProxy.cs @@ -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); diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvProxy.cs b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvProxy.cs index b13ef180b..c4fafe006 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvProxy.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvProxy.cs @@ -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; diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs index f9b96ca56..8866775c6 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexTv/PlexTvService.cs @@ -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()); diff --git a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs index b4bee754d..8bf9cd59d 100644 --- a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs +++ b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServerProxy.cs @@ -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()) diff --git a/src/NzbDrone.Core/Notifications/Prowl/ProwlService.cs b/src/NzbDrone.Core/Notifications/Prowl/ProwlService.cs index e6897bb38..03a3d596b 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/ProwlService.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/ProwlService.cs @@ -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, diff --git a/src/NzbDrone.Core/Rest/RestClientFactory.cs b/src/NzbDrone.Core/Rest/RestClientFactory.cs index 2cc3d15aa..99a3d1060 100644 --- a/src/NzbDrone.Core/Rest/RestClientFactory.cs +++ b/src/NzbDrone.Core/Rest/RestClientFactory.cs @@ -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})" }; diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs index 0fe04070e..a7d8e6684 100644 --- a/src/NzbDrone.Core/Update/InstallUpdateService.cs +++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs @@ -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."); diff --git a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs index 84d0c5057..340b2056c 100644 --- a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs +++ b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs @@ -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(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(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>(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>(request.Build()); return updates.Resource; } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Host.Test/RouterTest.cs b/src/NzbDrone.Host.Test/RouterTest.cs index c7a07b6fc..b44807fdb 100644 --- a/src/NzbDrone.Host.Test/RouterTest.cs +++ b/src/NzbDrone.Host.Test/RouterTest.cs @@ -29,7 +29,6 @@ namespace NzbDrone.App.Test Mocker.GetMock() .Setup(c => c.SpawnNewProcess("sc.exe", It.IsAny(), null, true)); - Mocker.GetMock().SetupGet(c => c.IsUserInteractive).Returns(true); Subject.Route(ApplicationModes.InstallService); diff --git a/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs b/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs new file mode 100644 index 000000000..f9bbd6ac5 --- /dev/null +++ b/src/NzbDrone.Host/AccessControl/RemoteAccessAdapter.cs @@ -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(); + } + } +} diff --git a/src/NzbDrone.Host/AccessControl/RemoteAccessException.cs b/src/NzbDrone.Host/AccessControl/RemoteAccessException.cs new file mode 100644 index 000000000..be7917f90 --- /dev/null +++ b/src/NzbDrone.Host/AccessControl/RemoteAccessException.cs @@ -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) + { + } + } +} diff --git a/src/NzbDrone.Host/ApplicationModes.cs b/src/NzbDrone.Host/ApplicationModes.cs index 3495d8688..196aecde6 100644 --- a/src/NzbDrone.Host/ApplicationModes.cs +++ b/src/NzbDrone.Host/ApplicationModes.cs @@ -7,5 +7,6 @@ InstallService, UninstallService, Service, + RegisterUrl } } diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs index c29f7c36f..4c00d2e75 100644 --- a/src/NzbDrone.Host/Bootstrap.cs +++ b/src/NzbDrone.Host/Bootstrap.cs @@ -29,6 +29,7 @@ namespace Radarr.Host } _container = MainAppContainerBuilder.BuildContainer(startupContext); + _container.Resolve().Initialize(); _container.Resolve().Register(); _container.Resolve().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; diff --git a/src/NzbDrone.Host/MainAppContainerBuilder.cs b/src/NzbDrone.Host/MainAppContainerBuilder.cs index 3bbc0cc52..5dab4fb81 100644 --- a/src/NzbDrone.Host/MainAppContainerBuilder.cs +++ b/src/NzbDrone.Host/MainAppContainerBuilder.cs @@ -31,7 +31,6 @@ namespace Radarr.Host AutoRegisterImplementations(); Container.Register(); - Container.Register(); } } } diff --git a/src/NzbDrone.Host/Owin/OwinHostController.cs b/src/NzbDrone.Host/Owin/OwinHostController.cs index a2000974b..5beb8c4c8 100644 --- a/src/NzbDrone.Host/Owin/OwinHostController.cs +++ b/src/NzbDrone.Host/Owin/OwinHostController.cs @@ -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"); } - - - } } \ No newline at end of file diff --git a/src/NzbDrone.Host/Owin/OwinServiceProvider.cs b/src/NzbDrone.Host/Owin/OwinServiceProvider.cs index 80d3e9a83..29baa908e 100644 --- a/src/NzbDrone.Host/Owin/OwinServiceProvider.cs +++ b/src/NzbDrone.Host/Owin/OwinServiceProvider.cs @@ -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)) { diff --git a/src/NzbDrone.Host/Router.cs b/src/NzbDrone.Host/Router.cs index e617fb50d..98f0057d1 100644 --- a/src/NzbDrone.Host/Router.cs +++ b/src/NzbDrone.Host/Router.cs @@ -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 } } } - - } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/ApiTests/BlacklistFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/BlacklistFixture.cs index b20d7b886..7443b8cf1 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/BlacklistFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/BlacklistFixture.cs @@ -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" diff --git a/src/NzbDrone.Integration.Test/ApiTests/CalendarFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/CalendarFixture.cs index cc2a13619..2d66accf7 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/CalendarFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/CalendarFixture.cs @@ -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; diff --git a/src/NzbDrone.Integration.Test/ApiTests/CommandFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/CommandFixture.cs index 2c3da675c..719d1c7ac 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/CommandFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/CommandFixture.cs @@ -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); } diff --git a/src/NzbDrone.Integration.Test/ApiTests/DiskSpaceFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/DiskSpaceFixture.cs index 527f18346..e43e0859e 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/DiskSpaceFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/DiskSpaceFixture.cs @@ -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 diff --git a/src/NzbDrone.Integration.Test/ApiTests/DownloadClientFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/DownloadClientFixture.cs index 5032fd3c6..a95d44641 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/DownloadClientFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/DownloadClientFixture.cs @@ -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(); diff --git a/src/NzbDrone.Integration.Test/ApiTests/MovieEditorFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/MovieEditorFixture.cs index f46553294..a90932aaf 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/MovieEditorFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/MovieEditorFixture.cs @@ -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(); } } } diff --git a/src/NzbDrone.Integration.Test/ApiTests/MovieFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/MovieFixture.cs index 3b6343ac8..e0a632c18 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/MovieFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/MovieFixture.cs @@ -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(); 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)] diff --git a/src/NzbDrone.Integration.Test/ApiTests/NamingConfigFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/NamingConfigFixture.cs index c203e6576..df59cc90d 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/NamingConfigFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/NamingConfigFixture.cs @@ -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); diff --git a/src/NzbDrone.Integration.Test/ApiTests/NotificationFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/NotificationFixture.cs index c5ebfa8ef..d5abc9930 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/NotificationFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/NotificationFixture.cs @@ -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); diff --git a/src/NzbDrone.Integration.Test/ApiTests/ReleaseFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/ReleaseFixture.cs index cfad5038e..151d7f3d1 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/ReleaseFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/ReleaseFixture.cs @@ -1,6 +1,6 @@ using FluentAssertions; using NUnit.Framework; -using NzbDrone.Api.Indexers; +using Radarr.Api.V2.Indexers; using System.Linq; using System.Net; diff --git a/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs index 5133a5da7..c8332e4cb 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/RootFolderFixture.cs @@ -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 { diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs deleted file mode 100644 index 5b3c0a06f..000000000 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs deleted file mode 100644 index 62e594381..000000000 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/Client/ClientBase.cs b/src/NzbDrone.Integration.Test/Client/ClientBase.cs index 28c7a1d7a..3811ae1c8 100644 --- a/src/NzbDrone.Integration.Test/Client/ClientBase.cs +++ b/src/NzbDrone.Integration.Test/Client/ClientBase.cs @@ -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(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(response.Content); + return response.Content; + } + + public T Execute(IRestRequest request, HttpStatusCode statusCode) where T : class, new() + { + var content = Execute(request, statusCode); + + return Json.Deserialize(content); } private static void AssertDisableCache(IList headers) diff --git a/src/NzbDrone.Integration.Test/Client/CommandClient.cs b/src/NzbDrone.Integration.Test/Client/CommandClient.cs index 047427a98..59becc84d 100644 --- a/src/NzbDrone.Integration.Test/Client/CommandClient.cs +++ b/src/NzbDrone.Integration.Test/Client/CommandClient.cs @@ -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 + 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 { public CommandClient(IRestClient restClient, string apiKey) - : base(restClient, apiKey) + : base(restClient, apiKey, "command") { } - public CommandResource PostAndWait(CommandResource command) + public SimpleCommandResource PostAndWait(T command) where T : Command, new() { - var result = Post(command); + var request = BuildRequest(); + request.AddBody(command); + var result = Post(request); result.Id.Should().NotBe(0); for (var i = 0; i < 50; i++) diff --git a/src/NzbDrone.Integration.Test/Client/DownloadClientClient.cs b/src/NzbDrone.Integration.Test/Client/DownloadClientClient.cs index e31e38748..74797e56d 100644 --- a/src/NzbDrone.Integration.Test/Client/DownloadClientClient.cs +++ b/src/NzbDrone.Integration.Test/Client/DownloadClientClient.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using NzbDrone.Api.DownloadClient; +using Radarr.Api.V2.DownloadClient; using RestSharp; namespace NzbDrone.Integration.Test.Client diff --git a/src/NzbDrone.Integration.Test/Client/IndexerClient.cs b/src/NzbDrone.Integration.Test/Client/IndexerClient.cs index 9d6f9b974..cf231b1b9 100644 --- a/src/NzbDrone.Integration.Test/Client/IndexerClient.cs +++ b/src/NzbDrone.Integration.Test/Client/IndexerClient.cs @@ -1,4 +1,4 @@ -using NzbDrone.Api.Indexers; +using Radarr.Api.V2.Indexers; using RestSharp; namespace NzbDrone.Integration.Test.Client diff --git a/src/NzbDrone.Integration.Test/Client/LogsClient.cs b/src/NzbDrone.Integration.Test/Client/LogsClient.cs new file mode 100644 index 000000000..b64ec2971 --- /dev/null +++ b/src/NzbDrone.Integration.Test/Client/LogsClient.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/Client/MovieClient.cs b/src/NzbDrone.Integration.Test/Client/MovieClient.cs index 38fe8c325..1a57f045a 100644 --- a/src/NzbDrone.Integration.Test/Client/MovieClient.cs +++ b/src/NzbDrone.Integration.Test/Client/MovieClient.cs @@ -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>(request); } - public List Editor(List movie) + public List Editor(MovieEditorResource movie) { var request = BuildRequest("editor"); request.AddJsonBody(movie); diff --git a/src/NzbDrone.Integration.Test/Client/NotificationClient.cs b/src/NzbDrone.Integration.Test/Client/NotificationClient.cs index 6f0f06eb5..e3c0790eb 100644 --- a/src/NzbDrone.Integration.Test/Client/NotificationClient.cs +++ b/src/NzbDrone.Integration.Test/Client/NotificationClient.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using NzbDrone.Api.Notifications; +using Radarr.Api.V2.Notifications; using RestSharp; namespace NzbDrone.Integration.Test.Client diff --git a/src/NzbDrone.Integration.Test/Client/ReleaseClient.cs b/src/NzbDrone.Integration.Test/Client/ReleaseClient.cs index 46a6db839..6b189961f 100644 --- a/src/NzbDrone.Integration.Test/Client/ReleaseClient.cs +++ b/src/NzbDrone.Integration.Test/Client/ReleaseClient.cs @@ -1,4 +1,4 @@ -using NzbDrone.Api.Indexers; +using Radarr.Api.V2.Indexers; using RestSharp; namespace NzbDrone.Integration.Test.Client diff --git a/src/NzbDrone.Integration.Test/HttpLogFixture.cs b/src/NzbDrone.Integration.Test/HttpLogFixture.cs index be474ad0a..ba2d4cf35 100644 --- a/src/NzbDrone.Integration.Test/HttpLogFixture.cs +++ b/src/NzbDrone.Integration.Test/HttpLogFixture.cs @@ -1,8 +1,7 @@ -using System.IO; +using System; using System.Linq; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Api.Movies; namespace NzbDrone.Integration.Test { @@ -18,16 +17,18 @@ namespace NzbDrone.Integration.Test var resultGet = Movies.All(); - var logFile = Path.Combine(_runner.AppData, "logs", "radarr.trace.txt"); - var logLines = File.ReadAllLines(logFile); + var logFile = "radarr.trace.txt"; + var logLines = Logs.GetLogFileLines(logFile); - var resultPost = Movies.InvalidPost(new MovieResource()); + var resultPost = Movies.InvalidPost(new Radarr.Api.V2.Movies.MovieResource()); - logLines = File.ReadAllLines(logFile).Skip(logLines.Length).ToArray(); + // Skip 2 and 1 to ignore the logs endpoint + logLines = Logs.GetLogFileLines(logFile).Skip(logLines.Length + 2).ToArray(); + Array.Resize(ref logLines, logLines.Length - 1); - logLines.Should().Contain(v => v.Contains("|Trace|Http|Req")); - logLines.Should().Contain(v => v.Contains("|Trace|Http|Res")); - logLines.Should().Contain(v => v.Contains("|Debug|Api|")); + logLines.Should().Contain(v => v.Contains("|Trace|Http|Req") && v.Contains("/api/v2/movie/")); + logLines.Should().Contain(v => v.Contains("|Trace|Http|Res") && v.Contains("/api/v2/movie/: 400.BadRequest")); + logLines.Should().Contain(v => v.Contains("|Debug|Api|") && v.Contains("/api/v2/movie/: 400.BadRequest")); } } -} +} \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/IntegrationTest.cs b/src/NzbDrone.Integration.Test/IntegrationTest.cs index 2e6020ac8..a23b22676 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTest.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTest.cs @@ -26,10 +26,11 @@ namespace NzbDrone.Integration.Test protected override void InitializeTestTarget() { - Indexers.Post(new Api.Indexers.IndexerResource + Indexers.Post(new Radarr.Api.V2.Indexers.IndexerResource { EnableRss = false, - EnableSearch = false, + EnableInteractiveSearch = false, + EnableAutomaticSearch = false, ConfigContract = nameof(NewznabSettings), Implementation = nameof(Newznab), Name = "NewznabTest", diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index e51bed674..389327c59 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -10,16 +11,16 @@ using NLog; using NLog.Config; using NLog.Targets; using NUnit.Framework; -using NzbDrone.Api.Blacklist; -using NzbDrone.Api.Commands; -using NzbDrone.Api.Config; -using NzbDrone.Api.DownloadClient; -using NzbDrone.Api.MovieFiles; -using NzbDrone.Api.History; -using NzbDrone.Api.Profiles; -using NzbDrone.Api.RootFolders; -using NzbDrone.Api.Movies; -using NzbDrone.Api.Tags; +using Radarr.Api.V2.Blacklist; +using Radarr.Api.V2.Commands; +using Radarr.Api.V2.Config; +using Radarr.Api.V2.DownloadClient; +using Radarr.Api.V2.MovieFiles; +using Radarr.Api.V2.History; +using Radarr.Api.V2.Profiles.Quality; +using Radarr.Api.V2.RootFolders; +using Radarr.Api.V2.Movies; +using Radarr.Api.V2.Tags; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Serializer; using NzbDrone.Core.MediaFiles.Events; @@ -43,9 +44,10 @@ namespace NzbDrone.Integration.Test public ClientBase History; public ClientBase HostConfig; public IndexerClient Indexers; + public LogsClient Logs; public ClientBase NamingConfig; public NotificationClient Notifications; - public ClientBase Profiles; + public ClientBase Profiles; public ReleaseClient Releases; public ClientBase RootFolders; public MovieClient Movies; @@ -63,11 +65,9 @@ namespace NzbDrone.Integration.Test new StartupContext(); LogManager.Configuration = new LoggingConfiguration(); - var consoleTarget = new ConsoleTarget { Layout = "${level}: ${message} ${exception}", DetectConsoleAvailable = true}; + var consoleTarget = new ConsoleTarget { Layout = "${level}: ${message} ${exception}" }; LogManager.Configuration.AddTarget(consoleTarget.GetType().Name, consoleTarget); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget)); - - LogManager.ReconfigExistingLoggers(); } public string TempDirectory { get; private set; } @@ -94,7 +94,7 @@ namespace NzbDrone.Integration.Test protected virtual void InitRestClients() { - RestClient = new RestClient(RootUrl + "api/"); + RestClient = new RestClient(RootUrl + "api/v2/"); RestClient.AddDefaultHeader("Authentication", ApiKey); RestClient.AddDefaultHeader("X-Api-Key", ApiKey); @@ -104,9 +104,10 @@ namespace NzbDrone.Integration.Test History = new ClientBase(RestClient, ApiKey); HostConfig = new ClientBase(RestClient, ApiKey, "config/host"); Indexers = new IndexerClient(RestClient, ApiKey); + Logs = new LogsClient(RestClient, ApiKey); NamingConfig = new ClientBase(RestClient, ApiKey, "config/naming"); Notifications = new NotificationClient(RestClient, ApiKey); - Profiles = new ClientBase(RestClient, ApiKey); + Profiles = new ClientBase(RestClient, ApiKey); Releases = new ReleaseClient(RestClient, ApiKey); RootFolders = new ClientBase(RestClient, ApiKey); Movies = new MovieClient(RestClient, ApiKey); @@ -124,7 +125,7 @@ namespace NzbDrone.Integration.Test [SetUp] public void IntegrationSetUp() { - TempDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "_test_" + DateTime.UtcNow.Ticks); + TempDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "_test_" + Process.GetCurrentProcess().Id + "_" + DateTime.UtcNow.Ticks); // Wait for things to get quiet, otherwise the previous test might influence the current one. Commands.WaitAll(); @@ -148,6 +149,17 @@ namespace NzbDrone.Integration.Test _signalrConnection = null; _signalRReceived = new List(); } + + if (Directory.Exists(TempDirectory)) + { + try + { + Directory.Delete(TempDirectory, true); + } + catch + { + } + } } public string GetTempDirectory(params string[] args) @@ -213,7 +225,7 @@ namespace NzbDrone.Integration.Test { var lookup = Movies.Lookup("tmdb:" + tmdbid); var movie = lookup.First(); - movie.ProfileId = 1; + movie.QualityProfileId = 1; movie.Path = Path.Combine(MovieRootFolder, movie.Title); movie.Monitored = true; movie.AddOptions = new Core.Movies.AddMovieOptions(); @@ -268,7 +280,7 @@ namespace NzbDrone.Integration.Test //File.Copy(sourcePath, path); File.WriteAllText(path, "Fake Movie"); - Commands.PostAndWait(new CommandResource { Name = "refreshmovie", Body = new RefreshMovieCommand(movie.Id) }); + Commands.PostAndWait(new RefreshMovieCommand(movie.Id)); Commands.WaitAll(); result = Movies.Get(movie.Id); @@ -279,13 +291,13 @@ namespace NzbDrone.Integration.Test return result.MovieFile; } - public ProfileResource EnsureProfileCutoff(int profileId, Quality cutoff) + public QualityProfileResource EnsureProfileCutoff(int profileId, Quality cutoff) { var profile = Profiles.Get(profileId); - if (profile.Cutoff != cutoff) + if (profile.Cutoff != cutoff.Id) { - profile.Cutoff = cutoff; + profile.Cutoff = cutoff.Id; profile = Profiles.Put(profile); } @@ -324,8 +336,8 @@ namespace NzbDrone.Integration.Test schema.Enable = enabled; 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"); client = DownloadClients.Post(schema); } diff --git a/src/NzbDrone.Integration.Test/Radarr.Integration.Test.csproj b/src/NzbDrone.Integration.Test/Radarr.Integration.Test.csproj index c8e920721..9b760c349 100644 --- a/src/NzbDrone.Integration.Test/Radarr.Integration.Test.csproj +++ b/src/NzbDrone.Integration.Test/Radarr.Integration.Test.csproj @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/src/NzbDrone.Mono.Test/EnvironmentInfo/MonoPlatformInfoFixture.cs b/src/NzbDrone.Mono.Test/EnvironmentInfo/MonoPlatformInfoFixture.cs index a1bcfd1cd..80fce9cc1 100644 --- a/src/NzbDrone.Mono.Test/EnvironmentInfo/MonoPlatformInfoFixture.cs +++ b/src/NzbDrone.Mono.Test/EnvironmentInfo/MonoPlatformInfoFixture.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo [Test] public void should_get_framework_version() { - Subject.Version.Major.Should().BeOneOf(4, 5); + Subject.Version.Major.Should().BeOneOf(4, 5, 6); if (Subject.Version.Major == 4) { Subject.Version.Minor.Should().BeOneOf(0, 5, 6); diff --git a/src/NzbDrone.Mono.Test/EnvironmentInfo/ReleaseFileVersionAdapterFixture.cs b/src/NzbDrone.Mono.Test/EnvironmentInfo/ReleaseFileVersionAdapterFixture.cs index f13c58bba..c4b477c5e 100644 --- a/src/NzbDrone.Mono.Test/EnvironmentInfo/ReleaseFileVersionAdapterFixture.cs +++ b/src/NzbDrone.Mono.Test/EnvironmentInfo/ReleaseFileVersionAdapterFixture.cs @@ -8,7 +8,7 @@ using NzbDrone.Test.Common; namespace NzbDrone.Mono.Test.EnvironmentInfo { [TestFixture] - [Platform("Mono")] + [Platform("Linux")] public class ReleaseFileVersionAdapterFixture : TestBase { [SetUp] diff --git a/src/NzbDrone.Mono.Test/EnvironmentInfo/VersionAdapters/ReleaseFileVersionAdapterFixture.cs b/src/NzbDrone.Mono.Test/EnvironmentInfo/VersionAdapters/ReleaseFileVersionAdapterFixture.cs index 4f1bee23f..a6937868e 100644 --- a/src/NzbDrone.Mono.Test/EnvironmentInfo/VersionAdapters/ReleaseFileVersionAdapterFixture.cs +++ b/src/NzbDrone.Mono.Test/EnvironmentInfo/VersionAdapters/ReleaseFileVersionAdapterFixture.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Mono.Test.EnvironmentInfo.VersionAdapters { [Test] [IntegrationTest] - [Platform("Mono")] + [Platform("Linux")] public void should_get_version_info_from_actual_linux() { Mocker.SetConstant(Mocker.Resolve()); diff --git a/src/NzbDrone.Mono/EnvironmentInfo/MonoPlatformInfo.cs b/src/NzbDrone.Mono/EnvironmentInfo/MonoPlatformInfo.cs index 0676b69cc..ca487eedd 100644 --- a/src/NzbDrone.Mono/EnvironmentInfo/MonoPlatformInfo.cs +++ b/src/NzbDrone.Mono/EnvironmentInfo/MonoPlatformInfo.cs @@ -30,7 +30,6 @@ namespace NzbDrone.Mono.EnvironmentInfo if (versionMatch.Success) { runTimeVersion = new Version(versionMatch.Groups["version"].Value); - Environment.SetEnvironmentVariable("RUNTIME_VERSION", runTimeVersion.ToString()); } } } diff --git a/src/NzbDrone.Test.Common/ExceptionVerification.cs b/src/NzbDrone.Test.Common/ExceptionVerification.cs index b86220f7c..a60d1297b 100644 --- a/src/NzbDrone.Test.Common/ExceptionVerification.cs +++ b/src/NzbDrone.Test.Common/ExceptionVerification.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using NLog; using NLog.Targets; using NUnit.Framework; @@ -11,17 +12,27 @@ namespace NzbDrone.Test.Common { private static List _logs = new List(); + private static ManualResetEventSlim _waitEvent = new ManualResetEventSlim(); + protected override void Write(LogEventInfo logEvent) { - if (logEvent.Level >= LogLevel.Warn) + lock (_logs) { - _logs.Add(logEvent); + if (logEvent.Level >= LogLevel.Warn) + { + _logs.Add(logEvent); + _waitEvent.Set(); + } } } public static void Reset() { - _logs = new List(); + lock (_logs) + { + _logs.Clear(); + _waitEvent.Reset(); + } } public static void AssertNoUnexpectedLogs() @@ -47,6 +58,29 @@ namespace NzbDrone.Test.Common return errors; } + public static void WaitForErrors(int count, int msec) + { + while (true) + { + lock (_logs) + { + var levelLogs = _logs.Where(l => l.Level == LogLevel.Error).ToList(); + + if (levelLogs.Count >= count) + { + break; + } + + _waitEvent.Reset(); + } + + if (!_waitEvent.Wait(msec)) + break; + } + + Expected(LogLevel.Error, count); + } + public static void ExpectedErrors(int count) { Expected(LogLevel.Error, count); @@ -74,50 +108,62 @@ namespace NzbDrone.Test.Common public static void MarkInconclusive(Type exception) { - var inconclusiveLogs = _logs.Where(l => l.Exception != null && l.Exception.GetType() == exception).ToList(); - - if (inconclusiveLogs.Any()) + lock (_logs) { - inconclusiveLogs.ForEach(c => _logs.Remove(c)); - Assert.Inconclusive(GetLogsString(inconclusiveLogs)); + var inconclusiveLogs = _logs.Where(l => l.Exception != null && l.Exception.GetType() == exception).ToList(); + + if (inconclusiveLogs.Any()) + { + inconclusiveLogs.ForEach(c => _logs.Remove(c)); + Assert.Inconclusive(GetLogsString(inconclusiveLogs)); + } } } public static void MarkInconclusive(string text) { - var inconclusiveLogs = _logs.Where(l => l.FormattedMessage.ToLower().Contains(text.ToLower())).ToList(); - - if (inconclusiveLogs.Any()) + lock (_logs) { - inconclusiveLogs.ForEach(c => _logs.Remove(c)); - Assert.Inconclusive(GetLogsString(inconclusiveLogs)); + var inconclusiveLogs = _logs.Where(l => l.FormattedMessage.ToLower().Contains(text.ToLower())).ToList(); + + if (inconclusiveLogs.Any()) + { + inconclusiveLogs.ForEach(c => _logs.Remove(c)); + Assert.Inconclusive(GetLogsString(inconclusiveLogs)); + } } } private static void Expected(LogLevel level, int count) { - var levelLogs = _logs.Where(l => l.Level == level).ToList(); - - if (levelLogs.Count != count) + lock (_logs) { + var levelLogs = _logs.Where(l => l.Level == level).ToList(); - var message = string.Format("{0} {1}(s) were expected but {2} were logged.\n\r{3}", - count, level, levelLogs.Count, GetLogsString(levelLogs)); + if (levelLogs.Count != count) + { - message = "\n\r****************************************************************************************\n\r" - + message + - "\n\r****************************************************************************************"; + var message = string.Format("{0} {1}(s) were expected but {2} were logged.\n\r{3}", + count, level, levelLogs.Count, GetLogsString(levelLogs)); - Assert.Fail(message); + message = "\n\r****************************************************************************************\n\r" + + message + + "\n\r****************************************************************************************"; + + Assert.Fail(message); + } + + levelLogs.ForEach(c => _logs.Remove(c)); } - - levelLogs.ForEach(c => _logs.Remove(c)); } private static void Ignore(LogLevel level) { - var levelLogs = _logs.Where(l => l.Level == level).ToList(); - levelLogs.ForEach(c => _logs.Remove(c)); + lock (_logs) + { + var levelLogs = _logs.Where(l => l.Level == level).ToList(); + levelLogs.ForEach(c => _logs.Remove(c)); + } } } } \ No newline at end of file diff --git a/src/NzbDrone.Test.Common/LoggingTest.cs b/src/NzbDrone.Test.Common/LoggingTest.cs index b8aba6dcd..3b673390b 100644 --- a/src/NzbDrone.Test.Common/LoggingTest.cs +++ b/src/NzbDrone.Test.Common/LoggingTest.cs @@ -1,6 +1,8 @@ using NLog; using NLog.Config; using NLog.Targets; +using System; +using System.IO; using NUnit.Framework; using NUnit.Framework.Interfaces; using NzbDrone.Common.EnvironmentInfo; @@ -20,9 +22,19 @@ namespace NzbDrone.Test.Common if (LogManager.Configuration == null || LogManager.Configuration.AllTargets.None(c => c is ExceptionVerification)) { LogManager.Configuration = new LoggingConfiguration(); - var consoleTarget = new ConsoleTarget { Layout = "${level}: ${message} ${exception}" }; - LogManager.Configuration.AddTarget(consoleTarget.GetType().Name, consoleTarget); - LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget)); + + var logOutput = TestLogOutput.Console; + Enum.TryParse(Environment.GetEnvironmentVariable("RADARR_TESTS_LOG_OUTPUT"), out logOutput); + + switch (logOutput) + { + case TestLogOutput.Console: + RegisterConsoleLogger(); + break; + case TestLogOutput.File: + RegisterFileLogger(); + break; + } RegisterExceptionVerification(); @@ -30,6 +42,32 @@ namespace NzbDrone.Test.Common } } + private static void RegisterConsoleLogger() + { + var consoleTarget = new ConsoleTarget { Layout = "${level}: ${message} ${exception}" }; + LogManager.Configuration.AddTarget(consoleTarget.GetType().Name, consoleTarget); + LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget)); + } + + private static void RegisterFileLogger() + { + const string layout = @"${level}|${message}${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}"; + + var fileTarget = new FileTarget(); + + fileTarget.Name = "Test File Logger"; + fileTarget.FileName = Path.Combine(TestContext.CurrentContext.WorkDirectory, "TestLog.txt"); + fileTarget.AutoFlush = false; + fileTarget.KeepFileOpen = true; + fileTarget.ConcurrentWrites = true; + fileTarget.ConcurrentWriteAttemptDelay = 50; + fileTarget.ConcurrentWriteAttempts = 10; + fileTarget.Layout = layout; + + LogManager.Configuration.AddTarget(fileTarget.GetType().Name, fileTarget); + LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget)); + } + private static void RegisterExceptionVerification() { var exceptionVerification = new ExceptionVerification(); @@ -42,6 +80,7 @@ namespace NzbDrone.Test.Common { InitLogging(); ExceptionVerification.Reset(); + TestLogger.Info("--- Start: {0} ---", TestContext.CurrentContext.Test.FullName); } [TearDown] @@ -53,6 +92,8 @@ namespace NzbDrone.Test.Common { ExceptionVerification.AssertNoUnexpectedLogs(); } + + TestLogger.Info("--- End: {0} ---", TestContext.CurrentContext.Test.FullName); } } } diff --git a/src/NzbDrone.Test.Common/NzbDroneRunner.cs b/src/NzbDrone.Test.Common/NzbDroneRunner.cs index 1e89f8636..da8b9bc9b 100644 --- a/src/NzbDrone.Test.Common/NzbDroneRunner.cs +++ b/src/NzbDrone.Test.Common/NzbDroneRunner.cs @@ -9,6 +9,7 @@ using NLog; using NUnit.Framework; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; +using NzbDrone.Core.Configuration; using RestSharp; namespace NzbDrone.Test.Common @@ -30,7 +31,10 @@ namespace NzbDrone.Test.Common public void Start() { - AppData = Path.Combine(TestContext.CurrentContext.TestDirectory, "_intg_" + DateTime.Now.Ticks); + AppData = Path.Combine(TestContext.CurrentContext.TestDirectory, "_intg_" + TestBase.GetUID()); + Directory.CreateDirectory(AppData); + + GenerateConfigFile(); var radarrConsoleExe = OsInfo.IsWindows ? "Radarr.Console.exe" : "Radarr.exe"; @@ -40,7 +44,7 @@ namespace NzbDrone.Test.Common } else { - Start(Path.Combine(TestContext.CurrentContext.TestDirectory, "bin", radarrConsoleExe)); + Start(Path.Combine("bin", radarrConsoleExe)); } while (true) @@ -52,8 +56,6 @@ namespace NzbDrone.Test.Common Assert.Fail("Process has exited"); } - SetApiKey(); - var request = new RestRequest("system/status"); request.AddHeader("Authorization", ApiKey); request.AddHeader("X-Api-Key", ApiKey); @@ -74,13 +76,22 @@ namespace NzbDrone.Test.Common public void KillAll() { - if (_nzbDroneProcess != null) + try { - _processProvider.Kill(_nzbDroneProcess.Id); + if (_nzbDroneProcess != null) + { + _processProvider.Kill(_nzbDroneProcess.Id); + } + + _processProvider.KillAll(ProcessProvider.RADARR_CONSOLE_PROCESS_NAME); + _processProvider.KillAll(ProcessProvider.RADARR_PROCESS_NAME); + } + catch (InvalidOperationException) + { + // May happen if the process closes while being closed } - _processProvider.KillAll(ProcessProvider.RADARR_CONSOLE_PROCESS_NAME); - _processProvider.KillAll(ProcessProvider.RADARR_PROCESS_NAME); + TestBase.DeleteTempFolder(AppData); } private void Start(string outputRadarrConsoleExe) @@ -100,33 +111,26 @@ namespace NzbDrone.Test.Common } } - private void SetApiKey() + private void GenerateConfigFile() { var configFile = Path.Combine(AppData, "config.xml"); - var attempts = 0; - while (ApiKey == null && attempts < 50) - { - try - { - if (File.Exists(configFile)) - { - var apiKeyElement = XDocument.Load(configFile) - .XPathSelectElement("Config/ApiKey"); - if (apiKeyElement != null) - { - ApiKey = apiKeyElement.Value; - } - } - } - catch (XmlException ex) - { - Console.WriteLine("Error getting API Key from XML file: " + ex.Message, ex); - } + // Generate and set the api key so we don't have to poll the config file + var apiKey = Guid.NewGuid().ToString().Replace("-", ""); - attempts++; - Thread.Sleep(1000); - } + var xDoc = new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), + new XElement(ConfigFileProvider.CONFIG_ELEMENT_NAME, + new XElement(nameof(ConfigFileProvider.ApiKey), apiKey), + new XElement(nameof(ConfigFileProvider.AnalyticsEnabled), false) + ) + ); + + var data = xDoc.ToString(); + + File.WriteAllText(configFile, data); + + ApiKey = apiKey; } } } diff --git a/src/NzbDrone.Test.Common/TestBase.cs b/src/NzbDrone.Test.Common/TestBase.cs index 14310f7f3..3ea76b4d8 100644 --- a/src/NzbDrone.Test.Common/TestBase.cs +++ b/src/NzbDrone.Test.Common/TestBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Threading; using FluentAssertions; @@ -45,6 +46,7 @@ namespace NzbDrone.Test.Common { private static readonly Random _random = new Random(); + private static int _nextUid; private AutoMoqer _mocker; protected AutoMoqer Mocker @@ -84,7 +86,21 @@ namespace NzbDrone.Test.Common } } - protected string TempFolder { get; private set; } + private string _tempFolder; + protected string TempFolder + { + get + { + if (_tempFolder == null) + { + _tempFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "_temp_" + GetUID()); + + Directory.CreateDirectory(_tempFolder); + } + + return _tempFolder; + } + } [SetUp] public void TestBaseSetup() @@ -93,9 +109,7 @@ namespace NzbDrone.Test.Common LogManager.ReconfigExistingLoggers(); - TempFolder = Path.Combine(TestContext.CurrentContext.TestDirectory, "_temp_" + DateTime.Now.Ticks); - - Directory.CreateDirectory(TempFolder); + _tempFolder = null; } [TearDown] @@ -103,9 +117,26 @@ namespace NzbDrone.Test.Common { _mocker = null; + DeleteTempFolder(_tempFolder); + } + + + public static string GetUID() + { + return Process.GetCurrentProcess().Id + "_" + DateTime.Now.Ticks + "_" + Interlocked.Increment(ref _nextUid); + } + + public static void DeleteTempFolder(string folder) + { + if (folder == null) + { + return; + } + + try { - var tempFolder = new DirectoryInfo(TempFolder); + var tempFolder = new DirectoryInfo(folder); if (tempFolder.Exists) { foreach (var file in tempFolder.GetFiles("*", SearchOption.AllDirectories)) diff --git a/src/NzbDrone.Test.Common/TestLogOutput.cs b/src/NzbDrone.Test.Common/TestLogOutput.cs new file mode 100644 index 000000000..91cf2d52d --- /dev/null +++ b/src/NzbDrone.Test.Common/TestLogOutput.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Test.Common +{ + public enum TestLogOutput + { + Console = 0, + File = 1, + None = 2 + } +} diff --git a/src/NzbDrone.Update.Test/StartNzbDroneService.cs b/src/NzbDrone.Update.Test/StartNzbDroneService.cs index ddfeb14e1..789cfc864 100644 --- a/src/NzbDrone.Update.Test/StartNzbDroneService.cs +++ b/src/NzbDrone.Update.Test/StartNzbDroneService.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Update.Test [Test] public void should_start_service_if_app_type_was_serivce() { - const string targetFolder = "c:\\Radarr\\"; + string targetFolder = "c:\\Radarr\\".AsOsAgnostic(); Subject.Start(AppType.Service, targetFolder); @@ -26,13 +26,14 @@ namespace NzbDrone.Update.Test [Test] public void should_start_console_if_app_type_was_service_but_start_failed_because_of_permissions() { - const string targetFolder = "c:\\Radarr\\"; + string targetFolder = "c:\\Radarr\\".AsOsAgnostic(); + string targetProcess = "c:\\Radarr\\Radarr.Console.exe".AsOsAgnostic(); Mocker.GetMock().Setup(c => c.Start(ServiceProvider.SERVICE_NAME)).Throws(new InvalidOperationException()); Subject.Start(AppType.Service, targetFolder); - Mocker.GetMock().Verify(c => c.SpawnNewProcess("c:\\Radarr\\Radarr.Console.exe", "/" + StartupContext.NO_BROWSER, null, false), Times.Once()); + Mocker.GetMock().Verify(c => c.SpawnNewProcess(targetProcess, "/" + StartupContext.NO_BROWSER, null, false), Times.Once()); ExceptionVerification.ExpectedWarns(1); } diff --git a/src/NzbDrone.Update/UpdateApp.cs b/src/NzbDrone.Update/UpdateApp.cs index 9bfd4d1b0..3e91d4977 100644 --- a/src/NzbDrone.Update/UpdateApp.cs +++ b/src/NzbDrone.Update/UpdateApp.cs @@ -35,7 +35,7 @@ namespace NzbDrone.Update Logger.Info("Starting Radarr Update Client"); _container = UpdateContainerBuilder.Build(startupArgument); - + _container.Resolve().Initialize(); _container.Resolve().Start(args); Logger.Info("Update completed successfully"); diff --git a/src/NzbDrone.Update/UpdateContainerBuilder.cs b/src/NzbDrone.Update/UpdateContainerBuilder.cs index 47fcbe598..91aa048bb 100644 --- a/src/NzbDrone.Update/UpdateContainerBuilder.cs +++ b/src/NzbDrone.Update/UpdateContainerBuilder.cs @@ -10,7 +10,7 @@ namespace NzbDrone.Update private UpdateContainerBuilder(IStartupContext startupContext, List assemblies) : base(startupContext, assemblies) { - Container.Register(); + } public static IContainer Build(IStartupContext startupContext) diff --git a/src/NzbDrone.Windows.Test/EnvironmentInfo/DotNetPlatformInfoFixture.cs b/src/NzbDrone.Windows.Test/EnvironmentInfo/DotNetPlatformInfoFixture.cs index 2cc9856f3..7bfeb70e1 100644 --- a/src/NzbDrone.Windows.Test/EnvironmentInfo/DotNetPlatformInfoFixture.cs +++ b/src/NzbDrone.Windows.Test/EnvironmentInfo/DotNetPlatformInfoFixture.cs @@ -13,7 +13,7 @@ namespace NzbDrone.Windows.Test.EnvironmentInfo public void should_get_framework_version() { Subject.Version.Major.Should().Be(4); - Subject.Version.Minor.Should().BeOneOf(0, 5, 6); + Subject.Version.Minor.Should().BeOneOf(0, 5, 6, 7, 8); } } } diff --git a/src/NzbDrone.Windows/EnvironmentInfo/DotNetPlatformInfo.cs b/src/NzbDrone.Windows/EnvironmentInfo/DotNetPlatformInfo.cs index 929ad2f03..64a2bf90b 100644 --- a/src/NzbDrone.Windows/EnvironmentInfo/DotNetPlatformInfo.cs +++ b/src/NzbDrone.Windows/EnvironmentInfo/DotNetPlatformInfo.cs @@ -13,7 +13,6 @@ namespace NzbDrone.Windows.EnvironmentInfo { _logger = logger; var version = GetFrameworkVersion(); - Environment.SetEnvironmentVariable("RUNTIME_VERSION", version.ToString()); Version = version; } @@ -33,6 +32,22 @@ namespace NzbDrone.Windows.EnvironmentInfo var releaseKey = (int)ndpKey.GetValue("Release"); + if (releaseKey >= 528040) + { + return new Version(4, 8, 0); + } + if (releaseKey >= 461808) + { + return new Version(4, 7, 2); + } + if (releaseKey >= 461308) + { + return new Version(4, 7, 1); + } + if (releaseKey >= 460798) + { + return new Version(4, 7); + } if (releaseKey >= 394802) { return new Version(4, 6, 2); diff --git a/src/Radarr.Api.V2/Logs/LogFileModuleBase.cs b/src/Radarr.Api.V2/Logs/LogFileModuleBase.cs index c68c40ce2..7db242260 100644 --- a/src/Radarr.Api.V2/Logs/LogFileModuleBase.cs +++ b/src/Radarr.Api.V2/Logs/LogFileModuleBase.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Nancy; using Nancy.Responses; +using NLog; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using Radarr.Http; @@ -44,7 +45,7 @@ namespace Radarr.Api.V2.Logs Id = i + 1, Filename = filename, LastWriteTime = _diskProvider.FileGetLastWrite(file), - ContentsUrl = string.Format("{0}/api/v3/{1}/{2}", _configFileProvider.UrlBase, Resource, filename), + ContentsUrl = string.Format("{0}/api/v2/{1}/{2}", _configFileProvider.UrlBase, Resource, filename), DownloadUrl = string.Format("{0}/{1}/{2}", _configFileProvider.UrlBase, DownloadUrlRoot, filename) }); } @@ -54,6 +55,8 @@ namespace Radarr.Api.V2.Logs private Response GetLogFileResponse(string filename) { + LogManager.Flush(); + var filePath = GetLogFilePath(filename); if (!_diskProvider.FileExists(filePath)) diff --git a/src/Radarr.Api.V2/Movies/MovieEditorResource.cs b/src/Radarr.Api.V2/Movies/MovieEditorResource.cs index 0f74af501..51ac3a058 100644 --- a/src/Radarr.Api.V2/Movies/MovieEditorResource.cs +++ b/src/Radarr.Api.V2/Movies/MovieEditorResource.cs @@ -4,7 +4,7 @@ using NzbDrone.Core.Movies; namespace Radarr.Api.V2.Movies { - class MovieEditorResource + public class MovieEditorResource { public List MovieIds { get; set; } public bool? Monitored { get; set; } diff --git a/src/Radarr.Api.V2/System/SystemModule.cs b/src/Radarr.Api.V2/System/SystemModule.cs index 25b5546a1..6957cce4c 100644 --- a/src/Radarr.Api.V2/System/SystemModule.cs +++ b/src/Radarr.Api.V2/System/SystemModule.cs @@ -64,6 +64,7 @@ namespace Radarr.Api.V2.System IsLinux = OsInfo.IsLinux, IsOsx = OsInfo.IsOsx, IsWindows = OsInfo.IsWindows, + IsDocker = _osInfo.IsDocker, Mode = _runtimeInfo.Mode, Branch = _configFileProvider.Branch, Authentication = _configFileProvider.AuthenticationMethod, diff --git a/src/Radarr.sln b/src/Radarr.sln index 400d33e87..0b5389717 100644 --- a/src/Radarr.sln +++ b/src/Radarr.sln @@ -41,8 +41,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platform", "Platform", "{4E EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoTorrent", "MonoTorrent\MonoTorrent.csproj", "{411A9E0E-FDC6-4E25-828A-0C2CD1CD96F8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurlSharp", "ExternalModules\CurlSharp\CurlSharp\CurlSharp.csproj", "{74420A79-CC16-442C-8B1E-7C1B913844F0}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Http", "Radarr.Http\Radarr.Http.csproj", "{C5953DAB-89DB-46D9-A401-D620F54B776E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Radarr.Api.V2", "Radarr.Api.V2\Radarr.Api.V2.csproj", "{38497DC6-E488-4B9E-A973-A1A7961B33C4}" @@ -118,12 +116,6 @@ Global {411A9E0E-FDC6-4E25-828A-0C2CD1CD96F8}.Mono|x86.Build.0 = Release|x86 {411A9E0E-FDC6-4E25-828A-0C2CD1CD96F8}.Release|x86.ActiveCfg = Release|x86 {411A9E0E-FDC6-4E25-828A-0C2CD1CD96F8}.Release|x86.Build.0 = Release|x86 - {74420A79-CC16-442C-8B1E-7C1B913844F0}.Debug|x86.ActiveCfg = Debug|Any CPU - {74420A79-CC16-442C-8B1E-7C1B913844F0}.Debug|x86.Build.0 = Debug|Any CPU - {74420A79-CC16-442C-8B1E-7C1B913844F0}.Mono|x86.ActiveCfg = Release|Any CPU - {74420A79-CC16-442C-8B1E-7C1B913844F0}.Mono|x86.Build.0 = Release|Any CPU - {74420A79-CC16-442C-8B1E-7C1B913844F0}.Release|x86.ActiveCfg = Release|Any CPU - {74420A79-CC16-442C-8B1E-7C1B913844F0}.Release|x86.Build.0 = Release|Any CPU {C5953DAB-89DB-46D9-A401-D620F54B776E}.Debug|x86.ActiveCfg = Debug|x86 {C5953DAB-89DB-46D9-A401-D620F54B776E}.Debug|x86.Build.0 = Debug|x86 {C5953DAB-89DB-46D9-A401-D620F54B776E}.Mono|x86.ActiveCfg = Release|x86 @@ -279,7 +271,6 @@ Global {F6FC6BE7-0847-4817-A1ED-223DC647C3D7} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} {4EACDBBC-BCD7-4765-A57B-3E08331E4749} = {57A04B72-8088-4F75-A582-1158CF8291F7} {411A9E0E-FDC6-4E25-828A-0C2CD1CD96F8} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} - {74420A79-CC16-442C-8B1E-7C1B913844F0} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} {D9468BE6-8242-4E7F-9470-EE6FDA8832D0} = {57A04B72-8088-4F75-A582-1158CF8291F7} {779D577B-2287-4FB8-8760-D653DBB87109} = {57A04B72-8088-4F75-A582-1158CF8291F7} {175CE3A0-0D8C-48DB-9D8D-C7BBD9591813} = {57A04B72-8088-4F75-A582-1158CF8291F7} diff --git a/src/SharedLiveTemplates.xml b/src/SharedLiveTemplates.xml deleted file mode 100644 index da03090c5..000000000 --- a/src/SharedLiveTemplates.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file