1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-07 13:40:38 -05:00

Compare commits

...

14 Commits

Author SHA1 Message Date
ta264
c7c5d176be fixup! Modernize startup and ConfigFileProvider 2022-06-21 21:05:53 +01:00
ta264
31f082e516 Support legacy postgres options 2022-06-20 21:53:25 +01:00
ta264
a84ef3cd2f Rename configFileProvider 2022-06-20 21:35:11 +01:00
ta264
41e8f7aa45 Modernize startup and ConfigFileProvider 2022-06-20 21:35:11 +01:00
ta264
e3468daba0 Use DryIoc for Automoqer, drop Unity dependency
[common]
2022-06-20 21:29:22 +01:00
Qstick
f2a7d0d520 Additional logging for partial Plex path scan 2022-06-18 16:33:49 -05:00
Weblate
43257f0726 Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]
Currently translated at 99.3% (1136 of 1143 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 100.0% (1143 of 1143 strings)

Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: carreyli <laddie1987@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2022-06-11 09:19:20 -05:00
James Hughes
6c2bf860fe Fixed: Improved empty root folder failsafe logging (#7341)
Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2022-06-08 16:10:23 -05:00
Qstick
3a1d848e59 Fixed: Register PostgresOptions when running in utility mode 2022-06-08 06:04:19 -05:00
PearsonFlyer
f6590e71d2 Fixed: Clarified genre filtering helptext on Trakt lists 2022-06-08 05:55:44 -05:00
Qstick
586dd737fd Fixed: Lithuanian media info parsing
Fixes #7336
2022-06-05 14:38:55 -05:00
Weblate
fa84dda38c Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1143 of 1143 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 99.8% (1139 of 1141 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-06-04 13:43:25 -05:00
Servarr
4a233ce915 Automated API Docs update 2022-06-04 13:40:14 -05:00
Qstick
ffdd9a1708 Fixed: MovieAdded trigger not available in UI 2022-06-04 13:21:44 -05:00
77 changed files with 977 additions and 1107 deletions

View File

@@ -544,10 +544,10 @@ stages:
variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz'
artifactName: linux-x64-tests
Radarr__Postgres__Host: 'localhost'
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
Radarr__PostgresHost: 'localhost'
Radarr__PostgresPort: '5432'
Radarr__PostgresUser: 'radarr'
Radarr__PostgresPassword: 'radarr'
pool:
vmImage: 'ubuntu-18.04'
@@ -681,10 +681,10 @@ stages:
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz'
Radarr__Postgres__Host: 'localhost'
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
Radarr__PostgresHost: 'localhost'
Radarr__PostgresPort: '5432'
Radarr__PostgresUser: 'radarr'
Radarr__PostgresPassword: 'radarr'
pool:
vmImage: 'ubuntu-18.04'

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@@ -13,7 +13,7 @@ namespace NzbDrone.Common.Test
{
[TestFixture]
public class ConfigFileProviderTest : TestBase<ConfigFileProvider>
public class ConfigFileWriterTest : TestBase<ConfigFileWriter>
{
private string _configFileContents;
private string _configFilePath;
@@ -45,56 +45,7 @@ namespace NzbDrone.Common.Test
.Callback<string, string>((p, t) => _configFileContents = t);
}
[Test]
public void GetValue_Success()
{
const string key = "Port";
const string value = "7878";
var result = Subject.GetValue(key, value);
result.Should().Be(value);
}
[Test]
public void GetInt_Success()
{
const string key = "Port";
const int value = 7878;
var result = Subject.GetValueInt(key, value);
result.Should().Be(value);
}
[Test]
public void GetBool_Success()
{
const string key = "LaunchBrowser";
const bool value = true;
var result = Subject.GetValueBoolean(key, value);
result.Should().BeTrue();
}
[Test]
public void GetLaunchBrowser_Success()
{
var result = Subject.LaunchBrowser;
result.Should().Be(true);
}
[Test]
public void GetPort_Success()
{
const int value = 7878;
var result = Subject.Port;
result.Should().Be(value);
}
/*
[Test]
public void SetValue_bool()
@@ -120,17 +71,6 @@ namespace NzbDrone.Common.Test
result.Should().Be(value);
}
[Test]
public void GetValue_New_Key()
{
const string key = "Hello";
const string value = "World";
var result = Subject.GetValue(key, value);
result.Should().Be(value);
}
[Test]
public void GetAuthenticationType_No_Existing_Value()
{
@@ -139,6 +79,7 @@ namespace NzbDrone.Common.Test
result.Should().Be(AuthenticationType.None);
}
/*
[Test]
public void SaveDictionary_should_save_proper_value()
{
@@ -170,32 +111,6 @@ namespace NzbDrone.Common.Test
Subject.Port.Should().Be(port);
Subject.SslPort.Should().Be(sslPort);
}
[Test]
public void should_throw_if_config_file_is_empty()
{
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FileExists(_configFilePath))
.Returns(true);
Assert.Throws<InvalidConfigFileException>(() => Subject.GetValue("key", "value"));
}
[Test]
public void should_throw_if_config_file_contains_only_null_character()
{
_configFileContents = "\0";
Assert.Throws<InvalidConfigFileException>(() => Subject.GetValue("key", "value"));
}
[Test]
public void should_throw_if_config_file_contains_invalid_xml()
{
_configFileContents = "{ \"key\": \"value\" }";
Assert.Throws<InvalidConfigFileException>(() => Subject.GetValue("key", "value"));
}
}*/
}
}

View File

@@ -2,6 +2,7 @@ using System.Linq;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
@@ -10,7 +11,7 @@ using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
@@ -32,7 +33,8 @@ namespace NzbDrone.Common.Test
.AddStartupContext(new StartupContext("first", "second"));
container.RegisterInstance(new Mock<IHostLifetime>().Object);
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
container.RegisterInstance(new Mock<IConfiguration>().Object);
container.RegisterInstance(new Mock<IOptionsMonitor<ConfigFileOptions>>().Object);
var serviceProvider = container.GetServiceProvider();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;

View File

@@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Npgsql;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
@@ -124,13 +126,13 @@ namespace NzbDrone.Core.Test.Framework
private void CreatePostgresDb()
{
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
var options = Mocker.Resolve<IOptionsMonitor<ConfigFileOptions>>().CurrentValue;
PostgresDatabase.Create(options, MigrationType);
}
private void DropPostgresDb()
{
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
var options = Mocker.Resolve<IOptionsMonitor<ConfigFileOptions>>().CurrentValue;
PostgresDatabase.Drop(options, MigrationType);
}
@@ -174,12 +176,11 @@ namespace NzbDrone.Core.Test.Framework
SetupLogging();
// populate the possible postgres options
var postgresOptions = PostgresDatabase.GetTestOptions();
_databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
var options = PostgresDatabase.GetTestOptions();
_databaseType = options.PostgresHost.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
// Set up remaining container services
Mocker.SetConstant(Options.Create(postgresOptions));
Mocker.SetConstant<IConfigFileProvider>(Mocker.Resolve<ConfigFileProvider>());
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>().Setup(x => x.CurrentValue).Returns(options);
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());

View File

@@ -0,0 +1,51 @@
using System;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class LegacyPostgresCheckFixture : CoreTest<LegacyPostgresCheck>
{
[SetUp]
public void Setup()
{
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Warning {0} -> {1}");
}
[TearDown]
public void Teardown()
{
foreach (var name in new[] { "__Postgres__Host", "__Postgres__Port", ":Postgres:Host", ":Postgres:Port" })
{
Environment.SetEnvironmentVariable(BuildInfo.AppName + name, null);
}
}
[Test]
public void should_return_ok_normally()
{
Subject.Check().ShouldBeOk();
}
[TestCase("__")]
[TestCase(":")]
public void should_return_error_if_vars_defined(string separator)
{
Environment.SetEnvironmentVariable(BuildInfo.AppName + separator + "Postgres" + separator + "Host", "localhost");
Environment.SetEnvironmentVariable(BuildInfo.AppName + separator + "Postgres" + separator + "Port", "localhost");
var result = Subject.Check();
result.ShouldBeError("Warning " + BuildInfo.AppName + separator + "Postgres" + separator + "Host, " +
BuildInfo.AppName + separator + "Postgres" + separator + "Port -> " +
BuildInfo.AppName + separator + "PostgresHost, " +
BuildInfo.AppName + separator + "PostgresPort");
}
}
}

View File

@@ -1,9 +1,11 @@
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
@@ -20,9 +22,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
private void GivenValidBranch(string branch)
{
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.Branch)
.Returns(branch);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { Branch = branch });
}
[TestCase("aphrodite")]

View File

@@ -1,3 +1,4 @@
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
@@ -19,6 +20,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(c => c.CurrentValue)
.Returns(new ConfigFileOptions());
}
[Test]
@@ -44,9 +49,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
const string startupFolder = @"/opt/nzbdrone";
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)
.Returns(true);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true });
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)
@@ -67,9 +72,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
const string startupFolder = @"/opt/nzbdrone";
const string uiFolder = @"/opt/nzbdrone/UI";
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)
.Returns(true);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true });
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)
@@ -91,13 +96,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
{
PosixOnly();
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)
.Returns(true);
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateMechanism)
.Returns(UpdateMechanism.Script);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true, UpdateMechanism = UpdateMechanism.Script });
Mocker.GetMock<IAppFolderInfo>()
.Setup(s => s.StartUpFolder)

View File

@@ -4,10 +4,12 @@ using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.Events;
@@ -31,6 +33,10 @@ namespace NzbDrone.Core.Test.MediaCoverTests
.Build();
Mocker.GetMock<IMovieService>().Setup(m => m.GetMovie(It.Is<int>(id => id == _movie.Id))).Returns(_movie);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(x => x.CurrentValue)
.Returns(new ConfigFileOptions());
}
[Test]

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
@@ -60,9 +61,9 @@ namespace NzbDrone.Core.Test.UpdateTests
Mocker.GetMock<IProcessProvider>().Setup(c => c.GetCurrentProcess()).Returns(new ProcessInfo { Id = 12 });
Mocker.GetMock<IRuntimeInfo>().Setup(c => c.ExecutingApplication).Returns(@"C:\Test\Radarr.exe");
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateAutomatically)
.Returns(true);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true });
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.FolderWritable(It.IsAny<string>()))
@@ -77,13 +78,9 @@ namespace NzbDrone.Core.Test.UpdateTests
private void GivenInstallScript(string path)
{
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateMechanism)
.Returns(UpdateMechanism.Script);
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateScriptPath)
.Returns(path);
Mocker.GetMock<IOptionsMonitor<ConfigFileOptions>>()
.Setup(s => s.CurrentValue)
.Returns(new ConfigFileOptions { UpdateAutomatically = true, UpdateMechanism = UpdateMechanism.Script, UpdateScriptPath = path });
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FileExists(path, StringComparison.Ordinal))
@@ -334,7 +331,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.Execute(new ApplicationUpdateCommand());
Mocker.GetMock<IConfigFileProvider>()
Mocker.GetMock<IConfigFileWriter>()
.Verify(v => v.SaveConfigDictionary(It.Is<Dictionary<string, object>>(d => d.ContainsKey("Branch") && (string)d["Branch"] == "fake")), Times.Once());
}

View File

