Add early tag check exit and enhance search ordering

- BaseItem: Skip GetInheritedTags() call for users without tag
  restrictions, improving visibility check performance
- BaseItem: Only fetch parents once in visibility chec
- OrderMapper: Include OriginalTitle in search relevance scoring
  for better matching of foreign content
This commit is contained in:
Shadowghost
2026-01-17 14:54:32 +01:00
parent 0b77f97048
commit 1491494bcb
2 changed files with 34 additions and 9 deletions

View File

@@ -1,4 +1,7 @@
#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;
@@ -57,10 +60,18 @@ public static class OrderMapper
(ItemSortBy.SeriesDatePlayed, not null) => e =>
jellyfinDbContext.BaseItems
.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
.Join(jellyfinDbContext.UserData.Where(w => w.UserId == query.User.Id && w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate)
.LeftJoin(
jellyfinDbContext.UserData.Where(w => w.UserId == query.User.Id && w.Played),
item => item.Id,
userData => userData.ItemId,
(item, userData) => userData == null ? (DateTime?)null : userData.LastPlayedDate)
.Max(f => f),
(ItemSortBy.SeriesDatePlayed, null) => e => jellyfinDbContext.BaseItems.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
.Join(jellyfinDbContext.UserData.Where(w => w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate)
.LeftJoin(
jellyfinDbContext.UserData.Where(w => w.Played),
item => item.Id,
userData => userData.ItemId,
(item, userData) => userData == null ? (DateTime?)null : userData.LastPlayedDate)
.Max(f => f),
// ItemSortBy.SeriesDatePlayed => e => jellyfinDbContext.UserData
// .Where(u => u.Item!.SeriesPresentationUniqueKey == e.PresentationUniqueKey && u.Played)
@@ -73,6 +84,7 @@ public static class OrderMapper
/// <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>
@@ -80,10 +92,15 @@ public static class OrderMapper
{
var cleanSearchTerm = GetCleanValue(searchTerm);
var searchPrefix = cleanSearchTerm + " ";
var originalSearchLower = searchTerm.ToLowerInvariant();
var originalSearchPrefix = originalSearchLower + " ";
return e =>
e.CleanName == cleanSearchTerm ? 0 :
e.CleanName!.StartsWith(searchPrefix) ? 1 :
e.CleanName!.StartsWith(cleanSearchTerm) ? 2 : 3;
// 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)

View File

@@ -1334,6 +1334,7 @@ namespace MediaBrowser.Controller.Entities
return false;
}
var parents = GetParents().ToList();
if (GetParents().Any(i => !i.IsVisible(user, true)))
{
return false;
@@ -1341,7 +1342,7 @@ namespace MediaBrowser.Controller.Entities
if (checkFolders)
{
var topParent = GetParents().LastOrDefault() ?? this;
var topParent = parents.Count > 0 ? parents[^1] : this;
if (string.IsNullOrEmpty(topParent.Path))
{
@@ -1670,8 +1671,16 @@ namespace MediaBrowser.Controller.Entities
private bool IsVisibleViaTags(User user, bool skipAllowedTagsCheck)
{
var allowedTagsPreference = user.GetPreference(PreferenceKind.AllowedTags);
var blockedTagsPreference = user.GetPreference(PreferenceKind.BlockedTags);
var needsTagCheck = allowedTagsPreference.Length > 0 || blockedTagsPreference.Length > 0;
if (!needsTagCheck)
{
return true;
}
var allTags = GetInheritedTags();
if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
if (blockedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
@@ -1682,8 +1691,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
var allowedTagsPreference = user.GetPreference(PreferenceKind.AllowedTags);
if (!skipAllowedTagsCheck && allowedTagsPreference.Length != 0 && !allowedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
if (!skipAllowedTagsCheck && !allowedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
{
return false;
}