From 98d7c8d59fa3180e50ee311dfc53164325210896 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 7 Feb 2026 08:44:42 +0100 Subject: [PATCH] Make sure we deduplicate LinkedChildren --- .../Item/BaseItemRepository.cs | 16 ++++++++++++++-- .../Routines/RemoveDuplicatePlaylistChildren.cs | 11 +++++++---- .../BoxSets/BoxSetMetadataService.cs | 7 ++++++- .../Playlists/PlaylistMetadataService.cs | 7 ++++++- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 20df42583d..3ba6750045 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -1553,7 +1553,13 @@ public sealed class BaseItemRepository } } - var childIdsToCheck = resolvedChildren.Select(c => c.ChildId).Distinct().ToList(); + // Deduplicate by ChildId, keeping the last occurrence (for playlist ordering) + resolvedChildren = resolvedChildren + .GroupBy(c => c.ChildId) + .Select(g => g.Last()) + .ToList(); + + var childIdsToCheck = resolvedChildren.Select(c => c.ChildId).ToList(); var existingChildIds = childIdsToCheck.Count > 0 ? context.BaseItems .Where(e => childIdsToCheck.Contains(e.Id)) @@ -1646,8 +1652,14 @@ public sealed class BaseItemRepository } } + // Deduplicate by ChildId, keeping the last occurrence + newLinkedChildren = newLinkedChildren + .GroupBy(c => c.ChildId) + .Select(g => g.Last()) + .ToList(); + // Validate that all child items exist - var childIdsToCheck = newLinkedChildren.Select(c => c.ChildId).Distinct().ToList(); + var childIdsToCheck = newLinkedChildren.Select(c => c.ChildId).ToList(); var existingChildIds = childIdsToCheck.Count > 0 ? context.BaseItems .Where(e => childIdsToCheck.Contains(e.Id)) diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs index 23f212424b..1545ebdc8e 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs @@ -45,10 +45,13 @@ internal class RemoveDuplicatePlaylistChildren : IMigrationRoutine var linkedChildren = playlist.LinkedChildren; if (linkedChildren.Length > 0) { - var nullItemChildren = linkedChildren.Where(c => c.ItemId is null); - var deduplicatedChildren = linkedChildren.DistinctBy(c => c.ItemId); - var newLinkedChildren = nullItemChildren.Concat(deduplicatedChildren); - playlist.LinkedChildren = linkedChildren; + var newLinkedChildren = linkedChildren + .Where(c => c.ItemId is null || c.ItemId.Value.Equals(Guid.Empty)) + .Concat(linkedChildren + .Where(c => c.ItemId.HasValue && !c.ItemId.Value.Equals(Guid.Empty)) + .DistinctBy(c => c.ItemId)) + .ToArray(); + playlist.LinkedChildren = newLinkedChildren; playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult(); _playlistManager.SavePlaylistFile(playlist); } diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index bdc5b5df29..3a872f687c 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Configuration; @@ -70,7 +71,11 @@ public class BoxSetMetadataService : MetadataService if (mergeMetadataSettings) { // TODO: Change to only replace when currently empty or requested. This is currently not done because the metadata service is not handling attaching collection items based on the provider responses - targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).DistinctBy(i => i.ItemId).ToArray(); +#pragma warning disable CS0618 // Type or member is obsolete - fallback for legacy path-based dedup + targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren) + .DistinctBy(i => i.ItemId.HasValue && !i.ItemId.Value.Equals(Guid.Empty) ? i.ItemId.Value.ToString() : i.Path ?? string.Empty) + .ToArray(); +#pragma warning restore CS0618 } } diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index 45b61319b7..429830c70c 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Configuration; @@ -72,7 +73,11 @@ public class PlaylistMetadataService : MetadataService } else { - targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).DistinctBy(i => i.ItemId).ToArray(); +#pragma warning disable CS0618 // Type or member is obsolete - fallback for legacy path-based dedup + targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren) + .DistinctBy(i => i.ItemId.HasValue && !i.ItemId.Value.Equals(Guid.Empty) ? i.ItemId.Value.ToString() : i.Path ?? string.Empty) + .ToArray(); +#pragma warning restore CS0618 } if (replaceData || targetItem.Shares.Count == 0)