mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-03 23:36:38 +01:00
106 lines
6.4 KiB
C#
106 lines
6.4 KiB
C#
#pragma warning disable RS0030 // Do not use banned APIs
|
|
#pragma warning disable CA1304 // Specify CultureInfo
|
|
#pragma warning disable CA1311 // Specify a culture or use an invariant version
|
|
#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using Jellyfin.Data.Enums;
|
|
using Jellyfin.Database.Implementations;
|
|
using Jellyfin.Database.Implementations.Entities;
|
|
using Jellyfin.Extensions;
|
|
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>
|
|
/// <param name="jellyfinDbContext">Context.</param>
|
|
/// <returns>Func to be executed later for sorting query.</returns>
|
|
public static Expression<Func<BaseItemEntity, object?>> MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query, JellyfinDbContext jellyfinDbContext)
|
|
{
|
|
return (sortBy, query.User) switch
|
|
{
|
|
(ItemSortBy.AirTime, _) => e => e.SortName, // TODO
|
|
(ItemSortBy.Runtime, _) => e => e.RunTimeTicks,
|
|
(ItemSortBy.Random, _) => e => EF.Functions.Random(),
|
|
(ItemSortBy.DatePlayed, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.CustomDataKey).FirstOrDefault()!.LastPlayedDate,
|
|
(ItemSortBy.PlayCount, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.CustomDataKey).FirstOrDefault()!.PlayCount,
|
|
(ItemSortBy.IsFavoriteOrLiked, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.CustomDataKey).Select(f => (bool?)f.IsFavorite).FirstOrDefault() ?? false,
|
|
(ItemSortBy.IsFolder, _) => e => e.IsFolder,
|
|
(ItemSortBy.IsPlayed, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.CustomDataKey).FirstOrDefault()!.Played,
|
|
(ItemSortBy.IsUnplayed, _) => e => !e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.CustomDataKey).FirstOrDefault()!.Played,
|
|
(ItemSortBy.DateLastContentAdded, _) => e => e.DateLastMediaAdded,
|
|
(ItemSortBy.Artist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).OrderBy(f => f.ItemValue.CleanValue).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
|
(ItemSortBy.AlbumArtist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).OrderBy(f => f.ItemValue.CleanValue).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
|
(ItemSortBy.Studio, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).OrderBy(f => f.ItemValue.CleanValue).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
|
(ItemSortBy.OfficialRating, _) => e => e.InheritedParentalRatingValue,
|
|
(ItemSortBy.SeriesSortName, _) => e => e.SeriesName,
|
|
(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.CleanName,
|
|
(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,
|
|
// SeriesDatePlayed is normally handled via pre-aggregated join in ApplySeriesDatePlayedOrder.
|
|
// This correlated subquery fallback is only reached when combined with search.
|
|
(ItemSortBy.SeriesDatePlayed, not null) => e =>
|
|
jellyfinDbContext.UserData
|
|
.Where(w => w.UserId == query.User.Id && w.Played && w.Item!.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
|
|
.Max(f => f.LastPlayedDate),
|
|
(ItemSortBy.SeriesDatePlayed, null) => e =>
|
|
jellyfinDbContext.UserData
|
|
.Where(w => w.Played && w.Item!.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
|
|
.Max(f => f.LastPlayedDate),
|
|
_ => e => e.SortName
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an expression to order search results by match quality.
|
|
/// Prioritizes: exact match (0) > prefix match with word boundary (1) > prefix match (2) > contains (3).
|
|
/// Considers both CleanName and OriginalTitle for matching.
|
|
/// </summary>
|
|
/// <param name="searchTerm">The search term to match against.</param>
|
|
/// <returns>An expression that returns an integer representing match quality (lower is better).</returns>
|
|
public static Expression<Func<BaseItemEntity, int>> MapSearchRelevanceOrder(string searchTerm)
|
|
{
|
|
var cleanSearchTerm = GetCleanValue(searchTerm);
|
|
var searchPrefix = cleanSearchTerm + " ";
|
|
var originalSearchLower = searchTerm.ToLowerInvariant();
|
|
var originalSearchPrefix = originalSearchLower + " ";
|
|
return e =>
|
|
// Exact match on CleanName or OriginalTitle
|
|
(e.CleanName == cleanSearchTerm || (e.OriginalTitle != null && e.OriginalTitle.ToLower() == originalSearchLower)) ? 0 :
|
|
// Prefix match with word boundary
|
|
(e.CleanName!.StartsWith(searchPrefix) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().StartsWith(originalSearchPrefix))) ? 1 :
|
|
// Prefix match
|
|
(e.CleanName!.StartsWith(cleanSearchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().StartsWith(originalSearchLower))) ? 2 : 3;
|
|
}
|
|
|
|
private static string GetCleanValue(string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return value.RemoveDiacritics().ToLowerInvariant();
|
|
}
|
|
}
|