@@ -1,5 +1,6 @@
using System;
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
@@ -15,16 +16,16 @@ namespace NzbDrone.Core.Analytics
public class AnalyticsService : IAnalyticsService
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IHistoryService _historyService;
public AnalyticsService(IHistoryService historyService, IConfigFileProvider configFileProvider)
public AnalyticsService(IHistoryService historyService, IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_historyService = historyService;
}
public bool IsEnabled => (_configFileProvider.AnalyticsEnabled && RuntimeInfo.IsProduction) || RuntimeInfo.IsDevelopment;
public bool IsEnabled => (_configFileOptions.CurrentValue.AnalyticsEnabled && RuntimeInfo.IsProduction) || RuntimeInfo.IsDevelopment;
public bool InstallIsActive
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -19,22 +20,22 @@ namespace NzbDrone.Core.Authentication
User FindUser(Guid identifier);
}
public class UserService : IUserService, IHandle<ApplicationStartedEvent>
public class UserService : IUserService
{
private readonly IUserRepository _repo;
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public UserService(IUserRepository repo,
IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider)
IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_repo = repo;
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public User Add(string username, string password)
@@ -102,28 +103,5 @@ namespace NzbDrone.Core.Authentication
{
return _repo.FindUser(identifier);
}
public void Handle(ApplicationStartedEvent message)
{
if (_repo.All().Any())
{
return;
}
var xDoc = _configFileProvider.LoadConfigFile();
var config = xDoc.Descendants("Config").Single();
var usernameElement = config.Descendants("Username").FirstOrDefault();
var passwordElement = config.Descendants("Password").FirstOrDefault();
if (usernameElement == null || passwordElement == null)
{
return;
}
var username = usernameElement.Value;
var password = passwordElement.Value;
Add(username, password);
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using Microsoft.Extensions.Configuration;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration
{
public class ConfigFileOptions
{
[Persist]
public string BindAddress { get; set; } = "*";
[Persist]
public int Port { get; set; } = 7878;
[Persist]
public int SslPort { get; set; } = 9898;
[Persist]
public bool EnableSsl { get; set; }
[Persist]
public bool LaunchBrowser { get; set; } = true;
public AuthenticationType AuthenticationMethod { get; set; }
public bool AnalyticsEnabled { get; set; } = true;
[Persist]
public string Branch { get; set; } = "master";
[Persist]
public string LogLevel { get; set; } = "info";
public string ConsoleLogLevel { get; set; } = string.Empty;
public bool LogSql { get; set; }
public int LogRotate { get; set; } = 50;
public bool FilterSentryEvents { get; set; } = true;
[Persist]
public string ApiKey { get; set; } = GenerateApiKey();
[Persist]
public string SslCertPath { get; set; }
[Persist]
public string SslCertPassword { get; set; }
[Persist]
public string UrlBase { get; set; } = string.Empty;
[Persist]
public string InstanceName { get; set; } = BuildInfo.AppName;
public bool UpdateAutomatically { get; set; }
public UpdateMechanism UpdateMechanism { get; set; } = UpdateMechanism.BuiltIn;
public string UpdateScriptPath { get; set; } = string.Empty;
public string SyslogServer { get; set; } = string.Empty;
public int SyslogPort { get; set; } = 514;
public string SyslogLevel { get; set; } = "info";
public string PostgresHost { get; set; }
public int PostgresPort { get; set; }
public string PostgresUser { get; set; }
public string PostgresPassword { get; set; }
public string PostgresMainDb { get; set; } = BuildInfo.AppName.ToLower() + "-main";
public string PostgresLogDb { get; set; } = BuildInfo.AppName.ToLower() + "-log";
private static string GenerateApiKey()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
public static ConfigFileOptions GetOptions()
{
var config = new ConfigurationBuilder()
.AddEnvironmentVariables($"{BuildInfo.AppName}:")
.Build();
var options = new ConfigFileOptions();
config.Bind(options);
return options;
}
}
}

View File

@@ -1,401 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration
{
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
IExecute<ResetApiKeyCommand>
{
XDocument LoadConfigFile();
Dictionary<string, object> GetConfigDictionary();
void SaveConfigDictionary(Dictionary<string, object> configValues);
string BindAddress { get; }
int Port { get; }
int SslPort { get; }
bool EnableSsl { get; }
bool LaunchBrowser { get; }
AuthenticationType AuthenticationMethod { get; }
bool AnalyticsEnabled { get; }
string LogLevel { get; }
string ConsoleLogLevel { get; }
bool LogSql { get; }
int LogRotate { get; }
bool FilterSentryEvents { get; }
string Branch { get; }
string ApiKey { get; }
string SslCertPath { get; }
string SslCertPassword { get; }
string UrlBase { get; }
string UiFolder { get; }
string InstanceName { get; }
bool UpdateAutomatically { get; }
UpdateMechanism UpdateMechanism { get; }
string UpdateScriptPath { get; }
string SyslogServer { get; }
int SyslogPort { get; }
string SyslogLevel { get; }
string PostgresHost { get; }
int PostgresPort { get; }
string PostgresUser { get; }
string PostgresPassword { get; }
string PostgresMainDb { get; }
string PostgresLogDb { get; }
}
public class ConfigFileProvider : IConfigFileProvider
{
public const string CONFIG_ELEMENT_NAME = "Config";
private readonly IEventAggregator _eventAggregator;
private readonly IDiskProvider _diskProvider;
private readonly ICached<string> _cache;
private readonly PostgresOptions _postgresOptions;
private readonly string _configFile;
private static readonly Regex HiddenCharacterRegex = new Regex("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly object Mutex = new object();
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
ICacheManager cacheManager,
IEventAggregator eventAggregator,
IDiskProvider diskProvider,
IOptions<PostgresOptions> postgresOptions)
{
_cache = cacheManager.GetCache<string>(GetType());
_eventAggregator = eventAggregator;
_diskProvider = diskProvider;
_configFile = appFolderInfo.GetConfigPath();
_postgresOptions = postgresOptions.Value;
}
public Dictionary<string, object> GetConfigDictionary()
{
var dict = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
var type = GetType();
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
var value = propertyInfo.GetValue(this, null);
dict.Add(propertyInfo.Name, value);
}
return dict;
}
public void SaveConfigDictionary(Dictionary<string, object> configValues)
{
_cache.Clear();
var allWithDefaults = GetConfigDictionary();
foreach (var configValue in configValues)
{
if (configValue.Key.Equals("ApiKey", StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
object currentValue;
allWithDefaults.TryGetValue(configValue.Key, out currentValue);
if (currentValue == null)
{
continue;
}
var equal = configValue.Value.ToString().Equals(currentValue.ToString());
if (!equal)
{
SetValue(configValue.Key.FirstCharToUpper(), configValue.Value.ToString());
}
}
_eventAggregator.PublishEvent(new ConfigFileSavedEvent());
}
public string BindAddress
{
get
{
const string defaultValue = "*";
string bindAddress = GetValue("BindAddress", defaultValue);
if (string.IsNullOrWhiteSpace(bindAddress))
{
return defaultValue;
}
return bindAddress;
}
}
public int Port => GetValueInt("Port", 7878);
public int SslPort => GetValueInt("SslPort", 9898);
public bool EnableSsl => GetValueBoolean("EnableSsl", false);
public bool LaunchBrowser => GetValueBoolean("LaunchBrowser", true);
public string ApiKey
{
get
{
var apiKey = GetValue("ApiKey", GenerateApiKey());
if (apiKey.IsNullOrWhiteSpace())
{
apiKey = GenerateApiKey();
SetValue("ApiKey", apiKey);
}
return apiKey;
}
}
public AuthenticationType AuthenticationMethod
{
get
{
var enabled = GetValueBoolean("AuthenticationEnabled", false, false);
if (enabled)
{
SetValue("AuthenticationMethod", AuthenticationType.Basic);
return AuthenticationType.Basic;
}
return GetValueEnum("AuthenticationMethod", AuthenticationType.None);
}
}
public bool AnalyticsEnabled => GetValueBoolean("AnalyticsEnabled", true, persist: false);
public string Branch => GetValue("Branch", "master").ToLowerInvariant();
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "radarr-main", persist: false);
public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "radarr-log", persist: false);
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
public string SslCertPath => GetValue("SslCertPath", "");
public string SslCertPassword => GetValue("SslCertPassword", "");
public string UrlBase
{
get
{
var urlBase = GetValue("UrlBase", "").Trim('/');
if (urlBase.IsNullOrWhiteSpace())
{
return urlBase;
}
return "/" + urlBase.Trim('/').ToLower();
}
}
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
public string InstanceName => GetValue("InstanceName", BuildInfo.AppName);
public bool UpdateAutomatically => GetValueBoolean("UpdateAutomatically", false, false);
public UpdateMechanism UpdateMechanism => GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false);
public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false);
public string SyslogServer => GetValue("SyslogServer", "", persist: false);
public int SyslogPort => GetValueInt("SyslogPort", 514, persist: false);
public string SyslogLevel => GetValue("SyslogLevel", LogLevel, false).ToLowerInvariant();
public int GetValueInt(string key, int defaultValue, bool persist = true)
{
return Convert.ToInt32(GetValue(key, defaultValue, persist));
}
public bool GetValueBoolean(string key, bool defaultValue, bool persist = true)
{
return Convert.ToBoolean(GetValue(key, defaultValue, persist));
}
public T GetValueEnum<T>(string key, T defaultValue, bool persist = true)
{
return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), persist);
}
public string GetValue(string key, object defaultValue, bool persist = true)
{
return _cache.Get(key, () =>
{
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var parentContainer = config;
var valueHolder = parentContainer.Descendants(key).ToList();
if (valueHolder.Count == 1)
{
return valueHolder.First().Value.Trim();
}
//Save the value
if (persist)
{
SetValue(key, defaultValue);
}
//return the default value
return defaultValue.ToString();
});
}
public void SetValue(string key, object value)
{
var valueString = value.ToString().Trim();
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var parentContainer = config;
var keyHolder = parentContainer.Descendants(key);
if (keyHolder.Count() != 1)
{
parentContainer.Add(new XElement(key, valueString));
}
else
{
parentContainer.Descendants(key).Single().Value = valueString;
}
_cache.Set(key, valueString);
SaveConfigFile(xDoc);
}
public void SetValue(string key, Enum value)
{
SetValue(key, value.ToString().ToLower());
}
private void EnsureDefaultConfigFile()
{
if (!File.Exists(_configFile))
{
SaveConfigDictionary(GetConfigDictionary());
}
}
private void DeleteOldValues()
{
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var type = GetType();
var properties = type.GetProperties();
foreach (var configValue in config.Descendants().ToList())
{
var name = configValue.Name.LocalName;
if (!properties.Any(p => p.Name == name))
{
config.Descendants(name).Remove();
}
}
SaveConfigFile(xDoc);
}
public XDocument LoadConfigFile()
{
try
{
lock (Mutex)
{
if (_diskProvider.FileExists(_configFile))
{
var contents = _diskProvider.ReadAllText(_configFile);
if (contents.IsNullOrWhiteSpace())
{
throw new InvalidConfigFileException($"{_configFile} is empty. Please delete the config file and Radarr will recreate it.");
}
if (contents.All(char.IsControl))
{
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Radarr will recreate it.");
}
return XDocument.Parse(_diskProvider.ReadAllText(_configFile));
}
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
return xDoc;
}
}
catch (XmlException ex)
{
throw new InvalidConfigFileException($"{_configFile} is corrupt is invalid. Please delete the config file and Radarr will recreate it.", ex);
}
}
private void SaveConfigFile(XDocument xDoc)
{
lock (Mutex)
{
_diskProvider.WriteAllText(_configFile, xDoc.ToString());
}
}
private string GenerateApiKey()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
public void HandleAsync(ApplicationStartedEvent message)
{
EnsureDefaultConfigFile();
DeleteOldValues();
}
public void Execute(ResetApiKeyCommand message)
{
SetValue("ApiKey", GenerateApiKey());
}
}
}

View File

