From fe1d8d88401a5536b03bb99bd0f4f126665bf3a0 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sun, 7 Jun 2026 22:37:34 +0200 Subject: Collapse version groups to the primary version in queries --- .../Item/BaseItemRepository.QueryBuilding.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs') diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs index d6ddf8f5c8..decd45ae2c 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs @@ -62,18 +62,21 @@ public sealed partial class BaseItemRepository private IQueryable ApplyGroupingFilter(JellyfinDbContext context, IQueryable dbQuery, InternalItemsQuery filter) { - // Collapse duplicates sharing a presentation key (e.g. alternate versions) by picking - // the min Id per group. Keep the grouped ids as an IQueryable sub-select; materializing + // Collapse duplicates sharing a presentation key (e.g. alternate versions), preferring the + // primary version (PrimaryVersionId is null) so detail pages and actions target it instead + // of an arbitrary alternate. Keep the grouped ids as an IQueryable sub-select; materializing // to a List would inline one bound parameter per id and hit SQLite's variable cap. var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter); if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey) { - var groupedIds = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.Min(x => x.Id)); + var groupedIds = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }) + .Select(g => g.Where(e => e.PrimaryVersionId == null).Min(e => (Guid?)e.Id) ?? g.Min(e => (Guid?)e.Id)); dbQuery = context.BaseItems.AsNoTracking().Where(e => groupedIds.Contains(e.Id)); } else if (enableGroupByPresentationUniqueKey) { - var groupedIds = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.Min(x => x.Id)); + var groupedIds = dbQuery.GroupBy(e => e.PresentationUniqueKey) + .Select(g => g.Where(e => e.PrimaryVersionId == null).Min(e => (Guid?)e.Id) ?? g.Min(e => (Guid?)e.Id)); dbQuery = context.BaseItems.AsNoTracking().Where(e => groupedIds.Contains(e.Id)); } else if (filter.GroupBySeriesPresentationUniqueKey) -- cgit v1.2.3 From 5a9fb802395d7c8f919a73aca84fea5ae3d79aee Mon Sep 17 00:00:00 2001 From: mbastian77 Date: Sat, 18 Jul 2026 14:07:52 +0200 Subject: Exempt people from the allowed tags visibility check --- .../Item/BaseItemRepository.QueryBuilding.cs | 6 +++++- .../Item/BaseItemRepository.TranslateQuery.cs | 4 ++++ MediaBrowser.Controller/Entities/Person.cs | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs') diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs index decd45ae2c..a4de9feb05 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs @@ -447,6 +447,7 @@ public sealed partial class BaseItemRepository if (filter.IncludeInheritedTags.Length > 0) { var includeTags = filter.IncludeInheritedTags.Select(e => e.GetCleanValue()).ToArray(); + var personTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]; var allowedTagItemIds = context.ItemValuesMap .Where(f => f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue)) .Select(f => f.ItemId); @@ -455,7 +456,10 @@ public sealed partial class BaseItemRepository allowedTagItemIds.Contains(e.Id) || (e.SeriesId.HasValue && allowedTagItemIds.Contains(e.SeriesId.Value)) || e.Parents!.Any(p => allowedTagItemIds.Contains(p.ParentItemId)) - || (e.TopParentId.HasValue && allowedTagItemIds.Contains(e.TopParentId.Value))); + || (e.TopParentId.HasValue && allowedTagItemIds.Contains(e.TopParentId.Value)) + + // People don't carry the tags of the media they appear in and would never match + || e.Type == personTypeName); } // Exclude alternate versions (have PrimaryVersionId set) and owned non-extra items. diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs index 52cebccc37..d69a589d65 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs @@ -1084,6 +1084,7 @@ public sealed partial class BaseItemRepository { var includeTags = filter.IncludeInheritedTags.Select(e => e.GetCleanValue()).ToArray(); var isPlaylistOnlyQuery = includeTypes.Length == 1 && includeTypes.FirstOrDefault() == BaseItemKind.Playlist; + var personTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]; var allowedTagItemIds = context.ItemValuesMap .Where(f => f.ItemValue.Type == ItemValueType.Tags && includeTags.Contains(f.ItemValue.CleanValue)) .Select(f => f.ItemId); @@ -1094,6 +1095,9 @@ public sealed partial class BaseItemRepository || e.Parents!.Any(p => allowedTagItemIds.Contains(p.ParentItemId)) || (e.TopParentId.HasValue && allowedTagItemIds.Contains(e.TopParentId.Value)) + // People don't carry the tags of the media they appear in and would never match + || e.Type == personTypeName + // A playlist should be accessible to its owner regardless of allowed tags || (isPlaylistOnlyQuery && e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\""))); } diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 5cc4d322f7..14325d971a 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; using MediaBrowser.Controller.Providers; using Microsoft.Extensions.Logging; @@ -75,6 +76,16 @@ namespace MediaBrowser.Controller.Entities return false; } + /// + /// + /// People don't carry the tags of the media they appear in, so the allowed tags check + /// is skipped for them; otherwise no person would be visible to users with allowed tags configured. + /// + public override bool IsVisible(User user, bool skipAllowedTagsCheck = false) + { + return base.IsVisible(user, true); + } + public override bool IsSaveLocalMetadataEnabled() { return true; -- cgit v1.2.3