diff --git a/src/Sonarr.Api.V3/CustomFormats/CustomFormatResource.cs b/src/Sonarr.Api.V3/CustomFormats/CustomFormatResource.cs index e6962c0e2..c8870aa7e 100644 --- a/src/Sonarr.Api.V3/CustomFormats/CustomFormatResource.cs +++ b/src/Sonarr.Api.V3/CustomFormats/CustomFormatResource.cs @@ -48,7 +48,11 @@ namespace Sonarr.Api.V3.CustomFormats private static ICustomFormatSpecification MapSpecification(CustomFormatSpecificationSchema resource, List specifications) { var type = specifications.SingleOrDefault(x => x.GetType().Name == resource.Implementation).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/Sonarr.Api.V3/DownloadClient/DownloadClientResource.cs b/src/Sonarr.Api.V3/DownloadClient/DownloadClientResource.cs index 85372aedb..d94e8a36f 100644 --- a/src/Sonarr.Api.V3/DownloadClient/DownloadClientResource.cs +++ b/src/Sonarr.Api.V3/DownloadClient/DownloadClientResource.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Download; +using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; namespace Sonarr.Api.V3.DownloadClient @@ -32,14 +32,14 @@ namespace Sonarr.Api.V3.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/Sonarr.Api.V3/ImportLists/ImportListResource.cs b/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs index 7f033e0c3..c98e5c316 100644 --- a/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs +++ b/src/Sonarr.Api.V3/ImportLists/ImportListResource.cs @@ -40,14 +40,14 @@ namespace Sonarr.Api.V3.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/Sonarr.Api.V3/Indexers/IndexerResource.cs b/src/Sonarr.Api.V3/Indexers/IndexerResource.cs index 9816e08db..f534b0ff9 100644 --- a/src/Sonarr.Api.V3/Indexers/IndexerResource.cs +++ b/src/Sonarr.Api.V3/Indexers/IndexerResource.cs @@ -39,14 +39,14 @@ namespace Sonarr.Api.V3.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/Sonarr.Api.V3/Metadata/MetadataResource.cs b/src/Sonarr.Api.V3/Metadata/MetadataResource.cs index 8c9177ee3..634dcfc6a 100644 --- a/src/Sonarr.Api.V3/Metadata/MetadataResource.cs +++ b/src/Sonarr.Api.V3/Metadata/MetadataResource.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Extras.Metadata; +using NzbDrone.Core.Extras.Metadata; namespace Sonarr.Api.V3.Metadata { @@ -23,14 +23,14 @@ namespace Sonarr.Api.V3.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/Sonarr.Api.V3/Notifications/NotificationResource.cs b/src/Sonarr.Api.V3/Notifications/NotificationResource.cs index 8845cf126..61a5c98ec 100644 --- a/src/Sonarr.Api.V3/Notifications/NotificationResource.cs +++ b/src/Sonarr.Api.V3/Notifications/NotificationResource.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Notifications; +using NzbDrone.Core.Notifications; namespace Sonarr.Api.V3.Notifications { @@ -61,14 +61,14 @@ namespace Sonarr.Api.V3.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.OnDownload = resource.OnDownload; diff --git a/src/Sonarr.Api.V3/ProviderControllerBase.cs b/src/Sonarr.Api.V3/ProviderControllerBase.cs index 0c63d3016..ccc68418d 100644 --- a/src/Sonarr.Api.V3/ProviderControllerBase.cs +++ b/src/Sonarr.Api.V3/ProviderControllerBase.cs @@ -92,7 +92,8 @@ namespace Sonarr.Api.V3 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/Sonarr.Api.V3/ProviderResource.cs b/src/Sonarr.Api.V3/ProviderResource.cs index 774796272..09dcb8094 100644 --- a/src/Sonarr.Api.V3/ProviderResource.cs +++ b/src/Sonarr.Api.V3/ProviderResource.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NzbDrone.Common.Reflection; using NzbDrone.Core.ThingiProvider; using Sonarr.Http.ClientSchema; @@ -42,7 +42,7 @@ namespace Sonarr.Api.V3 }; } - public virtual TProviderDefinition ToModel(TProviderResource resource) + public virtual TProviderDefinition ToModel(TProviderResource resource, TProviderDefinition existingDefinition) { if (resource == null) { @@ -62,7 +62,7 @@ namespace Sonarr.Api.V3 }; 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/Sonarr.Http/ClientSchema/Field.cs b/src/Sonarr.Http/ClientSchema/Field.cs index cdc2a9d33..8c2e312e6 100644 --- a/src/Sonarr.Http/ClientSchema/Field.cs +++ b/src/Sonarr.Http/ClientSchema/Field.cs @@ -18,7 +18,7 @@ namespace Sonarr.Http.ClientSchema public string SelectOptionsProviderAction { get; set; } public string Section { get; set; } public string Hidden { get; set; } - + public PrivacyLevel Privacy { get; set; } public Field Clone() { return (Field)MemberwiseClone(); diff --git a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs index 4263cd32a..a45253f95 100644 --- a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs @@ -13,6 +13,7 @@ namespace Sonarr.Http.ClientSchema { public static class SchemaBuilder { + private static readonly string PRIVATE_VALUE = "********"; private static Dictionary _mappings = new Dictionary(); public static List ToSchema(object model) @@ -26,7 +27,15 @@ namespace Sonarr.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 Sonarr.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(); @@ -49,18 +58,25 @@ namespace Sonarr.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,7 +120,8 @@ namespace Sonarr.Http.ClientSchema Order = fieldAttribute.Order, Advanced = fieldAttribute.Advanced, Type = fieldAttribute.Type.ToString().FirstCharToLower(), - Section = fieldAttribute.Section + Section = fieldAttribute.Section, + Privacy = fieldAttribute.Privacy }; if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect) @@ -131,7 +148,7 @@ namespace Sonarr.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