aboutsummaryrefslogtreecommitdiff
path: root/src/Jellyfin.Database/Jellyfin.Database.Implementations
diff options
context:
space:
mode:
authorDaniel Țuțuianu <tutuianu_daniel@yahoo.com>2026-06-17 06:16:42 +0300
committerDaniel Țuțuianu <tutuianu_daniel@yahoo.com>2026-06-17 06:16:42 +0300
commit1ea525a4083dbdc929605eb0eb5c6add93bc8392 (patch)
tree97056e3e9b8e06ae825199214ec3f9d34b53e4c8 /src/Jellyfin.Database/Jellyfin.Database.Implementations
parent372c1681d8272c6fa8f120a132bc40351067fb10 (diff)
parent3307406ac8d7aa62184f99946f69a1cbf92a060b (diff)
Merge branch 'master' into fix/livetv-channel-icon-refresh
Resolve GuideManager conflict by keeping LiveTvChannelImageHelper so channel icons re-fetch on every guide refresh, including when the URL is unchanged.
Diffstat (limited to 'src/Jellyfin.Database/Jellyfin.Database.Implementations')
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/User.cs11
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ViewType.cs47
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs91
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/UserConfiguration.cs4
4 files changed, 150 insertions, 3 deletions
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/User.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/User.cs
index 6c81fa729c..b10e210e5d 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/User.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/User.cs
@@ -27,6 +27,7 @@ namespace Jellyfin.Database.Implementations.Entities
ArgumentException.ThrowIfNullOrEmpty(passwordResetProviderId);
Username = username;
+ NormalizedUsername = username.ToUpperInvariant();
AuthenticationProviderId = authenticationProviderId;
PasswordResetProviderId = passwordResetProviderId;
@@ -74,6 +75,16 @@ namespace Jellyfin.Database.Implementations.Entities
public string Username { get; set; }
/// <summary>
+ /// Gets or sets the user's normalized name.
+ /// </summary>
+ /// <remarks>
+ /// Required, Max length = 255.
+ /// </remarks>
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string NormalizedUsername { get; set; }
+
+ /// <summary>
/// Gets or sets the user's password, or <c>null</c> if none is set.
/// </summary>
/// <remarks>
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ViewType.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ViewType.cs
index b2bcbf2bb6..34810b9199 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ViewType.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/ViewType.cs
@@ -108,5 +108,50 @@ public enum ViewType
/// <summary>
/// Shows upcoming.
/// </summary>
- Upcoming = 20
+ Upcoming = 20,
+
+ /// <summary>
+ /// Shows authors.
+ /// </summary>
+ Authors = 21,
+
+ /// <summary>
+ /// Shows books.
+ /// </summary>
+ Books = 22,
+
+ /// <summary>
+ /// Shows folders.
+ /// </summary>
+ Folders = 23,
+
+ /// <summary>
+ /// Shows mixed media.
+ /// </summary>
+ Mixed = 24,
+
+ /// <summary>
+ /// Shows photos.
+ /// </summary>
+ Photos = 25,
+
+ /// <summary>
+ /// Shows photo albums.
+ /// </summary>
+ PhotoAlbums = 26,
+
+ /// <summary>
+ /// Shows series timers.
+ /// </summary>
+ SeriesTimers = 27,
+
+ /// <summary>
+ /// Shows studios.
+ /// </summary>
+ Studios = 28,
+
+ /// <summary>
+ /// Shows videos.
+ /// </summary>
+ Videos = 29
}
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs
index f386e882e2..1af7460540 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs
@@ -112,6 +112,92 @@ public static class JellyfinQueryHelperExtensions
}
/// <summary>
+ /// Filters items that match any of the specified (provider name, value) pairs.
+ /// </summary>
+ /// <param name="baseQuery">The source query.</param>
+ /// <param name="providerIds">Dictionary mapping provider names to arrays of values to match.</param>
+ /// <returns>A filtered query.</returns>
+ public static IQueryable<BaseItemEntity> WhereHasAnyProviderIds(
+ this IQueryable<BaseItemEntity> baseQuery,
+ IReadOnlyDictionary<string, string[]> providerIds)
+ {
+ var providerKeys = providerIds
+ .SelectMany(kvp => kvp.Value.Select(v => $"{kvp.Key}:{v}"))
+ .ToList();
+
+ if (providerKeys.Count == 0)
+ {
+ return baseQuery;
+ }
+
+ return baseQuery.Where(e => e.Provider!.Any(p => providerKeys.Contains(p.ProviderId + ":" + p.ProviderValue)));
+ }
+
+ /// <summary>
+ /// Filters items that have any of the specified providers. Empty/null values match any value for that provider.
+ /// </summary>
+ /// <param name="baseQuery">The source query.</param>
+ /// <param name="providerIds">Dictionary mapping provider names to optional values.</param>
+ /// <returns>A filtered query.</returns>
+ public static IQueryable<BaseItemEntity> WhereHasAnyProviderId(
+ this IQueryable<BaseItemEntity> baseQuery,
+ IReadOnlyDictionary<string, string> providerIds)
+ {
+ var existenceOnly = providerIds
+ .Where(e => string.IsNullOrEmpty(e.Value))
+ .Select(e => e.Key)
+ .ToList();
+
+ var specificValues = providerIds
+ .Where(e => !string.IsNullOrEmpty(e.Value))
+ .Select(e => $"{e.Key}:{e.Value}")
+ .ToList();
+
+ if (existenceOnly.Count == 0 && specificValues.Count == 0)
+ {
+ return baseQuery;
+ }
+
+ if (existenceOnly.Count == 0)
+ {
+ return baseQuery.Where(e => e.Provider!.Any(p =>
+ specificValues.Contains(p.ProviderId + ":" + p.ProviderValue)));
+ }
+
+ if (specificValues.Count == 0)
+ {
+ return baseQuery.Where(e => e.Provider!.Any(p => existenceOnly.Contains(p.ProviderId)));
+ }
+
+ // Single EXISTS over Provider with both predicates OR'd, instead of two separate subqueries.
+ return baseQuery.Where(e => e.Provider!.Any(p =>
+ existenceOnly.Contains(p.ProviderId) ||
+ specificValues.Contains(p.ProviderId + ":" + p.ProviderValue)));
+ }
+
+ /// <summary>
+ /// Excludes items that match any of the specified (provider name, value) pairs.
+ /// </summary>
+ /// <param name="baseQuery">The source query.</param>
+ /// <param name="providerIds">Dictionary mapping provider names to values to exclude.</param>
+ /// <returns>A filtered query.</returns>
+ public static IQueryable<BaseItemEntity> WhereExcludeProviderIds(
+ this IQueryable<BaseItemEntity> baseQuery,
+ IReadOnlyDictionary<string, string> providerIds)
+ {
+ var excludeKeys = providerIds
+ .Select(e => $"{e.Key}:{e.Value}")
+ .ToList();
+
+ if (excludeKeys.Count == 0)
+ {
+ return baseQuery;
+ }
+
+ return baseQuery.Where(e => e.Provider!.All(p => !excludeKeys.Contains(p.ProviderId + ":" + p.ProviderValue)));
+ }
+
+ /// <summary>
/// Builds an optimised query expression checking one property against a list of values while maintaining an optimal query.
/// </summary>
/// <typeparam name="TEntity">The entity.</typeparam>
@@ -138,9 +224,10 @@ public static class JellyfinQueryHelperExtensions
var containsMethodInfo = _containsQueryCache.GetOrAdd(typeof(TProperty), static (key) => _containsMethodGenericCache.MakeGenericMethod(key));
- if (oneOf.Count < 4) // arbitrary value choosen.
+ // Threshold picked from microbenchmarks on SQLite: inline IN(const,...) beats a
+ // parameterized array lookup by ~5-10% up to ~32 elements.
+ if (oneOf.Count <= 32)
{
- // if we have 3 or fewer values to check against its faster to do a IN(const,const,const) lookup
return Expression.Lambda<Func<TEntity, bool>>(Expression.Call(null, containsMethodInfo, Expression.Constant(oneOf), property.Body), parameter);
}
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/UserConfiguration.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/UserConfiguration.cs
index 61b5e06e8a..ed4138680d 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/UserConfiguration.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/UserConfiguration.cs
@@ -50,6 +50,10 @@ namespace Jellyfin.Database.Implementations.ModelConfiguration
builder
.HasIndex(entity => entity.Username)
.IsUnique();
+
+ builder
+ .HasIndex(entity => entity.NormalizedUsername)
+ .IsUnique();
}
}
}