diff options
| author | Shadowghost <Shadowghost@users.noreply.github.com> | 2026-09-15 11:15:46 -0400 |
|---|---|---|
| committer | Cody Robibero <cody@robibe.ro> | 2026-09-15 11:15:46 -0400 |
| commit | 006ecadbe041ea422b34adf6a3fcf6638d189a70 (patch) | |
| tree | c845d30aafd6cb23bf2e41dd955712db0f641ce4 /Jellyfin.Server.Implementations/Item/ItemCountService.cs | |
| parent | 65bc888b07a25d1883d4d0a2f55a7f3e1ac35c87 (diff) | |
Backport pull request #17881 from jellyfin/release-12.z
Fix /UserViews exhausting memory and reporting random child counts
Original-merge: a838a06aa51eac388a76e9e6421f1a80873c417b
Merged-by: crobibero <cody@robibe.ro>
Backported-by: Cody Robibero <cody@robibe.ro>
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/ItemCountService.cs')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/ItemCountService.cs | 34 |
1 files changed, 25 insertions, 9 deletions
diff --git a/Jellyfin.Server.Implementations/Item/ItemCountService.cs b/Jellyfin.Server.Implementations/Item/ItemCountService.cs index 57705cdf11..f8903127b9 100644 --- a/Jellyfin.Server.Implementations/Item/ItemCountService.cs +++ b/Jellyfin.Server.Implementations/Item/ItemCountService.cs @@ -483,7 +483,19 @@ public class ItemCountService : IItemCountService var includeVirtual = user is null || user.DisplayMissingEpisodes; - var hierarchicalCounts = dbContext.BaseItems + var accessibleItems = dbContext.BaseItems.AsNoTracking(); + if (user is null) + { + // Access filtering is what would otherwise drop an alternate version, and a child count + // must not report a title twice just because no user was passed in. + accessibleItems = accessibleItems.Where(DescendantQueryHelper.IsDistinctLibraryItem); + } + else + { + accessibleItems = _queryHelpers.ApplyAccessFiltering(dbContext, accessibleItems, new InternalItemsQuery(user)); + } + + var hierarchicalCounts = accessibleItems .Where(b => b.ParentId.HasValue && !b.SeasonId.HasValue && (includeVirtual || !b.IsVirtualItem)) .WhereOneOrMany(parentIdsArray, b => b.ParentId!.Value) .GroupBy(b => b.ParentId!.Value) @@ -493,20 +505,22 @@ public class ItemCountService : IItemCountService // An episode is a child of its season even when it is not stored under one: with a flat // structure ParentId points at the series, so counting by ParentId alone leaves the season // empty and counts its episodes towards the series instead. - var seasonCounts = dbContext.BaseItems + var seasonCounts = accessibleItems .Where(b => b.SeasonId.HasValue && (includeVirtual || !b.IsVirtualItem)) .WhereOneOrMany(parentIdsArray, b => b.SeasonId!.Value) .GroupBy(b => b.SeasonId!.Value) .Select(g => new { SeasonId = g.Key, Count = g.Count() }) .ToDictionary(x => x.SeasonId, x => x.Count); + // A linked child counts only when the item it points at is one the user may open. var linkedCounts = dbContext.LinkedChildren .WhereOneOrMany(parentIdsArray, lc => lc.ParentId) - .GroupBy(lc => lc.ParentId) + .Join(accessibleItems, lc => lc.ChildId, b => b.Id, (lc, b) => lc.ParentId) + .GroupBy(parentId => parentId) .Select(g => new { ParentId = g.Key, Count = g.Count() }) .ToDictionary(x => x.ParentId, x => x.Count); - var mergedChildCounts = GetMergedChildCounts(dbContext, parentIdsArray, includeVirtual); + var mergedChildCounts = GetMergedChildCounts(dbContext, accessibleItems, parentIdsArray, includeVirtual); var result = new Dictionary<Guid, int>(); foreach (var parentId in parentIds) @@ -527,7 +541,11 @@ public class ItemCountService : IItemCountService return result; } - private static Dictionary<Guid, int> GetMergedChildCounts(JellyfinDbContext dbContext, IReadOnlyList<Guid> parentIds, bool includeVirtual) + private static Dictionary<Guid, int> GetMergedChildCounts( + JellyfinDbContext dbContext, + IQueryable<BaseItemEntity> accessibleItems, + IReadOnlyList<Guid> parentIds, + bool includeVirtual) { var mergedGroups = GetPresentationKeyGroups(dbContext, parentIds) .Where(group => group.Value.Count > 1) @@ -540,14 +558,12 @@ public class ItemCountService : IItemCountService // Only merged folders. var memberIds = mergedGroups.SelectMany(group => group.Value).Distinct().ToArray(); - var children = dbContext.BaseItems - .AsNoTracking() + var children = accessibleItems .Where(b => b.ParentId.HasValue && !b.SeasonId.HasValue && (includeVirtual || !b.IsVirtualItem)) .WhereOneOrMany(memberIds, b => b.ParentId!.Value) .Select(b => new { ParentId = b.ParentId!.Value, b.Id, b.PresentationUniqueKey }) .ToArray() - .Concat(dbContext.BaseItems - .AsNoTracking() + .Concat(accessibleItems .Where(b => b.SeasonId.HasValue && (includeVirtual || !b.IsVirtualItem)) .WhereOneOrMany(memberIds, b => b.SeasonId!.Value) .Select(b => new { ParentId = b.SeasonId!.Value, b.Id, b.PresentationUniqueKey }) |
