mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-02 18:47:18 -05:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/api-migration' into api-livetv
This commit is contained in:
		
						commit
						14faebc7fe
					
				@ -42,11 +42,13 @@ namespace Jellyfin.Api.Auth
 | 
				
			|||||||
        /// <param name="claimsPrincipal">Request claims.</param>
 | 
					        /// <param name="claimsPrincipal">Request claims.</param>
 | 
				
			||||||
        /// <param name="ignoreSchedule">Whether to ignore parental control.</param>
 | 
					        /// <param name="ignoreSchedule">Whether to ignore parental control.</param>
 | 
				
			||||||
        /// <param name="localAccessOnly">Whether access is to be allowed locally only.</param>
 | 
					        /// <param name="localAccessOnly">Whether access is to be allowed locally only.</param>
 | 
				
			||||||
 | 
					        /// <param name="requiredDownloadPermission">Whether validation requires download permission.</param>
 | 
				
			||||||
        /// <returns>Validated claim status.</returns>
 | 
					        /// <returns>Validated claim status.</returns>
 | 
				
			||||||
        protected bool ValidateClaims(
 | 
					        protected bool ValidateClaims(
 | 
				
			||||||
            ClaimsPrincipal claimsPrincipal,
 | 
					            ClaimsPrincipal claimsPrincipal,
 | 
				
			||||||
            bool ignoreSchedule = false,
 | 
					            bool ignoreSchedule = false,
 | 
				
			||||||
            bool localAccessOnly = false)
 | 
					            bool localAccessOnly = false,
 | 
				
			||||||
 | 
					            bool requiredDownloadPermission = false)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // Ensure claim has userId.
 | 
					            // Ensure claim has userId.
 | 
				
			||||||
            var userId = ClaimHelpers.GetUserId(claimsPrincipal);
 | 
					            var userId = ClaimHelpers.GetUserId(claimsPrincipal);
 | 
				
			||||||
@ -89,6 +91,13 @@ namespace Jellyfin.Api.Auth
 | 
				
			|||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // User attempting to download without permission.
 | 
				
			||||||
 | 
					            if (requiredDownloadPermission
 | 
				
			||||||
 | 
					                && !user.HasPermission(PermissionKind.EnableContentDownloading))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										45
									
								
								Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Auth.DownloadPolicy
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Download authorization handler.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class DownloadHandler : BaseAuthorizationHandler<DownloadRequirement>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="DownloadHandler"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
 | 
				
			||||||
 | 
					        public DownloadHandler(
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            INetworkManager networkManager,
 | 
				
			||||||
 | 
					            IHttpContextAccessor httpContextAccessor)
 | 
				
			||||||
 | 
					            : base(userManager, networkManager, httpContextAccessor)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DownloadRequirement requirement)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var validated = ValidateClaims(context.User);
 | 
				
			||||||
 | 
					            if (validated)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                context.Succeed(requirement);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                context.Fail();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Task.CompletedTask;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								Jellyfin.Api/Auth/DownloadPolicy/DownloadRequirement.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Jellyfin.Api/Auth/DownloadPolicy/DownloadRequirement.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Auth.DownloadPolicy
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The download permission requirement.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class DownloadRequirement : IAuthorizationRequirement
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -29,5 +29,10 @@ namespace Jellyfin.Api.Constants
 | 
				
			|||||||
        /// Policy name for escaping schedule controls.
 | 
					        /// Policy name for escaping schedule controls.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public const string IgnoreSchedule = "IgnoreSchedule";
 | 
					        public const string IgnoreSchedule = "IgnoreSchedule";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Policy name for requiring download permission.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public const string Download = "Download";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -35,17 +35,14 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <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="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="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
        /// <param name="minDate">Optional. The minimum date. Format = ISO.</param>
 | 
					        /// <param name="minDate">Optional. The minimum date. Format = ISO.</param>
 | 
				
			||||||
        /// <param name="hasUserId">Optional. Only returns activities that have a user associated.</param>
 | 
					 | 
				
			||||||
        /// <response code="200">Activity log returned.</response>
 | 
					        /// <response code="200">Activity log returned.</response>
 | 
				
			||||||
        /// <returns>A <see cref="QueryResult{ActivityLogEntry}"/> containing the log entries.</returns>
 | 
					        /// <returns>A <see cref="QueryResult{ActivityLogEntry}"/> containing the log entries.</returns>
 | 
				
			||||||
        [HttpGet("Entries")]
 | 
					        [HttpGet("Entries")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "hasUserId", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        public ActionResult<QueryResult<ActivityLogEntry>> GetLogEntries(
 | 
					        public ActionResult<QueryResult<ActivityLogEntry>> GetLogEntries(
 | 
				
			||||||
            [FromQuery] int? startIndex,
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
            [FromQuery] int? limit,
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
            [FromQuery] DateTime? minDate,
 | 
					            [FromQuery] DateTime? minDate)
 | 
				
			||||||
            bool? hasUserId)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
 | 
					            var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
 | 
				
			||||||
                entries => entries.Where(entry => entry.DateCreated >= minDate));
 | 
					                entries => entries.Where(entry => entry.DateCreated >= minDate));
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										133
									
								
								Jellyfin.Api/Controllers/AlbumsController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								Jellyfin.Api/Controllers/AlbumsController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,133 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities.Audio;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The albums controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class AlbumsController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="AlbumsController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        public AlbumsController(
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Finds albums similar to a given album.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="albumId">The album id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="excludeArtistIds">Optional. Ids of artists to exclude.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Similar albums returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar albums.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Albums/{albumId}/Similar")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
 | 
				
			||||||
 | 
					            [FromRoute] string albumId,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string? excludeArtistIds,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return SimilarItemsHelper.GetSimilarItemsResult(
 | 
				
			||||||
 | 
					                dtoOptions,
 | 
				
			||||||
 | 
					                _userManager,
 | 
				
			||||||
 | 
					                _libraryManager,
 | 
				
			||||||
 | 
					                _dtoService,
 | 
				
			||||||
 | 
					                userId,
 | 
				
			||||||
 | 
					                albumId,
 | 
				
			||||||
 | 
					                excludeArtistIds,
 | 
				
			||||||
 | 
					                limit,
 | 
				
			||||||
 | 
					                new[] { typeof(MusicAlbum) },
 | 
				
			||||||
 | 
					                GetAlbumSimilarityScore);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Finds artists similar to a given artist.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="artistId">The artist id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="excludeArtistIds">Optional. Ids of artists to exclude.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Similar artists returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar artists.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Artists/{artistId}/Similar")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
 | 
				
			||||||
 | 
					            [FromRoute] string artistId,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string? excludeArtistIds,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return SimilarItemsHelper.GetSimilarItemsResult(
 | 
				
			||||||
 | 
					                dtoOptions,
 | 
				
			||||||
 | 
					                _userManager,
 | 
				
			||||||
 | 
					                _libraryManager,
 | 
				
			||||||
 | 
					                _dtoService,
 | 
				
			||||||
 | 
					                userId,
 | 
				
			||||||
 | 
					                artistId,
 | 
				
			||||||
 | 
					                excludeArtistIds,
 | 
				
			||||||
 | 
					                limit,
 | 
				
			||||||
 | 
					                new[] { typeof(MusicArtist) },
 | 
				
			||||||
 | 
					                SimilarItemsHelper.GetSimiliarityScore);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets a similairty score of two albums.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="item1">The first item.</param>
 | 
				
			||||||
 | 
					        /// <param name="item1People">The item1 people.</param>
 | 
				
			||||||
 | 
					        /// <param name="allPeople">All people.</param>
 | 
				
			||||||
 | 
					        /// <param name="item2">The second item.</param>
 | 
				
			||||||
 | 
					        /// <returns>System.Int32.</returns>
 | 
				
			||||||
 | 
					        private int GetAlbumSimilarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var points = SimilarItemsHelper.GetSimiliarityScore(item1, item1People, allPeople, item2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var album1 = (MusicAlbum)item1;
 | 
				
			||||||
 | 
					            var album2 = (MusicAlbum)item2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var artists1 = album1
 | 
				
			||||||
 | 
					                .GetAllArtists()
 | 
				
			||||||
 | 
					                .DistinctNames()
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var artists2 = new HashSet<string>(
 | 
				
			||||||
 | 
					                album2.GetAllArtists().DistinctNames(),
 | 
				
			||||||
 | 
					                StringComparer.OrdinalIgnoreCase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return points + artists1.Where(artists2.Contains).Sum(i => 5);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("Keys")]
 | 
					        [HttpPost("Keys")]
 | 
				
			||||||
        [Authorize(Policy = Policies.RequiresElevation)]
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult CreateKey([FromQuery, Required] string app)
 | 
					        public ActionResult CreateKey([FromQuery, Required] string? app)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _authRepo.Create(new AuthenticationInfo
 | 
					            _authRepo.Create(new AuthenticationInfo
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpDelete("Keys/{key}")]
 | 
					        [HttpDelete("Keys/{key}")]
 | 
				
			||||||
        [Authorize(Policy = Policies.RequiresElevation)]
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult RevokeKey([FromRoute] string key)
 | 
					        public ActionResult RevokeKey([FromRoute] string? key)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _sessionManager.RevokeToken(key);
 | 
					            _sessionManager.RevokeToken(key);
 | 
				
			||||||
            return NoContent();
 | 
					            return NoContent();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										488
									
								
								Jellyfin.Api/Controllers/ArtistsController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										488
									
								
								Jellyfin.Api/Controllers/ArtistsController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,488 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using Jellyfin.Data.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The artists controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    [Route("/Artists")]
 | 
				
			||||||
 | 
					    public class ArtistsController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="ArtistsController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        public ArtistsController(
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets all artists from a given item, folder, or the entire library.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="minCommunityRating">Optional filter by minimum community rating.</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="searchTerm">Optional. Search term.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
 | 
				
			||||||
 | 
					        /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param>
 | 
				
			||||||
 | 
					        /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional, include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
 | 
				
			||||||
 | 
					        /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person ids.</param>
 | 
				
			||||||
 | 
					        /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional, include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableTotalRecordCount">Total record count.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Artists returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the artists.</returns>
 | 
				
			||||||
 | 
					        [HttpGet]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetArtists(
 | 
				
			||||||
 | 
					            [FromQuery] double? minCommunityRating,
 | 
				
			||||||
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string searchTerm,
 | 
				
			||||||
 | 
					            [FromQuery] string parentId,
 | 
				
			||||||
 | 
					            [FromQuery] string fields,
 | 
				
			||||||
 | 
					            [FromQuery] string excludeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string includeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string filters,
 | 
				
			||||||
 | 
					            [FromQuery] bool? isFavorite,
 | 
				
			||||||
 | 
					            [FromQuery] string mediaTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string genres,
 | 
				
			||||||
 | 
					            [FromQuery] string genreIds,
 | 
				
			||||||
 | 
					            [FromQuery] string officialRatings,
 | 
				
			||||||
 | 
					            [FromQuery] string tags,
 | 
				
			||||||
 | 
					            [FromQuery] string years,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string person,
 | 
				
			||||||
 | 
					            [FromQuery] string personIds,
 | 
				
			||||||
 | 
					            [FromQuery] string personTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string studios,
 | 
				
			||||||
 | 
					            [FromQuery] string studioIds,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string nameStartsWithOrGreater,
 | 
				
			||||||
 | 
					            [FromQuery] string nameStartsWith,
 | 
				
			||||||
 | 
					            [FromQuery] string nameLessThan,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableTotalRecordCount = true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            User? user = null;
 | 
				
			||||||
 | 
					            BaseItem parentItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!userId.Equals(Guid.Empty))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					                parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
 | 
				
			||||||
 | 
					            var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
 | 
				
			||||||
 | 
					            var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var query = new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ExcludeItemTypes = excludeItemTypesArr,
 | 
				
			||||||
 | 
					                IncludeItemTypes = includeItemTypesArr,
 | 
				
			||||||
 | 
					                MediaTypes = mediaTypesArr,
 | 
				
			||||||
 | 
					                StartIndex = startIndex,
 | 
				
			||||||
 | 
					                Limit = limit,
 | 
				
			||||||
 | 
					                IsFavorite = isFavorite,
 | 
				
			||||||
 | 
					                NameLessThan = nameLessThan,
 | 
				
			||||||
 | 
					                NameStartsWith = nameStartsWith,
 | 
				
			||||||
 | 
					                NameStartsWithOrGreater = nameStartsWithOrGreater,
 | 
				
			||||||
 | 
					                Tags = RequestHelpers.Split(tags, ',', true),
 | 
				
			||||||
 | 
					                OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
 | 
				
			||||||
 | 
					                Genres = RequestHelpers.Split(genres, ',', true),
 | 
				
			||||||
 | 
					                GenreIds = RequestHelpers.GetGuids(genreIds),
 | 
				
			||||||
 | 
					                StudioIds = RequestHelpers.GetGuids(studioIds),
 | 
				
			||||||
 | 
					                Person = person,
 | 
				
			||||||
 | 
					                PersonIds = RequestHelpers.GetGuids(personIds),
 | 
				
			||||||
 | 
					                PersonTypes = RequestHelpers.Split(personTypes, ',', true),
 | 
				
			||||||
 | 
					                Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
 | 
				
			||||||
 | 
					                MinCommunityRating = minCommunityRating,
 | 
				
			||||||
 | 
					                DtoOptions = dtoOptions,
 | 
				
			||||||
 | 
					                SearchTerm = searchTerm,
 | 
				
			||||||
 | 
					                EnableTotalRecordCount = enableTotalRecordCount
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!string.IsNullOrWhiteSpace(parentId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (parentItem is Folder)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    query.AncestorIds = new[] { new Guid(parentId) };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    query.ItemIds = new[] { new Guid(parentId) };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Studios
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(studios))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query.StudioIds = studios.Split('|').Select(i =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    try
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return _libraryManager.GetStudio(i);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    catch
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return null;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }).Where(i => i != null).Select(i => i!.Id).ToArray();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var filter in RequestHelpers.GetFilters(filters))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                switch (filter)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    case ItemFilter.Dislikes:
 | 
				
			||||||
 | 
					                        query.IsLiked = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFavorite:
 | 
				
			||||||
 | 
					                        query.IsFavorite = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFavoriteOrLikes:
 | 
				
			||||||
 | 
					                        query.IsFavoriteOrLiked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFolder:
 | 
				
			||||||
 | 
					                        query.IsFolder = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsNotFolder:
 | 
				
			||||||
 | 
					                        query.IsFolder = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsPlayed:
 | 
				
			||||||
 | 
					                        query.IsPlayed = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsResumable:
 | 
				
			||||||
 | 
					                        query.IsResumable = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsUnplayed:
 | 
				
			||||||
 | 
					                        query.IsPlayed = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.Likes:
 | 
				
			||||||
 | 
					                        query.IsLiked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result = _libraryManager.GetArtists(query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtos = result.Items.Select(i =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var (baseItem, itemCounts) = i;
 | 
				
			||||||
 | 
					                var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!string.IsNullOrWhiteSpace(includeItemTypes))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dto.ChildCount = itemCounts.ItemCount;
 | 
				
			||||||
 | 
					                    dto.ProgramCount = itemCounts.ProgramCount;
 | 
				
			||||||
 | 
					                    dto.SeriesCount = itemCounts.SeriesCount;
 | 
				
			||||||
 | 
					                    dto.EpisodeCount = itemCounts.EpisodeCount;
 | 
				
			||||||
 | 
					                    dto.MovieCount = itemCounts.MovieCount;
 | 
				
			||||||
 | 
					                    dto.TrailerCount = itemCounts.TrailerCount;
 | 
				
			||||||
 | 
					                    dto.AlbumCount = itemCounts.AlbumCount;
 | 
				
			||||||
 | 
					                    dto.SongCount = itemCounts.SongCount;
 | 
				
			||||||
 | 
					                    dto.ArtistCount = itemCounts.ArtistCount;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return dto;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Items = dtos.ToArray(),
 | 
				
			||||||
 | 
					                TotalRecordCount = result.TotalRecordCount
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets all album artists from a given item, folder, or the entire library.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="minCommunityRating">Optional filter by minimum community rating.</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="searchTerm">Optional. Search term.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
 | 
				
			||||||
 | 
					        /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param>
 | 
				
			||||||
 | 
					        /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional, include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
 | 
				
			||||||
 | 
					        /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person ids.</param>
 | 
				
			||||||
 | 
					        /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional, include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableTotalRecordCount">Total record count.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Album artists returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the album artists.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("AlbumArtists")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetAlbumArtists(
 | 
				
			||||||
 | 
					            [FromQuery] double? minCommunityRating,
 | 
				
			||||||
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string searchTerm,
 | 
				
			||||||
 | 
					            [FromQuery] string parentId,
 | 
				
			||||||
 | 
					            [FromQuery] string fields,
 | 
				
			||||||
 | 
					            [FromQuery] string excludeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string includeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string filters,
 | 
				
			||||||
 | 
					            [FromQuery] bool? isFavorite,
 | 
				
			||||||
 | 
					            [FromQuery] string mediaTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string genres,
 | 
				
			||||||
 | 
					            [FromQuery] string genreIds,
 | 
				
			||||||
 | 
					            [FromQuery] string officialRatings,
 | 
				
			||||||
 | 
					            [FromQuery] string tags,
 | 
				
			||||||
 | 
					            [FromQuery] string years,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string person,
 | 
				
			||||||
 | 
					            [FromQuery] string personIds,
 | 
				
			||||||
 | 
					            [FromQuery] string personTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string studios,
 | 
				
			||||||
 | 
					            [FromQuery] string studioIds,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string nameStartsWithOrGreater,
 | 
				
			||||||
 | 
					            [FromQuery] string nameStartsWith,
 | 
				
			||||||
 | 
					            [FromQuery] string nameLessThan,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableTotalRecordCount = true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            User? user = null;
 | 
				
			||||||
 | 
					            BaseItem parentItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!userId.Equals(Guid.Empty))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					                parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
 | 
				
			||||||
 | 
					            var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
 | 
				
			||||||
 | 
					            var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var query = new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ExcludeItemTypes = excludeItemTypesArr,
 | 
				
			||||||
 | 
					                IncludeItemTypes = includeItemTypesArr,
 | 
				
			||||||
 | 
					                MediaTypes = mediaTypesArr,
 | 
				
			||||||
 | 
					                StartIndex = startIndex,
 | 
				
			||||||
 | 
					                Limit = limit,
 | 
				
			||||||
 | 
					                IsFavorite = isFavorite,
 | 
				
			||||||
 | 
					                NameLessThan = nameLessThan,
 | 
				
			||||||
 | 
					                NameStartsWith = nameStartsWith,
 | 
				
			||||||
 | 
					                NameStartsWithOrGreater = nameStartsWithOrGreater,
 | 
				
			||||||
 | 
					                Tags = RequestHelpers.Split(tags, ',', true),
 | 
				
			||||||
 | 
					                OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
 | 
				
			||||||
 | 
					                Genres = RequestHelpers.Split(genres, ',', true),
 | 
				
			||||||
 | 
					                GenreIds = RequestHelpers.GetGuids(genreIds),
 | 
				
			||||||
 | 
					                StudioIds = RequestHelpers.GetGuids(studioIds),
 | 
				
			||||||
 | 
					                Person = person,
 | 
				
			||||||
 | 
					                PersonIds = RequestHelpers.GetGuids(personIds),
 | 
				
			||||||
 | 
					                PersonTypes = RequestHelpers.Split(personTypes, ',', true),
 | 
				
			||||||
 | 
					                Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
 | 
				
			||||||
 | 
					                MinCommunityRating = minCommunityRating,
 | 
				
			||||||
 | 
					                DtoOptions = dtoOptions,
 | 
				
			||||||
 | 
					                SearchTerm = searchTerm,
 | 
				
			||||||
 | 
					                EnableTotalRecordCount = enableTotalRecordCount
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!string.IsNullOrWhiteSpace(parentId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (parentItem is Folder)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    query.AncestorIds = new[] { new Guid(parentId) };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    query.ItemIds = new[] { new Guid(parentId) };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Studios
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(studios))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query.StudioIds = studios.Split('|').Select(i =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    try
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return _libraryManager.GetStudio(i);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    catch
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return null;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }).Where(i => i != null).Select(i => i!.Id).ToArray();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var filter in RequestHelpers.GetFilters(filters))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                switch (filter)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    case ItemFilter.Dislikes:
 | 
				
			||||||
 | 
					                        query.IsLiked = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFavorite:
 | 
				
			||||||
 | 
					                        query.IsFavorite = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFavoriteOrLikes:
 | 
				
			||||||
 | 
					                        query.IsFavoriteOrLiked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFolder:
 | 
				
			||||||
 | 
					                        query.IsFolder = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsNotFolder:
 | 
				
			||||||
 | 
					                        query.IsFolder = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsPlayed:
 | 
				
			||||||
 | 
					                        query.IsPlayed = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsResumable:
 | 
				
			||||||
 | 
					                        query.IsResumable = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsUnplayed:
 | 
				
			||||||
 | 
					                        query.IsPlayed = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.Likes:
 | 
				
			||||||
 | 
					                        query.IsLiked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result = _libraryManager.GetAlbumArtists(query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtos = result.Items.Select(i =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var (baseItem, itemCounts) = i;
 | 
				
			||||||
 | 
					                var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!string.IsNullOrWhiteSpace(includeItemTypes))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dto.ChildCount = itemCounts.ItemCount;
 | 
				
			||||||
 | 
					                    dto.ProgramCount = itemCounts.ProgramCount;
 | 
				
			||||||
 | 
					                    dto.SeriesCount = itemCounts.SeriesCount;
 | 
				
			||||||
 | 
					                    dto.EpisodeCount = itemCounts.EpisodeCount;
 | 
				
			||||||
 | 
					                    dto.MovieCount = itemCounts.MovieCount;
 | 
				
			||||||
 | 
					                    dto.TrailerCount = itemCounts.TrailerCount;
 | 
				
			||||||
 | 
					                    dto.AlbumCount = itemCounts.AlbumCount;
 | 
				
			||||||
 | 
					                    dto.SongCount = itemCounts.SongCount;
 | 
				
			||||||
 | 
					                    dto.ArtistCount = itemCounts.ArtistCount;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return dto;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Items = dtos.ToArray(),
 | 
				
			||||||
 | 
					                TotalRecordCount = result.TotalRecordCount
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets an artist by name.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="name">Studio name.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Artist returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the artist.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("{name}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<BaseItemDto> GetArtistByName([FromRoute] string name, [FromQuery] Guid userId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetArtist(name, dtoOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!userId.Equals(Guid.Empty))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return _dtoService.GetBaseItemDto(item, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										110
									
								
								Jellyfin.Api/Controllers/CollectionController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								Jellyfin.Api/Controllers/CollectionController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Collections;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Collections;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The collection controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    [Route("/Collections")]
 | 
				
			||||||
 | 
					    public class CollectionController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly ICollectionManager _collectionManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					        private readonly IAuthorizationContext _authContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="CollectionController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="collectionManager">Instance of <see cref="ICollectionManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="authContext">Instance of <see cref="IAuthorizationContext"/> interface.</param>
 | 
				
			||||||
 | 
					        public CollectionController(
 | 
				
			||||||
 | 
					            ICollectionManager collectionManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService,
 | 
				
			||||||
 | 
					            IAuthorizationContext authContext)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _collectionManager = collectionManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					            _authContext = authContext;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Creates a new collection.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="name">The name of the collection.</param>
 | 
				
			||||||
 | 
					        /// <param name="ids">Item Ids to add to the collection.</param>
 | 
				
			||||||
 | 
					        /// <param name="isLocked">Whether or not to lock the new collection.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Optional. Create the collection within a specific folder.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Collection created.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="CollectionCreationOptions"/> with information about the new collection.</returns>
 | 
				
			||||||
 | 
					        [HttpPost]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<CollectionCreationResult> CreateCollection(
 | 
				
			||||||
 | 
					            [FromQuery] string? name,
 | 
				
			||||||
 | 
					            [FromQuery] string? ids,
 | 
				
			||||||
 | 
					            [FromQuery] bool isLocked,
 | 
				
			||||||
 | 
					            [FromQuery] Guid? parentId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var userId = _authContext.GetAuthorizationInfo(Request).UserId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = _collectionManager.CreateCollection(new CollectionCreationOptions
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IsLocked = isLocked,
 | 
				
			||||||
 | 
					                Name = name,
 | 
				
			||||||
 | 
					                ParentId = parentId,
 | 
				
			||||||
 | 
					                ItemIdList = RequestHelpers.Split(ids, ',', true),
 | 
				
			||||||
 | 
					                UserIds = new[] { userId }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dto = _dtoService.GetBaseItemDto(item, dtoOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new CollectionCreationResult
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Id = dto.Id
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Adds items to a collection.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="collectionId">The collection id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemIds">Item ids, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Items added to collection.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("{collectionId}/Items")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery] string? itemIds)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _collectionManager.AddToCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Removes items from a collection.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="collectionId">The collection id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemIds">Item ids, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Items removed from collection.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 | 
				
			||||||
 | 
					        [HttpDelete("{collectionId}/Items")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery] string? itemIds)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _collectionManager.RemoveFromCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -70,7 +70,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <returns>Configuration.</returns>
 | 
					        /// <returns>Configuration.</returns>
 | 
				
			||||||
        [HttpGet("Configuration/{key}")]
 | 
					        [HttpGet("Configuration/{key}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public ActionResult<object> GetNamedConfiguration([FromRoute] string key)
 | 
					        public ActionResult<object> GetNamedConfiguration([FromRoute] string? key)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return _configurationManager.GetConfiguration(key);
 | 
					            return _configurationManager.GetConfiguration(key);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("Configuration/{key}")]
 | 
					        [HttpPost("Configuration/{key}")]
 | 
				
			||||||
        [Authorize(Policy = Policies.RequiresElevation)]
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string key)
 | 
					        public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string? key)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var configurationType = _configurationManager.GetConfigurationType(key);
 | 
					            var configurationType = _configurationManager.GetConfigurationType(key);
 | 
				
			||||||
            var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType).ConfigureAwait(false);
 | 
					            var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType).ConfigureAwait(false);
 | 
				
			||||||
 | 
				
			|||||||
@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpGet("/web/ConfigurationPage")]
 | 
					        [HttpGet("/web/ConfigurationPage")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult GetDashboardConfigurationPage([FromQuery] string name)
 | 
					        public ActionResult GetDashboardConfigurationPage([FromQuery] string? name)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            IPlugin? plugin = null;
 | 
					            IPlugin? plugin = null;
 | 
				
			||||||
            Stream? stream = null;
 | 
					            Stream? stream = null;
 | 
				
			||||||
@ -178,14 +178,13 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ApiExplorerSettings(IgnoreApi = true)]
 | 
					        [ApiExplorerSettings(IgnoreApi = true)]
 | 
				
			||||||
        public ActionResult GetRobotsTxt()
 | 
					        public ActionResult GetRobotsTxt()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return GetWebClientResource("robots.txt", string.Empty);
 | 
					            return GetWebClientResource("robots.txt");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets a resource from the web client.
 | 
					        /// Gets a resource from the web client.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="resourceName">The resource name.</param>
 | 
					        /// <param name="resourceName">The resource name.</param>
 | 
				
			||||||
        /// <param name="v">The v.</param>
 | 
					 | 
				
			||||||
        /// <response code="200">Web client returned.</response>
 | 
					        /// <response code="200">Web client returned.</response>
 | 
				
			||||||
        /// <response code="404">Server does not host a web client.</response>
 | 
					        /// <response code="404">Server does not host a web client.</response>
 | 
				
			||||||
        /// <returns>The resource.</returns>
 | 
					        /// <returns>The resource.</returns>
 | 
				
			||||||
@ -193,10 +192,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ApiExplorerSettings(IgnoreApi = true)]
 | 
					        [ApiExplorerSettings(IgnoreApi = true)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "v", Justification = "Imported from ServiceStack")]
 | 
					        public ActionResult GetWebClientResource([FromRoute] string resourceName)
 | 
				
			||||||
        public ActionResult GetWebClientResource(
 | 
					 | 
				
			||||||
            [FromRoute] string resourceName,
 | 
					 | 
				
			||||||
            [FromQuery] string? v)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!_appConfig.HostWebClient() || WebClientUiPath == null)
 | 
					            if (!_appConfig.HostWebClient() || WebClientUiPath == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -228,7 +224,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ApiExplorerSettings(IgnoreApi = true)]
 | 
					        [ApiExplorerSettings(IgnoreApi = true)]
 | 
				
			||||||
        public ActionResult GetFavIcon()
 | 
					        public ActionResult GetFavIcon()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return GetWebClientResource("favicon.ico", string.Empty);
 | 
					            return GetWebClientResource("favicon.ico");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [Authorize(Policy = Policies.RequiresElevation)]
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, BindRequired] string id)
 | 
					        public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, BindRequired] string? id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var deviceInfo = _deviceManager.GetDevice(id);
 | 
					            var deviceInfo = _deviceManager.GetDevice(id);
 | 
				
			||||||
            if (deviceInfo == null)
 | 
					            if (deviceInfo == null)
 | 
				
			||||||
@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [Authorize(Policy = Policies.RequiresElevation)]
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, BindRequired] string id)
 | 
					        public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, BindRequired] string? id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var deviceInfo = _deviceManager.GetDeviceOptions(id);
 | 
					            var deviceInfo = _deviceManager.GetDeviceOptions(id);
 | 
				
			||||||
            if (deviceInfo == null)
 | 
					            if (deviceInfo == null)
 | 
				
			||||||
@ -111,7 +111,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult UpdateDeviceOptions(
 | 
					        public ActionResult UpdateDeviceOptions(
 | 
				
			||||||
            [FromQuery, BindRequired] string id,
 | 
					            [FromQuery, BindRequired] string? id,
 | 
				
			||||||
            [FromBody, BindRequired] DeviceOptions deviceOptions)
 | 
					            [FromBody, BindRequired] DeviceOptions deviceOptions)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
 | 
					            var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
 | 
				
			||||||
