aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api')
-rw-r--r--Jellyfin.Api/Attributes/ParameterObsoleteAttribute.cs12
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/DashboardController.cs58
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs8
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs4
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs29
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs2
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs10
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs5
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs37
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs6
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs5
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs2
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs53
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj4
-rw-r--r--Jellyfin.Api/Models/ConfigurationPageInfo.cs23
-rw-r--r--Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs7
-rw-r--r--Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs16
19 files changed, 134 insertions, 161 deletions
diff --git a/Jellyfin.Api/Attributes/ParameterObsoleteAttribute.cs b/Jellyfin.Api/Attributes/ParameterObsoleteAttribute.cs
new file mode 100644
index 0000000000..56c9772b6d
--- /dev/null
+++ b/Jellyfin.Api/Attributes/ParameterObsoleteAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Jellyfin.Api.Attributes
+{
+ /// <summary>
+ /// Attribute to mark a parameter as obsolete.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public class ParameterObsoleteAttribute : Attribute
+ {
+ }
+}
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index b70c76e807..54bd800951 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
-using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -121,9 +120,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery] string? sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
- [FromQuery] string? sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index b77d79209a..ff7895373b 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -51,7 +51,6 @@ namespace Jellyfin.Api.Controllers
/// Gets the configuration pages.
/// </summary>
/// <param name="enableInMainMenu">Whether to enable in the main menu.</param>
- /// <param name="pageType">The <see cref="ConfigurationPageInfo"/>.</param>
/// <response code="200">ConfigurationPages returned.</response>
/// <response code="404">Server still loading.</response>
/// <returns>An <see cref="IEnumerable{ConfigurationPageInfo}"/> with infos about the plugins.</returns>
@@ -59,40 +58,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ConfigurationPageInfo?>> GetConfigurationPages(
- [FromQuery] bool? enableInMainMenu,
- [FromQuery] ConfigurationPageType? pageType)
+ [FromQuery] bool? enableInMainMenu)
{
- const string unavailableMessage = "The server is still loading. Please try again momentarily.";
-
- var pages = _appHost.GetExports<IPluginConfigurationPage>().ToList();
-
- if (pages == null)
- {
- return NotFound(unavailableMessage);
- }
-
- // Don't allow a failing plugin to fail them all
- var configPages = pages.Select(p =>
- {
- try
- {
- return new ConfigurationPageInfo(p);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error getting plugin information from {Plugin}", p.GetType().Name);
- return null;
- }
- })
- .Where(i => i != null)
- .ToList();
-
- configPages.AddRange(_pluginManager.Plugins.SelectMany(GetConfigPages));
-
- if (pageType.HasValue)
- {
- configPages = configPages.Where(p => p!.ConfigurationPageType == pageType).ToList();
- }
+ var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList();
if (enableInMainMenu.HasValue)
{
@@ -121,24 +89,14 @@ namespace Jellyfin.Api.Controllers
var isJs = false;
var isTemplate = false;
- var page = _appHost.GetExports<IPluginConfigurationPage>().FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase));
- if (page != null)
- {
- plugin = page.Plugin;
- stream = page.GetHtmlStream();
- }
-
- if (plugin == null)
+ var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase));
+ if (altPage != null)
{
- var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase));
- if (altPage != null)
- {
- plugin = altPage.Item2;
- stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath);
+ plugin = altPage.Item2;
+ stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath);
- isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase);
- isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal);
- }
+ isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase);
+ isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal);
}
if (plugin != null && stream != null)
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index b84136ac6f..7d77474958 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -175,7 +175,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
- [FromQuery] string? sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
@@ -184,7 +184,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
- [FromQuery] string? sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery] bool? isPlayed,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
@@ -608,7 +608,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
- [FromQuery] string? sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
@@ -617,7 +617,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
- [FromQuery] string? sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery] bool? isPlayed,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 6f2d432277..24ee833ef7 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -553,8 +553,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isSports,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery] string? sortBy,
- [FromQuery] string? sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
[FromQuery] bool? enableImages,
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index baa2e06361..e330f02b61 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -83,6 +83,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <remarks>
/// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.
+ /// Query parameters are obsolete.
/// </remarks>
/// <param name="itemId">The item id.</param>
/// <param name="userId">The user id.</param>
@@ -106,20 +107,20 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
[FromRoute, Required] Guid itemId,
- [FromQuery] Guid? userId,
- [FromQuery] int? maxStreamingBitrate,
- [FromQuery] long? startTimeTicks,
- [FromQuery] int? audioStreamIndex,
- [FromQuery] int? subtitleStreamIndex,
- [FromQuery] int? maxAudioChannels,
- [FromQuery] string? mediaSourceId,
- [FromQuery] string? liveStreamId,
- [FromQuery] bool? autoOpenLiveStream,
- [FromQuery] bool? enableDirectPlay,
- [FromQuery] bool? enableDirectStream,
- [FromQuery] bool? enableTranscoding,
- [FromQuery] bool? allowVideoStreamCopy,
- [FromQuery] bool? allowAudioStreamCopy,
+ [FromQuery, ParameterObsolete] Guid? userId,
+ [FromQuery, ParameterObsolete] int? maxStreamingBitrate,
+ [FromQuery, ParameterObsolete] long? startTimeTicks,
+ [FromQuery, ParameterObsolete] int? audioStreamIndex,
+ [FromQuery, ParameterObsolete] int? subtitleStreamIndex,
+ [FromQuery, ParameterObsolete] int? maxAudioChannels,
+ [FromQuery, ParameterObsolete] string? mediaSourceId,
+ [FromQuery, ParameterObsolete] string? liveStreamId,
+ [FromQuery, ParameterObsolete] bool? autoOpenLiveStream,
+ [FromQuery, ParameterObsolete] bool? enableDirectPlay,
+ [FromQuery, ParameterObsolete] bool? enableDirectStream,
+ [FromQuery, ParameterObsolete] bool? enableTranscoding,
+ [FromQuery, ParameterObsolete] bool? allowVideoStreamCopy,
+ [FromQuery, ParameterObsolete] bool? allowAudioStreamCopy,
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto)
{
var authInfo = _authContext.GetAuthorizationInfo(Request);
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 9ab8e0bdcf..c589f54ac4 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Repositories")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos)
+ public ActionResult SetRepositories([FromBody, Required] List<RepositoryInfo> repositoryInfos)
{
_serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos;
_serverConfigurationManager.SaveConfiguration();
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index fcdad4bc72..a55e4ad2ff 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
+using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@@ -57,6 +58,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <remarks>
/// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.
+ /// Query parameters are obsolete.
/// </remarks>
/// <param name="name">The playlist name.</param>
/// <param name="ids">The item ids.</param>
@@ -70,10 +72,10 @@ namespace Jellyfin.Api.Controllers
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist(
- [FromQuery] string? name,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] IReadOnlyList<Guid> ids,
- [FromQuery] Guid? userId,
- [FromQuery] string? mediaType,
+ [FromQuery, ParameterObsolete] string? name,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids,
+ [FromQuery, ParameterObsolete] Guid? userId,
+ [FromQuery, ParameterObsolete] string? mediaType,
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest)
{
if (ids.Count == 0)
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index 8e9ece14f8..242b8f068e 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -1,6 +1,7 @@
using System;
using Jellyfin.Api.Constants;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
@@ -144,7 +145,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
- [FromQuery] string? sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
@@ -152,7 +153,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
- [FromQuery] string? sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery] bool? isPlayed,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index ca18901e5d..223f588593 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -67,6 +67,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="enableTotalRecordCount">Whether to enable the total records count. Defaults to true.</param>
+ /// <param name="disableFirstEpisode">Whether to disable sending the first episode in a series as next up.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns>
[HttpGet("NextUp")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -81,7 +82,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData,
- [FromQuery] bool enableTotalRecordCount = true)
+ [FromQuery] bool enableTotalRecordCount = true,
+ [FromQuery] bool disableFirstEpisode = false)
{
var options = new DtoOptions { Fields = fields }
.AddClientFields(Request)
@@ -95,7 +97,8 @@ namespace Jellyfin.Api.Controllers
SeriesId = seriesId,
StartIndex = startIndex,
UserId = userId ?? Guid.Empty,
- EnableTotalRecordCount = enableTotalRecordCount
+ EnableTotalRecordCount = enableTotalRecordCount,
+ DisableFirstEpisode = disableFirstEpisode
},
options);
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 34c9f32fa4..bacd95baca 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -278,7 +278,7 @@ namespace Jellyfin.Api.Controllers
var directPlayProfiles = new DirectPlayProfile[len];
for (int i = 0; i < len; i++)
{
- var parts = RequestHelpers.Split(containers[i], '|', true);
+ var parts = containers[i].Split('|', StringSplitOptions.RemoveEmptyEntries);
var audioCodecs = parts.Length == 1 ? null : string.Join(',', parts.Skip(1));
@@ -312,25 +312,52 @@ namespace Jellyfin.Api.Controllers
if (maxAudioSampleRate.HasValue)
{
// codec profile
- conditions.Add(new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, IsRequired = false, Property = ProfileConditionValue.AudioSampleRate, Value = maxAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) });
+ conditions.Add(
+ new ProfileCondition
+ {
+ Condition = ProfileConditionType.LessThanEqual,
+ IsRequired = false,
+ Property = ProfileConditionValue.AudioSampleRate,
+ Value = maxAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)
+ });
}
if (maxAudioBitDepth.HasValue)
{
// codec profile
- conditions.Add(new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, IsRequired = false, Property = ProfileConditionValue.AudioBitDepth, Value = maxAudioBitDepth.Value.ToString(CultureInfo.InvariantCulture) });
+ conditions.Add(
+ new ProfileCondition
+ {
+ Condition = ProfileConditionType.LessThanEqual,
+ IsRequired = false,
+ Property = ProfileConditionValue.AudioBitDepth,
+ Value = maxAudioBitDepth.Value.ToString(CultureInfo.InvariantCulture)
+ });
}
if (maxAudioChannels.HasValue)
{
// codec profile
- conditions.Add(new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, IsRequired = false, Property = ProfileConditionValue.AudioChannels, Value = maxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) });
+ conditions.Add(
+ new ProfileCondition
+ {
+ Condition = ProfileConditionType.LessThanEqual,
+ IsRequired = false,
+ Property = ProfileConditionValue.AudioChannels,
+ Value = maxAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
+ });
}
if (conditions.Count > 0)
{
// codec profile
- codecProfiles.Add(new CodecProfile { Type = CodecType.Audio, Container = string.Join(',', containers), Conditions = conditions.ToArray() });
+ codecProfiles.Add(
+ new CodecProfile
+ {
+ Type = CodecType.Audio,
+ Container = string.Join(',', containers),
+ Conditions = conditions.ToArray()
+ });
}
deviceProfile.CodecProfiles = codecProfiles.ToArray();
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 0f0bee4bcf..43ee309b76 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -509,14 +509,14 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Redeems a forgot password pin.
/// </summary>
- /// <param name="pin">The pin.</param>
+ /// <param name="forgotPasswordPinRequest">The forgot password pin request containing the entered pin.</param>
/// <response code="200">Pin reset process started.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns>
[HttpPost("ForgotPassword/Pin")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody] string? pin)
+ public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody, Required] ForgotPasswordPinDto forgotPasswordPinRequest)
{
- var result = await _userManager.RedeemPasswordResetPin(pin).ConfigureAwait(false);
+ var result = await _userManager.RedeemPasswordResetPin(forgotPasswordPinRequest.Pin).ConfigureAwait(false);
return result;
}
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index 48c639b08d..7c27752f7f 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -7,6 +7,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -70,13 +71,13 @@ namespace Jellyfin.Api.Controllers
public ActionResult<QueryResult<BaseItemDto>> GetYears(
[FromQuery] int? startIndex,
[FromQuery] int? limit,
- [FromQuery] string? sortOrder,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
- [FromQuery] string? sortBy,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index a4da54cfdf..92ff42b49f 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -427,7 +427,7 @@ namespace Jellyfin.Api.Helpers
if (framerate.HasValue)
{
builder.Append(",FRAME-RATE=")
- .Append(framerate.Value);
+ .Append(framerate.Value.ToString(CultureInfo.InvariantCulture));
}
}
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index efce11f8a7..db0ccc6577 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -8,7 +9,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Http;
@@ -25,60 +25,33 @@ namespace Jellyfin.Api.Helpers
/// <param name="sortBy">Sort By. Comma delimited string.</param>
/// <param name="requestedSortOrder">Sort Order. Comma delimited string.</param>
/// <returns>Order By.</returns>
- public static ValueTuple<string, SortOrder>[] GetOrderBy(string? sortBy, string? requestedSortOrder)
+ public static (string, SortOrder)[] GetOrderBy(IReadOnlyList<string> sortBy, IReadOnlyList<SortOrder> requestedSortOrder)
{
- var val = sortBy;
-
- if (string.IsNullOrEmpty(val))
+ if (sortBy.Count == 0)
{
return Array.Empty<ValueTuple<string, SortOrder>>();
}
- var vals = val.Split(',');
- if (string.IsNullOrWhiteSpace(requestedSortOrder))
+ var result = new (string, SortOrder)[sortBy.Count];
+ var i = 0;
+ // Add elements which have a SortOrder specified
+ for (; i < requestedSortOrder.Count; i++)
{
- requestedSortOrder = "Ascending";
+ result[i] = (sortBy[i], requestedSortOrder[i]);
}
- var sortOrders = requestedSortOrder.Split(',');
-
- var result = new ValueTuple<string, SortOrder>[vals.Length];
-
- for (var i = 0; i < vals.Length; i++)
+ // Add remaining elements with the first specified SortOrder
+ // or the default one if no SortOrders are specified
+ var order = requestedSortOrder.Count > 0 ? requestedSortOrder[0] : SortOrder.Ascending;
+ for (; i < sortBy.Count; i++)
{
- var sortOrderIndex = sortOrders.Length > i ? i : 0;
-
- var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
- var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
- ? SortOrder.Descending
- : SortOrder.Ascending;
-
- result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
+ result[i] = (sortBy[i], order);
}
return result;
}
/// <summary>
- /// Splits a string at a separating character into an array of substrings.
- /// </summary>
- /// <param name="value">The string to split.</param>
- /// <param name="separator">The char that separates the substrings.</param>
- /// <param name="removeEmpty">Option to remove empty substrings from the array.</param>
- /// <returns>An array of the substrings.</returns>
- internal static string[] Split(string? value, char separator, bool removeEmpty)
- {
- if (string.IsNullOrWhiteSpace(value))
- {
- return Array.Empty<string>();
- }
-
- return removeEmpty
- ? value.Split(separator, StringSplitOptions.RemoveEmptyEntries)
- : value.Split(separator);
- }
-
- /// <summary>
/// Checks if the user can update an entry.
/// </summary>
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index f01f50cea7..3dd4776c12 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -15,10 +15,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
- <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.6.3" />
+ <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.0.2" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs
index f56ef59765..a7bbe42fe5 100644
--- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs
+++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs
@@ -13,23 +13,6 @@ namespace Jellyfin.Api.Models
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationPageInfo"/> class.
/// </summary>
- /// <param name="page">Instance of <see cref="IPluginConfigurationPage"/> interface.</param>
- public ConfigurationPageInfo(IPluginConfigurationPage page)
- {
- Name = page.Name;
-
- ConfigurationPageType = page.ConfigurationPageType;
-
- if (page.Plugin != null)
- {
- DisplayName = page.Plugin.Name;
- PluginId = page.Plugin.Id;
- }
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ConfigurationPageInfo"/> class.
- /// </summary>
/// <param name="plugin">Instance of <see cref="IPlugin"/> interface.</param>
/// <param name="page">Instance of <see cref="PluginPageInfo"/> interface.</param>
public ConfigurationPageInfo(IPlugin? plugin, PluginPageInfo page)
@@ -69,12 +52,6 @@ namespace Jellyfin.Api.Models
public string? DisplayName { get; set; }
/// <summary>
- /// Gets or sets the type of the configuration page.
- /// </summary>
- /// <value>The type of the configuration page.</value>
- public ConfigurationPageType ConfigurationPageType { get; set; }
-
- /// <summary>
/// Gets or sets the plugin id.
/// </summary>
/// <value>The plugin id.</value>
diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
index a47ae926c1..588ce717c8 100644
--- a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
+++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
+using Jellyfin.Data.Enums;
using MediaBrowser.Common.Json.Converters;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
@@ -106,12 +107,14 @@ namespace Jellyfin.Api.Models.LiveTvDtos
/// Gets or sets specify one or more sort orders, comma delimited. Options: Name, StartDate.
/// Optional.
/// </summary>
- public string? SortBy { get; set; }
+ [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
+ public IReadOnlyList<string> SortBy { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets sort Order - Ascending,Descending.
/// </summary>
- public string? SortOrder { get; set; }
+ [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
+ public IReadOnlyList<SortOrder> SortOrder { get; set; } = Array.Empty<SortOrder>();
/// <summary>
/// Gets or sets the genres to return guide information for.
diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
new file mode 100644
index 0000000000..62780e23c5
--- /dev/null
+++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Jellyfin.Api.Models.UserDtos
+{
+ /// <summary>
+ /// Forgot Password Pin enter request body DTO.
+ /// </summary>
+ public class ForgotPasswordPinDto
+ {
+ /// <summary>
+ /// Gets or sets the entered pin to have the password reset.
+ /// </summary>
+ [Required]
+ public string? Pin { get; set; }
+ }
+}