Refactor to pull item counts in a single query

This commit is contained in:
Cody Robibero 2025-08-11 21:03:55 -06:00
parent 5eef85f027
commit beca405ad4
14 changed files with 207 additions and 233 deletions

View File

@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@ -37,6 +38,77 @@ namespace Emby.Server.Implementations.Dto
{
public class DtoService : IDtoService
{
private static readonly FrozenDictionary<BaseItemKind, BaseItemKind[]> _relatedItemKinds = new Dictionary<BaseItemKind, BaseItemKind[]>
{
{
BaseItemKind.Genre, [
BaseItemKind.Audio,
BaseItemKind.Episode,
BaseItemKind.Movie,
BaseItemKind.LiveTvProgram,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.MusicVideo,
BaseItemKind.Series,
BaseItemKind.Trailer
]
},
{
BaseItemKind.MusicArtist, [
BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicVideo
]
},
{
BaseItemKind.MusicGenre, [
BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.MusicVideo
]
},
{
BaseItemKind.Person, [
BaseItemKind.Audio,
BaseItemKind.Episode,
BaseItemKind.Movie,
BaseItemKind.LiveTvProgram,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.MusicVideo,
BaseItemKind.Series,
BaseItemKind.Trailer
]
},
{
BaseItemKind.Studio, [
BaseItemKind.Audio,
BaseItemKind.Episode,
BaseItemKind.Movie,
BaseItemKind.LiveTvProgram,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.MusicVideo,
BaseItemKind.Series,
BaseItemKind.Trailer
]
},
{
BaseItemKind.Year, [
BaseItemKind.Audio,
BaseItemKind.Episode,
BaseItemKind.Movie,
BaseItemKind.LiveTvProgram,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.MusicVideo,
BaseItemKind.Series,
BaseItemKind.Trailer
]
}
}.ToFrozenDictionary();
private readonly ILogger<DtoService> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserDataManager _userDataRepository;
@ -102,9 +174,9 @@ namespace Emby.Server.Implementations.Dto
(programTuples ??= []).Add((item, dto));
}
if (item is IItemByName itemByName && options.ContainsField(ItemFields.ItemCounts))
if (options.ContainsField(ItemFields.ItemCounts))
{
SetItemByNameInfo(itemByName, dto, user);
SetItemByNameInfo(dto, user);
}
returnItems[index] = dto;
@ -135,9 +207,9 @@ namespace Emby.Server.Implementations.Dto
LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
}
if (item is IItemByName itemByName && options.ContainsField(ItemFields.ItemCounts))
if (options.ContainsField(ItemFields.ItemCounts))
{
SetItemByNameInfo(itemByName, dto, user);
SetItemByNameInfo(dto, user);
}
return dto;
@ -283,34 +355,60 @@ namespace Emby.Server.Implementations.Dto
}
/// <inheritdoc />
/// TODO refactor this to use the new SetItemByNameInfo.
/// Some callers already have the counts extracted so no reason to retrieve them again.
public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user = null)
{
var dto = GetBaseItemDtoInternal(item, options, user);
if (options.ContainsField(ItemFields.ItemCounts))
if (options.ContainsField(ItemFields.ItemCounts)
&& taggedItems is not null
&& taggedItems.Count != 0)
{
if (taggedItems is not null)
{
SetItemByNameInfo(item, dto, taggedItems!);
}
else if (item is IItemByName itemByName)
{
SetItemByNameInfo(itemByName, dto, user);
}
SetItemByNameInfo(item, dto, taggedItems);
}
return dto;
}
private static void SetItemByNameInfo(IItemByName item, BaseItemDto dto, User? user)
private void SetItemByNameInfo(BaseItemDto dto, User? user)
{
if (!_relatedItemKinds.TryGetValue(dto.Type, out var relatedItemKinds))
{
return;
}
var query = new InternalItemsQuery(user)
{
Recursive = true,
DtoOptions = new DtoOptions(false) { EnableImages = false }
DtoOptions = new DtoOptions(false) { EnableImages = false },
IncludeItemTypes = relatedItemKinds
};
var counts = item.GetTaggedItemCounts(query);
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);
dto.AlbumCount = counts.AlbumCount;
dto.ArtistCount = counts.ArtistCount;
@ -321,7 +419,7 @@ namespace Emby.Server.Implementations.Dto
dto.SeriesCount = counts.SeriesCount;
dto.SongCount = counts.SongCount;
dto.TrailerCount = counts.TrailerCount;
dto.ChildCount = counts.ChildCount;
dto.ChildCount = counts.TotalItemCount();
}
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IReadOnlyList<BaseItem> taggedItems)

View File

