diff options
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.cs')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 313 |
1 files changed, 204 insertions, 109 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 30de711ea..2c18ce69a 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -75,6 +75,7 @@ public sealed class BaseItemRepository private static readonly IReadOnlyList<ItemValueType> _getAlbumArtistValueTypes = [ItemValueType.AlbumArtist]; private static readonly IReadOnlyList<ItemValueType> _getStudiosValueTypes = [ItemValueType.Studios]; private static readonly IReadOnlyList<ItemValueType> _getGenreValueTypes = [ItemValueType.Genre]; + private static readonly IReadOnlyList<char> SearchWildcardTerms = ['%', '_', '[', ']', '^']; /// <summary> /// Initializes a new instance of the <see cref="BaseItemRepository"/> class. @@ -99,11 +100,11 @@ public sealed class BaseItemRepository } /// <inheritdoc /> - public void DeleteItem(Guid id) + public void DeleteItem(params IReadOnlyList<Guid> ids) { - if (id.IsEmpty() || id.Equals(PlaceholderId)) + if (ids is null || ids.Count == 0 || ids.Any(f => f.Equals(PlaceholderId))) { - throw new ArgumentException("Guid can't be empty or the placeholder id.", nameof(id)); + throw new ArgumentException("Guid can't be empty or the placeholder id.", nameof(ids)); } using var context = _dbProvider.CreateDbContext(); @@ -111,7 +112,7 @@ public sealed class BaseItemRepository var date = (DateTime?)DateTime.UtcNow; - var relatedItems = TraverseHirachyDown(id, context).ToArray(); + var relatedItems = ids.SelectMany(f => TraverseHirachyDown(f, context)).ToArray(); // Remove any UserData entries for the placeholder item that would conflict with the UserData // being detached from the item being deleted. This is necessary because, during an update, @@ -266,13 +267,15 @@ public sealed class BaseItemRepository IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter); dbQuery = TranslateQuery(dbQuery, context, filter); + dbQuery = ApplyGroupingFilter(context, dbQuery, filter); + if (filter.EnableTotalRecordCount) { result.TotalRecordCount = dbQuery.Count(); } - dbQuery = ApplyGroupingFilter(dbQuery, filter); dbQuery = ApplyQueryPaging(dbQuery, filter); + dbQuery = ApplyNavigations(dbQuery, filter); result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray(); result.StartIndex = filter.StartIndex ?? 0; @@ -290,8 +293,9 @@ public sealed class BaseItemRepository dbQuery = TranslateQuery(dbQuery, context, filter); - dbQuery = ApplyGroupingFilter(dbQuery, filter); + dbQuery = ApplyGroupingFilter(context, dbQuery, filter); dbQuery = ApplyQueryPaging(dbQuery, filter); + dbQuery = ApplyNavigations(dbQuery, filter); return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray(); } @@ -332,9 +336,11 @@ public sealed class BaseItemRepository var mainquery = PrepareItemQuery(context, filter); mainquery = TranslateQuery(mainquery, context, filter); mainquery = mainquery.Where(g => g.DateCreated >= subqueryGrouped.Min(s => s.MaxDateCreated)); - mainquery = ApplyGroupingFilter(mainquery, filter); + mainquery = ApplyGroupingFilter(context, mainquery, filter); mainquery = ApplyQueryPaging(mainquery, filter); + mainquery = ApplyNavigations(mainquery, filter); + return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray(); } @@ -369,36 +375,50 @@ public sealed class BaseItemRepository return query.ToArray(); } - private IQueryable<BaseItemEntity> ApplyGroupingFilter(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter) + private IQueryable<BaseItemEntity> ApplyGroupingFilter(JellyfinDbContext context, IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter) { // This whole block is needed to filter duplicate entries on request // for the time being it cannot be used because it would destroy the ordering // this results in "duplicate" responses for queries that try to lookup individual series or multiple versions but // for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own - // var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter); - // if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey) - // { - // dbQuery = ApplyOrder(dbQuery, filter); - // dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.First()); - // } - // else if (enableGroupByPresentationUniqueKey) - // { - // dbQuery = ApplyOrder(dbQuery, filter); - // dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First()); - // } - // else if (filter.GroupBySeriesPresentationUniqueKey) - // { - // dbQuery = ApplyOrder(dbQuery, filter); - // dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.First()); - // } - // else - // { - // dbQuery = dbQuery.Distinct(); - // dbQuery = ApplyOrder(dbQuery, filter); - // } - dbQuery = dbQuery.Distinct(); - dbQuery = ApplyOrder(dbQuery, filter); + var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter); + if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey) + { + var tempQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.FirstOrDefault()).Select(e => e!.Id); + dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id)); + } + else if (enableGroupByPresentationUniqueKey) + { + var tempQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!.Id); + dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id)); + } + else if (filter.GroupBySeriesPresentationUniqueKey) + { + var tempQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!.Id); + dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id)); + } + else + { + dbQuery = dbQuery.Distinct(); + } + + dbQuery = ApplyOrder(dbQuery, filter, context); + + return dbQuery; + } + + private static IQueryable<BaseItemEntity> ApplyNavigations(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter) + { + dbQuery = dbQuery.Include(e => e.TrailerTypes) + .Include(e => e.Provider) + .Include(e => e.LockedFields) + .Include(e => e.UserData); + + if (filter.DtoOptions.EnableImages) + { + dbQuery = dbQuery.Include(e => e.Images); + } return dbQuery; } @@ -426,25 +446,16 @@ public sealed class BaseItemRepository private IQueryable<BaseItemEntity> ApplyQueryFilter(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, InternalItemsQuery filter) { dbQuery = TranslateQuery(dbQuery, context, filter); - dbQuery = ApplyOrder(dbQuery, filter); - dbQuery = ApplyGroupingFilter(dbQuery, filter); + dbQuery = ApplyGroupingFilter(context, dbQuery, filter); dbQuery = ApplyQueryPaging(dbQuery, filter); + dbQuery = ApplyNavigations(dbQuery, filter); return dbQuery; } private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter) { IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking(); - dbQuery = dbQuery.AsSingleQuery() - .Include(e => e.TrailerTypes) - .Include(e => e.Provider) - .Include(e => e.LockedFields) - .Include(e => e.UserData); - - if (filter.DtoOptions.EnableImages) - { - dbQuery = dbQuery.Include(e => e.Images); - } + dbQuery = dbQuery.AsSingleQuery(); return dbQuery; } @@ -475,7 +486,7 @@ public sealed class BaseItemRepository var counts = dbQuery .GroupBy(x => x.Type) .Select(x => new { x.Key, Count = x.Count() }) - .AsEnumerable(); + .ToArray(); var lookup = _itemTypeLookup.BaseItemKindNames; var result = new ItemCounts(); @@ -606,6 +617,13 @@ public sealed class BaseItemRepository else { context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + context.BaseItemImageInfos.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + + if (entity.Images is { Count: > 0 }) + { + context.BaseItemImageInfos.AddRange(entity.Images); + } + context.BaseItems.Attach(entity).State = EntityState.Modified; } } @@ -729,13 +747,20 @@ public sealed class BaseItemRepository } using var context = _dbProvider.CreateDbContext(); - var item = PrepareItemQuery(context, new() + var dbQuery = PrepareItemQuery(context, new() { DtoOptions = new() { EnableImages = true } - }).FirstOrDefault(e => e.Id == id); + }); + dbQuery = dbQuery.Include(e => e.TrailerTypes) + .Include(e => e.Provider) + .Include(e => e.LockedFields) + .Include(e => e.UserData) + .Include(e => e.Images); + + var item = dbQuery.FirstOrDefault(e => e.Id == id); if (item is null) { return null; @@ -1217,8 +1242,20 @@ public sealed class BaseItemRepository ExcludeItemIds = filter.ExcludeItemIds }; - var query = TranslateQuery(innerQuery, context, outerQueryFilter) - .GroupBy(e => e.PresentationUniqueKey); + var masterQuery = TranslateQuery(innerQuery, context, outerQueryFilter) + .GroupBy(e => e.PresentationUniqueKey) + .Select(e => e.FirstOrDefault()) + .Select(e => e!.Id); + + var query = context.BaseItems + .Include(e => e.TrailerTypes) + .Include(e => e.Provider) + .Include(e => e.LockedFields) + .Include(e => e.Images) + .AsSingleQuery() + .Where(e => masterQuery.Contains(e.Id)); + + query = ApplyOrder(query, filter, context); var result = new QueryResult<(BaseItemDto, ItemCounts?)>(); if (filter.EnableTotalRecordCount) @@ -1273,12 +1310,7 @@ public sealed class BaseItemRepository var resultQuery = query.Select(e => new { - item = e.AsQueryable() - .Include(e => e.TrailerTypes) - .Include(e => e.Provider) - .Include(e => e.LockedFields) - .Include(e => e.Images) - .AsSingleQuery().First(), + item = e, // TODO: This is bad refactor! itemCount = new ItemCounts() { @@ -1310,7 +1342,6 @@ public sealed class BaseItemRepository result.Items = [ .. query - .Select(e => e.First()) .AsEnumerable() .Where(e => e is not null) .Select<BaseItemEntity, (BaseItemDto, ItemCounts?)>(e => @@ -1490,7 +1521,7 @@ public sealed class BaseItemRepository || query.IncludeItemTypes.Contains(BaseItemKind.Season); } - private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter) + private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter, JellyfinDbContext context) { var orderBy = filter.OrderBy; var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm); @@ -1509,7 +1540,7 @@ public sealed class BaseItemRepository var firstOrdering = orderBy.FirstOrDefault(); if (firstOrdering != default) { - var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter); + var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter, context); if (firstOrdering.SortOrder == SortOrder.Ascending) { orderedQuery = query.OrderBy(expression); @@ -1534,7 +1565,7 @@ public sealed class BaseItemRepository foreach (var item in orderBy.Skip(1)) { - var expression = OrderMapper.MapOrderByField(item.OrderBy, filter); + var expression = OrderMapper.MapOrderByField(item.OrderBy, filter, context); if (item.SortOrder == SortOrder.Ascending) { orderedQuery = orderedQuery!.ThenBy(expression); @@ -1673,8 +1704,17 @@ public sealed class BaseItemRepository if (!string.IsNullOrEmpty(filter.SearchTerm)) { - var searchTerm = filter.SearchTerm.ToLower(); - baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().Contains(searchTerm))); + var cleanedSearchTerm = GetCleanValue(filter.SearchTerm); + var originalSearchTerm = filter.SearchTerm.ToLower(); + if (SearchWildcardTerms.Any(f => cleanedSearchTerm.Contains(f))) + { + cleanedSearchTerm = $"%{cleanedSearchTerm.Trim('%')}%"; + baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName!, cleanedSearchTerm) || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle.ToLower(), originalSearchTerm))); + } + else + { + baseQuery = baseQuery.Where(e => e.CleanName!.Contains(cleanedSearchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().Contains(originalSearchTerm))); + } } if (filter.IsFolder.HasValue) @@ -1727,7 +1767,8 @@ public sealed class BaseItemRepository if (!string.IsNullOrWhiteSpace(filter.Path)) { - baseQuery = baseQuery.Where(e => e.Path == filter.Path); + var pathToQuery = GetPathToSave(filter.Path); + baseQuery = baseQuery.Where(e => e.Path == pathToQuery); } if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey)) @@ -1846,10 +1887,17 @@ public sealed class BaseItemRepository if (filter.PersonIds.Length > 0) { + var peopleEntityIds = context.BaseItems + .WhereOneOrMany(filter.PersonIds, b => b.Id) + .Join( + context.Peoples, + b => b.Name, + p => p.Name, + (b, p) => p.Id); + baseQuery = baseQuery - .Where(e => - context.PeopleBaseItemMap.Where(w => context.BaseItems.Where(r => filter.PersonIds.Contains(r.Id)).Any(f => f.Name == w.People.Name)) - .Any(f => f.ItemId == e.Id)); + .Where(e => context.PeopleBaseItemMap + .Any(m => m.ItemId == e.Id && peopleEntityIds.Contains(m.PeopleId))); } if (!string.IsNullOrWhiteSpace(filter.Person)) @@ -1885,26 +1933,35 @@ public sealed class BaseItemRepository var nameContains = filter.NameContains; if (!string.IsNullOrWhiteSpace(nameContains)) { - baseQuery = baseQuery.Where(e => - e.CleanName!.Contains(nameContains) - || e.OriginalTitle!.ToLower().Contains(nameContains!)); + if (SearchWildcardTerms.Any(f => nameContains.Contains(f))) + { + nameContains = $"%{nameContains.Trim('%')}%"; + baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName, nameContains) || EF.Functions.Like(e.OriginalTitle, nameContains)); + } + else + { + baseQuery = baseQuery.Where(e => + e.CleanName!.Contains(nameContains) + || e.OriginalTitle!.ToLower().Contains(nameContains!)); + } } if (!string.IsNullOrWhiteSpace(filter.NameStartsWith)) { - baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(filter.NameStartsWith)); + var startsWithLower = filter.NameStartsWith.ToLowerInvariant(); + baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(startsWithLower)); } if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater)) { - // i hate this - baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() > filter.NameStartsWithOrGreater[0] || e.Name!.FirstOrDefault() > filter.NameStartsWithOrGreater[0]); + var startsOrGreaterLower = filter.NameStartsWithOrGreater.ToLowerInvariant(); + baseQuery = baseQuery.Where(e => e.SortName!.CompareTo(startsOrGreaterLower) >= 0); } if (!string.IsNullOrWhiteSpace(filter.NameLessThan)) { - // i hate this - baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() < filter.NameLessThan[0] || e.Name!.FirstOrDefault() < filter.NameLessThan[0]); + var lessThanLower = filter.NameLessThan.ToLowerInvariant(); + baseQuery = baseQuery.Where(e => e.SortName!.CompareTo(lessThanLower ) < 0); } if (filter.ImageTypes.Length > 0) @@ -1970,7 +2027,7 @@ public sealed class BaseItemRepository if (filter.ArtistIds.Length > 0) { - baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ArtistIds); + baseQuery = baseQuery.WhereReferencedItemMultipleTypes(context, [ItemValueType.Artist, ItemValueType.AlbumArtist], filter.ArtistIds); } if (filter.AlbumArtistIds.Length > 0) @@ -1980,7 +2037,18 @@ public sealed class BaseItemRepository if (filter.ContributingArtistIds.Length > 0) { - baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ContributingArtistIds); + var contributingNames = context.BaseItems + .Where(b => filter.ContributingArtistIds.Contains(b.Id)) + .Select(b => b.CleanName); + + baseQuery = baseQuery.Where(e => + e.ItemValues!.Any(ivm => + ivm.ItemValue.Type == ItemValueType.Artist && + contributingNames.Contains(ivm.ItemValue.CleanValue)) + && + !e.ItemValues!.Any(ivm => + ivm.ItemValue.Type == ItemValueType.AlbumArtist && + contributingNames.Contains(ivm.ItemValue.CleanValue))); } if (filter.AlbumIds.Length > 0) @@ -1991,7 +2059,7 @@ public sealed class BaseItemRepository if (filter.ExcludeArtistIds.Length > 0) { - baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ExcludeArtistIds, true); + baseQuery = baseQuery.WhereReferencedItemMultipleTypes(context, [ItemValueType.Artist, ItemValueType.AlbumArtist], filter.ExcludeArtistIds, true); } if (filter.GenreIds.Count > 0) @@ -2035,22 +2103,26 @@ public sealed class BaseItemRepository if (filter.MinParentalRating != null) { var min = filter.MinParentalRating; - minParentalRatingFilter = e => e.InheritedParentalRatingValue >= min.Score || e.InheritedParentalRatingValue == null; - if (min.SubScore != null) - { - minParentalRatingFilter = minParentalRatingFilter.And(e => e.InheritedParentalRatingValue >= min.SubScore || e.InheritedParentalRatingValue == null); - } + var minScore = min.Score; + var minSubScore = min.SubScore ?? 0; + + minParentalRatingFilter = e => + e.InheritedParentalRatingValue == null || + e.InheritedParentalRatingValue > minScore || + (e.InheritedParentalRatingValue == minScore && (e.InheritedParentalRatingSubValue ?? 0) >= minSubScore); } Expression<Func<BaseItemEntity, bool>>? maxParentalRatingFilter = null; if (filter.MaxParentalRating != null) { var max = filter.MaxParentalRating; - maxParentalRatingFilter = e => e.InheritedParentalRatingValue <= max.Score || e.InheritedParentalRatingValue == null; - if (max.SubScore != null) - { - maxParentalRatingFilter = maxParentalRatingFilter.And(e => e.InheritedParentalRatingValue <= max.SubScore || e.InheritedParentalRatingValue == null); - } + var maxScore = max.Score; + var maxSubScore = max.SubScore ?? 0; + + maxParentalRatingFilter = e => + e.InheritedParentalRatingValue == null || + e.InheritedParentalRatingValue < maxScore || + (e.InheritedParentalRatingValue == maxScore && (e.InheritedParentalRatingSubValue ?? 0) <= maxSubScore); } if (filter.HasParentalRating ?? false) @@ -2278,23 +2350,39 @@ public sealed class BaseItemRepository if (filter.HasAnyProviderId is not null && filter.HasAnyProviderId.Count > 0) { - var include = filter.HasAnyProviderId.Select(e => $"{e.Key}:{e.Value}").ToArray(); - baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.Any(f => include.Contains(f))); + // Allow setting a null or empty value to get all items that have the specified provider set. + var includeAny = filter.HasAnyProviderId.Where(e => string.IsNullOrEmpty(e.Value)).Select(e => e.Key).ToArray(); + if (includeAny.Length > 0) + { + baseQuery = baseQuery.Where(e => e.Provider!.Any(f => includeAny.Contains(f.ProviderId))); + } + + var includeSelected = filter.HasAnyProviderId.Where(e => !string.IsNullOrEmpty(e.Value)).Select(e => $"{e.Key}:{e.Value}").ToArray(); + if (includeSelected.Length > 0) + { + baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.Any(f => includeSelected.Contains(f))); + } } if (filter.HasImdbId.HasValue) { - baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "imdb")); + baseQuery = filter.HasImdbId.Value + ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Imdb.ToString().ToLower())) + : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Imdb.ToString().ToLower())); } if (filter.HasTmdbId.HasValue) { - baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tmdb")); + baseQuery = filter.HasTmdbId.Value + ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Tmdb.ToString().ToLower())) + : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Tmdb.ToString().ToLower())); } if (filter.HasTvdbId.HasValue) { - baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tvdb")); + baseQuery = filter.HasTvdbId.Value + ? baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId.ToLower() == MetadataProvider.Tvdb.ToString().ToLower())) + : baseQuery.Where(e => e.Provider!.All(f => f.ProviderId.ToLower() != MetadataProvider.Tvdb.ToString().ToLower())); } var queryTopParentIds = filter.TopParentIds; @@ -2332,39 +2420,34 @@ public sealed class BaseItemRepository if (filter.ExcludeInheritedTags.Length > 0) { - baseQuery = baseQuery - .Where(e => !e.ItemValues!.Where(w => w.ItemValue.Type == ItemValueType.InheritedTags || w.ItemValue.Type == ItemValueType.Tags) - .Any(f => filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue))); + baseQuery = baseQuery.Where(e => + !e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)) + && (e.Type != _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode] || !e.SeriesId.HasValue || + !context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)))); } if (filter.IncludeInheritedTags.Length > 0) { - // Episodes do not store inherit tags from their parents in the database, and the tag may be still required by the client. - // In addition to the tags for the episodes themselves, we need to manually query its parent (the season)'s tags as well. - if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode) + // For seasons and episodes, we also need to check the parent series' tags. + if (includeTypes.Any(t => t == BaseItemKind.Episode || t == BaseItemKind.Season)) { - baseQuery = baseQuery - .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.Type == ItemValueType.Tags) - .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)) - || - (e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value && (w.ItemValue.Type == ItemValueType.InheritedTags || w.ItemValue.Type == ItemValueType.Tags)) - .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)))); + baseQuery = baseQuery.Where(e => + e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)) + || (e.SeriesId.HasValue && context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)))); } // A playlist should be accessible to its owner regardless of allowed tags. else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist) { - baseQuery = baseQuery - .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.Type == ItemValueType.Tags) - .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)) - || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")); + baseQuery = baseQuery.Where(e => + e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)) + || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")); // d ^^ this is stupid it hate this. } else { - baseQuery = baseQuery - .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.Type == ItemValueType.Tags) - .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))); + baseQuery = baseQuery.Where(e => + e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))); } } @@ -2509,4 +2592,16 @@ public sealed class BaseItemRepository return folderList; } + + /// <inheritdoc/> + public IReadOnlyDictionary<string, MusicArtist[]> FindArtists(IReadOnlyList<string> artistNames) + { + using var dbContext = _dbProvider.CreateDbContext(); + + var artists = dbContext.BaseItems.Where(e => e.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!) + .Where(e => artistNames.Contains(e.Name)) + .ToArray(); + + return artists.GroupBy(e => e.Name).ToDictionary(e => e.Key!, e => e.Select(f => DeserializeBaseItem(f)).Cast<MusicArtist>().ToArray()); + } } |