@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Configuration
{
public interface IConfigFileWriter : IHandleAsync<ApplicationStartedEvent>,
IExecute<ResetApiKeyCommand>
{
public void EnsureDefaultConfigFile();
void SaveConfigDictionary(Dictionary<string, object> configValues);
}
public class ConfigFileWriter : IConfigFileWriter
{
public static string CONFIG_ELEMENT_NAME = BuildInfo.AppName;
private readonly IEventAggregator _eventAggregator;
private readonly IDiskProvider _diskProvider;
private readonly IConfigurationRoot _configuration;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly Logger _logger;
private readonly string _configFile;
private static readonly object Mutex = new object();
public ConfigFileWriter(IAppFolderInfo appFolderInfo,
IEventAggregator eventAggregator,
IDiskProvider diskProvider,
IConfiguration configuration,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
Logger logger)
{
_eventAggregator = eventAggregator;
_diskProvider = diskProvider;
_configuration = configuration as IConfigurationRoot;
_configFileOptions = configFileOptions;
_logger = logger;
_configFile = appFolderInfo.GetConfigPath();
_configFileOptions.OnChange(OnChange);
}
private Dictionary<string, object> GetConfigDictionary()
{
var dict = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
var properties = typeof(ConfigFileOptions).GetProperties();
foreach (var propertyInfo in properties)
{
var value = propertyInfo.GetValue(_configFileOptions.CurrentValue, null);
dict.Add(propertyInfo.Name, value);
}
return dict;
}
public void SaveConfigDictionary(Dictionary<string, object> configValues)
{
var allWithDefaults = GetConfigDictionary();
var persistKeys = typeof(ConfigFileOptions).GetProperties()
.Where(x => Attribute.IsDefined(x, typeof(PersistAttribute)))
.Select(x => x.Name)
.ToList();
foreach (var configValue in configValues)
{
if (configValue.Key.Equals("ApiKey", StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
allWithDefaults.TryGetValue(configValue.Key, out var currentValue);
if (currentValue == null)
{
continue;
}
var equal = configValue.Value.ToString().Equals(currentValue.ToString());
var persist = persistKeys.Contains(configValue.Key);
if (persist || !equal)
{
SetValue(configValue.Key.FirstCharToUpper(), configValue.Value.ToString());
}
}
_eventAggregator.PublishEvent(new ConfigFileSavedEvent());
}
public void SetValue(string key, object value)
{
var valueString = value.ToString().Trim();
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var keyHolder = config.Descendants(key);
if (keyHolder.Count() != 1)
{
config.Add(new XElement(key, valueString));
}
else
{
config.Descendants(key).Single().Value = valueString;
}
SaveConfigFile(xDoc);
}
public void EnsureDefaultConfigFile()
{
if (!File.Exists(_configFile))
{
SaveConfigDictionary(GetConfigDictionary());
SetValue(nameof(ConfigFileOptions.ApiKey), _configFileOptions.CurrentValue.ApiKey);
}
}
private void DeleteOldValues()
{
var xDoc = LoadConfigFile();
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
var properties = typeof(ConfigFileOptions).GetProperties();
foreach (var configValue in config.Descendants().ToList())
{
var name = configValue.Name.LocalName;
if (!properties.Any(p => p.Name == name))
{
config.Descendants(name).Remove();
}
}
SaveConfigFile(xDoc);
}
public XDocument LoadConfigFile()
{
try
{
lock (Mutex)
{
if (_diskProvider.FileExists(_configFile))
{
var contents = _diskProvider.ReadAllText(_configFile);
if (contents.IsNullOrWhiteSpace())
{
throw new InvalidConfigFileException($"{_configFile} is empty. Please delete the config file and Radarr will recreate it.");
}
if (contents.All(char.IsControl))
{
throw new InvalidConfigFileException($"{_configFile} is corrupt. Please delete the config file and Radarr will recreate it.");
}
return XDocument.Parse(_diskProvider.ReadAllText(_configFile));
}
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
return xDoc;
}
}
catch (XmlException ex)
{
throw new InvalidConfigFileException($"{_configFile} is corrupt is invalid. Please delete the config file and Radarr will recreate it.", ex);
}
}
private void SaveConfigFile(XDocument xDoc)
{
lock (Mutex)
{
_diskProvider.WriteAllText(_configFile, xDoc.ToString());
_configuration.Reload();
}
}
public void HandleAsync(ApplicationStartedEvent message)
{
DeleteOldValues();
}
public void Execute(ResetApiKeyCommand message)
{
SetValue(nameof(ConfigFileOptions.ApiKey), new ConfigFileOptions().ApiKey);
}
private void OnChange(ConfigFileOptions options)
{
_logger.Info("Config file updated");
_eventAggregator.PublishEvent(new ConfigFileSavedEvent());
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace NzbDrone.Core.Configuration
{
[AttributeUsage(AttributeTargets.Property)]
public class PersistAttribute : Attribute
{
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Data.SQLite;
using Microsoft.Extensions.Options;
using Npgsql;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -16,16 +17,25 @@ namespace NzbDrone.Core.Datastore
public class ConnectionStringFactory : IConnectionStringFactory
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider)
// Catch legacy config, to be removed soon
private readonly PostgresOptions _postgresOptions;
public ConnectionStringFactory(IAppFolderInfo appFolderInfo,
IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_postgresOptions = PostgresOptions.GetOptions();
MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
var isPostgres = _configFileOptions.CurrentValue.PostgresHost.IsNotNullOrWhiteSpace() || _postgresOptions.Host.IsNotNullOrWhiteSpace();
var mainDb = _configFileOptions.CurrentValue.PostgresMainDb ?? _postgresOptions.MainDb;
var logDb = _configFileOptions.CurrentValue.PostgresLogDb ?? _postgresOptions.LogDb;
MainDbConnectionString = isPostgres ? GetPostgresConnectionString(mainDb) :
GetConnectionString(appFolderInfo.GetDatabase());
LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
LogDbConnectionString = isPostgres ? GetPostgresConnectionString(logDb) :
GetConnectionString(appFolderInfo.GetLogDatabase());
}
@@ -63,10 +73,10 @@ namespace NzbDrone.Core.Datastore
var connectionBuilder = new NpgsqlConnectionStringBuilder();
connectionBuilder.Database = dbName;
connectionBuilder.Host = _configFileProvider.PostgresHost;
connectionBuilder.Username = _configFileProvider.PostgresUser;
connectionBuilder.Password = _configFileProvider.PostgresPassword;
connectionBuilder.Port = _configFileProvider.PostgresPort;
connectionBuilder.Host = _configFileOptions.CurrentValue.PostgresHost ?? _postgresOptions.Host;
connectionBuilder.Username = _configFileOptions.CurrentValue.PostgresUser ?? _postgresOptions.User;
connectionBuilder.Password = _configFileOptions.CurrentValue.PostgresPassword ?? _postgresOptions.Password;
connectionBuilder.Port = _configFileOptions.CurrentValue.PostgresPort > 0 ? _configFileOptions.CurrentValue.PostgresPort : _postgresOptions.Port;
connectionBuilder.Enlist = false;
return connectionBuilder.ConnectionString;

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Configuration;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.Datastore
{
@@ -18,7 +19,7 @@ namespace NzbDrone.Core.Datastore
.Build();
var postgresOptions = new PostgresOptions();
config.GetSection("Radarr:Postgres").Bind(postgresOptions);
config.GetSection($"{BuildInfo.AppName}:Postgres").Bind(postgresOptions);
return postgresOptions;
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections;
using System.Linq;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Localization;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class LegacyPostgresCheck : HealthCheckBase
{
private readonly Logger _logger;
public LegacyPostgresCheck(ILocalizationService localizationService, Logger logger)
: base(localizationService)
{
_logger = logger;
}
public override HealthCheck Check()
{
var legacyVars = Environment
.GetEnvironmentVariables()
.Cast<DictionaryEntry>()
.Select(x => x.Key.ToString())
.Where(k => k.StartsWith(BuildInfo.AppName + "__Postgres__") || k.StartsWith(BuildInfo.AppName + ":Postgres:"))
.ToList();
if (legacyVars.Count == 0)
{
return new HealthCheck(GetType());
}
var legacyString = legacyVars.OrderBy(x => x).ConcatToString();
var newString = legacyString
.Replace(BuildInfo.AppName + "__Postgres__", BuildInfo.AppName + "__Postgres")
.Replace(BuildInfo.AppName + ":Postgres:", BuildInfo.AppName + ":Postgres");
return new HealthCheck(GetType(),
HealthCheckResult.Error,
string.Format(_localizationService.GetLocalizedString("PostgresLegacyEnvironmentVariables"), legacyString, newString));
}
public override bool CheckOnSchedule => false;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.Localization;
@@ -9,9 +10,9 @@ namespace NzbDrone.Core.HealthCheck.Checks
[CheckOn(typeof(ConfigSavedEvent))]
public class ReleaseBranchCheck : HealthCheckBase
{
private readonly IConfigFileProvider _configFileService;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileService;
public ReleaseBranchCheck(IConfigFileProvider configFileService, ILocalizationService localizationService)
public ReleaseBranchCheck(IOptionsMonitor<ConfigFileOptions> configFileService, ILocalizationService localizationService)
: base(localizationService)
{
_configFileService = configFileService;
@@ -19,11 +20,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var currentBranch = _configFileService.Branch.ToLower();
var currentBranch = _configFileService.CurrentValue.Branch.ToLower();
if (!Enum.GetNames(typeof(ReleaseBranches)).Any(x => x.ToLower() == currentBranch))
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.Branch), "#branch-is-not-a-valid-release-branch");
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("ReleaseBranchCheckOfficialBranchMessage"), _configFileService.CurrentValue.Branch), "#branch-is-not-a-valid-release-branch");
}
return new HealthCheck(GetType());

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -16,13 +17,13 @@ namespace NzbDrone.Core.HealthCheck.Checks
private readonly IDiskProvider _diskProvider;
private readonly IAppFolderInfo _appFolderInfo;
private readonly ICheckUpdateService _checkUpdateService;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IOsInfo _osInfo;
public UpdateCheck(IDiskProvider diskProvider,
IAppFolderInfo appFolderInfo,
ICheckUpdateService checkUpdateService,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
IOsInfo osInfo,
ILocalizationService localizationService)
: base(localizationService)
@@ -30,7 +31,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
_diskProvider = diskProvider;
_appFolderInfo = appFolderInfo;
_checkUpdateService = checkUpdateService;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_osInfo = osInfo;
}
@@ -39,8 +40,8 @@ namespace NzbDrone.Core.HealthCheck.Checks
var startupFolder = _appFolderInfo.StartUpFolder;
var uiFolder = Path.Combine(startupFolder, "UI");
if ((OsInfo.IsWindows || _configFileProvider.UpdateAutomatically) &&
_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn &&
if ((OsInfo.IsWindows || _configFileOptions.CurrentValue.UpdateAutomatically) &&
_configFileOptions.CurrentValue.UpdateMechanism == UpdateMechanism.BuiltIn &&
!_osInfo.IsDocker)
{
if (OsInfo.IsOsx && startupFolder.GetAncestorFolders().Contains("AppTranslocation"))

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.EnvironmentInfo;
@@ -19,14 +20,14 @@ namespace NzbDrone.Core.HealthCheck
public class ServerSideNotificationService : IServerSideNotificationService
{
private readonly IHttpClient _client;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IHttpRequestBuilderFactory _cloudRequestBuilder;
private readonly Logger _logger;
public ServerSideNotificationService(IHttpClient client, IConfigFileProvider configFileProvider, IRadarrCloudRequestBuilder cloudRequestBuilder, Logger logger)
public ServerSideNotificationService(IHttpClient client, IOptionsMonitor<ConfigFileOptions> configFileOptions, IRadarrCloudRequestBuilder cloudRequestBuilder, Logger logger)
{
_client = client;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_cloudRequestBuilder = cloudRequestBuilder.Services;
_logger = logger;
}
@@ -39,7 +40,7 @@ namespace NzbDrone.Core.HealthCheck
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
.AddQueryParam("branch", _configFileProvider.Branch)
.AddQueryParam("branch", _configFileOptions.CurrentValue.Branch)
.Build();
try
{

View File

@@ -90,7 +90,7 @@ namespace NzbDrone.Core.ImportLists.Trakt
[FieldDefinition(2, Label = "Certification", HelpText = "Filter movies by a certification (NR,G,PG,PG-13,R,NC-17), (Comma Separated)")]
public string Certification { get; set; }
[FieldDefinition(3, Label = "Genres", HelpText = "Filter movies by Trakt Genre Slug (Comma Separated)")]
[FieldDefinition(3, Label = "Genres", HelpText = "Filter movies by Trakt Genre Slug (Comma Separated) Only for Popular Lists")]
public string Genres { get; set; }
[FieldDefinition(4, Label = "Years", HelpText = "Filter movies by year or year range")]

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
using NLog;
using NLog.Config;
using NLog.Targets.Syslog;
@@ -17,21 +18,21 @@ namespace NzbDrone.Core.Instrumentation
{
public class ReconfigureLogging : IHandleAsync<ConfigFileSavedEvent>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public ReconfigureLogging(IConfigFileProvider configFileProvider)
public ReconfigureLogging(IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public void Reconfigure()
{
var minimumLogLevel = LogLevel.FromString(_configFileProvider.LogLevel);
var minimumLogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.LogLevel);
LogLevel minimumConsoleLogLevel;
if (_configFileProvider.ConsoleLogLevel.IsNotNullOrWhiteSpace())
if (_configFileOptions.CurrentValue.ConsoleLogLevel.IsNotNullOrWhiteSpace())
{
minimumConsoleLogLevel = LogLevel.FromString(_configFileProvider.ConsoleLogLevel);
minimumConsoleLogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.ConsoleLogLevel);
}
else if (minimumLogLevel > LogLevel.Info)
{
@@ -42,10 +43,10 @@ namespace NzbDrone.Core.Instrumentation
minimumConsoleLogLevel = LogLevel.Info;
}
if (_configFileProvider.SyslogServer.IsNotNullOrWhiteSpace())
if (_configFileOptions.CurrentValue.SyslogServer.IsNotNullOrWhiteSpace())
{
var syslogLevel = LogLevel.FromString(_configFileProvider.SyslogLevel);
SetSyslogParameters(_configFileProvider.SyslogServer, _configFileProvider.SyslogPort, syslogLevel);
var syslogLevel = LogLevel.FromString(_configFileOptions.CurrentValue.SyslogLevel);
SetSyslogParameters(_configFileOptions.CurrentValue.SyslogServer, _configFileOptions.CurrentValue.SyslogPort, syslogLevel);
}
var rules = LogManager.Configuration.LoggingRules;
@@ -60,7 +61,7 @@ namespace NzbDrone.Core.Instrumentation
SetLogRotation();
//Log Sql
SqlBuilderExtensions.LogSql = _configFileProvider.LogSql;
SqlBuilderExtensions.LogSql = _configFileOptions.CurrentValue.LogSql;
//Sentry
ReconfigureSentry();
@@ -95,7 +96,7 @@ namespace NzbDrone.Core.Instrumentation
{
foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>())
{
target.MaxArchiveFiles = _configFileProvider.LogRotate;
target.MaxArchiveFiles = _configFileOptions.CurrentValue.LogRotate;
}
}
@@ -104,8 +105,8 @@ namespace NzbDrone.Core.Instrumentation
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null)
{
sentryTarget.SentryEnabled = (RuntimeInfo.IsProduction && _configFileProvider.AnalyticsEnabled) || RuntimeInfo.IsDevelopment;
sentryTarget.FilterEvents = _configFileProvider.FilterSentryEvents;
sentryTarget.SentryEnabled = (RuntimeInfo.IsProduction && _configFileOptions.CurrentValue.AnalyticsEnabled) || RuntimeInfo.IsDevelopment;
sentryTarget.FilterEvents = _configFileOptions.CurrentValue.FilterSentryEvents;
}
}
@@ -119,7 +120,7 @@ namespace NzbDrone.Core.Instrumentation
syslogTarget.MessageSend.Udp.Server = syslogServer;
syslogTarget.MessageSend.Udp.ReconnectInterval = 500;
syslogTarget.MessageCreation.Rfc = RfcNumber.Rfc5424;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileProvider.InstanceName;
syslogTarget.MessageCreation.Rfc5424.AppName = _configFileOptions.CurrentValue.InstanceName;
var loggingRule = new LoggingRule("*", minimumLogLevel, syslogTarget);

View File

@@ -1,4 +1,5 @@
using System.Linq;
using System.Linq;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Sentry;
@@ -11,15 +12,15 @@ namespace NzbDrone.Core.Instrumentation
{
public class ReconfigureSentry : IHandleAsync<ApplicationStartedEvent>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IPlatformInfo _platformInfo;
private readonly IMainDatabase _database;
public ReconfigureSentry(IConfigFileProvider configFileProvider,
public ReconfigureSentry(IOptionsMonitor<ConfigFileOptions> configFileOptions,
IPlatformInfo platformInfo,
IMainDatabase database)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_platformInfo = platformInfo;
_database = database;
}
@@ -30,7 +31,7 @@ namespace NzbDrone.Core.Instrumentation
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
if (sentryTarget != null)
{
sentryTarget.UpdateScope(_database.Version, _database.Migration, _configFileProvider.Branch, _platformInfo);
sentryTarget.UpdateScope(_database.Version, _database.Migration, _configFileOptions.CurrentValue.Branch, _platformInfo);
}
}

View File

@@ -696,6 +696,7 @@
"PosterOptions": "Poster Options",
"Posters": "Posters",
"PosterSize": "Poster Size",
"PostgresLegacyEnvironmentVariables": "You have defined the following legacy PostgreSQL environment variables: {0}. Please update them to: {1}",
"PreferAndUpgrade": "Prefer and Upgrade",
"PreferIndexerFlags": "Prefer Indexer Flags",
"PreferIndexerFlagsHelpText": "Prioritize releases with special flags",

View File

@@ -425,7 +425,7 @@
"RestartRequiredHelpTextWarning": "Käyttöönotto vaatii uudelleenkäynnistyksen.",
"Restore": "Palauta",
"RestoreBackup": "Palauta varmuuskopio",
"RootFolder": "Pääkansio",
"RootFolder": "Juurikansio",
"RootFolderCheckMultipleMessage": "Useita juurikansioita puuttuu: {0}",
"SendAnonymousUsageData": "Lähetä nimettömiä käyttötietoja",
"Search": "Haku",
@@ -807,7 +807,7 @@
"RetentionHelpText": "Vain Usenet: Aseta nollaan asettamaan rajoittamaton säilytys",
"RetryingDownloadInterp": "Yritetään ladata uudelleen {0} osoitteessa {1}",
"RootFolderCheckSingleMessage": "Puuttuva juurikansio: {0}",
"RootFolders": "Pääkansiot",
"RootFolders": "Juurikansiot",
"RSS": "RSS",
"RSSIsNotSupportedWithThisIndexer": "RSS-syötettä ei ole käytettävissä tälle tietolähteelle",
"RSSSyncInterval": "RSS-synkronointiväli",
@@ -970,7 +970,7 @@
"UnableToLoadRemotePathMappings": "Etäsijaintien kartoitusten lataus epäonnistui.",
"UnableToLoadRestrictions": "Rajoitusten lataus epäonnistui.",
"UnableToLoadResultsIntSearch": "Elokuvahaun tuloksien lataus epäonnistui. Yritä myöhemmin uudelleen.",
"UnableToLoadRootFolders": "Pääkansioiden lataus epäonnistui.",
"UnableToLoadRootFolders": "Juurikansioiden lataus epäonnistui.",
"UnableToLoadTags": "Tunnisteiden lataus epäonnistui.",
"UnableToLoadTheCalendar": "Kalenterin lataus epäonnistui.",
"UnableToUpdateRadarrDirectly": "Radarrin suora päivitys epäonnistui.",
@@ -1041,7 +1041,7 @@
"Reddit": "Reddit",
"More": "Lisää",
"Download": "Lataa",
"DownloadClientCheckDownloadingToRoot": "Lataustyökalu '{0}' sijoittaa lataukset pääkansioon '{1}' ja näin ei pitäisi tehdä, vaan lataukset tulee tallentaa erilliseen sijaintiin.",
"DownloadClientCheckDownloadingToRoot": "Lataustyökalu '{0}' sijoittaa lataukset juurikansioon '{1}' ja näin ei pitäisi tehdä, vaan lataukset tulee tallentaa erilliseen sijaintiin.",
"DeleteFileLabel": "Poista {0} elokuvatiedosto",
"UnableToAddRootFolder": "Juurikansioiden lataus epäonnistui.",
"AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist": "Haluatko varmasti poistaa valitut kohteet estolistalta?",
@@ -1134,5 +1134,12 @@
"ScrollMovies": "Vieritä elokuvia",
"ShowCollectionDetails": "Näytä kokoelman tila",
"ShowOverview": "Näytä yleiskatsaus",
"UnableToLoadCollections": "Kokoelmia ei voitu ladata"
"UnableToLoadCollections": "Kokoelmia ei voitu ladata",
"OnMovieAdded": "Kun elokuva lisätään",
"OnMovieAddedHelpText": "Kun elokuva lisätään",
"ShowPosters": "Näytä julisteet",
"CollectionShowOverviewsHelpText": "Näytä kokoelmien katsaukset",
"CollectionOptions": "Kokoelmien valinnat",
"CollectionShowDetailsHelpText": "Näytä kokoelmien tila ja ominaisuudet",
"CollectionShowPostersHelpText": "Näytä kokoelmien julisteet"
}

View File

@@ -1133,5 +1133,9 @@
"MonitorCollection": "Gyűjtemény monitorozása",
"SearchOnAddCollectionHelpText": "Ebben a gyűjteményben található filmek keresése, a hozzáadás adás után",
"UnableToLoadCollections": "Nem sikerült betölteni a gyűjteményeket",
"MonitoredCollectionHelpText": "A gyűjteményből származó filmek monitorozása, hogy automatikusan hozzáadódjanak a könyvtárhoz"
"MonitoredCollectionHelpText": "A gyűjteményből származó filmek monitorozása, hogy automatikusan hozzáadódjanak a könyvtárhoz",
"CollectionOptions": "Gyűjtemény baállítások",
"CollectionShowOverviewsHelpText": "Gyűjtemények áttekintésének megjelenítése",
"CollectionShowDetailsHelpText": "A gyűjtemény állapotának és tulajdonságainak megjelenítése",
"CollectionShowPostersHelpText": "Gyűjteményelemek posztereinek megjelenítése"
}

View File

@@ -1134,5 +1134,12 @@
"MonitoredCollectionHelpText": "Monitorar para que os filmes desta coleção sejam adicionados automaticamente à biblioteca",
"RefreshCollections": "Atualizar Coleções",
"SearchOnAddCollectionHelpText": "Pesquisar filmes nesta coleção quando adicionados à biblioteca",
"ScrollMovies": "Rolar Filmes"
"ScrollMovies": "Rolar Filmes",
"OnMovieAdded": "Ao Filme Adicionado",
"OnMovieAddedHelpText": "Ao Filme Adicionado",
"ShowPosters": "Mostrar pôsteres",
"CollectionOptions": "Opções de Coleção",
"CollectionShowDetailsHelpText": "Mostrar estado e propriedades da coleção",
"CollectionShowOverviewsHelpText": "Mostrar visão geral da coleção",
"CollectionShowPostersHelpText": "Mostrar pôsteres de itens da coleção"
}

View File

@@ -1134,5 +1134,6 @@
"MovieOnly": "仅电影",
"NoCollections": "没有发现集合,点击添加新的电影或者导入已经存在的电影",
"InstanceNameHelpText": "选项卡及日志应用名称",
"ScrollMovies": "滚动电影"
"ScrollMovies": "滚动电影",
"CollectionOptions": "Collection Options"
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
@@ -18,13 +19,13 @@ namespace NzbDrone.Core.MediaCover
public class MediaCoverProxy : IMediaCoverProxy
{
private readonly IHttpClient _httpClient;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly ICached<string> _cache;
public MediaCoverProxy(IHttpClient httpClient, IConfigFileProvider configFileProvider, ICacheManager cacheManager)
public MediaCoverProxy(IHttpClient httpClient, IOptionsMonitor<ConfigFileOptions> configFileOptions, ICacheManager cacheManager)
{
_httpClient = httpClient;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_cache = cacheManager.GetCache<string>(GetType());
}
@@ -37,7 +38,7 @@ namespace NzbDrone.Core.MediaCover
_cache.ClearExpired();
var fileName = Path.GetFileName(url);
return _configFileProvider.UrlBase + @"/MediaCoverProxy/" + hash + "/" + fileName;
return _configFileOptions.CurrentValue.UrlBase + @"/MediaCoverProxy/" + hash + "/" + fileName;
}
public string GetUrl(string hash)

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
@@ -35,7 +36,7 @@ namespace NzbDrone.Core.MediaCover
private readonly IHttpClient _httpClient;
private readonly IDiskProvider _diskProvider;
private readonly ICoverExistsSpecification _coverExistsSpecification;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
@@ -51,7 +52,7 @@ namespace NzbDrone.Core.MediaCover
IDiskProvider diskProvider,
IAppFolderInfo appFolderInfo,
ICoverExistsSpecification coverExistsSpecification,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
IEventAggregator eventAggregator,
Logger logger)
{
@@ -60,7 +61,7 @@ namespace NzbDrone.Core.MediaCover
_httpClient = httpClient;
_diskProvider = diskProvider;
_coverExistsSpecification = coverExistsSpecification;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_eventAggregator = eventAggregator;
_logger = logger;
@@ -104,7 +105,7 @@ namespace NzbDrone.Core.MediaCover
var filePath = GetCoverPath(movieId, mediaCover.CoverType);
mediaCover.RemoteUrl = mediaCover.Url;
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + movieId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
mediaCover.Url = _configFileOptions.CurrentValue.UrlBase + @"/MediaCover/" + movieId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
FileInfo file;
var fileExists = false;

View File

@@ -91,7 +91,7 @@ namespace NzbDrone.Core.MediaFiles
if (_diskProvider.FolderEmpty(rootFolder))
{
_logger.Warn("Movie's root folder ({0}) is empty.", rootFolder);
_logger.Warn("Movie's root folder ({0}) is empty. Rescan will not update movies as a failsafe.", rootFolder);
_eventAggregator.PublishEvent(new MovieScanSkippedEvent(movie, MovieScanSkippedReason.RootFolderIsEmpty));
return;
}

View File

@@ -61,8 +61,8 @@ namespace NzbDrone.Core.MediaFiles
if (_diskProvider.GetDirectories(rootFolder).Empty())
{
_logger.Warn("Movie's root folder ({0}) is empty.", rootFolder);
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Movie's root folder ({0}) is empty.", rootFolder);
_logger.Warn("Movie's root folder ({0}) is empty. Rescan will not update movies as a failsafe.", rootFolder);
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Movie's root folder ({0}) is empty. Rescan will not update movies as a failsafe.", rootFolder);
}
if (_diskProvider.FolderExists(movie.Path) && _diskProvider.FileExists(fullPath))

View File

@@ -85,6 +85,7 @@ namespace NzbDrone.Core.Notifications
definition.SupportsOnDownload = provider.SupportsOnDownload;
definition.SupportsOnUpgrade = provider.SupportsOnUpgrade;
definition.SupportsOnRename = provider.SupportsOnRename;
definition.SupportsOnMovieAdded = provider.SupportsOnMovieAdded;
definition.SupportsOnMovieDelete = provider.SupportsOnMovieDelete;
definition.SupportsOnMovieFileDelete = provider.SupportsOnMovieFileDelete;
definition.SupportsOnMovieFileDeleteForUpgrade = provider.SupportsOnMovieFileDeleteForUpgrade;

View File

@@ -57,15 +57,15 @@ namespace NzbDrone.Core.Notifications.Plex.Server
var partialUpdates = _partialUpdateCache.Get(settings.Host, () => PartialUpdatesAllowed(settings, version), TimeSpan.FromHours(2));
var pathScanCache = _pathScanCache.Get(settings.Host, () => PathUpdatesAllowed(settings, version), TimeSpan.FromHours(2));
var pathUpdated = true;
var pathsUpdated = true;
if (pathScanCache)
{
foreach (var movie in multipleMovies)
{
pathUpdated &= UpdatePath(movie, sections, settings);
pathsUpdated &= UpdatePath(movie, sections, settings);
if (!pathUpdated)
if (!pathsUpdated)
{
break;
}
@@ -73,7 +73,7 @@ namespace NzbDrone.Core.Notifications.Plex.Server
}
// If we couldn't path update then try partial and full update
if (!pathUpdated)
if (!pathScanCache || (pathScanCache && !pathsUpdated))
{
if (partialUpdates)
{
@@ -244,6 +244,10 @@ namespace NzbDrone.Core.Notifications.Plex.Server
pathUpdated = true;
}
else
{
_logger.Debug("Unable to update path, mapped movie path {0} is not a child of any plex libraries: {1}", mappedPath, sections.SelectMany(s => s.Locations.Select(l => l.Path)).Join(", "));
}
return pathUpdated;
}

View File

@@ -41,7 +41,8 @@ namespace NzbDrone.Core.Parser
new IsoLanguage("ar", "", "ara", "Arabic", Language.Arabic),
new IsoLanguage("uk", "", "ukr", "Ukrainian", Language.Ukrainian),
new IsoLanguage("fa", "", "fas", "Persian", Language.Persian),
new IsoLanguage("be", "", "ben", "Bengali", Language.Bengali)
new IsoLanguage("be", "", "ben", "Bengali", Language.Bengali),
new IsoLanguage("lt", "", "lit", "Lithuanian", Language.Lithuanian)
};
public static IsoLanguage Find(string isoCode)

View File

@@ -13,7 +13,8 @@
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0-preview.5.22301.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0-preview.5.22301.12" />
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" />
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="3.3.2" />
<PackageReference Include="FluentValidation" Version="8.6.2" />

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
@@ -16,19 +17,21 @@ namespace NzbDrone.Core.Update
public class UpdaterConfigProvider : IUpdaterConfigProvider, IHandle<ApplicationStartedEvent>
{
private readonly Logger _logger;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IConfigFileWriter _configFileWriter;
private readonly IDeploymentInfoProvider _deploymentInfoProvider;
public UpdaterConfigProvider(IDeploymentInfoProvider deploymentInfoProvider, IConfigFileProvider configFileProvider, Logger logger)
public UpdaterConfigProvider(IDeploymentInfoProvider deploymentInfoProvider, IOptionsMonitor<ConfigFileOptions> configFileOptions, IConfigFileWriter configFileWriter, Logger logger)
{
_deploymentInfoProvider = deploymentInfoProvider;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_configFileWriter = configFileWriter;
_logger = logger;
}
public void Handle(ApplicationStartedEvent message)
{
var updateMechanism = _configFileProvider.UpdateMechanism;
var updateMechanism = _configFileOptions.CurrentValue.UpdateMechanism;
var packageUpdateMechanism = _deploymentInfoProvider.PackageUpdateMechanism;
var externalMechanisms = Enum.GetValues(typeof(UpdateMechanism))
@@ -49,7 +52,7 @@ namespace NzbDrone.Core.Update
if (_deploymentInfoProvider.IsExternalUpdateMechanism)
{
var currentBranch = _configFileProvider.Branch;
var currentBranch = _configFileOptions.CurrentValue.Branch;
var packageBranch = _deploymentInfoProvider.PackageBranch;
if (packageBranch.IsNotNullOrWhiteSpace() && packageBranch != currentBranch)
{
@@ -63,18 +66,18 @@ namespace NzbDrone.Core.Update
{
var config = new Dictionary<string, object>
{
[nameof(_configFileProvider.UpdateMechanism)] = updateMechanism
[nameof(_configFileOptions.CurrentValue.UpdateMechanism)] = updateMechanism
};
_configFileProvider.SaveConfigDictionary(config);
_configFileWriter.SaveConfigDictionary(config);
}
private void ChangeBranch(string branch)
{
var config = new Dictionary<string, object>
{
[nameof(_configFileProvider.Branch)] = branch
[nameof(_configFileOptions.CurrentValue.Branch)] = branch
};
_configFileProvider.SaveConfigDictionary(config);
_configFileWriter.SaveConfigDictionary(config);
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
@@ -34,7 +36,8 @@ namespace NzbDrone.Core.Update
private readonly IVerifyUpdates _updateVerifier;
private readonly IStartupContext _startupContext;
private readonly IDeploymentInfoProvider _deploymentInfoProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IConfigFileWriter _configFileWriter;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IBackupService _backupService;
private readonly IOsInfo _osInfo;
@@ -50,15 +53,16 @@ namespace NzbDrone.Core.Update
IVerifyUpdates updateVerifier,
IStartupContext startupContext,
IDeploymentInfoProvider deploymentInfoProvider,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
IConfigFileWriter configFileWriter,
IRuntimeInfo runtimeInfo,
IBackupService backupService,
IOsInfo osInfo,
Logger logger)
{
if (configFileProvider == null)
if (configFileOptions == null)
{
throw new ArgumentNullException(nameof(configFileProvider));
throw new ArgumentNullException(nameof(configFileOptions));
}
_checkUpdateService = checkUpdateService;
@@ -72,7 +76,8 @@ namespace NzbDrone.Core.Update
_updateVerifier = updateVerifier;
_startupContext = startupContext;
_deploymentInfoProvider = deploymentInfoProvider;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_configFileWriter = configFileWriter;
_runtimeInfo = runtimeInfo;
_backupService = backupService;
_osInfo = osInfo;
@@ -83,7 +88,7 @@ namespace NzbDrone.Core.Update
{
EnsureAppDataSafety();
if (OsInfo.IsWindows || _configFileProvider.UpdateMechanism != UpdateMechanism.Script)
if (OsInfo.IsWindows || _configFileOptions.CurrentValue.UpdateMechanism != UpdateMechanism.Script)
{
var startupFolder = _appFolderInfo.StartUpFolder;
var uiFolder = Path.Combine(startupFolder, "UI");
@@ -137,7 +142,7 @@ namespace NzbDrone.Core.Update
_backupService.Backup(BackupType.Update);
if (OsInfo.IsNotWindows && _configFileProvider.UpdateMechanism == UpdateMechanism.Script)
if (OsInfo.IsNotWindows && _configFileOptions.CurrentValue.UpdateMechanism == UpdateMechanism.Script)
{
InstallUpdateWithScript(updateSandboxFolder);
return true;
@@ -170,7 +175,7 @@ namespace NzbDrone.Core.Update
private void EnsureValidBranch(UpdatePackage package)
{
var currentBranch = _configFileProvider.Branch;
var currentBranch = _configFileOptions.CurrentValue.Branch;
if (package.Branch != currentBranch)
{
try
@@ -178,7 +183,7 @@ namespace NzbDrone.Core.Update
_logger.Info("Branch [{0}] is being redirected to [{1}]]", currentBranch, package.Branch);
var config = new Dictionary<string, object>();
config["Branch"] = package.Branch;
_configFileProvider.SaveConfigDictionary(config);
_configFileWriter.SaveConfigDictionary(config);
}
catch (Exception e)
{
@@ -189,7 +194,7 @@ namespace NzbDrone.Core.Update
private void InstallUpdateWithScript(string updateSandboxFolder)
{
var scriptPath = _configFileProvider.UpdateScriptPath;
var scriptPath = _configFileOptions.CurrentValue.UpdateScriptPath;
if (scriptPath.IsNullOrWhiteSpace())
{
@@ -204,7 +209,7 @@ namespace NzbDrone.Core.Update
_logger.Info("Removing Radarr.Update");
_diskProvider.DeleteFolder(_appFolderInfo.GetUpdateClientFolder(), true);
_logger.ProgressInfo("Starting update script: {0}", _configFileProvider.UpdateScriptPath);
_logger.ProgressInfo("Starting update script: {0}", _configFileOptions.CurrentValue.UpdateScriptPath);
_processProvider.Start(scriptPath, GetUpdaterArgs(updateSandboxFolder));
}
@@ -243,19 +248,19 @@ namespace NzbDrone.Core.Update
return null;
}
if (OsInfo.IsNotWindows && !_configFileProvider.UpdateAutomatically && updateTrigger != CommandTrigger.Manual)
if (OsInfo.IsNotWindows && !_configFileOptions.CurrentValue.UpdateAutomatically && updateTrigger != CommandTrigger.Manual)
{
_logger.ProgressDebug("Auto-update not enabled, not installing available update.");
return null;
}
// Safety net, ConfigureUpdateMechanism should take care of invalid settings
if (_configFileProvider.UpdateMechanism == UpdateMechanism.BuiltIn && _deploymentInfoProvider.IsExternalUpdateMechanism)
if (_configFileOptions.CurrentValue.UpdateMechanism == UpdateMechanism.BuiltIn && _deploymentInfoProvider.IsExternalUpdateMechanism)
{
_logger.ProgressDebug("Built-In updater disabled, please use {0} to install", _deploymentInfoProvider.PackageUpdateMechanism);
return null;
}
else if (_configFileProvider.UpdateMechanism != UpdateMechanism.Script && _deploymentInfoProvider.IsExternalUpdateMechanism)
else if (_configFileOptions.CurrentValue.UpdateMechanism != UpdateMechanism.Script && _deploymentInfoProvider.IsExternalUpdateMechanism)
{
_logger.ProgressDebug("Update available, please use {0} to install", _deploymentInfoProvider.PackageUpdateMechanism);
return null;
@@ -315,8 +320,8 @@ namespace NzbDrone.Core.Update
_logger.Debug("Post-install update check requested");
// Don't do a prestartup update check unless BuiltIn update is enabled
if (!_configFileProvider.UpdateAutomatically ||
_configFileProvider.UpdateMechanism != UpdateMechanism.BuiltIn ||
if (!_configFileOptions.CurrentValue.UpdateAutomatically ||
_configFileOptions.CurrentValue.UpdateMechanism != UpdateMechanism.BuiltIn ||
_deploymentInfoProvider.IsExternalUpdateMechanism)
{
_logger.Debug("Built-in updater disabled, skipping post-install update check");

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Update.History;
@@ -12,22 +13,22 @@ namespace NzbDrone.Core.Update
public class RecentUpdateProvider : IRecentUpdateProvider
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IUpdatePackageProvider _updatePackageProvider;
private readonly IUpdateHistoryService _updateHistoryService;
public RecentUpdateProvider(IConfigFileProvider configFileProvider,
public RecentUpdateProvider(IOptionsMonitor<ConfigFileOptions> configFileOptions,
IUpdatePackageProvider updatePackageProvider,
IUpdateHistoryService updateHistoryService)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_updatePackageProvider = updatePackageProvider;
_updateHistoryService = updateHistoryService;
}
public List<UpdatePackage> GetRecentUpdatePackages()
{
var branch = _configFileProvider.Branch;
var branch = _configFileOptions.CurrentValue.Branch;
var version = BuildInfo.Version;
var prevVersion = _updateHistoryService.PreviouslyInstalled();
return _updatePackageProvider.GetRecentUpdates(branch, version, prevVersion);

View File

@@ -1,3 +1,4 @@
using Microsoft.Extensions.Options;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
@@ -11,18 +12,18 @@ namespace NzbDrone.Core.Update
public class CheckUpdateService : ICheckUpdateService
{
private readonly IUpdatePackageProvider _updatePackageProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public CheckUpdateService(IUpdatePackageProvider updatePackageProvider,
IConfigFileProvider configFileProvider)
IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_updatePackageProvider = updatePackageProvider;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public UpdatePackage AvailableUpdate()
{
return _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version);
return _updatePackageProvider.GetLatestUpdate(_configFileOptions.CurrentValue.Branch, BuildInfo.Version);
}
}
}

View File

@@ -12,6 +12,7 @@ using NzbDrone.Common;
using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Download;
@@ -44,9 +45,9 @@ namespace NzbDrone.App.Test
.AddStartupContext(args);
// dummy lifetime and broadcaster so tests resolve
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
container.RegisterInstance<IBroadcastSignalRMessage>(new Mock<IBroadcastSignalRMessage>().Object);
container.RegisterInstance<IOptions<PostgresOptions>>(new Mock<IOptions<PostgresOptions>>().Object);
container.RegisterInstance(new Mock<IHostLifetime>().Object);
container.RegisterInstance(new Mock<IBroadcastSignalRMessage>().Object);
container.RegisterInstance(new Mock<IOptionsMonitor<ConfigFileOptions>>().Object);
_container = container.GetServiceProvider();
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Microsoft.Extensions.Options;
using NetFwTypeLib;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
@@ -16,12 +17,12 @@ namespace Radarr.Host.AccessControl
{
private const NET_FW_PROFILE_TYPE_ FIREWALL_PROFILE = NET_FW_PROFILE_TYPE_.NET_FW_PROFILE_STANDARD;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly Logger _logger;
public FirewallAdapter(IConfigFileProvider configFileProvider, Logger logger)
public FirewallAdapter(IOptionsMonitor<ConfigFileOptions> configFileOptions, Logger logger)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_logger = logger;
}
@@ -29,16 +30,16 @@ namespace Radarr.Host.AccessControl
{
if (IsFirewallEnabled())
{
if (!IsNzbDronePortOpen(_configFileProvider.Port))
if (!IsNzbDronePortOpen(_configFileOptions.CurrentValue.Port))
{
_logger.Debug("Opening Port for Radarr: {0}", _configFileProvider.Port);
OpenFirewallPort(_configFileProvider.Port);
_logger.Debug("Opening Port for Radarr: {0}", _configFileOptions.CurrentValue.Port);
OpenFirewallPort(_configFileOptions.CurrentValue.Port);
}
if (_configFileProvider.EnableSsl && !IsNzbDronePortOpen(_configFileProvider.SslPort))
if (_configFileOptions.CurrentValue.EnableSsl && !IsNzbDronePortOpen(_configFileOptions.CurrentValue.SslPort))
{
_logger.Debug("Opening SSL Port for Radarr: {0}", _configFileProvider.SslPort);
OpenFirewallPort(_configFileProvider.SslPort);
_logger.Debug("Opening SSL Port for Radarr: {0}", _configFileOptions.CurrentValue.SslPort);
OpenFirewallPort(_configFileOptions.CurrentValue.SslPort);
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes;
@@ -14,7 +15,7 @@ namespace NzbDrone.Host
public class AppLifetime : IHostedService, IHandle<ApplicationShutdownRequested>
{
private readonly IHostApplicationLifetime _appLifetime;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IStartupContext _startupContext;
private readonly IBrowserService _browserService;
@@ -23,7 +24,7 @@ namespace NzbDrone.Host
private readonly Logger _logger;
public AppLifetime(IHostApplicationLifetime appLifetime,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
IRuntimeInfo runtimeInfo,
IStartupContext startupContext,
IBrowserService browserService,
@@ -32,7 +33,7 @@ namespace NzbDrone.Host
Logger logger)
{
_appLifetime = appLifetime;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_runtimeInfo = runtimeInfo;
_startupContext = startupContext;
_browserService = browserService;
@@ -59,7 +60,7 @@ namespace NzbDrone.Host
_runtimeInfo.IsExiting = false;
if (!_startupContext.Flags.Contains(StartupContext.NO_BROWSER)
&& _configFileProvider.LaunchBrowser)
&& _configFileOptions.CurrentValue.LaunchBrowser)
{
_browserService.LaunchWebUI();
}

View File

@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
@@ -23,7 +21,6 @@ using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Host;
using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions;
namespace Radarr.Host
{
@@ -81,12 +78,17 @@ namespace Radarr.Host
// Utility mode
default:
{
new Container(rules => rules.WithNzbDroneRules())
.AutoAddServices(ASSEMBLIES)
.AddNzbDroneLogger()
.AddStartupContext(startupContext)
.Resolve<UtilityModeRouter>()
.Route(appMode);
new HostBuilder()
.UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
.ConfigureContainer<IContainer>(c =>
{
c.AutoAddServices(ASSEMBLIES)
.AddNzbDroneLogger()
.AddDatabase()
.AddStartupContext(startupContext)
.Resolve<UtilityModeRouter>()
.Route(appMode);
}).Build();
break;
}
}
@@ -104,55 +106,20 @@ namespace Radarr.Host
public static IHostBuilder CreateConsoleHostBuilder(string[] args, StartupContext context)
{
var config = GetConfiguration(context);
var bindAddress = config.GetValue(nameof(ConfigFileProvider.BindAddress), "*");
var port = config.GetValue(nameof(ConfigFileProvider.Port), 7878);
var sslPort = config.GetValue(nameof(ConfigFileProvider.SslPort), 8787);
var enableSsl = config.GetValue(nameof(ConfigFileProvider.EnableSsl), false);
var sslCertPath = config.GetValue<string>(nameof(ConfigFileProvider.SslCertPath));
var sslCertPassword = config.GetValue<string>(nameof(ConfigFileProvider.SslCertPassword));
var urls = new List<string> { BuildUrl("http", bindAddress, port) };
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
{
urls.Add(BuildUrl("https", bindAddress, sslPort));
}
return new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.AddConfig(context)
.UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
.ConfigureContainer<IContainer>(c =>
{
c.AutoAddServices(Bootstrap.ASSEMBLIES)
c.AutoAddServices(ASSEMBLIES)
.AddNzbDroneLogger()
.AddDatabase()
.AddStartupContext(context);
})
.ConfigureServices(services =>
{
services.Configure<PostgresOptions>(config.GetSection("Radarr:Postgres"));
})
.ConfigureWebHost(builder =>
{
builder.UseConfiguration(config);
builder.UseUrls(urls.ToArray());
builder.UseKestrel(options =>
{
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
{
options.ConfigureHttpsDefaults(configureOptions =>
{
configureOptions.ServerCertificate = ValidateSslCertificate(sslCertPath, sslCertPassword);
});
}
});
builder.ConfigureKestrel(serverOptions =>
{
serverOptions.AllowSynchronousIO = true;
serverOptions.Limits.MaxRequestBodySize = null;
});
builder.UseKestrel();
builder.UseStartup<Startup>();
});
}
@@ -198,41 +165,15 @@ namespace Radarr.Host
return ApplicationModes.Interactive;
}
private static IConfiguration GetConfiguration(StartupContext context)
private static IHostBuilder AddConfig(this IHostBuilder builder, StartupContext context)
{
var appFolder = new AppFolderInfo(context);
return new ConfigurationBuilder()
.AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false)
return builder.ConfigureAppConfiguration((_, config) =>
{
config.AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: true)
.AddInMemoryCollection(new List<KeyValuePair<string, string>> { new ("dataProtectionFolder", appFolder.GetDataProtectionPath()) })
.AddEnvironmentVariables()
.Build();
}
private static string BuildUrl(string scheme, string bindAddress, int port)
{
return $"{scheme}://{bindAddress}:{port}";
}
private static X509Certificate2 ValidateSslCertificate(string cert, string password)
{
X509Certificate2 certificate;
try
{
certificate = new X509Certificate2(cert, password, X509KeyStorageFlags.DefaultKeySet);
}
catch (CryptographicException ex)
{
if (ex.HResult == 0x2 || ex.HResult == 0x2006D080)
{
throw new RadarrStartupException(ex,
$"The SSL certificate file {cert} does not exist");
}
throw new RadarrStartupException(ex);
}
return certificate;
.AddEnvironmentVariables($"{BuildInfo.AppName}:");
});
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes;
@@ -14,21 +15,21 @@ namespace Radarr.Host
public class BrowserService : IBrowserService
{
private readonly IProcessProvider _processProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IRuntimeInfo _runtimeInfo;
private readonly Logger _logger;
public BrowserService(IProcessProvider processProvider, IConfigFileProvider configFileProvider, IRuntimeInfo runtimeInfo, Logger logger)
public BrowserService(IProcessProvider processProvider, IOptionsMonitor<ConfigFileOptions> configFileOptions, IRuntimeInfo runtimeInfo, Logger logger)
{
_processProvider = processProvider;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_runtimeInfo = runtimeInfo;
_logger = logger;
}
public void LaunchWebUI()
{
var url = string.Format("http://localhost:{0}", _configFileProvider.Port);
var url = string.Format("http://localhost:{0}", _configFileOptions.CurrentValue.Port);
try
{
if (_runtimeInfo.IsUserInteractive)

View File

@@ -0,0 +1,81 @@
using System;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Host
{
public class ConfigureKestrel : IConfigureOptions<KestrelServerOptions>
{
private readonly IOptionsMonitor<ConfigFileOptions> _config;
public ConfigureKestrel(IOptionsMonitor<ConfigFileOptions> config)
{
_config = config;
}
public void Configure(KestrelServerOptions options)
{
options.AllowSynchronousIO = true;
options.Limits.MaxRequestBodySize = null;
Listen(options, _config.CurrentValue.BindAddress, _config.CurrentValue.Port);
if (_config.CurrentValue.EnableSsl && _config.CurrentValue.SslCertPath.IsNotNullOrWhiteSpace())
{
options.ConfigureHttpsDefaults(opts => opts.ServerCertificate = ValidateSslCertificate(_config.CurrentValue.SslCertPath, _config.CurrentValue.SslCertPassword));
Listen(options, _config.CurrentValue.BindAddress, _config.CurrentValue.SslPort, opts => opts.UseHttps());
}
}
private static void Listen(KestrelServerOptions options, string address, int port)
{
Listen(options, address, port, _ => { });
}
private static void Listen(KestrelServerOptions options, string address, int port, Action<ListenOptions> configureListenOptions)
{
// following https://github.com/dotnet/aspnetcore/blob/d96a100bddc72606f7417b665428411388b8ac54/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs#L123
if (string.Equals(address, "localhost", StringComparison.OrdinalIgnoreCase))
{
options.ListenLocalhost(port, configureListenOptions);
}
else if (IPAddress.TryParse(address, out var endpoint))
{
options.Listen(endpoint, port, configureListenOptions);
}
else
{
options.ListenAnyIP(port, configureListenOptions);
}
}
private static X509Certificate2 ValidateSslCertificate(string cert, string password)
{
X509Certificate2 certificate;
try
{
certificate = new X509Certificate2(cert, password, X509KeyStorageFlags.DefaultKeySet);
}
catch (CryptographicException ex)
{
if (ex.HResult == 0x2 || ex.HResult == 0x2006D080)
{
throw new RadarrStartupException(ex,
$"The SSL certificate file {cert} does not exist");
}
throw new RadarrStartupException(ex);
}
return certificate;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
@@ -10,6 +11,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using NLog.Extensions.Logging;
using NzbDrone.Common.EnvironmentInfo;
@@ -44,13 +46,14 @@ namespace NzbDrone.Host
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ConfigFileOptions>(Configuration);
services.AddLogging(b =>
{
b.ClearProviders();
b.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
b.AddFilter("Microsoft.AspNetCore", Microsoft.Extensions.Logging.LogLevel.Warning);
b.SetMinimumLevel(LogLevel.Trace);
b.AddFilter("Microsoft.AspNetCore", LogLevel.Warning);
b.AddFilter("Radarr.Http.Authentication", LogLevel.Information);
b.AddFilter("Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager", LogLevel.Error);
b.AddNLog();
});
@@ -213,7 +216,8 @@ namespace NzbDrone.Host
ReconfigureLogging reconfigureLogging,
IAppFolderFactory appFolderFactory,
IProvidePidFile pidFileProvider,
IConfigFileProvider configFileProvider,
IConfigFileWriter configFileWriter,
IOptions<ConfigFileOptions> configFileOptions,
IRuntimeInfo runtimeInfo,
IFirewallAdapter firewallAdapter,
RadarrErrorPipeline errorHandler)
@@ -221,6 +225,7 @@ namespace NzbDrone.Host
initializeLogger.Initialize();
appFolderFactory.Register();
pidFileProvider.Write();
configFileWriter.EnsureDefaultConfigFile();
reconfigureLogging.Reconfigure();
@@ -244,7 +249,7 @@ namespace NzbDrone.Host
app.UseForwardedHeaders();
app.UseMiddleware<LoggingMiddleware>();
app.UsePathBase(new PathString(configFileProvider.UrlBase));
app.UsePathBase(new PathString(configFileOptions.Value.UrlBase));
app.UseExceptionHandler(new ExceptionHandlerOptions
{
AllowStatusCode404Response = true,
@@ -259,7 +264,7 @@ namespace NzbDrone.Host
app.Properties["host.AppName"] = BuildInfo.AppName;
app.UseMiddleware<VersionMiddleware>();
app.UseMiddleware<UrlBaseMiddleware>(configFileProvider.UrlBase);
app.UseMiddleware<UrlBaseMiddleware>(configFileOptions.Value.UrlBase);
app.UseMiddleware<CacheHeaderMiddleware>();
app.UseMiddleware<IfModifiedMiddleware>();
app.UseMiddleware<BufferingMiddleware>(new List<string> { "/api/v3/command" });

View File

@@ -5,6 +5,7 @@ using NLog;
using Npgsql;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Indexers.Newznab;
@@ -25,7 +26,7 @@ namespace NzbDrone.Integration.Test
protected int Port { get; private set; }
protected PostgresOptions PostgresOptions { get; set; } = new ();
protected ConfigFileOptions Options { get; set; } = new ();
protected override string RootUrl => $"http://localhost:{Port}/";
@@ -35,14 +36,14 @@ namespace NzbDrone.Integration.Test
{
Port = Interlocked.Increment(ref StaticPort);
PostgresOptions = PostgresDatabase.GetTestOptions();
Options = PostgresDatabase.GetTestOptions();
if (PostgresOptions?.Host != null)
if (Options?.PostgresHost != null)
{
CreatePostgresDb(PostgresOptions);
CreatePostgresDb(Options);
}
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), PostgresOptions, Port);
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), Options, Port);
_runner.Kill();
_runner.Start();
@@ -74,19 +75,19 @@ namespace NzbDrone.Integration.Test
protected override void StopTestTarget()
{
_runner.Kill();
if (PostgresOptions?.Host != null)
if (Options?.PostgresHost != null)
{
DropPostgresDb(PostgresOptions);
DropPostgresDb(Options);
}
}
private static void CreatePostgresDb(PostgresOptions options)
private static void CreatePostgresDb(ConfigFileOptions options)
{
PostgresDatabase.Create(options, MigrationType.Main);
PostgresDatabase.Create(options, MigrationType.Log);
}
private static void DropPostgresDb(PostgresOptions options)
private static void DropPostgresDb(ConfigFileOptions options)
{
PostgresDatabase.Drop(options, MigrationType.Main);
PostgresDatabase.Drop(options, MigrationType.Log);

View File

@@ -3,17 +3,11 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using DryIoc;
using Moq;
using Moq.Language.Flow;
using NzbDrone.Common.Composition;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Test.Common.AutoMoq.Unity;
using Unity;
[assembly: InternalsVisibleTo("AutoMoq.Tests")]
namespace NzbDrone.Test.Common.AutoMoq
{
@@ -21,32 +15,18 @@ namespace NzbDrone.Test.Common.AutoMoq
public class AutoMoqer
{
public readonly MockBehavior DefaultBehavior = MockBehavior.Default;
public Type ResolveType;
private IUnityContainer _container;
private IContainer _container;
private IDictionary<Type, object> _registeredMocks;
public AutoMoqer()
{
SetupAutoMoqer(new UnityContainer());
}
public AutoMoqer(MockBehavior defaultBehavior)
{
DefaultBehavior = defaultBehavior;
SetupAutoMoqer(new UnityContainer());
}
public AutoMoqer(IUnityContainer container)
{
SetupAutoMoqer(container);
SetupAutoMoqer(CreateTestContainer(new Container()));
}
public virtual T Resolve<T>()
{
ResolveType = typeof(T);
var result = _container.Resolve<T>();
SetConstant(result);
ResolveType = null;
return result;
}
@@ -59,7 +39,6 @@ namespace NzbDrone.Test.Common.AutoMoq
public virtual Mock<T> GetMock<T>(MockBehavior behavior)
where T : class
{
ResolveType = null;
var type = GetTheMockType<T>();
if (GetMockHasNotBeenCalledForThisType(type))
{
@@ -78,90 +57,83 @@ namespace NzbDrone.Test.Common.AutoMoq
public virtual void SetMock(Type type, Mock mock)
{
if (_registeredMocks.ContainsKey(type) == false)
if (GetMockHasNotBeenCalledForThisType(type))
{
_registeredMocks.Add(type, mock);
}
if (mock != null)
{
_container.RegisterInstance(type, mock.Object);
_container.RegisterInstance(type, mock.Object, ifAlreadyRegistered: IfAlreadyRegistered.Replace);
}
}
public virtual void SetConstant<T>(T instance)
{
_container.RegisterInstance(instance);
_container.RegisterInstance(instance, ifAlreadyRegistered: IfAlreadyRegistered.Replace);
SetMock(instance.GetType(), null);
}
public ISetup<T> Setup<T>(Expression<Action<T>> expression)
where T : class
private IContainer CreateTestContainer(IContainer container)
{
return GetMock<T>().Setup(expression);
var c = container.CreateChild(IfAlreadyRegistered.Replace,
container.Rules
.WithDynamicRegistration((serviceType, serviceKey) =>
{
// ignore services with non-default key
if (serviceKey != null)
{
return null;
}
if (serviceType == typeof(object))
{
return null;
}
if (serviceType.IsGenericType && serviceType.IsOpenGeneric())
{
return null;
}
// get the Mock object for the abstract class or interface
if (serviceType.IsInterface || serviceType.IsAbstract)
{
var mockType = typeof(Mock<>).MakeGenericType(serviceType);
var mockFactory = new DelegateFactory(r =>
{
var mock = (Mock)r.Resolve(mockType);
SetMock(serviceType, mock);
return mock.Object;
}, Reuse.Singleton);
return new[] { new DynamicRegistration(mockFactory, IfAlreadyRegistered.Keep) };
}
// concrete types
var concreteTypeFactory = serviceType.ToFactory(Reuse.Singleton, FactoryMethod.ConstructorWithResolvableArgumentsIncludingNonPublic);
return new[] { new DynamicRegistration(concreteTypeFactory) };
},
DynamicRegistrationFlags.Service | DynamicRegistrationFlags.AsFallback));
c.Register(typeof(Mock<>), Reuse.Singleton, FactoryMethod.DefaultConstructor());
return c;
}
public ISetup<T, TResult> Setup<T, TResult>(Expression<Func<T, TResult>> expression)
where T : class
{
return GetMock<T>().Setup(expression);
}
public void Verify<T>(Expression<Action<T>> expression)
where T : class
{
GetMock<T>().Verify(expression);
}
public void Verify<T>(Expression<Action<T>> expression, string failMessage)
where T : class
{
GetMock<T>().Verify(expression, failMessage);
}
public void Verify<T>(Expression<Action<T>> expression, Times times)
where T : class
{
GetMock<T>().Verify(expression, times);
}
public void Verify<T>(Expression<Action<T>> expression, Times times, string failMessage)
where T : class
{
GetMock<T>().Verify(expression, times, failMessage);
}
public void VerifyAllMocks()
{
foreach (var registeredMock in _registeredMocks)
{
var mock = registeredMock.Value as Mock;
if (mock != null)
{
mock.VerifyAll();
}
}
}
private void SetupAutoMoqer(IUnityContainer container)
private void SetupAutoMoqer(IContainer container)
{
_container = container;
container.RegisterInstance(this);
_registeredMocks = new Dictionary<Type, object>();
RegisterPlatformLibrary(container);
AddTheAutoMockingContainerExtensionToTheContainer(container);
LoadPlatformLibrary();
AssemblyLoader.RegisterNativeResolver(new[] { "System.Data.SQLite", "Radarr.Core" });
}
private static void AddTheAutoMockingContainerExtensionToTheContainer(IUnityContainer container)
{
container.AddNewExtension<AutoMockingContainerExtension>();
return;
}
private Mock<T> TheRegisteredMockForThisType<T>(Type type)
where T : class
{
@@ -178,7 +150,7 @@ namespace NzbDrone.Test.Common.AutoMoq
private bool GetMockHasNotBeenCalledForThisType(Type type)
{
return _registeredMocks.ContainsKey(type) == false;
return !_registeredMocks.ContainsKey(type);
}
private static Type GetTheMockType<T>()
@@ -187,7 +159,7 @@ namespace NzbDrone.Test.Common.AutoMoq
return typeof(T);
}
private void RegisterPlatformLibrary(IUnityContainer container)
private void LoadPlatformLibrary()
{
var assemblyName = "Radarr.Windows";

View File

@@ -1,80 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Moq;
using Unity;
using Unity.Builder;
using Unity.Strategies;
namespace NzbDrone.Test.Common.AutoMoq.Unity
{
public class AutoMockingBuilderStrategy : BuilderStrategy
{
private readonly IUnityContainer _container;
private readonly MockRepository _mockFactory;
private readonly IEnumerable<Type> _registeredTypes;
public AutoMockingBuilderStrategy(IEnumerable<Type> registeredTypes, IUnityContainer container)
{
var autoMoqer = container.Resolve<AutoMoqer>();
_mockFactory = new MockRepository(autoMoqer.DefaultBehavior);
_registeredTypes = registeredTypes;
_container = container;
}
public override void PreBuildUp(ref BuilderContext context)
{
var autoMoqer = _container.Resolve<AutoMoqer>();
var type = GetTheTypeFromTheBuilderContext(context);
if (AMockObjectShouldBeCreatedForThisType(type))
{
var mock = CreateAMockObject(type);
context.Existing = mock.Object;
autoMoqer.SetMock(type, mock);
}
}
private bool AMockObjectShouldBeCreatedForThisType(Type type)
{
var mocker = _container.Resolve<AutoMoqer>();
return TypeIsNotRegistered(type) && (mocker.ResolveType == null || mocker.ResolveType != type);
}
private static Type GetTheTypeFromTheBuilderContext(BuilderContext context)
{
// return (context.OriginalBuildKey).Type;
return context.Type;
}
private bool TypeIsNotRegistered(Type type)
{
return _registeredTypes.Any(x => x.Equals(type)) == false;
}
private Mock CreateAMockObject(Type type)
{
var createMethod = GenerateAnInterfaceMockCreationMethod(type);
return InvokeTheMockCreationMethod(createMethod);
}
private Mock InvokeTheMockCreationMethod(MethodInfo createMethod)
{
return (Mock)createMethod.Invoke(_mockFactory, new object[] { new List<object>().ToArray() });
}
private MethodInfo GenerateAnInterfaceMockCreationMethod(Type type)
{
var createMethodWithNoParameters = _mockFactory.GetType().GetMethod("Create", EmptyArgumentList());
return createMethodWithNoParameters.MakeGenericMethod(new[] { type });
}
private static Type[] EmptyArgumentList()
{
return new[] { typeof(object[]) };
}
}
}

View File

@@ -1,35 +0,0 @@
using System;
using System.Collections.Generic;
using Unity.Builder;
using Unity.Extension;
namespace NzbDrone.Test.Common.AutoMoq.Unity
{
public class AutoMockingContainerExtension : UnityContainerExtension
{
private readonly IList<Type> _registeredTypes = new List<Type>();
protected override void Initialize()
{
SetEventsOnContainerToTrackAllRegisteredTypes();
SetBuildingStrategyForBuildingUnregisteredTypes();
}
private void SetEventsOnContainerToTrackAllRegisteredTypes()
{
Context.Registering += (sender, e) => RegisterType(e.TypeFrom);
Context.RegisteringInstance += (sender, e) => RegisterType(e.RegisteredType);
}
private void RegisterType(Type typeToRegister)
{
_registeredTypes.Add(typeToRegister);
}
private void SetBuildingStrategyForBuildingUnregisteredTypes()
{
var strategy = new AutoMockingBuilderStrategy(_registeredTypes, Container);
Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using Npgsql;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Migration.Framework;
@@ -7,18 +8,18 @@ namespace NzbDrone.Test.Common.Datastore
{
public static class PostgresDatabase
{
public static PostgresOptions GetTestOptions()
public static ConfigFileOptions GetTestOptions()
{
var options = PostgresOptions.GetOptions();
var options = ConfigFileOptions.GetOptions();
var uid = TestBase.GetUID();
options.MainDb = uid + "_main";
options.LogDb = uid + "_log";
options.PostgresMainDb = uid + "_main";
options.PostgresLogDb = uid + "_log";
return options;
}
public static void Create(PostgresOptions options, MigrationType migrationType)
public static void Create(ConfigFileOptions options, MigrationType migrationType)
{
var db = GetDatabaseName(options, migrationType);
var connectionString = GetConnectionString(options);
@@ -26,11 +27,11 @@ namespace NzbDrone.Test.Common.Datastore
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = $"CREATE DATABASE \"{db}\" WITH OWNER = {options.User} ENCODING = 'UTF8' CONNECTION LIMIT = -1;";
cmd.CommandText = $"CREATE DATABASE \"{db}\" WITH OWNER = {options.PostgresUser} ENCODING = 'UTF8' CONNECTION LIMIT = -1;";
cmd.ExecuteNonQuery();
}
public static void Drop(PostgresOptions options, MigrationType migrationType)
public static void Drop(ConfigFileOptions options, MigrationType migrationType)
{
var db = GetDatabaseName(options, migrationType);
var connectionString = GetConnectionString(options);
@@ -42,26 +43,26 @@ namespace NzbDrone.Test.Common.Datastore
cmd.ExecuteNonQuery();
}
private static string GetConnectionString(PostgresOptions options)
private static string GetConnectionString(ConfigFileOptions options)
{
var builder = new NpgsqlConnectionStringBuilder()
{
Host = options.Host,
Port = options.Port,
Username = options.User,
Password = options.Password,
Host = options.PostgresHost,
Port = options.PostgresPort,
Username = options.PostgresUser,
Password = options.PostgresPassword,
Enlist = false
};
return builder.ConnectionString;
}
private static string GetDatabaseName(PostgresOptions options, MigrationType migrationType)
private static string GetDatabaseName(ConfigFileOptions options, MigrationType migrationType)
{
return migrationType switch
{
MigrationType.Main => options.MainDb,
MigrationType.Log => options.LogDb,
MigrationType.Main => options.PostgresMainDb,
MigrationType.Log => options.PostgresLogDb,
_ => throw new NotImplementedException("Unknown migration type")
};
}

View File

@@ -25,15 +25,15 @@ namespace NzbDrone.Test.Common
public string AppData { get; private set; }
public string ApiKey { get; private set; }
public PostgresOptions PostgresOptions { get; private set; }
public ConfigFileOptions Options { get; private set; }
public int Port { get; private set; }
public NzbDroneRunner(Logger logger, PostgresOptions postgresOptions, int port = 7878)
public NzbDroneRunner(Logger logger, ConfigFileOptions options, int port = 7878)
{
_processProvider = new ProcessProvider(logger);
_restClient = new RestClient($"http://localhost:{port}/api/v3");
PostgresOptions = postgresOptions;
Options = options;
Port = port;
}
@@ -138,14 +138,14 @@ namespace NzbDrone.Test.Common
private void Start(string outputRadarrConsoleExe)
{
StringDictionary envVars = new ();
if (PostgresOptions?.Host != null)
if (Options?.PostgresHost != null)
{
envVars.Add("Radarr__Postgres__Host", PostgresOptions.Host);
envVars.Add("Radarr__Postgres__Port", PostgresOptions.Port.ToString());
envVars.Add("Radarr__Postgres__User", PostgresOptions.User);
envVars.Add("Radarr__Postgres__Password", PostgresOptions.Password);
envVars.Add("Radarr__Postgres__MainDb", PostgresOptions.MainDb);
envVars.Add("Radarr__Postgres__LogDb", PostgresOptions.LogDb);
envVars.Add("Radarr__PostgresHost", Options.PostgresHost);
envVars.Add("Radarr__PostgresPort", Options.PostgresPort.ToString());
envVars.Add("Radarr__PostgresUser", Options.PostgresUser);
envVars.Add("Radarr__PostgresPassword", Options.PostgresPassword);
envVars.Add("Radarr__PostgresMainDb", Options.PostgresMainDb);
envVars.Add("Radarr__PostgresLogDb", Options.PostgresLogDb);
TestContext.Progress.WriteLine("Using env vars:\n{0}", envVars.ToJson());
}
@@ -175,11 +175,11 @@ namespace NzbDrone.Test.Common
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.LogLevel), "trace"),
new XElement(nameof(ConfigFileProvider.AnalyticsEnabled), false),
new XElement(nameof(ConfigFileProvider.Port), Port)));
new XElement(ConfigFileWriter.CONFIG_ELEMENT_NAME,
new XElement(nameof(ConfigFileOptions.ApiKey), apiKey),
new XElement(nameof(ConfigFileOptions.LogLevel), "trace"),
new XElement(nameof(ConfigFileOptions.AnalyticsEnabled), false),
new XElement(nameof(ConfigFileOptions.Port), Port)));
var data = xDoc.ToString();

View File

@@ -9,7 +9,6 @@
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="RestSharp" Version="106.15.0" />
<PackageReference Include="Unity" Version="5.11.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Common\Radarr.Common.csproj" />

View File

@@ -4,6 +4,7 @@ using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
@@ -19,16 +20,19 @@ namespace Radarr.Api.V3.Config
[V3ApiController("config/host")]
public class HostConfigController : RestController<HostConfigResource>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IConfigFileWriter _configFileWriter;
private readonly IConfigService _configService;
private readonly IUserService _userService;
public HostConfigController(IConfigFileProvider configFileProvider,
public HostConfigController(IOptionsMonitor<ConfigFileOptions> configFileOptions,
IConfigFileWriter configFileWriter,
IConfigService configService,
IUserService userService,
FileExistsValidator fileExistsValidator)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_configFileWriter = configFileWriter;
_configService = configService;
_userService = userService;
@@ -87,7 +91,7 @@ namespace Radarr.Api.V3.Config
[HttpGet]
public HostConfigResource GetHostConfig()
{
var resource = _configFileProvider.ToResource(_configService);
var resource = _configFileOptions.CurrentValue.ToResource(_configService);
resource.Id = 1;
var user = _userService.FindUser();
@@ -107,7 +111,7 @@ namespace Radarr.Api.V3.Config
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
_configFileProvider.SaveConfigDictionary(dictionary);
_configFileWriter.SaveConfigDictionary(dictionary);
_configService.SaveConfigDictionary(dictionary);
if (resource.Username.IsNotNullOrWhiteSpace() && resource.Password.IsNotNullOrWhiteSpace())

View File

@@ -45,7 +45,7 @@ namespace Radarr.Api.V3.Config
public static class HostConfigResourceMapper
{
public static HostConfigResource ToResource(this IConfigFileProvider model, IConfigService configService)
public static HostConfigResource ToResource(this ConfigFileOptions model, IConfigService configService)
{
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead?
return new HostConfigResource

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -16,8 +17,8 @@ namespace Radarr.Api.V3.Logs
public LogFileController(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider)
: base(diskProvider, configFileProvider, "")
IOptionsMonitor<ConfigFileOptions> configFileOptions)
: base(diskProvider, configFileOptions, "")
{
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
@@ -14,14 +15,14 @@ namespace Radarr.Api.V3.Logs
protected string _resource;
private readonly IDiskProvider _diskProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public LogFileControllerBase(IDiskProvider diskProvider,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
string resource)
{
_diskProvider = diskProvider;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_resource = resource;
}
@@ -42,8 +43,8 @@ namespace Radarr.Api.V3.Logs
Id = i + 1,
Filename = filename,
LastWriteTime = _diskProvider.FileGetLastWrite(file),
ContentsUrl = string.Format("{0}/api/v1/{1}/{2}", _configFileProvider.UrlBase, _resource, filename),
DownloadUrl = string.Format("{0}/{1}/{2}", _configFileProvider.UrlBase, DownloadUrlRoot, filename)
ContentsUrl = string.Format("{0}/api/v1/{1}/{2}", _configFileOptions.CurrentValue.UrlBase, _resource, filename),
DownloadUrl = string.Format("{0}/{1}/{2}", _configFileOptions.CurrentValue.UrlBase, DownloadUrlRoot, filename)
});
}

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Options;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@@ -18,8 +19,8 @@ namespace Radarr.Api.V3.Logs
public UpdateLogFileController(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider)
: base(diskProvider, configFileProvider, "update")
IOptionsMonitor<ConfigFileOptions> configFileOptions)
: base(diskProvider, configFileOptions, "update")
{
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.Extensions.Options;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
@@ -20,7 +21,7 @@ namespace Radarr.Api.V3.System
private readonly IRuntimeInfo _runtimeInfo;
private readonly IPlatformInfo _platformInfo;
private readonly IOsInfo _osInfo;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IMainDatabase _database;
private readonly ILifecycleService _lifecycleService;
private readonly IDeploymentInfoProvider _deploymentInfoProvider;
@@ -32,7 +33,7 @@ namespace Radarr.Api.V3.System
IRuntimeInfo runtimeInfo,
IPlatformInfo platformInfo,
IOsInfo osInfo,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
IMainDatabase database,
ILifecycleService lifecycleService,
IDeploymentInfoProvider deploymentInfoProvider,
@@ -44,7 +45,7 @@ namespace Radarr.Api.V3.System
_runtimeInfo = runtimeInfo;
_platformInfo = platformInfo;
_osInfo = osInfo;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_database = database;
_lifecycleService = lifecycleService;
_deploymentInfoProvider = deploymentInfoProvider;
@@ -59,7 +60,7 @@ namespace Radarr.Api.V3.System
return new
{
AppName = BuildInfo.AppName,
InstanceName = _configFileProvider.InstanceName,
InstanceName = _configFileOptions.CurrentValue.InstanceName,
Version = BuildInfo.Version.ToString(),
BuildTime = BuildInfo.BuildDateTime,
IsDebug = BuildInfo.IsDebug,
@@ -76,12 +77,12 @@ namespace Radarr.Api.V3.System
IsWindows = OsInfo.IsWindows,
IsDocker = _osInfo.IsDocker,
Mode = _runtimeInfo.Mode,
Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationMethod,
Branch = _configFileOptions.CurrentValue.Branch,
Authentication = _configFileOptions.CurrentValue.AuthenticationMethod,
DatabaseType = _database.DatabaseType,
DatabaseVersion = _database.Version,
MigrationVersion = _database.Migration,
UrlBase = _configFileProvider.UrlBase,
UrlBase = _configFileOptions.CurrentValue.UrlBase,
RuntimeVersion = _platformInfo.Version,
RuntimeName = PlatformInfo.Platform,
StartTime = _runtimeInfo.StartTime,

View File

@@ -8313,6 +8313,14 @@
},
"components": {
"schemas": {
"AddMovieMethod": {
"enum": [
"manual",
"list",
"collection"
],
"type": "string"
},
"AddMovieOptions": {
"type": "object",
"properties": {
@@ -8327,6 +8335,9 @@
},
"searchForMovie": {
"type": "boolean"
},
"addMethod": {
"$ref": "#/components/schemas/AddMovieMethod"
}
},
"additionalProperties": false
@@ -11207,6 +11218,9 @@
"onRename": {
"type": "boolean"
},
"onMovieAdded": {
"type": "boolean"
},
"onMovieDelete": {
"type": "boolean"
},
@@ -11234,6 +11248,9 @@
"supportsOnRename": {
"type": "boolean"
},
"supportsOnMovieAdded": {
"type": "boolean"
},
"supportsOnMovieDelete": {
"type": "boolean"
},

View File

@@ -28,10 +28,10 @@ namespace Radarr.Http.Authentication
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IConfigFileProvider config)
IOptionsMonitor<ConfigFileOptions> config)
: base(options, logger, encoder, clock)
{
_apiKey = config.ApiKey;
_apiKey = config.CurrentValue.ApiKey;
}
private string ParseApiKey()

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
@@ -14,12 +15,12 @@ namespace Radarr.Http.Authentication
public class AuthenticationController : Controller
{
private readonly IAuthenticationService _authService;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public AuthenticationController(IAuthenticationService authService, IConfigFileProvider configFileProvider)
public AuthenticationController(IAuthenticationService authService, IOptionsMonitor<ConfigFileOptions> configFileOptions)
{
_authService = authService;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
[HttpPost("login")]
@@ -46,7 +47,7 @@ namespace Radarr.Http.Authentication
await HttpContext.SignInAsync(AuthenticationType.Forms.ToString(), new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties);
return Redirect(_configFileProvider.UrlBase + "/");
return Redirect(_configFileOptions.CurrentValue.UrlBase + "/");
}
[HttpGet("logout")]
@@ -54,7 +55,7 @@ namespace Radarr.Http.Authentication
{
_authService.Logout(HttpContext);
await HttpContext.SignOutAsync(AuthenticationType.Forms.ToString());
return Redirect(_configFileProvider.UrlBase + "/");
return Redirect(_configFileOptions.CurrentValue.UrlBase + "/");
}
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
@@ -22,11 +23,11 @@ namespace Radarr.Http.Authentication
private static string API_KEY;
private static AuthenticationType AUTH_METHOD;
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService)
public AuthenticationService(IOptionsMonitor<ConfigFileOptions> configFileOptions, IUserService userService)
{
_userService = userService;
API_KEY = configFileProvider.ApiKey;
AUTH_METHOD = configFileProvider.AuthenticationMethod;
API_KEY = configFileOptions.CurrentValue.ApiKey;
AUTH_METHOD = configFileOptions.CurrentValue.AuthenticationMethod;
}
public User Login(HttpRequest request, string username, string password)

View File

@@ -9,12 +9,12 @@ namespace NzbDrone.Http.Authentication
public class UiAuthorizationPolicyProvider : IAuthorizationPolicyProvider
{
private const string POLICY_NAME = "UI";
private readonly IConfigFileProvider _config;
private readonly IOptionsMonitor<ConfigFileOptions> _config;
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
public UiAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options,
IConfigFileProvider config)
IOptionsMonitor<ConfigFileOptions> config)
{
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
_config = config;
@@ -28,7 +28,7 @@ namespace NzbDrone.Http.Authentication
{
if (policyName.Equals(POLICY_NAME, StringComparison.OrdinalIgnoreCase))
{
var policy = new AuthorizationPolicyBuilder(_config.AuthenticationMethod.ToString())
var policy = new AuthorizationPolicyBuilder(_config.CurrentValue.AuthenticationMethod.ToString())
.RequireAuthenticatedUser();
return Task.FromResult(policy.Build());
}

View File

@@ -1,6 +1,7 @@
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Analytics;
@@ -12,21 +13,17 @@ namespace Radarr.Http.Frontend
[ApiController]
public class InitializeJsController : Controller
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
private readonly IAnalyticsService _analyticsService;
private static string _apiKey;
private static string _urlBase;
private string _generatedContent;
private string ApiKey => _configFileOptions.CurrentValue.ApiKey;
private string UrlBase => _configFileOptions.CurrentValue.UrlBase;
public InitializeJsController(IConfigFileProvider configFileProvider,
public InitializeJsController(IOptionsMonitor<ConfigFileOptions> configFileOptions,
IAnalyticsService analyticsService)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
_analyticsService = analyticsService;
_apiKey = configFileProvider.ApiKey;
_urlBase = configFileProvider.UrlBase;
}
[HttpGet("/initialize.js")]
@@ -37,28 +34,21 @@ namespace Radarr.Http.Frontend
private string GetContent()
{
if (RuntimeInfo.IsProduction && _generatedContent != null)
{
return _generatedContent;
}
var builder = new StringBuilder();
builder.AppendLine("window.Radarr = {");
builder.AppendLine($" apiRoot: '{_urlBase}/api/v3',");
builder.AppendLine($" apiKey: '{_apiKey}',");
builder.AppendLine($" apiRoot: '{UrlBase}/api/v3',");
builder.AppendLine($" apiKey: '{ApiKey}',");
builder.AppendLine($" release: '{BuildInfo.Release}',");
builder.AppendLine($" version: '{BuildInfo.Version.ToString()}',");
builder.AppendLine($" instanceName: '{_configFileProvider.InstanceName.ToString()}',");
builder.AppendLine($" branch: '{_configFileProvider.Branch.ToLower()}',");
builder.AppendLine($" instanceName: '{_configFileOptions.CurrentValue.InstanceName}',");
builder.AppendLine($" branch: '{_configFileOptions.CurrentValue.Branch.ToLower()}',");
builder.AppendLine($" analytics: {_analyticsService.IsEnabled.ToString().ToLowerInvariant()},");
builder.AppendLine($" userHash: '{HashUtil.AnonymousToken()}',");
builder.AppendLine($" urlBase: '{_urlBase}',");
builder.AppendLine($" urlBase: '{UrlBase}',");
builder.AppendLine($" isProduction: {RuntimeInfo.IsProduction.ToString().ToLowerInvariant()}");
builder.AppendLine("};");
_generatedContent = builder.ToString();
return _generatedContent;
return builder.ToString();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.IO;
using System.IO;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -9,13 +10,13 @@ namespace Radarr.Http.Frontend.Mappers
public class BrowserConfig : StaticResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public BrowserConfig(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
public BrowserConfig(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IOptionsMonitor<ConfigFileOptions> configFileOptions, Logger logger)
: base(diskProvider, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public override string Map(string resourceUrl)
@@ -23,7 +24,7 @@ namespace Radarr.Http.Frontend.Mappers
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path), "xml");
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _uiFolder, path), "xml");
}
public override bool CanHandle(string resourceUrl)

View File

@@ -1,4 +1,5 @@
using System.IO;
using System.IO;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -9,13 +10,13 @@ namespace Radarr.Http.Frontend.Mappers
public class FaviconMapper : StaticResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public FaviconMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
public FaviconMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IOptionsMonitor<ConfigFileOptions> configFileOptions, Logger logger)
: base(diskProvider, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public override string Map(string resourceUrl)
@@ -29,7 +30,7 @@ namespace Radarr.Http.Frontend.Mappers
var path = Path.Combine("Content", "Images", "Icons", fileName);
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
return Path.Combine(_appFolderInfo.StartUpFolder, _uiFolder, path);
}
public override bool CanHandle(string resourceUrl)

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -9,19 +10,19 @@ namespace Radarr.Http.Frontend.Mappers
{
public class IndexHtmlMapper : HtmlMapperBase
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger)
: base(diskProvider, cacheBreakProviderFactory, logger)
{
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, "index.html");
UrlBase = configFileProvider.UrlBase;
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, _uiFolder, "index.html");
UrlBase = configFileOptions.CurrentValue.UrlBase;
}
public override string Map(string resourceUrl)

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -12,12 +13,12 @@ namespace Radarr.Http.Frontend.Mappers
public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
IConfigFileProvider configFileProvider,
IOptionsMonitor<ConfigFileOptions> configFileOptions,
Logger logger)
: base(diskProvider, cacheBreakProviderFactory, logger)
{
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "login.html");
UrlBase = configFileProvider.UrlBase;
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, _uiFolder, "login.html");
UrlBase = configFileOptions.CurrentValue.UrlBase;
}
public override string Map(string resourceUrl)

View File

@@ -1,4 +1,5 @@
using System.IO;
using System.IO;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -9,13 +10,13 @@ namespace Radarr.Http.Frontend.Mappers
public class ManifestMapper : StaticResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public ManifestMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
public ManifestMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IOptionsMonitor<ConfigFileOptions> configFileOptions, Logger logger)
: base(diskProvider, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public override string Map(string resourceUrl)
@@ -23,7 +24,7 @@ namespace Radarr.Http.Frontend.Mappers
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path), "json");
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _uiFolder, path), "json");
}
public override bool CanHandle(string resourceUrl)

View File

@@ -1,4 +1,5 @@
using System.IO;
using System.IO;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -9,20 +10,20 @@ namespace Radarr.Http.Frontend.Mappers
public class RobotsTxtMapper : StaticResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public RobotsTxtMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
public RobotsTxtMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IOptionsMonitor<ConfigFileOptions> configFileOptions, Logger logger)
: base(diskProvider, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public override string Map(string resourceUrl)
{
var path = Path.Combine("Content", "robots.txt");
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
return Path.Combine(_appFolderInfo.StartUpFolder, _uiFolder, path);
}
public override bool CanHandle(string resourceUrl)

View File

@@ -1,4 +1,5 @@
using System.IO;
using Microsoft.Extensions.Options;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -9,13 +10,13 @@ namespace Radarr.Http.Frontend.Mappers
public class StaticResourceMapper : StaticResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
private readonly IOptionsMonitor<ConfigFileOptions> _configFileOptions;
public StaticResourceMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
public StaticResourceMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IOptionsMonitor<ConfigFileOptions> configFileOptions, Logger logger)
: base(diskProvider, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
_configFileOptions = configFileOptions;
}
public override string Map(string resourceUrl)
@@ -23,7 +24,7 @@ namespace Radarr.Http.Frontend.Mappers
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);
return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
return Path.Combine(_appFolderInfo.StartUpFolder, _uiFolder, path);
}
public override bool CanHandle(string resourceUrl)

View File

@@ -10,6 +10,8 @@ namespace Radarr.Http.Frontend.Mappers
{
public abstract class StaticResourceMapperBase : IMapHttpRequestsToDisk
{
protected readonly string _uiFolder;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
private readonly StringComparison _caseSensitive;
@@ -22,6 +24,7 @@ namespace Radarr.Http.Frontend.Mappers
_mimeTypeProvider = new FileExtensionContentTypeProvider();
_caseSensitive = RuntimeInfo.IsProduction ? DiskProviderBase.PathStringComparison : StringComparison.OrdinalIgnoreCase;
_uiFolder = BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
}
public abstract string Map(string resourceUrl);

View File

@@ -2,10 +2,10 @@
<RunSettings>
<RunConfiguration>
<EnvironmentVariables>
<Radarr__Postgres__Host>192.168.100.5</Radarr__Postgres__Host>
<Radarr__Postgres__Port>5432</Radarr__Postgres__Port>
<Radarr__Postgres__User>abc</Radarr__Postgres__User>
<Radarr__Postgres__Password>abc</Radarr__Postgres__Password>
<Radarr__PostgresHost>192.168.100.5</Radarr__PostgresHost>
<Radarr__PostgresPort>5432</Radarr__PostgresPort>
<Radarr__PostgresUser>abc</Radarr__PostgresUser>
<Radarr__PostgresPassword>abc</Radarr__PostgresPassword>
</EnvironmentVariables>
</RunConfiguration>
</RunSettings>