diff options
Diffstat (limited to 'Jellyfin.Api/ModelBinders')
4 files changed, 213 insertions, 0 deletions
diff --git a/Jellyfin.Api/ModelBinders/LegacyDateTimeModelBinder.cs b/Jellyfin.Api/ModelBinders/LegacyDateTimeModelBinder.cs new file mode 100644 index 000000000..e1cb725f3 --- /dev/null +++ b/Jellyfin.Api/ModelBinders/LegacyDateTimeModelBinder.cs @@ -0,0 +1,49 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders +{ + /// <summary> + /// DateTime model binder. + /// </summary> + public class LegacyDateTimeModelBinder : IModelBinder + { + // Borrowed from the DateTimeModelBinderProvider + private const DateTimeStyles SupportedStyles = DateTimeStyles.AdjustToUniversal | DateTimeStyles.AllowWhiteSpaces; + private readonly DateTimeModelBinder _defaultModelBinder; + + /// <summary> + /// Initializes a new instance of the <see cref="LegacyDateTimeModelBinder"/> class. + /// </summary> + /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> + public LegacyDateTimeModelBinder(ILoggerFactory loggerFactory) + { + _defaultModelBinder = new DateTimeModelBinder(SupportedStyles, loggerFactory); + } + + /// <inheritdoc /> + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (valueProviderResult.Values.Count == 1) + { + var dateTimeString = valueProviderResult.FirstValue; + // Mark Played Item. + if (DateTime.TryParseExact(dateTimeString, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dateTime)) + { + bindingContext.Result = ModelBindingResult.Success(dateTime); + } + else + { + return _defaultModelBinder.BindModelAsync(bindingContext); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/ModelBinders/NullableEnumModelBinder.cs b/Jellyfin.Api/ModelBinders/NullableEnumModelBinder.cs new file mode 100644 index 000000000..5d296227e --- /dev/null +++ b/Jellyfin.Api/ModelBinders/NullableEnumModelBinder.cs @@ -0,0 +1,47 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders +{ + /// <summary> + /// Nullable enum model binder. + /// </summary> + public class NullableEnumModelBinder : IModelBinder + { + private readonly ILogger<NullableEnumModelBinder> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="NullableEnumModelBinder"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{NullableEnumModelBinder}"/> interface.</param> + public NullableEnumModelBinder(ILogger<NullableEnumModelBinder> logger) + { + _logger = logger; + } + + /// <inheritdoc /> + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + var elementType = bindingContext.ModelType.GetElementType() ?? bindingContext.ModelType.GenericTypeArguments[0]; + var converter = TypeDescriptor.GetConverter(elementType); + if (valueProviderResult.Length != 0) + { + try + { + var convertedValue = converter.ConvertFromString(valueProviderResult.FirstValue); + bindingContext.Result = ModelBindingResult.Success(convertedValue); + } + catch (FormatException e) + { + _logger.LogWarning(e, "Error converting value."); + } + } + + return Task.CompletedTask; + } + } +}
\ No newline at end of file diff --git a/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs b/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs new file mode 100644 index 000000000..bc12ad05d --- /dev/null +++ b/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders +{ + /// <summary> + /// Nullable enum model binder provider. + /// </summary> + public class NullableEnumModelBinderProvider : IModelBinderProvider + { + /// <inheritdoc /> + public IModelBinder? GetBinder(ModelBinderProviderContext context) + { + var nullableType = Nullable.GetUnderlyingType(context.Metadata.ModelType); + if (nullableType == null || !nullableType.IsEnum) + { + // Type isn't nullable or isn't an enum. + return null; + } + + var logger = context.Services.GetRequiredService<ILogger<NullableEnumModelBinder>>(); + return new NullableEnumModelBinder(logger); + } + } +}
\ No newline at end of file diff --git a/Jellyfin.Api/ModelBinders/PipeDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/PipeDelimitedArrayModelBinder.cs new file mode 100644 index 000000000..a42e0e4da --- /dev/null +++ b/Jellyfin.Api/ModelBinders/PipeDelimitedArrayModelBinder.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.ModelBinders +{ + /// <summary> + /// Comma delimited array model binder. + /// Returns an empty array of specified type if there is no query parameter. + /// </summary> + public class PipeDelimitedArrayModelBinder : IModelBinder + { + private readonly ILogger<PipeDelimitedArrayModelBinder> _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="PipeDelimitedArrayModelBinder"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{PipeDelimitedArrayModelBinder}"/> interface.</param> + public PipeDelimitedArrayModelBinder(ILogger<PipeDelimitedArrayModelBinder> logger) + { + _logger = logger; + } + + /// <inheritdoc/> + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + var elementType = bindingContext.ModelType.GetElementType() ?? bindingContext.ModelType.GenericTypeArguments[0]; + var converter = TypeDescriptor.GetConverter(elementType); + + if (valueProviderResult.Length > 1) + { + var typedValues = GetParsedResult(valueProviderResult.Values, elementType, converter); + bindingContext.Result = ModelBindingResult.Success(typedValues); + } + else + { + var value = valueProviderResult.FirstValue; + + if (value != null) + { + var splitValues = value.Split('|', StringSplitOptions.RemoveEmptyEntries); + var typedValues = GetParsedResult(splitValues, elementType, converter); + bindingContext.Result = ModelBindingResult.Success(typedValues); + } + else + { + var emptyResult = Array.CreateInstance(elementType, 0); + bindingContext.Result = ModelBindingResult.Success(emptyResult); + } + } + + return Task.CompletedTask; + } + + private Array GetParsedResult(IReadOnlyList<string> values, Type elementType, TypeConverter converter) + { + var parsedValues = new object?[values.Count]; + var convertedCount = 0; + for (var i = 0; i < values.Count; i++) + { + try + { + parsedValues[i] = converter.ConvertFromString(values[i].Trim()); + convertedCount++; + } + catch (FormatException e) + { + _logger.LogWarning(e, "Error converting value."); + } + } + + var typedValues = Array.CreateInstance(elementType, convertedCount); + var typedValueIndex = 0; + for (var i = 0; i < parsedValues.Length; i++) + { + if (parsedValues[i] != null) + { + typedValues.SetValue(parsedValues[i], typedValueIndex); + typedValueIndex++; + } + } + + return typedValues; + } + } +} |
