aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers/ItemsController.cs
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-05-03 23:33:56 +0200
committerShadowghost <Ghost_of_Stone@web.de>2026-05-04 01:55:07 +0200
commit07a802d8fa93460c9f2a7f42da7a1f14a893a322 (patch)
tree61b6cf30ba21f34ebd98f9f5a7ed296a81c75f0a /Jellyfin.Api/Controllers/ItemsController.cs
parent622947e37425f3620432995cde5d4a0809d91694 (diff)
Implement search providers
Diffstat (limited to 'Jellyfin.Api/Controllers/ItemsController.cs')
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs76
1 files changed, 67 insertions, 9 deletions
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 53656186c8..133104680f 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
@@ -41,6 +42,7 @@ public class ItemsController : BaseJellyfinApiController
private readonly ILogger<ItemsController> _logger;
private readonly ISessionManager _sessionManager;
private readonly IUserDataManager _userDataRepository;
+ private readonly ISearchManager _searchManager;
/// <summary>
/// Initializes a new instance of the <see cref="ItemsController"/> class.
@@ -52,6 +54,7 @@ public class ItemsController : BaseJellyfinApiController
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="searchManager">Instance of the <see cref="ISearchManager"/> interface.</param>
public ItemsController(
IUserManager userManager,
ILibraryManager libraryManager,
@@ -59,7 +62,8 @@ public class ItemsController : BaseJellyfinApiController
IDtoService dtoService,
ILogger<ItemsController> logger,
ISessionManager sessionManager,
- IUserDataManager userDataRepository)
+ IUserDataManager userDataRepository,
+ ISearchManager searchManager)
{
_userManager = userManager;
_libraryManager = libraryManager;
@@ -68,6 +72,7 @@ public class ItemsController : BaseJellyfinApiController
_logger = logger;
_sessionManager = sessionManager;
_userDataRepository = userDataRepository;
+ _searchManager = searchManager;
}
/// <summary>
@@ -298,7 +303,7 @@ public class ItemsController : BaseJellyfinApiController
if (collectionType == CollectionType.playlists)
{
recursive = true;
- includeItemTypes = new[] { BaseItemKind.Playlist };
+ includeItemTypes = [BaseItemKind.Playlist];
}
else if (folder is ICollectionFolder)
{
@@ -328,6 +333,34 @@ public class ItemsController : BaseJellyfinApiController
if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder)
{
+ // Use search providers when searchTerm is provided. Providers return only IDs and scores;
+ // items are loaded server-side via folder.GetItems below, which applies user-access filtering.
+ Dictionary<Guid, float>? searchResultScores = null;
+ Guid[] itemIds = ids;
+
+ if (!string.IsNullOrWhiteSpace(searchTerm))
+ {
+ var searchProviderQuery = new SearchProviderQuery
+ {
+ SearchTerm = searchTerm,
+ UserId = userId,
+ IncludeItemTypes = includeItemTypes,
+ ExcludeItemTypes = excludeItemTypes,
+ MediaTypes = mediaTypes,
+ Limit = limit.HasValue ? limit.Value * 3 : null,
+ ParentId = parentId
+ };
+
+ var searchResults = await _searchManager.GetSearchResultsAsync(searchProviderQuery, HttpContext.RequestAborted).ConfigureAwait(false);
+ if (searchResults.Count > 0)
+ {
+ searchResultScores = searchResults.ToDictionary(r => r.ItemId, r => r.Score);
+ itemIds = ids.Length > 0
+ ? ids.Concat(searchResultScores.Keys).Distinct().ToArray()
+ : searchResultScores.Keys.ToArray();
+ }
+ }
+
var query = new InternalItemsQuery(user)
{
IsPlayed = isPlayed,
@@ -337,8 +370,8 @@ public class ItemsController : BaseJellyfinApiController
Recursive = recursive ?? false,
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
IsFavorite = isFavorite,
- Limit = limit,
- StartIndex = startIndex,
+ Limit = searchResultScores is not null ? null : limit,
+ StartIndex = searchResultScores is not null ? null : startIndex,
IsMissing = isMissing,
IsUnaired = isUnaired,
CollapseBoxSetItems = collapseBoxSetItems,
@@ -385,7 +418,7 @@ public class ItemsController : BaseJellyfinApiController
ImageTypes = imageTypes,
VideoTypes = videoTypes,
AdjacentTo = adjacentTo,
- ItemIds = ids,
+ ItemIds = itemIds,
MinCommunityRating = minCommunityRating,
MinCriticRating = minCriticRating,
ParentId = parentId ?? Guid.Empty,
@@ -394,7 +427,7 @@ public class ItemsController : BaseJellyfinApiController
EnableTotalRecordCount = enableTotalRecordCount,
ExcludeItemIds = excludeItemIds,
DtoOptions = dtoOptions,
- SearchTerm = searchTerm,
+ SearchTerm = searchResultScores is null ? searchTerm : null,
MinDateLastSaved = minDateLastSaved?.ToUniversalTime(),
MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(),
MinPremiereDate = minPremiereDate?.ToUniversalTime(),
@@ -476,7 +509,7 @@ public class ItemsController : BaseJellyfinApiController
{
query.AlbumIds = albums.SelectMany(i =>
{
- return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, Name = i, Limit = 1 });
+ return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.MusicAlbum], Name = i, Limit = 1 });
}).ToArray();
}
@@ -502,12 +535,37 @@ public class ItemsController : BaseJellyfinApiController
// Albums by artist
if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.MusicAlbum)
{
- query.OrderBy = new[] { (ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending) };
+ query.OrderBy = [(ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending)];
}
}
query.Parent = null;
+
+ // folder.GetItems applies user-access filtering via the InternalItemsQuery's User.
result = folder.GetItems(query);
+ if (searchResultScores is not null && searchResultScores.Count > 0)
+ {
+ var orderedItems = result.Items
+ .OrderByDescending(item => searchResultScores.GetValueOrDefault(item.Id, 0f))
+ .ThenBy(item => item.SortName)
+ .ToArray();
+
+ var totalCount = orderedItems.Length;
+ if (startIndex.HasValue && startIndex.Value > 0)
+ {
+ orderedItems = orderedItems.Skip(startIndex.Value).ToArray();
+ }
+
+ if (limit.HasValue)
+ {
+ orderedItems = orderedItems.Take(limit.Value).ToArray();
+ }
+
+ return new QueryResult<BaseItemDto>(
+ startIndex,
+ totalCount,
+ _dtoService.GetBaseItemDtos(orderedItems, dtoOptions, user));
+ }
}
else
{
@@ -861,7 +919,7 @@ public class ItemsController : BaseJellyfinApiController
var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{
- OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
+ OrderBy = [(ItemSortBy.DatePlayed, SortOrder.Descending)],
IsResumable = true,
StartIndex = startIndex,
Limit = limit,