aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers/LibraryController.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers/LibraryController.cs')
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs166
1 files changed, 115 insertions, 51 deletions
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 69c17f2486..39a6fbace8 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -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;
@@ -47,8 +48,10 @@ namespace Jellyfin.Api.Controllers;
public class LibraryController : BaseJellyfinApiController
{
private readonly IProviderManager _providerManager;
+ 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;
@@ -60,8 +63,10 @@ public class LibraryController : BaseJellyfinApiController
/// Initializes a new instance of the <see cref="LibraryController"/> class.
/// </summary>
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
+ /// <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>
@@ -70,8 +75,10 @@ public class LibraryController : BaseJellyfinApiController
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
public LibraryController(
IProviderManager providerManager,
+ ISimilarItemsManager similarItemsManager,
ILibraryManager libraryManager,
IUserManager userManager,
+ ICollectionManager collectionManager,
IDtoService dtoService,
IActivityManager activityManager,
ILocalizationManager localization,
@@ -80,8 +87,10 @@ public class LibraryController : BaseJellyfinApiController
IServerConfigurationManager serverConfigurationManager)
{
_providerManager = providerManager;
+ _similarItemsManager = similarItemsManager;
_libraryManager = libraryManager;
_userManager = userManager;
+ _collectionManager = collectionManager;
_dtoService = dtoService;
_activityManager = activityManager;
_localization = localization;
@@ -110,7 +119,18 @@ public class LibraryController : BaseJellyfinApiController
return NotFound();
}
- return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true);
+ var filePath = item.Path;
+ if (item.IsFileProtocol)
+ {
+ // PhysicalFile does not work well with symlinks at the moment.
+ var resolved = FileSystemHelper.ResolveLinkTarget(filePath, returnFinalTarget: true);
+ if (resolved is not null && resolved.Exists)
+ {
+ filePath = resolved.FullName;
+ }
+ }
+
+ return PhysicalFile(filePath, MimeTypes.GetMimeType(filePath), true);
}
/// <summary>
@@ -701,6 +721,72 @@ public class LibraryController : BaseJellyfinApiController
}
/// <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>
/// <param name="itemId">The item id.</param>
@@ -708,6 +794,7 @@ public class LibraryController : BaseJellyfinApiController
/// <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 delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <response code="200">Similar items returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
[HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists")]
@@ -718,12 +805,13 @@ public class LibraryController : BaseJellyfinApiController
[HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
+ public async Task<ActionResult<QueryResult<BaseItemDto>>> GetSimilarItems(
[FromRoute, Required] Guid itemId,
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
- [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields)
+ [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
+ CancellationToken cancellationToken)
{
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
@@ -746,57 +834,22 @@ public class LibraryController : BaseJellyfinApiController
var dtoOptions = new DtoOptions { Fields = fields };
- var program = item as IHasProgramAttributes;
- bool? isMovie = item is Movie || (program is not null && program.IsMovie) || item is Trailer;
- bool? isSeries = item is Series || (program is not null && program.IsSeries);
-
- var includeItemTypes = new List<BaseItemKind>();
- if (isMovie.Value)
- {
- includeItemTypes.Add(BaseItemKind.Movie);
- if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
- {
- includeItemTypes.Add(BaseItemKind.Trailer);
- includeItemTypes.Add(BaseItemKind.LiveTvProgram);
- }
- }
- else if (isSeries.Value)
- {
- includeItemTypes.Add(BaseItemKind.Series);
- }
- else
- {
- // For non series and movie types these columns are typically null
- // isSeries = null;
- isMovie = null;
- includeItemTypes.Add(item.GetBaseItemKind());
- }
-
- var query = new InternalItemsQuery(user)
- {
- Genres = item.Genres,
- Tags = item.Tags,
- Limit = limit,
- IncludeItemTypes = includeItemTypes.ToArray(),
- DtoOptions = dtoOptions,
- EnableTotalRecordCount = !isMovie ?? true,
- EnableGroupByMetadataKey = isMovie ?? false,
- ExcludeItemIds = [itemId],
- OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)]
- };
-
- // ExcludeArtistIds
- if (excludeArtistIds.Length != 0)
- {
- query.ExcludeArtistIds = excludeArtistIds;
- }
+ // Get library options for provider configuration
+ var libraryOptions = _libraryManager.GetLibraryOptions(item);
- var itemsResult = _libraryManager.GetItemList(query);
+ var itemsResult = await _similarItemsManager.GetSimilarItemsAsync(
+ item,
+ excludeArtistIds,
+ user,
+ dtoOptions,
+ limit,
+ libraryOptions,
+ cancellationToken).ConfigureAwait(false);
var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user);
return new QueryResult<BaseItemDto>(
- query.StartIndex,
+ 0,
itemsResult.Count,
returnList);
}
@@ -907,6 +960,17 @@ public class LibraryController : BaseJellyfinApiController
.DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.ToArray(),
+ SimilarItemProviders = plugins
+ .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
+ .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalSimilarityProvider || p.Type == MetadataPluginType.SimilarityProvider))
+ .Select(i => new LibraryOptionInfoDto
+ {
+ Name = i.Name,
+ DefaultEnabled = i.Type == MetadataPluginType.LocalSimilarityProvider
+ })
+ .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
+ .ToArray(),
+
SupportedImageTypes = plugins
.Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>())
@@ -935,11 +999,11 @@ public class LibraryController : BaseJellyfinApiController
try
{
await _activityManager.CreateAsync(new ActivityLog(
- string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name),
+ string.Format(CultureInfo.InvariantCulture, _localization.GetServerLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name),
"UserDownloadingContent",
User.GetUserId())
{
- ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceValues"), User.GetClient(), User.GetDevice()),
+ ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetServerLocalizedString("AppDeviceValues"), User.GetClient(), User.GetDevice()),
ItemId = item.Id.ToString("N", CultureInfo.InvariantCulture)
}).ConfigureAwait(false);
}