@ -134,7 +134,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpDelete]
 | 
					        [HttpDelete]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult DeleteDevice([FromQuery, BindRequired] string id)
 | 
					        public ActionResult DeleteDevice([FromQuery, BindRequired] string? id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var existingDevice = _deviceManager.GetDevice(id);
 | 
					            var existingDevice = _deviceManager.GetDevice(id);
 | 
				
			||||||
            if (existingDevice == null)
 | 
					            if (existingDevice == null)
 | 
				
			||||||
 | 
				
			|||||||
@ -39,9 +39,9 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpGet("{displayPreferencesId}")]
 | 
					        [HttpGet("{displayPreferencesId}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public ActionResult<DisplayPreferences> GetDisplayPreferences(
 | 
					        public ActionResult<DisplayPreferences> GetDisplayPreferences(
 | 
				
			||||||
            [FromRoute] string displayPreferencesId,
 | 
					            [FromRoute] string? displayPreferencesId,
 | 
				
			||||||
            [FromQuery] [Required] string userId,
 | 
					            [FromQuery] [Required] string? userId,
 | 
				
			||||||
            [FromQuery] [Required] string client)
 | 
					            [FromQuery] [Required] string? client)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return _displayPreferencesRepository.GetDisplayPreferences(displayPreferencesId, userId, client);
 | 
					            return _displayPreferencesRepository.GetDisplayPreferences(displayPreferencesId, userId, client);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -59,9 +59,9 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
        public ActionResult UpdateDisplayPreferences(
 | 
					        public ActionResult UpdateDisplayPreferences(
 | 
				
			||||||
            [FromRoute] string displayPreferencesId,
 | 
					            [FromRoute] string? displayPreferencesId,
 | 
				
			||||||
            [FromQuery, BindRequired] string userId,
 | 
					            [FromQuery, BindRequired] string? userId,
 | 
				
			||||||
            [FromQuery, BindRequired] string client,
 | 
					            [FromQuery, BindRequired] string? client,
 | 
				
			||||||
            [FromBody, BindRequired] DisplayPreferences displayPreferences)
 | 
					            [FromBody, BindRequired] DisplayPreferences displayPreferences)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _displayPreferencesRepository.SaveDisplayPreferences(
 | 
					            _displayPreferencesRepository.SaveDisplayPreferences(
 | 
				
			||||||
 | 
				
			|||||||
@ -125,7 +125,6 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <param name="userId">Optional. User id.</param>
 | 
					        /// <param name="userId">Optional. User id.</param>
 | 
				
			||||||
        /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
					        /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
        /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
 | 
					        /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
        /// <param name="mediaTypes">[Unused] Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
 | 
					 | 
				
			||||||
        /// <param name="isAiring">Optional. Is item airing.</param>
 | 
					        /// <param name="isAiring">Optional. Is item airing.</param>
 | 
				
			||||||
        /// <param name="isMovie">Optional. Is item movie.</param>
 | 
					        /// <param name="isMovie">Optional. Is item movie.</param>
 | 
				
			||||||
        /// <param name="isSports">Optional. Is item sports.</param>
 | 
					        /// <param name="isSports">Optional. Is item sports.</param>
 | 
				
			||||||
@ -137,12 +136,10 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <returns>Query filters.</returns>
 | 
					        /// <returns>Query filters.</returns>
 | 
				
			||||||
        [HttpGet("/Items/Filters2")]
 | 
					        [HttpGet("/Items/Filters2")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "mediaTypes", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        public ActionResult<QueryFilters> GetQueryFilters(
 | 
					        public ActionResult<QueryFilters> GetQueryFilters(
 | 
				
			||||||
            [FromQuery] Guid? userId,
 | 
					            [FromQuery] Guid? userId,
 | 
				
			||||||
            [FromQuery] string? parentId,
 | 
					            [FromQuery] string? parentId,
 | 
				
			||||||
            [FromQuery] string? includeItemTypes,
 | 
					            [FromQuery] string? includeItemTypes,
 | 
				
			||||||
            [FromQuery] string? mediaTypes,
 | 
					 | 
				
			||||||
            [FromQuery] bool? isAiring,
 | 
					            [FromQuery] bool? isAiring,
 | 
				
			||||||
            [FromQuery] bool? isMovie,
 | 
					            [FromQuery] bool? isMovie,
 | 
				
			||||||
            [FromQuery] bool? isSports,
 | 
					            [FromQuery] bool? isSports,
 | 
				
			||||||
 | 
				
			|||||||
@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [Produces(MediaTypeNames.Application.Octet)]
 | 
					        [Produces(MediaTypeNames.Application.Octet)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult<FileStreamResult> GetGeneralImage([FromRoute] string name, [FromRoute] string type)
 | 
					        public ActionResult<FileStreamResult> GetGeneralImage([FromRoute] string? name, [FromRoute] string? type)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase)
 | 
					            var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase)
 | 
				
			||||||
                ? "folder"
 | 
					                ? "folder"
 | 
				
			||||||
@ -110,8 +110,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult<FileStreamResult> GetRatingImage(
 | 
					        public ActionResult<FileStreamResult> GetRatingImage(
 | 
				
			||||||
            [FromRoute] string theme,
 | 
					            [FromRoute] string? theme,
 | 
				
			||||||
            [FromRoute] string name)
 | 
					            [FromRoute] string? name)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return GetImageFile(_applicationPaths.RatingsPath, theme, name);
 | 
					            return GetImageFile(_applicationPaths.RatingsPath, theme, name);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -143,8 +143,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult<FileStreamResult> GetMediaInfoImage(
 | 
					        public ActionResult<FileStreamResult> GetMediaInfoImage(
 | 
				
			||||||
            [FromRoute] string theme,
 | 
					            [FromRoute] string? theme,
 | 
				
			||||||
            [FromRoute] string name)
 | 
					            [FromRoute] string? name)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name);
 | 
					            return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -156,7 +156,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <param name="theme">Theme to search.</param>
 | 
					        /// <param name="theme">Theme to search.</param>
 | 
				
			||||||
        /// <param name="name">File name to search for.</param>
 | 
					        /// <param name="name">File name to search for.</param>
 | 
				
			||||||
        /// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns>
 | 
					        /// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns>
 | 
				
			||||||
        private ActionResult<FileStreamResult> GetImageFile(string basePath, string theme, string name)
 | 
					        private ActionResult<FileStreamResult> GetImageFile(string basePath, string? theme, string? name)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var themeFolder = Path.Combine(basePath, theme);
 | 
					            var themeFolder = Path.Combine(basePath, theme);
 | 
				
			||||||
            if (Directory.Exists(themeFolder))
 | 
					            if (Directory.Exists(themeFolder))
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										314
									
								
								Jellyfin.Api/Controllers/InstantMixController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								Jellyfin.Api/Controllers/InstantMixController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,314 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Data.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Playlists;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The instant mix controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    public class InstantMixController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly IMusicManager _musicManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="InstantMixController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="musicManager">Instance of the <see cref="IMusicManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        public InstantMixController(
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService,
 | 
				
			||||||
 | 
					            IMusicManager musicManager,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					            _musicManager = musicManager;
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Creates an instant playlist based on a given song.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Songs/{id}/InstantMix")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
 | 
				
			||||||
 | 
					            [FromRoute] Guid id,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					            var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
				
			||||||
 | 
					            return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Creates an instant playlist based on a given song.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Albums/{id}/InstantMix")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
 | 
				
			||||||
 | 
					            [FromRoute] Guid id,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var album = _libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					            var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions);
 | 
				
			||||||
 | 
					            return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Creates an instant playlist based on a given song.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Playlists/{id}/InstantMix")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
 | 
				
			||||||
 | 
					            [FromRoute] Guid id,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var playlist = (Playlist)_libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					            var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions);
 | 
				
			||||||
 | 
					            return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Creates an instant playlist based on a given song.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="name">The genre name.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/MusicGenres/{name}/InstantMix")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
 | 
				
			||||||
 | 
					            [FromRoute] string? name,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					            var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions);
 | 
				
			||||||
 | 
					            return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Creates an instant playlist based on a given song.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Artists/InstantMix")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
 | 
				
			||||||
 | 
					            [FromRoute] Guid id,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					            var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
				
			||||||
 | 
					            return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Creates an instant playlist based on a given song.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/MusicGenres/InstantMix")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
 | 
				
			||||||
 | 
					            [FromRoute] Guid id,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					            var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
				
			||||||
 | 
					            return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Creates an instant playlist based on a given song.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Items/{id}/InstantMix")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
 | 
				
			||||||
 | 
					            [FromRoute] Guid id,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					            var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
				
			||||||
 | 
					            return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User user, int? limit, DtoOptions dtoOptions)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var list = items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result = new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                TotalRecordCount = list.Count
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (limit.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                list = list.Take(limit.Value).ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result.Items = returnList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -47,7 +47,6 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <param name="imageRefreshMode">(Optional) Specifies the image refresh mode.</param>
 | 
					        /// <param name="imageRefreshMode">(Optional) Specifies the image refresh mode.</param>
 | 
				
			||||||
        /// <param name="replaceAllMetadata">(Optional) Determines if metadata should be replaced. Only applicable if mode is FullRefresh.</param>
 | 
					        /// <param name="replaceAllMetadata">(Optional) Determines if metadata should be replaced. Only applicable if mode is FullRefresh.</param>
 | 
				
			||||||
        /// <param name="replaceAllImages">(Optional) Determines if images should be replaced. Only applicable if mode is FullRefresh.</param>
 | 
					        /// <param name="replaceAllImages">(Optional) Determines if images should be replaced. Only applicable if mode is FullRefresh.</param>
 | 
				
			||||||
        /// <param name="recursive">(Unused) Indicates if the refresh should occur recursively.</param>
 | 
					 | 
				
			||||||
        /// <response code="204">Item metadata refresh queued.</response>
 | 
					        /// <response code="204">Item metadata refresh queued.</response>
 | 
				
			||||||
        /// <response code="404">Item to refresh not found.</response>
 | 
					        /// <response code="404">Item to refresh not found.</response>
 | 
				
			||||||
        /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
 | 
					        /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
 | 
				
			||||||
@ -55,14 +54,12 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [Description("Refreshes metadata for an item.")]
 | 
					        [Description("Refreshes metadata for an item.")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "recursive", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        public ActionResult Post(
 | 
					        public ActionResult Post(
 | 
				
			||||||
            [FromRoute] Guid itemId,
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
            [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
 | 
					            [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
 | 
				
			||||||
            [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
 | 
					            [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
 | 
				
			||||||
            [FromQuery] bool replaceAllMetadata = false,
 | 
					            [FromQuery] bool replaceAllMetadata = false,
 | 
				
			||||||
            [FromQuery] bool replaceAllImages = false,
 | 
					            [FromQuery] bool replaceAllImages = false)
 | 
				
			||||||
            [FromQuery] bool recursive = false)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = _libraryManager.GetItemById(itemId);
 | 
					            var item = _libraryManager.GetItemById(itemId);
 | 
				
			||||||
            if (item == null)
 | 
					            if (item == null)
 | 
				
			||||||
 | 
				
			|||||||
@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Items/{itemId}/ContentType")]
 | 
					        [HttpPost("/Items/{itemId}/ContentType")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string contentType)
 | 
					        public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string? contentType)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = _libraryManager.GetItemById(itemId);
 | 
					            var item = _libraryManager.GetItemById(itemId);
 | 
				
			||||||
            if (item == null)
 | 
					            if (item == null)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1030
									
								
								Jellyfin.Api/Controllers/LibraryController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1030
									
								
								Jellyfin.Api/Controllers/LibraryController.cs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -50,13 +50,11 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets all virtual folders.
 | 
					        /// Gets all virtual folders.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="userId">The user id.</param>
 | 
					 | 
				
			||||||
        /// <response code="200">Virtual folders retrieved.</response>
 | 
					        /// <response code="200">Virtual folders retrieved.</response>
 | 
				
			||||||
        /// <returns>An <see cref="IEnumerable{VirtualFolderInfo}"/> with the virtual folders.</returns>
 | 
					        /// <returns>An <see cref="IEnumerable{VirtualFolderInfo}"/> with the virtual folders.</returns>
 | 
				
			||||||
        [HttpGet]
 | 
					        [HttpGet]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
 | 
					        public ActionResult<IEnumerable<VirtualFolderInfo>> GetVirtualFolders()
 | 
				
			||||||
        public ActionResult<IEnumerable<VirtualFolderInfo>> GetVirtualFolders([FromQuery] string userId)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return _libraryManager.GetVirtualFolders(true);
 | 
					            return _libraryManager.GetVirtualFolders(true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -74,8 +72,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost]
 | 
					        [HttpPost]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public async Task<ActionResult> AddVirtualFolder(
 | 
					        public async Task<ActionResult> AddVirtualFolder(
 | 
				
			||||||
            [FromQuery] string name,
 | 
					            [FromQuery] string? name,
 | 
				
			||||||
            [FromQuery] string collectionType,
 | 
					            [FromQuery] string? collectionType,
 | 
				
			||||||
            [FromQuery] bool refreshLibrary,
 | 
					            [FromQuery] bool refreshLibrary,
 | 
				
			||||||
            [FromQuery] string[] paths,
 | 
					            [FromQuery] string[] paths,
 | 
				
			||||||
            [FromQuery] LibraryOptions libraryOptions)
 | 
					            [FromQuery] LibraryOptions libraryOptions)
 | 
				
			||||||
@ -102,7 +100,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpDelete]
 | 
					        [HttpDelete]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public async Task<ActionResult> RemoveVirtualFolder(
 | 
					        public async Task<ActionResult> RemoveVirtualFolder(
 | 
				
			||||||
            [FromQuery] string name,
 | 
					            [FromQuery] string? name,
 | 
				
			||||||
            [FromQuery] bool refreshLibrary)
 | 
					            [FromQuery] bool refreshLibrary)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false);
 | 
					            await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false);
 | 
				
			||||||
@ -125,8 +123,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status409Conflict)]
 | 
					        [ProducesResponseType(StatusCodes.Status409Conflict)]
 | 
				
			||||||
        public ActionResult RenameVirtualFolder(
 | 
					        public ActionResult RenameVirtualFolder(
 | 
				
			||||||
            [FromQuery] string name,
 | 
					            [FromQuery] string? name,
 | 
				
			||||||
            [FromQuery] string newName,
 | 
					            [FromQuery] string? newName,
 | 
				
			||||||
            [FromQuery] bool refreshLibrary)
 | 
					            [FromQuery] bool refreshLibrary)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(name))
 | 
					            if (string.IsNullOrWhiteSpace(name))
 | 
				
			||||||
@ -207,8 +205,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("Paths")]
 | 
					        [HttpPost("Paths")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult AddMediaPath(
 | 
					        public ActionResult AddMediaPath(
 | 
				
			||||||
            [FromQuery] string name,
 | 
					            [FromQuery] string? name,
 | 
				
			||||||
            [FromQuery] string path,
 | 
					            [FromQuery] string? path,
 | 
				
			||||||
            [FromQuery] MediaPathInfo pathInfo,
 | 
					            [FromQuery] MediaPathInfo pathInfo,
 | 
				
			||||||
            [FromQuery] bool refreshLibrary)
 | 
					            [FromQuery] bool refreshLibrary)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -258,7 +256,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("Paths/Update")]
 | 
					        [HttpPost("Paths/Update")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult UpdateMediaPath(
 | 
					        public ActionResult UpdateMediaPath(
 | 
				
			||||||
            [FromQuery] string name,
 | 
					            [FromQuery] string? name,
 | 
				
			||||||
            [FromQuery] MediaPathInfo pathInfo)
 | 
					            [FromQuery] MediaPathInfo pathInfo)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(name))
 | 
					            if (string.IsNullOrWhiteSpace(name))
 | 
				
			||||||
@ -282,8 +280,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpDelete("Paths")]
 | 
					        [HttpDelete("Paths")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult RemoveMediaPath(
 | 
					        public ActionResult RemoveMediaPath(
 | 
				
			||||||
            [FromQuery] string name,
 | 
					            [FromQuery] string? name,
 | 
				
			||||||
            [FromQuery] string path,
 | 
					            [FromQuery] string? path,
 | 
				
			||||||
            [FromQuery] bool refreshLibrary)
 | 
					            [FromQuery] bool refreshLibrary)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(name))
 | 
					            if (string.IsNullOrWhiteSpace(name))
 | 
				
			||||||
