Add Config Validator + Bug Fixing

This commit is contained in:
Felix Glang
2024-04-28 12:59:44 +02:00
parent f73b3b5578
commit 0bb480b1d0
9 changed files with 148 additions and 65 deletions

View File

@@ -33,6 +33,7 @@ Einige Beispiele finden sich [weiter unten](https://github.com/PCJones/UmlautAda
| Anfragen-Caching für 12 Minuten zur Reduzierung der API-Zugriffe | ✓ | | Anfragen-Caching für 12 Minuten zur Reduzierung der API-Zugriffe | ✓ |
| Usenet (newznab) Support |✓| | Usenet (newznab) Support |✓|
| Torrent (torznab) Support |✓| | Torrent (torznab) Support |✓|
| Support von meheren *arrs Instanzen | ✓
| Radarr Support | Geplant | | Radarr Support | Geplant |
| Prowlarr Unterstützung für "DE" SceneNZBs Kategorien | Geplant | | Prowlarr Unterstützung für "DE" SceneNZBs Kategorien | Geplant |
| Unterstützung weiterer Sprachen neben Deutsch | Geplant | | Unterstützung weiterer Sprachen neben Deutsch | Geplant |

View File

@@ -53,7 +53,7 @@ internal class Program
//options.SizeLimit = 20000; //options.SizeLimit = 20000;
}); });
builder.Services.AllowResolvingKeyedServicesAsDictionary();
builder.Services.AddControllers(); builder.Services.AddControllers();
builder.AddTitleLookupService(); builder.AddTitleLookupService();
builder.Services.AddSingleton<SearchItemLookupService>(); builder.Services.AddSingleton<SearchItemLookupService>();
@@ -63,7 +63,7 @@ internal class Program
builder.AddReadarrSupport(); builder.AddReadarrSupport();
builder.Services.AddSingleton<CacheService>(); builder.Services.AddSingleton<CacheService>();
builder.Services.AddSingleton<ProxyRequestService>(); builder.Services.AddSingleton<ProxyRequestService>();
builder.Services.AddSingleton<RrApplicationFactory>(); builder.Services.AddSingleton<ArrApplicationFactory>();
builder.Services.AddHostedService<ArrSyncBackgroundService>(); builder.Services.AddHostedService<ArrSyncBackgroundService>();
builder.Services.AddSingleton<IHostedService, HttpProxyService>(); builder.Services.AddSingleton<IHostedService, HttpProxyService>();

View File

