diff options
| author | Bond-009 <bond.009@outlook.com> | 2026-06-08 19:41:39 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-08 19:41:39 +0200 |
| commit | 97fd210cd38b26bceed3b601e2a8797d070a5b02 (patch) | |
| tree | a2c0f6f6720aa37da6dc8d628f7b1723e4249d6a | |
| parent | 1a786f26c1e800550e823751f08581167edbdfc1 (diff) | |
| parent | f5847596384c35b5bf4119cd8b39e4234af6e899 (diff) | |
Prefer local alternate versions when deduplicating linked children
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs | 4 | ||||
| -rw-r--r-- | Jellyfin.Server/Migrations/Routines/20260113120000_MigrateLinkedChildren.cs | 29 |
2 files changed, 32 insertions, 1 deletions
diff --git a/Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs b/Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs index ffa5cff1f2..7c0cfe7c15 100644 --- a/Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs +++ b/Jellyfin.Server.Implementations/Item/ItemPersistenceService.cs @@ -557,9 +557,11 @@ public class ItemPersistenceService : IItemPersistenceService } } + // Deduplicate; local (file-based) relationships take priority over linked (user-merged) + // ones, matching the LinkedChildren migration. newLinkedChildren = newLinkedChildren .GroupBy(c => c.ChildId) - .Select(g => g.Last()) + .Select(g => g.OrderBy(c => c.Type == LinkedChildType.LocalAlternateVersion ? 0 : 1).First()) .ToList(); var childIdsToCheck = newLinkedChildren.Select(c => c.ChildId).ToList(); diff --git a/Jellyfin.Server/Migrations/Routines/20260113120000_MigrateLinkedChildren.cs b/Jellyfin.Server/Migrations/Routines/20260113120000_MigrateLinkedChildren.cs index 74f03f5107..c433c1d043 100644 --- a/Jellyfin.Server/Migrations/Routines/20260113120000_MigrateLinkedChildren.cs +++ b/Jellyfin.Server/Migrations/Routines/20260113120000_MigrateLinkedChildren.cs @@ -223,6 +223,35 @@ internal class MigrateLinkedChildren : IDatabaseMigrationRoutine toInsert = toInsert.Where(lc => existingChildIds.Contains(lc.ChildId)).ToList(); + // Drop linked (user-merged) entries that point at items the parent owns (local + // file-based alternates or extras). These stem from legacy data that merged an + // owned item onto its own primary and would wrongly mark server-merged groups + // as user-merged (splittable). + var linkedChildIds = toInsert + .Where(lc => lc.ChildType == LinkedChildType.LinkedAlternateVersion) + .Select(lc => lc.ChildId) + .Distinct() + .ToList(); + + if (linkedChildIds.Count > 0) + { + var ownerIdByChildId = context.BaseItems + .WhereOneOrMany(linkedChildIds, b => b.Id) + .Where(b => b.OwnerId.HasValue) + .Select(b => new { b.Id, b.OwnerId }) + .ToDictionary(b => b.Id, b => b.OwnerId!.Value); + + var removedCount = toInsert.RemoveAll(lc => + lc.ChildType == LinkedChildType.LinkedAlternateVersion + && ownerIdByChildId.TryGetValue(lc.ChildId, out var ownerId) + && ownerId.Equals(lc.ParentId)); + + if (removedCount > 0) + { + _logger.LogInformation("Skipped {Count} LinkedAlternateVersion records pointing at items owned by their parent.", removedCount); + } + } + context.LinkedChildren.AddRange(toInsert); context.SaveChanges(); |
