mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-09 09:18:46 +01:00
Fix unplayed propagation
This commit is contained in:
@@ -2112,12 +2112,23 @@ namespace MediaBrowser.Controller.Entities
|
||||
// I think it is okay to do this here.
|
||||
// if this is only called when a user is manually forcing something to un-played
|
||||
// then it probably is what we want to do...
|
||||
ResetPlayedState(data);
|
||||
|
||||
UserDataManager.SaveUserData(user, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the played state on the supplied user data.
|
||||
/// </summary>
|
||||
/// <param name="data">The user data to reset.</param>
|
||||
protected static void ResetPlayedState(UserItemData data)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
data.PlayCount = 0;
|
||||
data.PlaybackPositionTicks = 0;
|
||||
data.LastPlayedDate = null;
|
||||
data.Played = false;
|
||||
|
||||
UserDataManager.SaveUserData(user, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -364,9 +364,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="played">The played status to apply to the alternate versions.</param>
|
||||
/// <param name="resetPosition">When <c>true</c>, the playback position of each version is also
|
||||
/// reset, keeping the versions consistent with a deliberate played/unplayed toggle. When
|
||||
/// <c>false</c>, only the played flag changes and each version keeps its own resume point.</param>
|
||||
/// <param name="resetPosition">When marking played, controls whether each version's resume point
|
||||
/// is also reset (<c>true</c>) or left untouched (<c>false</c>). Ignored when marking unplayed,
|
||||
/// which always fully resets every version.</param>
|
||||
public void PropagatePlayedState(User user, bool played, bool resetPosition = true)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
@@ -383,14 +383,28 @@ namespace MediaBrowser.Controller.Entities
|
||||
continue;
|
||||
}
|
||||
|
||||
var dto = new UpdateUserItemDataDto { Played = played };
|
||||
if (resetPosition)
|
||||
if (played)
|
||||
{
|
||||
dto.PlaybackPositionTicks = 0;
|
||||
}
|
||||
var dto = new UpdateUserItemDataDto { Played = true };
|
||||
if (resetPosition)
|
||||
{
|
||||
dto.PlaybackPositionTicks = 0;
|
||||
}
|
||||
|
||||
// SaveUserData only writes the fields set on the DTO, so play count and other state are preserved.
|
||||
UserDataManager.SaveUserData(user, item, dto, UserDataSaveReason.TogglePlayed);
|
||||
// SaveUserData only writes the fields set on the DTO, so play count and other state are preserved.
|
||||
UserDataManager.SaveUserData(user, item, dto, UserDataSaveReason.TogglePlayed);
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = UserDataManager.GetUserData(user, item);
|
||||
if (data is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ResetPlayedState(data);
|
||||
UserDataManager.SaveUserData(user, item, data, UserDataSaveReason.TogglePlayed, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -168,6 +169,41 @@ public class BaseItemTests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropagatePlayedState_Unwatched_ClearsAllWatchedStateOnVersions()
|
||||
{
|
||||
var (primary, alt1, alt2) = SetupVersionGroup();
|
||||
|
||||
// Each alternate starts out watched, with a play count, resume point and last-played date.
|
||||
var existing = new Dictionary<Guid, UserItemData>
|
||||
{
|
||||
[alt1.Id] = new UserItemData { Key = "alt1", Played = true, PlayCount = 3, PlaybackPositionTicks = 1000, LastPlayedDate = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
|
||||
[alt2.Id] = new UserItemData { Key = "alt2", Played = true, PlayCount = 1, PlaybackPositionTicks = 500, LastPlayedDate = new DateTime(2021, 2, 2, 0, 0, 0, DateTimeKind.Utc) },
|
||||
};
|
||||
|
||||
var saved = new List<UserItemData>();
|
||||
var userDataManager = new Mock<IUserDataManager>();
|
||||
userDataManager.Setup(x => x.GetUserData(It.IsAny<User>(), It.IsAny<BaseItem>()))
|
||||
.Returns((User _, BaseItem item) => existing.GetValueOrDefault(item.Id));
|
||||
userDataManager
|
||||
.Setup(x => x.SaveUserData(It.IsAny<User>(), It.IsAny<BaseItem>(), It.IsAny<UserItemData>(), It.IsAny<UserDataSaveReason>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<User, BaseItem, UserItemData, UserDataSaveReason, CancellationToken>((_, _, data, _, _) => saved.Add(data));
|
||||
BaseItem.UserDataManager = userDataManager.Object;
|
||||
|
||||
primary.PropagatePlayedState(new User("test", "default", "default"), false);
|
||||
|
||||
// Every alternate is fully reset to an unwatched state, mirroring MarkUnplayed: the played flag,
|
||||
// play count, resume point and last-played date are all cleared so no watched state lingers.
|
||||
Assert.Equal(2, saved.Count);
|
||||
Assert.All(saved, d =>
|
||||
{
|
||||
Assert.False(d.Played);
|
||||
Assert.Equal(0, d.PlayCount);
|
||||
Assert.Equal(0, d.PlaybackPositionTicks);
|
||||
Assert.Null(d.LastPlayedDate);
|
||||
});
|
||||
}
|
||||
|
||||
private static List<(Guid ItemId, UpdateUserItemDataDto Dto)> CaptureSaves()
|
||||
{
|
||||
var saved = new List<(Guid ItemId, UpdateUserItemDataDto Dto)>();
|
||||
|
||||
Reference in New Issue
Block a user