From ea81db67f412dee6203e3f18798e449dce7c06f9 Mon Sep 17 00:00:00 2001
From: JPVenson <6794763+JPVenson@users.noreply.github.com>
Date: Tue, 8 Oct 2024 16:27:47 +0000
Subject: [PATCH] Added Sorting and Grouping
---
.../Data/SqliteItemRepository.cs | 1816 +----------------
Jellyfin.Data/Enums/ItemSortBy.cs | 10 -
.../Item/BaseItemManager.cs | 306 ++-
3 files changed, 301 insertions(+), 1831 deletions(-)
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 94a5eba816..26255e6aa4 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -529,159 +529,6 @@ namespace Emby.Server.Implementations.Data
return string.Empty;
}
- ///
- public int GetCount(InternalItemsQuery query)
- {
- ArgumentNullException.ThrowIfNull(query);
-
- CheckDisposed();
-
- // Hack for right now since we currently don't support filtering out these duplicates within a query
- if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
- {
- query.Limit = query.Limit.Value + 4;
- }
-
- var columns = new List { "count(distinct PresentationUniqueKey)" };
- SetFinalColumnsToSelect(query, columns);
- var commandTextBuilder = new StringBuilder("select ", 256)
- .AppendJoin(',', columns)
- .Append(FromText)
- .Append(GetJoinUserDataText(query));
-
- var whereClauses = GetWhereClauses(query, null);
- if (whereClauses.Count != 0)
- {
- commandTextBuilder.Append(" where ")
- .AppendJoin(" AND ", whereClauses);
- }
-
- var commandText = commandTextBuilder.ToString();
-
- using (new QueryTimeLogger(Logger, commandText))
- using (var connection = GetConnection(true))
- using (var statement = PrepareStatement(connection, commandText))
- {
- if (EnableJoinUserData(query))
- {
- statement.TryBind("@UserId", query.User.InternalId);
- }
-
- BindSimilarParams(query, statement);
- BindSearchParams(query, statement);
-
- // Running this again will bind the params
- GetWhereClauses(query, statement);
-
- return statement.SelectScalarInt();
- }
- }
-
- ///
- public List GetItemList(InternalItemsQuery query)
- {
- ArgumentNullException.ThrowIfNull(query);
-
- CheckDisposed();
-
- // Hack for right now since we currently don't support filtering out these duplicates within a query
- if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
- {
- query.Limit = query.Limit.Value + 4;
- }
-
- var columns = _retrieveItemColumns.ToList();
- SetFinalColumnsToSelect(query, columns);
- var commandTextBuilder = new StringBuilder("select ", 1024)
- .AppendJoin(',', columns)
- .Append(FromText)
- .Append(GetJoinUserDataText(query));
-
- var whereClauses = GetWhereClauses(query, null);
-
- if (whereClauses.Count != 0)
- {
- commandTextBuilder.Append(" where ")
- .AppendJoin(" AND ", whereClauses);
- }
-
- commandTextBuilder.Append(GetGroupBy(query))
- .Append(GetOrderByText(query));
-
- if (query.Limit.HasValue || query.StartIndex.HasValue)
- {
- var offset = query.StartIndex ?? 0;
-
- if (query.Limit.HasValue || offset > 0)
- {
- commandTextBuilder.Append(" LIMIT ")
- .Append(query.Limit ?? int.MaxValue);
- }
-
- if (offset > 0)
- {
- commandTextBuilder.Append(" OFFSET ")
- .Append(offset);
- }
- }
-
- var commandText = commandTextBuilder.ToString();
- var items = new List();
- using (new QueryTimeLogger(Logger, commandText))
- using (var connection = GetConnection(true))
- using (var statement = PrepareStatement(connection, commandText))
- {
- if (EnableJoinUserData(query))
- {
- statement.TryBind("@UserId", query.User.InternalId);
- }
-
- BindSimilarParams(query, statement);
- BindSearchParams(query, statement);
-
- // Running this again will bind the params
- GetWhereClauses(query, statement);
-
- var hasEpisodeAttributes = HasEpisodeAttributes(query);
- var hasServiceName = HasServiceName(query);
- var hasProgramAttributes = HasProgramAttributes(query);
- var hasStartDate = HasStartDate(query);
- var hasTrailerTypes = HasTrailerTypes(query);
- var hasArtistFields = HasArtistFields(query);
- var hasSeriesFields = HasSeriesFields(query);
-
- foreach (var row in statement.ExecuteQuery())
- {
- var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields, query.SkipDeserialization);
- if (item is not null)
- {
- items.Add(item);
- }
- }
- }
-
- // Hack for right now since we currently don't support filtering out these duplicates within a query
- if (query.EnableGroupByMetadataKey)
- {
- var limit = query.Limit ?? int.MaxValue;
- limit -= 4;
- var newList = new List();
-
- foreach (var item in items)
- {
- AddItem(newList, item);
-
- if (newList.Count >= limit)
- {
- break;
- }
- }
-
- items = newList;
- }
-
- return items;
- }
private string FixUnicodeChars(string buffer)
{
@@ -703,204 +550,6 @@ namespace Emby.Server.Implementations.Data
return buffer.Replace('\u00B4', '\''); // acute accent
}
- private void AddItem(List items, BaseItem newItem)
- {
- for (var i = 0; i < items.Count; i++)
- {
- var item = items[i];
-
- foreach (var providerId in newItem.ProviderIds)
- {
- if (string.Equals(providerId.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.Ordinal))
- {
- continue;
- }
-
- if (string.Equals(item.GetProviderId(providerId.Key), providerId.Value, StringComparison.Ordinal))
- {
- if (newItem.SourceType == SourceType.Library)
- {
- items[i] = newItem;
- }
-
- return;
- }
- }
- }
-
- items.Add(newItem);
- }
-
- ///
- public QueryResult GetItems(InternalItemsQuery query)
- {
- ArgumentNullException.ThrowIfNull(query);
-
- CheckDisposed();
-
- if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
- {
- var returnList = GetItemList(query);
- return new QueryResult(
- query.StartIndex,
- returnList.Count,
- returnList);
- }
-
- // Hack for right now since we currently don't support filtering out these duplicates within a query
- if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
- {
- query.Limit = query.Limit.Value + 4;
- }
-
- var columns = _retrieveItemColumns.ToList();
- SetFinalColumnsToSelect(query, columns);
- var commandTextBuilder = new StringBuilder("select ", 512)
- .AppendJoin(',', columns)
- .Append(FromText)
- .Append(GetJoinUserDataText(query));
-
- var whereClauses = GetWhereClauses(query, null);
-
- var whereText = whereClauses.Count == 0 ?
- string.Empty :
- string.Join(" AND ", whereClauses);
-
- if (!string.IsNullOrEmpty(whereText))
- {
- commandTextBuilder.Append(" where ")
- .Append(whereText);
- }
-
- commandTextBuilder.Append(GetGroupBy(query))
- .Append(GetOrderByText(query));
-
- if (query.Limit.HasValue || query.StartIndex.HasValue)
- {
- var offset = query.StartIndex ?? 0;
-
- if (query.Limit.HasValue || offset > 0)
- {
- commandTextBuilder.Append(" LIMIT ")
- .Append(query.Limit ?? int.MaxValue);
- }
-
- if (offset > 0)
- {
- commandTextBuilder.Append(" OFFSET ")
- .Append(offset);
- }
- }
-
- var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
-
- var itemQuery = string.Empty;
- var totalRecordCountQuery = string.Empty;
- if (!isReturningZeroItems)
- {
- itemQuery = commandTextBuilder.ToString();
- }
-
- if (query.EnableTotalRecordCount)
- {
- commandTextBuilder.Clear();
-
- commandTextBuilder.Append(" select ");
-
- List columnsToSelect;
- if (EnableGroupByPresentationUniqueKey(query))
- {
- columnsToSelect = new List { "count (distinct PresentationUniqueKey)" };
- }
- else if (query.GroupBySeriesPresentationUniqueKey)
- {
- columnsToSelect = new List { "count (distinct SeriesPresentationUniqueKey)" };
- }
- else
- {
- columnsToSelect = new List { "count (guid)" };
- }
-
- SetFinalColumnsToSelect(query, columnsToSelect);
-
- commandTextBuilder.AppendJoin(',', columnsToSelect)
- .Append(FromText)
- .Append(GetJoinUserDataText(query));
- if (!string.IsNullOrEmpty(whereText))
- {
- commandTextBuilder.Append(" where ")
- .Append(whereText);
- }
-
- totalRecordCountQuery = commandTextBuilder.ToString();
- }
-
- var list = new List();
- var result = new QueryResult();
- using var connection = GetConnection(true);
- using var transaction = connection.BeginTransaction();
- if (!isReturningZeroItems)
- {
- using (new QueryTimeLogger(Logger, itemQuery, "GetItems.ItemQuery"))
- using (var statement = PrepareStatement(connection, itemQuery))
- {
- if (EnableJoinUserData(query))
- {
- statement.TryBind("@UserId", query.User.InternalId);
- }
-
- BindSimilarParams(query, statement);
- BindSearchParams(query, statement);
-
- // Running this again will bind the params
- GetWhereClauses(query, statement);
-
- var hasEpisodeAttributes = HasEpisodeAttributes(query);
- var hasServiceName = HasServiceName(query);
- var hasProgramAttributes = HasProgramAttributes(query);
- var hasStartDate = HasStartDate(query);
- var hasTrailerTypes = HasTrailerTypes(query);
- var hasArtistFields = HasArtistFields(query);
- var hasSeriesFields = HasSeriesFields(query);
-
- foreach (var row in statement.ExecuteQuery())
- {
- var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields, false);
- if (item is not null)
- {
- list.Add(item);
- }
- }
- }
- }
-
- if (query.EnableTotalRecordCount)
- {
- using (new QueryTimeLogger(Logger, totalRecordCountQuery, "GetItems.TotalRecordCount"))
- using (var statement = PrepareStatement(connection, totalRecordCountQuery))
- {
- if (EnableJoinUserData(query))
- {
- statement.TryBind("@UserId", query.User.InternalId);
- }
-
- BindSimilarParams(query, statement);
- BindSearchParams(query, statement);
-
- // Running this again will bind the params
- GetWhereClauses(query, statement);
-
- result.TotalRecordCount = statement.SelectScalarInt();
- }
- }
-
- transaction.Commit();
-
- result.StartIndex = query.StartIndex ?? 0;
- result.Items = list;
- return result;
- }
-
private string GetOrderByText(InternalItemsQuery query)
{
var orderBy = query.OrderBy;
@@ -1066,1433 +715,19 @@ namespace Emby.Server.Implementations.Data
return IsAlphaNumeric(value);
}
-#nullable enable
- private List GetWhereClauses(InternalItemsQuery query, SqliteCommand? statement)
+ private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
{
- if (query.IsResumable ?? false)
+ if (query.ExcludeItemTypes.Contains(type))
{
- query.IsVirtualItem = false;
+ return false;
}
- var minWidth = query.MinWidth;
- var maxWidth = query.MaxWidth;
+ return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
+ }
- if (query.IsHD.HasValue)
- {
- const int Threshold = 1200;
- if (query.IsHD.Value)
- {
- minWidth = Threshold;
- }
- else
- {
- maxWidth = Threshold - 1;
- }
- }
-
- if (query.Is4K.HasValue)
- {
- const int Threshold = 3800;
- if (query.Is4K.Value)
- {
- minWidth = Threshold;
- }
- else
- {
- maxWidth = Threshold - 1;
- }
- }
-
- var whereClauses = new List();
-
- if (minWidth.HasValue)
- {
- whereClauses.Add("Width>=@MinWidth");
- statement?.TryBind("@MinWidth", minWidth);
- }
-
- if (query.MinHeight.HasValue)
- {
- whereClauses.Add("Height>=@MinHeight");
- statement?.TryBind("@MinHeight", query.MinHeight);
- }
-
- if (maxWidth.HasValue)
- {
- whereClauses.Add("Width<=@MaxWidth");
- statement?.TryBind("@MaxWidth", maxWidth);
- }
-
- if (query.MaxHeight.HasValue)
- {
- whereClauses.Add("Height<=@MaxHeight");
- statement?.TryBind("@MaxHeight", query.MaxHeight);
- }
-
- if (query.IsLocked.HasValue)
- {
- whereClauses.Add("IsLocked=@IsLocked");
- statement?.TryBind("@IsLocked", query.IsLocked);
- }
-
- var tags = query.Tags.ToList();
- var excludeTags = query.ExcludeTags.ToList();
-
- if (query.IsMovie == true)
- {
- if (query.IncludeItemTypes.Length == 0
- || query.IncludeItemTypes.Contains(BaseItemKind.Movie)
- || query.IncludeItemTypes.Contains(BaseItemKind.Trailer))
- {
- whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
- }
- else
- {
- whereClauses.Add("IsMovie=@IsMovie");
- }
-
- statement?.TryBind("@IsMovie", true);
- }
- else if (query.IsMovie.HasValue)
- {
- whereClauses.Add("IsMovie=@IsMovie");
- statement?.TryBind("@IsMovie", query.IsMovie);
- }
-
- if (query.IsSeries.HasValue)
- {
- whereClauses.Add("IsSeries=@IsSeries");
- statement?.TryBind("@IsSeries", query.IsSeries);
- }
-
- if (query.IsSports.HasValue)
- {
- if (query.IsSports.Value)
- {
- tags.Add("Sports");
- }
- else
- {
- excludeTags.Add("Sports");
- }
- }
-
- if (query.IsNews.HasValue)
- {
- if (query.IsNews.Value)
- {
- tags.Add("News");
- }
- else
- {
- excludeTags.Add("News");
- }
- }
-
- if (query.IsKids.HasValue)
- {
- if (query.IsKids.Value)
- {
- tags.Add("Kids");
- }
- else
- {
- excludeTags.Add("Kids");
- }
- }
-
- if (query.SimilarTo is not null && query.MinSimilarityScore > 0)
- {
- whereClauses.Add("SimilarityScore > " + (query.MinSimilarityScore - 1).ToString(CultureInfo.InvariantCulture));
- }
-
- if (!string.IsNullOrEmpty(query.SearchTerm))
- {
- whereClauses.Add("SearchScore > 0");
- }
-
- if (query.IsFolder.HasValue)
- {
- whereClauses.Add("IsFolder=@IsFolder");
- statement?.TryBind("@IsFolder", query.IsFolder);
- }
-
- var includeTypes = query.IncludeItemTypes;
- // Only specify excluded types if no included types are specified
- if (query.IncludeItemTypes.Length == 0)
- {
- var excludeTypes = query.ExcludeItemTypes;
- if (excludeTypes.Length == 1)
- {
- if (_baseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
- {
- whereClauses.Add("type<>@type");
- statement?.TryBind("@type", excludeTypeName);
- }
- else
- {
- Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeTypes[0]);
- }
- }
- else if (excludeTypes.Length > 1)
- {
- var whereBuilder = new StringBuilder("type not in (");
- foreach (var excludeType in excludeTypes)
- {
- if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
- {
- whereBuilder
- .Append('\'')
- .Append(baseItemKindName)
- .Append("',");
- }
- else
- {
- Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeType);
- }
- }
-
- // Remove trailing comma.
- whereBuilder.Length--;
- whereBuilder.Append(')');
- whereClauses.Add(whereBuilder.ToString());
- }
- }
- else if (includeTypes.Length == 1)
- {
- if (_baseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
- {
- whereClauses.Add("type=@type");
- statement?.TryBind("@type", includeTypeName);
- }
- else
- {
- Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeTypes[0]);
- }
- }
- else if (includeTypes.Length > 1)
- {
- var whereBuilder = new StringBuilder("type in (");
- foreach (var includeType in includeTypes)
- {
- if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
- {
- whereBuilder
- .Append('\'')
- .Append(baseItemKindName)
- .Append("',");
- }
- else
- {
- Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeType);
- }
- }
-
- // Remove trailing comma.
- whereBuilder.Length--;
- whereBuilder.Append(')');
- whereClauses.Add(whereBuilder.ToString());
- }
-
- if (query.ChannelIds.Count == 1)
- {
- whereClauses.Add("ChannelId=@ChannelId");
- statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
- }
- else if (query.ChannelIds.Count > 1)
- {
- var inClause = string.Join(',', query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
- whereClauses.Add($"ChannelId in ({inClause})");
- }
-
- if (!query.ParentId.IsEmpty())
- {
- whereClauses.Add("ParentId=@ParentId");
- statement?.TryBind("@ParentId", query.ParentId);
- }
-
- if (!string.IsNullOrWhiteSpace(query.Path))
- {
- whereClauses.Add("Path=@Path");
- statement?.TryBind("@Path", GetPathToSave(query.Path));
- }
-
- if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
- {
- whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
- statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
- }
-
- if (query.MinCommunityRating.HasValue)
- {
- whereClauses.Add("CommunityRating>=@MinCommunityRating");
- statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
- }
-
- if (query.MinIndexNumber.HasValue)
- {
- whereClauses.Add("IndexNumber>=@MinIndexNumber");
- statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
- }
-
- if (query.MinParentAndIndexNumber.HasValue)
- {
- whereClauses.Add("((ParentIndexNumber=@MinParentAndIndexNumberParent and IndexNumber>=@MinParentAndIndexNumberIndex) or ParentIndexNumber>@MinParentAndIndexNumberParent)");
- statement?.TryBind("@MinParentAndIndexNumberParent", query.MinParentAndIndexNumber.Value.ParentIndexNumber);
- statement?.TryBind("@MinParentAndIndexNumberIndex", query.MinParentAndIndexNumber.Value.IndexNumber);
- }
-
- if (query.MinDateCreated.HasValue)
- {
- whereClauses.Add("DateCreated>=@MinDateCreated");
- statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value);
- }
-
- if (query.MinDateLastSaved.HasValue)
- {
- whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
- statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
- }
-
- if (query.MinDateLastSavedForUser.HasValue)
- {
- whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
- statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
- }
-
- if (query.IndexNumber.HasValue)
- {
- whereClauses.Add("IndexNumber=@IndexNumber");
- statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
- }
-
- if (query.ParentIndexNumber.HasValue)
- {
- whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
- statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
- }
-
- if (query.ParentIndexNumberNotEquals.HasValue)
- {
- whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
- statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
- }
-
- var minEndDate = query.MinEndDate;
- var maxEndDate = query.MaxEndDate;
-
- if (query.HasAired.HasValue)
- {
- if (query.HasAired.Value)
- {
- maxEndDate = DateTime.UtcNow;
- }
- else
- {
- minEndDate = DateTime.UtcNow;
- }
- }
-
- if (minEndDate.HasValue)
- {
- whereClauses.Add("EndDate>=@MinEndDate");
- statement?.TryBind("@MinEndDate", minEndDate.Value);
- }
-
- if (maxEndDate.HasValue)
- {
- whereClauses.Add("EndDate<=@MaxEndDate");
- statement?.TryBind("@MaxEndDate", maxEndDate.Value);
- }
-
- if (query.MinStartDate.HasValue)
- {
- whereClauses.Add("StartDate>=@MinStartDate");
- statement?.TryBind("@MinStartDate", query.MinStartDate.Value);
- }
-
- if (query.MaxStartDate.HasValue)
- {
- whereClauses.Add("StartDate<=@MaxStartDate");
- statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value);
- }
-
- if (query.MinPremiereDate.HasValue)
- {
- whereClauses.Add("PremiereDate>=@MinPremiereDate");
- statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
- }
-
- if (query.MaxPremiereDate.HasValue)
- {
- whereClauses.Add("PremiereDate<=@MaxPremiereDate");
- statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
- }
-
- StringBuilder clauseBuilder = new StringBuilder();
- const string Or = " OR ";
-
- var trailerTypes = query.TrailerTypes;
- int trailerTypesLen = trailerTypes.Length;
- if (trailerTypesLen > 0)
- {
- clauseBuilder.Append('(');
-
- for (int i = 0; i < trailerTypesLen; i++)
- {
- var paramName = "@TrailerTypes" + i;
- clauseBuilder.Append("TrailerTypes like ")
- .Append(paramName)
- .Append(Or);
- statement?.TryBind(paramName, "%" + trailerTypes[i] + "%");
- }
-
- clauseBuilder.Length -= Or.Length;
- clauseBuilder.Append(')');
-
- whereClauses.Add(clauseBuilder.ToString());
-
- clauseBuilder.Length = 0;
- }
-
- if (query.IsAiring.HasValue)
- {
- if (query.IsAiring.Value)
- {
- whereClauses.Add("StartDate<=@MaxStartDate");
- statement?.TryBind("@MaxStartDate", DateTime.UtcNow);
-
- whereClauses.Add("EndDate>=@MinEndDate");
- statement?.TryBind("@MinEndDate", DateTime.UtcNow);
- }
- else
- {
- whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
- statement?.TryBind("@IsAiringDate", DateTime.UtcNow);
- }
- }
-
- int personIdsLen = query.PersonIds.Length;
- if (personIdsLen > 0)
- {
- // TODO: Should this query with CleanName ?
-
- clauseBuilder.Append('(');
-
- Span idBytes = stackalloc byte[16];
- for (int i = 0; i < personIdsLen; i++)
- {
- string paramName = "@PersonId" + i;
- clauseBuilder.Append("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=")
- .Append(paramName)
- .Append("))) OR ");
-
- statement?.TryBind(paramName, query.PersonIds[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- clauseBuilder.Append(')');
-
- whereClauses.Add(clauseBuilder.ToString());
-
- clauseBuilder.Length = 0;
- }
-
- if (!string.IsNullOrWhiteSpace(query.Person))
- {
- whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
- statement?.TryBind("@PersonName", query.Person);
- }
-
- if (!string.IsNullOrWhiteSpace(query.MinSortName))
- {
- whereClauses.Add("SortName>=@MinSortName");
- statement?.TryBind("@MinSortName", query.MinSortName);
- }
-
- if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
- {
- whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
- statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
- }
-
- if (!string.IsNullOrWhiteSpace(query.ExternalId))
- {
- whereClauses.Add("ExternalId=@ExternalId");
- statement?.TryBind("@ExternalId", query.ExternalId);
- }
-
- if (!string.IsNullOrWhiteSpace(query.Name))
- {
- whereClauses.Add("CleanName=@Name");
- statement?.TryBind("@Name", GetCleanValue(query.Name));
- }
-
- // These are the same, for now
- var nameContains = query.NameContains;
- if (!string.IsNullOrWhiteSpace(nameContains))
- {
- whereClauses.Add("(CleanName like @NameContains or OriginalTitle like @NameContains)");
- if (statement is not null)
- {
- nameContains = FixUnicodeChars(nameContains);
- statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%");
- }
- }
-
- if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
- {
- whereClauses.Add("SortName like @NameStartsWith");
- statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%");
- }
-
- if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
- {
- whereClauses.Add("SortName >= @NameStartsWithOrGreater");
- // lowercase this because SortName is stored as lowercase
- statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
- }
-
- if (!string.IsNullOrWhiteSpace(query.NameLessThan))
- {
- whereClauses.Add("SortName < @NameLessThan");
- // lowercase this because SortName is stored as lowercase
- statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
- }
-
- if (query.ImageTypes.Length > 0)
- {
- foreach (var requiredImage in query.ImageTypes)
- {
- whereClauses.Add("Images like '%" + requiredImage + "%'");
- }
- }
-
- if (query.IsLiked.HasValue)
- {
- if (query.IsLiked.Value)
- {
- whereClauses.Add("rating>=@UserRating");
- statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
- }
- else
- {
- whereClauses.Add("(rating is null or rating<@UserRating)");
- statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
- }
- }
-
- if (query.IsFavoriteOrLiked.HasValue)
- {
- if (query.IsFavoriteOrLiked.Value)
- {
- whereClauses.Add("IsFavorite=@IsFavoriteOrLiked");
- }
- else
- {
- whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
- }
-
- statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
- }
-
- if (query.IsFavorite.HasValue)
- {
- if (query.IsFavorite.Value)
- {
- whereClauses.Add("IsFavorite=@IsFavorite");
- }
- else
- {
- whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
- }
-
- statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
- }
-
- if (EnableJoinUserData(query))
- {
- if (query.IsPlayed.HasValue)
- {
- // We should probably figure this out for all folders, but for right now, this is the only place where we need it
- if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.Series)
- {
- if (query.IsPlayed.Value)
- {
- whereClauses.Add("PresentationUniqueKey not in (select S.SeriesPresentationUniqueKey from TypedBaseitems S left join UserDatas UD on S.UserDataKey=UD.Key And UD.UserId=@UserId where Coalesce(UD.Played, 0)=0 and S.IsFolder=0 and S.IsVirtualItem=0 and S.SeriesPresentationUniqueKey not null)");
- }
- else
- {
- whereClauses.Add("PresentationUniqueKey in (select S.SeriesPresentationUniqueKey from TypedBaseitems S left join UserDatas UD on S.UserDataKey=UD.Key And UD.UserId=@UserId where Coalesce(UD.Played, 0)=0 and S.IsFolder=0 and S.IsVirtualItem=0 and S.SeriesPresentationUniqueKey not null)");
- }
- }
- else
- {
- if (query.IsPlayed.Value)
- {
- whereClauses.Add("(played=@IsPlayed)");
- }
- else
- {
- whereClauses.Add("(played is null or played=@IsPlayed)");
- }
-
- statement?.TryBind("@IsPlayed", query.IsPlayed.Value);
- }
- }
- }
-
- if (query.IsResumable.HasValue)
- {
- if (query.IsResumable.Value)
- {
- whereClauses.Add("playbackPositionTicks > 0");
- }
- else
- {
- whereClauses.Add("(playbackPositionTicks is null or playbackPositionTicks = 0)");
- }
- }
-
- if (query.ArtistIds.Length > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.ArtistIds.Length; i++)
- {
- clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@ArtistIds")
- .Append(i)
- .Append(") and Type<=1)) OR ");
- statement?.TryBind("@ArtistIds" + i, query.ArtistIds[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.AlbumArtistIds.Length > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.AlbumArtistIds.Length; i++)
- {
- clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@ArtistIds")
- .Append(i)
- .Append(") and Type=1)) OR ");
- statement?.TryBind("@ArtistIds" + i, query.AlbumArtistIds[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.ContributingArtistIds.Length > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.ContributingArtistIds.Length; i++)
- {
- clauseBuilder.Append("((select CleanName from TypedBaseItems where guid=@ArtistIds")
- .Append(i)
- .Append(") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=@ArtistIds")
- .Append(i)
- .Append(") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1)) OR ");
- statement?.TryBind("@ArtistIds" + i, query.ContributingArtistIds[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.AlbumIds.Length > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.AlbumIds.Length; i++)
- {
- clauseBuilder.Append("Album in (select Name from typedbaseitems where guid=@AlbumIds")
- .Append(i)
- .Append(") OR ");
- statement?.TryBind("@AlbumIds" + i, query.AlbumIds[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.ExcludeArtistIds.Length > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.ExcludeArtistIds.Length; i++)
- {
- clauseBuilder.Append("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@ExcludeArtistId")
- .Append(i)
- .Append(") and Type<=1)) OR ");
- statement?.TryBind("@ExcludeArtistId" + i, query.ExcludeArtistIds[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.GenreIds.Count > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.GenreIds.Count; i++)
- {
- clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@GenreId")
- .Append(i)
- .Append(") and Type=2)) OR ");
- statement?.TryBind("@GenreId" + i, query.GenreIds[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.Genres.Count > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.Genres.Count; i++)
- {
- clauseBuilder.Append("@Genre")
- .Append(i)
- .Append(" in (select CleanValue from ItemValues where ItemId=Guid and Type=2) OR ");
- statement?.TryBind("@Genre" + i, GetCleanValue(query.Genres[i]));
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (tags.Count > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < tags.Count; i++)
- {
- clauseBuilder.Append("@Tag")
- .Append(i)
- .Append(" in (select CleanValue from ItemValues where ItemId=Guid and Type=4) OR ");
- statement?.TryBind("@Tag" + i, GetCleanValue(tags[i]));
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (excludeTags.Count > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < excludeTags.Count; i++)
- {
- clauseBuilder.Append("@ExcludeTag")
- .Append(i)
- .Append(" not in (select CleanValue from ItemValues where ItemId=Guid and Type=4) OR ");
- statement?.TryBind("@ExcludeTag" + i, GetCleanValue(excludeTags[i]));
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.StudioIds.Length > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.StudioIds.Length; i++)
- {
- clauseBuilder.Append("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@StudioId")
- .Append(i)
- .Append(") and Type=3)) OR ");
- statement?.TryBind("@StudioId" + i, query.StudioIds[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.OfficialRatings.Length > 0)
- {
- clauseBuilder.Append('(');
- for (var i = 0; i < query.OfficialRatings.Length; i++)
- {
- clauseBuilder.Append("OfficialRating=@OfficialRating").Append(i).Append(Or);
- statement?.TryBind("@OfficialRating" + i, query.OfficialRatings[i]);
- }
-
- clauseBuilder.Length -= Or.Length;
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- clauseBuilder.Append('(');
- if (query.HasParentalRating ?? false)
- {
- clauseBuilder.Append("InheritedParentalRatingValue not null");
- if (query.MinParentalRating.HasValue)
- {
- clauseBuilder.Append(" AND InheritedParentalRatingValue >= @MinParentalRating");
- statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
- }
-
- if (query.MaxParentalRating.HasValue)
- {
- clauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
- statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
- }
- }
- else if (query.BlockUnratedItems.Length > 0)
- {
- const string ParamName = "@UnratedType";
- clauseBuilder.Append("(InheritedParentalRatingValue is null AND UnratedType not in (");
-
- for (int i = 0; i < query.BlockUnratedItems.Length; i++)
- {
- clauseBuilder.Append(ParamName).Append(i).Append(',');
- statement?.TryBind(ParamName + i, query.BlockUnratedItems[i].ToString());
- }
-
- // Remove trailing comma
- clauseBuilder.Length--;
- clauseBuilder.Append("))");
-
- if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
- {
- clauseBuilder.Append(" OR (");
- }
-
- if (query.MinParentalRating.HasValue)
- {
- clauseBuilder.Append("InheritedParentalRatingValue >= @MinParentalRating");
- statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
- }
-
- if (query.MaxParentalRating.HasValue)
- {
- if (query.MinParentalRating.HasValue)
- {
- clauseBuilder.Append(" AND ");
- }
-
- clauseBuilder.Append("InheritedParentalRatingValue <= @MaxParentalRating");
- statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
- }
-
- if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
- {
- clauseBuilder.Append(')');
- }
-
- if (!(query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue))
- {
- clauseBuilder.Append(" OR InheritedParentalRatingValue not null");
- }
- }
- else if (query.MinParentalRating.HasValue)
- {
- clauseBuilder.Append("InheritedParentalRatingValue is null OR (InheritedParentalRatingValue >= @MinParentalRating");
- statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
-
- if (query.MaxParentalRating.HasValue)
- {
- clauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
- statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
- }
-
- clauseBuilder.Append(')');
- }
- else if (query.MaxParentalRating.HasValue)
- {
- clauseBuilder.Append("InheritedParentalRatingValue is null OR InheritedParentalRatingValue <= @MaxParentalRating");
- statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
- }
- else if (!query.HasParentalRating ?? false)
- {
- clauseBuilder.Append("InheritedParentalRatingValue is null");
- }
-
- if (clauseBuilder.Length > 1)
- {
- whereClauses.Add(clauseBuilder.Append(')').ToString());
- clauseBuilder.Length = 0;
- }
-
- if (query.HasOfficialRating.HasValue)
- {
- if (query.HasOfficialRating.Value)
- {
- whereClauses.Add("(OfficialRating not null AND OfficialRating<>'')");
- }
- else
- {
- whereClauses.Add("(OfficialRating is null OR OfficialRating='')");
- }
- }
-
- if (query.HasOverview.HasValue)
- {
- if (query.HasOverview.Value)
- {
- whereClauses.Add("(Overview not null AND Overview<>'')");
- }
- else
- {
- whereClauses.Add("(Overview is null OR Overview='')");
- }
- }
-
- if (query.HasOwnerId.HasValue)
- {
- if (query.HasOwnerId.Value)
- {
- whereClauses.Add("OwnerId not null");
- }
- else
- {
- whereClauses.Add("OwnerId is null");
- }
- }
-
- if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
- {
- whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)");
- statement?.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
- }
-
- if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
- {
- whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)");
- statement?.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
- }
-
- if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
- {
- whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)");
- statement?.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
- }
-
- if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
- {
- whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)");
- statement?.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
- }
-
- if (query.HasSubtitles.HasValue)
- {
- if (query.HasSubtitles.Value)
- {
- whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' limit 1) not null)");
- }
- else
- {
- whereClauses.Add("((select type from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' limit 1) is null)");
- }
- }
-
- if (query.HasChapterImages.HasValue)
- {
- if (query.HasChapterImages.Value)
- {
- whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) not null)");
- }
- else
- {
- whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) is null)");
- }
- }
-
- if (query.HasDeadParentId.HasValue && query.HasDeadParentId.Value)
- {
- whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)");
- }
-
- if (query.IsDeadArtist.HasValue && query.IsDeadArtist.Value)
- {
- whereClauses.Add("CleanName not in (Select CleanValue From ItemValues where Type in (0,1))");
- }
-
- if (query.IsDeadStudio.HasValue && query.IsDeadStudio.Value)
- {
- whereClauses.Add("CleanName not in (Select CleanValue From ItemValues where Type = 3)");
- }
-
- if (query.IsDeadPerson.HasValue && query.IsDeadPerson.Value)
- {
- whereClauses.Add("Name not in (Select Name From People)");
- }
-
- if (query.Years.Length == 1)
- {
- whereClauses.Add("ProductionYear=@Years");
- statement?.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
- }
- else if (query.Years.Length > 1)
- {
- var val = string.Join(',', query.Years);
- whereClauses.Add("ProductionYear in (" + val + ")");
- }
-
- var isVirtualItem = query.IsVirtualItem ?? query.IsMissing;
- if (isVirtualItem.HasValue)
- {
- whereClauses.Add("IsVirtualItem=@IsVirtualItem");
- statement?.TryBind("@IsVirtualItem", isVirtualItem.Value);
- }
-
- if (query.IsSpecialSeason.HasValue)
- {
- if (query.IsSpecialSeason.Value)
- {
- whereClauses.Add("IndexNumber = 0");
- }
- else
- {
- whereClauses.Add("IndexNumber <> 0");
- }
- }
-
- if (query.IsUnaired.HasValue)
- {
- if (query.IsUnaired.Value)
- {
- whereClauses.Add("PremiereDate >= DATETIME('now')");
- }
- else
- {
- whereClauses.Add("PremiereDate < DATETIME('now')");
- }
- }
-
- if (query.MediaTypes.Length == 1)
- {
- whereClauses.Add("MediaType=@MediaTypes");
- statement?.TryBind("@MediaTypes", query.MediaTypes[0].ToString());
- }
- else if (query.MediaTypes.Length > 1)
- {
- var val = string.Join(',', query.MediaTypes.Select(i => $"'{i}'"));
- whereClauses.Add("MediaType in (" + val + ")");
- }
-
- if (query.ItemIds.Length > 0)
- {
- var includeIds = new List();
- var index = 0;
- foreach (var id in query.ItemIds)
- {
- includeIds.Add("Guid = @IncludeId" + index);
- statement?.TryBind("@IncludeId" + index, id);
- index++;
- }
-
- whereClauses.Add("(" + string.Join(" OR ", includeIds) + ")");
- }
-
- if (query.ExcludeItemIds.Length > 0)
- {
- var excludeIds = new List();
- var index = 0;
- foreach (var id in query.ExcludeItemIds)
- {
- excludeIds.Add("Guid <> @ExcludeId" + index);
- statement?.TryBind("@ExcludeId" + index, id);
- index++;
- }
-
- whereClauses.Add(string.Join(" AND ", excludeIds));
- }
-
- if (query.ExcludeProviderIds is not null && query.ExcludeProviderIds.Count > 0)
- {
- var excludeIds = new List();
-
- var index = 0;
- foreach (var pair in query.ExcludeProviderIds)
- {
- if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- var paramName = "@ExcludeProviderId" + index;
- excludeIds.Add("(ProviderIds is null or ProviderIds not like " + paramName + ")");
- statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
- index++;
-
- break;
- }
-
- if (excludeIds.Count > 0)
- {
- whereClauses.Add(string.Join(" AND ", excludeIds));
- }
- }
-
- if (query.HasAnyProviderId is not null && query.HasAnyProviderId.Count > 0)
- {
- var hasProviderIds = new List();
-
- var index = 0;
- foreach (var pair in query.HasAnyProviderId)
- {
- if (string.Equals(pair.Key, nameof(MetadataProvider.TmdbCollection), StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- // TODO this seems to be an idea for a better schema where ProviderIds are their own table
- // but this is not implemented
- // hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
-
- // TODO this is a really BAD way to do it since the pair:
- // Tmdb, 1234 matches Tmdb=1234 but also Tmdb=1234567
- // and maybe even NotTmdb=1234.
-
- // this is a placeholder for this specific pair to correlate it in the bigger query
- var paramName = "@HasAnyProviderId" + index;
-
- // this is a search for the placeholder
- hasProviderIds.Add("ProviderIds like " + paramName);
-
- // this replaces the placeholder with a value, here: %key=val%
- statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
- index++;
-
- break;
- }
-
- if (hasProviderIds.Count > 0)
- {
- whereClauses.Add("(" + string.Join(" OR ", hasProviderIds) + ")");
- }
- }
-
- if (query.HasImdbId.HasValue)
- {
- whereClauses.Add(GetProviderIdClause(query.HasImdbId.Value, "imdb"));
- }
-
- if (query.HasTmdbId.HasValue)
- {
- whereClauses.Add(GetProviderIdClause(query.HasTmdbId.Value, "tmdb"));
- }
-
- if (query.HasTvdbId.HasValue)
- {
- whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb"));
- }
-
- var queryTopParentIds = query.TopParentIds;
-
- if (queryTopParentIds.Length > 0)
- {
- var includedItemByNameTypes = GetItemByNameTypesInQuery(query);
- var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
-
- if (queryTopParentIds.Length == 1)
- {
- if (enableItemsByName && includedItemByNameTypes.Count == 1)
- {
- whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)");
- statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
- }
- else if (enableItemsByName && includedItemByNameTypes.Count > 1)
- {
- var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
- whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))");
- }
- else
- {
- whereClauses.Add("(TopParentId=@TopParentId)");
- }
-
- statement?.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
- }
- else if (queryTopParentIds.Length > 1)
- {
- var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
-
- if (enableItemsByName && includedItemByNameTypes.Count == 1)
- {
- whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))");
- statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
- }
- else if (enableItemsByName && includedItemByNameTypes.Count > 1)
- {
- var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
- whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))");
- }
- else
- {
- whereClauses.Add("TopParentId in (" + val + ")");
- }
- }
- }
-
- if (query.AncestorIds.Length == 1)
- {
- whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)");
- statement?.TryBind("@AncestorId", query.AncestorIds[0]);
- }
-
- if (query.AncestorIds.Length > 1)
- {
- var inClause = string.Join(',', query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
- whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
- }
-
- if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
- {
- var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
- whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
- statement?.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
- }
-
- if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey))
- {
- whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey");
- statement?.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey);
- }
-
- if (query.ExcludeInheritedTags.Length > 0)
- {
- var paramName = "@ExcludeInheritedTags";
- if (statement is null)
- {
- int index = 0;
- string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++));
- whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
- }
- else
- {
- for (int index = 0; index < query.ExcludeInheritedTags.Length; index++)
- {
- statement.TryBind(paramName + index, GetCleanValue(query.ExcludeInheritedTags[index]));
- }
- }
- }
-
- if (query.IncludeInheritedTags.Length > 0)
- {
- var paramName = "@IncludeInheritedTags";
- if (statement is null)
- {
- int index = 0;
- string includedTags = string.Join(',', query.IncludeInheritedTags.Select(_ => paramName + index++));
- // Episodes do not store inherit tags from their parents in the database, and the tag may be still required by the client.
- // In addtion 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)
- {
- whereClauses.Add($"""
- ((select CleanValue from ItemValues where ItemId=Guid and Type=6 and CleanValue in ({includedTags})) is not null
- OR (select CleanValue from ItemValues where ItemId=ParentId and Type=6 and CleanValue in ({includedTags})) is not null)
- """);
- }
-
- // A playlist should be accessible to its owner regardless of allowed tags.
- else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
- {
- whereClauses.Add($"""
- ((select CleanValue from ItemValues where ItemId=Guid and Type=6 and CleanValue in ({includedTags})) is not null
- OR data like @PlaylistOwnerUserId)
- """);
- }
- else
- {
- whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + includedTags + ")) is not null)");
- }
- }
- else
- {
- for (int index = 0; index < query.IncludeInheritedTags.Length; index++)
- {
- statement.TryBind(paramName + index, GetCleanValue(query.IncludeInheritedTags[index]));
- }
-
- if (query.User is not null)
- {
- statement.TryBind("@PlaylistOwnerUserId", $"""%"OwnerUserId":"{query.User.Id.ToString("N")}"%""");
- }
- }
- }
-
- if (query.SeriesStatuses.Length > 0)
- {
- var statuses = new List();
-
- foreach (var seriesStatus in query.SeriesStatuses)
- {
- statuses.Add("data like '%" + seriesStatus + "%'");
- }
-
- whereClauses.Add("(" + string.Join(" OR ", statuses) + ")");
- }
-
- if (query.BoxSetLibraryFolders.Length > 0)
- {
- var folderIdQueries = new List();
-
- foreach (var folderId in query.BoxSetLibraryFolders)
- {
- folderIdQueries.Add("data like '%" + folderId.ToString("N", CultureInfo.InvariantCulture) + "%'");
- }
-
- whereClauses.Add("(" + string.Join(" OR ", folderIdQueries) + ")");
- }
-
- if (query.VideoTypes.Length > 0)
- {
- var videoTypes = new List();
-
- foreach (var videoType in query.VideoTypes)
- {
- videoTypes.Add("data like '%\"VideoType\":\"" + videoType + "\"%'");
- }
-
- whereClauses.Add("(" + string.Join(" OR ", videoTypes) + ")");
- }
-
- if (query.Is3D.HasValue)
- {
- if (query.Is3D.Value)
- {
- whereClauses.Add("data like '%Video3DFormat%'");
- }
- else
- {
- whereClauses.Add("data not like '%Video3DFormat%'");
- }
- }
-
- if (query.IsPlaceHolder.HasValue)
- {
- if (query.IsPlaceHolder.Value)
- {
- whereClauses.Add("data like '%\"IsPlaceHolder\":true%'");
- }
- else
- {
- whereClauses.Add("(data is null or data not like '%\"IsPlaceHolder\":true%')");
- }
- }
-
- if (query.HasSpecialFeature.HasValue)
- {
- if (query.HasSpecialFeature.Value)
- {
- whereClauses.Add("ExtraIds not null");
- }
- else
- {
- whereClauses.Add("ExtraIds is null");
- }
- }
-
- if (query.HasTrailer.HasValue)
- {
- if (query.HasTrailer.Value)
- {
- whereClauses.Add("ExtraIds not null");
- }
- else
- {
- whereClauses.Add("ExtraIds is null");
- }
- }
-
- if (query.HasThemeSong.HasValue)
- {
- if (query.HasThemeSong.Value)
- {
- whereClauses.Add("ExtraIds not null");
- }
- else
- {
- whereClauses.Add("ExtraIds is null");
- }
- }
-
- if (query.HasThemeVideo.HasValue)
- {
- if (query.HasThemeVideo.Value)
- {
- whereClauses.Add("ExtraIds not null");
- }
- else
- {
- whereClauses.Add("ExtraIds is null");
- }
- }
-
- return whereClauses;
- }
-
- ///
- /// Formats a where clause for the specified provider.
- ///
- /// Whether or not to include items with this provider's ids.
- /// Provider name.
- /// Formatted SQL clause.
- private string GetProviderIdClause(bool includeResults, string provider)
- {
- return string.Format(
- CultureInfo.InvariantCulture,
- "ProviderIds {0} like '%{1}=%'",
- includeResults ? string.Empty : "not",
- provider);
- }
-
-#nullable disable
- private List GetItemByNameTypesInQuery(InternalItemsQuery query)
- {
- var list = new List();
-
- if (IsTypeInQuery(BaseItemKind.Person, query))
- {
- list.Add(typeof(Person).FullName);
- }
-
- if (IsTypeInQuery(BaseItemKind.Genre, query))
- {
- list.Add(typeof(Genre).FullName);
- }
-
- if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
- {
- list.Add(typeof(MusicGenre).FullName);
- }
-
- if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
- {
- list.Add(typeof(MusicArtist).FullName);
- }
-
- if (IsTypeInQuery(BaseItemKind.Studio, query))
- {
- list.Add(typeof(Studio).FullName);
- }
-
- return list;
- }
-
- private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
- {
- if (query.ExcludeItemTypes.Contains(type))
- {
- return false;
- }
-
- return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
- }
-
- private string GetCleanValue(string value)
- {
- if (string.IsNullOrWhiteSpace(value))
+ private string GetCleanValue(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
{
return value;
}
@@ -2500,41 +735,6 @@ namespace Emby.Server.Implementations.Data
return value.RemoveDiacritics().ToLowerInvariant();
}
- private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
- {
- if (!query.GroupByPresentationUniqueKey)
- {
- return false;
- }
-
- if (query.GroupBySeriesPresentationUniqueKey)
- {
- return false;
- }
-
- if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
- {
- return false;
- }
-
- if (query.User is null)
- {
- return false;
- }
-
- if (query.IncludeItemTypes.Length == 0)
- {
- return true;
- }
-
- return query.IncludeItemTypes.Contains(BaseItemKind.Episode)
- || query.IncludeItemTypes.Contains(BaseItemKind.Video)
- || query.IncludeItemTypes.Contains(BaseItemKind.Movie)
- || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
- || query.IncludeItemTypes.Contains(BaseItemKind.Series)
- || query.IncludeItemTypes.Contains(BaseItemKind.Season);
- }
-
///
public void UpdateInheritedValues()
{
diff --git a/Jellyfin.Data/Enums/ItemSortBy.cs b/Jellyfin.Data/Enums/ItemSortBy.cs
index 17bf1166de..ef76502947 100644
--- a/Jellyfin.Data/Enums/ItemSortBy.cs
+++ b/Jellyfin.Data/Enums/ItemSortBy.cs
@@ -154,14 +154,4 @@ public enum ItemSortBy
/// The index number.
///
IndexNumber = 29,
-
- ///
- /// The similarity score.
- ///
- SimilarityScore = 30,
-
- ///
- /// The search score.
- ///
- SearchScore = 31,
}
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemManager.cs b/Jellyfin.Server.Implementations/Item/BaseItemManager.cs
index 8f3c9636ee..f2d6b6261d 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemManager.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
@@ -36,8 +37,6 @@ public class BaseItemManager : IItemRepository
private readonly IDbContextFactory _dbProvider;
private readonly IServerApplicationHost _appHost;
-
-
private readonly ItemFields[] _allItemFields = Enum.GetValues();
private static readonly BaseItemKind[] _programTypes = new[]
@@ -146,22 +145,284 @@ public class BaseItemManager : IItemRepository
_appHost = appHost;
}
- public int GetCount(InternalItemsQuery query)
+ private IQueryable Pageinate(IQueryable query, InternalItemsQuery filter)
+ {
+ if (filter.Limit.HasValue || filter.StartIndex.HasValue)
+ {
+ var offset = filter.StartIndex ?? 0;
+
+ if (offset > 0)
+ {
+ query = query.Skip(offset);
+ }
+
+ if (filter.Limit.HasValue)
+ {
+ query = query.Take(filter.Limit.Value);
+ }
+ }
+
+ return query;
+ }
+
+ private Expression> MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query)
+ {
+#pragma warning disable CS8603 // Possible null reference return.
+ return sortBy switch
+ {
+ ItemSortBy.AirTime => e => e.SortName, // TODO
+ ItemSortBy.Runtime => e => e.RunTimeTicks,
+ ItemSortBy.Random => e => EF.Functions.Random(),
+ ItemSortBy.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.LastPlayedDate,
+ ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.PlayCount,
+ ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite,
+ ItemSortBy.IsFolder => e => e.IsFolder,
+ ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played,
+ ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played,
+ ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded,
+ ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.Type == 0).Select(f => f.CleanValue),
+ ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.Type == 1).Select(f => f.CleanValue),
+ ItemSortBy.Studio => e => e.ItemValues!.Where(f => f.Type == 3).Select(f => f.CleanValue),
+ ItemSortBy.OfficialRating => e => e.InheritedParentalRatingValue,
+ // ItemSortBy.SeriesDatePlayed => "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)",
+ ItemSortBy.SeriesSortName => e => e.SeriesName,
+ // ItemSortBy.AiredEpisodeOrder => "AiredEpisodeOrder",
+ ItemSortBy.Album => e => e.Album,
+ ItemSortBy.DateCreated => e => e.DateCreated,
+ ItemSortBy.PremiereDate => e => e.PremiereDate,
+ ItemSortBy.StartDate => e => e.StartDate,
+ ItemSortBy.Name => e => e.Name,
+ ItemSortBy.CommunityRating => e => e.CommunityRating,
+ ItemSortBy.ProductionYear => e => e.ProductionYear,
+ ItemSortBy.CriticRating => e => e.CriticRating,
+ ItemSortBy.VideoBitRate => e => e.TotalBitrate,
+ ItemSortBy.ParentIndexNumber => e => e.ParentIndexNumber,
+ ItemSortBy.IndexNumber => e => e.IndexNumber,
+ _ => e => e.SortName
+ };
+#pragma warning restore CS8603 // Possible null reference return.
+
+ }
+
+ private IQueryable MapOrderByField(IQueryable dbQuery, ItemSortBy sortBy, InternalItemsQuery query)
+ {
+ return sortBy switch
+ {
+ ItemSortBy.AirTime => dbQuery.OrderBy(e => e.SortName), // TODO
+ ItemSortBy.Runtime => dbQuery.OrderBy(e => e.RunTimeTicks),
+ ItemSortBy.Random => dbQuery.OrderBy(e => EF.Functions.Random()),
+ ItemSortBy.DatePlayed => dbQuery.OrderBy(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.LastPlayedDate),
+ ItemSortBy.PlayCount => dbQuery.OrderBy(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.PlayCount),
+ ItemSortBy.IsFavoriteOrLiked => dbQuery.OrderBy(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite),
+ ItemSortBy.IsFolder => dbQuery.OrderBy(e => e.IsFolder),
+ ItemSortBy.IsPlayed => dbQuery.OrderBy(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played),
+ ItemSortBy.IsUnplayed => dbQuery.OrderBy(e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played),
+ ItemSortBy.DateLastContentAdded => dbQuery.OrderBy(e => e.DateLastMediaAdded),
+ ItemSortBy.Artist => dbQuery.OrderBy(e => e.ItemValues!.Where(f => f.Type == 0).Select(f => f.CleanValue)),
+ ItemSortBy.AlbumArtist => dbQuery.OrderBy(e => e.ItemValues!.Where(f => f.Type == 1).Select(f => f.CleanValue)),
+ ItemSortBy.Studio => dbQuery.OrderBy(e => e.ItemValues!.Where(f => f.Type == 3).Select(f => f.CleanValue)),
+ ItemSortBy.OfficialRating => dbQuery.OrderBy(e => e.InheritedParentalRatingValue),
+ // ItemSortBy.SeriesDatePlayed => "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)",
+ ItemSortBy.SeriesSortName => dbQuery.OrderBy(e => e.SeriesName),
+ // ItemSortBy.AiredEpisodeOrder => "AiredEpisodeOrder",
+ ItemSortBy.Album => dbQuery.OrderBy(e => e.Album),
+ ItemSortBy.DateCreated => dbQuery.OrderBy(e => e.DateCreated),
+ ItemSortBy.PremiereDate => dbQuery.OrderBy(e => e.PremiereDate),
+ ItemSortBy.StartDate => dbQuery.OrderBy(e => e.StartDate),
+ ItemSortBy.Name => dbQuery.OrderBy(e => e.Name),
+ ItemSortBy.CommunityRating => dbQuery.OrderBy(e => e.CommunityRating),
+ ItemSortBy.ProductionYear => dbQuery.OrderBy(e => e.ProductionYear),
+ ItemSortBy.CriticRating => dbQuery.OrderBy(e => e.CriticRating),
+ ItemSortBy.VideoBitRate => dbQuery.OrderBy(e => e.TotalBitrate),
+ ItemSortBy.ParentIndexNumber => dbQuery.OrderBy(e => e.ParentIndexNumber),
+ ItemSortBy.IndexNumber => dbQuery.OrderBy(e => e.IndexNumber),
+ _ => dbQuery.OrderBy(e => e.SortName)
+ };
+ }
+
+ private IQueryable ApplyOrder(IQueryable query, InternalItemsQuery filter)
+ {
+ var orderBy = filter.OrderBy;
+ bool hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
+
+ if (hasSearch)
+ {
+ List<(ItemSortBy, SortOrder)> prepend = new List<(ItemSortBy, SortOrder)>(4);
+ if (hasSearch)
+ {
+ prepend.Add((ItemSortBy.SortName, SortOrder.Ascending));
+ }
+
+ orderBy = filter.OrderBy = [.. prepend, .. orderBy];
+ }
+ else if (orderBy.Count == 0)
+ {
+ return query;
+ }
+
+ foreach (var item in orderBy)
+ {
+ var expression = MapOrderByField(item.OrderBy, filter);
+ if (item.SortOrder == SortOrder.Ascending)
+ {
+ query = query.OrderBy(expression);
+ }
+ else
+ {
+ query = query.OrderByDescending(expression);
+ }
+ }
+
+ return query;
+ }
+
+ public IReadOnlyList GetItemIdsList(InternalItemsQuery filter)
+ {
+ ArgumentNullException.ThrowIfNull(filter);
+ PrepareFilterQuery(filter);
+
+ using var context = _dbProvider.CreateDbContext();
+ var dbQuery = TranslateQuery(context.BaseItems, context, filter)
+ .DistinctBy(e => e.Id);
+
+ var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
+ if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
+ {
+ dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).SelectMany(e => e);
+ }
+
+ if (enableGroupByPresentationUniqueKey)
+ {
+ dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).SelectMany(e => e);
+ }
+
+ if (filter.GroupBySeriesPresentationUniqueKey)
+ {
+ dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).SelectMany(e => e);
+ }
+
+ dbQuery = ApplyOrder(dbQuery, filter);
+
+ return Pageinate(dbQuery, filter).Select(e => e.Id).ToImmutableArray();
+ }
+
+ private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
+ {
+ if (!query.GroupByPresentationUniqueKey)
+ {
+ return false;
+ }
+
+ if (query.GroupBySeriesPresentationUniqueKey)
+ {
+ return false;
+ }
+
+ if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
+ {
+ return false;
+ }
+
+ if (query.User is null)
+ {
+ return false;
+ }
+
+ if (query.IncludeItemTypes.Length == 0)
+ {
+ return true;
+ }
+
+ return query.IncludeItemTypes.Contains(BaseItemKind.Episode)
+ || query.IncludeItemTypes.Contains(BaseItemKind.Video)
+ || query.IncludeItemTypes.Contains(BaseItemKind.Movie)
+ || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
+ || query.IncludeItemTypes.Contains(BaseItemKind.Series)
+ || query.IncludeItemTypes.Contains(BaseItemKind.Season);
+ }
+
+ ///
+ public QueryResult GetItems(InternalItemsQuery query)
{
ArgumentNullException.ThrowIfNull(query);
- // Hack for right now since we currently don't support filtering out these duplicates within a query
- if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
+ if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
{
- query.Limit = query.Limit.Value + 4;
+ var returnList = GetItemList(query);
+ return new QueryResult(
+ query.StartIndex,
+ returnList.Count,
+ returnList);
}
- if (query.IsResumable ?? false)
+ PrepareFilterQuery(query);
+ var result = new QueryResult();
+
+ using var context = _dbProvider.CreateDbContext();
+ var dbQuery = TranslateQuery(context.BaseItems, context, query)
+ .DistinctBy(e => e.Id);
+ if (query.EnableTotalRecordCount)
{
- query.IsVirtualItem = false;
+ result.TotalRecordCount = dbQuery.Count();
+ }
+
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
+ {
+ var offset = query.StartIndex ?? 0;
+
+ if (offset > 0)
+ {
+ dbQuery = dbQuery.Skip(offset);
+ }
+
+ if (query.Limit.HasValue)
+ {
+ dbQuery = dbQuery.Take(query.Limit.Value);
+ }
+ }
+
+ result.Items = dbQuery.ToList().Select(DeserialiseBaseItem).ToImmutableArray();
+ result.StartIndex = query.StartIndex ?? 0;
+ return result;
+ }
+
+ ///
+ public IReadOnlyList GetItemList(InternalItemsQuery query)
+ {
+ ArgumentNullException.ThrowIfNull(query);
+ PrepareFilterQuery(query);
+
+ using var context = _dbProvider.CreateDbContext();
+ var dbQuery = TranslateQuery(context.BaseItems, context, query)
+ .DistinctBy(e => e.Id);
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
+ {
+ var offset = query.StartIndex ?? 0;
+
+ if (offset > 0)
+ {
+ dbQuery = dbQuery.Skip(offset);
+ }
+
+ if (query.Limit.HasValue)
+ {
+ dbQuery = dbQuery.Take(query.Limit.Value);
+ }
}
+ return dbQuery.ToList().Select(DeserialiseBaseItem).ToImmutableArray();
+ }
+
+ ///
+ public int GetCount(InternalItemsQuery query)
+ {
+ ArgumentNullException.ThrowIfNull(query);
+ // Hack for right now since we currently don't support filtering out these duplicates within a query
+ PrepareFilterQuery(query);
+ using var context = _dbProvider.CreateDbContext();
+ var dbQuery = TranslateQuery(context.BaseItems, context, query);
+ return dbQuery.Count();
}
private IQueryable TranslateQuery(
@@ -1049,6 +1310,8 @@ public class BaseItemManager : IItemRepository
.Where(e => e.ExtraIds == null);
}
}
+
+ return baseQuery;
}
///
@@ -1212,9 +1475,9 @@ public class BaseItemManager : IItemRepository
dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : Guid.Parse(entity.OwnerId);
dto.Width = entity.Width.GetValueOrDefault();
dto.Height = entity.Height.GetValueOrDefault();
- if (entity.ProviderIds is not null)
+ if (entity.Provider is not null)
{
- DeserializeProviderIds(entity.ProviderIds, dto);
+ dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
}
if (entity.ExtraType is not null)
@@ -1386,7 +1649,12 @@ public class BaseItemManager : IItemRepository
entity.OwnerId = dto.OwnerId.ToString();
entity.Width = dto.Width;
entity.Height = dto.Height;
- entity.ProviderIds = SerializeProviderIds(dto.ProviderIds);
+ entity.Provider = dto.ProviderIds.Select(e => new Data.Entities.BaseItemProvider()
+ {
+ Item = entity,
+ ProviderId = e.Key,
+ ProviderValue = e.Value
+ }).ToList();
entity.Audio = dto.Audio?.ToString();
entity.ExtraType = dto.ExtraType?.ToString();
@@ -1479,10 +1747,23 @@ public class BaseItemManager : IItemRepository
private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity)
{
var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialise unkown type.");
- var dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deserialise unkown type."); ;
+ var dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deserialise unkown type.");
return Map(baseItemEntity, dto);
}
+ private static void PrepareFilterQuery(InternalItemsQuery query)
+ {
+ if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
+ {
+ query.Limit = query.Limit.Value + 4;
+ }
+
+ if (query.IsResumable ?? false)
+ {
+ query.IsVirtualItem = false;
+ }
+ }
+
private string GetCleanValue(string value)
{
if (string.IsNullOrWhiteSpace(value))
@@ -1813,5 +2094,4 @@ public class BaseItemManager : IItemRepository
return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
}
-
}