diff options
Diffstat (limited to 'Jellyfin.Api')
56 files changed, 419 insertions, 425 deletions
diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index 2853e69b0..f6f2f59c5 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -3,7 +3,8 @@ using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; using Jellyfin.Api.Constants; -using Jellyfin.Data.Enums; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; @@ -50,7 +51,8 @@ namespace Jellyfin.Api.Auth } var role = UserRoles.User; - if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) + if (authorizationInfo.IsApiKey + || (authorizationInfo.User?.HasPermission(PermissionKind.IsAdministrator) ?? false)) { role = UserRoles.Administrator; } @@ -60,10 +62,10 @@ namespace Jellyfin.Api.Auth new Claim(ClaimTypes.Name, authorizationInfo.User?.Username ?? string.Empty), new Claim(ClaimTypes.Role, role), new Claim(InternalClaimTypes.UserId, authorizationInfo.UserId.ToString("N", CultureInfo.InvariantCulture)), - new Claim(InternalClaimTypes.DeviceId, authorizationInfo.DeviceId), - new Claim(InternalClaimTypes.Device, authorizationInfo.Device), - new Claim(InternalClaimTypes.Client, authorizationInfo.Client), - new Claim(InternalClaimTypes.Version, authorizationInfo.Version), + new Claim(InternalClaimTypes.DeviceId, authorizationInfo.DeviceId ?? string.Empty), + new Claim(InternalClaimTypes.Device, authorizationInfo.Device ?? string.Empty), + new Claim(InternalClaimTypes.Client, authorizationInfo.Client ?? string.Empty), + new Claim(InternalClaimTypes.Version, authorizationInfo.Version ?? string.Empty), new Claim(InternalClaimTypes.Token, authorizationInfo.Token), new Claim(InternalClaimTypes.IsApiKey, authorizationInfo.IsApiKey.ToString(CultureInfo.InvariantCulture)) }; diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs index 4928d5ed2..6b80d537f 100644 --- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs +++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs @@ -1,7 +1,8 @@ using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Data.Enums; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs index 5fcf72fb4..7efb5b169 100644 --- a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs +++ b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.SyncPlay; diff --git a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs index f20779f6c..d139eab16 100644 --- a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs +++ b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Jellyfin.Api.Extensions; +using Jellyfin.Data; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; diff --git a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs index a7c3cce97..152c400cd 100644 --- a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs +++ b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs @@ -1,5 +1,5 @@ using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; -using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; namespace Jellyfin.Api.Auth.UserPermissionPolicy { diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index 8b931f162..7ba75dc24 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -4,8 +4,9 @@ using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -91,31 +92,31 @@ public class ArtistsController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { @@ -295,31 +296,31 @@ public class ArtistsController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index a47c60473..272b4034e 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -238,7 +238,7 @@ public class AudioController : BaseJellyfinApiController /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> /// <param name="requireAvc">Optional. Whether to require avc.</param> /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> - /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamporphic stream.</param> + /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param> /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="liveStreamId">The live stream id.</param> diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index f83c71b57..880b3a82d 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Dto; @@ -121,10 +122,10 @@ public class ChannelsController : BaseJellyfinApiController [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -197,9 +198,9 @@ public class ChannelsController : BaseJellyfinApiController [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index 2d9f1ed69..c37f37633 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -50,7 +50,7 @@ public class CollectionController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult<CollectionCreationResult>> CreateCollection( [FromQuery] string? name, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] ids, [FromQuery] Guid? parentId, [FromQuery] bool isLocked = false) { @@ -86,7 +86,7 @@ public class CollectionController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> AddToCollection( [FromRoute, Required] Guid collectionId, - [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) + [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids) { await _collectionManager.AddToCollectionAsync(collectionId, ids).ConfigureAwait(true); return NoContent(); @@ -103,7 +103,7 @@ public class CollectionController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> RemoveFromCollection( [FromRoute, Required] Guid collectionId, - [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) + [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids) { await _collectionManager.RemoveFromCollectionAsync(collectionId, ids).ConfigureAwait(false); return NoContent(); diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 6d94d96f3..13064882c 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -4,8 +4,8 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using Jellyfin.Api.Helpers; -using Jellyfin.Data.Entities; -using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Model.Dto; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index a641ec209..ca8ab0ef7 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -459,7 +459,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary<string, string> streamOptions, - [FromQuery] bool enableAdaptiveBitrateStreaming = true, + [FromQuery] bool enableAdaptiveBitrateStreaming = false, [FromQuery] bool enableTrickplay = true, [FromQuery] bool enableAudioVbrEncoding = true, [FromQuery] bool alwaysBurnInSubtitleWhenTranscoding = false) @@ -634,7 +634,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary<string, string> streamOptions, - [FromQuery] bool enableAdaptiveBitrateStreaming = true, + [FromQuery] bool enableAdaptiveBitrateStreaming = false, [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new HlsAudioRequestDto @@ -1778,7 +1778,7 @@ public class DynamicHlsController : BaseJellyfinApiController } else if (state.AudioStream?.CodecTag is not null && state.AudioStream.CodecTag.Equals("ac-4", StringComparison.Ordinal)) { - // ac-4 audio tends to hava a super weird sample rate that will fail most encoders + // ac-4 audio tends to have a super weird sample rate that will fail most encoders // force resample it to 48KHz args += " -ar 48000"; } @@ -2056,16 +2056,16 @@ public class DynamicHlsController : BaseJellyfinApiController } } - private Task DeleteLastFile(string playlistPath, string segmentExtension, int retryCount) + private async Task DeleteLastFile(string playlistPath, string segmentExtension, int retryCount) { var file = GetLastTranscodingFile(playlistPath, segmentExtension, _fileSystem); if (file is null) { - return Task.CompletedTask; + return; } - return DeleteFile(file.FullName, retryCount); + await DeleteFile(file.FullName, retryCount).ConfigureAwait(false); } private async Task DeleteFile(string path, int retryCount) diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index 4abca3271..3f9aa93a6 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -50,8 +50,8 @@ public class FilterController : BaseJellyfinApiController public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy( [FromQuery] Guid? userId, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -137,7 +137,7 @@ public class FilterController : BaseJellyfinApiController public ActionResult<QueryFilters> GetQueryFilters( [FromQuery] Guid? userId, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isAiring, [FromQuery] bool? isMovie, [FromQuery] bool? isSports, diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 54d48aec2..dd60d01e0 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -4,8 +4,9 @@ using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -76,18 +77,18 @@ public class GenresController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index b71199026..e7b7405ca 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -130,7 +130,7 @@ public class ImageController : BaseJellyfinApiController await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); } - user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension)); + user.ProfileImage = new Database.Implementations.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension)); await _providerManager .SaveImage(stream, mimeType, user.ProfileImage.Path) diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index dcbacf1d7..c4b976756 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; +using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; +using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -71,11 +73,11 @@ public class InstantMixController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -115,11 +117,11 @@ public class InstantMixController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -159,11 +161,11 @@ public class InstantMixController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -201,11 +203,11 @@ public class InstantMixController : BaseJellyfinApiController [FromRoute, Required] string name, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -239,11 +241,11 @@ public class InstantMixController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -283,11 +285,11 @@ public class InstantMixController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -328,11 +330,11 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { return GetInstantMixFromArtists( id, @@ -366,11 +368,11 @@ public class InstantMixController : BaseJellyfinApiController [FromQuery, Required] Guid id, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -389,23 +391,19 @@ public class InstantMixController : BaseJellyfinApiController return GetResult(items, user, limit, dtoOptions); } - private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions) + private QueryResult<BaseItemDto> GetResult(IReadOnlyList<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions) { - var list = items; + var totalCount = items.Count; - var totalCount = list.Count; - - if (limit.HasValue && limit < list.Count) + if (limit.HasValue && limit < items.Count) { - list = list.GetRange(0, limit.Value); + items = items.Take(limit.Value).ToArray(); } - var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user); - var result = new QueryResult<BaseItemDto>( 0, totalCount, - returnList); + _dtoService.GetBaseItemDtos(items, dtoOptions, user)); return result; } diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 828bd5174..803c2f1f7 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -4,7 +4,9 @@ using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; @@ -171,8 +173,8 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] bool? hasParentalRating, [FromQuery] bool? isHd, [FromQuery] bool? is4K, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, @@ -190,42 +192,42 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] bool? isNews, [FromQuery] bool? isKids, [FromQuery] bool? isSports, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool? recursive, [FromQuery] string? searchTerm, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? isPlayed, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] artists, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] albums, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes, [FromQuery] string? minOfficialRating, [FromQuery] bool? isLocked, [FromQuery] bool? isPlaceHolder, @@ -236,12 +238,12 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] bool? is3D, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { @@ -638,8 +640,8 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] bool? hasParentalRating, [FromQuery] bool? isHd, [FromQuery] bool? is4K, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, @@ -657,42 +659,42 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] bool? isNews, [FromQuery] bool? isKids, [FromQuery] bool? isSports, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool? recursive, [FromQuery] string? searchTerm, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? isPlayed, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] artists, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] albums, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes, [FromQuery] string? minOfficialRating, [FromQuery] bool? isLocked, [FromQuery] bool? isPlaceHolder, @@ -703,12 +705,12 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] bool? is3D, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) => GetItems( @@ -827,13 +829,13 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true, [FromQuery] bool excludeActiveSessions = false) @@ -929,13 +931,13 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true, [FromQuery] bool excludeActiveSessions = false) @@ -967,7 +969,7 @@ public class ItemsController : BaseJellyfinApiController [HttpGet("UserItems/{itemId}/UserData")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<UserItemDataDto> GetItemUserData( + public ActionResult<UserItemDataDto?> GetItemUserData( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId) { @@ -1005,7 +1007,7 @@ public class ItemsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult<UserItemDataDto> GetItemUserDataLegacy( + public ActionResult<UserItemDataDto?> GetItemUserDataLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) => GetItemUserData(userId, itemId); @@ -1022,7 +1024,7 @@ public class ItemsController : BaseJellyfinApiController [HttpPost("UserItems/{itemId}/UserData")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<UserItemDataDto> UpdateItemUserData( + public ActionResult<UserItemDataDto?> UpdateItemUserData( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId, [FromBody, Required] UpdateUserItemDataDto userDataDto) @@ -1064,7 +1066,7 @@ public class ItemsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult<UserItemDataDto> UpdateItemUserDataLegacy( + public ActionResult<UserItemDataDto?> UpdateItemUserDataLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromBody, Required] UpdateUserItemDataDto userDataDto) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 1b23683fb..bde1758e9 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -11,8 +11,9 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LibraryDtos; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Extensions; @@ -144,8 +145,8 @@ public class LibraryController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, [FromQuery] bool inheritFromParent = false, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -218,8 +219,8 @@ public class LibraryController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, [FromQuery] bool inheritFromParent = false, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -290,8 +291,8 @@ public class LibraryController : BaseJellyfinApiController [FromRoute, Required] Guid itemId, [FromQuery] Guid? userId, [FromQuery] bool inheritFromParent = false, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null) { var themeSongs = GetThemeSongs( itemId, @@ -400,7 +401,7 @@ public class LibraryController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) + public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids) { var isApiKey = User.GetIsApiKey(); var userId = User.GetUserId(); @@ -722,10 +723,10 @@ public class LibraryController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems( [FromRoute, Required] Guid itemId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds, [FromQuery] Guid? userId, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields) { userId = RequestHelpers.GetUserId(User, userId); var user = userId.IsNullOrEmpty() @@ -780,11 +781,9 @@ public class LibraryController : BaseJellyfinApiController Genres = item.Genres, Limit = limit, IncludeItemTypes = includeItemTypes.ToArray(), - SimilarTo = item, DtoOptions = dtoOptions, EnableTotalRecordCount = !isMovie ?? true, EnableGroupByMetadataKey = isMovie ?? false, - MinSimilarityScore = 2 // A remnant from album/artist scoring }; // ExcludeArtistIds @@ -793,7 +792,7 @@ public class LibraryController : BaseJellyfinApiController query.ExcludeArtistIds = excludeArtistIds; } - List<BaseItem> itemsResult = _libraryManager.GetItemList(query); + var itemsResult = _libraryManager.GetItemList(query); var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index 93c2393f3..2a885662b 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -77,7 +77,7 @@ public class LibraryStructureController : BaseJellyfinApiController public async Task<ActionResult> AddVirtualFolder( [FromQuery] string name, [FromQuery] CollectionTypeOptions? collectionType, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] paths, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] paths, [FromBody] AddVirtualFolderDto? libraryOptionsDto, [FromQuery] bool refreshLibrary = false) { @@ -99,6 +99,7 @@ public class LibraryStructureController : BaseJellyfinApiController /// <param name="name">The name of the folder.</param> /// <param name="refreshLibrary">Whether to refresh the library.</param> /// <response code="204">Folder removed.</response> + /// <response code="404">Folder not found.</response> /// <returns>A <see cref="NoContentResult"/>.</returns> [HttpDelete] [ProducesResponseType(StatusCodes.Status204NoContent)] @@ -106,7 +107,9 @@ public class LibraryStructureController : BaseJellyfinApiController [FromQuery] string name, [FromQuery] bool refreshLibrary = false) { + // TODO: refactor! this relies on an FileNotFound exception to return NotFound when attempting to remove a library that does not exist. await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false); + return NoContent(); } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 421f23fa1..5461d12fa 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -15,6 +15,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LiveTvDtos; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; @@ -159,10 +160,10 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] bool? isDisliked, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableUserData, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, [FromQuery] SortOrder? sortOrder, [FromQuery] bool enableFavoriteSorting = false, [FromQuery] bool addCurrentProgram = true) @@ -283,8 +284,8 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] string? seriesTimerId, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool? isMovie, [FromQuery] bool? isSeries, @@ -371,8 +372,8 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] string? seriesTimerId, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { @@ -566,7 +567,7 @@ public class LiveTvController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] [Authorize(Policy = Policies.LiveTvAccess)] public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms( - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds, [FromQuery] Guid? userId, [FromQuery] DateTime? minStartDate, [FromQuery] bool? hasAired, @@ -581,17 +582,17 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] bool? isSports, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] string? seriesTimerId, [FromQuery] Guid? librarySeriesId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool enableTotalRecordCount = true) { userId = RequestHelpers.GetUserId(User, userId); @@ -698,6 +699,7 @@ public class LiveTvController : BaseJellyfinApiController /// Gets recommended live tv epgs. /// </summary> /// <param name="userId">Optional. filter by user id.</param> + /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> /// <param name="limit">Optional. The maximum number of records to return.</param> /// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param> /// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param> @@ -720,6 +722,7 @@ public class LiveTvController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms( [FromQuery] Guid? userId, + [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool? isAiring, [FromQuery] bool? hasAired, @@ -730,9 +733,9 @@ public class LiveTvController : BaseJellyfinApiController [FromQuery] bool? isSports, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableUserData, [FromQuery] bool enableTotalRecordCount = true) { @@ -744,6 +747,7 @@ public class LiveTvController : BaseJellyfinApiController var query = new InternalItemsQuery(user) { IsAiring = isAiring, + StartIndex = startIndex, Limit = limit, HasAired = hasAired, IsSeries = isSeries, diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs index 2d1d4e2c8..e30e2b54e 100644 --- a/Jellyfin.Api/Controllers/MediaSegmentsController.cs +++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Extensions; -using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index 471bcd096..363acf815 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -5,8 +5,9 @@ using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -65,7 +66,7 @@ public class MoviesController : BaseJellyfinApiController public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations( [FromQuery] Guid? userId, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] int categoryLimit = 5, [FromQuery] int itemLimit = 8) { @@ -120,7 +121,7 @@ public class MoviesController : BaseJellyfinApiController DtoOptions = dtoOptions }); - var mostRecentMovies = recentlyPlayedMovies.GetRange(0, Math.Min(recentlyPlayedMovies.Count, 6)); + var mostRecentMovies = recentlyPlayedMovies.Take(Math.Min(recentlyPlayedMovies.Count, 6)).ToList(); // Get recently played directors var recentDirectors = GetDirectors(mostRecentMovies) .ToList(); @@ -276,7 +277,6 @@ public class MoviesController : BaseJellyfinApiController Limit = itemLimit, IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, - SimilarTo = item, EnableGroupByMetadataKey = true, DtoOptions = dtoOptions }); diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 5411baa3e..1e45e53ca 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -4,8 +4,9 @@ using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -76,18 +77,18 @@ public class MusicGenresController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, [FromQuery] bool? enableImages = true, [FromQuery] bool enableTotalRecordCount = true) { diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 6ca308601..4d12dc18f 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -4,7 +4,7 @@ using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; +using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -67,14 +67,14 @@ public class PersonsController : BaseJellyfinApiController public ActionResult<QueryResult<BaseItemDto>> GetPersons( [FromQuery] int? limit, [FromQuery] string? searchTerm, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludePersonTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] excludePersonTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, [FromQuery] Guid? appearsInItemId, [FromQuery] Guid? userId, [FromQuery] bool? enableImages = true) diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 1ab36ccc6..ec5fdab38 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -76,7 +76,7 @@ public class PlaylistsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist( [FromQuery, ParameterObsolete] string? name, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids, [FromQuery, ParameterObsolete] Guid? userId, [FromQuery, ParameterObsolete] MediaType? mediaType, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest) @@ -370,7 +370,7 @@ public class PlaylistsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task<ActionResult> AddItemToPlaylist( [FromRoute, Required] Guid playlistId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); @@ -446,7 +446,7 @@ public class PlaylistsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task<ActionResult> RemoveItemFromPlaylist( [FromRoute, Required] string playlistId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] entryIds) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] entryIds) { var callingUserId = User.GetUserId(); @@ -493,11 +493,11 @@ public class PlaylistsController : BaseJellyfinApiController [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes) { var callingUserId = userId ?? User.GetUserId(); var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId); diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index 88aa0178f..1577b4594 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; +using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -72,7 +72,7 @@ public class PlaystateController : BaseJellyfinApiController [HttpPost("UserPlayedItems/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task<ActionResult<UserItemDataDto>> MarkPlayedItem( + public async Task<ActionResult<UserItemDataDto?>> MarkPlayedItem( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId, [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed) @@ -121,7 +121,7 @@ public class PlaystateController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public Task<ActionResult<UserItemDataDto>> MarkPlayedItemLegacy( + public Task<ActionResult<UserItemDataDto?>> MarkPlayedItemLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed) @@ -138,7 +138,7 @@ public class PlaystateController : BaseJellyfinApiController [HttpDelete("UserPlayedItems/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task<ActionResult<UserItemDataDto>> MarkUnplayedItem( + public async Task<ActionResult<UserItemDataDto?>> MarkUnplayedItem( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId) { @@ -185,7 +185,7 @@ public class PlaystateController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public Task<ActionResult<UserItemDataDto>> MarkUnplayedItemLegacy( + public Task<ActionResult<UserItemDataDto?>> MarkUnplayedItemLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) => MarkUnplayedItem(userId, itemId); @@ -502,7 +502,7 @@ public class PlaystateController : BaseJellyfinApiController /// <param name="wasPlayed">if set to <c>true</c> [was played].</param> /// <param name="datePlayed">The date played.</param> /// <returns>Task.</returns> - private UserItemDataDto UpdatePlayedStatus(User user, BaseItem item, bool wasPlayed, DateTime? datePlayed) + private UserItemDataDto? UpdatePlayedStatus(User user, BaseItem item, bool wasPlayed, DateTime? datePlayed) { if (wasPlayed) { diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 8bae6fb9b..ecf2335ba 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -84,9 +84,9 @@ public class SearchController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] Guid? userId, [FromQuery, Required] string searchTerm, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, [FromQuery] Guid? parentId, [FromQuery] bool? isMovie, [FromQuery] bool? isSeries, diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 2f9e9f091..9886d03de 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -122,7 +122,7 @@ public class SessionController : BaseJellyfinApiController public async Task<ActionResult> Play( [FromRoute, Required] string sessionId, [FromQuery, Required] PlayCommand playCommand, - [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds, + [FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] itemIds, [FromQuery] long? startPositionTicks, [FromQuery] string? mediaSourceId, [FromQuery] int? audioStreamIndex, @@ -347,8 +347,8 @@ public class SessionController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> PostCapabilities( [FromQuery] string? id, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] playableMediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] GeneralCommandType[] supportedCommands, [FromQuery] bool supportsMediaControl = false, [FromQuery] bool supportsPersistentIdentifier = true) { diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 41b0858d1..a6bc84311 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -93,7 +93,6 @@ public class StartupController : BaseJellyfinApiController { NetworkConfiguration settings = _config.GetNetworkConfiguration(); settings.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess; - settings.EnableUPnP = startupRemoteAccessDto.EnableAutomaticPortMapping; _config.SaveConfiguration(NetworkConfigurationStore.StoreKey, settings); return NoContent(); } diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index 708fc7436..52cb87e72 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -3,8 +3,8 @@ using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -73,13 +73,13 @@ public class StudiosController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 9da1dce93..e5df873f5 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -395,7 +395,7 @@ public class SubtitleController : BaseJellyfinApiController var url = string.Format( CultureInfo.InvariantCulture, - "stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}", + "stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&ApiKey={2}", positionTicks.ToString(CultureInfo.InvariantCulture), endPositionTicks.ToString(CultureInfo.InvariantCulture), accessToken); diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index ad625cc6e..52982c362 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -3,8 +3,9 @@ using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -59,8 +60,8 @@ public class SuggestionsController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult<QueryResult<BaseItemDto>> GetSuggestions( [FromQuery] Guid? userId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaType, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] type, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaType, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] type, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool enableTotalRecordCount = false) @@ -115,8 +116,8 @@ public class SuggestionsController : BaseJellyfinApiController [ApiExplorerSettings(IgnoreApi = true)] public ActionResult<QueryResult<BaseItemDto>> GetSuggestionsLegacy( [FromRoute, Required] Guid userId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaType, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] type, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaType, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] type, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool enableTotalRecordCount = false) diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index 6c5ce4715..0ee11c070 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -212,20 +212,4 @@ public class SystemController : BaseJellyfinApiController FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); return File(stream, "text/plain; charset=utf-8"); } - - /// <summary> - /// Gets wake on lan information. - /// </summary> - /// <response code="200">Information retrieved.</response> - /// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns> - [HttpGet("WakeOnLanInfo")] - [Authorize] - [Obsolete("This endpoint is obsolete.")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo() - { - var result = _networkManager.GetMacAddresses() - .Select(i => new WakeOnLanInfo(i)); - return Ok(result); - } } diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index d7d0cc454..3e4bac89a 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -1,6 +1,7 @@ using System; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -130,8 +131,8 @@ public class TrailersController : BaseJellyfinApiController [FromQuery] bool? hasParentalRating, [FromQuery] bool? isHd, [FromQuery] bool? is4K, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes, [FromQuery] bool? isMissing, [FromQuery] bool? isUnaired, [FromQuery] double? minCommunityRating, @@ -149,41 +150,41 @@ public class TrailersController : BaseJellyfinApiController [FromQuery] bool? isNews, [FromQuery] bool? isKids, [FromQuery] bool? isSports, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery] bool? recursive, [FromQuery] string? searchTerm, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? isPlayed, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, - [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings, + [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] string? person, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] studios, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] artists, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] albums, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] studios, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] artists, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] albums, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes, [FromQuery] string? minOfficialRating, [FromQuery] bool? isLocked, [FromQuery] bool? isPlaceHolder, @@ -194,12 +195,12 @@ public class TrailersController : BaseJellyfinApiController [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] bool? is3D, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus, [FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWith, [FromQuery] string? nameLessThan, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 914ccd7f9..0f08854d2 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using Jellyfin.Api.Attributes; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -77,16 +79,16 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] Guid? seriesId, [FromQuery] Guid? parentId, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] DateTime? nextUpDateCutoff, [FromQuery] bool enableTotalRecordCount = true, - [FromQuery] bool disableFirstEpisode = false, + [FromQuery][ParameterObsolete] bool disableFirstEpisode = false, [FromQuery] bool enableResumable = true, [FromQuery] bool enableRewatching = false) { @@ -109,7 +111,6 @@ public class TvShowsController : BaseJellyfinApiController StartIndex = startIndex, User = user, EnableTotalRecordCount = enableTotalRecordCount, - DisableFirstEpisode = disableFirstEpisode, NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue, EnableResumable = enableResumable, EnableRewatching = enableRewatching @@ -143,11 +144,11 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] Guid? parentId, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData) { userId = RequestHelpers.GetUserId(User, userId); @@ -208,7 +209,7 @@ public class TvShowsController : BaseJellyfinApiController public ActionResult<QueryResult<BaseItemDto>> GetEpisodes( [FromRoute, Required] Guid seriesId, [FromQuery] Guid? userId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] int? season, [FromQuery] Guid? seasonId, [FromQuery] bool? isMissing, @@ -218,7 +219,7 @@ public class TvShowsController : BaseJellyfinApiController [FromQuery] int? limit, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] ItemSortBy? sortBy) { @@ -332,13 +333,13 @@ public class TvShowsController : BaseJellyfinApiController public ActionResult<QueryResult<BaseItemDto>> GetSeasons( [FromRoute, Required] Guid seriesId, [FromQuery] Guid? userId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, [FromQuery] bool? isSpecialSeason, [FromQuery] bool? isMissing, [FromQuery] Guid? adjacentTo, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData) { userId = RequestHelpers.GetUserId(User, userId); diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 41c4886d4..a5b5fde62 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -98,7 +98,7 @@ public class UniversalAudioController : BaseJellyfinApiController [ProducesAudioFile] public async Task<ActionResult> GetUniversalAudioStream( [FromRoute, Required] Guid itemId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] container, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] container, [FromQuery] string? mediaSourceId, [FromQuery] string? deviceId, [FromQuery] Guid? userId, @@ -222,7 +222,7 @@ public class UniversalAudioController : BaseJellyfinApiController TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(), Context = EncodingContext.Static, StreamOptions = new Dictionary<string, string>(), - EnableAdaptiveBitrateStreaming = true, + EnableAdaptiveBitrateStreaming = false, EnableAudioVbrEncoding = enableAudioVbrEncoding }; diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index d7886d247..d0ced277a 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -7,7 +7,8 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.UserDtos; -using Jellyfin.Data.Enums; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Extensions; diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index e7bf71727..0e04beb14 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -7,8 +7,8 @@ using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -305,7 +305,7 @@ public class UserLibraryController : BaseJellyfinApiController /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> [HttpDelete("UserItems/{itemId}/Rating")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<UserItemDataDto> DeleteUserItemRating( + public ActionResult<UserItemDataDto?> DeleteUserItemRating( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId) { @@ -338,7 +338,7 @@ public class UserLibraryController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult<UserItemDataDto> DeleteUserItemRatingLegacy( + public ActionResult<UserItemDataDto?> DeleteUserItemRatingLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) => DeleteUserItemRating(userId, itemId); @@ -353,7 +353,7 @@ public class UserLibraryController : BaseJellyfinApiController /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> [HttpPost("UserItems/{itemId}/Rating")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<UserItemDataDto> UpdateUserItemRating( + public ActionResult<UserItemDataDto?> UpdateUserItemRating( [FromQuery] Guid? userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes) @@ -388,7 +388,7 @@ public class UserLibraryController : BaseJellyfinApiController [ProducesResponseType(StatusCodes.Status200OK)] [Obsolete("Kept for backwards compatibility")] [ApiExplorerSettings(IgnoreApi = true)] - public ActionResult<UserItemDataDto> UpdateUserItemRatingLegacy( + public ActionResult<UserItemDataDto?> UpdateUserItemRatingLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes) @@ -523,12 +523,12 @@ public class UserLibraryController : BaseJellyfinApiController public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia( [FromQuery] Guid? userId, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isPlayed, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] int limit = 20, [FromQuery] bool groupItems = true) @@ -608,12 +608,12 @@ public class UserLibraryController : BaseJellyfinApiController public ActionResult<IEnumerable<BaseItemDto>> GetLatestMediaLegacy( [FromRoute, Required] Guid userId, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isPlayed, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] bool? enableUserData, [FromQuery] int limit = 20, [FromQuery] bool groupItems = true) @@ -634,10 +634,10 @@ public class UserLibraryController : BaseJellyfinApiController { if (item is Person) { - var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary); - var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3; + var hasMetadata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary); + var performFullRefresh = !hasMetadata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3; - if (!hasMetdata) + if (!hasMetadata) { var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { @@ -662,12 +662,15 @@ public class UserLibraryController : BaseJellyfinApiController // Get the user data for this item var data = _userDataRepository.GetUserData(user, item); - // Set favorite status - data.IsFavorite = isFavorite; + if (data is not null) + { + // Set favorite status + data.IsFavorite = isFavorite; - _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + } - return _userDataRepository.GetUserDataDto(item, user); + return _userDataRepository.GetUserDataDto(item, user)!; } /// <summary> @@ -676,14 +679,17 @@ public class UserLibraryController : BaseJellyfinApiController /// <param name="user">The user.</param> /// <param name="item">The item.</param> /// <param name="likes">if set to <c>true</c> [likes].</param> - private UserItemDataDto UpdateUserItemRatingInternal(User user, BaseItem item, bool? likes) + private UserItemDataDto? UpdateUserItemRatingInternal(User user, BaseItem item, bool? likes) { // Get the user data for this item var data = _userDataRepository.GetUserData(user, item); - data.Likes = likes; + if (data is not null) + { + data.Likes = likes; - _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); + } return _userDataRepository.GetUserDataDto(item, user); } diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index e24f78a88..64b2dffb3 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -66,7 +66,7 @@ public class UserViewsController : BaseJellyfinApiController public QueryResult<BaseItemDto> GetUserViews( [FromQuery] Guid? userId, [FromQuery] bool? includeExternalContent, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] CollectionType?[] presetViews, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] CollectionType?[] presetViews, [FromQuery] bool includeHidden = false) { userId = RequestHelpers.GetUserId(User, userId); @@ -110,7 +110,7 @@ public class UserViewsController : BaseJellyfinApiController public QueryResult<BaseItemDto> GetUserViewsLegacy( [FromRoute, Required] Guid userId, [FromQuery] bool? includeExternalContent, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] CollectionType?[] presetViews, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] CollectionType?[] presetViews, [FromQuery] bool includeHidden = false) => GetUserViews(userId, includeExternalContent, presetViews, includeHidden); diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 8348fd937..6f18c1603 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -184,7 +184,7 @@ public class VideosController : BaseJellyfinApiController [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) + public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids) { var userId = User.GetUserId(); var items = ids diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index e4aa0ea42..ebf98da45 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -71,16 +73,16 @@ public class YearsController : BaseJellyfinApiController public ActionResult<QueryResult<BaseItemDto>> GetYears( [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes, [FromQuery] Guid? userId, [FromQuery] bool recursive = true, [FromQuery] bool? enableImages = true) @@ -105,18 +107,18 @@ public class YearsController : BaseJellyfinApiController bool Filter(BaseItem i) => FilterItem(i, excludeItemTypes, includeItemTypes, mediaTypes); - IList<BaseItem> items; + IReadOnlyList<BaseItem> items; if (parentItem.IsFolder) { var folder = (Folder)parentItem; if (userId.IsNullOrEmpty()) { - items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList(); + items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToArray(); } else { - items = recursive ? folder.GetRecursiveChildren(user, query).ToList() : folder.GetChildren(user, true).Where(Filter).ToList(); + items = recursive ? folder.GetRecursiveChildren(user, query) : folder.GetChildren(user, true).Where(Filter).ToArray(); } } else diff --git a/Jellyfin.Api/Formatters/CssOutputFormatter.cs b/Jellyfin.Api/Formatters/CssOutputFormatter.cs index 495f771e1..9ad1c863e 100644 --- a/Jellyfin.Api/Formatters/CssOutputFormatter.cs +++ b/Jellyfin.Api/Formatters/CssOutputFormatter.cs @@ -1,6 +1,4 @@ -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using System.Net.Mime; using Microsoft.AspNetCore.Mvc.Formatters; namespace Jellyfin.Api.Formatters; @@ -8,28 +6,14 @@ namespace Jellyfin.Api.Formatters; /// <summary> /// Css output formatter. /// </summary> -public class CssOutputFormatter : TextOutputFormatter +public sealed class CssOutputFormatter : StringOutputFormatter { /// <summary> /// Initializes a new instance of the <see cref="CssOutputFormatter"/> class. /// </summary> public CssOutputFormatter() { - SupportedMediaTypes.Add("text/css"); - - SupportedEncodings.Add(Encoding.UTF8); - SupportedEncodings.Add(Encoding.Unicode); - } - - /// <summary> - /// Write context object to stream. - /// </summary> - /// <param name="context">Writer context.</param> - /// <param name="selectedEncoding">Unused. Writer encoding.</param> - /// <returns>Write stream task.</returns> - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) - { - var stringResponse = context.Object?.ToString(); - return stringResponse is null ? Task.CompletedTask : context.HttpContext.Response.WriteAsync(stringResponse); + SupportedMediaTypes.Clear(); + SupportedMediaTypes.Add(MediaTypeNames.Text.Css); } } diff --git a/Jellyfin.Api/Formatters/XmlOutputFormatter.cs b/Jellyfin.Api/Formatters/XmlOutputFormatter.cs index 1c9feedcb..8dbb91d0a 100644 --- a/Jellyfin.Api/Formatters/XmlOutputFormatter.cs +++ b/Jellyfin.Api/Formatters/XmlOutputFormatter.cs @@ -1,7 +1,4 @@ using System.Net.Mime; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; namespace Jellyfin.Api.Formatters; @@ -9,7 +6,7 @@ namespace Jellyfin.Api.Formatters; /// <summary> /// Xml output formatter. /// </summary> -public class XmlOutputFormatter : TextOutputFormatter +public sealed class XmlOutputFormatter : StringOutputFormatter { /// <summary> /// Initializes a new instance of the <see cref="XmlOutputFormatter"/> class. @@ -18,15 +15,5 @@ public class XmlOutputFormatter : TextOutputFormatter { SupportedMediaTypes.Clear(); SupportedMediaTypes.Add(MediaTypeNames.Text.Xml); - - SupportedEncodings.Add(Encoding.UTF8); - SupportedEncodings.Add(Encoding.Unicode); - } - - /// <inheritdoc /> - public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) - { - var stringResponse = context.Object?.ToString(); - return stringResponse is null ? Task.CompletedTask : context.HttpContext.Response.WriteAsync(stringResponse); } } diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 0e620e72a..ebd0288ca 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -8,8 +8,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -267,7 +267,7 @@ public class DynamicHlsHelper if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming, _httpContextAccessor.HttpContext.GetNormalizedRemoteIP())) { - var requestedVideoBitrate = state.VideoRequest is null ? 0 : state.VideoRequest.VideoBitRate ?? 0; + var requestedVideoBitrate = state.VideoRequest?.VideoBitRate ?? 0; // By default, vary by just 200k var variation = GetBitrateVariation(totalBitrate); @@ -526,9 +526,7 @@ public class DynamicHlsHelper return false; } - // Having problems in android - return false; - // return state.VideoRequest.VideoBitRate.HasValue; + return state.VideoRequest?.VideoBitRate.HasValue ?? false; } private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder, ClaimsPrincipal user) @@ -550,7 +548,7 @@ public class DynamicHlsHelper var url = string.Format( CultureInfo.InvariantCulture, - "{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}", + "{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&ApiKey={3}", state.Request.MediaSourceId, stream.Index.ToString(CultureInfo.InvariantCulture), 30.ToString(CultureInfo.InvariantCulture), @@ -587,7 +585,7 @@ public class DynamicHlsHelper var url = string.Format( CultureInfo.InvariantCulture, - "Trickplay/{0}/tiles.m3u8?MediaSourceId={1}&api_key={2}", + "Trickplay/{0}/tiles.m3u8?MediaSourceId={1}&ApiKey={2}", width.ToString(CultureInfo.InvariantCulture), state.Request.MediaSourceId, user.GetToken()); @@ -616,7 +614,7 @@ public class DynamicHlsHelper && state.VideoStream is not null && state.VideoStream.Level.HasValue) { - levelString = state.VideoStream.Level.Value.ToString(CultureInfo.InvariantCulture) ?? string.Empty; + levelString = state.VideoStream.Level.Value.ToString(CultureInfo.InvariantCulture); } else { diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 4adda0b69..63c9c173b 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -7,8 +7,10 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; -using Jellyfin.Data.Entities; +using Jellyfin.Data; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -288,9 +290,7 @@ public class MediaInfoHelper mediaSource.SupportsDirectPlay = false; mediaSource.SupportsDirectStream = false; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", claimsPrincipal.GetToken()).TrimStart('-'); - mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; - mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; + mediaSource.TranscodingUrl = streamInfo.ToUrl(null, claimsPrincipal.GetToken(), "&allowVideoStreamCopy=false&allowAudioStreamCopy=false"); mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; if (streamInfo.AlwaysBurnInSubtitleWhenTranscoding) @@ -303,7 +303,7 @@ public class MediaInfoHelper if (!mediaSource.SupportsDirectPlay && (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream)) { streamInfo.PlayMethod = PlayMethod.Transcode; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", claimsPrincipal.GetToken()).TrimStart('-'); + mediaSource.TranscodingUrl = streamInfo.ToUrl(null, claimsPrincipal.GetToken(), null); if (!allowVideoStreamCopy) { diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index eb83a37ba..e10e940f2 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -5,8 +5,9 @@ using System.Security.Claims; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 3a5db2f3f..2601fa3be 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -132,7 +132,7 @@ public static class StreamingHelpers mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId) ? mediaSources[0] - : mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal)); + : mediaSources.FirstOrDefault(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal)); if (mediaSource is null && Guid.Parse(streamingRequest.MediaSourceId).Equals(streamingRequest.Id)) { @@ -210,7 +210,7 @@ public static class StreamingHelpers && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value) { // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested, - // and the requested video bitrate is higher than source video bitrate. + // and the requested video bitrate is greater than source video bitrate. if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue) { state.VideoRequest.MaxWidth = state.VideoStream?.Width; @@ -235,6 +235,11 @@ public static class StreamingHelpers state.VideoRequest.MaxHeight = resolution.MaxHeight; } } + + if (state.AudioStream is not null && !EncodingHelper.IsCopyCodec(state.OutputAudioCodec) && string.Equals(state.AudioStream.Codec, state.OutputAudioCodec, StringComparison.OrdinalIgnoreCase) && state.OutputAudioBitrate.HasValue) + { + state.OutputAudioCodec = state.SupportedAudioCodecs.Where(c => !EncodingHelper.LosslessAudioCodecs.Contains(c)).FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec); + } } var ext = string.IsNullOrWhiteSpace(state.OutputContainer) diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedCollectionModelBinder.cs index 3e3604b2a..25b84cbcc 100644 --- a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedCollectionModelBinder.cs @@ -8,18 +8,18 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Api.ModelBinders; /// <summary> -/// Comma delimited array model binder. +/// Comma delimited collection model binder. /// Returns an empty array of specified type if there is no query parameter. /// </summary> -public class CommaDelimitedArrayModelBinder : IModelBinder +public class CommaDelimitedCollectionModelBinder : IModelBinder { - private readonly ILogger<CommaDelimitedArrayModelBinder> _logger; + private readonly ILogger<CommaDelimitedCollectionModelBinder> _logger; /// <summary> - /// Initializes a new instance of the <see cref="CommaDelimitedArrayModelBinder"/> class. + /// Initializes a new instance of the <see cref="CommaDelimitedCollectionModelBinder"/> class. /// </summary> - /// <param name="logger">Instance of the <see cref="ILogger{CommaDelimitedArrayModelBinder}"/> interface.</param> - public CommaDelimitedArrayModelBinder(ILogger<CommaDelimitedArrayModelBinder> logger) + /// <param name="logger">Instance of the <see cref="ILogger{CommaDelimitedCollectionModelBinder}"/> interface.</param> + public CommaDelimitedCollectionModelBinder(ILogger<CommaDelimitedCollectionModelBinder> logger) { _logger = logger; } diff --git a/Jellyfin.Api/ModelBinders/PipeDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/PipeDelimitedCollectionModelBinder.cs index ae9f0a8cd..7d0fb2e19 100644 --- a/Jellyfin.Api/ModelBinders/PipeDelimitedArrayModelBinder.cs +++ b/Jellyfin.Api/ModelBinders/PipeDelimitedCollectionModelBinder.cs @@ -8,18 +8,18 @@ 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. +/// Comma delimited collection model binder. +/// Returns an empty collection of specified type if there is no query parameter. /// </summary> -public class PipeDelimitedArrayModelBinder : IModelBinder +public class PipeDelimitedCollectionModelBinder : IModelBinder { - private readonly ILogger<PipeDelimitedArrayModelBinder> _logger; + private readonly ILogger<PipeDelimitedCollectionModelBinder> _logger; /// <summary> - /// Initializes a new instance of the <see cref="PipeDelimitedArrayModelBinder"/> class. + /// Initializes a new instance of the <see cref="PipeDelimitedCollectionModelBinder"/> class. /// </summary> - /// <param name="logger">Instance of the <see cref="ILogger{PipeDelimitedArrayModelBinder}"/> interface.</param> - public PipeDelimitedArrayModelBinder(ILogger<PipeDelimitedArrayModelBinder> logger) + /// <param name="logger">Instance of the <see cref="ILogger{PipeDelimitedCollectionModelBinder}"/> interface.</param> + public PipeDelimitedCollectionModelBinder(ILogger<PipeDelimitedCollectionModelBinder> logger) { _logger = logger; } diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs index 190d90681..2616694d8 100644 --- a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs +++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -17,7 +18,7 @@ public class GetProgramsDto /// <summary> /// Gets or sets the channels to return guide information for. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<Guid>? ChannelIds { get; set; } /// <summary> @@ -93,25 +94,25 @@ public class GetProgramsDto /// <summary> /// Gets or sets specify one or more sort orders, comma delimited. Options: Name, StartDate. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<ItemSortBy>? SortBy { get; set; } /// <summary> /// Gets or sets sort order. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<SortOrder>? SortOrder { get; set; } /// <summary> /// Gets or sets the genres to return guide information for. /// </summary> - [JsonConverter(typeof(JsonPipeDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonPipeDelimitedCollectionConverterFactory))] public IReadOnlyList<string>? Genres { get; set; } /// <summary> /// Gets or sets the genre ids to return guide information for. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<Guid>? GenreIds { get; set; } /// <summary> @@ -133,7 +134,7 @@ public class GetProgramsDto /// <summary> /// Gets or sets the image types to include in the output. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<ImageType>? EnableImageTypes { get; set; } /// <summary> @@ -154,6 +155,6 @@ public class GetProgramsDto /// <summary> /// Gets or sets specify additional fields of information to return in the output. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<ItemFields>? Fields { get; set; } } diff --git a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs index 978e99b35..758c89938 100644 --- a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs +++ b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs @@ -61,7 +61,7 @@ public class OpenLiveStreamDto public bool? EnableDirectPlay { get; set; } /// <summary> - /// Gets or sets a value indicating whether to enale direct stream. + /// Gets or sets a value indicating whether to enable direct stream. /// </summary> public bool? EnableDirectStream { get; set; } diff --git a/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs index 82f603ca1..73ab76817 100644 --- a/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs +++ b/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.Dlna; namespace Jellyfin.Api.Models.MediaInfoDtos; /// <summary> -/// Plabyback info dto. +/// Playback info dto. /// </summary> public class PlaybackInfoDto { diff --git a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs index 61a3f2ed6..891d758c4 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs @@ -20,7 +20,7 @@ public class CreatePlaylistDto /// <summary> /// Gets or sets item ids to add to the playlist. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<Guid> Ids { get; set; } = []; /// <summary> diff --git a/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs index 80e20995c..339a0d5d2 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/UpdatePlaylistDto.cs @@ -19,7 +19,7 @@ public class UpdatePlaylistDto /// <summary> /// Gets or sets item ids of the playlist. /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + [JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))] public IReadOnlyList<Guid>? Ids { get; set; } /// <summary> diff --git a/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs index 1ae2cad4b..9c29e372c 100644 --- a/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs +++ b/Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel.DataAnnotations; namespace Jellyfin.Api.Models.StartupDtos; @@ -17,5 +18,6 @@ public class StartupRemoteAccessDto /// Gets or sets a value indicating whether enable automatic port mapping. /// </summary> [Required] + [Obsolete("No longer supported")] public bool EnableAutomaticPortMapping { get; set; } } diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index 99516e938..60379f415 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -1,7 +1,8 @@ using System; using System.Threading.Tasks; -using Jellyfin.Data.Enums; +using Jellyfin.Data; using Jellyfin.Data.Events; +using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; @@ -70,7 +71,9 @@ public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<Activi /// <param name="message">The message.</param> protected override void Start(WebSocketMessageInfo message) { - if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) + if (!message.Connection.AuthorizationInfo.IsApiKey + && (message.Connection.AuthorizationInfo.User is null + || !message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))) { throw new AuthenticationException("Only admin users can retrieve the activity log."); } diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs index a6cfe4d56..9d149cc85 100644 --- a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Jellyfin.Data.Enums; +using Jellyfin.Data; +using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; @@ -79,7 +80,9 @@ public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnume /// <param name="message">The message.</param> protected override void Start(WebSocketMessageInfo message) { - if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) + if (!message.Connection.AuthorizationInfo.IsApiKey + && (message.Connection.AuthorizationInfo.User is null + || !message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))) { throw new AuthenticationException("Only admin users can subscribe to session information."); } |
