mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-10-26 16:45:09 +00:00
Backport pull request #18280 from jellyfin/release-12.z
Only propagate a changed rating to children that follow it
Original-merge: 1d5590d02a
Merged-by: crobibero <cody@robibe.ro>
Backported-by: Cody Robibero <cody@robibe.ro>
This commit is contained in:
1 parent
3114520c87
commit
35cee77a98
2 files changed
+110
-9
No files matched your search
@@ -292,6 +292,8 @@ public class ItemUpdateController : BaseJellyfinApiController
|
||||
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : null;
|
||||
item.ProductionYear = request.ProductionYear;
|
||||
|
||||
var previousOfficialRating = item.OfficialRating;
|
||||
var previousCustomRating = item.CustomRating;
|
||||
request.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
|
||||
item.OfficialRating = request.OfficialRating;
|
||||
item.CustomRating = request.CustomRating;
|
||||
@@ -318,12 +320,14 @@ public class ItemUpdateController : BaseJellyfinApiController
|
||||
{
|
||||
season.SeriesName = rseries.Name;
|
||||
|
||||
var previousSeasonOfficialRating = season.OfficialRating;
|
||||
var previousSeasonCustomRating = season.CustomRating;
|
||||
if (!season.LockedFields.Contains(MetadataField.OfficialRating))
|
||||
{
|
||||
season.OfficialRating = request.OfficialRating;
|
||||
season.OfficialRating = GetPropagatedRating(season.OfficialRating, previousOfficialRating, request.OfficialRating);
|
||||
}
|
||||
|
||||
season.CustomRating = request.CustomRating;
|
||||
season.CustomRating = GetPropagatedRating(season.CustomRating, previousCustomRating, request.CustomRating);
|
||||
|
||||
if (!season.LockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
@@ -339,10 +343,10 @@ public class ItemUpdateController : BaseJellyfinApiController
|
||||
|
||||
if (!ep.LockedFields.Contains(MetadataField.OfficialRating))
|
||||
{
|
||||
ep.OfficialRating = request.OfficialRating;
|
||||
ep.OfficialRating = GetPropagatedRating(ep.OfficialRating, previousSeasonOfficialRating, season.OfficialRating);
|
||||
}
|
||||
|
||||
ep.CustomRating = request.CustomRating;
|
||||
ep.CustomRating = GetPropagatedRating(ep.CustomRating, previousSeasonCustomRating, season.CustomRating);
|
||||
|
||||
if (!ep.LockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
@@ -362,10 +366,10 @@ public class ItemUpdateController : BaseJellyfinApiController
|
||||
|
||||
if (!ep.LockedFields.Contains(MetadataField.OfficialRating))
|
||||
{
|
||||
ep.OfficialRating = request.OfficialRating;
|
||||
ep.OfficialRating = GetPropagatedRating(ep.OfficialRating, previousOfficialRating, request.OfficialRating);
|
||||
}
|
||||
|
||||
ep.CustomRating = request.CustomRating;
|
||||
ep.CustomRating = GetPropagatedRating(ep.CustomRating, previousCustomRating, request.CustomRating);
|
||||
|
||||
if (!ep.LockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
@@ -382,10 +386,10 @@ public class ItemUpdateController : BaseJellyfinApiController
|
||||
{
|
||||
if (!track.LockedFields.Contains(MetadataField.OfficialRating))
|
||||
{
|
||||
track.OfficialRating = request.OfficialRating;
|
||||
track.OfficialRating = GetPropagatedRating(track.OfficialRating, previousOfficialRating, request.OfficialRating);
|
||||
}
|
||||
|
||||
track.CustomRating = request.CustomRating;
|
||||
track.CustomRating = GetPropagatedRating(track.CustomRating, previousCustomRating, request.CustomRating);
|
||||
|
||||
if (!track.LockedFields.Contains(MetadataField.Tags))
|
||||
{
|
||||
@@ -477,6 +481,23 @@ public class ItemUpdateController : BaseJellyfinApiController
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rating a child should carry after its parent's rating changed from <paramref name="previousRating"/>
|
||||
/// to <paramref name="newRating"/>: a child with no rating, or the parent's previous one, follows the parent.
|
||||
/// A child with a rating of its own, or any child when the parent's rating did not change, keeps its rating.
|
||||
/// </summary>
|
||||
private static string? GetPropagatedRating(string? childRating, string? previousRating, string? newRating)
|
||||
{
|
||||
if (string.Equals(previousRating ?? string.Empty, newRating ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return childRating;
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(childRating) || string.Equals(childRating, previousRating, StringComparison.OrdinalIgnoreCase)
|
||||
? newRating
|
||||
: childRating;
|
||||
}
|
||||
|
||||
private SeriesStatus? GetSeriesStatus(BaseItemDto item)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.Status))
|
||||
|
||||
@@ -4,9 +4,11 @@ using Jellyfin.Api.Controllers;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Moq;
|
||||
@@ -14,12 +16,21 @@ using Xunit;
|
||||
|
||||
namespace Jellyfin.Api.Tests.Controllers;
|
||||
|
||||
public class ItemUpdateControllerTests
|
||||
[Collection("LibraryManagerTests")]
|
||||
public sealed class ItemUpdateControllerTests : IDisposable
|
||||
{
|
||||
private readonly ItemUpdateController _subject;
|
||||
private readonly ILibraryManager? _previousLibraryManager;
|
||||
private readonly ILocalizationManager? _previousLocalizationManager;
|
||||
|
||||
public ItemUpdateControllerTests()
|
||||
{
|
||||
// Children are scored and saved through BaseItem's process-wide statics; restored in Dispose.
|
||||
_previousLibraryManager = BaseItem.LibraryManager;
|
||||
_previousLocalizationManager = BaseItem.LocalizationManager;
|
||||
BaseItem.LibraryManager = Mock.Of<ILibraryManager>();
|
||||
BaseItem.LocalizationManager = Mock.Of<ILocalizationManager>();
|
||||
|
||||
_subject = new ItemUpdateController(
|
||||
Mock.Of<IFileSystem>(),
|
||||
Mock.Of<ILibraryManager>(),
|
||||
@@ -70,6 +81,75 @@ public class ItemUpdateControllerTests
|
||||
Assert.Equal("tt1234567", movie.ProviderIds["Imdb"]);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BaseItem.LibraryManager = _previousLibraryManager!;
|
||||
BaseItem.LocalizationManager = _previousLocalizationManager!;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateItem_SeriesRatingUnchanged_KeepsEpisodeRatings()
|
||||
{
|
||||
// Regression test for https://github.com/jellyfin/jellyfin/issues/18241
|
||||
var (series, season, episodes) = CreateSeries("TV-PG", "TV-PG", "TV-14", null);
|
||||
|
||||
await InvokeUpdateItem(new BaseItemDto { Overview = "New overview", OfficialRating = "TV-PG" }, series);
|
||||
|
||||
Assert.Equal("TV-PG", season.OfficialRating);
|
||||
Assert.Equal("TV-14", episodes[1].OfficialRating);
|
||||
Assert.Null(episodes[2].OfficialRating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateItem_SeriesRatingChanged_PropagatesOnlyToChildrenFollowingIt()
|
||||
{
|
||||
var (series, season, episodes) = CreateSeries("TV-PG", "TV-PG", "TV-14", null);
|
||||
var locked = new Episode { OfficialRating = "TV-PG", PreferredMetadataCountryCode = "us", LockedFields = [MetadataField.OfficialRating] };
|
||||
season.Children = [.. episodes, locked];
|
||||
|
||||
await InvokeUpdateItem(new BaseItemDto { OfficialRating = "TV-MA" }, series);
|
||||
|
||||
Assert.Equal("TV-MA", season.OfficialRating);
|
||||
Assert.Equal("TV-MA", episodes[0].OfficialRating);
|
||||
Assert.Equal("TV-14", episodes[1].OfficialRating);
|
||||
Assert.Equal("TV-MA", episodes[2].OfficialRating);
|
||||
Assert.Equal("TV-PG", locked.OfficialRating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateItem_SeriesCustomRatingChanged_PropagatesIt()
|
||||
{
|
||||
var (series, season, episodes) = CreateSeries("TV-PG", "TV-PG", "TV-14", null);
|
||||
episodes[0].CustomRating = "TV-Y";
|
||||
|
||||
await InvokeUpdateItem(new BaseItemDto { OfficialRating = "TV-PG", CustomRating = "XXX" }, series);
|
||||
|
||||
Assert.Equal("XXX", season.CustomRating);
|
||||
Assert.Equal("TV-Y", episodes[0].CustomRating);
|
||||
Assert.Equal("XXX", episodes[1].CustomRating);
|
||||
Assert.Equal("TV-14", episodes[1].OfficialRating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateItem_SeasonRatingChanged_KeepsEpisodesWithTheirOwnRating()
|
||||
{
|
||||
var (_, season, episodes) = CreateSeries("TV-PG", "TV-PG", "TV-14", null);
|
||||
|
||||
await InvokeUpdateItem(new BaseItemDto { OfficialRating = "TV-MA" }, season);
|
||||
|
||||
Assert.Equal("TV-MA", episodes[0].OfficialRating);
|
||||
Assert.Equal("TV-14", episodes[1].OfficialRating);
|
||||
Assert.Equal("TV-MA", episodes[2].OfficialRating);
|
||||
}
|
||||
|
||||
private static (Series Series, Season Season, Episode[] Episodes) CreateSeries(string seriesRating, params string?[] episodeRatings)
|
||||
{
|
||||
var episodes = Array.ConvertAll(episodeRatings, r => new Episode { OfficialRating = r, PreferredMetadataCountryCode = "us" });
|
||||
var season = new Season { OfficialRating = seriesRating, PreferredMetadataCountryCode = "us", Children = episodes };
|
||||
var series = new Series { OfficialRating = seriesRating, PreferredMetadataCountryCode = "us", Children = [season] };
|
||||
return (series, season, episodes);
|
||||
}
|
||||
|
||||
private Task InvokeUpdateItem(BaseItemDto request, BaseItem item)
|
||||
{
|
||||
return _subject.UpdateItem(request, item);
|
||||
|
||||
Reference in new issue
Block a user