diff options
| author | Shadowghost <Shadowghost@users.noreply.github.com> | 2026-10-05 19:18:41 -0400 |
|---|---|---|
| committer | Cody Robibero <cody@robibe.ro> | 2026-10-05 19:18:41 -0400 |
| commit | e7764cbbd9db5e20ce764121d550b732405100e3 (patch) | |
| tree | 6a88640e294ef6499ee26eadadbce2c2551b0eab /Jellyfin.Server.Implementations | |
| parent | 2e5e042f22f30fa7f49339f6a7f51cbec32383c5 (diff) | |
Backport pull request #18001 from jellyfin/release-12.z
Keep an item's user data rows in agreement
Original-merge: fc98019b12e311fea244dccef72d521278326024
Merged-by: crobibero <cody@robibe.ro>
Backported-by: Cody Robibero <cody@robibe.ro>
Diffstat (limited to 'Jellyfin.Server.Implementations')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs | 73 |
1 files changed, 65 insertions, 8 deletions
diff --git a/Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs b/Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs index 8b79e44680..3bf33b969e 100644 --- a/Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs +++ b/Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs @@ -230,17 +230,18 @@ public class ItemPersistenceService : IItemPersistenceService var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false); await using (transaction.ConfigureAwait(false)) { - var userKeys = item.GetUserDataKeys().ToArray(); - var retentionDate = (DateTime?)null; + var userKeys = item.GetUserDataKeys().Distinct().ToList(); - await dbContext.UserData + var detached = await dbContext.UserData .Where(e => e.ItemId == BaseItemRepository.PlaceholderId) .Where(e => userKeys.Contains(e.CustomDataKey)) - .ExecuteUpdateAsync( - e => e - .SetProperty(f => f.ItemId, item.Id) - .SetProperty(f => f.RetentionDate, retentionDate), - cancellationToken).ConfigureAwait(false); + .ToListAsync(cancellationToken) + .ConfigureAwait(false); + + if (detached.Count > 0) + { + await ReconcileUserDataAsync(dbContext, item, userKeys, detached, cancellationToken).ConfigureAwait(false); + } item.UserData = await dbContext.UserData .AsNoTracking() @@ -253,6 +254,62 @@ public class ItemPersistenceService : IItemPersistenceService } } + private static async Task ReconcileUserDataAsync( + JellyfinDbContext dbContext, + BaseItemDto item, + IReadOnlyList<string> userKeys, + List<UserData> detached, + CancellationToken cancellationToken) + { + var existing = await dbContext.UserData + .Where(e => e.ItemId == item.Id) + .ToListAsync(cancellationToken) + .ConfigureAwait(false); + + // Keys the item no longer reports are carried over: they may only be missing mid-refresh. + var winners = detached.Concat(existing) + .GroupBy(e => e.UserId) + .Select(g => ( + Winner: g + .OrderByDescending(e => e.LastPlayedDate) + .ThenByDescending(e => e.PlayCount) + .ThenByDescending(e => e.PlaybackPositionTicks) + .First(), + Keys: userKeys.Union(g.Select(e => e.CustomDataKey)).ToList())) + .ToList(); + + dbContext.UserData.RemoveRange(detached); + dbContext.UserData.RemoveRange(existing); + await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + + foreach (var (winner, keys) in winners) + { + foreach (var key in keys) + { + dbContext.UserData.Add(new UserData + { + ItemId = item.Id, + Item = null, + UserId = winner.UserId, + User = null, + CustomDataKey = key, + RetentionDate = null, + AudioStreamIndex = winner.AudioStreamIndex, + IsFavorite = winner.IsFavorite, + LastPlayedDate = winner.LastPlayedDate, + Likes = winner.Likes, + PlaybackPositionTicks = winner.PlaybackPositionTicks, + PlayCount = winner.PlayCount, + Played = winner.Played, + Rating = winner.Rating, + SubtitleStreamIndex = winner.SubtitleStreamIndex + }); + } + } + + await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } + private void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(items); |
