diff options
Diffstat (limited to 'Emby.Server.Implementations/Dto/DtoService.cs')
| -rw-r--r-- | Emby.Server.Implementations/Dto/DtoService.cs | 139 |
1 files changed, 122 insertions, 17 deletions
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 2462a754ae..e539508644 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using Jellyfin.Data; using Jellyfin.Data.Enums; using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; @@ -185,15 +186,18 @@ namespace Emby.Server.Implementations.Dto allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); } - // Batch-fetch child counts for all folders to avoid N+1 queries + // Batch-fetch by-name item counts to avoid N+1 queries + Dictionary<Guid, ItemCounts>? itemCountsBatch = null; + if (options.ContainsField(ItemFields.ItemCounts)) + { + itemCountsBatch = GetItemCountsBatch(accessibleItems, user); + } + + // Batch-fetch child counts for all folders to avoid N+1 queries. Dictionary<Guid, int>? childCountBatch = null; - if (options.ContainsField(ItemFields.ChildCount)) + if (user is not null && options.ContainsField(ItemFields.ChildCount)) { - var folderIds = accessibleItems.OfType<Folder>().Select(f => f.Id).ToList(); - if (folderIds.Count > 0) - { - childCountBatch = _libraryManager.GetChildCountBatch(folderIds, user?.Id); - } + childCountBatch = GetChildCountBatch(accessibleItems, user); } // Batch-fetch played/total counts for all folders to avoid N+1 queries @@ -293,7 +297,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.ItemCounts)) { - SetItemByNameInfo(dto, user); + SetItemByNameInfo(dto, user, itemCountsBatch); } returnItems[index] = dto; @@ -518,14 +522,36 @@ namespace Emby.Server.Implementations.Dto return dto; } - private void SetItemByNameInfo(BaseItemDto dto, User? user) + private Dictionary<Guid, ItemCounts> GetItemCountsBatch(IReadOnlyList<BaseItem> items, User? user) + { + var result = new Dictionary<Guid, ItemCounts>(); + + foreach (var group in items.GroupBy(item => item.GetBaseItemKind())) + { + if (!_relatedItemKinds.TryGetValue(group.Key, out var relatedItemKinds)) + { + continue; + } + + var ids = group.Select(item => item.Id).ToArray(); + foreach (var (id, counts) in _libraryManager.GetItemCountsForNameItems(group.Key, ids, relatedItemKinds, user)) + { + result[id] = counts; + } + } + + return result; + } + + private void SetItemByNameInfo(BaseItemDto dto, User? user, IReadOnlyDictionary<Guid, ItemCounts>? prefetchedCounts = null) { if (!_relatedItemKinds.TryGetValue(dto.Type, out var relatedItemKinds)) { return; } - var counts = _libraryManager.GetItemCountsForNameItem(dto.Type, dto.Id, relatedItemKinds, user); + var counts = prefetchedCounts?.GetValueOrDefault(dto.Id) + ?? _libraryManager.GetItemCountsForNameItem(dto.Type, dto.Id, relatedItemKinds, user); dto.AlbumCount = counts.AlbumCount; dto.ArtistCount = counts.ArtistCount; @@ -685,23 +711,102 @@ namespace Emby.Server.Implementations.Dto }; } - private static int GetChildCount(Folder folder, User user, Dictionary<Guid, int>? childCountBatch) + private Dictionary<Guid, int>? GetChildCountBatch(IReadOnlyList<BaseItem> items, User user) { - // Right now this is too slow to calculate for top level folders on a per-user basis - // Just return something so that apps that are expecting a value won't think the folders are empty - if (folder is ICollectionFolder || folder is UserView) + Dictionary<Guid, IReadOnlyList<Guid>>? sources = null; + foreach (var folder in items.OfType<Folder>()) + { + var sourceIds = GetChildCountSourceIds(folder, user); + if (sourceIds.Count > 0) + { + (sources ??= new Dictionary<Guid, IReadOnlyList<Guid>>())[folder.Id] = sourceIds; + } + } + + if (sources is null) { - return Random.Shared.Next(1, 10); + return null; } + var counts = _libraryManager.GetChildCountBatch( + sources.Values.SelectMany(ids => ids).Distinct().ToList(), + user); + + var result = new Dictionary<Guid, int>(sources.Count); + foreach (var (folderId, sourceIds) in sources) + { + var total = 0; + foreach (var sourceId in sourceIds) + { + total += counts.GetValueOrDefault(sourceId); + } + + result[folderId] = total; + } + + return result; + } + + private IReadOnlyList<Guid> GetChildCountSourceIds(Folder folder, User user) + { + if (folder is CollectionFolder collectionFolder) + { + return collectionFolder.PhysicalFolderIds; + } + + if (folder is not UserView view) + { + return [folder.Id]; + } + + // Only a view that stands for a library proxies it. The sub-views a movie or show view + // is built from hang off the same library but hold a query, not the library's children. + if (!UserView.EnableOriginalFolder(view.ViewType) + && view.ViewType is not (CollectionType.movies or CollectionType.tvshows)) + { + return []; + } + + // A view over a single library proxies that library, whatever the view type. + var parentId = view.DisplayParentId.IsEmpty() ? view.ParentId : view.DisplayParentId; + if (!parentId.IsEmpty() + && !parentId.Equals(view.Id) + && _libraryManager.GetItemById(parentId) is Folder parent + && parent is not UserView) + { + return GetChildCountSourceIds(parent, user); + } + + // A grouped view has no single parent: it stands for every library the user grouped + // into it, the same set UserViewManager builds the view from. + if (view.ViewType is CollectionType.movies or CollectionType.tvshows) + { + return _libraryManager.GetUserRootFolder() + .GetChildren(user, true) + .OfType<CollectionFolder>() + .Where(f => user.IsFolderGrouped(f.Id) + && (f.CollectionType == view.ViewType || f.CollectionType is null)) + .SelectMany(f => f.PhysicalFolderIds) + .Distinct() + .ToList(); + } + + return []; + } + + private int GetChildCount(Folder folder, User user, Dictionary<Guid, int>? childCountBatch) + { // Use pre-fetched batch data if available if (childCountBatch is not null && childCountBatch.TryGetValue(folder.Id, out var count)) { return count; } - // Fall back to individual query for special cases (Series, Season, etc.) - return folder.GetChildCount(user); + // No batch covered this folder. + var single = GetChildCountBatch([folder], user); + return single is not null && single.TryGetValue(folder.Id, out var singleCount) + ? singleCount + : folder.GetChildCount(user); } private static void SetBookProperties(BaseItemDto dto, Book item) |
