diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-01-17 14:54:32 +0100 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-01-18 19:46:37 +0100 |
| commit | 1491494bcb8764e48133123226a8e025c5357474 (patch) | |
| tree | 81bd08695a18a08a0c41510ca4be1189ddf1bdba | |
| parent | 0b77f970489869395b9a032c84c571c286d95a7f (diff) | |
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
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/OrderMapper.cs | 27 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Entities/BaseItem.cs | 16 |
2 files changed, 34 insertions, 9 deletions
diff --git a/Jellyfin.Server.Implementations/Item/OrderMapper.cs b/Jellyfin.Server.Implementations/Item/OrderMapper.cs index 1ae7cc6c4a..91b4f58f41 100644 --- a/Jellyfin.Server.Implementations/Item/OrderMapper.cs +++ b/Jellyfin.Server.Implementations/Item/OrderMapper.cs @@ -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) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 7586b99e77..498b7d19e4 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -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; } |