@ -1389,6 +1389,25 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetCount(query);
}
public ItemCounts GetItemCounts(InternalItemsQuery query)
{
if (query.Recursive && !query.ParentId.IsEmpty())
{
var parent = GetItemById(query.ParentId);
if (parent is not null)
{
SetTopParentIdsOrAncestors(query, [parent]);
}
}
if (query.User is not null)
{
AddUserToQuery(query, query.User);
}
return _itemRepository.GetItemCounts(query);
}
public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
{
SetTopParentIdsOrAncestors(query, parents);

View File

@ -457,6 +457,66 @@ public sealed class BaseItemRepository
return dbQuery.Count();
}
/// <inheritdoc />
public ItemCounts GetItemCounts(InternalItemsQuery filter)
{
ArgumentNullException.ThrowIfNull(filter);
// Hack for right now since we currently don't support filtering out these duplicates within a query
PrepareFilterQuery(filter);
using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
var counts = dbQuery
.GroupBy(x => x.Type)
.Select(x => new { x.Key, Count = x.Count() })
.AsEnumerable();
var lookup = _itemTypeLookup.BaseItemKindNames;
var result = new ItemCounts();
foreach (var count in counts)
{
if (string.Equals(count.Key, lookup[BaseItemKind.MusicAlbum], StringComparison.Ordinal))
{
result.AlbumCount = count.Count;
}
else if (string.Equals(count.Key, lookup[BaseItemKind.MusicArtist], StringComparison.Ordinal))
{
result.ArtistCount = count.Count;
}
else if (string.Equals(count.Key, lookup[BaseItemKind.Episode], StringComparison.Ordinal))
{
result.EpisodeCount = count.Count;
}
else if (string.Equals(count.Key, lookup[BaseItemKind.Movie], StringComparison.Ordinal))
{
result.MovieCount = count.Count;
}
else if (string.Equals(count.Key, lookup[BaseItemKind.MusicVideo], StringComparison.Ordinal))
{
result.MusicVideoCount = count.Count;
}
else if (string.Equals(count.Key, lookup[BaseItemKind.LiveTvProgram], StringComparison.Ordinal))
{
result.ProgramCount = count.Count;
}
else if (string.Equals(count.Key, lookup[BaseItemKind.Series], StringComparison.Ordinal))
{
result.SeriesCount = count.Count;
}
else if (string.Equals(count.Key, lookup[BaseItemKind.Audio], StringComparison.Ordinal))
{
result.SongCount = count.Count;
}
else if (string.Equals(count.Key, lookup[BaseItemKind.Trailer], StringComparison.Ordinal))
{
result.TrailerCount = count.Count;
}
}
return result;
}
#pragma warning disable CA1307 // Specify StringComparison for clarity
/// <summary>
/// Gets the type.

View File

