using FluentValidation; using System.Linq.Expressions; using UmlautAdaptarr.Interfaces; using UmlautAdaptarr.Options; using UmlautAdaptarr.Options.ArrOptions.InstanceOptions; using UmlautAdaptarr.Providers; using UmlautAdaptarr.Services; using UmlautAdaptarr.Validator; namespace UmlautAdaptarr.Utilities; /// /// Extension methods for configuring services related to ARR Applications /// public static class ServicesExtensions { /// /// Logger instance for logging proxy configurations. /// private static ILogger Logger = GlobalStaticLogger.Logger; /// /// Adds a service with specified options and service to the service collection. /// /// The options type for the service. /// The service type for the service. /// The Interface of the service type /// The to configure the service collection. /// The name of the configuration section containing service options. /// The configured . private static WebApplicationBuilder AddServicesWithOptions( this WebApplicationBuilder builder, string sectionName) where TOptions : class, new() where TService : class, TInterface where TInterface : class { try { if (builder.Services == null) throw new ArgumentNullException(nameof(builder), "Service collection is null."); var singleInstance = builder.Configuration.GetSection(sectionName).Get(); var singleHost = (string?)typeof(TOptions).GetProperty("Host")?.GetValue(singleInstance, null); // If we have no Single Instance, we try to parse for an Array var optionsArray = singleHost == null ? builder.Configuration.GetSection(sectionName).Get() : [ singleInstance ]; if (optionsArray == null || !optionsArray.Any()) throw new InvalidOperationException( $"{typeof(TService).Name} options could not be loaded from Configuration or ENV Variable."); 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); // We only want to create instances that are enabled in the Configs if (instanceState) { // User can give the Instance a readable Name otherwise we use the Host Property var instanceName = (string)(typeof(TOptions).GetProperty("Name")?.GetValue(option, null) ?? (string)typeof(TOptions).GetProperty("Host")?.GetValue(option, null)!); // Dark Magic , we don't know the Property's of TOptions , and we won't cast them for each Options // Todo eventuell schönere Lösung finden var paraexpression = Expression.Parameter(Type.GetType(option.GetType().FullName), "x"); foreach (var prop in option.GetType().GetProperties()) { var val = Expression.Constant(prop.GetValue(option)); var memberexpression = Expression.PropertyOrField(paraexpression, prop.Name); if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(string) || prop.PropertyType == typeof(bool)) { var assign = Expression.Assign(memberexpression, Expression.Convert(val, prop.PropertyType)); var exp = Expression.Lambda>(assign, paraexpression); builder.Services.Configure(instanceName, exp.Compile()); } else { Logger.LogWarning(prop.PropertyType + "No Support"); } } builder.Services.AddKeyedSingleton(instanceName); } } return builder; } catch (Exception ex) { Console.WriteLine($"Error in AddServicesWithOptions: {ex.Message}"); throw; } } /// /// Adds a service with specified options and service to the service collection. /// /// The options type for the service. /// The service type for the service. /// The to configure the service collection. /// The name of the configuration section containing service options. /// The configured . private static WebApplicationBuilder AddServiceWithOptions(this WebApplicationBuilder builder, string sectionName) where TOptions : class where TService : class { if (builder.Services == null) throw new ArgumentNullException(nameof(builder), "Service collection is null."); var options = builder.Configuration.GetSection(sectionName).Get() ?? throw new InvalidOperationException( $"{typeof(TService).Name} options could not be loaded from Configuration or ENV Variable."); builder.Services.Configure(builder.Configuration.GetSection(sectionName)); builder.Services.AddSingleton(); return builder; } /// /// Adds support for Sonarr with default options and client. /// /// The to configure the service collection. /// The configured . public static WebApplicationBuilder AddSonarrSupport(this WebApplicationBuilder builder) { // builder.Serviceses.AddSingleton, OptionsMonitoSonarrInstanceOptionsns>>(); return builder.AddServicesWithOptions("Sonarr"); } /// /// Adds support for Lidarr with default options and client. /// /// The to configure the service collection. /// The configured . public static WebApplicationBuilder AddLidarrSupport(this WebApplicationBuilder builder) { return builder.AddServicesWithOptions("Lidarr"); } /// /// Adds support for Readarr with default options and client. /// /// The to configure the service collection. /// The configured . public static WebApplicationBuilder AddReadarrSupport(this WebApplicationBuilder builder) { return builder.AddServicesWithOptions("Readarr"); } /// /// Adds a title lookup service to the service collection. /// /// The to configure the service collection. /// The configured . public static WebApplicationBuilder AddTitleLookupService(this WebApplicationBuilder builder) { return builder.AddServiceWithOptions("Settings"); } /// /// Adds a proxy request service to the service collection. /// /// The to configure the service collection. /// The configured . public static WebApplicationBuilder AddProxyRequestService(this WebApplicationBuilder builder) { return builder.AddServiceWithOptions("Settings"); } }