mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-03 23:36:38 +01:00
Optimize item count calculation for named items
This commit is contained in:
@@ -418,37 +418,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;
|
||||
|
||||
@@ -1561,6 +1561,18 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetItemCounts(query);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ItemCounts GetItemCountsForNameItem(BaseItemKind kind, Guid id, BaseItemKind[] relatedItemKinds, User? user)
|
||||
{
|
||||
var query = new InternalItemsQuery(user);
|
||||
if (user is not null)
|
||||
{
|
||||
AddUserToQuery(query, user);
|
||||
}
|
||||
|
||||
return _itemRepository.GetItemCountsForNameItem(kind, id, relatedItemKinds, query);
|
||||
}
|
||||
|
||||
public Dictionary<Guid, int> GetChildCountBatch(IReadOnlyList<Guid> parentIds, Guid? userId)
|
||||
{
|
||||
return _itemRepository.GetChildCountBatch(parentIds, userId);
|
||||
|
||||
@@ -647,13 +647,13 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
var hasMetadata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
|
||||
var performFullRefresh = !hasMetadata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
|
||||
|
||||
if (!hasMetadata)
|
||||
if (performFullRefresh)
|
||||
{
|
||||
var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||
{
|
||||
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||
ForceSave = performFullRefresh
|
||||
ForceSave = true
|
||||
};
|
||||
|
||||
await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
@@ -1343,6 +1343,141 @@ public sealed class BaseItemRepository
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ItemCounts GetItemCountsForNameItem(BaseItemKind kind, Guid id, BaseItemKind[] relatedItemKinds, InternalItemsQuery accessFilter)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
|
||||
// Look up the item's name/cleanname
|
||||
var item = context.BaseItems.AsNoTracking()
|
||||
.Where(e => e.Id == id)
|
||||
.Select(e => new { e.Name, e.CleanName })
|
||||
.FirstOrDefault();
|
||||
|
||||
if (item is null)
|
||||
{
|
||||
return new ItemCounts();
|
||||
}
|
||||
|
||||
// Build the base query starting from the mapping table (flipped join order)
|
||||
IQueryable<BaseItemEntity> baseQuery;
|
||||
switch (kind)
|
||||
{
|
||||
case BaseItemKind.Person:
|
||||
baseQuery = context.PeopleBaseItemMap
|
||||
.AsNoTracking()
|
||||
.Where(m => m.People.Name == item.Name)
|
||||
.Select(m => m.Item);
|
||||
break;
|
||||
case BaseItemKind.MusicArtist:
|
||||
baseQuery = context.ItemValuesMap
|
||||
.AsNoTracking()
|
||||
.Where(ivm => ivm.ItemValue.CleanValue == item.CleanName
|
||||
&& (ivm.ItemValue.Type == ItemValueType.Artist || ivm.ItemValue.Type == ItemValueType.AlbumArtist))
|
||||
.Select(ivm => ivm.Item);
|
||||
break;
|
||||
case BaseItemKind.Genre:
|
||||
case BaseItemKind.MusicGenre:
|
||||
baseQuery = context.ItemValuesMap
|
||||
.AsNoTracking()
|
||||
.Where(ivm => ivm.ItemValue.CleanValue == item.CleanName
|
||||
&& ivm.ItemValue.Type == ItemValueType.Genre)
|
||||
.Select(ivm => ivm.Item);
|
||||
break;
|
||||
case BaseItemKind.Studio:
|
||||
baseQuery = context.ItemValuesMap
|
||||
.AsNoTracking()
|
||||
.Where(ivm => ivm.ItemValue.CleanValue == item.CleanName
|
||||
&& ivm.ItemValue.Type == ItemValueType.Studios)
|
||||
.Select(ivm => ivm.Item);
|
||||
break;
|
||||
case BaseItemKind.Year:
|
||||
if (int.TryParse(item.Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
|
||||
{
|
||||
baseQuery = context.BaseItems
|
||||
.AsNoTracking()
|
||||
.Where(e => e.ProductionYear == year);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ItemCounts();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return new ItemCounts();
|
||||
}
|
||||
|
||||
// Apply type filter
|
||||
var typeNames = relatedItemKinds.Select(k => _itemTypeLookup.BaseItemKindNames[k]).ToArray();
|
||||
baseQuery = baseQuery.Where(e => typeNames.Contains(e.Type));
|
||||
|
||||
// Apply access filtering (parental ratings, blocked/allowed tags, library access)
|
||||
baseQuery = ApplyAccessFiltering(context, baseQuery, accessFilter);
|
||||
|
||||
// Group by type and count
|
||||
var counts = baseQuery
|
||||
.GroupBy(x => x.Type)
|
||||
.Select(x => new { x.Key, Count = x.Count() })
|
||||
.ToArray();
|
||||
|
||||
var lookup = _itemTypeLookup.BaseItemKindNames;
|
||||
var result = new ItemCounts
|
||||
{
|
||||
ItemCount = counts.Sum(c => c.Count)
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
else if (string.Equals(count.Key, lookup[BaseItemKind.BoxSet], StringComparison.Ordinal))
|
||||
{
|
||||
result.BoxSetCount = count.Count;
|
||||
}
|
||||
else if (string.Equals(count.Key, lookup[BaseItemKind.Book], StringComparison.Ordinal))
|
||||
{
|
||||
result.BookCount = count.Count;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma warning disable CA1307 // Specify StringComparison for clarity
|
||||
/// <summary>
|
||||
/// Gets the type.
|
||||
|
||||
@@ -706,6 +706,16 @@ namespace MediaBrowser.Controller.Library
|
||||
|
||||
ItemCounts GetItemCounts(InternalItemsQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets item counts for a "by-name" item using an optimized query path.
|
||||
/// </summary>
|
||||
/// <param name="kind">The kind of the name item.</param>
|
||||
/// <param name="id">The ID of the name item.</param>
|
||||
/// <param name="relatedItemKinds">The item kinds to count.</param>
|
||||
/// <param name="user">The user for access filtering.</param>
|
||||
/// <returns>The item counts grouped by type.</returns>
|
||||
ItemCounts GetItemCountsForNameItem(BaseItemKind kind, Guid id, BaseItemKind[] relatedItemKinds, User? user);
|
||||
|
||||
/// <summary>
|
||||
/// Batch-fetches child counts for multiple parent folders.
|
||||
/// Returns the count of immediate children (non-recursive) for each parent.
|
||||
|
||||
@@ -112,6 +112,17 @@ public interface IItemRepository
|
||||
|
||||
ItemCounts GetItemCounts(InternalItemsQuery filter);
|
||||
|
||||
/// <summary>
|
||||
/// Gets item counts for a "by-name" item (Person, MusicArtist, Genre, MusicGenre, Studio, Year)
|
||||
/// using an optimized query that starts from the mapping table instead of scanning all BaseItems.
|
||||
/// </summary>
|
||||
/// <param name="kind">The kind of the name item.</param>
|
||||
/// <param name="id">The ID of the name item.</param>
|
||||
/// <param name="relatedItemKinds">The item kinds to count.</param>
|
||||
/// <param name="accessFilter">A pre-configured query with user access filtering settings.</param>
|
||||
/// <returns>The item counts grouped by type.</returns>
|
||||
ItemCounts GetItemCountsForNameItem(BaseItemKind kind, Guid id, BaseItemKind[] relatedItemKinds, InternalItemsQuery accessFilter);
|
||||
|
||||
QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter);
|
||||
|
||||
QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter);
|
||||
|
||||
Reference in New Issue
Block a user