aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Shadowghost@users.noreply.github.com>2026-10-05 19:19:07 -0400
committerCody Robibero <cody@robibe.ro>2026-10-05 19:19:07 -0400
commit3114520c87579eb5cf150e4d764f59fd32f39412 (patch)
tree368192251a4717c7617858b55be559875bac805a
parenta2bd48c1d235b929b20047d124382f83d9830668 (diff)
Backport pull request #18279 from jellyfin/release-12.z
Recompute the parental rating score whenever an item is saved Original-merge: 29d7a3ca2444070bda748161a2da777c8e42594e Merged-by: crobibero <cody@robibe.ro> Backported-by: Cody Robibero <cody@robibe.ro>
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs6
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/ParentalRatingScoreTests.cs112
2 files changed, 118 insertions, 0 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index b4f6f57bf6..16616dd328 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2452,6 +2452,9 @@ namespace Emby.Server.Implementations.Library
var parentCollectionType = parent is not null ? GetTopFolderContentType(parent) : null;
foreach (var item in items)
{
+ // Parental controls filter on the stored score, so it has to match the ratings being saved.
+ item.OnMetadataChanged();
+
if (item is Video video && video.LocalAlternateVersions.Length > 0)
{
var videoType = video.GetType();
@@ -2658,6 +2661,9 @@ namespace Emby.Server.Implementations.Library
{
foreach (var item in items)
{
+ // Parental controls filter on the stored score, so it has to match the ratings being saved.
+ item.OnMetadataChanged();
+
item.DateLastSaved = DateTime.UtcNow;
await RunMetadataSavers(item, updateReason).ConfigureAwait(false);
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/ParentalRatingScoreTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/ParentalRatingScoreTests.cs
new file mode 100644
index 0000000000..7adf7ce3b3
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/ParentalRatingScoreTests.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Naming.Common;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Library.LibraryManager;
+
+[Collection("LibraryManagerTests")]
+public sealed class ParentalRatingScoreTests : IDisposable
+{
+ private readonly Emby.Server.Implementations.Library.LibraryManager _libraryManager;
+ private readonly List<BaseItem> _saved = [];
+ private readonly ILibraryManager? _previousLibraryManager;
+ private readonly ILocalizationManager? _previousLocalizationManager;
+
+ public ParentalRatingScoreTests()
+ {
+ var fixture = new Fixture().Customize(new AutoMoqCustomization());
+ fixture.Register(() => new NamingOptions());
+ fixture.Freeze<Mock<IServerConfigurationManager>>()
+ .Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
+ fixture.Freeze<Mock<IItemPersistenceService>>()
+ .Setup(p => p.SaveItems(It.IsAny<IReadOnlyList<BaseItem>>(), It.IsAny<CancellationToken>()))
+ .Callback<IReadOnlyList<BaseItem>, CancellationToken>((items, _) => _saved.AddRange(items));
+
+ _libraryManager = fixture.Build<Emby.Server.Implementations.Library.LibraryManager>()
+ .Do(s => s.AddParts(
+ fixture.Create<IEnumerable<IResolverIgnoreRule>>(),
+ [],
+ fixture.Create<IEnumerable<IIntroProvider>>(),
+ fixture.Create<IEnumerable<IBaseItemComparer>>(),
+ fixture.Create<IEnumerable<ILibraryPostScanTask>>()))
+ .Create();
+
+ // BaseItem resolves these through process-wide statics; restored in Dispose.
+ _previousLibraryManager = BaseItem.LibraryManager;
+ _previousLocalizationManager = BaseItem.LocalizationManager;
+ BaseItem.LibraryManager = _libraryManager;
+
+ var localizationManagerMock = new Mock<ILocalizationManager>();
+ localizationManagerMock.Setup(l => l.GetRatingScore("G", It.IsAny<string>())).Returns(new ParentalRatingScore(0, null));
+ localizationManagerMock.Setup(l => l.GetRatingScore("XXX", It.IsAny<string>())).Returns(new ParentalRatingScore(1000, null));
+ BaseItem.LocalizationManager = localizationManagerMock.Object;
+ Video.RecordingsManager ??= fixture.Create<IRecordingsManager>();
+ }
+
+ public void Dispose()
+ {
+ BaseItem.LibraryManager = _previousLibraryManager!;
+ BaseItem.LocalizationManager = _previousLocalizationManager!;
+ }
+
+ private static Movie CreateRatedMovie() => new()
+ {
+ Id = Guid.NewGuid(),
+ Name = "Up",
+ PreferredMetadataCountryCode = "us",
+ OfficialRating = "G",
+ InheritedParentalRatingValue = 0
+ };
+
+ [Fact]
+ public async Task UpdateItemAsync_ChangedCustomRating_StoresItsScore()
+ {
+ var movie = CreateRatedMovie();
+ movie.CustomRating = "XXX";
+
+ await _libraryManager.UpdateItemAsync(movie, null!, ItemUpdateType.MetadataEdit, CancellationToken.None);
+
+ Assert.Equal(1000, Assert.Single(_saved).InheritedParentalRatingValue);
+ }
+
+ [Fact]
+ public async Task UpdateItemAsync_ClearedRatings_ClearsScore()
+ {
+ var movie = CreateRatedMovie();
+ movie.OfficialRating = null;
+
+ await _libraryManager.UpdateItemAsync(movie, null!, ItemUpdateType.MetadataEdit, CancellationToken.None);
+
+ Assert.Null(Assert.Single(_saved).InheritedParentalRatingValue);
+ }
+
+ [Fact]
+ public void CreateItem_RatedItem_StoresItsScore()
+ {
+ var movie = CreateRatedMovie();
+ movie.InheritedParentalRatingValue = null;
+ movie.CustomRating = "XXX";
+
+ _libraryManager.CreateItem(movie, null);
+
+ Assert.Equal(1000, _saved.Single().InheritedParentalRatingValue);
+ }
+}