aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations
diff options
context:
space:
mode:
authorTim Eisele <Ghost_of_Stone@web.de>2025-03-31 05:51:54 +0200
committerGitHub <noreply@github.com>2025-03-30 21:51:54 -0600
commit3fc3b04daf929d1d3a9533fc410cb77885eb2e8a (patch)
treefe15e4d6c24780f9efd393df845cd3099a8f18d6 /Jellyfin.Server.Implementations
parent2ace8803453b235b0b0ae29f0069f9e5a3c069c8 (diff)
Rework parental ratings (#12615)
Diffstat (limited to 'Jellyfin.Server.Implementations')
-rw-r--r--Jellyfin.Server.Implementations/Extensions/ExpressionExtensions.cs70
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.cs83
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs6
3 files changed, 123 insertions, 36 deletions
diff --git a/Jellyfin.Server.Implementations/Extensions/ExpressionExtensions.cs b/Jellyfin.Server.Implementations/Extensions/ExpressionExtensions.cs
new file mode 100644
index 000000000..d70ac672f
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Extensions/ExpressionExtensions.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Jellyfin.Server.Implementations.Extensions;
+
+/// <summary>
+/// Provides <see cref="Expression"/> extension methods.
+/// </summary>
+public static class ExpressionExtensions
+{
+ /// <summary>
+ /// Combines two predicates into a single predicate using a logical OR operation.
+ /// </summary>
+ /// <typeparam name="T">The predicate parameter type.</typeparam>
+ /// <param name="firstPredicate">The first predicate expression to combine.</param>
+ /// <param name="secondPredicate">The second predicate expression to combine.</param>
+ /// <returns>A new expression representing the OR combination of the input predicates.</returns>
+ public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> firstPredicate, Expression<Func<T, bool>> secondPredicate)
+ {
+ ArgumentNullException.ThrowIfNull(firstPredicate);
+ ArgumentNullException.ThrowIfNull(secondPredicate);
+
+ var invokedExpression = Expression.Invoke(secondPredicate, firstPredicate.Parameters);
+ return Expression.Lambda<Func<T, bool>>(Expression.OrElse(firstPredicate.Body, invokedExpression), firstPredicate.Parameters);
+ }
+
+ /// <summary>
+ /// Combines multiple predicates into a single predicate using a logical OR operation.
+ /// </summary>
+ /// <typeparam name="T">The predicate parameter type.</typeparam>
+ /// <param name="predicates">A collection of predicate expressions to combine.</param>
+ /// <returns>A new expression representing the OR combination of all input predicates.</returns>
+ public static Expression<Func<T, bool>> Or<T>(this IEnumerable<Expression<Func<T, bool>>> predicates)
+ {
+ ArgumentNullException.ThrowIfNull(predicates);
+
+ return predicates.Aggregate((aggregatePredicate, nextPredicate) => aggregatePredicate.Or(nextPredicate));
+ }
+
+ /// <summary>
+ /// Combines two predicates into a single predicate using a logical AND operation.
+ /// </summary>
+ /// <typeparam name="T">The predicate parameter type.</typeparam>
+ /// <param name="firstPredicate">The first predicate expression to combine.</param>
+ /// <param name="secondPredicate">The second predicate expression to combine.</param>
+ /// <returns>A new expression representing the AND combination of the input predicates.</returns>
+ public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> firstPredicate, Expression<Func<T, bool>> secondPredicate)
+ {
+ ArgumentNullException.ThrowIfNull(firstPredicate);
+ ArgumentNullException.ThrowIfNull(secondPredicate);
+
+ var invokedExpression = Expression.Invoke(secondPredicate, firstPredicate.Parameters);
+ return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(firstPredicate.Body, invokedExpression), firstPredicate.Parameters);
+ }
+
+ /// <summary>
+ /// Combines multiple predicates into a single predicate using a logical AND operation.
+ /// </summary>
+ /// <typeparam name="T">The predicate parameter type.</typeparam>
+ /// <param name="predicates">A collection of predicate expressions to combine.</param>
+ /// <returns>A new expression representing the AND combination of all input predicates.</returns>
+ public static Expression<Func<T, bool>> And<T>(this IEnumerable<Expression<Func<T, bool>>> predicates)
+ {
+ ArgumentNullException.ThrowIfNull(predicates);
+
+ return predicates.Aggregate((aggregatePredicate, nextPredicate) => aggregatePredicate.And(nextPredicate));
+ }
+}
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
index b0a36b3ae..08c024f43 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
@@ -9,6 +9,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.Json;
@@ -19,6 +20,7 @@ using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
+using Jellyfin.Server.Implementations.Extensions;
using MediaBrowser.Common;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Channels;
@@ -781,6 +783,7 @@ public sealed class BaseItemRepository
entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
entity.IsInMixedFolder = dto.IsInMixedFolder;
entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
+ entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
entity.CriticRating = dto.CriticRating;
entity.PresentationUniqueKey = dto.PresentationUniqueKey;
entity.OriginalTitle = dto.OriginalTitle;
@@ -1796,61 +1799,73 @@ public sealed class BaseItemRepository
.Where(e => filter.OfficialRatings.Contains(e.OfficialRating));
}
- if (filter.HasParentalRating ?? false)
+ Expression<Func<BaseItemEntity, bool>>? minParentalRatingFilter = null;
+ if (filter.MinParentalRating != null)
{
- if (filter.MinParentalRating.HasValue)
+ var min = filter.MinParentalRating;
+ minParentalRatingFilter = e => e.InheritedParentalRatingValue >= min.Score || e.InheritedParentalRatingValue == null;
+ if (min.SubScore != null)
{
- baseQuery = baseQuery
- .Where(e => e.InheritedParentalRatingValue >= filter.MinParentalRating.Value);
+ minParentalRatingFilter = minParentalRatingFilter.And(e => e.InheritedParentalRatingValue >= min.SubScore || e.InheritedParentalRatingValue == null);
}
+ }
- if (filter.MaxParentalRating.HasValue)
+ Expression<Func<BaseItemEntity, bool>>? maxParentalRatingFilter = null;
+ if (filter.MaxParentalRating != null)
+ {
+ var max = filter.MaxParentalRating;
+ maxParentalRatingFilter = e => e.InheritedParentalRatingValue <= max.Score || e.InheritedParentalRatingValue == null;
+ if (max.SubScore != null)
{
- baseQuery = baseQuery
- .Where(e => e.InheritedParentalRatingValue < filter.MaxParentalRating.Value);
+ maxParentalRatingFilter = maxParentalRatingFilter.And(e => e.InheritedParentalRatingValue <= max.SubScore || e.InheritedParentalRatingValue == null);
}
}
- else if (filter.BlockUnratedItems.Length > 0)
+
+ if (filter.HasParentalRating ?? false)
{
- var unratedItems = filter.BlockUnratedItems.Select(f => f.ToString()).ToArray();
- if (filter.MinParentalRating.HasValue)
+ if (minParentalRatingFilter != null)
{
- if (filter.MaxParentalRating.HasValue)
- {
- baseQuery = baseQuery
- .Where(e => (e.InheritedParentalRatingValue == null && !unratedItems.Contains(e.UnratedType))
- || (e.InheritedParentalRatingValue >= filter.MinParentalRating && e.InheritedParentalRatingValue <= filter.MaxParentalRating));
- }
- else
- {
- baseQuery = baseQuery
- .Where(e => (e.InheritedParentalRatingValue == null && !unratedItems.Contains(e.UnratedType))
- || e.InheritedParentalRatingValue >= filter.MinParentalRating);
- }
+ baseQuery = baseQuery.Where(minParentalRatingFilter);
}
- else
+
+ if (maxParentalRatingFilter != null)
{
- baseQuery = baseQuery
- .Where(e => e.InheritedParentalRatingValue != null && !unratedItems.Contains(e.UnratedType));
+ baseQuery = baseQuery.Where(maxParentalRatingFilter);
}
}
- else if (filter.MinParentalRating.HasValue)
+ else if (filter.BlockUnratedItems.Length > 0)
{
- if (filter.MaxParentalRating.HasValue)
+ var unratedItemTypes = filter.BlockUnratedItems.Select(f => f.ToString()).ToArray();
+ Expression<Func<BaseItemEntity, bool>> unratedItemFilter = e => e.InheritedParentalRatingValue != null || !unratedItemTypes.Contains(e.UnratedType);
+
+ if (minParentalRatingFilter != null && maxParentalRatingFilter != null)
{
- baseQuery = baseQuery
- .Where(e => e.InheritedParentalRatingValue != null && e.InheritedParentalRatingValue >= filter.MinParentalRating.Value && e.InheritedParentalRatingValue <= filter.MaxParentalRating.Value);
+ baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter.And(maxParentalRatingFilter)));
+ }
+ else if (minParentalRatingFilter != null)
+ {
+ baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter));
+ }
+ else if (maxParentalRatingFilter != null)
+ {
+ baseQuery = baseQuery.Where(unratedItemFilter.And(maxParentalRatingFilter));
}
else
{
- baseQuery = baseQuery
- .Where(e => e.InheritedParentalRatingValue != null && e.InheritedParentalRatingValue >= filter.MinParentalRating.Value);
+ baseQuery = baseQuery.Where(unratedItemFilter);
}
}
- else if (filter.MaxParentalRating.HasValue)
+ else if (minParentalRatingFilter != null || maxParentalRatingFilter != null)
{
- baseQuery = baseQuery
- .Where(e => e.InheritedParentalRatingValue != null && e.InheritedParentalRatingValue >= filter.MaxParentalRating.Value);
+ if (minParentalRatingFilter != null)
+ {
+ baseQuery = baseQuery.Where(minParentalRatingFilter);
+ }
+
+ if (maxParentalRatingFilter != null)
+ {
+ baseQuery = baseQuery.Where(maxParentalRatingFilter);
+ }
}
else if (!filter.HasParentalRating ?? false)
{
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 3c39e5503..3dfb14d71 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -342,7 +342,8 @@ namespace Jellyfin.Server.Implementations.Users
},
Policy = new UserPolicy
{
- MaxParentalRating = user.MaxParentalAgeRating,
+ MaxParentalRating = user.MaxParentalRatingScore,
+ MaxParentalSubRating = user.MaxParentalRatingSubScore,
EnableUserPreferenceAccess = user.EnableUserPreferenceAccess,
RemoteClientBitrateLimit = user.RemoteClientBitrateLimit ?? 0,
AuthenticationProviderId = user.AuthenticationProviderId,
@@ -668,7 +669,8 @@ namespace Jellyfin.Server.Implementations.Users
_ => policy.LoginAttemptsBeforeLockout
};
- user.MaxParentalAgeRating = policy.MaxParentalRating;
+ user.MaxParentalRatingScore = policy.MaxParentalRating;
+ user.MaxParentalRatingSubScore = policy.MaxParentalSubRating;
user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess;
user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit;
user.AuthenticationProviderId = policy.AuthenticationProviderId;