diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-05-04 21:26:26 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-05-04 21:26:26 +0200 |
| commit | 57c0fcd674c659c658369f0aebfd5d9d6787a9d4 (patch) | |
| tree | 7aff23d6f54e913a6a34cb5a2568a07298582444 /Emby.Server.Implementations/Dto | |
| parent | 68ab58589444091925c15ad20d36f935b7bc2e21 (diff) | |
| parent | ec04313317bed62728b059108cd232e9744f6354 (diff) | |
Merge remote-tracking branch 'upstream/master' into epg-fixes
Diffstat (limited to 'Emby.Server.Implementations/Dto')
| -rw-r--r-- | Emby.Server.Implementations/Dto/DtoService.cs | 188 |
1 files changed, 140 insertions, 48 deletions
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 08ced387b8..cc57d183b6 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -153,17 +153,68 @@ namespace Emby.Server.Implementations.Dto private ILiveTvManager LivetvManager => _livetvManagerFactory.Value; /// <inheritdoc /> - public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User? user = null, BaseItem? owner = null) + public IReadOnlyList<BaseItemDto> GetBaseItemDtos( + IReadOnlyList<BaseItem> items, + DtoOptions options, + User? user = null, + BaseItem? owner = null, + bool skipVisibilityCheck = false) { - var accessibleItems = user is null ? items : items.Where(x => x.IsVisible(user)).ToList(); + var accessibleItems = skipVisibilityCheck || user is null ? items : items.Where(x => x.IsVisible(user)).ToList(); var returnItems = new BaseItemDto[accessibleItems.Count]; List<(BaseItem, BaseItemDto)>? programTuples = null; List<(BaseItemDto, LiveTvChannel)>? channelTuples = null; + // Batch-fetch user data for all items + Dictionary<Guid, UserItemData>? userDataBatch = null; + if (user is not null && options.EnableUserData) + { + userDataBatch = _userDataRepository.GetUserDataBatch(accessibleItems, user); + } + + // Pre-compute collection folders once to avoid N+1 queries in CanDelete + List<Folder>? allCollectionFolders = null; + if (user is not null && options.ContainsField(ItemFields.CanDelete)) + { + allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList(); + } + + // Batch-fetch child counts for all folders to avoid N+1 queries + Dictionary<Guid, int>? childCountBatch = null; + if (options.ContainsField(ItemFields.ChildCount)) + { + var folderIds = accessibleItems.OfType<Folder>().Select(f => f.Id).ToList(); + if (folderIds.Count > 0) + { + childCountBatch = _libraryManager.GetChildCountBatch(folderIds, user?.Id); + } + } + + // Batch-fetch played/total counts for all folders to avoid N+1 queries + Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null; + if (user is not null && options.EnableUserData) + { + var folderIds = accessibleItems.OfType<Folder>() + .Where(f => f.SupportsUserDataFromChildren && (f.SupportsPlayedStatus || options.ContainsField(ItemFields.RecursiveItemCount))) + .Select(f => f.Id).ToList(); + if (folderIds.Count > 0) + { + playedCountBatch = _libraryManager.GetPlayedAndTotalCountBatch(folderIds, user); + } + } + for (int index = 0; index < accessibleItems.Count; index++) { var item = accessibleItems[index]; - var dto = GetBaseItemDtoInternal(item, options, user, owner); + var dto = GetBaseItemDtoInternal( + item, + options, + user, + owner, + userDataBatch?.GetValueOrDefault(item.Id), + allCollectionFolders, + childCountBatch, + playedCountBatch); if (item is LiveTvChannel tvChannel) { @@ -197,7 +248,7 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null) { - var dto = GetBaseItemDtoInternal(item, options, user, owner); + var dto = GetBaseItemDtoInternal(item, options, user, owner, null); if (item is LiveTvChannel tvChannel) { LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user); @@ -215,7 +266,15 @@ namespace Emby.Server.Implementations.Dto return dto; } - private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null) + private BaseItemDto GetBaseItemDtoInternal( + BaseItem item, + DtoOptions options, + User? user = null, + BaseItem? owner = null, + UserItemData? userData = null, + List<Folder>? allCollectionFolders = null, + Dictionary<Guid, int>? childCountBatch = null, + Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null) { var dto = new BaseItemDto { @@ -252,7 +311,14 @@ namespace Emby.Server.Implementations.Dto if (user is not null) { - AttachUserSpecificInfo(dto, item, user, options); + AttachUserSpecificInfo( + dto, + item, + user, + options, + userData, + childCountBatch, + playedCountBatch); } if (item is IHasMediaSources @@ -274,7 +340,9 @@ namespace Emby.Server.Implementations.Dto { dto.CanDelete = user is null ? item.CanDelete() - : item.CanDelete(user); + : allCollectionFolders is not null + ? item.CanDelete(user, allCollectionFolders) + : item.CanDelete(user); } if (options.ContainsField(ItemFields.CanDownload)) @@ -378,37 +446,7 @@ namespace Emby.Server.Implementations.Dto return; } - var query = new InternalItemsQuery(user) - { - Recursive = true, - DtoOptions = new DtoOptions(false) { EnableImages = false }, - IncludeItemTypes = relatedItemKinds - }; - - switch (dto.Type) - { - case BaseItemKind.Genre: - case BaseItemKind.MusicGenre: - query.GenreIds = [dto.Id]; - break; - case BaseItemKind.MusicArtist: - query.ArtistIds = [dto.Id]; - break; - case BaseItemKind.Person: - query.PersonIds = [dto.Id]; - break; - case BaseItemKind.Studio: - query.StudioIds = [dto.Id]; - break; - case BaseItemKind.Year - when int.TryParse(dto.Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year): - query.Years = [year]; - break; - default: - return; - } - - var counts = _libraryManager.GetItemCounts(query); + var counts = _libraryManager.GetItemCountsForNameItem(dto.Type, dto.Id, relatedItemKinds, user); dto.AlbumCount = counts.AlbumCount; dto.ArtistCount = counts.ArtistCount; @@ -458,7 +496,14 @@ namespace Emby.Server.Implementations.Dto /// <summary> /// Attaches the user specific info. /// </summary> - private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options) + private void AttachUserSpecificInfo( + BaseItemDto dto, + BaseItem item, + User user, + DtoOptions options, + UserItemData? userData = null, + Dictionary<Guid, int>? childCountBatch = null, + Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null) { if (item.IsFolder) { @@ -466,7 +511,19 @@ namespace Emby.Server.Implementations.Dto if (options.EnableUserData) { - dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options); + if (userData is not null) + { + // Use pre-fetched user data + dto.UserData = GetUserItemDataDto(userData, item.Id); + (int Played, int Total)? precomputed = playedCountBatch is not null + && playedCountBatch.TryGetValue(item.Id, out var counts) ? counts : null; + item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options, precomputed); + } + else + { + // Fall back to individual fetch + dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options); + } } if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library) @@ -485,7 +542,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.ChildCount)) { - dto.ChildCount ??= GetChildCount(folder, user); + dto.ChildCount ??= GetChildCount(folder, user, childCountBatch); } } @@ -503,7 +560,17 @@ namespace Emby.Server.Implementations.Dto { if (options.EnableUserData) { - dto.UserData = _userDataRepository.GetUserDataDto(item, user); + if (userData is not null) + { + // Use pre-fetched user data + dto.UserData = GetUserItemDataDto(userData, item.Id); + item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options); + } + else + { + // Fall back to individual fetch + dto.UserData = _userDataRepository.GetUserDataDto(item, user); + } } } @@ -513,7 +580,25 @@ namespace Emby.Server.Implementations.Dto } } - private static int GetChildCount(Folder folder, User user) + private static UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId) + { + ArgumentNullException.ThrowIfNull(data); + + return new UserItemDataDto + { + IsFavorite = data.IsFavorite, + Likes = data.Likes, + PlaybackPositionTicks = data.PlaybackPositionTicks, + PlayCount = data.PlayCount, + Rating = data.Rating, + Played = data.Played, + LastPlayedDate = data.LastPlayedDate, + ItemId = itemId, + Key = data.Key + }; + } + + private static int GetChildCount(Folder folder, User user, Dictionary<Guid, int>? childCountBatch) { // 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 @@ -522,6 +607,13 @@ namespace Emby.Server.Implementations.Dto return Random.Shared.Next(1, 10); } + // 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); } @@ -1132,11 +1224,6 @@ namespace Emby.Server.Implementations.Dto } } - if (options.ContainsField(ItemFields.Chapters)) - { - dto.Chapters = _chapterManager.GetChapters(item.Id).ToList(); - } - if (options.ContainsField(ItemFields.Trickplay)) { var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult(); @@ -1150,6 +1237,11 @@ namespace Emby.Server.Implementations.Dto dto.ExtraType = video.ExtraType; } + if (options.ContainsField(ItemFields.Chapters)) + { + dto.Chapters = _chapterManager.GetChapters(item.Id).ToList(); + } + if (options.ContainsField(ItemFields.MediaStreams)) { // Add VideoInfo |
