Bläddra i källkod

Fix Sort by Year Bug (#12101) (#13733)

Jacob Warren 2 månader sedan
förälder
incheckning
07f07ba6bc

+ 2 - 44
Jellyfin.Server.Implementations/Item/BaseItemRepository.cs

@@ -7,11 +7,8 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
-using System.Collections.Immutable;
 using System.Globalization;
-using System.IO;
 using System.Linq;
-using System.Linq.Expressions;
 using System.Reflection;
 using System.Text;
 using System.Text.Json;
@@ -1209,45 +1206,6 @@ public sealed class BaseItemRepository
         return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
     }
 
-    private Expression<Func<BaseItemEntity, object>> 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 == query.User!.Id)!.LastPlayedDate,
-            ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.PlayCount,
-            ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.IsFavorite,
-            ItemSortBy.IsFolder => e => e.IsFolder,
-            ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.Played,
-            ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.Played,
-            ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded,
-            ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
-            ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
-            ItemSortBy.Studio => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
-            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 bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
     {
         if (!query.GroupByPresentationUniqueKey)
@@ -1302,7 +1260,7 @@ public sealed class BaseItemRepository
         var firstOrdering = orderBy.FirstOrDefault();
         if (firstOrdering != default)
         {
-            var expression = MapOrderByField(firstOrdering.OrderBy, filter);
+            var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter);
             if (firstOrdering.SortOrder == SortOrder.Ascending)
             {
                 orderedQuery = query.OrderBy(expression);
@@ -1327,7 +1285,7 @@ public sealed class BaseItemRepository
 
         foreach (var item in orderBy.Skip(1))
         {
-            var expression = MapOrderByField(item.OrderBy, filter);
+            var expression = OrderMapper.MapOrderByField(item.OrderBy, filter);
             if (item.SortOrder == SortOrder.Ascending)
             {
                 orderedQuery = orderedQuery!.ThenBy(expression);

+ 57 - 0
Jellyfin.Server.Implementations/Item/OrderMapper.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Entities;
+using Microsoft.EntityFrameworkCore;
+
+namespace Jellyfin.Server.Implementations.Item;
+
+/// <summary>
+/// Static class for methods which maps types of ordering to their respecting ordering functions.
+/// </summary>
+public static class OrderMapper
+{
+    /// <summary>
+    /// Creates Func to be executed later with a given BaseItemEntity input for sorting items on query.
+    /// </summary>
+    /// <param name="sortBy">Item property to sort by.</param>
+    /// <param name="query">Context Query.</param>
+    /// <returns>Func to be executed later for sorting query.</returns>
+    public static Expression<Func<BaseItemEntity, object?>> MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query)
+    {
+        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))!.LastPlayedDate,
+            ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.PlayCount,
+            ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.IsFavorite,
+            ItemSortBy.IsFolder => e => e.IsFolder,
+            ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.Played,
+            ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.Played,
+            ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded,
+            ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
+            ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
+            ItemSortBy.Studio => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
+            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 ?? (e.ProductionYear.HasValue ? DateTime.MinValue.AddYears(e.ProductionYear.Value - 1) : null)),
+            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
+        };
+    }
+}

+ 35 - 0
tests/Jellyfin.Server.Implementations.Tests/Item/OrderMapperTests.cs

@@ -0,0 +1,35 @@
+using System;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using Jellyfin.Server.Implementations.Item;
+using MediaBrowser.Controller.Entities;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Item;
+
+public class OrderMapperTests
+{
+    [Fact]
+    public void ShouldReturnMappedOrderForSortingByPremierDate()
+    {
+        var orderFunc = OrderMapper.MapOrderByField(ItemSortBy.PremiereDate, new InternalItemsQuery()).Compile();
+
+        var expectedDate = new DateTime(1, 2, 3);
+        var expectedProductionYearDate = new DateTime(4, 1, 1);
+
+        var entityWithOnlyProductionYear = new BaseItemEntity { Id = Guid.NewGuid(), Type = "Test", ProductionYear = expectedProductionYearDate.Year };
+        var entityWithOnlyPremierDate = new BaseItemEntity { Id = Guid.NewGuid(), Type = "Test", PremiereDate = expectedDate };
+        var entityWithBothPremierDateAndProductionYear = new BaseItemEntity { Id = Guid.NewGuid(), Type = "Test", PremiereDate = expectedDate, ProductionYear = expectedProductionYearDate.Year };
+        var entityWithoutEitherPremierDateOrProductionYear = new BaseItemEntity { Id = Guid.NewGuid(), Type = "Test" };
+
+        var resultWithOnlyProductionYear = orderFunc(entityWithOnlyProductionYear);
+        var resultWithOnlyPremierDate = orderFunc(entityWithOnlyPremierDate);
+        var resultWithBothPremierDateAndProductionYear = orderFunc(entityWithBothPremierDateAndProductionYear);
+        var resultWithoutEitherPremierDateOrProductionYear = orderFunc(entityWithoutEitherPremierDateOrProductionYear);
+
+        Assert.Equal(resultWithOnlyProductionYear, expectedProductionYearDate);
+        Assert.Equal(resultWithOnlyPremierDate, expectedDate);
+        Assert.Equal(resultWithBothPremierDateAndProductionYear, expectedDate);
+        Assert.Null(resultWithoutEitherPremierDateOrProductionYear);
+    }
+}