From cb98b1046886d6bce75b5deaf5588c12c6ea28de Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Fri, 10 Nov 2023 17:44:04 +0100 Subject: [PATCH] Translate fields on the backend (cherry picked from commit 48b12f5b00429a7cd218d23f0544641b0da62a06) --- .../Annotations/FieldDefinitionAttribute.cs | 24 ++++++++++ src/NzbDrone.Host/Bootstrap.cs | 3 ++ .../Client/IndexerClient.cs | 9 +++- .../IntegrationTest.cs | 26 ++++++----- src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs | 2 + .../ClientSchemaTests/SchemaBuilderFixture.cs | 13 ++++++ .../ClientSchema/SchemaBuilder.cs | 46 +++++++++++++++++-- 7 files changed, 108 insertions(+), 15 deletions(-) diff --git a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs index d35409c0b..8d31502fa 100644 --- a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs +++ b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs @@ -41,6 +41,23 @@ namespace NzbDrone.Core.Annotations public string Hint { get; set; } } + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class FieldTokenAttribute : Attribute + { + public FieldTokenAttribute(TokenField field, string label = "", string token = "", object value = null) + { + Label = label; + Field = field; + Token = token; + Value = value?.ToString(); + } + + public string Label { get; set; } + public TokenField Field { get; set; } + public string Token { get; set; } + public string Value { get; set; } + } + public class FieldSelectOption { public int Value { get; set; } @@ -82,4 +99,11 @@ namespace NzbDrone.Core.Annotations ApiKey, UserName } + + public enum TokenField + { + Label, + HelpText, + HelpTextWarning + } } diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs index 5f4a750e2..5f3bfe0b4 100644 --- a/src/NzbDrone.Host/Bootstrap.cs +++ b/src/NzbDrone.Host/Bootstrap.cs @@ -24,6 +24,7 @@ using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore.Extensions; +using Prowlarr.Http.ClientSchema; using PostgresOptions = NzbDrone.Core.Datastore.PostgresOptions; namespace NzbDrone.Host @@ -147,6 +148,8 @@ namespace NzbDrone.Host .AddNzbDroneLogger() .AddDatabase() .AddStartupContext(context); + + SchemaBuilder.Initialize(c); }) .ConfigureServices(services => { diff --git a/src/NzbDrone.Integration.Test/Client/IndexerClient.cs b/src/NzbDrone.Integration.Test/Client/IndexerClient.cs index ba88aca50..f8213f186 100644 --- a/src/NzbDrone.Integration.Test/Client/IndexerClient.cs +++ b/src/NzbDrone.Integration.Test/Client/IndexerClient.cs @@ -1,4 +1,5 @@ -using Prowlarr.Api.V1.Indexers; +using System.Collections.Generic; +using Prowlarr.Api.V1.Indexers; using RestSharp; namespace NzbDrone.Integration.Test.Client @@ -9,5 +10,11 @@ namespace NzbDrone.Integration.Test.Client : base(restClient, apiKey) { } + + public List Schema() + { + var request = BuildRequest("/schema"); + return Get>(request); + } } } diff --git a/src/NzbDrone.Integration.Test/IntegrationTest.cs b/src/NzbDrone.Integration.Test/IntegrationTest.cs index c58d35f89..cb060c56f 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTest.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTest.cs @@ -1,13 +1,14 @@ +using System; +using System.Linq; using System.Threading; using NLog; using NUnit.Framework; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Migration.Framework; -using NzbDrone.Core.Indexers.Definitions.FileList; +using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Test.Common; using NzbDrone.Test.Common.Datastore; -using Prowlarr.Http.ClientSchema; namespace NzbDrone.Integration.Test { @@ -49,16 +50,19 @@ namespace NzbDrone.Integration.Test { WaitForCompletion(() => Tasks.All().SelectList(x => x.TaskName).Contains("CheckHealth"), 20000); - Indexers.Post(new Prowlarr.Api.V1.Indexers.IndexerResource + var indexer = Indexers.Schema().FirstOrDefault(i => i.Implementation == nameof(Newznab)); + + if (indexer == null) { - Enable = false, - ConfigContract = nameof(FileListSettings), - Implementation = nameof(FileList), - Name = "NewznabTest", - Protocol = Core.Indexers.DownloadProtocol.Usenet, - AppProfileId = 1, - Fields = SchemaBuilder.ToSchema(new FileListSettings()) - }); + throw new NullReferenceException("Expected valid indexer schema, found null"); + } + + indexer.Enable = false; + indexer.ConfigContract = nameof(NewznabSettings); + indexer.Implementation = nameof(Newznab); + indexer.Name = "NewznabTest"; + indexer.Protocol = Core.Indexers.DownloadProtocol.Usenet; + indexer.AppProfileId = 1; // Change Console Log Level to Debug so we get more details. var config = HostConfig.Get(1); diff --git a/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs b/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs index 945dd3aae..821b91fa7 100644 --- a/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs +++ b/src/NzbDrone.Test.Common/AutoMoq/AutoMoqer.cs @@ -23,6 +23,8 @@ namespace NzbDrone.Test.Common.AutoMoq SetupAutoMoqer(CreateTestContainer(new Container())); } + public IContainer Container => _container; + public virtual T Resolve() { var result = _container.Resolve(); diff --git a/src/Prowlarr.Api.V1.Test/ClientSchemaTests/SchemaBuilderFixture.cs b/src/Prowlarr.Api.V1.Test/ClientSchemaTests/SchemaBuilderFixture.cs index 16b54921f..2b982e78a 100644 --- a/src/Prowlarr.Api.V1.Test/ClientSchemaTests/SchemaBuilderFixture.cs +++ b/src/Prowlarr.Api.V1.Test/ClientSchemaTests/SchemaBuilderFixture.cs @@ -1,6 +1,9 @@ +using System.Collections.Generic; using FluentAssertions; +using Moq; using NUnit.Framework; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Localization; using NzbDrone.Test.Common; using Prowlarr.Http.ClientSchema; @@ -9,6 +12,16 @@ namespace NzbDrone.Api.Test.ClientSchemaTests [TestFixture] public class SchemaBuilderFixture : TestBase { + [SetUp] + public void Setup() + { + Mocker.GetMock() + .Setup(s => s.GetLocalizedString(It.IsAny(), It.IsAny>())) + .Returns>((s, d) => s); + + SchemaBuilder.Initialize(Mocker.Container); + } + [Test] public void should_return_field_for_every_property() { diff --git a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs index 171c9db04..ddfbe40f9 100644 --- a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs @@ -3,17 +3,25 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.Json; +using DryIoc; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Common.Reflection; using NzbDrone.Common.Serializer; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Localization; namespace Prowlarr.Http.ClientSchema { public static class SchemaBuilder { private static Dictionary _mappings = new Dictionary(); + private static ILocalizationService _localizationService; + + public static void Initialize(IContainer container) + { + _localizationService = container.Resolve(); + } public static List ToSchema(object model) { @@ -93,13 +101,27 @@ namespace Prowlarr.Http.ClientSchema if (propertyInfo.PropertyType.IsSimpleType()) { var fieldAttribute = property.Item2; + + var label = fieldAttribute.Label.IsNotNullOrWhiteSpace() + ? _localizationService.GetLocalizedString(fieldAttribute.Label, + GetTokens(type, fieldAttribute.Label, TokenField.Label)) + : fieldAttribute.Label; + var helpText = fieldAttribute.HelpText.IsNotNullOrWhiteSpace() + ? _localizationService.GetLocalizedString(fieldAttribute.HelpText, + GetTokens(type, fieldAttribute.Label, TokenField.HelpText)) + : fieldAttribute.HelpText; + var helpTextWarning = fieldAttribute.HelpTextWarning.IsNotNullOrWhiteSpace() + ? _localizationService.GetLocalizedString(fieldAttribute.HelpTextWarning, + GetTokens(type, fieldAttribute.Label, TokenField.HelpTextWarning)) + : fieldAttribute.HelpTextWarning; + var field = new Field { Name = prefix + GetCamelCaseName(propertyInfo.Name), - Label = fieldAttribute.Label, + Label = label, Unit = fieldAttribute.Unit, - HelpText = fieldAttribute.HelpText, - HelpTextWarning = fieldAttribute.HelpTextWarning, + HelpText = helpText, + HelpTextWarning = helpTextWarning, HelpLink = fieldAttribute.HelpLink, Order = fieldAttribute.Order, Advanced = fieldAttribute.Advanced, @@ -158,6 +180,24 @@ namespace Prowlarr.Http.ClientSchema .ToArray(); } + private static Dictionary GetTokens(Type type, string label, TokenField field) + { + var tokens = new Dictionary(); + + foreach (var propertyInfo in type.GetProperties()) + { + foreach (var attribute in propertyInfo.GetCustomAttributes(true)) + { + if (attribute is FieldTokenAttribute fieldTokenAttribute && fieldTokenAttribute.Field == field && fieldTokenAttribute.Label == label) + { + tokens.Add(fieldTokenAttribute.Token, fieldTokenAttribute.Value); + } + } + } + + return tokens; + } + private static List GetSelectOptions(Type selectOptions) { if (selectOptions.IsEnum)