aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Item/ItemCountService.cs
diff options
context:
space:
mode:
authorShadowghost <Shadowghost@users.noreply.github.com>2026-09-15 11:15:46 -0400
committerCody Robibero <cody@robibe.ro>2026-09-15 11:15:46 -0400
commit006ecadbe041ea422b34adf6a3fcf6638d189a70 (patch)
treec845d30aafd6cb23bf2e41dd955712db0f641ce4 /Jellyfin.Server.Implementations/Item/ItemCountService.cs
parent65bc888b07a25d1883d4d0a2f55a7f3e1ac35c87 (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.cs34
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 })