#pragma warning disable RS0030 // Do not use banned APIs using System; using System.Linq; using System.Linq.Expressions; using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; namespace Jellyfin.Server.Implementations.Item; /// /// Extension methods for applying folder-aware filters that check items and their descendants. /// internal static class FolderAwareFilterExtensions { /// /// Filters items where either the item matches the condition (for non-folders) /// or any descendant matches (for folders). Uses reverse traversal through AncestorIds. /// /// The query to filter. /// The database context. /// The condition to check on BaseItemEntity. /// Filtered query. public static IQueryable WhereItemOrDescendantMatches( this IQueryable query, JellyfinDbContext context, Expression> condition) { // Get IDs of items that directly match the condition var directMatchIds = context.BaseItems.Where(condition).Select(b => b.Id); // Get parent IDs where a descendant (via AncestorIds) matches var ancestorMatchIds = context.AncestorIds .Where(a => directMatchIds.Contains(a.ItemId)) .Select(a => a.ParentItemId); // Get parent IDs where a linked child matches var linkedMatchIds = context.LinkedChildren .Where(lc => directMatchIds.Contains(lc.ChildId)) .Select(lc => lc.ParentId); var allMatchingIds = directMatchIds .Concat(ancestorMatchIds) .Concat(linkedMatchIds) .Distinct(); return query.Where(e => allMatchingIds.Contains(e.Id)); } /// /// Filters items where neither the item matches the condition (for non-folders) /// nor any descendant matches (for folders). Uses reverse traversal for infinite depth. /// /// The query to filter. /// The database context. /// The condition that should NOT match. /// Filtered query. public static IQueryable WhereNeitherItemNorDescendantMatches( this IQueryable query, JellyfinDbContext context, Expression> condition) { // Get IDs of items that directly match the condition var directMatchIds = context.BaseItems.Where(condition).Select(b => b.Id); // Get parent IDs where a descendant (via AncestorIds) matches var ancestorMatchIds = context.AncestorIds .Where(a => directMatchIds.Contains(a.ItemId)) .Select(a => a.ParentItemId); // Get parent IDs where a linked child matches var linkedMatchIds = context.LinkedChildren .Where(lc => directMatchIds.Contains(lc.ChildId)) .Select(lc => lc.ParentId); var allMatchingIds = directMatchIds .Concat(ancestorMatchIds) .Concat(linkedMatchIds) .Distinct(); return query.Where(e => !allMatchingIds.Contains(e.Id)); } }