Merge pull request #17576 from obiwantoby/perf/batch-mediasourcecount-dto

Bugfix: #17547 | Batching MediaSourceCount into one call
This commit is contained in:
Cody Robibero
2026-08-10 16:15:54 -04:00
committed by GitHub
6 changed files with 164 additions and 12 deletions
+39 -12
View File
@@ -253,6 +253,18 @@ namespace Emby.Server.Implementations.Dto
}
}
// Batch-detect which videos own alternate versions to avoid the per-item alternate-version
// queries in MediaSourceCount. Videos absent from this set have a single media source.
IReadOnlySet<Guid>? alternateVersionItemIds = null;
if (options.ContainsField(ItemFields.MediaSourceCount))
{
var versionItemIds = accessibleItems.OfType<Video>().Select(i => i.Id).ToList();
if (versionItemIds.Count > 0)
{
alternateVersionItemIds = _libraryManager.GetItemIdsWithAlternateVersions(versionItemIds);
}
}
for (int index = 0; index < accessibleItems.Count; index++)
{
var item = accessibleItems[index];
@@ -267,7 +279,8 @@ namespace Emby.Server.Implementations.Dto
playedCountBatch,
artistsBatch,
resumeDataBatch?.GetValueOrDefault(item.Id),
peopleBatch);
peopleBatch,
alternateVersionItemIds);
if (item is LiveTvChannel tvChannel)
{
@@ -330,7 +343,8 @@ namespace Emby.Server.Implementations.Dto
Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null,
VersionResumeData? resumeData = null,
IReadOnlyDictionary<Guid, IReadOnlyList<PersonInfo>>? peopleBatch = null)
IReadOnlyDictionary<Guid, IReadOnlyList<PersonInfo>>? peopleBatch = null,
IReadOnlySet<Guid>? alternateVersionItemIds = null)
{
var dto = new BaseItemDto
{
@@ -399,7 +413,7 @@ namespace Emby.Server.Implementations.Dto
AttachStudios(dto, item);
}
AttachBasicFields(dto, item, owner, options, artistsBatch, user);
AttachBasicFields(dto, item, owner, options, artistsBatch, user, alternateVersionItemIds);
if (options.ContainsField(ItemFields.CanDelete))
{
@@ -984,7 +998,8 @@ namespace Emby.Server.Implementations.Dto
/// <param name="options">The options.</param>
/// <param name="artistsBatch">Optional pre-fetched artist lookup shared across a batch of items.</param>
/// <param name="user">The user, for per-user values such as the accessible media source count.</param>
private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options, IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null, User? user = null)
/// <param name="alternateVersionItemIds">Optional pre-fetched set of item IDs that own alternate versions, shared across a batch of items.</param>
private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options, IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null, User? user = null, IReadOnlySet<Guid>? alternateVersionItemIds = null)
{
if (options.ContainsField(ItemFields.DateCreated))
{
@@ -1298,15 +1313,27 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.MediaSourceCount))
{
// 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.
var mediaSourceCount = user is null
|| (!video.PrimaryVersionId.HasValue && video.LinkedAlternateVersions.Length == 0 && !video.HasLocalAlternateVersions)
? video.MediaSourceCount
: video.GetAllVersions().Count(v => v.Id.Equals(video.Id) || v.IsVisibleStandalone(user));
if (mediaSourceCount != 1)
// A video with no primary version and no alternate versions always has a single
// 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)
{
dto.MediaSourceCount = mediaSourceCount;
// 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.
var mediaSourceCount = user is null
|| (!video.PrimaryVersionId.HasValue && video.LinkedAlternateVersions.Length == 0 && !video.HasLocalAlternateVersions)
? video.MediaSourceCount
: video.GetAllVersions().Count(v => v.Id.Equals(video.Id) || v.IsVisibleStandalone(user));
if (mediaSourceCount != 1)
{
dto.MediaSourceCount = mediaSourceCount;
}
}
}
@@ -2234,6 +2234,12 @@ namespace Emby.Server.Implementations.Library
return [];
}
/// <inheritdoc />
public IReadOnlySet<Guid> GetItemIdsWithAlternateVersions(IReadOnlyList<Guid> itemIds)
{
return _linkedChildrenService.GetItemIdsWithAlternateVersions(itemIds);
}
/// <inheritdoc />
public void UpsertLinkedChild(Guid parentId, Guid childId, MediaBrowser.Controller.Entities.LinkedChildType childType)
{