@ -98,24 +98,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return LibraryManager.GetItemList(query);
}
public TaggedItemCounts GetTaggedItemCounts(InternalItemsQuery query)
{
query.ArtistIds = [Id];
var counts = new TaggedItemCounts();
query.IncludeItemTypes = [BaseItemKind.MusicAlbum];
counts.AlbumCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicVideo];
counts.MusicVideoCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Audio];
counts.SongCount = LibraryManager.GetCount(query);
return counts;
}
public override int GetChildCount(User user)
{
return IsAccessedByName ? 0 : base.GetChildCount(user);

View File

@ -73,27 +73,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return LibraryManager.GetItemList(query);
}
public TaggedItemCounts GetTaggedItemCounts(InternalItemsQuery query)
{
query.GenreIds = [Id];
var counts = new TaggedItemCounts();
query.IncludeItemTypes = [BaseItemKind.MusicAlbum];
counts.AlbumCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicArtist];
counts.ArtistCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicVideo];
counts.MusicVideoCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Audio];
counts.SongCount = LibraryManager.GetCount(query);
return counts;
}
public static string GetPath(string name)
{
return GetPath(name, true);

View File

@ -76,37 +76,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query);
}
public TaggedItemCounts GetTaggedItemCounts(InternalItemsQuery query)
{
query.GenreIds = [Id];
query.ExcludeItemTypes =
[
BaseItemKind.MusicVideo,
BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist
];
var counts = new TaggedItemCounts();
query.IncludeItemTypes = [BaseItemKind.Episode];
counts.EpisodeCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Movie];
counts.MovieCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.LiveTvProgram];
counts.ProgramCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Series];
counts.SeriesCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Trailer];
counts.TrailerCount = LibraryManager.GetCount(query);
return counts;
}
public static string GetPath(string name)
{
return GetPath(name, true);

View File

@ -10,8 +10,6 @@ namespace MediaBrowser.Controller.Entities
public interface IItemByName
{
IReadOnlyList<BaseItem> GetTaggedItems(InternalItemsQuery query);
TaggedItemCounts GetTaggedItemCounts(InternalItemsQuery query);
}
public interface IHasDualAccess : IItemByName

View File

@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using Microsoft.Extensions.Logging;
@ -71,43 +70,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query);
}
public TaggedItemCounts GetTaggedItemCounts(InternalItemsQuery query)
{
query.PersonIds = [Id];
var counts = new TaggedItemCounts();
// TODO: Remove MusicAlbum and MusicArtist when the relationship between Persons and Music is removed
query.IncludeItemTypes = [BaseItemKind.MusicAlbum];
counts.AlbumCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicArtist];
counts.ArtistCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Episode];
counts.EpisodeCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Movie];
counts.MovieCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicVideo];
counts.MusicVideoCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.LiveTvProgram];
counts.ProgramCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Series];
counts.SeriesCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Audio];
counts.SongCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Trailer];
counts.TrailerCount = LibraryManager.GetCount(query);
return counts;
}
public override bool CanDelete()
{
return false;

View File

@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging;
@ -72,42 +71,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query);
}
public TaggedItemCounts GetTaggedItemCounts(InternalItemsQuery query)
{
query.StudioIds = [Id];
var counts = new TaggedItemCounts();
query.IncludeItemTypes = [BaseItemKind.MusicAlbum];
counts.AlbumCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicArtist];
counts.ArtistCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Episode];
counts.EpisodeCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Movie];
counts.MovieCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicVideo];
counts.MusicVideoCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.LiveTvProgram];
counts.ProgramCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Series];
counts.SeriesCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Audio];
counts.SongCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Trailer];
counts.TrailerCount = LibraryManager.GetCount(query);
return counts;
}
public static string GetPath(string name)
{
return GetPath(name, true);

View File

@ -1,27 +0,0 @@
#pragma warning disable CS1591
namespace MediaBrowser.Controller.Entities
{
public class TaggedItemCounts
{
public int? AlbumCount { get; set; }
public int? ArtistCount { get; set; }
public int? EpisodeCount { get; set; }
public int? MovieCount { get; set; }
public int? MusicVideoCount { get; set; }
public int? ProgramCount { get; set; }
public int? SeriesCount { get; set; }
public int? SongCount { get; set; }
public int? TrailerCount { get; set; }
public int ChildCount => (AlbumCount ?? 0) + (ArtistCount ?? 0) + (EpisodeCount ?? 0) + (MovieCount ?? 0) + (MusicVideoCount ?? 0) + (ProgramCount ?? 0) + (SeriesCount ?? 0) + (SongCount ?? 0) + (TrailerCount ?? 0);
}
}

View File

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
@ -69,47 +68,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query);
}
public TaggedItemCounts GetTaggedItemCounts(InternalItemsQuery query)
{
if (!int.TryParse(Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
return new TaggedItemCounts();
}
query.Years = [year];
var counts = new TaggedItemCounts();
query.IncludeItemTypes = [BaseItemKind.MusicAlbum];
counts.AlbumCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicArtist];
counts.ArtistCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Episode];
counts.EpisodeCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Movie];
counts.MovieCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.MusicVideo];
counts.MusicVideoCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.LiveTvProgram];
counts.ProgramCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Series];
counts.SeriesCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Audio];
counts.SongCount = LibraryManager.GetCount(query);
query.IncludeItemTypes = [BaseItemKind.Trailer];
counts.TrailerCount = LibraryManager.GetCount(query);
return counts;
}
public int? GetYearValue()
{
if (int.TryParse(Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))

View File

@ -630,6 +630,8 @@ namespace MediaBrowser.Controller.Library
int GetCount(InternalItemsQuery query);
ItemCounts GetItemCounts(InternalItemsQuery query);
Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason);
BaseItem GetParentItem(Guid? parentId, Guid? userId);

View File

@ -84,6 +84,8 @@ public interface IItemRepository
int GetCount(InternalItemsQuery filter);
ItemCounts GetItemCounts(InternalItemsQuery filter);
QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter);
QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter);

View File

@ -76,5 +76,14 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The item count.</value>
public int ItemCount { get; set; }
/// <summary>
/// Adds all counts.
/// </summary>
/// <returns>The total of the counts.</returns>
public int TotalItemCount()
{
return MovieCount + SeriesCount + EpisodeCount + ArtistCount + ProgramCount + TrailerCount + SongCount + AlbumCount + MusicVideoCount + BoxSetCount + BookCount;
}
}
}