Complete LinkedChildren integration and batch DTO optimizations

This commit integrates remaining performance changes:

- Add batch user data fetching in DtoService to reduce N+1 queries
- Add GetNextUpEpisodesBatch in TVSeriesManager for efficient batch retrieval
- Update Video/Movie/BoxSet to use LibraryManager for alternate versions
- Transition LinkedChild to use ItemId instead of Path (obsolete Path/LibraryItemId)
- Update providers and controllers for LinkedChildren-based references
- Add NextUpEpisodeBatchResult for batched episode queries
- Integrate IDescendantQueryProvider in SqliteDatabaseProvider
This commit is contained in:
Shadowghost
2026-01-17 17:10:07 +01:00
parent dfa78590c2
commit 5996c4afce
35 changed files with 2277 additions and 936 deletions

View File

@@ -16,9 +16,7 @@ using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace MediaBrowser.Controller.Entities
{
@@ -140,7 +138,7 @@ namespace MediaBrowser.Controller.Entities
if (query.IncludeItemTypes.Length == 0)
{
query.IncludeItemTypes = new[] { BaseItemKind.Movie };
query.IncludeItemTypes = [BaseItemKind.Movie];
}
return parent.QueryRecursive(query);
@@ -165,7 +163,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
query.IncludeItemTypes = new[] { BaseItemKind.Movie };
query.IncludeItemTypes = [BaseItemKind.Movie];
return _libraryManager.GetItemsResult(query);
}
@@ -176,7 +174,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
query.IncludeItemTypes = new[] { BaseItemKind.Series };
query.IncludeItemTypes = [BaseItemKind.Series];
return _libraryManager.GetItemsResult(query);
}
@@ -187,7 +185,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
query.IncludeItemTypes = new[] { BaseItemKind.Episode };
query.IncludeItemTypes = [BaseItemKind.Episode];
return _libraryManager.GetItemsResult(query);
}
@@ -198,7 +196,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.IncludeItemTypes = new[] { BaseItemKind.Movie };
query.IncludeItemTypes = [BaseItemKind.Movie];
return _libraryManager.GetItemsResult(query);
}
@@ -206,7 +204,7 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetMovieCollections(User user, InternalItemsQuery query)
{
query.Parent = null;
query.IncludeItemTypes = new[] { BaseItemKind.BoxSet };
query.IncludeItemTypes = [BaseItemKind.BoxSet];
query.SetUser(user);
query.Recursive = true;
@@ -215,25 +213,25 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetMovieLatest(Folder parent, User user, InternalItemsQuery query)
{
query.OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending) };
query.OrderBy = [(ItemSortBy.DateCreated, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending)];
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
query.IncludeItemTypes = new[] { BaseItemKind.Movie };
query.IncludeItemTypes = [BaseItemKind.Movie];
return ConvertToResult(_libraryManager.GetItemList(query));
}
private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, InternalItemsQuery query)
{
query.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending) };
query.OrderBy = [(ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending)];
query.IsResumable = true;
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
query.IncludeItemTypes = new[] { BaseItemKind.Movie };
query.IncludeItemTypes = [BaseItemKind.Movie];
return ConvertToResult(_libraryManager.GetItemList(query));
}
@@ -247,7 +245,7 @@ namespace MediaBrowser.Controller.Entities
{
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { BaseItemKind.Movie },
IncludeItemTypes = [BaseItemKind.Movie],
Recursive = true,
EnableTotalRecordCount = false
}).Items
@@ -275,10 +273,10 @@ namespace MediaBrowser.Controller.Entities
{
query.Recursive = true;
query.Parent = queryParent;
query.GenreIds = new[] { displayParent.Id };
query.GenreIds = [displayParent.Id];
query.SetUser(user);
query.IncludeItemTypes = new[] { BaseItemKind.Movie };
query.IncludeItemTypes = [BaseItemKind.Movie];
return _libraryManager.GetItemsResult(query);
}
@@ -292,12 +290,12 @@ namespace MediaBrowser.Controller.Entities
if (query.IncludeItemTypes.Length == 0)
{
query.IncludeItemTypes = new[]
{
query.IncludeItemTypes =
[
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.Episode
};
];
}
return parent.QueryRecursive(query);
@@ -319,12 +317,12 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, InternalItemsQuery query)
{
query.OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending) };
query.OrderBy = [(ItemSortBy.DateCreated, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending)];
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
query.IncludeItemTypes = new[] { BaseItemKind.Episode };
query.IncludeItemTypes = [BaseItemKind.Episode];
query.IsVirtualItem = false;
return ConvertToResult(_libraryManager.GetItemList(query));
@@ -332,7 +330,7 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetTvNextUp(Folder parent, InternalItemsQuery query)
{
var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.tvshows });
var parentFolders = GetMediaFolders(parent, query.User, [CollectionType.tvshows]);
var result = _tvSeriesManager.GetNextUp(
new NextUpQuery
@@ -349,13 +347,13 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetTvResume(Folder parent, User user, InternalItemsQuery query)
{
query.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending) };
query.OrderBy = [(ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending)];
query.IsResumable = true;
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
query.IncludeItemTypes = new[] { BaseItemKind.Episode };
query.IncludeItemTypes = [BaseItemKind.Episode];
return ConvertToResult(_libraryManager.GetItemList(query));
}
@@ -366,7 +364,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.IncludeItemTypes = new[] { BaseItemKind.Series };
query.IncludeItemTypes = [BaseItemKind.Series];
return _libraryManager.GetItemsResult(query);
}
@@ -375,7 +373,7 @@ namespace MediaBrowser.Controller.Entities
{
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { BaseItemKind.Series },
IncludeItemTypes = [BaseItemKind.Series],
Recursive = true,
EnableTotalRecordCount = false
}).Items
@@ -403,10 +401,10 @@ namespace MediaBrowser.Controller.Entities
{
query.Recursive = true;
query.Parent = queryParent;
query.GenreIds = new[] { displayParent.Id };
query.GenreIds = [displayParent.Id];
query.SetUser(user);
query.IncludeItemTypes = new[] { BaseItemKind.Series };
query.IncludeItemTypes = [BaseItemKind.Series];
return _libraryManager.GetItemsResult(query);
}
@@ -418,7 +416,7 @@ namespace MediaBrowser.Controller.Entities
{
items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager));
return PostFilterAndSort(items, null, query, _libraryManager);
return SortAndPage(items, null, query, _libraryManager);
}
public static bool FilterItem(BaseItem item, InternalItemsQuery query)
@@ -426,21 +424,6 @@ namespace MediaBrowser.Controller.Entities
return Filter(item, query.User, query, BaseItem.UserDataManager, BaseItem.LibraryManager);
}
public static QueryResult<BaseItem> PostFilterAndSort(
IEnumerable<BaseItem> items,
int? totalRecordLimit,
InternalItemsQuery query,
ILibraryManager libraryManager)
{
// This must be the last filter
if (!query.AdjacentTo.IsNullOrEmpty())
{
items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
return SortAndPage(items, totalRecordLimit, query, libraryManager);
}
public static QueryResult<BaseItem> SortAndPage(
IEnumerable<BaseItem> items,
int? totalRecordLimit,
@@ -556,38 +539,6 @@ namespace MediaBrowser.Controller.Entities
}
}
if (query.IsPlayed.HasValue)
{
userData ??= userDataManager.GetUserData(user, item);
if (item.IsPlayed(user, userData) != query.IsPlayed.Value)
{
return false;
}
}
// Filter by Video3DFormat
if (query.Is3D.HasValue)
{
var val = query.Is3D.Value;
var video = item as Video;
if (video is null || val != video.Video3DFormat.HasValue)
{
return false;
}
}
/*
* fuck - fix this
if (query.IsHD.HasValue)
{
if (item.IsHD != query.IsHD.Value)
{
return false;
}
}
*/
if (query.IsLocked.HasValue)
{
var val = query.IsLocked.Value;
@@ -645,68 +596,6 @@ namespace MediaBrowser.Controller.Entities
}
}
if (query.HasOfficialRating.HasValue)
{
var filterValue = query.HasOfficialRating.Value;
var hasValue = !string.IsNullOrEmpty(item.OfficialRating);
if (hasValue != filterValue)
{
return false;
}
}
if (query.IsPlaceHolder.HasValue)
{
var filterValue = query.IsPlaceHolder.Value;
var isPlaceHolder = false;
if (item is ISupportsPlaceHolders hasPlaceHolder)
{
isPlaceHolder = hasPlaceHolder.IsPlaceHolder;
}
if (isPlaceHolder != filterValue)
{
return false;
}
}
if (query.HasSpecialFeature.HasValue)
{
var filterValue = query.HasSpecialFeature.Value;
if (item is IHasSpecialFeatures movie)
{
var ok = filterValue
? movie.SpecialFeatureIds.Count > 0
: movie.SpecialFeatureIds.Count == 0;
if (!ok)
{
return false;
}
}
else
{
return false;
}
}
if (query.HasSubtitles.HasValue)
{
var val = query.HasSubtitles.Value;
var video = item as Video;
if (video is null || val != video.HasSubtitles)
{
return false;
}
}
if (query.HasParentalRating.HasValue)
{
var val = query.HasParentalRating.Value;
@@ -734,66 +623,12 @@ namespace MediaBrowser.Controller.Entities
}
}
if (query.HasTrailer.HasValue)
{
var val = query.HasTrailer.Value;
var trailerCount = 0;
if (item is IHasTrailers hasTrailers)
{
trailerCount = hasTrailers.GetTrailerCount();
}
var ok = val ? trailerCount > 0 : trailerCount == 0;
if (!ok)
{
return false;
}
}
if (query.HasThemeSong.HasValue)
{
var filterValue = query.HasThemeSong.Value;
var themeCount = item.GetThemeSongs(user).Count;
var ok = filterValue ? themeCount > 0 : themeCount == 0;
if (!ok)
{
return false;
}
}
if (query.HasThemeVideo.HasValue)
{
var filterValue = query.HasThemeVideo.Value;
var themeCount = item.GetThemeVideos(user).Count;
var ok = filterValue ? themeCount > 0 : themeCount == 0;
if (!ok)
{
return false;
}
}
// Apply genre filter
if (query.Genres.Count > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
// Filter by VideoType
if (query.VideoTypes.Length > 0)
{
var video = item as Video;
if (video is null || !query.VideoTypes.Contains(video.VideoType))
{
return false;
}
}
if (query.ImageTypes.Length > 0 && !query.ImageTypes.Any(item.HasImage))
{
return false;
@@ -912,30 +747,6 @@ namespace MediaBrowser.Controller.Entities
}
}
if (query.SeriesStatuses.Length > 0)
{
var ok = new[] { item }.OfType<Series>().Any(p => p.Status.HasValue && query.SeriesStatuses.Contains(p.Status.Value));
if (!ok)
{
return false;
}
}
if (query.AiredDuringSeason.HasValue)
{
var episode = item as Episode;
if (episode is null)
{
return false;
}
if (!Series.FilterEpisodesBySeason(new[] { episode }, query.AiredDuringSeason.Value, true).Any())
{
return false;
}
}
if (query.ExcludeItemIds.Contains(item.Id))
{
return false;
@@ -989,7 +800,7 @@ namespace MediaBrowser.Controller.Entities
return GetMediaFolders(user, viewTypes);
}
return new BaseItem[] { parent };
return [parent];
}
private UserView GetUserViewWithName(CollectionType? type, string sortName, BaseItem parent)