@ -329,7 +327,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("LibraryOptions")]
 | 
					        [HttpPost("LibraryOptions")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult UpdateLibraryOptions(
 | 
					        public ActionResult UpdateLibraryOptions(
 | 
				
			||||||
            [FromQuery] string id,
 | 
					            [FromQuery] string? id,
 | 
				
			||||||
            [FromQuery] LibraryOptions libraryOptions)
 | 
					            [FromQuery] LibraryOptions libraryOptions)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(id);
 | 
					            var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										773
									
								
								Jellyfin.Api/Controllers/MediaInfoController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										773
									
								
								Jellyfin.Api/Controllers/MediaInfoController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,773 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Buffers;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Net.Mime;
 | 
				
			||||||
 | 
					using System.Text.Json;
 | 
				
			||||||
 | 
					using System.Threading;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Data.Entities;
 | 
				
			||||||
 | 
					using Jellyfin.Data.Enums;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Configuration;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Devices;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities.Audio;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dlna;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.MediaInfo;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Session;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The media info controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    public class MediaInfoController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IMediaSourceManager _mediaSourceManager;
 | 
				
			||||||
 | 
					        private readonly IDeviceManager _deviceManager;
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly INetworkManager _networkManager;
 | 
				
			||||||
 | 
					        private readonly IMediaEncoder _mediaEncoder;
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly IAuthorizationContext _authContext;
 | 
				
			||||||
 | 
					        private readonly ILogger _logger;
 | 
				
			||||||
 | 
					        private readonly IServerConfigurationManager _serverConfigurationManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="MediaInfoController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
 | 
				
			||||||
 | 
					        public MediaInfoController(
 | 
				
			||||||
 | 
					            IMediaSourceManager mediaSourceManager,
 | 
				
			||||||
 | 
					            IDeviceManager deviceManager,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            INetworkManager networkManager,
 | 
				
			||||||
 | 
					            IMediaEncoder mediaEncoder,
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            IAuthorizationContext authContext,
 | 
				
			||||||
 | 
					            ILogger<MediaInfoController> logger,
 | 
				
			||||||
 | 
					            IServerConfigurationManager serverConfigurationManager)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _mediaSourceManager = mediaSourceManager;
 | 
				
			||||||
 | 
					            _deviceManager = deviceManager;
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _networkManager = networkManager;
 | 
				
			||||||
 | 
					            _mediaEncoder = mediaEncoder;
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _authContext = authContext;
 | 
				
			||||||
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					            _serverConfigurationManager = serverConfigurationManager;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets live playback media info for an item.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="itemId">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Playback info returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Items/{itemId}/PlaybackInfo")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid userId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return await GetPlaybackInfoInternal(itemId, userId, null, null).ConfigureAwait(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets live playback media info for an item.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="itemId">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id.</param>
 | 
				
			||||||
 | 
					        /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
 | 
				
			||||||
 | 
					        /// <param name="startTimeTicks">The start time in ticks.</param>
 | 
				
			||||||
 | 
					        /// <param name="audioStreamIndex">The audio stream index.</param>
 | 
				
			||||||
 | 
					        /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
 | 
				
			||||||
 | 
					        /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaSourceId">The media source id.</param>
 | 
				
			||||||
 | 
					        /// <param name="liveStreamId">The livestream id.</param>
 | 
				
			||||||
 | 
					        /// <param name="deviceProfile">The device profile.</param>
 | 
				
			||||||
 | 
					        /// <param name="autoOpenLiveStream">Whether to auto open the livestream.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableTranscoding">Whether to enable transcoding. Default: true.</param>
 | 
				
			||||||
 | 
					        /// <param name="allowVideoStreamCopy">Whether to allow to copy the video stream. Default: true.</param>
 | 
				
			||||||
 | 
					        /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Playback info returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Items/{itemId}/PlaybackInfo")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
 | 
				
			||||||
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] long? maxStreamingBitrate,
 | 
				
			||||||
 | 
					            [FromQuery] long? startTimeTicks,
 | 
				
			||||||
 | 
					            [FromQuery] int? audioStreamIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? subtitleStreamIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? maxAudioChannels,
 | 
				
			||||||
 | 
					            [FromQuery] string mediaSourceId,
 | 
				
			||||||
 | 
					            [FromQuery] string liveStreamId,
 | 
				
			||||||
 | 
					            [FromQuery] DeviceProfile deviceProfile,
 | 
				
			||||||
 | 
					            [FromQuery] bool autoOpenLiveStream,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableDirectPlay = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableDirectStream = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableTranscoding = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool allowVideoStreamCopy = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool allowAudioStreamCopy = true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var authInfo = _authContext.GetAuthorizationInfo(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var profile = deviceProfile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (profile == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
 | 
				
			||||||
 | 
					                if (caps != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    profile = caps.DeviceProfile;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var info = await GetPlaybackInfoInternal(itemId, userId, mediaSourceId, liveStreamId).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (profile != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // set device specific data
 | 
				
			||||||
 | 
					                var item = _libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                foreach (var mediaSource in info.MediaSources)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    SetDeviceSpecificData(
 | 
				
			||||||
 | 
					                        item,
 | 
				
			||||||
 | 
					                        mediaSource,
 | 
				
			||||||
 | 
					                        profile,
 | 
				
			||||||
 | 
					                        authInfo,
 | 
				
			||||||
 | 
					                        maxStreamingBitrate ?? profile.MaxStreamingBitrate,
 | 
				
			||||||
 | 
					                        startTimeTicks ?? 0,
 | 
				
			||||||
 | 
					                        mediaSourceId,
 | 
				
			||||||
 | 
					                        audioStreamIndex,
 | 
				
			||||||
 | 
					                        subtitleStreamIndex,
 | 
				
			||||||
 | 
					                        maxAudioChannels,
 | 
				
			||||||
 | 
					                        info!.PlaySessionId!,
 | 
				
			||||||
 | 
					                        userId,
 | 
				
			||||||
 | 
					                        enableDirectPlay,
 | 
				
			||||||
 | 
					                        enableDirectStream,
 | 
				
			||||||
 | 
					                        enableTranscoding,
 | 
				
			||||||
 | 
					                        allowVideoStreamCopy,
 | 
				
			||||||
 | 
					                        allowAudioStreamCopy);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                SortMediaSources(info, maxStreamingBitrate);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (autoOpenLiveStream)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var mediaSource = string.IsNullOrWhiteSpace(mediaSourceId) ? info.MediaSources[0] : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.Ordinal));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var openStreamResult = await OpenMediaSource(new LiveStreamRequest
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        AudioStreamIndex = audioStreamIndex,
 | 
				
			||||||
 | 
					                        DeviceProfile = deviceProfile,
 | 
				
			||||||
 | 
					                        EnableDirectPlay = enableDirectPlay,
 | 
				
			||||||
 | 
					                        EnableDirectStream = enableDirectStream,
 | 
				
			||||||
 | 
					                        ItemId = itemId,
 | 
				
			||||||
 | 
					                        MaxAudioChannels = maxAudioChannels,
 | 
				
			||||||
 | 
					                        MaxStreamingBitrate = maxStreamingBitrate,
 | 
				
			||||||
 | 
					                        PlaySessionId = info.PlaySessionId,
 | 
				
			||||||
 | 
					                        StartTimeTicks = startTimeTicks,
 | 
				
			||||||
 | 
					                        SubtitleStreamIndex = subtitleStreamIndex,
 | 
				
			||||||
 | 
					                        UserId = userId,
 | 
				
			||||||
 | 
					                        OpenToken = mediaSource.OpenToken
 | 
				
			||||||
 | 
					                    }).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    info.MediaSources = new[] { openStreamResult.MediaSource };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (info.MediaSources != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var mediaSource in info.MediaSources)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    NormalizeMediaSourceContainer(mediaSource, profile!, DlnaProfileType.Video);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return info;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Opens a media source.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="openToken">The open token.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id.</param>
 | 
				
			||||||
 | 
					        /// <param name="playSessionId">The play session id.</param>
 | 
				
			||||||
 | 
					        /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
 | 
				
			||||||
 | 
					        /// <param name="startTimeTicks">The start time in ticks.</param>
 | 
				
			||||||
 | 
					        /// <param name="audioStreamIndex">The audio stream index.</param>
 | 
				
			||||||
 | 
					        /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
 | 
				
			||||||
 | 
					        /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="deviceProfile">The device profile.</param>
 | 
				
			||||||
 | 
					        /// <param name="directPlayProtocols">The direct play protocols. Default: <see cref="MediaProtocol.Http"/>.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Media source opened.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/LiveStreams/Open")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
 | 
				
			||||||
 | 
					            [FromQuery] string openToken,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string playSessionId,
 | 
				
			||||||
 | 
					            [FromQuery] long? maxStreamingBitrate,
 | 
				
			||||||
 | 
					            [FromQuery] long? startTimeTicks,
 | 
				
			||||||
 | 
					            [FromQuery] int? audioStreamIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? subtitleStreamIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? maxAudioChannels,
 | 
				
			||||||
 | 
					            [FromQuery] Guid itemId,
 | 
				
			||||||
 | 
					            [FromQuery] DeviceProfile deviceProfile,
 | 
				
			||||||
 | 
					            [FromQuery] MediaProtocol[] directPlayProtocols,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableDirectPlay = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableDirectStream = true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var request = new LiveStreamRequest
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                OpenToken = openToken,
 | 
				
			||||||
 | 
					                UserId = userId,
 | 
				
			||||||
 | 
					                PlaySessionId = playSessionId,
 | 
				
			||||||
 | 
					                MaxStreamingBitrate = maxStreamingBitrate,
 | 
				
			||||||
 | 
					                StartTimeTicks = startTimeTicks,
 | 
				
			||||||
 | 
					                AudioStreamIndex = audioStreamIndex,
 | 
				
			||||||
 | 
					                SubtitleStreamIndex = subtitleStreamIndex,
 | 
				
			||||||
 | 
					                MaxAudioChannels = maxAudioChannels,
 | 
				
			||||||
 | 
					                ItemId = itemId,
 | 
				
			||||||
 | 
					                DeviceProfile = deviceProfile,
 | 
				
			||||||
 | 
					                EnableDirectPlay = enableDirectPlay,
 | 
				
			||||||
 | 
					                EnableDirectStream = enableDirectStream,
 | 
				
			||||||
 | 
					                DirectPlayProtocols = directPlayProtocols ?? new[] { MediaProtocol.Http }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            return await OpenMediaSource(request).ConfigureAwait(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Closes a media source.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="liveStreamId">The livestream id.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Livestream closed.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/LiveStreams/Close")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public ActionResult CloseLiveStream([FromQuery] string liveStreamId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult();
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Tests the network with a request with the size of the bitrate.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="size">The bitrate. Defaults to 102400.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Test buffer returned.</response>
 | 
				
			||||||
 | 
					        /// <response code="400">Size has to be a numer between 0 and 10,000,000.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Playback/BitrateTest")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
				
			||||||
 | 
					        [Produces(MediaTypeNames.Application.Octet)]
 | 
				
			||||||
 | 
					        public ActionResult GetBitrateTestBytes([FromQuery] int size = 102400)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            const int MaxSize = 10_000_000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (size <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest($"The requested size ({size}) is equal to or smaller than 0.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (size > MaxSize)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return BadRequest($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                new Random().NextBytes(buffer);
 | 
				
			||||||
 | 
					                return File(buffer, MediaTypeNames.Application.Octet);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            finally
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ArrayPool<byte>.Shared.Return(buffer);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task<PlaybackInfoResponse> GetPlaybackInfoInternal(
 | 
				
			||||||
 | 
					            Guid id,
 | 
				
			||||||
 | 
					            Guid userId,
 | 
				
			||||||
 | 
					            string? mediaSourceId = null,
 | 
				
			||||||
 | 
					            string? liveStreamId = null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
					            var result = new PlaybackInfoResponse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            MediaSourceInfo[] mediaSources;
 | 
				
			||||||
 | 
					            if (string.IsNullOrWhiteSpace(liveStreamId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // TODO (moved from MediaBrowser.Api) handle supportedLiveMediaTypes?
 | 
				
			||||||
 | 
					                var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (string.IsNullOrWhiteSpace(mediaSourceId))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    mediaSources = mediaSourcesList.ToArray();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    mediaSources = mediaSourcesList
 | 
				
			||||||
 | 
					                        .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					                        .ToArray();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                mediaSources = new[] { mediaSource };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (mediaSources.Length == 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                result.MediaSources = Array.Empty<MediaSourceInfo>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                result.ErrorCode ??= PlaybackErrorCode.NoCompatibleStream;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
 | 
				
			||||||
 | 
					                // Should we move this directly into MediaSourceManager?
 | 
				
			||||||
 | 
					                result.MediaSources = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void SetDeviceSpecificData(
 | 
				
			||||||
 | 
					            BaseItem item,
 | 
				
			||||||
 | 
					            MediaSourceInfo mediaSource,
 | 
				
			||||||
 | 
					            DeviceProfile profile,
 | 
				
			||||||
 | 
					            AuthorizationInfo auth,
 | 
				
			||||||
 | 
					            long? maxBitrate,
 | 
				
			||||||
 | 
					            long startTimeTicks,
 | 
				
			||||||
 | 
					            string mediaSourceId,
 | 
				
			||||||
 | 
					            int? audioStreamIndex,
 | 
				
			||||||
 | 
					            int? subtitleStreamIndex,
 | 
				
			||||||
 | 
					            int? maxAudioChannels,
 | 
				
			||||||
 | 
					            string playSessionId,
 | 
				
			||||||
 | 
					            Guid userId,
 | 
				
			||||||
 | 
					            bool enableDirectPlay,
 | 
				
			||||||
 | 
					            bool enableDirectStream,
 | 
				
			||||||
 | 
					            bool enableTranscoding,
 | 
				
			||||||
 | 
					            bool allowVideoStreamCopy,
 | 
				
			||||||
 | 
					            bool allowAudioStreamCopy)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var streamBuilder = new StreamBuilder(_mediaEncoder, _logger);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var options = new VideoOptions
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                MediaSources = new[] { mediaSource },
 | 
				
			||||||
 | 
					                Context = EncodingContext.Streaming,
 | 
				
			||||||
 | 
					                DeviceId = auth.DeviceId,
 | 
				
			||||||
 | 
					                ItemId = item.Id,
 | 
				
			||||||
 | 
					                Profile = profile,
 | 
				
			||||||
 | 
					                MaxAudioChannels = maxAudioChannels
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                options.MediaSourceId = mediaSourceId;
 | 
				
			||||||
 | 
					                options.AudioStreamIndex = audioStreamIndex;
 | 
				
			||||||
 | 
					                options.SubtitleStreamIndex = subtitleStreamIndex;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!enableDirectPlay)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mediaSource.SupportsDirectPlay = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!enableDirectStream)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mediaSource.SupportsDirectStream = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!enableTranscoding)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                mediaSource.SupportsTranscoding = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (item is Audio)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogInformation(
 | 
				
			||||||
 | 
					                    "User policy for {0}. EnableAudioPlaybackTranscoding: {1}",
 | 
				
			||||||
 | 
					                    user.Username,
 | 
				
			||||||
 | 
					                    user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogInformation(
 | 
				
			||||||
 | 
					                    "User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}",
 | 
				
			||||||
 | 
					                    user.Username,
 | 
				
			||||||
 | 
					                    user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
 | 
				
			||||||
 | 
					                    user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding),
 | 
				
			||||||
 | 
					                    user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Beginning of Playback Determination: Attempt DirectPlay first
 | 
				
			||||||
 | 
					            if (mediaSource.SupportsDirectPlay)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    mediaSource.SupportsDirectPlay = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var supportsDirectStream = mediaSource.SupportsDirectStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Dummy this up to fool StreamBuilder
 | 
				
			||||||
 | 
					                    mediaSource.SupportsDirectStream = true;
 | 
				
			||||||
 | 
					                    options.MaxBitrate = maxBitrate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (item is Audio)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            options.ForceDirectPlay = true;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else if (item is Video)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
 | 
				
			||||||
 | 
					                            && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
 | 
				
			||||||
 | 
					                            && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            options.ForceDirectPlay = true;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // The MediaSource supports direct stream, now test to see if the client supports it
 | 
				
			||||||
 | 
					                    var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
 | 
				
			||||||
 | 
					                        ? streamBuilder.BuildAudioItem(options)
 | 
				
			||||||
 | 
					                        : streamBuilder.BuildVideoItem(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (streamInfo == null || !streamInfo.IsDirectStream)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        mediaSource.SupportsDirectPlay = false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Set this back to what it was
 | 
				
			||||||
 | 
					                    mediaSource.SupportsDirectStream = supportsDirectStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (streamInfo != null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (mediaSource.SupportsDirectStream)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    mediaSource.SupportsDirectStream = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (item is Audio)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            options.ForceDirectStream = true;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else if (item is Video)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
 | 
				
			||||||
 | 
					                            && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
 | 
				
			||||||
 | 
					                            && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            options.ForceDirectStream = true;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // The MediaSource supports direct stream, now test to see if the client supports it
 | 
				
			||||||
 | 
					                    var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
 | 
				
			||||||
 | 
					                        ? streamBuilder.BuildAudioItem(options)
 | 
				
			||||||
 | 
					                        : streamBuilder.BuildVideoItem(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (streamInfo == null || !streamInfo.IsDirectStream)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        mediaSource.SupportsDirectStream = false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (streamInfo != null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (mediaSource.SupportsTranscoding)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // The MediaSource supports direct stream, now test to see if the client supports it
 | 
				
			||||||
 | 
					                var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
 | 
				
			||||||
 | 
					                    ? streamBuilder.BuildAudioItem(options)
 | 
				
			||||||
 | 
					                    : streamBuilder.BuildVideoItem(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (streamInfo != null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        streamInfo.PlaySessionId = playSessionId;
 | 
				
			||||||
 | 
					                        streamInfo.StartPositionTicks = startTimeTicks;
 | 
				
			||||||
 | 
					                        mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
 | 
				
			||||||
 | 
					                        mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
 | 
				
			||||||
 | 
					                        mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
 | 
				
			||||||
 | 
					                        mediaSource.TranscodingContainer = streamInfo.Container;
 | 
				
			||||||
 | 
					                        mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Do this after the above so that StartPositionTicks is set
 | 
				
			||||||
 | 
					                        SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (streamInfo != null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        streamInfo.PlaySessionId = playSessionId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (streamInfo.PlayMethod == PlayMethod.Transcode)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            streamInfo.StartPositionTicks = startTimeTicks;
 | 
				
			||||||
 | 
					                            mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (!allowVideoStreamCopy)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (!allowAudioStreamCopy)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            mediaSource.TranscodingContainer = streamInfo.Container;
 | 
				
			||||||
 | 
					                            mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (!allowAudioStreamCopy)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        mediaSource.TranscodingContainer = streamInfo.Container;
 | 
				
			||||||
 | 
					                        mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Do this after the above so that StartPositionTicks is set
 | 
				
			||||||
 | 
					                        SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var attachment in mediaSource.MediaAttachments)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                attachment.DeliveryUrl = string.Format(
 | 
				
			||||||
 | 
					                    CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                    "/Videos/{0}/{1}/Attachments/{2}",
 | 
				
			||||||
 | 
					                    item.Id,
 | 
				
			||||||
 | 
					                    mediaSource.Id,
 | 
				
			||||||
 | 
					                    attachment.Index);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task<LiveStreamResponse> OpenMediaSource(LiveStreamRequest request)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var authInfo = _authContext.GetAuthorizationInfo(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var profile = request.DeviceProfile;
 | 
				
			||||||
 | 
					            if (profile == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
 | 
				
			||||||
 | 
					                if (caps != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    profile = caps.DeviceProfile;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (profile != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var item = _libraryManager.GetItemById(request.ItemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                SetDeviceSpecificData(
 | 
				
			||||||
 | 
					                    item,
 | 
				
			||||||
 | 
					                    result.MediaSource,
 | 
				
			||||||
 | 
					                    profile,
 | 
				
			||||||
 | 
					                    authInfo,
 | 
				
			||||||
 | 
					                    request.MaxStreamingBitrate,
 | 
				
			||||||
 | 
					                    request.StartTimeTicks ?? 0,
 | 
				
			||||||
 | 
					                    result.MediaSource.Id,
 | 
				
			||||||
 | 
					                    request.AudioStreamIndex,
 | 
				
			||||||
 | 
					                    request.SubtitleStreamIndex,
 | 
				
			||||||
 | 
					                    request.MaxAudioChannels,
 | 
				
			||||||
 | 
					                    request.PlaySessionId,
 | 
				
			||||||
 | 
					                    request.UserId,
 | 
				
			||||||
 | 
					                    request.EnableDirectPlay,
 | 
				
			||||||
 | 
					                    request.EnableDirectStream,
 | 
				
			||||||
 | 
					                    true,
 | 
				
			||||||
 | 
					                    true,
 | 
				
			||||||
 | 
					                    true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // here was a check if (result.MediaSource != null) but Rider said it will never be null
 | 
				
			||||||
 | 
					            NormalizeMediaSourceContainer(result.MediaSource, profile!, DlnaProfileType.Video);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var profiles = info.GetSubtitleProfiles(_mediaEncoder, false, "-", accessToken);
 | 
				
			||||||
 | 
					            mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mediaSource.TranscodeReasons = info.TranscodeReasons;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var profile in profiles)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var stream in mediaSource.MediaStreams)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        stream.DeliveryMethod = profile.DeliveryMethod;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            stream.DeliveryUrl = profile.Url.TrimStart('-');
 | 
				
			||||||
 | 
					                            stream.IsExternalUrl = profile.IsExternalUrl;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private long? GetMaxBitrate(long? clientMaxBitrate, User user)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var maxBitrate = clientMaxBitrate;
 | 
				
			||||||
 | 
					            var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (remoteClientMaxBitrate <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                remoteClientMaxBitrate = _serverConfigurationManager.Configuration.RemoteClientBitrateLimit;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (remoteClientMaxBitrate > 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.HttpContext.Connection.RemoteIpAddress.ToString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogInformation("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.HttpContext.Connection.RemoteIpAddress.ToString(), isInLocalNetwork);
 | 
				
			||||||
 | 
					                if (!isInLocalNetwork)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return maxBitrate;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var originalList = result.MediaSources.ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result.MediaSources = result.MediaSources.OrderBy(i =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // Nothing beats direct playing a file
 | 
				
			||||||
 | 
					                    if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return 0;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return 1;
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .ThenBy(i =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // Let's assume direct streaming a file is just as desirable as direct playing a remote url
 | 
				
			||||||
 | 
					                    if (i.SupportsDirectPlay || i.SupportsDirectStream)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return 0;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return 1;
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .ThenBy(i =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return i.Protocol switch
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        MediaProtocol.File => 0,
 | 
				
			||||||
 | 
					                        _ => 1,
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .ThenBy(i =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (maxBitrate.HasValue && i.Bitrate.HasValue)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return 1;
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .ThenBy(originalList.IndexOf)
 | 
				
			||||||
 | 
					                .ToArray();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										340
									
								
								Jellyfin.Api/Controllers/MoviesController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										340
									
								
								Jellyfin.Api/Controllers/MoviesController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,340 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Data.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Configuration;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.LiveTv;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Movies controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    public class MoviesController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					        private readonly IServerConfigurationManager _serverConfigurationManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="MoviesController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
 | 
				
			||||||
 | 
					        public MoviesController(
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService,
 | 
				
			||||||
 | 
					            IServerConfigurationManager serverConfigurationManager)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					            _serverConfigurationManager = serverConfigurationManager;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets movie recommendations.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">(Unused) Optional. include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">(Unused) Optional. include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">(Unused) Optional. the max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">(Unused) Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. The fields to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="categoryLimit">The max number of categories to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemLimit">The max number of items to return per category.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Movie recommendations returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>The list of movie recommendations.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("Recommendations")]
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImages", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableUserData", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageTypeLimit", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImageTypes", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string parentId,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] int categoryLimit = 5,
 | 
				
			||||||
 | 
					            [FromQuery] int itemLimit = 8)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var categories = new List<RecommendationDto>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var query = new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IncludeItemTypes = new[]
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    nameof(Movie),
 | 
				
			||||||
 | 
					                    // typeof(Trailer).Name,
 | 
				
			||||||
 | 
					                    // typeof(LiveTvProgram).Name
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                // IsMovie = true
 | 
				
			||||||
 | 
					                OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
 | 
				
			||||||
 | 
					                Limit = 7,
 | 
				
			||||||
 | 
					                ParentId = parentIdGuid,
 | 
				
			||||||
 | 
					                Recursive = true,
 | 
				
			||||||
 | 
					                IsPlayed = true,
 | 
				
			||||||
 | 
					                DtoOptions = dtoOptions
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var recentlyPlayedMovies = _libraryManager.GetItemList(query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var itemTypes = new List<string> { nameof(Movie) };
 | 
				
			||||||
 | 
					            if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                itemTypes.Add(nameof(Trailer));
 | 
				
			||||||
 | 
					                itemTypes.Add(nameof(LiveTvProgram));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IncludeItemTypes = itemTypes.ToArray(),
 | 
				
			||||||
 | 
					                IsMovie = true,
 | 
				
			||||||
 | 
					                OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
 | 
				
			||||||
 | 
					                Limit = 10,
 | 
				
			||||||
 | 
					                IsFavoriteOrLiked = true,
 | 
				
			||||||
 | 
					                ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(),
 | 
				
			||||||
 | 
					                EnableGroupByMetadataKey = true,
 | 
				
			||||||
 | 
					                ParentId = parentIdGuid,
 | 
				
			||||||
 | 
					                Recursive = true,
 | 
				
			||||||
 | 
					                DtoOptions = dtoOptions
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList();
 | 
				
			||||||
 | 
					            // Get recently played directors
 | 
				
			||||||
 | 
					            var recentDirectors = GetDirectors(mostRecentMovies)
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get recently played actors
 | 
				
			||||||
 | 
					            var recentActors = GetActors(mostRecentMovies)
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
 | 
				
			||||||
 | 
					            var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
 | 
				
			||||||
 | 
					            var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var categoryTypes = new List<IEnumerator<RecommendationDto>>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Give this extra weight
 | 
				
			||||||
 | 
					                similarToRecentlyPlayed,
 | 
				
			||||||
 | 
					                similarToRecentlyPlayed,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Give this extra weight
 | 
				
			||||||
 | 
					                similarToLiked,
 | 
				
			||||||
 | 
					                similarToLiked,
 | 
				
			||||||
 | 
					                hasDirectorFromRecentlyPlayed,
 | 
				
			||||||
 | 
					                hasActorFromRecentlyPlayed
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (categories.Count < categoryLimit)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var allEmpty = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                foreach (var category in categoryTypes)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (category.MoveNext())
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        categories.Add(category.Current);
 | 
				
			||||||
 | 
					                        allEmpty = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (categories.Count >= categoryLimit)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (allEmpty)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(categories.OrderBy(i => i.RecommendationType));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private IEnumerable<RecommendationDto> GetWithDirector(
 | 
				
			||||||
 | 
					            User user,
 | 
				
			||||||
 | 
					            IEnumerable<string> names,
 | 
				
			||||||
 | 
					            int itemLimit,
 | 
				
			||||||
 | 
					            DtoOptions dtoOptions,
 | 
				
			||||||
 | 
					            RecommendationType type)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var itemTypes = new List<string> { nameof(MediaBrowser.Controller.Entities.Movies.Movie) };
 | 
				
			||||||
 | 
					            if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                itemTypes.Add(nameof(Trailer));
 | 
				
			||||||
 | 
					                itemTypes.Add(nameof(LiveTvProgram));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var name in names)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Person = name,
 | 
				
			||||||
 | 
					                        // Account for duplicates by imdb id, since the database doesn't support this yet
 | 
				
			||||||
 | 
					                        Limit = itemLimit + 2,
 | 
				
			||||||
 | 
					                        PersonTypes = new[] { PersonType.Director },
 | 
				
			||||||
 | 
					                        IncludeItemTypes = itemTypes.ToArray(),
 | 
				
			||||||
 | 
					                        IsMovie = true,
 | 
				
			||||||
 | 
					                        EnableGroupByMetadataKey = true,
 | 
				
			||||||
 | 
					                        DtoOptions = dtoOptions
 | 
				
			||||||
 | 
					                    }).GroupBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
 | 
				
			||||||
 | 
					                    .Select(x => x.First())
 | 
				
			||||||
 | 
					                    .Take(itemLimit)
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (items.Count > 0)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    yield return new RecommendationDto
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        BaselineItemName = name,
 | 
				
			||||||
 | 
					                        CategoryId = name.GetMD5(),
 | 
				
			||||||
 | 
					                        RecommendationType = type,
 | 
				
			||||||
 | 
					                        Items = returnItems
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private IEnumerable<RecommendationDto> GetWithActor(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var itemTypes = new List<string> { nameof(Movie) };
 | 
				
			||||||
 | 
					            if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                itemTypes.Add(nameof(Trailer));
 | 
				
			||||||
 | 
					                itemTypes.Add(nameof(LiveTvProgram));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var name in names)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Person = name,
 | 
				
			||||||
 | 
					                        // Account for duplicates by imdb id, since the database doesn't support this yet
 | 
				
			||||||
 | 
					                        Limit = itemLimit + 2,
 | 
				
			||||||
 | 
					                        IncludeItemTypes = itemTypes.ToArray(),
 | 
				
			||||||
 | 
					                        IsMovie = true,
 | 
				
			||||||
 | 
					                        EnableGroupByMetadataKey = true,
 | 
				
			||||||
 | 
					                        DtoOptions = dtoOptions
 | 
				
			||||||
 | 
					                    }).GroupBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
 | 
				
			||||||
 | 
					                    .Select(x => x.First())
 | 
				
			||||||
 | 
					                    .Take(itemLimit)
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (items.Count > 0)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    yield return new RecommendationDto
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        BaselineItemName = name,
 | 
				
			||||||
 | 
					                        CategoryId = name.GetMD5(),
 | 
				
			||||||
 | 
					                        RecommendationType = type,
 | 
				
			||||||
 | 
					                        Items = returnItems
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private IEnumerable<RecommendationDto> GetSimilarTo(User user, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var itemTypes = new List<string> { nameof(Movie) };
 | 
				
			||||||
 | 
					            if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                itemTypes.Add(nameof(Trailer));
 | 
				
			||||||
 | 
					                itemTypes.Add(nameof(LiveTvProgram));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var item in baselineItems)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Limit = itemLimit,
 | 
				
			||||||
 | 
					                    IncludeItemTypes = itemTypes.ToArray(),
 | 
				
			||||||
 | 
					                    IsMovie = true,
 | 
				
			||||||
 | 
					                    SimilarTo = item,
 | 
				
			||||||
 | 
					                    EnableGroupByMetadataKey = true,
 | 
				
			||||||
 | 
					                    DtoOptions = dtoOptions
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (similar.Count > 0)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    yield return new RecommendationDto
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        BaselineItemName = item.Name,
 | 
				
			||||||
 | 
					                        CategoryId = item.Id,
 | 
				
			||||||
 | 
					                        RecommendationType = type,
 | 
				
			||||||
 | 
					                        Items = returnItems
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private IEnumerable<string> GetActors(IEnumerable<BaseItem> items)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var people = _libraryManager.GetPeople(new InternalPeopleQuery
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ExcludePersonTypes = new[] { PersonType.Director },
 | 
				
			||||||
 | 
					                MaxListOrder = 3
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var itemIds = items.Select(i => i.Id).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return people
 | 
				
			||||||
 | 
					                .Where(i => itemIds.Contains(i.ItemId))
 | 
				
			||||||
 | 
					                .Select(i => i.Name)
 | 
				
			||||||
 | 
					                .DistinctNames();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var people = _libraryManager.GetPeople(new InternalPeopleQuery
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                PersonTypes = new[] { PersonType.Director }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var itemIds = items.Select(i => i.Id).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return people
 | 
				
			||||||
 | 
					                .Where(i => itemIds.Contains(i.ItemId))
 | 
				
			||||||
 | 
					                .Select(i => i.Name)
 | 
				
			||||||
 | 
					                .DistinctNames();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -36,23 +36,11 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets a user's notifications.
 | 
					        /// Gets a user's notifications.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="userId">The user's ID.</param>
 | 
					 | 
				
			||||||
        /// <param name="isRead">An optional filter by notification read state.</param>
 | 
					 | 
				
			||||||
        /// <param name="startIndex">The optional index to start at. All notifications with a lower index will be omitted from the results.</param>
 | 
					 | 
				
			||||||
        /// <param name="limit">An optional limit on the number of notifications returned.</param>
 | 
					 | 
				
			||||||
        /// <response code="200">Notifications returned.</response>
 | 
					        /// <response code="200">Notifications returned.</response>
 | 
				
			||||||
        /// <returns>An <see cref="OkResult"/> containing a list of notifications.</returns>
 | 
					        /// <returns>An <see cref="OkResult"/> containing a list of notifications.</returns>
 | 
				
			||||||
        [HttpGet("{userId}")]
 | 
					        [HttpGet("{userId}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
 | 
					        public ActionResult<NotificationResultDto> GetNotifications()
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "isRead", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "startIndex", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "limit", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        public ActionResult<NotificationResultDto> GetNotifications(
 | 
					 | 
				
			||||||
            [FromRoute] string userId,
 | 
					 | 
				
			||||||
            [FromQuery] bool? isRead,
 | 
					 | 
				
			||||||
            [FromQuery] int? startIndex,
 | 
					 | 
				
			||||||
            [FromQuery] int? limit)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return new NotificationResultDto();
 | 
					            return new NotificationResultDto();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -60,14 +48,11 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets a user's notification summary.
 | 
					        /// Gets a user's notification summary.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="userId">The user's ID.</param>
 | 
					 | 
				
			||||||
        /// <response code="200">Summary of user's notifications returned.</response>
 | 
					        /// <response code="200">Summary of user's notifications returned.</response>
 | 
				
			||||||
        /// <returns>An <cref see="OkResult"/> containing a summary of the users notifications.</returns>
 | 
					        /// <returns>An <cref see="OkResult"/> containing a summary of the users notifications.</returns>
 | 
				
			||||||
        [HttpGet("{userId}/Summary")]
 | 
					        [HttpGet("{userId}/Summary")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
 | 
					        public ActionResult<NotificationsSummaryDto> GetNotificationsSummary()
 | 
				
			||||||
        public ActionResult<NotificationsSummaryDto> GetNotificationsSummary(
 | 
					 | 
				
			||||||
            [FromRoute] string userId)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return new NotificationsSummaryDto();
 | 
					            return new NotificationsSummaryDto();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -108,8 +93,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("Admin")]
 | 
					        [HttpPost("Admin")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult CreateAdminNotification(
 | 
					        public ActionResult CreateAdminNotification(
 | 
				
			||||||
            [FromQuery] string name,
 | 
					            [FromQuery] string? name,
 | 
				
			||||||
            [FromQuery] string description,
 | 
					            [FromQuery] string? description,
 | 
				
			||||||
            [FromQuery] string? url,
 | 
					            [FromQuery] string? url,
 | 
				
			||||||
            [FromQuery] NotificationLevel? level)
 | 
					            [FromQuery] NotificationLevel? level)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -134,17 +119,11 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Sets notifications as read.
 | 
					        /// Sets notifications as read.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="userId">The userID.</param>
 | 
					 | 
				
			||||||
        /// <param name="ids">A comma-separated list of the IDs of notifications which should be set as read.</param>
 | 
					 | 
				
			||||||
        /// <response code="204">Notifications set as read.</response>
 | 
					        /// <response code="204">Notifications set as read.</response>
 | 
				
			||||||
        /// <returns>A <cref see="NoContentResult"/>.</returns>
 | 
					        /// <returns>A <cref see="NoContentResult"/>.</returns>
 | 
				
			||||||
        [HttpPost("{userId}/Read")]
 | 
					        [HttpPost("{userId}/Read")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
 | 
					        public ActionResult SetRead()
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "ids", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        public ActionResult SetRead(
 | 
					 | 
				
			||||||
            [FromRoute] string userId,
 | 
					 | 
				
			||||||
            [FromQuery] string ids)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NoContent();
 | 
					            return NoContent();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -152,17 +131,11 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Sets notifications as unread.
 | 
					        /// Sets notifications as unread.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="userId">The userID.</param>
 | 
					 | 
				
			||||||
        /// <param name="ids">A comma-separated list of the IDs of notifications which should be set as unread.</param>
 | 
					 | 
				
			||||||
        /// <response code="204">Notifications set as unread.</response>
 | 
					        /// <response code="204">Notifications set as unread.</response>
 | 
				
			||||||
        /// <returns>A <cref see="NoContentResult"/>.</returns>
 | 
					        /// <returns>A <cref see="NoContentResult"/>.</returns>
 | 
				
			||||||
        [HttpPost("{userId}/Unread")]
 | 
					        [HttpPost("{userId}/Unread")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
 | 
					        public ActionResult SetUnread()
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "ids", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        public ActionResult SetUnread(
 | 
					 | 
				
			||||||
            [FromRoute] string userId,
 | 
					 | 
				
			||||||
            [FromQuery] string ids)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NoContent();
 | 
					            return NoContent();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -40,7 +40,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpGet("/{name}")]
 | 
					        [HttpGet("/{name}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public async Task<ActionResult<PackageInfo>> GetPackageInfo(
 | 
					        public async Task<ActionResult<PackageInfo>> GetPackageInfo(
 | 
				
			||||||
            [FromRoute] [Required] string name,
 | 
					            [FromRoute] [Required] string? name,
 | 
				
			||||||
            [FromQuery] string? assemblyGuid)
 | 
					            [FromQuery] string? assemblyGuid)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
 | 
					            var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
 | 
				
			||||||
@ -80,9 +80,9 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        [Authorize(Policy = Policies.RequiresElevation)]
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
        public async Task<ActionResult> InstallPackage(
 | 
					        public async Task<ActionResult> InstallPackage(
 | 
				
			||||||
            [FromRoute] [Required] string name,
 | 
					            [FromRoute] [Required] string? name,
 | 
				
			||||||
            [FromQuery] string assemblyGuid,
 | 
					            [FromQuery] string? assemblyGuid,
 | 
				
			||||||
            [FromQuery] string version)
 | 
					            [FromQuery] string? version)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
 | 
					            var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
 | 
				
			||||||
            var package = _installationManager.GetCompatibleVersions(
 | 
					            var package = _installationManager.GetCompatibleVersions(
 | 
				
			||||||
 | 
				
			|||||||
@ -84,8 +84,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("{playlistId}/Items")]
 | 
					        [HttpPost("{playlistId}/Items")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult AddToPlaylist(
 | 
					        public ActionResult AddToPlaylist(
 | 
				
			||||||
            [FromRoute] string playlistId,
 | 
					            [FromRoute] string? playlistId,
 | 
				
			||||||
            [FromQuery] string ids,
 | 
					            [FromQuery] string? ids,
 | 
				
			||||||
            [FromQuery] Guid userId)
 | 
					            [FromQuery] Guid userId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _playlistManager.AddToPlaylist(playlistId, RequestHelpers.GetGuids(ids), userId);
 | 
					            _playlistManager.AddToPlaylist(playlistId, RequestHelpers.GetGuids(ids), userId);
 | 
				
			||||||
@ -103,8 +103,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
 | 
					        [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult MoveItem(
 | 
					        public ActionResult MoveItem(
 | 
				
			||||||
            [FromRoute] string playlistId,
 | 
					            [FromRoute] string? playlistId,
 | 
				
			||||||
            [FromRoute] string itemId,
 | 
					            [FromRoute] string? itemId,
 | 
				
			||||||
            [FromRoute] int newIndex)
 | 
					            [FromRoute] int newIndex)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _playlistManager.MoveItem(playlistId, itemId, newIndex);
 | 
					            _playlistManager.MoveItem(playlistId, itemId, newIndex);
 | 
				
			||||||
@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 | 
					        /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 | 
				
			||||||
        [HttpDelete("{playlistId}/Items")]
 | 
					        [HttpDelete("{playlistId}/Items")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult RemoveFromPlaylist([FromRoute] string playlistId, [FromQuery] string entryIds)
 | 
					        public ActionResult RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _playlistManager.RemoveFromPlaylist(playlistId, RequestHelpers.Split(entryIds, ',', true));
 | 
					            _playlistManager.RemoveFromPlaylist(playlistId, RequestHelpers.Split(entryIds, ',', true));
 | 
				
			||||||
            return NoContent();
 | 
					            return NoContent();
 | 
				
			||||||
@ -147,11 +147,11 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
            [FromRoute] Guid userId,
 | 
					            [FromRoute] Guid userId,
 | 
				
			||||||
            [FromRoute] int? startIndex,
 | 
					            [FromRoute] int? startIndex,
 | 
				
			||||||
            [FromRoute] int? limit,
 | 
					            [FromRoute] int? limit,
 | 
				
			||||||
            [FromRoute] string fields,
 | 
					            [FromRoute] string? fields,
 | 
				
			||||||
            [FromRoute] bool? enableImages,
 | 
					            [FromRoute] bool? enableImages,
 | 
				
			||||||
            [FromRoute] bool? enableUserData,
 | 
					            [FromRoute] bool? enableUserData,
 | 
				
			||||||
            [FromRoute] int? imageTypeLimit,
 | 
					            [FromRoute] int? imageTypeLimit,
 | 
				
			||||||
            [FromRoute] string enableImageTypes)
 | 
					            [FromRoute] string? enableImageTypes)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
 | 
					            var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
 | 
				
			||||||
            if (playlist == null)
 | 
					            if (playlist == null)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										372
									
								
								Jellyfin.Api/Controllers/PlaystateController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								Jellyfin.Api/Controllers/PlaystateController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,372 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using Jellyfin.Data.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Session;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Session;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Playstate controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    public class PlaystateController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly IUserDataManager _userDataRepository;
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly ISessionManager _sessionManager;
 | 
				
			||||||
 | 
					        private readonly IAuthorizationContext _authContext;
 | 
				
			||||||
 | 
					        private readonly ILogger<PlaystateController> _logger;
 | 
				
			||||||
 | 
					        private readonly TranscodingJobHelper _transcodingJobHelper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="PlaystateController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 | 
				
			||||||
 | 
					        public PlaystateController(
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            IUserDataManager userDataRepository,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            ISessionManager sessionManager,
 | 
				
			||||||
 | 
					            IAuthorizationContext authContext,
 | 
				
			||||||
 | 
					            ILoggerFactory loggerFactory,
 | 
				
			||||||
 | 
					            IMediaSourceManager mediaSourceManager,
 | 
				
			||||||
 | 
					            IFileSystem fileSystem)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _userDataRepository = userDataRepository;
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _sessionManager = sessionManager;
 | 
				
			||||||
 | 
					            _authContext = authContext;
 | 
				
			||||||
 | 
					            _logger = loggerFactory.CreateLogger<PlaystateController>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _transcodingJobHelper = new TranscodingJobHelper(
 | 
				
			||||||
 | 
					                loggerFactory.CreateLogger<TranscodingJobHelper>(),
 | 
				
			||||||
 | 
					                mediaSourceManager,
 | 
				
			||||||
 | 
					                fileSystem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Marks an item as played for user.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="datePlayed">Optional. The date the item was played.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Item marked as played.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Users/{userId}/PlayedItems/{itemId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<UserItemDataDto> MarkPlayedItem(
 | 
				
			||||||
 | 
					            [FromRoute] Guid userId,
 | 
				
			||||||
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
 | 
					            [FromQuery] DateTime? datePlayed)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
 | 
				
			||||||
 | 
					            var dto = UpdatePlayedStatus(user, itemId, true, datePlayed);
 | 
				
			||||||
 | 
					            foreach (var additionalUserInfo in session.AdditionalUsers)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
 | 
				
			||||||
 | 
					                UpdatePlayedStatus(additionalUser, itemId, true, datePlayed);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return dto;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Marks an item as unplayed for user.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Item marked as unplayed.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpDelete("/Users/{userId}/PlayedItem/{itemId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
 | 
				
			||||||
 | 
					            var dto = UpdatePlayedStatus(user, itemId, false, null);
 | 
				
			||||||
 | 
					            foreach (var additionalUserInfo in session.AdditionalUsers)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
 | 
				
			||||||
 | 
					                UpdatePlayedStatus(additionalUser, itemId, false, null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return dto;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Reports playback has started within a session.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="playbackStartInfo">The playback start info.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Playback start recorded.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Sessions/Playing")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult> ReportPlaybackStart([FromBody] PlaybackStartInfo playbackStartInfo)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId);
 | 
				
			||||||
 | 
					            playbackStartInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
 | 
				
			||||||
 | 
					            await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Reports playback progress within a session.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="playbackProgressInfo">The playback progress info.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Playback progress recorded.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Sessions/Playing/Progress")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult> ReportPlaybackProgress([FromBody] PlaybackProgressInfo playbackProgressInfo)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId);
 | 
				
			||||||
 | 
					            playbackProgressInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
 | 
				
			||||||
 | 
					            await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Pings a playback session.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="playSessionId">Playback session id.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Playback session pinged.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Sessions/Playing/Ping")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public ActionResult PingPlaybackSession([FromQuery] string playSessionId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _transcodingJobHelper.PingTranscodingJob(playSessionId, null);
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Reports playback has stopped within a session.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="playbackStopInfo">The playback stop info.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Playback stop recorded.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Sessions/Playing/Stopped")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult> ReportPlaybackStopped([FromBody] PlaybackStopInfo playbackStopInfo)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
 | 
				
			||||||
 | 
					            if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playbackStopInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
 | 
				
			||||||
 | 
					            await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Reports that a user has begun playing an item.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaSourceId">The id of the MediaSource.</param>
 | 
				
			||||||
 | 
					        /// <param name="canSeek">Indicates if the client can seek.</param>
 | 
				
			||||||
 | 
					        /// <param name="audioStreamIndex">The audio stream index.</param>
 | 
				
			||||||
 | 
					        /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
 | 
				
			||||||
 | 
					        /// <param name="playMethod">The play method.</param>
 | 
				
			||||||
 | 
					        /// <param name="liveStreamId">The live stream id.</param>
 | 
				
			||||||
 | 
					        /// <param name="playSessionId">The play session id.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Play start recorded.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Users/{userId}/PlayingItems/{itemId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
 | 
				
			||||||
 | 
					        public async Task<ActionResult> OnPlaybackStart(
 | 
				
			||||||
 | 
					            [FromRoute] Guid userId,
 | 
				
			||||||
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
 | 
					            [FromQuery] string mediaSourceId,
 | 
				
			||||||
 | 
					            [FromQuery] bool canSeek,
 | 
				
			||||||
 | 
					            [FromQuery] int? audioStreamIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? subtitleStreamIndex,
 | 
				
			||||||
 | 
					            [FromQuery] PlayMethod playMethod,
 | 
				
			||||||
 | 
					            [FromQuery] string liveStreamId,
 | 
				
			||||||
 | 
					            [FromQuery] string playSessionId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var playbackStartInfo = new PlaybackStartInfo
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                CanSeek = canSeek,
 | 
				
			||||||
 | 
					                ItemId = itemId,
 | 
				
			||||||
 | 
					                MediaSourceId = mediaSourceId,
 | 
				
			||||||
 | 
					                AudioStreamIndex = audioStreamIndex,
 | 
				
			||||||
 | 
					                SubtitleStreamIndex = subtitleStreamIndex,
 | 
				
			||||||
 | 
					                PlayMethod = playMethod,
 | 
				
			||||||
 | 
					                PlaySessionId = playSessionId,
 | 
				
			||||||
 | 
					                LiveStreamId = liveStreamId
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId);
 | 
				
			||||||
 | 
					            playbackStartInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
 | 
				
			||||||
 | 
					            await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Reports a user's playback progress.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaSourceId">The id of the MediaSource.</param>
 | 
				
			||||||
 | 
					        /// <param name="positionTicks">Optional. The current position, in ticks. 1 tick = 10000 ms.</param>
 | 
				
			||||||
 | 
					        /// <param name="isPaused">Indicates if the player is paused.</param>
 | 
				
			||||||
 | 
					        /// <param name="isMuted">Indicates if the player is muted.</param>
 | 
				
			||||||
 | 
					        /// <param name="audioStreamIndex">The audio stream index.</param>
 | 
				
			||||||
 | 
					        /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
 | 
				
			||||||
 | 
					        /// <param name="volumeLevel">Scale of 0-100.</param>
 | 
				
			||||||
 | 
					        /// <param name="playMethod">The play method.</param>
 | 
				
			||||||
 | 
					        /// <param name="liveStreamId">The live stream id.</param>
 | 
				
			||||||
 | 
					        /// <param name="playSessionId">The play session id.</param>
 | 
				
			||||||
 | 
					        /// <param name="repeatMode">The repeat mode.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Play progress recorded.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Users/{userId}/PlayingItems/{itemId}/Progress")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
 | 
				
			||||||
 | 
					        public async Task<ActionResult> OnPlaybackProgress(
 | 
				
			||||||
 | 
					            [FromRoute] Guid userId,
 | 
				
			||||||
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
 | 
					            [FromQuery] string mediaSourceId,
 | 
				
			||||||
 | 
					            [FromQuery] long? positionTicks,
 | 
				
			||||||
 | 
					            [FromQuery] bool isPaused,
 | 
				
			||||||
 | 
					            [FromQuery] bool isMuted,
 | 
				
			||||||
 | 
					            [FromQuery] int? audioStreamIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? subtitleStreamIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? volumeLevel,
 | 
				
			||||||
 | 
					            [FromQuery] PlayMethod playMethod,
 | 
				
			||||||
 | 
					            [FromQuery] string liveStreamId,
 | 
				
			||||||
 | 
					            [FromQuery] string playSessionId,
 | 
				
			||||||
 | 
					            [FromQuery] RepeatMode repeatMode)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var playbackProgressInfo = new PlaybackProgressInfo
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemId = itemId,
 | 
				
			||||||
 | 
					                PositionTicks = positionTicks,
 | 
				
			||||||
 | 
					                IsMuted = isMuted,
 | 
				
			||||||
 | 
					                IsPaused = isPaused,
 | 
				
			||||||
 | 
					                MediaSourceId = mediaSourceId,
 | 
				
			||||||
 | 
					                AudioStreamIndex = audioStreamIndex,
 | 
				
			||||||
 | 
					                SubtitleStreamIndex = subtitleStreamIndex,
 | 
				
			||||||
 | 
					                VolumeLevel = volumeLevel,
 | 
				
			||||||
 | 
					                PlayMethod = playMethod,
 | 
				
			||||||
 | 
					                PlaySessionId = playSessionId,
 | 
				
			||||||
 | 
					                LiveStreamId = liveStreamId,
 | 
				
			||||||
 | 
					                RepeatMode = repeatMode
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId);
 | 
				
			||||||
 | 
					            playbackProgressInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
 | 
				
			||||||
 | 
					            await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Reports that a user has stopped playing an item.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaSourceId">The id of the MediaSource.</param>
 | 
				
			||||||
 | 
					        /// <param name="nextMediaType">The next media type that will play.</param>
 | 
				
			||||||
 | 
					        /// <param name="positionTicks">Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms.</param>
 | 
				
			||||||
 | 
					        /// <param name="liveStreamId">The live stream id.</param>
 | 
				
			||||||
 | 
					        /// <param name="playSessionId">The play session id.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Playback stop recorded.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpDelete("/Users/{userId}/PlayingItems/{itemId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
 | 
				
			||||||
 | 
					        public async Task<ActionResult> OnPlaybackStopped(
 | 
				
			||||||
 | 
					            [FromRoute] Guid userId,
 | 
				
			||||||
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
 | 
					            [FromQuery] string mediaSourceId,
 | 
				
			||||||
 | 
					            [FromQuery] string nextMediaType,
 | 
				
			||||||
 | 
					            [FromQuery] long? positionTicks,
 | 
				
			||||||
 | 
					            [FromQuery] string liveStreamId,
 | 
				
			||||||
 | 
					            [FromQuery] string playSessionId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var playbackStopInfo = new PlaybackStopInfo
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ItemId = itemId,
 | 
				
			||||||
 | 
					                PositionTicks = positionTicks,
 | 
				
			||||||
 | 
					                MediaSourceId = mediaSourceId,
 | 
				
			||||||
 | 
					                PlaySessionId = playSessionId,
 | 
				
			||||||
 | 
					                LiveStreamId = liveStreamId,
 | 
				
			||||||
 | 
					                NextMediaType = nextMediaType
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
 | 
				
			||||||
 | 
					            if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playbackStopInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
 | 
				
			||||||
 | 
					            await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Updates the played status.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="user">The user.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">The item id.</param>
 | 
				
			||||||
 | 
					        /// <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, Guid itemId, bool wasPlayed, DateTime? datePlayed)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (wasPlayed)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                item.MarkPlayed(user, datePlayed, true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                item.MarkUnplayed(user);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return _userDataRepository.GetUserDataDto(item, user);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (method == PlayMethod.Transcode)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var job = string.IsNullOrWhiteSpace(playSessionId) ? null : _transcodingJobHelper.GetTranscodingJob(playSessionId);
 | 
				
			||||||
 | 
					                if (job == null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return PlayMethod.DirectPlay;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return method;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -42,13 +42,11 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets a list of currently installed plugins.
 | 
					        /// Gets a list of currently installed plugins.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="isAppStoreEnabled">Optional. Unused.</param>
 | 
					 | 
				
			||||||
        /// <response code="200">Installed plugins returned.</response>
 | 
					        /// <response code="200">Installed plugins returned.</response>
 | 
				
			||||||
        /// <returns>List of currently installed plugins.</returns>
 | 
					        /// <returns>List of currently installed plugins.</returns>
 | 
				
			||||||
        [HttpGet]
 | 
					        [HttpGet]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "isAppStoreEnabled", Justification = "Imported from ServiceStack")]
 | 
					        public ActionResult<IEnumerable<PluginInfo>> GetPlugins()
 | 
				
			||||||
        public ActionResult<IEnumerable<PluginInfo>> GetPlugins([FromRoute] bool? isAppStoreEnabled)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return Ok(_appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()));
 | 
					            return Ok(_appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -168,7 +166,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [Obsolete("This endpoint should not be used.")]
 | 
					        [Obsolete("This endpoint should not be used.")]
 | 
				
			||||||
        [HttpPost("RegistrationRecords/{name}")]
 | 
					        [HttpPost("RegistrationRecords/{name}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute] string name)
 | 
					        public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute] string? name)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return new MBRegistrationRecord
 | 
					            return new MBRegistrationRecord
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -190,7 +188,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [Obsolete("Paid plugins are not supported")]
 | 
					        [Obsolete("Paid plugins are not supported")]
 | 
				
			||||||
        [HttpGet("/Registrations/{name}")]
 | 
					        [HttpGet("/Registrations/{name}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status501NotImplemented)]
 | 
					        [ProducesResponseType(StatusCodes.Status501NotImplemented)]
 | 
				
			||||||
        public ActionResult GetRegistration([FromRoute] string name)
 | 
					        public ActionResult GetRegistration([FromRoute] string? name)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins,
 | 
					            // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins,
 | 
				
			||||||
            // delete all these registration endpoints. They are only kept for compatibility.
 | 
					            // delete all these registration endpoints. They are only kept for compatibility.
 | 
				
			||||||
 | 
				
			|||||||
@ -208,7 +208,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        public async Task<ActionResult> DownloadRemoteImage(
 | 
					        public async Task<ActionResult> DownloadRemoteImage(
 | 
				
			||||||
            [FromRoute] Guid itemId,
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
            [FromQuery, BindRequired] ImageType type,
 | 
					            [FromQuery, BindRequired] ImageType type,
 | 
				
			||||||
            [FromQuery] string imageUrl)
 | 
					            [FromQuery] string? imageUrl)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = _libraryManager.GetItemById(itemId);
 | 
					            var item = _libraryManager.GetItemById(itemId);
 | 
				
			||||||
            if (item == null)
 | 
					            if (item == null)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										161
									
								
								Jellyfin.Api/Controllers/ScheduledTasksController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								Jellyfin.Api/Controllers/ScheduledTasksController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,161 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Tasks;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc.ModelBinding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Scheduled Tasks Controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
 | 
					    public class ScheduledTasksController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly ITaskManager _taskManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="ScheduledTasksController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="taskManager">Instance of the <see cref="ITaskManager"/> interface.</param>
 | 
				
			||||||
 | 
					        public ScheduledTasksController(ITaskManager taskManager)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _taskManager = taskManager;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get tasks.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="isHidden">Optional filter tasks that are hidden, or not.</param>
 | 
				
			||||||
 | 
					        /// <param name="isEnabled">Optional filter tasks that are enabled, or not.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Scheduled tasks retrieved.</response>
 | 
				
			||||||
 | 
					        /// <returns>The list of scheduled tasks.</returns>
 | 
				
			||||||
 | 
					        [HttpGet]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public IEnumerable<IScheduledTaskWorker> GetTasks(
 | 
				
			||||||
 | 
					            [FromQuery] bool? isHidden,
 | 
				
			||||||
 | 
					            [FromQuery] bool? isEnabled)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            IEnumerable<IScheduledTaskWorker> tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var task in tasks)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (task.ScheduledTask is IConfigurableScheduledTask scheduledTask)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (isHidden.HasValue && isHidden.Value != scheduledTask.IsHidden)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        continue;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (isEnabled.HasValue && isEnabled.Value != scheduledTask.IsEnabled)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        continue;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                yield return task;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get task by id.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="taskId">Task Id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Task retrieved.</response>
 | 
				
			||||||
 | 
					        /// <response code="404">Task not found.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the task on success, or a <see cref="NotFoundResult"/> if the task could not be found.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("{taskId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
 | 
					        public ActionResult<TaskInfo> GetTask([FromRoute] string? taskId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var task = _taskManager.ScheduledTasks.FirstOrDefault(i =>
 | 
				
			||||||
 | 
					                string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (task == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return NotFound();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return ScheduledTaskHelpers.GetTaskInfo(task);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Start specified task.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="taskId">Task Id.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Task started.</response>
 | 
				
			||||||
 | 
					        /// <response code="404">Task not found.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("Running/{taskId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
 | 
					        public ActionResult StartTask([FromRoute] string? taskId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
 | 
				
			||||||
 | 
					                o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (task == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return NotFound();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _taskManager.Execute(task, new TaskOptions());
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Stop specified task.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="taskId">Task Id.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Task stopped.</response>
 | 
				
			||||||
 | 
					        /// <response code="404">Task not found.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns>
 | 
				
			||||||
 | 
					        [HttpDelete("Running/{taskId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
 | 
					        public ActionResult StopTask([FromRoute] string? taskId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
 | 
				
			||||||
 | 
					                o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (task == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return NotFound();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _taskManager.Cancel(task);
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Update specified task triggers.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="taskId">Task Id.</param>
 | 
				
			||||||
 | 
					        /// <param name="triggerInfos">Triggers.</param>
 | 
				
			||||||
 | 
					        /// <response code="204">Task triggers updated.</response>
 | 
				
			||||||
 | 
					        /// <response code="404">Task not found.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if the file could not be found.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("{taskId}/Triggers")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
 | 
					        public ActionResult UpdateTask(
 | 
				
			||||||
 | 
					            [FromRoute] string? taskId,
 | 
				
			||||||
 | 
					            [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
 | 
				
			||||||
 | 
					                o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
 | 
					            if (task == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return NotFound();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            task.Triggers = triggerInfos;
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -81,11 +81,11 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
            [FromQuery] int? startIndex,
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
            [FromQuery] int? limit,
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
            [FromQuery] Guid userId,
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
            [FromQuery, Required] string searchTerm,
 | 
					            [FromQuery, Required] string? searchTerm,
 | 
				
			||||||
            [FromQuery] string includeItemTypes,
 | 
					            [FromQuery] string? includeItemTypes,
 | 
				
			||||||
            [FromQuery] string excludeItemTypes,
 | 
					            [FromQuery] string? excludeItemTypes,
 | 
				
			||||||
            [FromQuery] string mediaTypes,
 | 
					            [FromQuery] string? mediaTypes,
 | 
				
			||||||
            [FromQuery] string parentId,
 | 
					            [FromQuery] string? parentId,
 | 
				
			||||||
            [FromQuery] bool? isMovie,
 | 
					            [FromQuery] bool? isMovie,
 | 
				
			||||||
            [FromQuery] bool? isSeries,
 | 
					            [FromQuery] bool? isSeries,
 | 
				
			||||||
            [FromQuery] bool? isNews,
 | 
					            [FromQuery] bool? isNews,
 | 
				
			||||||
 | 
				
			|||||||
@ -62,7 +62,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public ActionResult<IEnumerable<SessionInfo>> GetSessions(
 | 
					        public ActionResult<IEnumerable<SessionInfo>> GetSessions(
 | 
				
			||||||
            [FromQuery] Guid controllableByUserId,
 | 
					            [FromQuery] Guid controllableByUserId,
 | 
				
			||||||
            [FromQuery] string deviceId,
 | 
					            [FromQuery] string? deviceId,
 | 
				
			||||||
            [FromQuery] int? activeWithinSeconds)
 | 
					            [FromQuery] int? activeWithinSeconds)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var result = _sessionManager.Sessions;
 | 
					            var result = _sessionManager.Sessions;
 | 
				
			||||||
@ -123,10 +123,10 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/{sessionId}/Viewing")]
 | 
					        [HttpPost("/Sessions/{sessionId}/Viewing")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult DisplayContent(
 | 
					        public ActionResult DisplayContent(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromQuery] string itemType,
 | 
					            [FromQuery] string? itemType,
 | 
				
			||||||
            [FromQuery] string itemId,
 | 
					            [FromQuery] string? itemId,
 | 
				
			||||||
            [FromQuery] string itemName)
 | 
					            [FromQuery] string? itemName)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var command = new BrowseRequest
 | 
					            var command = new BrowseRequest
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -157,7 +157,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/{sessionId}/Playing")]
 | 
					        [HttpPost("/Sessions/{sessionId}/Playing")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult Play(
 | 
					        public ActionResult Play(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromQuery] Guid[] itemIds,
 | 
					            [FromQuery] Guid[] itemIds,
 | 
				
			||||||
            [FromQuery] long? startPositionTicks,
 | 
					            [FromQuery] long? startPositionTicks,
 | 
				
			||||||
            [FromQuery] PlayCommand playCommand,
 | 
					            [FromQuery] PlayCommand playCommand,
 | 
				
			||||||
@ -191,7 +191,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/{sessionId}/Playing/{command}")]
 | 
					        [HttpPost("/Sessions/{sessionId}/Playing/{command}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult SendPlaystateCommand(
 | 
					        public ActionResult SendPlaystateCommand(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromBody] PlaystateRequest playstateRequest)
 | 
					            [FromBody] PlaystateRequest playstateRequest)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _sessionManager.SendPlaystateCommand(
 | 
					            _sessionManager.SendPlaystateCommand(
 | 
				
			||||||
@ -213,8 +213,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/{sessionId}/System/{command}")]
 | 
					        [HttpPost("/Sessions/{sessionId}/System/{command}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult SendSystemCommand(
 | 
					        public ActionResult SendSystemCommand(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromRoute] string command)
 | 
					            [FromRoute] string? command)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var name = command;
 | 
					            var name = command;
 | 
				
			||||||
            if (Enum.TryParse(name, true, out GeneralCommandType commandType))
 | 
					            if (Enum.TryParse(name, true, out GeneralCommandType commandType))
 | 
				
			||||||
@ -244,8 +244,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/{sessionId}/Command/{Command}")]
 | 
					        [HttpPost("/Sessions/{sessionId}/Command/{Command}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult SendGeneralCommand(
 | 
					        public ActionResult SendGeneralCommand(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromRoute] string command)
 | 
					            [FromRoute] string? command)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
 | 
					            var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -270,7 +270,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/{sessionId}/Command")]
 | 
					        [HttpPost("/Sessions/{sessionId}/Command")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult SendFullGeneralCommand(
 | 
					        public ActionResult SendFullGeneralCommand(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromBody, Required] GeneralCommand command)
 | 
					            [FromBody, Required] GeneralCommand command)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
 | 
					            var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
 | 
				
			||||||
@ -303,9 +303,9 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/{sessionId}/Message")]
 | 
					        [HttpPost("/Sessions/{sessionId}/Message")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult SendMessageCommand(
 | 
					        public ActionResult SendMessageCommand(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromQuery] string text,
 | 
					            [FromQuery] string? text,
 | 
				
			||||||
            [FromQuery] string header,
 | 
					            [FromQuery] string? header,
 | 
				
			||||||
            [FromQuery] long? timeoutMs)
 | 
					            [FromQuery] long? timeoutMs)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var command = new MessageCommand
 | 
					            var command = new MessageCommand
 | 
				
			||||||
@ -330,7 +330,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/{sessionId}/User/{userId}")]
 | 
					        [HttpPost("/Sessions/{sessionId}/User/{userId}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult AddUserToSession(
 | 
					        public ActionResult AddUserToSession(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromRoute] Guid userId)
 | 
					            [FromRoute] Guid userId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _sessionManager.AddAdditionalUser(sessionId, userId);
 | 
					            _sessionManager.AddAdditionalUser(sessionId, userId);
 | 
				
			||||||
@ -347,7 +347,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpDelete("/Sessions/{sessionId}/User/{userId}")]
 | 
					        [HttpDelete("/Sessions/{sessionId}/User/{userId}")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult RemoveUserFromSession(
 | 
					        public ActionResult RemoveUserFromSession(
 | 
				
			||||||
            [FromRoute] string sessionId,
 | 
					            [FromRoute] string? sessionId,
 | 
				
			||||||
            [FromRoute] Guid userId)
 | 
					            [FromRoute] Guid userId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _sessionManager.RemoveAdditionalUser(sessionId, userId);
 | 
					            _sessionManager.RemoveAdditionalUser(sessionId, userId);
 | 
				
			||||||
@ -368,9 +368,9 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/Capabilities")]
 | 
					        [HttpPost("/Sessions/Capabilities")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult PostCapabilities(
 | 
					        public ActionResult PostCapabilities(
 | 
				
			||||||
            [FromQuery] string id,
 | 
					            [FromQuery] string? id,
 | 
				
			||||||
            [FromQuery] string playableMediaTypes,
 | 
					            [FromQuery] string? playableMediaTypes,
 | 
				
			||||||
            [FromQuery] string supportedCommands,
 | 
					            [FromQuery] string? supportedCommands,
 | 
				
			||||||
            [FromQuery] bool supportsMediaControl,
 | 
					            [FromQuery] bool supportsMediaControl,
 | 
				
			||||||
            [FromQuery] bool supportsSync,
 | 
					            [FromQuery] bool supportsSync,
 | 
				
			||||||
            [FromQuery] bool supportsPersistentIdentifier = true)
 | 
					            [FromQuery] bool supportsPersistentIdentifier = true)
 | 
				
			||||||
@ -401,7 +401,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/Capabilities/Full")]
 | 
					        [HttpPost("/Sessions/Capabilities/Full")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult PostFullCapabilities(
 | 
					        public ActionResult PostFullCapabilities(
 | 
				
			||||||
            [FromQuery] string id,
 | 
					            [FromQuery] string? id,
 | 
				
			||||||
            [FromBody, Required] ClientCapabilities capabilities)
 | 
					            [FromBody, Required] ClientCapabilities capabilities)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(id))
 | 
					            if (string.IsNullOrWhiteSpace(id))
 | 
				
			||||||
@ -424,8 +424,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("/Sessions/Viewing")]
 | 
					        [HttpPost("/Sessions/Viewing")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult ReportViewing(
 | 
					        public ActionResult ReportViewing(
 | 
				
			||||||
            [FromQuery] string sessionId,
 | 
					            [FromQuery] string? sessionId,
 | 
				
			||||||
            [FromQuery] string itemId)
 | 
					            [FromQuery] string? itemId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            string session = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
 | 
					            string session = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -75,9 +75,9 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [HttpPost("Configuration")]
 | 
					        [HttpPost("Configuration")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public ActionResult UpdateInitialConfiguration(
 | 
					        public ActionResult UpdateInitialConfiguration(
 | 
				
			||||||
            [FromForm] string uiCulture,
 | 
					            [FromForm] string? uiCulture,
 | 
				
			||||||
            [FromForm] string metadataCountryCode,
 | 
					            [FromForm] string? metadataCountryCode,
 | 
				
			||||||
            [FromForm] string preferredMetadataLanguage)
 | 
					            [FromForm] string? preferredMetadataLanguage)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _config.Configuration.UICulture = uiCulture;
 | 
					            _config.Configuration.UICulture = uiCulture;
 | 
				
			||||||
            _config.Configuration.MetadataCountryCode = metadataCountryCode;
 | 
					            _config.Configuration.MetadataCountryCode = metadataCountryCode;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										277
									
								
								Jellyfin.Api/Controllers/StudiosController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								Jellyfin.Api/Controllers/StudiosController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,277 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using Jellyfin.Data.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Studios controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    public class StudiosController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="StudiosController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        public StudiosController(
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets all studios from a given item, folder, or the entire library.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="minCommunityRating">Optional filter by minimum community rating.</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="searchTerm">Optional. Search term.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
 | 
				
			||||||
 | 
					        /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param>
 | 
				
			||||||
 | 
					        /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional, include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
 | 
				
			||||||
 | 
					        /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person ids.</param>
 | 
				
			||||||
 | 
					        /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional, include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableTotalRecordCount">Total record count.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Studios returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the studios.</returns>
 | 
				
			||||||
 | 
					        [HttpGet]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetStudios(
 | 
				
			||||||
 | 
					            [FromQuery] double? minCommunityRating,
 | 
				
			||||||
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string searchTerm,
 | 
				
			||||||
 | 
					            [FromQuery] string parentId,
 | 
				
			||||||
 | 
					            [FromQuery] string fields,
 | 
				
			||||||
 | 
					            [FromQuery] string excludeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string includeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string filters,
 | 
				
			||||||
 | 
					            [FromQuery] bool? isFavorite,
 | 
				
			||||||
 | 
					            [FromQuery] string mediaTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string genres,
 | 
				
			||||||
 | 
					            [FromQuery] string genreIds,
 | 
				
			||||||
 | 
					            [FromQuery] string officialRatings,
 | 
				
			||||||
 | 
					            [FromQuery] string tags,
 | 
				
			||||||
 | 
					            [FromQuery] string years,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string person,
 | 
				
			||||||
 | 
					            [FromQuery] string personIds,
 | 
				
			||||||
 | 
					            [FromQuery] string personTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string studios,
 | 
				
			||||||
 | 
					            [FromQuery] string studioIds,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string nameStartsWithOrGreater,
 | 
				
			||||||
 | 
					            [FromQuery] string nameStartsWith,
 | 
				
			||||||
 | 
					            [FromQuery] string nameLessThan,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableTotalRecordCount = true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            User? user = null;
 | 
				
			||||||
 | 
					            BaseItem parentItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!userId.Equals(Guid.Empty))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					                parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
 | 
				
			||||||
 | 
					            var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
 | 
				
			||||||
 | 
					            var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var query = new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ExcludeItemTypes = excludeItemTypesArr,
 | 
				
			||||||
 | 
					                IncludeItemTypes = includeItemTypesArr,
 | 
				
			||||||
 | 
					                MediaTypes = mediaTypesArr,
 | 
				
			||||||
 | 
					                StartIndex = startIndex,
 | 
				
			||||||
 | 
					                Limit = limit,
 | 
				
			||||||
 | 
					                IsFavorite = isFavorite,
 | 
				
			||||||
 | 
					                NameLessThan = nameLessThan,
 | 
				
			||||||
 | 
					                NameStartsWith = nameStartsWith,
 | 
				
			||||||
 | 
					                NameStartsWithOrGreater = nameStartsWithOrGreater,
 | 
				
			||||||
 | 
					                Tags = RequestHelpers.Split(tags, ',', true),
 | 
				
			||||||
 | 
					                OfficialRatings = RequestHelpers.Split(officialRatings, ',', true),
 | 
				
			||||||
 | 
					                Genres = RequestHelpers.Split(genres, ',', true),
 | 
				
			||||||
 | 
					                GenreIds = RequestHelpers.GetGuids(genreIds),
 | 
				
			||||||
 | 
					                StudioIds = RequestHelpers.GetGuids(studioIds),
 | 
				
			||||||
 | 
					                Person = person,
 | 
				
			||||||
 | 
					                PersonIds = RequestHelpers.GetGuids(personIds),
 | 
				
			||||||
 | 
					                PersonTypes = RequestHelpers.Split(personTypes, ',', true),
 | 
				
			||||||
 | 
					                Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
 | 
				
			||||||
 | 
					                MinCommunityRating = minCommunityRating,
 | 
				
			||||||
 | 
					                DtoOptions = dtoOptions,
 | 
				
			||||||
 | 
					                SearchTerm = searchTerm,
 | 
				
			||||||
 | 
					                EnableTotalRecordCount = enableTotalRecordCount
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!string.IsNullOrWhiteSpace(parentId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (parentItem is Folder)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    query.AncestorIds = new[] { new Guid(parentId) };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    query.ItemIds = new[] { new Guid(parentId) };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Studios
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(studios))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query.StudioIds = studios.Split('|').Select(i =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    try
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return _libraryManager.GetStudio(i);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    catch
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return null;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }).Where(i => i != null).Select(i => i!.Id)
 | 
				
			||||||
 | 
					                    .ToArray();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var filter in RequestHelpers.GetFilters(filters))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                switch (filter)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    case ItemFilter.Dislikes:
 | 
				
			||||||
 | 
					                        query.IsLiked = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFavorite:
 | 
				
			||||||
 | 
					                        query.IsFavorite = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFavoriteOrLikes:
 | 
				
			||||||
 | 
					                        query.IsFavoriteOrLiked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsFolder:
 | 
				
			||||||
 | 
					                        query.IsFolder = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsNotFolder:
 | 
				
			||||||
 | 
					                        query.IsFolder = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsPlayed:
 | 
				
			||||||
 | 
					                        query.IsPlayed = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsResumable:
 | 
				
			||||||
 | 
					                        query.IsResumable = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.IsUnplayed:
 | 
				
			||||||
 | 
					                        query.IsPlayed = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case ItemFilter.Likes:
 | 
				
			||||||
 | 
					                        query.IsLiked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result = new QueryResult<(BaseItem, ItemCounts)>();
 | 
				
			||||||
 | 
					            var dtos = result.Items.Select(i =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var (baseItem, itemCounts) = i;
 | 
				
			||||||
 | 
					                var dto = _dtoService.GetItemByNameDto(baseItem, dtoOptions, null, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!string.IsNullOrWhiteSpace(includeItemTypes))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    dto.ChildCount = itemCounts.ItemCount;
 | 
				
			||||||
 | 
					                    dto.ProgramCount = itemCounts.ProgramCount;
 | 
				
			||||||
 | 
					                    dto.SeriesCount = itemCounts.SeriesCount;
 | 
				
			||||||
 | 
					                    dto.EpisodeCount = itemCounts.EpisodeCount;
 | 
				
			||||||
 | 
					                    dto.MovieCount = itemCounts.MovieCount;
 | 
				
			||||||
 | 
					                    dto.TrailerCount = itemCounts.TrailerCount;
 | 
				
			||||||
 | 
					                    dto.AlbumCount = itemCounts.AlbumCount;
 | 
				
			||||||
 | 
					                    dto.SongCount = itemCounts.SongCount;
 | 
				
			||||||
 | 
					                    dto.ArtistCount = itemCounts.ArtistCount;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return dto;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Items = dtos.ToArray(),
 | 
				
			||||||
 | 
					                TotalRecordCount = result.TotalRecordCount
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets a studio by name.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="name">Studio name.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Studio returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the studio.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("{name}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<BaseItemDto> GetStudio([FromRoute] string name, [FromQuery] Guid userId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetStudio(name);
 | 
				
			||||||
 | 
					            if (!userId.Equals(Guid.Empty))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return _dtoService.GetBaseItemDto(item, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
 | 
					        public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
 | 
				
			||||||
            [FromRoute] Guid itemId,
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
            [FromRoute] string language,
 | 
					            [FromRoute] string? language,
 | 
				
			||||||
            [FromQuery] bool? isPerfectMatch)
 | 
					            [FromQuery] bool? isPerfectMatch)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var video = (Video)_libraryManager.GetItemById(itemId);
 | 
					            var video = (Video)_libraryManager.GetItemById(itemId);
 | 
				
			||||||
@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        public async Task<ActionResult> DownloadRemoteSubtitles(
 | 
					        public async Task<ActionResult> DownloadRemoteSubtitles(
 | 
				
			||||||
            [FromRoute] Guid itemId,
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
            [FromRoute] string subtitleId)
 | 
					            [FromRoute] string? subtitleId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var video = (Video)_libraryManager.GetItemById(itemId);
 | 
					            var video = (Video)_libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
					        [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [Produces(MediaTypeNames.Application.Octet)]
 | 
					        [Produces(MediaTypeNames.Application.Octet)]
 | 
				
			||||||
        public async Task<ActionResult> GetRemoteSubtitles([FromRoute] string id)
 | 
					        public async Task<ActionResult> GetRemoteSubtitles([FromRoute] string? id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var result = await _subtitleManager.GetRemoteSubtitles(id, CancellationToken.None).ConfigureAwait(false);
 | 
					            var result = await _subtitleManager.GetRemoteSubtitles(id, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -186,9 +186,9 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public async Task<ActionResult> GetSubtitle(
 | 
					        public async Task<ActionResult> GetSubtitle(
 | 
				
			||||||
            [FromRoute, Required] Guid itemId,
 | 
					            [FromRoute, Required] Guid itemId,
 | 
				
			||||||
            [FromRoute, Required] string mediaSourceId,
 | 
					            [FromRoute, Required] string? mediaSourceId,
 | 
				
			||||||
            [FromRoute, Required] int index,
 | 
					            [FromRoute, Required] int index,
 | 
				
			||||||
            [FromRoute, Required] string format,
 | 
					            [FromRoute, Required] string? format,
 | 
				
			||||||
            [FromQuery] long? endPositionTicks,
 | 
					            [FromQuery] long? endPositionTicks,
 | 
				
			||||||
            [FromQuery] bool copyTimestamps,
 | 
					            [FromQuery] bool copyTimestamps,
 | 
				
			||||||
            [FromQuery] bool addVttTimeMap,
 | 
					            [FromQuery] bool addVttTimeMap,
 | 
				
			||||||
@ -254,7 +254,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        public async Task<ActionResult> GetSubtitlePlaylist(
 | 
					        public async Task<ActionResult> GetSubtitlePlaylist(
 | 
				
			||||||
            [FromRoute] Guid itemId,
 | 
					            [FromRoute] Guid itemId,
 | 
				
			||||||
            [FromRoute] int index,
 | 
					            [FromRoute] int index,
 | 
				
			||||||
            [FromRoute] string mediaSourceId,
 | 
					            [FromRoute] string? mediaSourceId,
 | 
				
			||||||
            [FromQuery, Required] int segmentLength)
 | 
					            [FromQuery, Required] int segmentLength)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = (Video)_libraryManager.GetItemById(itemId);
 | 
					            var item = (Video)_libraryManager.GetItemById(itemId);
 | 
				
			||||||
@ -324,7 +324,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <returns>A <see cref="Task{Stream}"/> with the new subtitle file.</returns>
 | 
					        /// <returns>A <see cref="Task{Stream}"/> with the new subtitle file.</returns>
 | 
				
			||||||
        private Task<Stream> EncodeSubtitles(
 | 
					        private Task<Stream> EncodeSubtitles(
 | 
				
			||||||
            Guid id,
 | 
					            Guid id,
 | 
				
			||||||
            string mediaSourceId,
 | 
					            string? mediaSourceId,
 | 
				
			||||||
            int index,
 | 
					            int index,
 | 
				
			||||||
            string format,
 | 
					            string format,
 | 
				
			||||||
            long startPositionTicks,
 | 
					            long startPositionTicks,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										222
									
								
								Jellyfin.Api/Controllers/SystemController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								Jellyfin.Api/Controllers/SystemController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.ComponentModel.DataAnnotations;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Threading;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Configuration;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Configuration;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.System;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The system controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Route("/System")]
 | 
				
			||||||
 | 
					    public class SystemController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IServerApplicationHost _appHost;
 | 
				
			||||||
 | 
					        private readonly IApplicationPaths _appPaths;
 | 
				
			||||||
 | 
					        private readonly IFileSystem _fileSystem;
 | 
				
			||||||
 | 
					        private readonly INetworkManager _network;
 | 
				
			||||||
 | 
					        private readonly ILogger<SystemController> _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="SystemController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="fileSystem">Instance of <see cref="IFileSystem"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="network">Instance of <see cref="INetworkManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="logger">Instance of <see cref="ILogger{SystemController}"/> interface.</param>
 | 
				
			||||||
 | 
					        public SystemController(
 | 
				
			||||||
 | 
					            IServerConfigurationManager serverConfigurationManager,
 | 
				
			||||||
 | 
					            IServerApplicationHost appHost,
 | 
				
			||||||
 | 
					            IFileSystem fileSystem,
 | 
				
			||||||
 | 
					            INetworkManager network,
 | 
				
			||||||
 | 
					            ILogger<SystemController> logger)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _appPaths = serverConfigurationManager.ApplicationPaths;
 | 
				
			||||||
 | 
					            _appHost = appHost;
 | 
				
			||||||
 | 
					            _fileSystem = fileSystem;
 | 
				
			||||||
 | 
					            _network = network;
 | 
				
			||||||
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets information about the server.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <response code="200">Information retrieved.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="SystemInfo"/> with info about the system.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("Info")]
 | 
				
			||||||
 | 
					        [Authorize(Policy = Policies.IgnoreSchedule)]
 | 
				
			||||||
 | 
					        [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult<SystemInfo>> GetSystemInfo()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return await _appHost.GetSystemInfo(CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets public information about the server.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <response code="200">Information retrieved.</response>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="PublicSystemInfo"/> with public info about the system.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("Info/Public")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult<PublicSystemInfo>> GetPublicSystemInfo()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return await _appHost.GetPublicSystemInfo(CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Pings the system.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <response code="200">Information retrieved.</response>
 | 
				
			||||||
 | 
					        /// <returns>The server name.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("Ping")]
 | 
				
			||||||
 | 
					        [HttpPost("Ping")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<string> PingSystem()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return _appHost.Name;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Restarts the application.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <response code="204">Server restarted.</response>
 | 
				
			||||||
 | 
					        /// <returns>No content. Server restarted.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("Restart")]
 | 
				
			||||||
 | 
					        [Authorize(Policy = Policies.LocalAccessOnly)]
 | 
				
			||||||
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public ActionResult RestartApplication()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Task.Run(async () =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await Task.Delay(100).ConfigureAwait(false);
 | 
				
			||||||
 | 
					                _appHost.Restart();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Shuts down the application.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <response code="204">Server shut down.</response>
 | 
				
			||||||
 | 
					        /// <returns>No content. Server shut down.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("Shutdown")]
 | 
				
			||||||
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					        public ActionResult ShutdownApplication()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Task.Run(async () =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await Task.Delay(100).ConfigureAwait(false);
 | 
				
			||||||
 | 
					                await _appHost.Shutdown().ConfigureAwait(false);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            return NoContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets a list of available server log files.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <response code="200">Information retrieved.</response>
 | 
				
			||||||
 | 
					        /// <returns>An array of <see cref="LogFile"/> with the available log files.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("Logs")]
 | 
				
			||||||
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<LogFile[]> GetServerLogs()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            IEnumerable<FileSystemMetadata> files;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath, new[] { ".txt", ".log" }, true, false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (IOException ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "Error getting logs");
 | 
				
			||||||
 | 
					                files = Enumerable.Empty<FileSystemMetadata>();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result = files.Select(i => new LogFile
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    DateCreated = _fileSystem.GetCreationTimeUtc(i),
 | 
				
			||||||
 | 
					                    DateModified = _fileSystem.GetLastWriteTimeUtc(i),
 | 
				
			||||||
 | 
					                    Name = i.Name,
 | 
				
			||||||
 | 
					                    Size = i.Length
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .OrderByDescending(i => i.DateModified)
 | 
				
			||||||
 | 
					                .ThenByDescending(i => i.DateCreated)
 | 
				
			||||||
 | 
					                .ThenBy(i => i.Name)
 | 
				
			||||||
 | 
					                .ToArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets information about the request endpoint.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <response code="200">Information retrieved.</response>
 | 
				
			||||||
 | 
					        /// <returns><see cref="EndPointInfo"/> with information about the endpoint.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("Endpoint")]
 | 
				
			||||||
 | 
					        [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<EndPointInfo> GetEndpointInfo()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return new EndPointInfo
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IsLocal = Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress),
 | 
				
			||||||
 | 
					                IsInNetwork = _network.IsInLocalNetwork(Request.HttpContext.Connection.RemoteIpAddress.ToString())
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets a log file.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="name">The name of the log file to get.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Log file retrieved.</response>
 | 
				
			||||||
 | 
					        /// <returns>The log file.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("Logs/Log")]
 | 
				
			||||||
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult GetLogFile([FromQuery, Required] string? name)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
 | 
				
			||||||
 | 
					                .First(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // For older files, assume fully static
 | 
				
			||||||
 | 
					            var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare);
 | 
				
			||||||
 | 
					            return File(stream, "text/plain");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <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(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var result = _appHost.GetWakeOnLanInfo();
 | 
				
			||||||
 | 
					            return Ok(result);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										377
									
								
								Jellyfin.Api/Controllers/TvShowsController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										377
									
								
								Jellyfin.Api/Controllers/TvShowsController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,377 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities.TV;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.TV;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The tv shows controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Route("/Shows")]
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    public class TvShowsController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					        private readonly ITVSeriesManager _tvSeriesManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="TvShowsController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="tvSeriesManager">Instance of the <see cref="ITVSeriesManager"/> interface.</param>
 | 
				
			||||||
 | 
					        public TvShowsController(
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService,
 | 
				
			||||||
 | 
					            ITVSeriesManager tvSeriesManager)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					            _tvSeriesManager = tvSeriesManager;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets a list of next up episodes.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id of the user to get the next up episodes for.</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="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="seriesId">Optional. Filter by series id.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImges">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableTotalRecordCount">Whether to enable the total records count. Defaults to true.</param>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("NextUp")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetNextUp(
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] string? seriesId,
 | 
				
			||||||
 | 
					            [FromQuery] string? parentId,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImges,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] bool enableTotalRecordCount = true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var options = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields!)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result = _tvSeriesManager.GetNextUp(
 | 
				
			||||||
 | 
					                new NextUpQuery
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Limit = limit,
 | 
				
			||||||
 | 
					                    ParentId = parentId,
 | 
				
			||||||
 | 
					                    SeriesId = seriesId,
 | 
				
			||||||
 | 
					                    StartIndex = startIndex,
 | 
				
			||||||
 | 
					                    UserId = userId,
 | 
				
			||||||
 | 
					                    EnableTotalRecordCount = enableTotalRecordCount
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                TotalRecordCount = result.TotalRecordCount,
 | 
				
			||||||
 | 
					                Items = returnItems
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets a list of upcoming episodes.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id of the user to get the upcoming episodes for.</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="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImges">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the next up episodes.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("Upcoming")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetUpcomingEpisodes(
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] string? parentId,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImges,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var options = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields!)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImges, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IncludeItemTypes = new[] { nameof(Episode) },
 | 
				
			||||||
 | 
					                OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
 | 
				
			||||||
 | 
					                MinPremiereDate = minPremiereDate,
 | 
				
			||||||
 | 
					                StartIndex = startIndex,
 | 
				
			||||||
 | 
					                Limit = limit,
 | 
				
			||||||
 | 
					                ParentId = parentIdGuid,
 | 
				
			||||||
 | 
					                Recursive = true,
 | 
				
			||||||
 | 
					                DtoOptions = options
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                TotalRecordCount = itemsResult.Count,
 | 
				
			||||||
 | 
					                Items = returnItems
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets episodes for a tv season.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="seriesId">The series id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="season">Optional filter by season number.</param>
 | 
				
			||||||
 | 
					        /// <param name="seasonId">Optional. Filter by season id.</param>
 | 
				
			||||||
 | 
					        /// <param name="isMissing">Optional. Filter by items that are missing episodes or not.</param>
 | 
				
			||||||
 | 
					        /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param>
 | 
				
			||||||
 | 
					        /// <param name="startItemId">Optional. Skip through the list until a given item is found.</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="enableImages">Optional, include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the episodes on success or a <see cref="NotFoundResult"/> if the series was not found.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("{seriesId}/Episodes")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
 | 
				
			||||||
 | 
					            [FromRoute] string? seriesId,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] int? season,
 | 
				
			||||||
 | 
					            [FromQuery] string? seasonId,
 | 
				
			||||||
 | 
					            [FromQuery] bool? isMissing,
 | 
				
			||||||
 | 
					            [FromQuery] string? adjacentTo,
 | 
				
			||||||
 | 
					            [FromQuery] string? startItemId,
 | 
				
			||||||
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] string? sortBy)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            List<BaseItem> episodes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields!)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!string.IsNullOrWhiteSpace(seasonId)) // Season id was supplied. Get episodes by season id.
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var item = _libraryManager.GetItemById(new Guid(seasonId));
 | 
				
			||||||
 | 
					                if (!(item is Season seasonItem))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return NotFound("No season exists with Id " + seasonId);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                episodes = seasonItem.GetEpisodes(user, dtoOptions);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else if (season.HasValue) // Season number was supplied. Get episodes by season number
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (!(_libraryManager.GetItemById(seriesId) is Series series))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return NotFound("Series not found");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var seasonItem = series
 | 
				
			||||||
 | 
					                    .GetSeasons(user, dtoOptions)
 | 
				
			||||||
 | 
					                    .FirstOrDefault(i => i.IndexNumber == season.Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                episodes = seasonItem == null ?
 | 
				
			||||||
 | 
					                    new List<BaseItem>()
 | 
				
			||||||
 | 
					                    : ((Season)seasonItem).GetEpisodes(user, dtoOptions);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else // No season number or season id was supplied. Returning all episodes.
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (!(_libraryManager.GetItemById(seriesId) is Series series))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return NotFound("Series not found");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                episodes = series.GetEpisodes(user, dtoOptions).ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Filter after the fact in case the ui doesn't want them
 | 
				
			||||||
 | 
					            if (isMissing.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var val = isMissing.Value;
 | 
				
			||||||
 | 
					                episodes = episodes
 | 
				
			||||||
 | 
					                    .Where(i => ((Episode)i).IsMissingEpisode == val)
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!string.IsNullOrWhiteSpace(startItemId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                episodes = episodes
 | 
				
			||||||
 | 
					                    .SkipWhile(i => !string.Equals(i.Id.ToString("N", CultureInfo.InvariantCulture), startItemId, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // This must be the last filter
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(adjacentTo))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo).ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (string.Equals(sortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                episodes.Shuffle();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var returnItems = episodes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (startIndex.HasValue || limit.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                returnItems = ApplyPaging(episodes, startIndex, limit).ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                TotalRecordCount = episodes.Count,
 | 
				
			||||||
 | 
					                Items = dtos
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets seasons for a tv series.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="seriesId">The series id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
 | 
				
			||||||
 | 
					        /// <param name="isSpecialSeason">Optional. Filter by special season.</param>
 | 
				
			||||||
 | 
					        /// <param name="isMissing">Optional. Filter by items that are missing episodes or not.</param>
 | 
				
			||||||
 | 
					        /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="QueryResult{BaseItemDto}"/> on success or a <see cref="NotFoundResult"/> if the series was not found.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("{seriesId}/Seasons")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetSeasons(
 | 
				
			||||||
 | 
					            [FromRoute] string? seriesId,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] bool? isSpecialSeason,
 | 
				
			||||||
 | 
					            [FromQuery] bool? isMissing,
 | 
				
			||||||
 | 
					            [FromQuery] string? adjacentTo,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!(_libraryManager.GetItemById(seriesId) is Series series))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return NotFound("Series not found");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var seasons = series.GetItemList(new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IsMissing = isMissing,
 | 
				
			||||||
 | 
					                IsSpecialSeason = isSpecialSeason,
 | 
				
			||||||
 | 
					                AdjacentTo = adjacentTo
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                TotalRecordCount = returnItems.Count,
 | 
				
			||||||
 | 
					                Items = returnItems
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Applies the paging.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="items">The items.</param>
 | 
				
			||||||
 | 
					        /// <param name="startIndex">The start index.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">The limit.</param>
 | 
				
			||||||
 | 
					        /// <returns>IEnumerable{BaseItem}.</returns>
 | 
				
			||||||
 | 
					        private IEnumerable<BaseItem> ApplyPaging(IEnumerable<BaseItem> items, int? startIndex, int? limit)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Start at
 | 
				
			||||||
 | 
					            if (startIndex.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                items = items.Skip(startIndex.Value);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Return limit
 | 
				
			||||||
 | 
					            if (limit.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                items = items.Take(limit.Value);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return items;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -68,17 +68,14 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="isHidden">Optional filter by IsHidden=true or false.</param>
 | 
					        /// <param name="isHidden">Optional filter by IsHidden=true or false.</param>
 | 
				
			||||||
        /// <param name="isDisabled">Optional filter by IsDisabled=true or false.</param>
 | 
					        /// <param name="isDisabled">Optional filter by IsDisabled=true or false.</param>
 | 
				
			||||||
        /// <param name="isGuest">Optional filter by IsGuest=true or false.</param>
 | 
					 | 
				
			||||||
        /// <response code="200">Users returned.</response>
 | 
					        /// <response code="200">Users returned.</response>
 | 
				
			||||||
        /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the users.</returns>
 | 
					        /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the users.</returns>
 | 
				
			||||||
        [HttpGet]
 | 
					        [HttpGet]
 | 
				
			||||||
        [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
					        [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "isGuest", Justification = "Imported from ServiceStack")]
 | 
					 | 
				
			||||||
        public ActionResult<IEnumerable<UserDto>> GetUsers(
 | 
					        public ActionResult<IEnumerable<UserDto>> GetUsers(
 | 
				
			||||||
            [FromQuery] bool? isHidden,
 | 
					            [FromQuery] bool? isHidden,
 | 
				
			||||||
            [FromQuery] bool? isDisabled,
 | 
					            [FromQuery] bool? isDisabled)
 | 
				
			||||||
            [FromQuery] bool? isGuest)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var users = Get(isHidden, isDisabled, false, false);
 | 
					            var users = Get(isHidden, isDisabled, false, false);
 | 
				
			||||||
            return Ok(users);
 | 
					            return Ok(users);
 | 
				
			||||||
@ -167,8 +164,8 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public async Task<ActionResult<AuthenticationResult>> AuthenticateUser(
 | 
					        public async Task<ActionResult<AuthenticationResult>> AuthenticateUser(
 | 
				
			||||||
            [FromRoute, Required] Guid userId,
 | 
					            [FromRoute, Required] Guid userId,
 | 
				
			||||||
            [FromQuery, BindRequired] string pw,
 | 
					            [FromQuery, BindRequired] string? pw,
 | 
				
			||||||
            [FromQuery, BindRequired] string password)
 | 
					            [FromQuery, BindRequired] string? password)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var user = _userManager.GetUserById(userId);
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -486,7 +483,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns>
 | 
					        /// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns>
 | 
				
			||||||
        [HttpPost("ForgotPassword")]
 | 
					        [HttpPost("ForgotPassword")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody] string enteredUsername)
 | 
					        public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody] string? enteredUsername)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var isLocal = HttpContext.Connection.RemoteIpAddress.Equals(HttpContext.Connection.LocalIpAddress)
 | 
					            var isLocal = HttpContext.Connection.RemoteIpAddress.Equals(HttpContext.Connection.LocalIpAddress)
 | 
				
			||||||
                          || _networkManager.IsInLocalNetwork(HttpContext.Connection.RemoteIpAddress.ToString());
 | 
					                          || _networkManager.IsInLocalNetwork(HttpContext.Connection.RemoteIpAddress.ToString());
 | 
				
			||||||
@ -504,7 +501,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        /// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns>
 | 
					        /// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns>
 | 
				
			||||||
        [HttpPost("ForgotPassword/Pin")]
 | 
					        [HttpPost("ForgotPassword/Pin")]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
        public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody] string pin)
 | 
					        public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody] string? pin)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var result = await _userManager.RedeemPasswordResetPin(pin).ConfigureAwait(false);
 | 
					            var result = await _userManager.RedeemPasswordResetPin(pin).ConfigureAwait(false);
 | 
				
			||||||
            return result;
 | 
					            return result;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										391
									
								
								Jellyfin.Api/Controllers/UserLibraryController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										391
									
								
								Jellyfin.Api/Controllers/UserLibraryController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,391 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Threading;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities.Audio;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Providers;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// User library controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
				
			||||||
 | 
					    public class UserLibraryController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly IUserDataManager _userDataRepository;
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					        private readonly IUserViewManager _userViewManager;
 | 
				
			||||||
 | 
					        private readonly IFileSystem _fileSystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="UserLibraryController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="userViewManager">Instance of the <see cref="IUserViewManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 | 
				
			||||||
 | 
					        public UserLibraryController(
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            IUserDataManager userDataRepository,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService,
 | 
				
			||||||
 | 
					            IUserViewManager userViewManager,
 | 
				
			||||||
 | 
					            IFileSystem fileSystem)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _userDataRepository = userDataRepository;
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					            _userViewManager = userViewManager;
 | 
				
			||||||
 | 
					            _fileSystem = fileSystem;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets an item from a user's library.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Item returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the d item.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Users/{userId}/Items/{itemId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = itemId.Equals(Guid.Empty)
 | 
				
			||||||
 | 
					                ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
 | 
					                : _libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets the root folder from a user's library.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Root folder returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Users/{userId}/Items/Root")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<BaseItemDto> GetRootFolder([FromRoute] Guid userId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetUserRootFolder();
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					            return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets intros to play before the main media item plays.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Intros returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the intros to play.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Users/{userId}/Items/{itemId}/Intros")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute] Guid userId, [FromRoute] Guid itemId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = itemId.Equals(Guid.Empty)
 | 
				
			||||||
 | 
					                ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
 | 
					                : _libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					            var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Items = dtos,
 | 
				
			||||||
 | 
					                TotalRecordCount = dtos.Length
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Marks an item as a favorite.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Item marked as favorite.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Users/{userId}/FavoriteItems/{itemId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return MarkFavorite(userId, itemId, true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Unmarks item as a favorite.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Item unmarked as favorite.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpDelete("/Users/{userId}/FavoriteItems/{itemId}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return MarkFavorite(userId, itemId, false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Deletes a user's saved personal rating for an item.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Personal rating removed.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpDelete("/Users/{userId}/Items/{itemId}/Rating")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return UpdateUserItemRatingInternal(userId, itemId, null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Updates a user's rating for an item.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="likes">Whether this <see cref="UpdateUserItemRating" /> is likes.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Item rating updated.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("/Users/{userId}/Items/{itemId}/Rating")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool likes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return UpdateUserItemRatingInternal(userId, itemId, likes);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets local trailers for an item.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">An <see cref="OkResult"/> containing the item's local trailers.</response>
 | 
				
			||||||
 | 
					        /// <returns>The items local trailers.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Users/{userId}/Items/{itemId}/LocalTrailers")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute] Guid userId, [FromRoute] Guid itemId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = itemId.Equals(Guid.Empty)
 | 
				
			||||||
 | 
					                ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
 | 
					                : _libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					            var dtosExtras = item.GetExtras(new[] { ExtraType.Trailer })
 | 
				
			||||||
 | 
					                .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
 | 
				
			||||||
 | 
					                .ToArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (item is IHasTrailers hasTrailers)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var trailers = hasTrailers.GetTrailers();
 | 
				
			||||||
 | 
					                var dtosTrailers = _dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item);
 | 
				
			||||||
 | 
					                var allTrailers = new BaseItemDto[dtosExtras.Length + dtosTrailers.Count];
 | 
				
			||||||
 | 
					                dtosExtras.CopyTo(allTrailers, 0);
 | 
				
			||||||
 | 
					                dtosTrailers.CopyTo(allTrailers, dtosExtras.Length);
 | 
				
			||||||
 | 
					                return allTrailers;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return dtosExtras;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets special features for an item.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">Item id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Special features returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the special features.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Users/{userId}/Items/{itemId}/SpecialFeatures")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute] Guid userId, [FromRoute] Guid itemId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = itemId.Equals(Guid.Empty)
 | 
				
			||||||
 | 
					                ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
 | 
					                : _libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(item
 | 
				
			||||||
 | 
					                .GetExtras(BaseItem.DisplayExtraTypes)
 | 
				
			||||||
 | 
					                .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets latest media.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines.</param>
 | 
				
			||||||
 | 
					        /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.</param>
 | 
				
			||||||
 | 
					        /// <param name="isPlayed">Filter by items that are played, or not.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. the max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Return item limit.</param>
 | 
				
			||||||
 | 
					        /// <param name="groupItems">Whether or not to group items into a parent container.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Latest media returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the latest media.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Users/{userId}/Items/Latest")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
 | 
				
			||||||
 | 
					            [FromRoute] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] Guid parentId,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] string? includeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] bool? isPlayed,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int limit = 20,
 | 
				
			||||||
 | 
					            [FromQuery] bool groupItems = true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!isPlayed.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (user.HidePlayedInLatest)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    isPlayed = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var list = _userViewManager.GetLatestItems(
 | 
				
			||||||
 | 
					                new LatestItemsQuery
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    GroupItems = groupItems,
 | 
				
			||||||
 | 
					                    IncludeItemTypes = RequestHelpers.Split(includeItemTypes, ',', true),
 | 
				
			||||||
 | 
					                    IsPlayed = isPlayed,
 | 
				
			||||||
 | 
					                    Limit = limit,
 | 
				
			||||||
 | 
					                    ParentId = parentId,
 | 
				
			||||||
 | 
					                    UserId = userId,
 | 
				
			||||||
 | 
					                }, dtoOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtos = list.Select(i =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var item = i.Item2[0];
 | 
				
			||||||
 | 
					                var childCount = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (i.Item1 != null && (i.Item2.Count > 1 || i.Item1 is MusicAlbum))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    item = i.Item1;
 | 
				
			||||||
 | 
					                    childCount = i.Item2.Count;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var dto = _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                dto.ChildCount = childCount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return dto;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(dtos);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task RefreshItemOnDemandIfNeeded(BaseItem item)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (item is Person)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
 | 
				
			||||||
 | 
					                var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!hasMetdata)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
 | 
				
			||||||
 | 
					                        ImageRefreshMode = MetadataRefreshMode.FullRefresh,
 | 
				
			||||||
 | 
					                        ForceSave = performFullRefresh
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Marks the favorite.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
 | 
				
			||||||
 | 
					        private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get the user data for this item
 | 
				
			||||||
 | 
					            var data = _userDataRepository.GetUserData(user, item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Set favorite status
 | 
				
			||||||
 | 
					            data.IsFavorite = isFavorite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return _userDataRepository.GetUserDataDto(item, user);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Updates the user item rating.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id.</param>
 | 
				
			||||||
 | 
					        /// <param name="itemId">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="likes">if set to <c>true</c> [likes].</param>
 | 
				
			||||||
 | 
					        private UserItemDataDto UpdateUserItemRatingInternal(Guid userId, Guid itemId, bool? likes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get the user data for this item
 | 
				
			||||||
 | 
					            var data = _userDataRepository.GetUserData(user, item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            data.Likes = likes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return _userDataRepository.GetUserDataDto(item, user);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										148
									
								
								Jellyfin.Api/Controllers/UserViewsController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								Jellyfin.Api/Controllers/UserViewsController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,148 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Models.UserViewDtos;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// User views controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class UserViewsController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly IUserViewManager _userViewManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					        private readonly IAuthorizationContext _authContext;
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="UserViewsController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="userViewManager">Instance of the <see cref="IUserViewManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        public UserViewsController(
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            IUserViewManager userViewManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService,
 | 
				
			||||||
 | 
					            IAuthorizationContext authContext,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _userViewManager = userViewManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					            _authContext = authContext;
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get user views.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <param name="includeExternalContent">Whether or not to include external views such as channels or live tv.</param>
 | 
				
			||||||
 | 
					        /// <param name="includeHidden">Whether or not to include hidden content.</param>
 | 
				
			||||||
 | 
					        /// <param name="presetViews">Preset views.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">User views returned.</response>
 | 
				
			||||||
 | 
					        /// <returns>An <see cref="OkResult"/> containing the user views.</returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Users/{userId}/Views")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
 | 
				
			||||||
 | 
					            [FromRoute] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] bool? includeExternalContent,
 | 
				
			||||||
 | 
					            [FromQuery] bool includeHidden,
 | 
				
			||||||
 | 
					            [FromQuery] string? presetViews)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var query = new UserViewQuery
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                UserId = userId,
 | 
				
			||||||
 | 
					                IncludeHidden = includeHidden
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (includeExternalContent.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query.IncludeExternalContent = includeExternalContent.Value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!string.IsNullOrWhiteSpace(presetViews))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query.PresetViews = RequestHelpers.Split(presetViews, ',', true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
 | 
				
			||||||
 | 
					            if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var folders = _userViewManager.GetUserViews(query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions().AddClientFields(Request);
 | 
				
			||||||
 | 
					            var fields = dtoOptions.Fields.ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fields.Add(ItemFields.PrimaryImageAspectRatio);
 | 
				
			||||||
 | 
					            fields.Add(ItemFields.DisplayPreferencesId);
 | 
				
			||||||
 | 
					            fields.Remove(ItemFields.BasicSyncInfo);
 | 
				
			||||||
 | 
					            dtoOptions.Fields = fields.ToArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
 | 
				
			||||||
 | 
					                .ToArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Items = dtos,
 | 
				
			||||||
 | 
					                TotalRecordCount = dtos.Length
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get user view grouping options.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="userId">User id.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">User view grouping options returned.</response>
 | 
				
			||||||
 | 
					        /// <response code="404">User not found.</response>
 | 
				
			||||||
 | 
					        /// <returns>
 | 
				
			||||||
 | 
					        /// An <see cref="OkResult"/> containing the user view grouping options
 | 
				
			||||||
 | 
					        /// or a <see cref="NotFoundResult"/> if user not found.
 | 
				
			||||||
 | 
					        /// </returns>
 | 
				
			||||||
 | 
					        [HttpGet("/Users/{userId}/GroupingOptions")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
 | 
					        public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute] Guid userId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            if (user == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return NotFound();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(_libraryManager.GetUserRootFolder()
 | 
				
			||||||
 | 
					                .GetChildren(user, true)
 | 
				
			||||||
 | 
					                .OfType<Folder>()
 | 
				
			||||||
 | 
					                .Where(UserView.IsEligibleForGrouping)
 | 
				
			||||||
 | 
					                .Select(i => new SpecialViewOptionDto
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Name = i.Name,
 | 
				
			||||||
 | 
					                    Id = i.Id.ToString("N", CultureInfo.InvariantCulture)
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .OrderBy(i => i.Name));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -50,7 +50,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
        public async Task<ActionResult<FileStreamResult>> GetAttachment(
 | 
					        public async Task<ActionResult<FileStreamResult>> GetAttachment(
 | 
				
			||||||
            [FromRoute] Guid videoId,
 | 
					            [FromRoute] Guid videoId,
 | 
				
			||||||
            [FromRoute] string mediaSourceId,
 | 
					            [FromRoute] string? mediaSourceId,
 | 
				
			||||||
            [FromRoute] int index)
 | 
					            [FromRoute] int index)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
 | 
				
			|||||||
@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
        [Authorize(Policy = Policies.RequiresElevation)]
 | 
					        [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
					        [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
				
			||||||
        public ActionResult MergeVersions([FromQuery] string itemIds)
 | 
					        public ActionResult MergeVersions([FromQuery] string? itemIds)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var items = RequestHelpers.Split(itemIds, ',', true)
 | 
					            var items = RequestHelpers.Split(itemIds, ',', true)
 | 
				
			||||||
                .Select(i => _libraryManager.GetItemById(i))
 | 
					                .Select(i => _libraryManager.GetItemById(i))
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										231
									
								
								Jellyfin.Api/Controllers/YearsController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								Jellyfin.Api/Controllers/YearsController.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,231 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using Jellyfin.Data.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Controllers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Years controller.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class YearsController : BaseJellyfinApiController
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="YearsController"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 | 
				
			||||||
 | 
					        public YearsController(
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
 | 
					            _userManager = userManager;
 | 
				
			||||||
 | 
					            _dtoService = dtoService;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get years.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="startIndex">Skips over a given number of items within the results. Use for paging.</param>
 | 
				
			||||||
 | 
					        /// <param name="limit">Optional. The maximum number of records to return.</param>
 | 
				
			||||||
 | 
					        /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
 | 
				
			||||||
 | 
					        /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
 | 
				
			||||||
 | 
					        /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
 | 
				
			||||||
 | 
					        /// <param name="excludeItemTypes">Optional. If specified, results will be excluded based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="includeItemTypes">Optional. If specified, results will be included based on item type. This allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
 | 
				
			||||||
 | 
					        /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableUserData">Optional. Include user data.</param>
 | 
				
			||||||
 | 
					        /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">User Id.</param>
 | 
				
			||||||
 | 
					        /// <param name="recursive">Search recursively.</param>
 | 
				
			||||||
 | 
					        /// <param name="enableImages">Optional. Include image information in output.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Year query returned.</response>
 | 
				
			||||||
 | 
					        /// <returns> A <see cref="QueryResult{BaseItemDto}"/> containing the year result.</returns>
 | 
				
			||||||
 | 
					        [HttpGet]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        public ActionResult<QueryResult<BaseItemDto>> GetYears(
 | 
				
			||||||
 | 
					            [FromQuery] int? startIndex,
 | 
				
			||||||
 | 
					            [FromQuery] int? limit,
 | 
				
			||||||
 | 
					            [FromQuery] string? sortOrder,
 | 
				
			||||||
 | 
					            [FromQuery] string? parentId,
 | 
				
			||||||
 | 
					            [FromQuery] string? fields,
 | 
				
			||||||
 | 
					            [FromQuery] string? excludeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string? includeItemTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string? mediaTypes,
 | 
				
			||||||
 | 
					            [FromQuery] string? sortBy,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableUserData,
 | 
				
			||||||
 | 
					            [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
 | 
					            [FromQuery] string? enableImageTypes,
 | 
				
			||||||
 | 
					            [FromQuery] Guid userId,
 | 
				
			||||||
 | 
					            [FromQuery] bool recursive = true,
 | 
				
			||||||
 | 
					            [FromQuery] bool? enableImages = true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddItemFields(fields)
 | 
				
			||||||
 | 
					                .AddClientFields(Request)
 | 
				
			||||||
 | 
					                .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            User? user = null;
 | 
				
			||||||
 | 
					            BaseItem parentItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!userId.Equals(Guid.Empty))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					                parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            IList<BaseItem> items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var excludeItemTypesArr = RequestHelpers.Split(excludeItemTypes, ',', true);
 | 
				
			||||||
 | 
					            var includeItemTypesArr = RequestHelpers.Split(includeItemTypes, ',', true);
 | 
				
			||||||
 | 
					            var mediaTypesArr = RequestHelpers.Split(mediaTypes, ',', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var query = new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ExcludeItemTypes = excludeItemTypesArr,
 | 
				
			||||||
 | 
					                IncludeItemTypes = includeItemTypesArr,
 | 
				
			||||||
 | 
					                MediaTypes = mediaTypesArr,
 | 
				
			||||||
 | 
					                DtoOptions = dtoOptions
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bool Filter(BaseItem i) => FilterItem(i, excludeItemTypesArr, includeItemTypesArr, mediaTypesArr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (parentItem.IsFolder)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var folder = (Folder)parentItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!userId.Equals(Guid.Empty))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    items = recursive ? folder.GetRecursiveChildren(user, query).ToList() : folder.GetChildren(user, true).Where(Filter).ToList();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                items = new[] { parentItem }.Where(Filter).ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var extractedItems = GetAllItems(items);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var filteredItems = _libraryManager.Sort(extractedItems, user, RequestHelpers.GetOrderBy(sortBy, sortOrder));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var ibnItemsArray = filteredItems.ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            IEnumerable<BaseItem> ibnItems = ibnItemsArray;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var result = new QueryResult<BaseItemDto> { TotalRecordCount = ibnItemsArray.Count };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (startIndex.HasValue || limit.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (startIndex.HasValue)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ibnItems = ibnItems.Skip(startIndex.Value);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (limit.HasValue)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ibnItems = ibnItems.Take(limit.Value);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var tuples = ibnItems.Select(i => new Tuple<BaseItem, List<BaseItem>>(i, new List<BaseItem>()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtos = tuples.Select(i => _dtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result.Items = dtos.Where(i => i != null).ToArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets a year.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="year">The year.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 | 
				
			||||||
 | 
					        /// <response code="200">Year returned.</response>
 | 
				
			||||||
 | 
					        /// <response code="404">Year not found.</response>
 | 
				
			||||||
 | 
					        /// <returns>
 | 
				
			||||||
 | 
					        /// An <see cref="OkResult"/> containing the year,
 | 
				
			||||||
 | 
					        /// or a <see cref="NotFoundResult"/> if year not found.
 | 
				
			||||||
 | 
					        /// </returns>
 | 
				
			||||||
 | 
					        [HttpGet("{year}")]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					        [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
 | 
					        public ActionResult<BaseItemDto> GetYear([FromRoute] int year, [FromQuery] Guid userId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var item = _libraryManager.GetYear(year);
 | 
				
			||||||
 | 
					            if (item == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return NotFound();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtoOptions = new DtoOptions()
 | 
				
			||||||
 | 
					                .AddClientFields(Request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!userId.Equals(Guid.Empty))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var user = _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					                return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return _dtoService.GetBaseItemDto(item, dtoOptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private bool FilterItem(BaseItem f, IReadOnlyCollection<string> excludeItemTypes, IReadOnlyCollection<string> includeItemTypes, IReadOnlyCollection<string> mediaTypes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Exclude item types
 | 
				
			||||||
 | 
					            if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Include item types
 | 
				
			||||||
 | 
					            if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Include MediaTypes
 | 
				
			||||||
 | 
					            if (mediaTypes.Count > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private IEnumerable<BaseItem> GetAllItems(IEnumerable<BaseItem> items)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return items
 | 
				
			||||||
 | 
					                .Select(i => i.ProductionYear ?? 0)
 | 
				
			||||||
 | 
					                .Where(i => i > 0)
 | 
				
			||||||
 | 
					                .Distinct()
 | 
				
			||||||
 | 
					                .Select(year => _libraryManager.GetYear(year));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -78,35 +78,13 @@ namespace Jellyfin.Api.Helpers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the item fields.
 | 
					        /// Get Guid array from string.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="fields">The item field string.</param>
 | 
					        /// <param name="value">String value.</param>
 | 
				
			||||||
        /// <returns>Array of parsed item fields.</returns>
 | 
					        /// <returns>Guid array.</returns>
 | 
				
			||||||
        internal static ItemFields[] GetItemFields(string fields)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (string.IsNullOrEmpty(fields))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return Array.Empty<ItemFields>();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return Split(fields, ',', true)
 | 
					 | 
				
			||||||
                .Select(v =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    if (Enum.TryParse(v, true, out ItemFields value))
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        return (ItemFields?)value;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return null;
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .Where(i => i.HasValue)
 | 
					 | 
				
			||||||
                .Select(i => i!.Value)
 | 
					 | 
				
			||||||
                .ToArray();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        internal static Guid[] GetGuids(string? value)
 | 
					        internal static Guid[] GetGuids(string? value)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (string.IsNullOrEmpty(value))
 | 
					            if (value == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Array.Empty<Guid>();
 | 
					                return Array.Empty<Guid>();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -116,16 +94,20 @@ namespace Jellyfin.Api.Helpers
 | 
				
			|||||||
                .ToArray();
 | 
					                .ToArray();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get orderby.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="sortBy">Sort by.</param>
 | 
				
			||||||
 | 
					        /// <param name="requestedSortOrder">Sort order.</param>
 | 
				
			||||||
 | 
					        /// <returns>Resulting order by.</returns>
 | 
				
			||||||
        internal static ValueTuple<string, SortOrder>[] GetOrderBy(string? sortBy, string? requestedSortOrder)
 | 
					        internal static ValueTuple<string, SortOrder>[] GetOrderBy(string? sortBy, string? requestedSortOrder)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var val = sortBy;
 | 
					            if (string.IsNullOrEmpty(sortBy))
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (string.IsNullOrEmpty(val))
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return Array.Empty<ValueTuple<string, SortOrder>>();
 | 
					                return Array.Empty<ValueTuple<string, SortOrder>>();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var vals = val.Split(',');
 | 
					            var vals = sortBy.Split(',');
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(requestedSortOrder))
 | 
					            if (string.IsNullOrWhiteSpace(requestedSortOrder))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                requestedSortOrder = "Ascending";
 | 
					                requestedSortOrder = "Ascending";
 | 
				
			||||||
@ -149,5 +131,18 @@ namespace Jellyfin.Api.Helpers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return result;
 | 
					            return result;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets the filters.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="filters">The filter string.</param>
 | 
				
			||||||
 | 
					        /// <returns>IEnumerable{ItemFilter}.</returns>
 | 
				
			||||||
 | 
					        internal static ItemFilter[] GetFilters(string filters)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return string.IsNullOrEmpty(filters)
 | 
				
			||||||
 | 
					                ? Array.Empty<ItemFilter>()
 | 
				
			||||||
 | 
					                : Split(filters, ',', true)
 | 
				
			||||||
 | 
					                    .Select(v => Enum.Parse<ItemFilter>(v, true)).ToArray();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										182
									
								
								Jellyfin.Api/Helpers/SimilarItemsHelper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								Jellyfin.Api/Helpers/SimilarItemsHelper.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,182 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Persistence;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Helpers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// The similar items helper class.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public static class SimilarItemsHelper
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        internal static QueryResult<BaseItemDto> GetSimilarItemsResult(
 | 
				
			||||||
 | 
					            DtoOptions dtoOptions,
 | 
				
			||||||
 | 
					            IUserManager userManager,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IDtoService dtoService,
 | 
				
			||||||
 | 
					            Guid userId,
 | 
				
			||||||
 | 
					            string id,
 | 
				
			||||||
 | 
					            string? excludeArtistIds,
 | 
				
			||||||
 | 
					            int? limit,
 | 
				
			||||||
 | 
					            Type[] includeTypes,
 | 
				
			||||||
 | 
					            Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = !userId.Equals(Guid.Empty) ? userManager.GetUserById(userId) : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item = string.IsNullOrEmpty(id) ?
 | 
				
			||||||
 | 
					                (!userId.Equals(Guid.Empty) ? libraryManager.GetUserRootFolder() :
 | 
				
			||||||
 | 
					                libraryManager.RootFolder) : libraryManager.GetItemById(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var query = new InternalItemsQuery(user)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(),
 | 
				
			||||||
 | 
					                Recursive = true,
 | 
				
			||||||
 | 
					                DtoOptions = dtoOptions
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var inputItems = libraryManager.GetItemList(query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var returnItems = items;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (limit.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                returnItems = returnItems.Take(limit.Value).ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Items = dtos,
 | 
				
			||||||
 | 
					                TotalRecordCount = items.Count
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets the similaritems.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="item">The item.</param>
 | 
				
			||||||
 | 
					        /// <param name="libraryManager">The library manager.</param>
 | 
				
			||||||
 | 
					        /// <param name="inputItems">The input items.</param>
 | 
				
			||||||
 | 
					        /// <param name="getSimilarityScore">The get similarity score.</param>
 | 
				
			||||||
 | 
					        /// <returns>IEnumerable{BaseItem}.</returns>
 | 
				
			||||||
 | 
					        private static IEnumerable<BaseItem> GetSimilaritems(
 | 
				
			||||||
 | 
					            BaseItem item,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager,
 | 
				
			||||||
 | 
					            IEnumerable<BaseItem> inputItems,
 | 
				
			||||||
 | 
					            Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var itemId = item.Id;
 | 
				
			||||||
 | 
					            inputItems = inputItems.Where(i => i.Id != itemId);
 | 
				
			||||||
 | 
					            var itemPeople = libraryManager.GetPeople(item);
 | 
				
			||||||
 | 
					            var allPeople = libraryManager.GetPeople(new InternalPeopleQuery
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                AppearsInItemId = item.Id
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, itemPeople, allPeople, i)))
 | 
				
			||||||
 | 
					                .Where(i => i.Item2 > 2)
 | 
				
			||||||
 | 
					                .OrderByDescending(i => i.Item2)
 | 
				
			||||||
 | 
					                .Select(i => i.Item1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static IEnumerable<string> GetTags(BaseItem item)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return item.Tags;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets the similiarity score.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="item1">The item1.</param>
 | 
				
			||||||
 | 
					        /// <param name="item1People">The item1 people.</param>
 | 
				
			||||||
 | 
					        /// <param name="allPeople">All people.</param>
 | 
				
			||||||
 | 
					        /// <param name="item2">The item2.</param>
 | 
				
			||||||
 | 
					        /// <returns>System.Int32.</returns>
 | 
				
			||||||
 | 
					        internal static int GetSimiliarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var points = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                points += 10;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Find common genres
 | 
				
			||||||
 | 
					            points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Find common tags
 | 
				
			||||||
 | 
					            points += GetTags(item1).Where(i => GetTags(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Find common studios
 | 
				
			||||||
 | 
					            points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var item2PeopleNames = allPeople.Where(i => i.ItemId == item2.Id)
 | 
				
			||||||
 | 
					                .Select(i => i.Name)
 | 
				
			||||||
 | 
					                .Where(i => !string.IsNullOrWhiteSpace(i))
 | 
				
			||||||
 | 
					                .DistinctNames()
 | 
				
			||||||
 | 
					                .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            points += item1People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return 5;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return 3;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return 3;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return 3;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return 2;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return 1;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (item1.ProductionYear.HasValue && item2.ProductionYear.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var diff = Math.Abs(item1.ProductionYear.Value - item2.ProductionYear.Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Add if they came out within the same decade
 | 
				
			||||||
 | 
					                if (diff < 10)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    points += 2;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // And more if within five years
 | 
				
			||||||
 | 
					                if (diff < 5)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    points += 2;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return points;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										354
									
								
								Jellyfin.Api/Helpers/TranscodingJobHelper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								Jellyfin.Api/Helpers/TranscodingJobHelper.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,354 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Threading;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Models.PlaybackDtos;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Helpers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Transcoding job helpers.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class TranscodingJobHelper
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// The active transcoding jobs.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        private static readonly List<TranscodingJobDto> _activeTranscodingJobs = new List<TranscodingJobDto>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// The transcoding locks.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        private static readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new Dictionary<string, SemaphoreSlim>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private readonly ILogger<TranscodingJobHelper> _logger;
 | 
				
			||||||
 | 
					        private readonly IMediaSourceManager _mediaSourceManager;
 | 
				
			||||||
 | 
					        private readonly IFileSystem _fileSystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="TranscodingJobHelper"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobHelpers}"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 | 
				
			||||||
 | 
					        public TranscodingJobHelper(
 | 
				
			||||||
 | 
					            ILogger<TranscodingJobHelper> logger,
 | 
				
			||||||
 | 
					            IMediaSourceManager mediaSourceManager,
 | 
				
			||||||
 | 
					            IFileSystem fileSystem)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					            _mediaSourceManager = mediaSourceManager;
 | 
				
			||||||
 | 
					            _fileSystem = fileSystem;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get transcoding job.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="playSessionId">Playback session id.</param>
 | 
				
			||||||
 | 
					        /// <returns>The transcoding job.</returns>
 | 
				
			||||||
 | 
					        public TranscodingJobDto GetTranscodingJob(string playSessionId)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            lock (_activeTranscodingJobs)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Ping transcoding job.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="playSessionId">Play session id.</param>
 | 
				
			||||||
 | 
					        /// <param name="isUserPaused">Is user paused.</param>
 | 
				
			||||||
 | 
					        /// <exception cref="ArgumentNullException">Play session id is null.</exception>
 | 
				
			||||||
 | 
					        public void PingTranscodingJob(string playSessionId, bool? isUserPaused)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (string.IsNullOrEmpty(playSessionId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                throw new ArgumentNullException(nameof(playSessionId));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            List<TranscodingJobDto> jobs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lock (_activeTranscodingJobs)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // This is really only needed for HLS.
 | 
				
			||||||
 | 
					                // Progressive streams can stop on their own reliably
 | 
				
			||||||
 | 
					                jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var job in jobs)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (isUserPaused.HasValue)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
 | 
				
			||||||
 | 
					                    job.IsUserPaused = isUserPaused.Value;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                PingTimer(job, true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void PingTimer(TranscodingJobDto job, bool isProgressCheckIn)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (job.HasExited)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                job.StopKillTimer();
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var timerDuration = 10000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (job.Type != TranscodingJobType.Progressive)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                timerDuration = 60000;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            job.PingTimeout = timerDuration;
 | 
				
			||||||
 | 
					            job.LastPingDate = DateTime.UtcNow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Don't start the timer for playback checkins with progressive streaming
 | 
				
			||||||
 | 
					            if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                job.StartKillTimer(OnTranscodeKillTimerStopped);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                job.ChangeKillTimerIfStarted();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Called when [transcode kill timer stopped].
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="state">The state.</param>
 | 
				
			||||||
 | 
					        private async void OnTranscodeKillTimerStopped(object state)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var job = (TranscodingJobDto)state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (timeSinceLastPing < job.PingTimeout)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await KillTranscodingJob(job, true, path => true).ConfigureAwait(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Kills the single transcoding job.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="deviceId">The device id.</param>
 | 
				
			||||||
 | 
					        /// <param name="playSessionId">The play session identifier.</param>
 | 
				
			||||||
 | 
					        /// <param name="deleteFiles">The delete files.</param>
 | 
				
			||||||
 | 
					        /// <returns>Task.</returns>
 | 
				
			||||||
 | 
					        public Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return KillTranscodingJobs(
 | 
				
			||||||
 | 
					                j => string.IsNullOrWhiteSpace(playSessionId)
 | 
				
			||||||
 | 
					                    ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
 | 
				
			||||||
 | 
					                    : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Kills the transcoding jobs.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="killJob">The kill job.</param>
 | 
				
			||||||
 | 
					        /// <param name="deleteFiles">The delete files.</param>
 | 
				
			||||||
 | 
					        /// <returns>Task.</returns>
 | 
				
			||||||
 | 
					        private Task KillTranscodingJobs(Func<TranscodingJobDto, bool> killJob, Func<string, bool> deleteFiles)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var jobs = new List<TranscodingJobDto>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lock (_activeTranscodingJobs)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // This is really only needed for HLS.
 | 
				
			||||||
 | 
					                // Progressive streams can stop on their own reliably
 | 
				
			||||||
 | 
					                jobs.AddRange(_activeTranscodingJobs.Where(killJob));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (jobs.Count == 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Task.CompletedTask;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            IEnumerable<Task> GetKillJobs()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var job in jobs)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    yield return KillTranscodingJob(job, false, deleteFiles);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Task.WhenAll(GetKillJobs());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Kills the transcoding job.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="job">The job.</param>
 | 
				
			||||||
 | 
					        /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param>
 | 
				
			||||||
 | 
					        /// <param name="delete">The delete.</param>
 | 
				
			||||||
 | 
					        private async Task KillTranscodingJob(TranscodingJobDto job, bool closeLiveStream, Func<string, bool> delete)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            job.DisposeKillTimer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lock (_activeTranscodingJobs)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _activeTranscodingJobs.Remove(job);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!job.CancellationTokenSource!.IsCancellationRequested)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    job.CancellationTokenSource.Cancel();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lock (_transcodingLocks)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _transcodingLocks.Remove(job.Path!);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lock (job.ProcessLock!)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var process = job.Process;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var hasExited = job.HasExited;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!hasExited)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    try
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        process!.StandardInput.WriteLine("q");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Need to wait because killing is asynchronous
 | 
				
			||||||
 | 
					                        if (!process.WaitForExit(5000))
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            _logger.LogInformation("Killing ffmpeg process for {Path}", job.Path);
 | 
				
			||||||
 | 
					                            process.Kill();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    catch (InvalidOperationException)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (delete(job.Path!))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (Exception ex)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogError(ex, "Error closing live stream for {Path}", job.Path);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (retryCount >= 10)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInformation("Deleting partial stream file(s) {Path}", path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await Task.Delay(delayMs).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (jobType == TranscodingJobType.Progressive)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    DeleteProgressivePartialStreamFiles(path);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    DeleteHlsPartialStreamFiles(path);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (IOException ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Deletes the progressive partial stream files.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="outputFilePath">The output file path.</param>
 | 
				
			||||||
 | 
					        private void DeleteProgressivePartialStreamFiles(string outputFilePath)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (File.Exists(outputFilePath))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _fileSystem.DeleteFile(outputFilePath);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Deletes the HLS partial stream files.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="outputFilePath">The output file path.</param>
 | 
				
			||||||
 | 
					        private void DeleteHlsPartialStreamFiles(string outputFilePath)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var directory = Path.GetDirectoryName(outputFilePath);
 | 
				
			||||||
 | 
					            var name = Path.GetFileNameWithoutExtension(outputFilePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var filesToDelete = _fileSystem.GetFilePaths(directory)
 | 
				
			||||||
 | 
					                .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            List<Exception>? exs = null;
 | 
				
			||||||
 | 
					            foreach (var file in filesToDelete)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogDebug("Deleting HLS file {0}", file);
 | 
				
			||||||
 | 
					                    _fileSystem.DeleteFile(file);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (IOException ex)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    (exs ??= new List<Exception>(4)).Add(ex);
 | 
				
			||||||
 | 
					                    _logger.LogError(ex, "Error deleting HLS file {Path}", file);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (exs != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                throw new AggregateException("Error deleting HLS files", exs);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								Jellyfin.Api/Models/LibraryDtos/LibraryOptionInfoDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Jellyfin.Api/Models/LibraryDtos/LibraryOptionInfoDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					namespace Jellyfin.Api.Models.LibraryDtos
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Library option info dto.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class LibraryOptionInfoDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets name.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? Name { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets a value indicating whether default enabled.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public bool DefaultEnabled { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										34
									
								
								Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Models.LibraryDtos
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Library options result dto.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class LibraryOptionsResultDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the metadata savers.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataSavers", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public LibraryOptionInfoDto[] MetadataSavers { get; set; } = null!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the metadata readers.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataReaders", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public LibraryOptionInfoDto[] MetadataReaders { get; set; } = null!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the subtitle fetchers.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "SubtitleFetchers", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public LibraryOptionInfoDto[] SubtitleFetchers { get; set; } = null!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the type options.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "TypeOptions", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public LibraryTypeOptionsDto[] TypeOptions { get; set; } = null!;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Models.LibraryDtos
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Library type options dto.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class LibraryTypeOptionsDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the type.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? Type { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the metadata fetchers.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataFetchers", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public LibraryOptionInfoDto[] MetadataFetchers { get; set; } = null!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the image fetchers.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "ImageFetchers", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public LibraryOptionInfoDto[] ImageFetchers { get; set; } = null!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the supported image types.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "SupportedImageTypes", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public ImageType[] SupportedImageTypes { get; set; } = null!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the default image options.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "DefaultImageOptions", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public ImageOption[] DefaultImageOptions { get; set; } = null!;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					namespace Jellyfin.Api.Models.LibraryDtos
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Media Update Info Dto.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class MediaUpdateInfoDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets media path.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? Path { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets media update type.
 | 
				
			||||||
 | 
					        /// Created, Modified, Deleted.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? UpdateType { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										256
									
								
								Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,256 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Threading;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Models.PlaybackDtos
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Class TranscodingJob.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class TranscodingJobDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// The process lock.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "CA1051:NoVisibleInstanceFields", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        [SuppressMessage("Microsoft.Performance", "SA1401:PrivateField", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
 | 
					        public readonly object ProcessLock = new object();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Timer lock.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        private readonly object _timerLock = new object();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="TranscodingJobDto"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="logger">Instance of the <see cref="ILogger{TranscodingJobDto}"/> interface.</param>
 | 
				
			||||||
 | 
					        public TranscodingJobDto(ILogger<TranscodingJobDto> logger)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Logger = logger;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the play session identifier.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <value>The play session identifier.</value>
 | 
				
			||||||
 | 
					        public string? PlaySessionId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the live stream identifier.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <value>The live stream identifier.</value>
 | 
				
			||||||
 | 
					        public string? LiveStreamId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets a value indicating whether is live output.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public bool IsLiveOutput { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the path.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <value>The path.</value>
 | 
				
			||||||
 | 
					        public MediaSourceInfo? MediaSource { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets path.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? Path { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the type.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <value>The type.</value>
 | 
				
			||||||
 | 
					        public TranscodingJobType Type { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the process.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <value>The process.</value>
 | 
				
			||||||
 | 
					        public Process? Process { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets logger.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public ILogger<TranscodingJobDto> Logger { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the active request count.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <value>The active request count.</value>
 | 
				
			||||||
 | 
					        public int ActiveRequestCount { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets the kill timer.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <value>The kill timer.</value>
 | 
				
			||||||
 | 
					        private Timer? KillTimer { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets device id.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? DeviceId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets cancellation token source.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public CancellationTokenSource? CancellationTokenSource { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets a value indicating whether has exited.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public bool HasExited { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets a value indicating whether is user paused.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public bool IsUserPaused { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets id.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? Id { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets framerate.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public float? Framerate { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets completion percentage.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public double? CompletionPercentage { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets bytes downloaded.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public long? BytesDownloaded { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets bytes transcoded.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public long? BytesTranscoded { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets bit rate.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public int? BitRate { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets transcoding position ticks.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public long? TranscodingPositionTicks { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets download position ticks.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public long? DownloadPositionTicks { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets transcoding throttler.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public TranscodingThrottler? TranscodingThrottler { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets last ping date.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public DateTime LastPingDate { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets ping timeout.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public int PingTimeout { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Stop kill timer.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public void StopKillTimer()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            lock (_timerLock)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Dispose kill timer.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public void DisposeKillTimer()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            lock (_timerLock)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (KillTimer != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    KillTimer.Dispose();
 | 
				
			||||||
 | 
					                    KillTimer = null;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Start kill timer.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="callback">Callback action.</param>
 | 
				
			||||||
 | 
					        public void StartKillTimer(Action<object> callback)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            StartKillTimer(callback, PingTimeout);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Start kill timer.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="callback">Callback action.</param>
 | 
				
			||||||
 | 
					        /// <param name="intervalMs">Callback interval.</param>
 | 
				
			||||||
 | 
					        public void StartKillTimer(Action<object> callback, int intervalMs)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (HasExited)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lock (_timerLock)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (KillTimer == null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
 | 
				
			||||||
 | 
					                    KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
 | 
				
			||||||
 | 
					                    KillTimer.Change(intervalMs, Timeout.Infinite);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Change kill timer if started.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public void ChangeKillTimerIfStarted()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (HasExited)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lock (_timerLock)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (KillTimer != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var intervalMs = PingTimeout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
 | 
				
			||||||
 | 
					                    KillTimer.Change(intervalMs, Timeout.Infinite);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										212
									
								
								Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,212 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Threading;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Configuration;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Jellyfin.Api.Models.PlaybackDtos
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Transcoding throttler.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class TranscodingThrottler : IDisposable
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly TranscodingJobDto _job;
 | 
				
			||||||
 | 
					        private readonly ILogger<TranscodingThrottler> _logger;
 | 
				
			||||||
 | 
					        private readonly IConfigurationManager _config;
 | 
				
			||||||
 | 
					        private readonly IFileSystem _fileSystem;
 | 
				
			||||||
 | 
					        private Timer? _timer;
 | 
				
			||||||
 | 
					        private bool _isPaused;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Initializes a new instance of the <see cref="TranscodingThrottler"/> class.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="job">Transcoding job dto.</param>
 | 
				
			||||||
 | 
					        /// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
 | 
				
			||||||
 | 
					        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 | 
				
			||||||
 | 
					        public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _job = job;
 | 
				
			||||||
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					            _config = config;
 | 
				
			||||||
 | 
					            _fileSystem = fileSystem;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Start timer.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public void Start()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _timer = new Timer(TimerCallback, null, 5000, 5000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Unpause transcoding.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="Task"/>.</returns>
 | 
				
			||||||
 | 
					        public async Task UnpauseTranscoding()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (_isPaused)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogDebug("Sending resume command to ffmpeg");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    await _job.Process!.StandardInput.WriteLineAsync().ConfigureAwait(false);
 | 
				
			||||||
 | 
					                    _isPaused = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (Exception ex)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogError(ex, "Error resuming transcoding");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Stop throttler.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns>A <see cref="Task"/>.</returns>
 | 
				
			||||||
 | 
					        public async Task Stop()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            DisposeTimer();
 | 
				
			||||||
 | 
					            await UnpauseTranscoding().ConfigureAwait(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Dispose throttler.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public void Dispose()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Dispose(true);
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Dispose throttler.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="disposing">Disposing.</param>
 | 
				
			||||||
 | 
					        protected virtual void Dispose(bool disposing)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (disposing)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                DisposeTimer();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private EncodingOptions GetOptions()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return _config.GetConfiguration<EncodingOptions>("encoding");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async void TimerCallback(object state)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (_job.HasExited)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                DisposeTimer();
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var options = GetOptions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await PauseTranscoding().ConfigureAwait(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await UnpauseTranscoding().ConfigureAwait(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task PauseTranscoding()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!_isPaused)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogDebug("Sending pause command to ffmpeg");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    await _job.Process!.StandardInput.WriteAsync("c").ConfigureAwait(false);
 | 
				
			||||||
 | 
					                    _isPaused = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (Exception ex)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogError(ex, "Error pausing transcoding");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var bytesDownloaded = job.BytesDownloaded ?? 0;
 | 
				
			||||||
 | 
					            var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
 | 
				
			||||||
 | 
					            var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var path = job.Path;
 | 
				
			||||||
 | 
					            var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // HLS - time-based consideration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var targetGap = gapLengthInTicks;
 | 
				
			||||||
 | 
					                var gap = transcodingPositionTicks - downloadPositionTicks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (gap < targetGap)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogDebug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogDebug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Progressive Streaming - byte-based consideration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Estimate the bytes the transcoder should be ahead
 | 
				
			||||||
 | 
					                    double gapFactor = gapLengthInTicks;
 | 
				
			||||||
 | 
					                    gapFactor /= transcodingPositionTicks;
 | 
				
			||||||
 | 
					                    var targetGap = bytesTranscoded * gapFactor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var gap = bytesTranscoded - bytesDownloaded;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (gap < targetGap)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogDebug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
 | 
				
			||||||
 | 
					                        return false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _logger.LogDebug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (Exception ex)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogError(ex, "Error getting output size");
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogDebug("No throttle data for " + path);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void DisposeTimer()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (_timer != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _timer.Dispose();
 | 
				
			||||||
 | 
					                _timer = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Jellyfin.Api/Models/UserViewDtos/SpecialViewOptionDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					namespace Jellyfin.Api.Models.UserViewDtos
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Special view option dto.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class SpecialViewOptionDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets view option name.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? Name { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets view option id.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string? Id { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -6,6 +6,7 @@ using System.Reflection;
 | 
				
			|||||||
using Jellyfin.Api;
 | 
					using Jellyfin.Api;
 | 
				
			||||||
using Jellyfin.Api.Auth;
 | 
					using Jellyfin.Api.Auth;
 | 
				
			||||||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
 | 
					using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Auth.DownloadPolicy;
 | 
				
			||||||
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
 | 
					using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
 | 
				
			||||||
using Jellyfin.Api.Auth.IgnoreSchedulePolicy;
 | 
					using Jellyfin.Api.Auth.IgnoreSchedulePolicy;
 | 
				
			||||||
using Jellyfin.Api.Auth.LocalAccessPolicy;
 | 
					using Jellyfin.Api.Auth.LocalAccessPolicy;
 | 
				
			||||||
@ -39,6 +40,7 @@ namespace Jellyfin.Server.Extensions
 | 
				
			|||||||
        public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection)
 | 
					        public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>();
 | 
					            serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>();
 | 
				
			||||||
 | 
					            serviceCollection.AddSingleton<IAuthorizationHandler, DownloadHandler>();
 | 
				
			||||||
            serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>();
 | 
					            serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>();
 | 
				
			||||||
            serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreScheduleHandler>();
 | 
					            serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreScheduleHandler>();
 | 
				
			||||||
            serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>();
 | 
					            serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>();
 | 
				
			||||||
@ -52,6 +54,13 @@ namespace Jellyfin.Server.Extensions
 | 
				
			|||||||
                        policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
 | 
					                        policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
 | 
				
			||||||
                        policy.AddRequirements(new DefaultAuthorizationRequirement());
 | 
					                        policy.AddRequirements(new DefaultAuthorizationRequirement());
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					                options.AddPolicy(
 | 
				
			||||||
 | 
					                    Policies.Download,
 | 
				
			||||||
 | 
					                    policy =>
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
 | 
				
			||||||
 | 
					                        policy.AddRequirements(new DownloadRequirement());
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
                options.AddPolicy(
 | 
					                options.AddPolicy(
 | 
				
			||||||
                    Policies.FirstTimeSetupOrElevated,
 | 
					                    Policies.FirstTimeSetupOrElevated,
 | 
				
			||||||
                    policy =>
 | 
					                    policy =>
 | 
				
			||||||
@ -186,7 +195,7 @@ namespace Jellyfin.Server.Extensions
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                // Order actions by route path, then by http method.
 | 
					                // Order actions by route path, then by http method.
 | 
				
			||||||
                c.OrderActionsBy(description =>
 | 
					                c.OrderActionsBy(description =>
 | 
				
			||||||
                    $"{description.ActionDescriptor.RouteValues["controller"]}_{description.HttpMethod}");
 | 
					                    $"{description.ActionDescriptor.RouteValues["controller"]}_{description.RelativePath}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Use method name as operationId
 | 
					                // Use method name as operationId
 | 
				
			||||||
                c.CustomOperationIds(description =>
 | 
					                c.CustomOperationIds(description =>
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -14,6 +14,10 @@
 | 
				
			|||||||
    <Compile Include="..\SharedVersion.cs" />
 | 
					    <Compile Include="..\SharedVersion.cs" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <Folder Include="Library" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <PropertyGroup>
 | 
					  <PropertyGroup>
 | 
				
			||||||
    <TargetFramework>netstandard2.1</TargetFramework>
 | 
					    <TargetFramework>netstandard2.1</TargetFramework>
 | 
				
			||||||
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
 | 
					    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,105 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Collections;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Collections;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.Movies
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    [Route("/Collections", "POST", Summary = "Creates a new collection")]
 | 
					 | 
				
			||||||
    public class CreateCollection : IReturn<CollectionCreationResult>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsLocked", Description = "Whether or not to lock the new collection.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public bool IsLocked { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Name", Description = "The name of the new collection.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string Name { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ParentId", Description = "Optional - create the collection within a specific folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string ParentId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Ids", Description = "Item Ids to add to the collection", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string Ids { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Collections/{Id}/Items", "POST", Summary = "Adds items to a collection")]
 | 
					 | 
				
			||||||
    public class AddToCollection : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string Ids { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Collections/{Id}/Items", "DELETE", Summary = "Removes items from a collection")]
 | 
					 | 
				
			||||||
    public class RemoveFromCollection : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string Ids { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class CollectionService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly ICollectionManager _collectionManager;
 | 
					 | 
				
			||||||
        private readonly IDtoService _dtoService;
 | 
					 | 
				
			||||||
        private readonly IAuthorizationContext _authContext;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public CollectionService(
 | 
					 | 
				
			||||||
            ILogger<CollectionService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            ICollectionManager collectionManager,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            IAuthorizationContext authContext)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _collectionManager = collectionManager;
 | 
					 | 
				
			||||||
            _dtoService = dtoService;
 | 
					 | 
				
			||||||
            _authContext = authContext;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Post(CreateCollection request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var userId = _authContext.GetAuthorizationInfo(Request).UserId;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var parentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = _collectionManager.CreateCollection(new CollectionCreationOptions
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                IsLocked = request.IsLocked,
 | 
					 | 
				
			||||||
                Name = request.Name,
 | 
					 | 
				
			||||||
                ParentId = parentId,
 | 
					 | 
				
			||||||
                ItemIdList = SplitValue(request.Ids, ','),
 | 
					 | 
				
			||||||
                UserIds = new[] { userId }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dto = _dtoService.GetBaseItemDto(item, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new CollectionCreationResult
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Id = dto.Id
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Post(AddToCollection request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _collectionManager.AddToCollection(new Guid(request.Id), SplitValue(request.Ids, ','));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Delete(RemoveFromCollection request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _collectionManager.RemoveFromCollection(new Guid(request.Id), SplitValue(request.Ids, ','));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -20,50 +20,6 @@ using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Api.Movies
 | 
					namespace MediaBrowser.Api.Movies
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")]
 | 
					 | 
				
			||||||
    public class GetMovieRecommendations : IReturn<RecommendationDto[]>, IHasDtoOptions
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "CategoryLimit", Description = "The max number of categories to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int CategoryLimit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ItemLimit", Description = "The max number of items to return per category", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int ItemLimit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Specify this to localize the search to a specific item or folder. Omit to use the root.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The parent id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string ParentId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableImages { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableUserData { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? ImageTypeLimit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string EnableImageTypes { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public GetMovieRecommendations()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            CategoryLimit = 5;
 | 
					 | 
				
			||||||
            ItemLimit = 8;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public string Fields { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// Class MoviesService
 | 
					    /// Class MoviesService
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
@ -74,9 +30,7 @@ namespace MediaBrowser.Api.Movies
 | 
				
			|||||||
        /// The _user manager
 | 
					        /// The _user manager
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        private readonly IUserManager _userManager;
 | 
					        private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        private readonly IDtoService _dtoService;
 | 
					        private readonly IDtoService _dtoService;
 | 
				
			||||||
        private readonly IAuthorizationContext _authContext;
 | 
					        private readonly IAuthorizationContext _authContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -99,17 +53,6 @@ namespace MediaBrowser.Api.Movies
 | 
				
			|||||||
            _authContext = authContext;
 | 
					            _authContext = authContext;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public object Get(GetMovieRecommendations request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = GetRecommendationCategories(user, request.ParentId, request.CategoryLimit, request.ItemLimit, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
 | 
					        public QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
 | 
					            var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
 | 
				
			||||||
@ -149,270 +92,5 @@ namespace MediaBrowser.Api.Movies
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return result;
 | 
					            return result;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, string parentId, int categoryLimit, int itemLimit, DtoOptions dtoOptions)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var categories = new List<RecommendationDto>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var query = new InternalItemsQuery(user)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                IncludeItemTypes = new[]
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    typeof(Movie).Name,
 | 
					 | 
				
			||||||
                    //typeof(Trailer).Name,
 | 
					 | 
				
			||||||
                    //typeof(LiveTvProgram).Name
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                // IsMovie = true
 | 
					 | 
				
			||||||
                OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
 | 
					 | 
				
			||||||
                Limit = 7,
 | 
					 | 
				
			||||||
                ParentId = parentIdGuid,
 | 
					 | 
				
			||||||
                Recursive = true,
 | 
					 | 
				
			||||||
                IsPlayed = true,
 | 
					 | 
				
			||||||
                DtoOptions = dtoOptions
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var recentlyPlayedMovies = _libraryManager.GetItemList(query);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var itemTypes = new List<string> { typeof(Movie).Name };
 | 
					 | 
				
			||||||
            if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                itemTypes.Add(typeof(Trailer).Name);
 | 
					 | 
				
			||||||
                itemTypes.Add(typeof(LiveTvProgram).Name);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                IncludeItemTypes = itemTypes.ToArray(),
 | 
					 | 
				
			||||||
                IsMovie = true,
 | 
					 | 
				
			||||||
                OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
 | 
					 | 
				
			||||||
                Limit = 10,
 | 
					 | 
				
			||||||
                IsFavoriteOrLiked = true,
 | 
					 | 
				
			||||||
                ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(),
 | 
					 | 
				
			||||||
                EnableGroupByMetadataKey = true,
 | 
					 | 
				
			||||||
                ParentId = parentIdGuid,
 | 
					 | 
				
			||||||
                Recursive = true,
 | 
					 | 
				
			||||||
                DtoOptions = dtoOptions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList();
 | 
					 | 
				
			||||||
            // Get recently played directors
 | 
					 | 
				
			||||||
            var recentDirectors = GetDirectors(mostRecentMovies)
 | 
					 | 
				
			||||||
                .ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Get recently played actors
 | 
					 | 
				
			||||||
            var recentActors = GetActors(mostRecentMovies)
 | 
					 | 
				
			||||||
                .ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
 | 
					 | 
				
			||||||
            var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
 | 
					 | 
				
			||||||
            var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var categoryTypes = new List<IEnumerator<RecommendationDto>>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // Give this extra weight
 | 
					 | 
				
			||||||
                similarToRecentlyPlayed,
 | 
					 | 
				
			||||||
                similarToRecentlyPlayed,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Give this extra weight
 | 
					 | 
				
			||||||
                similarToLiked,
 | 
					 | 
				
			||||||
                similarToLiked,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                hasDirectorFromRecentlyPlayed,
 | 
					 | 
				
			||||||
                hasActorFromRecentlyPlayed
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            while (categories.Count < categoryLimit)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var allEmpty = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                foreach (var category in categoryTypes)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    if (category.MoveNext())
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        categories.Add(category.Current);
 | 
					 | 
				
			||||||
                        allEmpty = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        if (categories.Count >= categoryLimit)
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (allEmpty)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return categories.OrderBy(i => i.RecommendationType);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private IEnumerable<RecommendationDto> GetWithDirector(
 | 
					 | 
				
			||||||
            User user,
 | 
					 | 
				
			||||||
            IEnumerable<string> names,
 | 
					 | 
				
			||||||
            int itemLimit,
 | 
					 | 
				
			||||||
            DtoOptions dtoOptions,
 | 
					 | 
				
			||||||
            RecommendationType type)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var itemTypes = new List<string> { typeof(Movie).Name };
 | 
					 | 
				
			||||||
            if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                itemTypes.Add(typeof(Trailer).Name);
 | 
					 | 
				
			||||||
                itemTypes.Add(typeof(LiveTvProgram).Name);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            foreach (var name in names)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Person = name,
 | 
					 | 
				
			||||||
                    // Account for duplicates by imdb id, since the database doesn't support this yet
 | 
					 | 
				
			||||||
                    Limit = itemLimit + 2,
 | 
					 | 
				
			||||||
                    PersonTypes = new[] { PersonType.Director },
 | 
					 | 
				
			||||||
                    IncludeItemTypes = itemTypes.ToArray(),
 | 
					 | 
				
			||||||
                    IsMovie = true,
 | 
					 | 
				
			||||||
                    EnableGroupByMetadataKey = true,
 | 
					 | 
				
			||||||
                    DtoOptions = dtoOptions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                }).GroupBy(i => i.GetProviderId(MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
 | 
					 | 
				
			||||||
                .Select(x => x.First())
 | 
					 | 
				
			||||||
                .Take(itemLimit)
 | 
					 | 
				
			||||||
                .ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (items.Count > 0)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    yield return new RecommendationDto
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        BaselineItemName = name,
 | 
					 | 
				
			||||||
                        CategoryId = name.GetMD5(),
 | 
					 | 
				
			||||||
                        RecommendationType = type,
 | 
					 | 
				
			||||||
                        Items = returnItems
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private IEnumerable<RecommendationDto> GetWithActor(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var itemTypes = new List<string> { typeof(Movie).Name };
 | 
					 | 
				
			||||||
            if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                itemTypes.Add(typeof(Trailer).Name);
 | 
					 | 
				
			||||||
                itemTypes.Add(typeof(LiveTvProgram).Name);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            foreach (var name in names)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Person = name,
 | 
					 | 
				
			||||||
                    // Account for duplicates by imdb id, since the database doesn't support this yet
 | 
					 | 
				
			||||||
                    Limit = itemLimit + 2,
 | 
					 | 
				
			||||||
                    IncludeItemTypes = itemTypes.ToArray(),
 | 
					 | 
				
			||||||
                    IsMovie = true,
 | 
					 | 
				
			||||||
                    EnableGroupByMetadataKey = true,
 | 
					 | 
				
			||||||
                    DtoOptions = dtoOptions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                }).GroupBy(i => i.GetProviderId(MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture))
 | 
					 | 
				
			||||||
                .Select(x => x.First())
 | 
					 | 
				
			||||||
                .Take(itemLimit)
 | 
					 | 
				
			||||||
                .ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (items.Count > 0)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    yield return new RecommendationDto
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        BaselineItemName = name,
 | 
					 | 
				
			||||||
                        CategoryId = name.GetMD5(),
 | 
					 | 
				
			||||||
                        RecommendationType = type,
 | 
					 | 
				
			||||||
                        Items = returnItems
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var itemTypes = new List<string> { typeof(Movie).Name };
 | 
					 | 
				
			||||||
            if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                itemTypes.Add(typeof(Trailer).Name);
 | 
					 | 
				
			||||||
                itemTypes.Add(typeof(LiveTvProgram).Name);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            foreach (var item in baselineItems)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Limit = itemLimit,
 | 
					 | 
				
			||||||
                    IncludeItemTypes = itemTypes.ToArray(),
 | 
					 | 
				
			||||||
                    IsMovie = true,
 | 
					 | 
				
			||||||
                    SimilarTo = item,
 | 
					 | 
				
			||||||
                    EnableGroupByMetadataKey = true,
 | 
					 | 
				
			||||||
                    DtoOptions = dtoOptions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (similar.Count > 0)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    yield return new RecommendationDto
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        BaselineItemName = item.Name,
 | 
					 | 
				
			||||||
                        CategoryId = item.Id,
 | 
					 | 
				
			||||||
                        RecommendationType = type,
 | 
					 | 
				
			||||||
                        Items = returnItems
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private IEnumerable<string> GetActors(List<BaseItem> items)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var people = _libraryManager.GetPeople(new InternalPeopleQuery
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ExcludePersonTypes = new[]
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    PersonType.Director
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                MaxListOrder = 3
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var itemIds = items.Select(i => i.Id).ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return people
 | 
					 | 
				
			||||||
                .Where(i => itemIds.Contains(i.ItemId))
 | 
					 | 
				
			||||||
                .Select(i => i.Name)
 | 
					 | 
				
			||||||
                .DistinctNames();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private IEnumerable<string> GetDirectors(List<BaseItem> items)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var people = _libraryManager.GetPeople(new InternalPeopleQuery
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                PersonTypes = new[]
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    PersonType.Director
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var itemIds = items.Select(i => i.Id).ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return people
 | 
					 | 
				
			||||||
                .Where(i => itemIds.Contains(i.ItemId))
 | 
					 | 
				
			||||||
                .Select(i => i.Name)
 | 
					 | 
				
			||||||
                .DistinctNames();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,132 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities.Audio;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Persistence;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.Music
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
 | 
					 | 
				
			||||||
    public class GetSimilarAlbums : BaseGetSimilarItemsFromItem
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
 | 
					 | 
				
			||||||
    public class GetSimilarArtists : BaseGetSimilarItemsFromItem
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class AlbumsService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The _user manager
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        private readonly IUserManager _userManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The _user data repository
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        private readonly IUserDataManager _userDataRepository;
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The _library manager
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					 | 
				
			||||||
        private readonly IItemRepository _itemRepo;
 | 
					 | 
				
			||||||
        private readonly IDtoService _dtoService;
 | 
					 | 
				
			||||||
        private readonly IAuthorizationContext _authContext;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public AlbumsService(
 | 
					 | 
				
			||||||
            ILogger<AlbumsService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            IUserDataManager userDataRepository,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            IItemRepository itemRepo,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            IAuthorizationContext authContext)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _userManager = userManager;
 | 
					 | 
				
			||||||
            _userDataRepository = userDataRepository;
 | 
					 | 
				
			||||||
            _libraryManager = libraryManager;
 | 
					 | 
				
			||||||
            _itemRepo = itemRepo;
 | 
					 | 
				
			||||||
            _dtoService = dtoService;
 | 
					 | 
				
			||||||
            _authContext = authContext;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetSimilarArtists request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = SimilarItemsHelper.GetSimilarItemsResult(
 | 
					 | 
				
			||||||
                dtoOptions, 
 | 
					 | 
				
			||||||
                _userManager,
 | 
					 | 
				
			||||||
                _itemRepo,
 | 
					 | 
				
			||||||
                _libraryManager,
 | 
					 | 
				
			||||||
                _userDataRepository,
 | 
					 | 
				
			||||||
                _dtoService,
 | 
					 | 
				
			||||||
                request, new[] { typeof(MusicArtist) },
 | 
					 | 
				
			||||||
                SimilarItemsHelper.GetSimiliarityScore);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetSimilarAlbums request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = SimilarItemsHelper.GetSimilarItemsResult(
 | 
					 | 
				
			||||||
                dtoOptions, 
 | 
					 | 
				
			||||||
                _userManager,
 | 
					 | 
				
			||||||
                _itemRepo,
 | 
					 | 
				
			||||||
                _libraryManager,
 | 
					 | 
				
			||||||
                _userDataRepository,
 | 
					 | 
				
			||||||
                _dtoService,
 | 
					 | 
				
			||||||
                request, new[] { typeof(MusicAlbum) },
 | 
					 | 
				
			||||||
                GetAlbumSimilarityScore);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the album similarity score.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="item1">The item1.</param>
 | 
					 | 
				
			||||||
        /// <param name="item1People">The item1 people.</param>
 | 
					 | 
				
			||||||
        /// <param name="allPeople">All people.</param>
 | 
					 | 
				
			||||||
        /// <param name="item2">The item2.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Int32.</returns>
 | 
					 | 
				
			||||||
        private int GetAlbumSimilarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var points = SimilarItemsHelper.GetSimiliarityScore(item1, item1People, allPeople, item2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var album1 = (MusicAlbum)item1;
 | 
					 | 
				
			||||||
            var album2 = (MusicAlbum)item2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var artists1 = album1
 | 
					 | 
				
			||||||
                .GetAllArtists()
 | 
					 | 
				
			||||||
                .DistinctNames()
 | 
					 | 
				
			||||||
                .ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var artists2 = new HashSet<string>(
 | 
					 | 
				
			||||||
                album2.GetAllArtists().DistinctNames(),
 | 
					 | 
				
			||||||
                StringComparer.OrdinalIgnoreCase);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return points + artists1.Where(artists2.Contains).Sum(i => 5);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,197 +0,0 @@
 | 
				
			|||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using Jellyfin.Data.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Playlists;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Querying;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.Music
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    [Route("/Songs/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given song")]
 | 
					 | 
				
			||||||
    public class GetInstantMixFromSong : BaseGetSimilarItemsFromItem
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Albums/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given album")]
 | 
					 | 
				
			||||||
    public class GetInstantMixFromAlbum : BaseGetSimilarItemsFromItem
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Playlists/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given playlist")]
 | 
					 | 
				
			||||||
    public class GetInstantMixFromPlaylist : BaseGetSimilarItemsFromItem
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/MusicGenres/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")]
 | 
					 | 
				
			||||||
    public class GetInstantMixFromMusicGenre : BaseGetSimilarItems
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Name { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Artists/InstantMix", "GET", Summary = "Creates an instant playlist based on a given artist")]
 | 
					 | 
				
			||||||
    public class GetInstantMixFromArtistId : BaseGetSimilarItems
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "The artist Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/MusicGenres/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")]
 | 
					 | 
				
			||||||
    public class GetInstantMixFromMusicGenreId : BaseGetSimilarItems
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "The genre Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Items/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given item")]
 | 
					 | 
				
			||||||
    public class GetInstantMixFromItem : BaseGetSimilarItemsFromItem
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class InstantMixService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly IUserManager _userManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private readonly IDtoService _dtoService;
 | 
					 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					 | 
				
			||||||
        private readonly IMusicManager _musicManager;
 | 
					 | 
				
			||||||
        private readonly IAuthorizationContext _authContext;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public InstantMixService(
 | 
					 | 
				
			||||||
            ILogger<InstantMixService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            IMusicManager musicManager,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            IAuthorizationContext authContext)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _userManager = userManager;
 | 
					 | 
				
			||||||
            _dtoService = dtoService;
 | 
					 | 
				
			||||||
            _musicManager = musicManager;
 | 
					 | 
				
			||||||
            _libraryManager = libraryManager;
 | 
					 | 
				
			||||||
            _authContext = authContext;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetInstantMixFromItem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var item = _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GetResult(items, user, request, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetInstantMixFromArtistId request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var item = _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GetResult(items, user, request, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetInstantMixFromMusicGenreId request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var item = _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GetResult(items, user, request, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetInstantMixFromSong request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var item = _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GetResult(items, user, request, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetInstantMixFromAlbum request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var album = _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GetResult(items, user, request, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetInstantMixFromPlaylist request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GetResult(items, user, request, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetInstantMixFromMusicGenre request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var items = _musicManager.GetInstantMixFromGenres(new[] { request.Name }, user, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return GetResult(items, user, request, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private object GetResult(List<BaseItem> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var list = items;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = new QueryResult<BaseItemDto>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                TotalRecordCount = list.Count
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (request.Limit.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                list = list.Take(request.Limit.Value).ToList();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            result.Items = returnList;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return result;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -28,7 +28,6 @@ using Microsoft.Extensions.Logging;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Api.Playback
 | 
					namespace MediaBrowser.Api.Playback
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
 | 
					 | 
				
			||||||
    public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
 | 
					    public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
				
			||||||
@ -38,24 +37,20 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
        public Guid UserId { get; set; }
 | 
					        public Guid UserId { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
 | 
					 | 
				
			||||||
    public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
 | 
					    public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")]
 | 
					 | 
				
			||||||
    public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
 | 
					    public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")]
 | 
					 | 
				
			||||||
    public class CloseMediaSource : IReturnVoid
 | 
					    public class CloseMediaSource : IReturnVoid
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					        [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
				
			||||||
        public string LiveStreamId { get; set; }
 | 
					        public string LiveStreamId { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Route("/Playback/BitrateTest", "GET")]
 | 
					 | 
				
			||||||
    public class GetBitrateTestBytes
 | 
					    public class GetBitrateTestBytes
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					        [ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,234 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using MediaBrowser.Common.Extensions;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Tasks;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.ScheduledTasks
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetScheduledTask
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")]
 | 
					 | 
				
			||||||
    public class GetScheduledTask : IReturn<TaskInfo>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetScheduledTasks
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")]
 | 
					 | 
				
			||||||
    public class GetScheduledTasks : IReturn<TaskInfo[]>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? IsHidden { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsEnabled", Description = "Optional filter tasks that are enabled, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? IsEnabled { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class StartScheduledTask
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")]
 | 
					 | 
				
			||||||
    public class StartScheduledTask : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class StopScheduledTask
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")]
 | 
					 | 
				
			||||||
    public class StopScheduledTask : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class UpdateScheduledTaskTriggers
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")]
 | 
					 | 
				
			||||||
    public class UpdateScheduledTaskTriggers : List<TaskTriggerInfo>, IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the task id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The task id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class ScheduledTasksService
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Authenticated(Roles = "Admin")]
 | 
					 | 
				
			||||||
    public class ScheduledTaskService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The task manager.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        private readonly ITaskManager _taskManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Initializes a new instance of the <see cref="ScheduledTaskService" /> class.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="taskManager">The task manager.</param>
 | 
					 | 
				
			||||||
        /// <exception cref="ArgumentNullException">taskManager</exception>
 | 
					 | 
				
			||||||
        public ScheduledTaskService(
 | 
					 | 
				
			||||||
            ILogger<ScheduledTaskService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            ITaskManager taskManager)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _taskManager = taskManager;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>IEnumerable{TaskInfo}.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetScheduledTasks request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            IEnumerable<IScheduledTaskWorker> result = _taskManager.ScheduledTasks
 | 
					 | 
				
			||||||
                .OrderBy(i => i.Name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (request.IsHidden.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var val = request.IsHidden.Value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                result = result.Where(i =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var isHidden = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        isHidden = configurableTask.IsHidden;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return isHidden == val;
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (request.IsEnabled.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var val = request.IsEnabled.Value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                result = result.Where(i =>
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var isEnabled = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        isEnabled = configurableTask.IsEnabled;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return isEnabled == val;
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var infos = result
 | 
					 | 
				
			||||||
                .Select(ScheduledTaskHelpers.GetTaskInfo)
 | 
					 | 
				
			||||||
                .ToArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(infos);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>IEnumerable{TaskInfo}.</returns>
 | 
					 | 
				
			||||||
        /// <exception cref="ResourceNotFoundException">Task not found</exception>
 | 
					 | 
				
			||||||
        public object Get(GetScheduledTask request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (task == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ResourceNotFoundException("Task not found");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = ScheduledTaskHelpers.GetTaskInfo(task);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <exception cref="ResourceNotFoundException">Task not found</exception>
 | 
					 | 
				
			||||||
        public void Post(StartScheduledTask request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (task == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ResourceNotFoundException("Task not found");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            _taskManager.Execute(task, new TaskOptions());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <exception cref="ResourceNotFoundException">Task not found</exception>
 | 
					 | 
				
			||||||
        public void Delete(StopScheduledTask request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (task == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ResourceNotFoundException("Task not found");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            _taskManager.Cancel(task);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <exception cref="ResourceNotFoundException">Task not found</exception>
 | 
					 | 
				
			||||||
        public void Post(UpdateScheduledTaskTriggers request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // We need to parse this manually because we told service stack not to with IRequiresRequestStream
 | 
					 | 
				
			||||||
            // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
 | 
					 | 
				
			||||||
            var id = GetPathValue(1).ToString();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.Ordinal));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (task == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ResourceNotFoundException("Task not found");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            task.Triggers = request.ToArray();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,226 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using System.Threading;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
using MediaBrowser.Common.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Common.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.IO;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.System;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.System
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetSystemInfo
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/System/Info", "GET", Summary = "Gets information about the server")]
 | 
					 | 
				
			||||||
    [Authenticated(EscapeParentalControl = true, AllowBeforeStartupWizard = true)]
 | 
					 | 
				
			||||||
    public class GetSystemInfo : IReturn<SystemInfo>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")]
 | 
					 | 
				
			||||||
    public class GetPublicSystemInfo : IReturn<PublicSystemInfo>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/System/Ping", "POST")]
 | 
					 | 
				
			||||||
    [Route("/System/Ping", "GET")]
 | 
					 | 
				
			||||||
    public class PingSystem : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class RestartApplication
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")]
 | 
					 | 
				
			||||||
    [Authenticated(Roles = "Admin", AllowLocal = true)]
 | 
					 | 
				
			||||||
    public class RestartApplication
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// This is currently not authenticated because the uninstaller needs to be able to shutdown the server.
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")]
 | 
					 | 
				
			||||||
    [Authenticated(Roles = "Admin", AllowLocal = true)]
 | 
					 | 
				
			||||||
    public class ShutdownApplication
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")]
 | 
					 | 
				
			||||||
    [Authenticated(Roles = "Admin")]
 | 
					 | 
				
			||||||
    public class GetServerLogs : IReturn<LogFile[]>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/System/Endpoint", "GET", Summary = "Gets information about the request endpoint")]
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class GetEndpointInfo : IReturn<EndPointInfo>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public string Endpoint { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")]
 | 
					 | 
				
			||||||
    [Authenticated(Roles = "Admin")]
 | 
					 | 
				
			||||||
    public class GetLogFile
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string Name { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/System/WakeOnLanInfo", "GET", Summary = "Gets wake on lan information")]
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class GetWakeOnLanInfo : IReturn<WakeOnLanInfo[]>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class SystemInfoService
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    public class SystemService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The _app host
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        private readonly IServerApplicationHost _appHost;
 | 
					 | 
				
			||||||
        private readonly IApplicationPaths _appPaths;
 | 
					 | 
				
			||||||
        private readonly IFileSystem _fileSystem;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private readonly INetworkManager _network;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Initializes a new instance of the <see cref="SystemService" /> class.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="appHost">The app host.</param>
 | 
					 | 
				
			||||||
        /// <param name="fileSystem">The file system.</param>
 | 
					 | 
				
			||||||
        /// <exception cref="ArgumentNullException">jsonSerializer</exception>
 | 
					 | 
				
			||||||
        public SystemService(
 | 
					 | 
				
			||||||
            ILogger<SystemService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IServerApplicationHost appHost,
 | 
					 | 
				
			||||||
            IFileSystem fileSystem,
 | 
					 | 
				
			||||||
            INetworkManager network)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _appPaths = serverConfigurationManager.ApplicationPaths;
 | 
					 | 
				
			||||||
            _appHost = appHost;
 | 
					 | 
				
			||||||
            _fileSystem = fileSystem;
 | 
					 | 
				
			||||||
            _network = network;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Post(PingSystem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _appHost.Name;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetWakeOnLanInfo request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = _appHost.GetWakeOnLanInfo();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetServerLogs request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            IEnumerable<FileSystemMetadata> files;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath, new[] { ".txt", ".log" }, true, false);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (IOException ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Logger.LogError(ex, "Error getting logs");
 | 
					 | 
				
			||||||
                files = Enumerable.Empty<FileSystemMetadata>();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = files.Select(i => new LogFile
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                DateCreated = _fileSystem.GetCreationTimeUtc(i),
 | 
					 | 
				
			||||||
                DateModified = _fileSystem.GetLastWriteTimeUtc(i),
 | 
					 | 
				
			||||||
                Name = i.Name,
 | 
					 | 
				
			||||||
                Size = i.Length
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            }).OrderByDescending(i => i.DateModified)
 | 
					 | 
				
			||||||
                .ThenByDescending(i => i.DateCreated)
 | 
					 | 
				
			||||||
                .ThenBy(i => i.Name)
 | 
					 | 
				
			||||||
                .ToArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<object> Get(GetLogFile request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
 | 
					 | 
				
			||||||
                .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // For older files, assume fully static
 | 
					 | 
				
			||||||
            var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ResultFactory.GetStaticFileResult(Request, file.FullName, fileShare);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public async Task<object> Get(GetSystemInfo request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = await _appHost.GetSystemInfo(CancellationToken.None).ConfigureAwait(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public async Task<object> Get(GetPublicSystemInfo request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = await _appHost.GetPublicSystemInfo(CancellationToken.None).ConfigureAwait(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public void Post(RestartApplication request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _appHost.Restart();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public void Post(ShutdownApplication request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Task.Run(async () =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                await Task.Delay(100).ConfigureAwait(false);
 | 
					 | 
				
			||||||
                await _appHost.Shutdown().ConfigureAwait(false);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetEndpointInfo request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return ToOptimizedResult(new EndPointInfo
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                IsLocal = Request.IsLocal,
 | 
					 | 
				
			||||||
                IsInNetwork = _network.IsInLocalNetwork(request.Endpoint ?? Request.RemoteIp)
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,498 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Globalization;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using MediaBrowser.Common.Extensions;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities.TV;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.TV;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Querying;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetNextUpEpisodes
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Shows/NextUp", "GET", Summary = "Gets a list of next up episodes")]
 | 
					 | 
				
			||||||
    public class GetNextUpEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Skips over a given number of items within the results. Use for paging.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The start index.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? StartIndex { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The maximum number of items to return
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The limit.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? Limit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Fields to return within the items, in addition to basic information
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The fields.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string Fields { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "SeriesId", Description = "Optional. Filter by series id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string SeriesId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Specify this to localize the search to a specific item or folder. Omit to use the root.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The parent id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string ParentId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableImages { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? ImageTypeLimit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string EnableImageTypes { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableUserData { get; set; }
 | 
					 | 
				
			||||||
        public bool EnableTotalRecordCount { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public GetNextUpEpisodes()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            EnableTotalRecordCount = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")]
 | 
					 | 
				
			||||||
    public class GetUpcomingEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Skips over a given number of items within the results. Use for paging.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The start index.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? StartIndex { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The maximum number of items to return
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The limit.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? Limit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Fields to return within the items, in addition to basic information
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The fields.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string Fields { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Specify this to localize the search to a specific item or folder. Omit to use the root.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The parent id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string ParentId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableImages { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? ImageTypeLimit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string EnableImageTypes { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableUserData { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Shows/{Id}/Episodes", "GET", Summary = "Gets episodes for a tv season")]
 | 
					 | 
				
			||||||
    public class GetEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasItemFields, IHasDtoOptions
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Fields to return within the items, in addition to basic information
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The fields.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string Fields { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Season", Description = "Optional filter by season number.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? Season { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "SeasonId", Description = "Optional. Filter by season id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string SeasonId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? IsMissing { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string AdjacentTo { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "StartItemId", Description = "Optional. Skip through the list until a given item is found.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string StartItemId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Skips over a given number of items within the results. Use for paging.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The start index.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? StartIndex { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The maximum number of items to return
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The limit.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? Limit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableImages { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? ImageTypeLimit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string EnableImageTypes { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableUserData { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string SortBy { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public SortOrder? SortOrder { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")]
 | 
					 | 
				
			||||||
    public class GetSeasons : IReturn<QueryResult<BaseItemDto>>, IHasItemFields, IHasDtoOptions
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Fields to return within the items, in addition to basic information
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The fields.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string Fields { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsSpecialSeason", Description = "Optional. Filter by special season.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? IsSpecialSeason { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? IsMissing { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string AdjacentTo { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableImages { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? ImageTypeLimit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string EnableImageTypes { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableUserData { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class TvShowsService
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class TvShowsService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The _user manager
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        private readonly IUserManager _userManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// The _library manager
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private readonly IDtoService _dtoService;
 | 
					 | 
				
			||||||
        private readonly ITVSeriesManager _tvSeriesManager;
 | 
					 | 
				
			||||||
        private readonly IAuthorizationContext _authContext;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Initializes a new instance of the <see cref="TvShowsService" /> class.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="userManager">The user manager.</param>
 | 
					 | 
				
			||||||
        /// <param name="userDataManager">The user data repository.</param>
 | 
					 | 
				
			||||||
        /// <param name="libraryManager">The library manager.</param>
 | 
					 | 
				
			||||||
        public TvShowsService(
 | 
					 | 
				
			||||||
            ILogger<TvShowsService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            ITVSeriesManager tvSeriesManager,
 | 
					 | 
				
			||||||
            IAuthorizationContext authContext)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _userManager = userManager;
 | 
					 | 
				
			||||||
            _libraryManager = libraryManager;
 | 
					 | 
				
			||||||
            _dtoService = dtoService;
 | 
					 | 
				
			||||||
            _tvSeriesManager = tvSeriesManager;
 | 
					 | 
				
			||||||
            _authContext = authContext;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetUpcomingEpisodes request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var options = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                IncludeItemTypes = new[] { typeof(Episode).Name },
 | 
					 | 
				
			||||||
                OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
 | 
					 | 
				
			||||||
                MinPremiereDate = minPremiereDate,
 | 
					 | 
				
			||||||
                StartIndex = request.StartIndex,
 | 
					 | 
				
			||||||
                Limit = request.Limit,
 | 
					 | 
				
			||||||
                ParentId = parentIdGuid,
 | 
					 | 
				
			||||||
                Recursive = true,
 | 
					 | 
				
			||||||
                DtoOptions = options
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = new QueryResult<BaseItemDto>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                TotalRecordCount = itemsResult.Count,
 | 
					 | 
				
			||||||
                Items = returnItems
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetNextUpEpisodes request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var options = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = _tvSeriesManager.GetNextUp(new NextUpQuery
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Limit = request.Limit,
 | 
					 | 
				
			||||||
                ParentId = request.ParentId,
 | 
					 | 
				
			||||||
                SeriesId = request.SeriesId,
 | 
					 | 
				
			||||||
                StartIndex = request.StartIndex,
 | 
					 | 
				
			||||||
                UserId = request.UserId,
 | 
					 | 
				
			||||||
                EnableTotalRecordCount = request.EnableTotalRecordCount
 | 
					 | 
				
			||||||
            }, options);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(new QueryResult<BaseItemDto>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                TotalRecordCount = result.TotalRecordCount,
 | 
					 | 
				
			||||||
                Items = returnItems
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Applies the paging.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="items">The items.</param>
 | 
					 | 
				
			||||||
        /// <param name="startIndex">The start index.</param>
 | 
					 | 
				
			||||||
        /// <param name="limit">The limit.</param>
 | 
					 | 
				
			||||||
        /// <returns>IEnumerable{BaseItem}.</returns>
 | 
					 | 
				
			||||||
        private IEnumerable<BaseItem> ApplyPaging(IEnumerable<BaseItem> items, int? startIndex, int? limit)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Start at
 | 
					 | 
				
			||||||
            if (startIndex.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                items = items.Skip(startIndex.Value);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Return limit
 | 
					 | 
				
			||||||
            if (limit.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                items = items.Take(limit.Value);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return items;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetSeasons request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var series = GetSeries(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (series == null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ResourceNotFoundException("Series not found");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var seasons = series.GetItemList(new InternalItemsQuery(user)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                IsMissing = request.IsMissing,
 | 
					 | 
				
			||||||
                IsSpecialSeason = request.IsSpecialSeason,
 | 
					 | 
				
			||||||
                AdjacentTo = request.AdjacentTo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new QueryResult<BaseItemDto>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                TotalRecordCount = returnItems.Count,
 | 
					 | 
				
			||||||
                Items = returnItems
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private Series GetSeries(string seriesId)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(seriesId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return _libraryManager.GetItemById(seriesId) as Series;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetEpisodes request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            List<BaseItem> episodes;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(request.SeasonId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (!(_libraryManager.GetItemById(new Guid(request.SeasonId)) is Season season))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                episodes = season.GetEpisodes(user, dtoOptions);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else if (request.Season.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var series = GetSeries(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (series == null)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    throw new ResourceNotFoundException("Series not found");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                episodes = season == null ? new List<BaseItem>() : ((Season)season).GetEpisodes(user, dtoOptions);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var series = GetSeries(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (series == null)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    throw new ResourceNotFoundException("Series not found");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                episodes = series.GetEpisodes(user, dtoOptions).ToList();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Filter after the fact in case the ui doesn't want them
 | 
					 | 
				
			||||||
            if (request.IsMissing.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var val = request.IsMissing.Value;
 | 
					 | 
				
			||||||
                episodes = episodes.Where(i => ((Episode)i).IsMissingEpisode == val).ToList();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(request.StartItemId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                episodes = episodes.SkipWhile(i => !string.Equals(i.Id.ToString("N", CultureInfo.InvariantCulture), request.StartItemId, StringComparison.OrdinalIgnoreCase)).ToList();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // This must be the last filter
 | 
					 | 
				
			||||||
            if (!string.IsNullOrEmpty(request.AdjacentTo))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                episodes = UserViewBuilder.FilterForAdjacency(episodes, request.AdjacentTo).ToList();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (string.Equals(request.SortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                episodes.Shuffle();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var returnItems = episodes;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (request.StartIndex.HasValue || request.Limit.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                returnItems = ApplyPaging(episodes, request.StartIndex, request.Limit).ToList();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new QueryResult<BaseItemDto>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                TotalRecordCount = episodes.Count,
 | 
					 | 
				
			||||||
                Items = dtos
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,143 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities.Audio;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Querying;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.UserLibrary
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetArtists
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Artists", "GET", Summary = "Gets all artists from a given item, folder, or the entire library")]
 | 
					 | 
				
			||||||
    public class GetArtists : GetItemsByName
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Artists/AlbumArtists", "GET", Summary = "Gets all album artists from a given item, folder, or the entire library")]
 | 
					 | 
				
			||||||
    public class GetAlbumArtists : GetItemsByName
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Artists/{Name}", "GET", Summary = "Gets an artist, by name")]
 | 
					 | 
				
			||||||
    public class GetArtist : IReturn<BaseItemDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the name.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The name.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Name", Description = "The artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Name { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class ArtistsService
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class ArtistsService : BaseItemsByNameService<MusicArtist>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public ArtistsService(
 | 
					 | 
				
			||||||
            ILogger<ArtistsService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            IUserDataManager userDataRepository,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            IAuthorizationContext authorizationContext)
 | 
					 | 
				
			||||||
            : base(
 | 
					 | 
				
			||||||
                logger,
 | 
					 | 
				
			||||||
                serverConfigurationManager,
 | 
					 | 
				
			||||||
                httpResultFactory,
 | 
					 | 
				
			||||||
                userManager,
 | 
					 | 
				
			||||||
                libraryManager,
 | 
					 | 
				
			||||||
                userDataRepository,
 | 
					 | 
				
			||||||
                dtoService,
 | 
					 | 
				
			||||||
                authorizationContext)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetArtist request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return GetItem(request);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the item.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>Task{BaseItemDto}.</returns>
 | 
					 | 
				
			||||||
        private BaseItemDto GetItem(GetArtist request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(AuthorizationContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = GetArtist(request.Name, LibraryManager, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!request.UserId.Equals(Guid.Empty))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var user = UserManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return DtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return DtoService.GetBaseItemDto(item, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetArtists request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return GetResultSlim(request);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetAlbumArtists request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = GetResultSlim(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets all items.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <param name="items">The items.</param>
 | 
					 | 
				
			||||||
        /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
 | 
					 | 
				
			||||||
        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,456 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Globalization;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
using Jellyfin.Data.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Session;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Session;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.UserLibrary
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class MarkPlayedItem
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/PlayedItems/{Id}", "POST", Summary = "Marks an item as played")]
 | 
					 | 
				
			||||||
    public class MarkPlayedItem : IReturn<UserItemDataDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any). Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string DatePlayed { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class MarkUnplayedItem
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE", Summary = "Marks an item as unplayed")]
 | 
					 | 
				
			||||||
    public class MarkUnplayedItem : IReturn<UserItemDataDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Sessions/Playing", "POST", Summary = "Reports playback has started within a session")]
 | 
					 | 
				
			||||||
    public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Sessions/Playing/Progress", "POST", Summary = "Reports playback progress within a session")]
 | 
					 | 
				
			||||||
    public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Sessions/Playing/Ping", "POST", Summary = "Pings a playback session")]
 | 
					 | 
				
			||||||
    public class PingPlaybackSession : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string PlaySessionId { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Sessions/Playing/Stopped", "POST", Summary = "Reports playback has stopped within a session")]
 | 
					 | 
				
			||||||
    public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class OnPlaybackStart
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/PlayingItems/{Id}", "POST", Summary = "Reports that a user has begun playing an item")]
 | 
					 | 
				
			||||||
    public class OnPlaybackStart : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string MediaSourceId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public bool CanSeek { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public int? AudioStreamIndex { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public int? SubtitleStreamIndex { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public PlayMethod PlayMethod { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string LiveStreamId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string PlaySessionId { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class OnPlaybackProgress
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST", Summary = "Reports a user's playback progress")]
 | 
					 | 
				
			||||||
    public class OnPlaybackProgress : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string MediaSourceId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the position ticks.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The position ticks.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "PositionTicks", Description = "Optional. The current position, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public long? PositionTicks { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsPaused", Description = "Indicates if the player is paused.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public bool IsPaused { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public bool IsMuted { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public int? AudioStreamIndex { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public int? SubtitleStreamIndex { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public int? VolumeLevel { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public PlayMethod PlayMethod { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string LiveStreamId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string PlaySessionId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "RepeatMode", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public RepeatMode RepeatMode { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class OnPlaybackStopped
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE", Summary = "Reports that a user has stopped playing an item")]
 | 
					 | 
				
			||||||
    public class OnPlaybackStopped : IReturnVoid
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string MediaSourceId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "NextMediaType", Description = "The next media type that will play", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public string NextMediaType { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the position ticks.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The position ticks.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public long? PositionTicks { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string LiveStreamId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public string PlaySessionId { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class PlaystateService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly IUserManager _userManager;
 | 
					 | 
				
			||||||
        private readonly IUserDataManager _userDataRepository;
 | 
					 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					 | 
				
			||||||
        private readonly ISessionManager _sessionManager;
 | 
					 | 
				
			||||||
        private readonly ISessionContext _sessionContext;
 | 
					 | 
				
			||||||
        private readonly IAuthorizationContext _authContext;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public PlaystateService(
 | 
					 | 
				
			||||||
            ILogger<PlaystateService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            IUserDataManager userDataRepository,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            ISessionManager sessionManager,
 | 
					 | 
				
			||||||
            ISessionContext sessionContext,
 | 
					 | 
				
			||||||
            IAuthorizationContext authContext)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _userManager = userManager;
 | 
					 | 
				
			||||||
            _userDataRepository = userDataRepository;
 | 
					 | 
				
			||||||
            _libraryManager = libraryManager;
 | 
					 | 
				
			||||||
            _sessionManager = sessionManager;
 | 
					 | 
				
			||||||
            _sessionContext = sessionContext;
 | 
					 | 
				
			||||||
            _authContext = authContext;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public object Post(MarkPlayedItem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = MarkPlayed(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private UserItemDataDto MarkPlayed(MarkPlayedItem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(Guid.Parse(request.UserId));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            DateTime? datePlayed = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrEmpty(request.DatePlayed))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var session = GetSession(_sessionContext);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dto = UpdatePlayedStatus(user, request.Id, true, datePlayed);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            foreach (var additionalUserInfo in session.AdditionalUsers)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return dto;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (method == PlayMethod.Transcode)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var job = string.IsNullOrWhiteSpace(playSessionId) ? null : ApiEntryPoint.Instance.GetTranscodingJob(playSessionId);
 | 
					 | 
				
			||||||
                if (job == null)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return PlayMethod.DirectPlay;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return method;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public void Post(OnPlaybackStart request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Post(new ReportPlaybackStart
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                CanSeek = request.CanSeek,
 | 
					 | 
				
			||||||
                ItemId = new Guid(request.Id),
 | 
					 | 
				
			||||||
                MediaSourceId = request.MediaSourceId,
 | 
					 | 
				
			||||||
                AudioStreamIndex = request.AudioStreamIndex,
 | 
					 | 
				
			||||||
                SubtitleStreamIndex = request.SubtitleStreamIndex,
 | 
					 | 
				
			||||||
                PlayMethod = request.PlayMethod,
 | 
					 | 
				
			||||||
                PlaySessionId = request.PlaySessionId,
 | 
					 | 
				
			||||||
                LiveStreamId = request.LiveStreamId
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Post(ReportPlaybackStart request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            request.SessionId = GetSession(_sessionContext).Id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var task = _sessionManager.OnPlaybackStart(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Task.WaitAll(task);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public void Post(OnPlaybackProgress request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Post(new ReportPlaybackProgress
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ItemId = new Guid(request.Id),
 | 
					 | 
				
			||||||
                PositionTicks = request.PositionTicks,
 | 
					 | 
				
			||||||
                IsMuted = request.IsMuted,
 | 
					 | 
				
			||||||
                IsPaused = request.IsPaused,
 | 
					 | 
				
			||||||
                MediaSourceId = request.MediaSourceId,
 | 
					 | 
				
			||||||
                AudioStreamIndex = request.AudioStreamIndex,
 | 
					 | 
				
			||||||
                SubtitleStreamIndex = request.SubtitleStreamIndex,
 | 
					 | 
				
			||||||
                VolumeLevel = request.VolumeLevel,
 | 
					 | 
				
			||||||
                PlayMethod = request.PlayMethod,
 | 
					 | 
				
			||||||
                PlaySessionId = request.PlaySessionId,
 | 
					 | 
				
			||||||
                LiveStreamId = request.LiveStreamId,
 | 
					 | 
				
			||||||
                RepeatMode = request.RepeatMode
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Post(ReportPlaybackProgress request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            request.SessionId = GetSession(_sessionContext).Id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var task = _sessionManager.OnPlaybackProgress(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Task.WaitAll(task);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Post(PingPlaybackSession request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ApiEntryPoint.Instance.PingTranscodingJob(request.PlaySessionId, null);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public Task Delete(OnPlaybackStopped request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Post(new ReportPlaybackStopped
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ItemId = new Guid(request.Id),
 | 
					 | 
				
			||||||
                PositionTicks = request.PositionTicks,
 | 
					 | 
				
			||||||
                MediaSourceId = request.MediaSourceId,
 | 
					 | 
				
			||||||
                PlaySessionId = request.PlaySessionId,
 | 
					 | 
				
			||||||
                LiveStreamId = request.LiveStreamId,
 | 
					 | 
				
			||||||
                NextMediaType = request.NextMediaType
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public async Task Post(ReportPlaybackStopped request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                await ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            request.SessionId = GetSession(_sessionContext).Id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            await _sessionManager.OnPlaybackStopped(request);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Deletes the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public object Delete(MarkUnplayedItem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var task = MarkUnplayed(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(task);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private UserItemDataDto MarkUnplayed(MarkUnplayedItem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(Guid.Parse(request.UserId));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var session = GetSession(_sessionContext);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dto = UpdatePlayedStatus(user, request.Id, false, null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            foreach (var additionalUserInfo in session.AdditionalUsers)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                UpdatePlayedStatus(additionalUser, request.Id, false, null);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return dto;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Updates the played status.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="user">The user.</param>
 | 
					 | 
				
			||||||
        /// <param name="itemId">The item id.</param>
 | 
					 | 
				
			||||||
        /// <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, string itemId, bool wasPlayed, DateTime? datePlayed)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (wasPlayed)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                item.MarkPlayed(user, datePlayed, true);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                item.MarkUnplayed(user);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return _userDataRepository.GetUserDataDto(item, user);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,132 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Querying;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.UserLibrary
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetStudios
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Studios", "GET", Summary = "Gets all studios from a given item, folder, or the entire library")]
 | 
					 | 
				
			||||||
    public class GetStudios : GetItemsByName
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetStudio
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Studios/{Name}", "GET", Summary = "Gets a studio, by name")]
 | 
					 | 
				
			||||||
    public class GetStudio : IReturn<BaseItemDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the name.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The name.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Name", Description = "The studio name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Name { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class StudiosService
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class StudiosService : BaseItemsByNameService<Studio>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public StudiosService(
 | 
					 | 
				
			||||||
            ILogger<StudiosService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            IUserDataManager userDataRepository,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            IAuthorizationContext authorizationContext)
 | 
					 | 
				
			||||||
            : base(
 | 
					 | 
				
			||||||
                logger,
 | 
					 | 
				
			||||||
                serverConfigurationManager,
 | 
					 | 
				
			||||||
                httpResultFactory,
 | 
					 | 
				
			||||||
                userManager,
 | 
					 | 
				
			||||||
                libraryManager,
 | 
					 | 
				
			||||||
                userDataRepository,
 | 
					 | 
				
			||||||
                dtoService,
 | 
					 | 
				
			||||||
                authorizationContext)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetStudio request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = GetItem(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the item.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>Task{BaseItemDto}.</returns>
 | 
					 | 
				
			||||||
        private BaseItemDto GetItem(GetStudio request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(AuthorizationContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = GetStudio(request.Name, LibraryManager, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!request.UserId.Equals(Guid.Empty))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var user = UserManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return DtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return DtoService.GetBaseItemDto(item, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetStudios request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = GetResultSlim(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return LibraryManager.GetStudios(query);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets all items.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <param name="items">The items.</param>
 | 
					 | 
				
			||||||
        /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
 | 
					 | 
				
			||||||
        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,575 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using System.Threading;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
using MediaBrowser.Common.Extensions;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities.Audio;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Providers;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.IO;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Querying;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.UserLibrary
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetItem
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Items/{Id}", "GET", Summary = "Gets an item from a user's library")]
 | 
					 | 
				
			||||||
    public class GetItem : IReturn<BaseItemDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetItem
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Items/Root", "GET", Summary = "Gets the root folder from a user's library")]
 | 
					 | 
				
			||||||
    public class GetRootFolder : IReturn<BaseItemDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetIntros
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Items/{Id}/Intros", "GET", Summary = "Gets intros to play before the main media item plays")]
 | 
					 | 
				
			||||||
    public class GetIntros : IReturn<QueryResult<BaseItemDto>>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the item id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The item id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class MarkFavoriteItem
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/FavoriteItems/{Id}", "POST", Summary = "Marks an item as a favorite")]
 | 
					 | 
				
			||||||
    public class MarkFavoriteItem : IReturn<UserItemDataDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class UnmarkFavoriteItem
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE", Summary = "Unmarks an item as a favorite")]
 | 
					 | 
				
			||||||
    public class UnmarkFavoriteItem : IReturn<UserItemDataDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class ClearUserItemRating
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE", Summary = "Deletes a user's saved personal rating for an item")]
 | 
					 | 
				
			||||||
    public class DeleteUserItemRating : IReturn<UserItemDataDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
					 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class UpdateUserItemRating
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Items/{Id}/Rating", "POST", Summary = "Updates a user's rating for an item")]
 | 
					 | 
				
			||||||
    public class UpdateUserItemRating : IReturn<UserItemDataDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
					 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value><c>true</c> if likes; otherwise, <c>false</c>.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Likes", Description = "Whether the user likes the item or not. true/false", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
 | 
					 | 
				
			||||||
        public bool Likes { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetLocalTrailers
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET", Summary = "Gets local trailers for an item")]
 | 
					 | 
				
			||||||
    public class GetLocalTrailers : IReturn<BaseItemDto[]>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetSpecialFeatures
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET", Summary = "Gets special features for an item")]
 | 
					 | 
				
			||||||
    public class GetSpecialFeatures : IReturn<BaseItemDto[]>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Id", Description = "Movie Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Items/Latest", "GET", Summary = "Gets latest media")]
 | 
					 | 
				
			||||||
    public class GetLatestMedia : IReturn<BaseItemDto[]>, IHasDtoOptions
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Limit", Description = "Limit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int Limit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid ParentId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string Fields { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
 | 
					 | 
				
			||||||
        public string IncludeItemTypes { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsFolder", Description = "Filter by items that are folders, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? IsFolder { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IsPlayed", Description = "Filter by items that are played, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? IsPlayed { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "GroupItems", Description = "Whether or not to group items into a parent container.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool GroupItems { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableImages { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int? ImageTypeLimit { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public string EnableImageTypes { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? EnableUserData { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public GetLatestMedia()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Limit = 20;
 | 
					 | 
				
			||||||
            GroupItems = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class UserLibraryService
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class UserLibraryService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly IUserManager _userManager;
 | 
					 | 
				
			||||||
        private readonly IUserDataManager _userDataRepository;
 | 
					 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					 | 
				
			||||||
        private readonly IDtoService _dtoService;
 | 
					 | 
				
			||||||
        private readonly IUserViewManager _userViewManager;
 | 
					 | 
				
			||||||
        private readonly IFileSystem _fileSystem;
 | 
					 | 
				
			||||||
        private readonly IAuthorizationContext _authContext;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public UserLibraryService(
 | 
					 | 
				
			||||||
            ILogger<UserLibraryService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            IUserDataManager userDataRepository,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            IUserViewManager userViewManager,
 | 
					 | 
				
			||||||
            IFileSystem fileSystem,
 | 
					 | 
				
			||||||
            IAuthorizationContext authContext)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _userManager = userManager;
 | 
					 | 
				
			||||||
            _libraryManager = libraryManager;
 | 
					 | 
				
			||||||
            _userDataRepository = userDataRepository;
 | 
					 | 
				
			||||||
            _dtoService = dtoService;
 | 
					 | 
				
			||||||
            _userViewManager = userViewManager;
 | 
					 | 
				
			||||||
            _fileSystem = fileSystem;
 | 
					 | 
				
			||||||
            _authContext = authContext;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetSpecialFeatures request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = GetAsync(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetLatestMedia request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!request.IsPlayed.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (user.HidePlayedInLatest)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    request.IsPlayed = false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var list = _userViewManager.GetLatestItems(new LatestItemsQuery
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                GroupItems = request.GroupItems,
 | 
					 | 
				
			||||||
                IncludeItemTypes = ApiEntryPoint.Split(request.IncludeItemTypes, ',', true),
 | 
					 | 
				
			||||||
                IsPlayed = request.IsPlayed,
 | 
					 | 
				
			||||||
                Limit = request.Limit,
 | 
					 | 
				
			||||||
                ParentId = request.ParentId,
 | 
					 | 
				
			||||||
                UserId = request.UserId,
 | 
					 | 
				
			||||||
            }, dtoOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtos = list.Select(i =>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var item = i.Item2[0];
 | 
					 | 
				
			||||||
                var childCount = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (i.Item1 != null && (i.Item2.Count > 1 || i.Item1 is MusicAlbum))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    item = i.Item1;
 | 
					 | 
				
			||||||
                    childCount = i.Item2.Count;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                var dto = _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                dto.ChildCount = childCount;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return dto;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(dtos.ToArray());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private BaseItemDto[] GetAsync(GetSpecialFeatures request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = string.IsNullOrEmpty(request.Id) ?
 | 
					 | 
				
			||||||
                _libraryManager.GetUserRootFolder() :
 | 
					 | 
				
			||||||
                _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtos = item
 | 
					 | 
				
			||||||
                .GetExtras(BaseItem.DisplayExtraTypes)
 | 
					 | 
				
			||||||
                .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return dtos.ToArray();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetLocalTrailers request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtosExtras = item.GetExtras(new[] { ExtraType.Trailer })
 | 
					 | 
				
			||||||
                .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
 | 
					 | 
				
			||||||
                .ToArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (item is IHasTrailers hasTrailers)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var trailers = hasTrailers.GetTrailers();
 | 
					 | 
				
			||||||
                var dtosTrailers = _dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item);
 | 
					 | 
				
			||||||
                var allTrailers = new BaseItemDto[dtosExtras.Length + dtosTrailers.Count];
 | 
					 | 
				
			||||||
                dtosExtras.CopyTo(allTrailers, 0);
 | 
					 | 
				
			||||||
                dtosTrailers.CopyTo(allTrailers, dtosExtras.Length);
 | 
					 | 
				
			||||||
                return ToOptimizedResult(allTrailers);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(dtosExtras);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public async Task<object> Get(GetItem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private async Task RefreshItemOnDemandIfNeeded(BaseItem item)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (item is Person)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
 | 
					 | 
				
			||||||
                var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!hasMetdata)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
 | 
					 | 
				
			||||||
                        ImageRefreshMode = MetadataRefreshMode.FullRefresh,
 | 
					 | 
				
			||||||
                        ForceSave = performFullRefresh
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetRootFolder request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = _libraryManager.GetUserRootFolder();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public async Task<object> Get(GetIntros request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = new QueryResult<BaseItemDto>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Items = dtos,
 | 
					 | 
				
			||||||
                TotalRecordCount = dtos.Length
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public object Post(MarkFavoriteItem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dto = MarkFavorite(request.UserId, request.Id, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(dto);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Deletes the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public object Delete(UnmarkFavoriteItem request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dto = MarkFavorite(request.UserId, request.Id, false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(dto);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Marks the favorite.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="userId">The user id.</param>
 | 
					 | 
				
			||||||
        /// <param name="itemId">The item id.</param>
 | 
					 | 
				
			||||||
        /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
 | 
					 | 
				
			||||||
        private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(userId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Get the user data for this item
 | 
					 | 
				
			||||||
            var data = _userDataRepository.GetUserData(user, item);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Set favorite status
 | 
					 | 
				
			||||||
            data.IsFavorite = isFavorite;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return _userDataRepository.GetUserDataDto(item, user);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Deletes the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public object Delete(DeleteUserItemRating request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dto = UpdateUserItemRating(request.UserId, request.Id, null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(dto);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Posts the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        public object Post(UpdateUserItemRating request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(dto);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Updates the user item rating.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="userId">The user id.</param>
 | 
					 | 
				
			||||||
        /// <param name="itemId">The item id.</param>
 | 
					 | 
				
			||||||
        /// <param name="likes">if set to <c>true</c> [likes].</param>
 | 
					 | 
				
			||||||
        private UserItemDataDto UpdateUserItemRating(Guid userId, Guid itemId, bool? likes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(userId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Get the user data for this item
 | 
					 | 
				
			||||||
            var data = _userDataRepository.GetUserData(user, item);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            data.Likes = likes;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return _userDataRepository.GetUserDataDto(item, user);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,146 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Globalization;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Querying;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.UserLibrary
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/Views", "GET")]
 | 
					 | 
				
			||||||
    public class GetUserViews : IReturn<QueryResult<BaseItemDto>>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public bool? IncludeExternalContent { get; set; }
 | 
					 | 
				
			||||||
        public bool IncludeHidden { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public string PresetViews { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [Route("/Users/{UserId}/GroupingOptions", "GET")]
 | 
					 | 
				
			||||||
    public class GetGroupingOptions : IReturn<SpecialViewOption[]>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public class UserViewsService : BaseApiService
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly IUserManager _userManager;
 | 
					 | 
				
			||||||
        private readonly IUserViewManager _userViewManager;
 | 
					 | 
				
			||||||
        private readonly IDtoService _dtoService;
 | 
					 | 
				
			||||||
        private readonly IAuthorizationContext _authContext;
 | 
					 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public UserViewsService(
 | 
					 | 
				
			||||||
            ILogger<UserViewsService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            IUserViewManager userViewManager,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            IAuthorizationContext authContext,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager)
 | 
					 | 
				
			||||||
            : base(logger, serverConfigurationManager, httpResultFactory)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _userManager = userManager;
 | 
					 | 
				
			||||||
            _userViewManager = userViewManager;
 | 
					 | 
				
			||||||
            _dtoService = dtoService;
 | 
					 | 
				
			||||||
            _authContext = authContext;
 | 
					 | 
				
			||||||
            _libraryManager = libraryManager;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetUserViews request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var query = new UserViewQuery
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                UserId = request.UserId
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (request.IncludeExternalContent.HasValue)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                query.IncludeExternalContent = request.IncludeExternalContent.Value;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            query.IncludeHidden = request.IncludeHidden;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(request.PresetViews))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                query.PresetViews = request.PresetViews.Split(',');
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
 | 
					 | 
				
			||||||
            if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows };
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var folders = _userViewManager.GetUserViews(query);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(_authContext, request);
 | 
					 | 
				
			||||||
            var fields = dtoOptions.Fields.ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            fields.Add(ItemFields.PrimaryImageAspectRatio);
 | 
					 | 
				
			||||||
            fields.Add(ItemFields.DisplayPreferencesId);
 | 
					 | 
				
			||||||
            fields.Remove(ItemFields.BasicSyncInfo);
 | 
					 | 
				
			||||||
            dtoOptions.Fields = fields.ToArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
 | 
					 | 
				
			||||||
                .ToArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var result = new QueryResult<BaseItemDto>
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Items = dtos,
 | 
					 | 
				
			||||||
                TotalRecordCount = dtos.Length
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public object Get(GetGroupingOptions request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var list = _libraryManager.GetUserRootFolder()
 | 
					 | 
				
			||||||
                .GetChildren(user, true)
 | 
					 | 
				
			||||||
                .OfType<Folder>()
 | 
					 | 
				
			||||||
                .Where(UserView.IsEligibleForGrouping)
 | 
					 | 
				
			||||||
                .Select(i => new SpecialViewOption
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Name = i.Name,
 | 
					 | 
				
			||||||
                    Id = i.Id.ToString("N", CultureInfo.InvariantCulture)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            .OrderBy(i => i.Name)
 | 
					 | 
				
			||||||
            .ToArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(list);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class SpecialViewOption
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public string Name { get; set; }
 | 
					 | 
				
			||||||
        public string Id { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,131 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Dto;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace MediaBrowser.Api.UserLibrary
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetYears
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Years", "GET", Summary = "Gets all years from a given item, folder, or the entire library")]
 | 
					 | 
				
			||||||
    public class GetYears : GetItemsByName
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class GetYear
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Route("/Years/{Year}", "GET", Summary = "Gets a year")]
 | 
					 | 
				
			||||||
    public class GetYear : IReturn<BaseItemDto>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the year.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The year.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "Year", Description = "The year", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
 | 
					 | 
				
			||||||
        public int Year { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the user id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The user id.</value>
 | 
					 | 
				
			||||||
        [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
 | 
					 | 
				
			||||||
        public Guid UserId { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class YearsService
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    [Authenticated]
 | 
					 | 
				
			||||||
    public class YearsService : BaseItemsByNameService<Year>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public YearsService(
 | 
					 | 
				
			||||||
            ILogger<YearsService> logger,
 | 
					 | 
				
			||||||
            IServerConfigurationManager serverConfigurationManager,
 | 
					 | 
				
			||||||
            IHttpResultFactory httpResultFactory,
 | 
					 | 
				
			||||||
            IUserManager userManager,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            IUserDataManager userDataRepository,
 | 
					 | 
				
			||||||
            IDtoService dtoService,
 | 
					 | 
				
			||||||
            IAuthorizationContext authorizationContext)
 | 
					 | 
				
			||||||
            : base(
 | 
					 | 
				
			||||||
                logger,
 | 
					 | 
				
			||||||
                serverConfigurationManager,
 | 
					 | 
				
			||||||
                httpResultFactory,
 | 
					 | 
				
			||||||
                userManager,
 | 
					 | 
				
			||||||
                libraryManager,
 | 
					 | 
				
			||||||
                userDataRepository,
 | 
					 | 
				
			||||||
                dtoService,
 | 
					 | 
				
			||||||
                authorizationContext)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetYear request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = GetItem(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the item.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>Task{BaseItemDto}.</returns>
 | 
					 | 
				
			||||||
        private BaseItemDto GetItem(GetYear request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var item = LibraryManager.GetYear(request.Year);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var dtoOptions = GetDtoOptions(AuthorizationContext, request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!request.UserId.Equals(Guid.Empty))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var user = UserManager.GetUserById(request.UserId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return DtoService.GetBaseItemDto(item, dtoOptions, user);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return DtoService.GetBaseItemDto(item, dtoOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the specified request.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <returns>System.Object.</returns>
 | 
					 | 
				
			||||||
        public object Get(GetYears request)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var result = GetResult(request);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return ToOptimizedResult(result);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets all items.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="request">The request.</param>
 | 
					 | 
				
			||||||
        /// <param name="items">The items.</param>
 | 
					 | 
				
			||||||
        /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
 | 
					 | 
				
			||||||
        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return items
 | 
					 | 
				
			||||||
                .Select(i => i.ProductionYear ?? 0)
 | 
					 | 
				
			||||||
                .Where(i => i > 0)
 | 
					 | 
				
			||||||
                .Distinct()
 | 
					 | 
				
			||||||
                .Select(year => LibraryManager.GetYear(year));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										56
									
								
								MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Buffers;
 | 
				
			||||||
 | 
					using System.Buffers.Text;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					using System.Text.Json;
 | 
				
			||||||
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace MediaBrowser.Common.Json.Converters
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Long to String JSON converter.
 | 
				
			||||||
 | 
					    /// Javascript does not support 64-bit integers.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public class JsonInt64Converter : JsonConverter<long>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Read JSON string as int64.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="reader"><see cref="Utf8JsonReader"/>.</param>
 | 
				
			||||||
 | 
					        /// <param name="type">Type.</param>
 | 
				
			||||||
 | 
					        /// <param name="options">Options.</param>
 | 
				
			||||||
 | 
					        /// <returns>Parsed value.</returns>
 | 
				
			||||||
 | 
					        public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (reader.TokenType == JsonTokenType.String)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // try to parse number directly from bytes
 | 
				
			||||||
 | 
					                var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
 | 
				
			||||||
 | 
					                if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return number;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters
 | 
				
			||||||
 | 
					                if (long.TryParse(reader.GetString(), out number))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return number;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // fallback to default handling
 | 
				
			||||||
 | 
					            return reader.GetInt64();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Write long to JSON string.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="writer"><see cref="Utf8JsonWriter"/>.</param>
 | 
				
			||||||
 | 
					        /// <param name="value">Value to write.</param>
 | 
				
			||||||
 | 
					        /// <param name="options">Options.</param>
 | 
				
			||||||
 | 
					        public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -31,6 +31,7 @@ namespace MediaBrowser.Common.Json
 | 
				
			|||||||
            options.Converters.Add(new JsonInt32Converter());
 | 
					            options.Converters.Add(new JsonInt32Converter());
 | 
				
			||||||
            options.Converters.Add(new JsonStringEnumConverter());
 | 
					            options.Converters.Add(new JsonStringEnumConverter());
 | 
				
			||||||
            options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory());
 | 
					            options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory());
 | 
				
			||||||
 | 
					            options.Converters.Add(new JsonInt64Converter());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return options;
 | 
					            return options;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user