mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-30 04:18:27 +01:00
Add a collection API for Included In feature (#15516)
Add a collection API for `Included In` feature
This commit is contained in:
@@ -4,12 +4,15 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -29,6 +32,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
private readonly ILibraryMonitor _iLibraryMonitor;
|
||||
private readonly ILogger<CollectionManager> _logger;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly ILinkedChildrenService _linkedChildrenService;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
@@ -42,6 +46,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
/// <param name="iLibraryMonitor">The library monitor.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
/// <param name="providerManager">The provider manager.</param>
|
||||
/// <param name="linkedChildrenService">The linked children service.</param>
|
||||
public CollectionManager(
|
||||
ILibraryManager libraryManager,
|
||||
IApplicationPaths appPaths,
|
||||
@@ -49,13 +54,15 @@ namespace Emby.Server.Implementations.Collections
|
||||
IFileSystem fileSystem,
|
||||
ILibraryMonitor iLibraryMonitor,
|
||||
ILoggerFactory loggerFactory,
|
||||
IProviderManager providerManager)
|
||||
IProviderManager providerManager,
|
||||
ILinkedChildrenService linkedChildrenService)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_fileSystem = fileSystem;
|
||||
_iLibraryMonitor = iLibraryMonitor;
|
||||
_logger = loggerFactory.CreateLogger<CollectionManager>();
|
||||
_providerManager = providerManager;
|
||||
_linkedChildrenService = linkedChildrenService;
|
||||
_localizationManager = localizationManager;
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
@@ -120,6 +127,22 @@ namespace Emby.Server.Implementations.Collections
|
||||
return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<BoxSet> GetCollectionsContainingItem(User user, Guid itemId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
if (itemId.IsEmpty())
|
||||
{
|
||||
return Enumerable.Empty<BoxSet>();
|
||||
}
|
||||
|
||||
return _linkedChildrenService
|
||||
.GetManualLinkedParentIds(itemId, BaseItemKind.BoxSet)
|
||||
.Select(parentId => _libraryManager.GetItemById<BoxSet>(parentId, user))
|
||||
.OfType<BoxSet>();
|
||||
}
|
||||
|
||||
private IEnumerable<BoxSet> GetCollections(User user)
|
||||
{
|
||||
var folder = GetCollectionsFolder(false).GetAwaiter().GetResult();
|
||||
|
||||
@@ -17,6 +17,7 @@ using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -50,6 +51,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
private readonly ISimilarItemsManager _similarItemsManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ICollectionManager _collectionManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly IActivityManager _activityManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
@@ -64,6 +66,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
/// <param name="similarItemsManager">Instance of the <see cref="ISimilarItemsManager"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="collectionManager">Instance of the <see cref="ICollectionManager"/> interface.</param>
|
||||
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
|
||||
/// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param>
|
||||
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
|
||||
@@ -75,6 +78,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
ISimilarItemsManager similarItemsManager,
|
||||
ILibraryManager libraryManager,
|
||||
IUserManager userManager,
|
||||
ICollectionManager collectionManager,
|
||||
IDtoService dtoService,
|
||||
IActivityManager activityManager,
|
||||
ILocalizationManager localization,
|
||||
@@ -86,6 +90,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
_similarItemsManager = similarItemsManager;
|
||||
_libraryManager = libraryManager;
|
||||
_userManager = userManager;
|
||||
_collectionManager = collectionManager;
|
||||
_dtoService = dtoService;
|
||||
_activityManager = activityManager;
|
||||
_localization = localization;
|
||||
@@ -704,6 +709,72 @@ public class LibraryController : BaseJellyfinApiController
|
||||
return PhysicalFile(filePath, MimeTypes.GetMimeType(filePath), filename, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collections that include the specified item.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
|
||||
/// <param name="startIndex">Optional. The index of the first record in the output.</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.</param>
|
||||
/// <response code="200">Collections returned.</response>
|
||||
/// <response code="401">User context missing.</response>
|
||||
/// <response code="404">Item not found.</response>
|
||||
/// <returns>The collections that contain the requested item.</returns>
|
||||
[HttpGet("Items/{itemId}/Collections")]
|
||||
[Authorize]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetItemCollections(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
|
||||
if (item is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var dtoOptions = new DtoOptions { Fields = fields };
|
||||
|
||||
var visibleCollections = _collectionManager
|
||||
.GetCollectionsContainingItem(user, item.Id)
|
||||
.OrderBy(i => i.SortName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
IEnumerable<BaseItem> pagedCollections = visibleCollections;
|
||||
if (startIndex.HasValue)
|
||||
{
|
||||
pagedCollections = pagedCollections.Skip(startIndex.Value);
|
||||
}
|
||||
|
||||
if (limit.HasValue)
|
||||
{
|
||||
pagedCollections = pagedCollections.Take(limit.Value);
|
||||
}
|
||||
|
||||
var dtos = _dtoService.GetBaseItemDtos(pagedCollections.ToList(), dtoOptions, user);
|
||||
|
||||
return new QueryResult<BaseItemDto>(
|
||||
startIndex,
|
||||
visibleCollections.Count,
|
||||
dtos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets similar items.
|
||||
/// </summary>
|
||||
|
||||
@@ -91,14 +91,25 @@ public class LinkedChildrenService : ILinkedChildrenService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<Guid> GetManualLinkedParentIds(Guid childId)
|
||||
public IReadOnlyList<Guid> GetManualLinkedParentIds(Guid childId, BaseItemKind? parentType = null)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
return context.LinkedChildren
|
||||
.Where(lc => lc.ChildId == childId && lc.ChildType == DbLinkedChildType.Manual)
|
||||
.Select(lc => lc.ParentId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var query = context.LinkedChildren
|
||||
.Where(lc => lc.ChildId == childId && lc.ChildType == DbLinkedChildType.Manual);
|
||||
|
||||
if (parentType.HasValue)
|
||||
{
|
||||
var parentTypeName = _itemTypeLookup.BaseItemKindNames[parentType.Value];
|
||||
query = query.Join(
|
||||
context.BaseItems
|
||||
.Where(item => item.Type == parentTypeName),
|
||||
lc => lc.ParentId,
|
||||
item => item.Id,
|
||||
(lc, _) => lc);
|
||||
}
|
||||
|
||||
return query.Select(lc => lc.ParentId).Distinct().ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -57,6 +57,14 @@ namespace MediaBrowser.Controller.Collections
|
||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||
IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collections accessible to the supplied user that contain the provided item.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="itemId">The item identifier.</param>
|
||||
/// <returns>The collections containing the item.</returns>
|
||||
IEnumerable<BoxSet> GetCollectionsContainingItem(User user, Guid itemId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder where collections are stored.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using LinkedChildType = MediaBrowser.Controller.Entities.LinkedChildType;
|
||||
|
||||
@@ -29,8 +30,9 @@ public interface ILinkedChildrenService
|
||||
/// Gets parent IDs that reference the specified child with LinkedChildType.Manual.
|
||||
/// </summary>
|
||||
/// <param name="childId">The child item ID.</param>
|
||||
/// <param name="parentType">Optional parent item type filter.</param>
|
||||
/// <returns>List of parent IDs that reference the child.</returns>
|
||||
IReadOnlyList<Guid> GetManualLinkedParentIds(Guid childId);
|
||||
IReadOnlyList<Guid> GetManualLinkedParentIds(Guid childId, BaseItemKind? parentType = null);
|
||||
|
||||
/// <summary>
|
||||
/// Updates LinkedChildren references from one child to another.
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed class LibraryControllerTests : IClassFixture<JellyfinApplicationFa
|
||||
[InlineData("Items/{0}/ThemeMedia")]
|
||||
[InlineData("Items/{0}/Ancestors")]
|
||||
[InlineData("Items/{0}/Download")]
|
||||
[InlineData("Items/{0}/Collections")]
|
||||
[InlineData("Artists/{0}/Similar")]
|
||||
[InlineData("Items/{0}/Similar")]
|
||||
[InlineData("Albums/{0}/Similar")]
|
||||
|
||||
Reference in New Issue
Block a user