diff options
6 files changed, 61 insertions, 20 deletions
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 062c19a1d4..6fa057702c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -261,7 +261,7 @@ namespace Emby.Server.Implementations.Dto var versionItemIds = accessibleItems.OfType<Video>().Select(i => i.Id).ToList(); if (versionItemIds.Count > 0) { - alternateVersionItemIds = _libraryManager.GetItemsWithAlternateVersions(versionItemIds); + alternateVersionItemIds = _libraryManager.GetItemIdsWithAlternateVersions(versionItemIds); } } @@ -1314,13 +1314,15 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.MediaSourceCount)) { // A video with no primary version and no alternate versions always has a single - // media source. When the batch has already determined this item owns no alternate - // versions, skip the per-item alternate-version queries entirely (the common case). - var hasNoAlternateVersions = alternateVersionItemIds is not null - && !video.PrimaryVersionId.HasValue - && !alternateVersionItemIds.Contains(video.Id); - - if (!hasNoAlternateVersions) + // media source. Only compute the count for videos that might have more: a primary + // version, or membership in the batch's set of items that own alternate versions. + // Without the batch we can't rule it out, so fall back to computing (the single-item + // path). Everything else is the common case and keeps the default count of one. + var mayHaveAlternateVersions = alternateVersionItemIds is null + || video.PrimaryVersionId.HasValue + || alternateVersionItemIds.Contains(video.Id); + + if (mayHaveAlternateVersions) { // Match the per-user filtering of the media sources: versions the user cannot // access are not selectable, so they must not count towards the badge either. diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 9bb962c504..0c7c411d0c 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2235,9 +2235,9 @@ namespace Emby.Server.Implementations.Library } /// <inheritdoc /> - public IReadOnlySet<Guid> GetItemsWithAlternateVersions(IReadOnlyList<Guid> itemIds) + public IReadOnlySet<Guid> GetItemIdsWithAlternateVersions(IReadOnlyList<Guid> itemIds) { - return _linkedChildrenService.GetItemsWithAlternateVersions(itemIds); + return _linkedChildrenService.GetItemIdsWithAlternateVersions(itemIds); } /// <inheritdoc /> diff --git a/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs b/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs index 2452f8e3c6..a96e941c88 100644 --- a/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs +++ b/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs @@ -60,7 +60,7 @@ public class LinkedChildrenService : ILinkedChildrenService } /// <inheritdoc/> - public IReadOnlySet<Guid> GetItemsWithAlternateVersions(IReadOnlyList<Guid> itemIds) + public IReadOnlySet<Guid> GetItemIdsWithAlternateVersions(IReadOnlyList<Guid> itemIds) { if (itemIds.Count == 0) { @@ -69,15 +69,13 @@ public class LinkedChildrenService : ILinkedChildrenService using var dbContext = _dbProvider.CreateDbContext(); - var parentIds = dbContext.LinkedChildren + return dbContext.LinkedChildren .Where(lc => (lc.ChildType == DbLinkedChildType.LocalAlternateVersion || lc.ChildType == DbLinkedChildType.LinkedAlternateVersion) && itemIds.Contains(lc.ParentId)) .Select(lc => lc.ParentId) .Distinct() - .ToArray(); - - return parentIds.ToHashSet(); + .ToHashSet(); } /// <inheritdoc/> diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index f6bd948b99..ca686fbd9d 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -261,7 +261,7 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="itemIds">The item IDs to check.</param> /// <returns>The set of item IDs that have alternate versions.</returns> - IReadOnlySet<Guid> GetItemsWithAlternateVersions(IReadOnlyList<Guid> itemIds); + IReadOnlySet<Guid> GetItemIdsWithAlternateVersions(IReadOnlyList<Guid> itemIds); /// <summary> /// Creates or updates a LinkedChild entry linking a parent to a child item. diff --git a/MediaBrowser.Controller/Persistence/ILinkedChildrenService.cs b/MediaBrowser.Controller/Persistence/ILinkedChildrenService.cs index c1fe3231f4..79c29410e4 100644 --- a/MediaBrowser.Controller/Persistence/ILinkedChildrenService.cs +++ b/MediaBrowser.Controller/Persistence/ILinkedChildrenService.cs @@ -26,7 +26,7 @@ public interface ILinkedChildrenService /// </summary> /// <param name="itemIds">The item IDs to check.</param> /// <returns>The set of item IDs that have alternate versions.</returns> - IReadOnlySet<Guid> GetItemsWithAlternateVersions(IReadOnlyList<Guid> itemIds); + IReadOnlySet<Guid> GetItemIdsWithAlternateVersions(IReadOnlyList<Guid> itemIds); /// <summary> /// Gets all artist matches from the database. diff --git a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs index fa94250287..6a3dcab57a 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs @@ -222,11 +222,11 @@ public class DtoServiceImageInheritanceTests var libraryManager = new Mock<ILibraryManager>(); // DtoService detects which videos own alternate versions in ONE batch - // (GetItemsWithAlternateVersions) before the per-item loop. Videos absent from that set have a + // (GetItemIdsWithAlternateVersions) before the per-item loop. Videos absent from that set have a // single media source, so the per-item GetLinkedAlternateVersions/GetLocalAlternateVersionIds // queries (the N+1) must be skipped entirely. Here neither movie has alternate versions. libraryManager - .Setup(x => x.GetItemsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>())) + .Setup(x => x.GetItemIdsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>())) .Returns(new HashSet<Guid>()); var dtoService = BuildDtoService(libraryManager); @@ -236,13 +236,54 @@ public class DtoServiceImageInheritanceTests Assert.Equal(2, dtos.Count); + // A single media source is the default, so the count is left unset (the client treats null as one). + foreach (var dto in dtos) + { + Assert.Null(dto.MediaSourceCount); + } + // The alternate-version check is batched once for the whole set, and the per-item lookups are // never reached because the batch already ruled out alternate versions. - libraryManager.Verify(x => x.GetItemsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>()), Times.Once); + libraryManager.Verify(x => x.GetItemIdsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>()), Times.Once); libraryManager.Verify(x => x.GetLinkedAlternateVersions(It.IsAny<Video>()), Times.Never); libraryManager.Verify(x => x.GetLocalAlternateVersionIds(It.IsAny<Video>()), Times.Never); } + [Fact] + public void GetBaseItemDtos_VideoInAlternateVersionBatch_ResolvesRealCount() + { + var movie = new Movie + { + Id = Guid.NewGuid(), + Name = "Movie", + ImageInfos = [] + }; + + var libraryManager = new Mock<ILibraryManager>(); + + // This movie IS in the batch set, so the fast path must not short-circuit it: the per-item + // lookups still run and the count is computed exactly as it was before batching. Two linked + // alternate versions plus the movie itself is a count of three. + libraryManager + .Setup(x => x.GetItemIdsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>())) + .Returns(new HashSet<Guid> { movie.Id }); + libraryManager + .Setup(x => x.GetLinkedAlternateVersions(It.IsAny<Video>())) + .Returns([new Movie { Id = Guid.NewGuid() }, new Movie { Id = Guid.NewGuid() }]); + libraryManager + .Setup(x => x.GetLocalAlternateVersionIds(It.IsAny<Video>())) + .Returns([]); + + var dtoService = BuildDtoService(libraryManager); + + var options = new DtoOptions(false) { Fields = [ItemFields.MediaSourceCount] }; + var dtos = dtoService.GetBaseItemDtos([movie], options); + + Assert.Single(dtos); + Assert.Equal(3, dtos[0].MediaSourceCount); + libraryManager.Verify(x => x.GetItemIdsWithAlternateVersions(It.IsAny<IReadOnlyList<Guid>>()), Times.Once); + } + private static DtoService BuildDtoService(BaseItem displayParent) { var libraryManager = new Mock<ILibraryManager>(); |
