diff --git a/src/Lidarr.Api.V1/CustomFormats/CustomFormatResource.cs b/src/Lidarr.Api.V1/CustomFormats/CustomFormatResource.cs index c5f5dbee8..48f663149 100644 --- a/src/Lidarr.Api.V1/CustomFormats/CustomFormatResource.cs +++ b/src/Lidarr.Api.V1/CustomFormats/CustomFormatResource.cs @@ -65,7 +65,10 @@ namespace Lidarr.Api.V1.CustomFormats var type = matchingSpec.GetType(); - var spec = (ICustomFormatSpecification)SchemaBuilder.ReadFromSchema(resource.Fields, type); + // Finding the exact current specification isn't possible given the dynamic nature of them and the possibility that multiple + // of the same type exist within the same format. Passing in null is safe as long as there never exists a specification that + // relies on additional privacy. + var spec = (ICustomFormatSpecification)SchemaBuilder.ReadFromSchema(resource.Fields, type, null); spec.Name = resource.Name; spec.Negate = resource.Negate; spec.Required = resource.Required; diff --git a/src/Lidarr.Api.V1/DownloadClient/DownloadClientResource.cs b/src/Lidarr.Api.V1/DownloadClient/DownloadClientResource.cs index 42f8d69db..0e5e60bed 100644 --- a/src/Lidarr.Api.V1/DownloadClient/DownloadClientResource.cs +++ b/src/Lidarr.Api.V1/DownloadClient/DownloadClientResource.cs @@ -32,14 +32,14 @@ namespace Lidarr.Api.V1.DownloadClient return resource; } - public override DownloadClientDefinition ToModel(DownloadClientResource resource) + public override DownloadClientDefinition ToModel(DownloadClientResource resource, DownloadClientDefinition existingDefinition) { if (resource == null) { return null; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.Enable = resource.Enable; definition.Protocol = resource.Protocol; diff --git a/src/Lidarr.Api.V1/ImportLists/ImportListResource.cs b/src/Lidarr.Api.V1/ImportLists/ImportListResource.cs index e6a56bd21..0e89d88ff 100644 --- a/src/Lidarr.Api.V1/ImportLists/ImportListResource.cs +++ b/src/Lidarr.Api.V1/ImportLists/ImportListResource.cs @@ -45,14 +45,14 @@ namespace Lidarr.Api.V1.ImportLists return resource; } - public override ImportListDefinition ToModel(ImportListResource resource) + public override ImportListDefinition ToModel(ImportListResource resource, ImportListDefinition existingDefinition) { if (resource == null) { return null; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.EnableAutomaticAdd = resource.EnableAutomaticAdd; definition.ShouldMonitor = resource.ShouldMonitor; diff --git a/src/Lidarr.Api.V1/Indexers/IndexerResource.cs b/src/Lidarr.Api.V1/Indexers/IndexerResource.cs index c5c9589eb..2514ac5da 100644 --- a/src/Lidarr.Api.V1/Indexers/IndexerResource.cs +++ b/src/Lidarr.Api.V1/Indexers/IndexerResource.cs @@ -37,14 +37,14 @@ namespace Lidarr.Api.V1.Indexers return resource; } - public override IndexerDefinition ToModel(IndexerResource resource) + public override IndexerDefinition ToModel(IndexerResource resource, IndexerDefinition existingDefinition) { if (resource == null) { return null; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.EnableRss = resource.EnableRss; definition.EnableAutomaticSearch = resource.EnableAutomaticSearch; diff --git a/src/Lidarr.Api.V1/Metadata/MetadataResource.cs b/src/Lidarr.Api.V1/Metadata/MetadataResource.cs index 8072bc444..aa655b5df 100644 --- a/src/Lidarr.Api.V1/Metadata/MetadataResource.cs +++ b/src/Lidarr.Api.V1/Metadata/MetadataResource.cs @@ -23,14 +23,14 @@ namespace Lidarr.Api.V1.Metadata return resource; } - public override MetadataDefinition ToModel(MetadataResource resource) + public override MetadataDefinition ToModel(MetadataResource resource, MetadataDefinition existingDefinition) { if (resource == null) { return null; } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.Enable = resource.Enable; diff --git a/src/Lidarr.Api.V1/Notifications/NotificationResource.cs b/src/Lidarr.Api.V1/Notifications/NotificationResource.cs index df705d42d..0f28e8140 100644 --- a/src/Lidarr.Api.V1/Notifications/NotificationResource.cs +++ b/src/Lidarr.Api.V1/Notifications/NotificationResource.cs @@ -73,14 +73,14 @@ namespace Lidarr.Api.V1.Notifications return resource; } - public override NotificationDefinition ToModel(NotificationResource resource) + public override NotificationDefinition ToModel(NotificationResource resource, NotificationDefinition existingDefinition) { if (resource == null) { return default(NotificationDefinition); } - var definition = base.ToModel(resource); + var definition = base.ToModel(resource, existingDefinition); definition.OnGrab = resource.OnGrab; definition.OnReleaseImport = resource.OnReleaseImport; diff --git a/src/Lidarr.Api.V1/ProviderControllerBase.cs b/src/Lidarr.Api.V1/ProviderControllerBase.cs index 5b5e14249..be805c5d1 100644 --- a/src/Lidarr.Api.V1/ProviderControllerBase.cs +++ b/src/Lidarr.Api.V1/ProviderControllerBase.cs @@ -143,7 +143,8 @@ namespace Lidarr.Api.V1 private TProviderDefinition GetDefinition(TProviderResource providerResource, bool validate, bool includeWarnings, bool forceValidate) { - var definition = _resourceMapper.ToModel(providerResource); + var existingDefinition = providerResource.Id > 0 ? _providerFactory.Find(providerResource.Id) : null; + var definition = _resourceMapper.ToModel(providerResource, existingDefinition); if (validate && (definition.Enable || forceValidate)) { diff --git a/src/Lidarr.Api.V1/ProviderResource.cs b/src/Lidarr.Api.V1/ProviderResource.cs index b2d501a27..0bd7aa6d3 100644 --- a/src/Lidarr.Api.V1/ProviderResource.cs +++ b/src/Lidarr.Api.V1/ProviderResource.cs @@ -44,7 +44,7 @@ namespace Lidarr.Api.V1 }; } - public virtual TProviderDefinition ToModel(TProviderResource resource) + public virtual TProviderDefinition ToModel(TProviderResource resource, TProviderDefinition existingDefinition) { if (resource == null) { @@ -64,7 +64,7 @@ namespace Lidarr.Api.V1 }; var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract); - definition.Settings = (IProviderConfig)SchemaBuilder.ReadFromSchema(resource.Fields, configContract); + definition.Settings = (IProviderConfig)SchemaBuilder.ReadFromSchema(resource.Fields, configContract, existingDefinition?.Settings); return definition; } diff --git a/src/Lidarr.Http/ClientSchema/Field.cs b/src/Lidarr.Http/ClientSchema/Field.cs index 2973521c2..2516f5ae5 100644 --- a/src/Lidarr.Http/ClientSchema/Field.cs +++ b/src/Lidarr.Http/ClientSchema/Field.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NzbDrone.Core.Annotations; namespace Lidarr.Http.ClientSchema { @@ -18,6 +19,7 @@ namespace Lidarr.Http.ClientSchema public string SelectOptionsProviderAction { get; set; } public string Section { get; set; } public string Hidden { get; set; } + public PrivacyLevel Privacy { get; set; } public string Placeholder { get; set; } public bool IsFloat { get; set; } diff --git a/src/Lidarr.Http/ClientSchema/SchemaBuilder.cs b/src/Lidarr.Http/ClientSchema/SchemaBuilder.cs index 933925054..e0ee1c359 100644 --- a/src/Lidarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Lidarr.Http/ClientSchema/SchemaBuilder.cs @@ -13,6 +13,7 @@ namespace Lidarr.Http.ClientSchema { public static class SchemaBuilder { + private const string PRIVATE_VALUE = "********"; private static Dictionary _mappings = new Dictionary(); public static List ToSchema(object model) @@ -26,7 +27,15 @@ namespace Lidarr.Http.ClientSchema foreach (var mapping in mappings) { var field = mapping.Field.Clone(); - field.Value = mapping.GetterFunc(model); + + if (field.Privacy == PrivacyLevel.ApiKey || field.Privacy == PrivacyLevel.Password) + { + field.Value = PRIVATE_VALUE; + } + else + { + field.Value = mapping.GetterFunc(model); + } result.Add(field); } @@ -34,7 +43,7 @@ namespace Lidarr.Http.ClientSchema return result.OrderBy(r => r.Order).ToList(); } - public static object ReadFromSchema(List fields, Type targetType) + public static object ReadFromSchema(List fields, Type targetType, object model) { Ensure.That(targetType, () => targetType).IsNotNull(); @@ -48,18 +57,25 @@ namespace Lidarr.Http.ClientSchema if (field != null) { - mapping.SetterFunc(target, field.Value); + // Use the Privacy property from the mapping's field as Privacy may not be set in the API request (nor is it required) + if ((mapping.Field.Privacy == PrivacyLevel.ApiKey || mapping.Field.Privacy == PrivacyLevel.Password) && + (field.Value?.ToString()?.Equals(PRIVATE_VALUE) ?? false) && + model != null) + { + var existingValue = mapping.GetterFunc(model); + + mapping.SetterFunc(target, existingValue); + } + else + { + mapping.SetterFunc(target, field.Value); + } } } return target; } - public static T ReadFromSchema(List fields) - { - return (T)ReadFromSchema(fields, typeof(T)); - } - // Ideally this function should begin a System.Linq.Expression expression tree since it's faster. // But it's probably not needed till performance issues pop up. public static FieldMapping[] GetFieldMappings(Type type) @@ -104,6 +120,7 @@ namespace Lidarr.Http.ClientSchema Advanced = fieldAttribute.Advanced, Type = fieldAttribute.Type.ToString().FirstCharToLower(), Section = fieldAttribute.Section, + Privacy = fieldAttribute.Privacy, Placeholder = fieldAttribute.Placeholder }; @@ -136,7 +153,7 @@ namespace Lidarr.Http.ClientSchema Field = field, PropertyType = propertyInfo.PropertyType, GetterFunc = t => propertyInfo.GetValue(targetSelector(t), null), - SetterFunc = (t, v) => propertyInfo.SetValue(targetSelector(t), valueConverter(v), null) + SetterFunc = (t, v) => propertyInfo.SetValue(targetSelector(t), v?.GetType() == propertyInfo.PropertyType ? v : valueConverter(v), null) }); } else