@@ -4,12 +4,12 @@ using UmlautAdaptarr.Services.Factory;
namespace UmlautAdaptarr.Services; namespace UmlautAdaptarr.Services;
public class ArrSyncBackgroundService( public class ArrSyncBackgroundService(
RrApplicationFactory rrApplicationFactory, ArrApplicationFactory arrApplicationFactory,
CacheService cacheService, CacheService cacheService,
ILogger<ArrSyncBackgroundService> logger) ILogger<ArrSyncBackgroundService> logger)
: BackgroundService : BackgroundService
{ {
public RrApplicationFactory RrApplicationFactory { get; } = rrApplicationFactory; public ArrApplicationFactory ArrApplicationFactory { get; } = arrApplicationFactory;
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@@ -56,19 +56,19 @@ public class ArrSyncBackgroundService(
var success = true; var success = true;
if (RrApplicationFactory.SonarrInstances.Any()) if (ArrApplicationFactory.SonarrInstances.Any())
{ {
var syncSuccess = await FetchItemsFromSonarrAsync(); var syncSuccess = await FetchItemsFromSonarrAsync();
success = success && syncSuccess; success = success && syncSuccess;
} }
if (RrApplicationFactory.ReadarrInstances.Any()) if (ArrApplicationFactory.ReadarrInstances.Any())
{ {
var syncSuccess = await FetchItemsFromReadarrAsync(); var syncSuccess = await FetchItemsFromReadarrAsync();
success = success && syncSuccess; success = success && syncSuccess;
} }
if (RrApplicationFactory.ReadarrInstances.Any()) if (ArrApplicationFactory.ReadarrInstances.Any())
{ {
var syncSuccess = await FetchItemsFromLidarrAsync(); var syncSuccess = await FetchItemsFromLidarrAsync();
success = success && syncSuccess; success = success && syncSuccess;
@@ -91,7 +91,7 @@ public class ArrSyncBackgroundService(
{ {
var items = new List<SearchItem>(); var items = new List<SearchItem>();
foreach (var sonarrClient in RrApplicationFactory.SonarrInstances) foreach (var sonarrClient in ArrApplicationFactory.SonarrInstances)
{ {
var result = await sonarrClient.FetchAllItemsAsync(); var result = await sonarrClient.FetchAllItemsAsync();
items = items.Union(result).ToList(); items = items.Union(result).ToList();
@@ -115,7 +115,7 @@ public class ArrSyncBackgroundService(
{ {
var items = new List<SearchItem>(); var items = new List<SearchItem>();
foreach (var lidarrClient in RrApplicationFactory.LidarrInstances) foreach (var lidarrClient in ArrApplicationFactory.LidarrInstances)
{ {
var result = await lidarrClient.FetchAllItemsAsync(); var result = await lidarrClient.FetchAllItemsAsync();
items = items.Union(result).ToList(); items = items.Union(result).ToList();
@@ -138,7 +138,7 @@ public class ArrSyncBackgroundService(
{ {
var items = new List<SearchItem>(); var items = new List<SearchItem>();
foreach (var readarrClient in RrApplicationFactory.ReadarrInstances) foreach (var readarrClient in ArrApplicationFactory.ReadarrInstances)
{ {
var result = await readarrClient.FetchAllItemsAsync(); var result = await readarrClient.FetchAllItemsAsync();
items = items.Union(result).ToList(); items = items.Union(result).ToList();

View File

@@ -6,9 +6,9 @@ namespace UmlautAdaptarr.Services.Factory
/// <summary> /// <summary>
/// Factory for creating RrApplication instances. /// Factory for creating RrApplication instances.
/// </summary> /// </summary>
public class RrApplicationFactory public class ArrApplicationFactory
{ {
private readonly ILogger<RrApplicationFactory> _logger; private readonly ILogger<ArrApplicationFactory> _logger;
/// <summary> /// <summary>
/// Get all IArrApplication instances. /// Get all IArrApplication instances.
@@ -31,10 +31,11 @@ namespace UmlautAdaptarr.Services.Factory
public IEnumerable<ReadarrClient> ReadarrInstances { get; init; } public IEnumerable<ReadarrClient> ReadarrInstances { get; init; }
/// <summary> /// <summary>
/// Constructor for the RrApplicationFactory. /// Constructor for the ArrApplicationFactory.
/// </summary> /// </summary>
/// <param name="rrArrApplications">A dictionary of IArrApplication instances.</param> /// <param name="rrArrApplications">A dictionary of IArrApplication instances.</param>
public RrApplicationFactory(IDictionary<string, IArrApplication> rrArrApplications, ILogger<RrApplicationFactory> logger) /// <param name="logger">Logger Instanz</param>
public ArrApplicationFactory(IDictionary<string, IArrApplication> rrArrApplications, ILogger<ArrApplicationFactory> logger)
{ {
_logger = logger; _logger = logger;
try try
@@ -51,7 +52,7 @@ namespace UmlautAdaptarr.Services.Factory
} }
catch (Exception e) catch (Exception e)
{ {
_logger.LogError("Register RrFactory", e.Message); _logger.LogError("Error while Register ArrFactory. This might be a Config Problem", e.Message);
throw; throw;
} }
} }

View File

@@ -5,7 +5,7 @@ using UmlautAdaptarr.Services.Factory;
namespace UmlautAdaptarr.Services namespace UmlautAdaptarr.Services
{ {
public class SearchItemLookupService(CacheService cacheService, public class SearchItemLookupService(CacheService cacheService,
RrApplicationFactory rrApplicationFactory) ArrApplicationFactory arrApplicationFactory)
{ {
public async Task<SearchItem?> GetOrFetchSearchItemByExternalId(string mediaType, string externalId) public async Task<SearchItem?> GetOrFetchSearchItemByExternalId(string mediaType, string externalId)
{ {
@@ -22,7 +22,7 @@ namespace UmlautAdaptarr.Services
{ {
case "tv": case "tv":
var sonarrInstances = rrApplicationFactory.SonarrInstances; var sonarrInstances = arrApplicationFactory.SonarrInstances;
if (sonarrInstances.Any()) if (sonarrInstances.Any())
{ {
@@ -34,7 +34,7 @@ namespace UmlautAdaptarr.Services
break; break;
case "audio": case "audio":
var lidarrInstances = rrApplicationFactory.LidarrInstances; var lidarrInstances = arrApplicationFactory.LidarrInstances;
if (lidarrInstances.Any()) if (lidarrInstances.Any())
{ {
@@ -47,7 +47,7 @@ namespace UmlautAdaptarr.Services
break; break;
case "book": case "book":
var readarrInstances = rrApplicationFactory.ReadarrInstances; var readarrInstances = arrApplicationFactory.ReadarrInstances;
if (readarrInstances.Any()) if (readarrInstances.Any())
{ {
foreach (var readarrClient in readarrInstances) foreach (var readarrClient in readarrInstances)
@@ -83,7 +83,7 @@ namespace UmlautAdaptarr.Services
{ {
case "tv": case "tv":
var sonarrInstances = rrApplicationFactory.SonarrInstances; var sonarrInstances = arrApplicationFactory.SonarrInstances;
foreach (var sonarrClient in sonarrInstances) foreach (var sonarrClient in sonarrInstances)
{ {
fetchedItem = await sonarrClient.FetchItemByTitleAsync(title); fetchedItem = await sonarrClient.FetchItemByTitleAsync(title);

View File

@@ -9,6 +9,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.1" />
<PackageReference Include="IL.FluentValidation.Extensions.Options" Version="11.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" /> <PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

View File

@@ -1,9 +1,11 @@
using System.Linq.Expressions; using FluentValidation;
using System.Linq.Expressions;
using UmlautAdaptarr.Interfaces; using UmlautAdaptarr.Interfaces;
using UmlautAdaptarr.Options; using UmlautAdaptarr.Options;
using UmlautAdaptarr.Options.ArrOptions.InstanceOptions; using UmlautAdaptarr.Options.ArrOptions.InstanceOptions;
using UmlautAdaptarr.Providers; using UmlautAdaptarr.Providers;
using UmlautAdaptarr.Services; using UmlautAdaptarr.Services;
using UmlautAdaptarr.Validator;
namespace UmlautAdaptarr.Utilities; namespace UmlautAdaptarr.Utilities;
@@ -12,6 +14,12 @@ namespace UmlautAdaptarr.Utilities;
/// </summary> /// </summary>
public static class ServicesExtensions public static class ServicesExtensions
{ {
/// <summary>
/// Logger instance for logging proxy configurations.
/// </summary>
private static ILogger Logger = GlobalStaticLogger.Logger;
/// <summary> /// <summary>
/// Adds a service with specified options and service to the service collection. /// Adds a service with specified options and service to the service collection.
/// </summary> /// </summary>
@@ -26,6 +34,9 @@ public static class ServicesExtensions
where TOptions : class, new() where TOptions : class, new()
where TService : class, TInterface where TService : class, TInterface
where TInterface : class where TInterface : class
{
try
{ {
if (builder.Services == null) throw new ArgumentNullException(nameof(builder), "Service collection is null."); if (builder.Services == null) throw new ArgumentNullException(nameof(builder), "Service collection is null.");
@@ -48,6 +59,21 @@ public static class ServicesExtensions
foreach (var option in optionsArray) foreach (var option in optionsArray)
{ {
GlobalInstanceOptionsValidator validator = new GlobalInstanceOptionsValidator();
var results = validator.Validate(option as GlobalInstanceOptions);
if (!results.IsValid)
{
foreach (var failure in results.Errors)
{
Console.WriteLine(($"Property {failure.PropertyName } failed validation. Error was: {failure.ErrorMessage}"));
}
throw new Exception("Please fix first you config and then Start UmlautAdaptarr again");
}
var instanceState = (bool)(typeof(TOptions).GetProperty("Enabled")?.GetValue(option, null) ?? false); var instanceState = (bool)(typeof(TOptions).GetProperty("Enabled")?.GetValue(option, null) ?? false);
// We only want to create instances that are enabled in the Configs // We only want to create instances that are enabled in the Configs
@@ -74,18 +100,23 @@ public static class ServicesExtensions
} }
else else
{ {
Console.WriteLine(prop.PropertyType + "No Support"); Logger.LogWarning((prop.PropertyType + "No Support"));
} }
} }
builder.Services.AllowResolvingKeyedServicesAsDictionary();
builder.Services.AddKeyedSingleton<TInterface, TService>(instanceName); builder.Services.AddKeyedSingleton<TInterface, TService>(instanceName);
} }
} }
return builder; return builder;
} }
catch (Exception e)
{
Console.WriteLine("Error while Init UmlautAdaptrr");
throw;
}
}
/// <summary> /// <summary>
/// Adds a service with specified options and service to the service collection. /// Adds a service with specified options and service to the service collection.

View File

@@ -0,0 +1,46 @@
using FluentValidation;
using System.Net;
using UmlautAdaptarr.Options.ArrOptions.InstanceOptions;
namespace UmlautAdaptarr.Validator
{
public class GlobalInstanceOptionsValidator : AbstractValidator<GlobalInstanceOptions>
{
public GlobalInstanceOptionsValidator()
{
RuleFor(x => x.Enabled).NotNull();
When(x => x.Enabled, () =>
{
RuleFor(x => x.Host)
.NotEmpty().WithMessage("Host is required when Enabled is true.")
.Must(BeAValidUrl).WithMessage("Host must start with http:// or https:// and be a valid address.")
.Must(BeReachable).WithMessage("Host is not reachable. Please check your Host or your UmlautAdaptrr Settings");
RuleFor(x => x.ApiKey)
.NotEmpty().WithMessage("ApiKey is required when Enabled is true.");
});
}
private bool BeAValidUrl(string url)
{
return Uri.TryCreate(url, UriKind.Absolute, out var uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
}
private bool BeReachable(string url)
{
try
{
var request = WebRequest.Create(url);
var response = (HttpWebResponse)request.GetResponse();
return response.StatusCode == HttpStatusCode.OK;
}
catch
{
return false;
}
}
}
}

View File

@@ -26,6 +26,7 @@
{ {
// Docker Environment Variables: // Docker Environment Variables:
// - Sonarr__0__Enabled: true (set to false to disable) // - Sonarr__0__Enabled: true (set to false to disable)
// - Sonarr__0__Name: Name of the Instance (Optional)
// - Sonarr__0__Host: your_sonarr_host_url // - Sonarr__0__Host: your_sonarr_host_url
// - Sonarr__0__ApiKey: your_sonarr_api_key // - Sonarr__0__ApiKey: your_sonarr_api_key
"Enabled": false, "Enabled": false,
@@ -36,6 +37,7 @@
{ {
// Docker Environment Variables: // Docker Environment Variables:
// - Sonarr__1__Enabled: true (set to false to disable) // - Sonarr__1__Enabled: true (set to false to disable)
// - Sonarr__0__Name: Name of the Instance (Optional)
// - Sonarr__1__Host: your_sonarr_host_url // - Sonarr__1__Host: your_sonarr_host_url
// - Sonarr__1__ApiKey: your_sonarr_api_key // - Sonarr__1__ApiKey: your_sonarr_api_key
"Enabled": false, "Enabled": false,