diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-02-22 12:34:58 +0100 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-02-22 12:34:58 +0100 |
| commit | 6ce5f9dfd5d9a27cf70366f35948b2f02e941389 (patch) | |
| tree | 9ed4aab7f33f3002e5f3f832491d729b6051be23 | |
| parent | 5541653f73c5ac7d6ff0de79b08ba332d346ee62 (diff) | |
Cleanup folder duplicates of series
| -rw-r--r-- | Jellyfin.Server/Migrations/Routines/FixIncorrectOwnerIdRelationships.cs | 15 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Entities/Folder.cs | 30 |
2 files changed, 39 insertions, 6 deletions
diff --git a/Jellyfin.Server/Migrations/Routines/FixIncorrectOwnerIdRelationships.cs b/Jellyfin.Server/Migrations/Routines/FixIncorrectOwnerIdRelationships.cs index a8445d6da8..b9f5964c6a 100644 --- a/Jellyfin.Server/Migrations/Routines/FixIncorrectOwnerIdRelationships.cs +++ b/Jellyfin.Server/Migrations/Routines/FixIncorrectOwnerIdRelationships.cs @@ -101,7 +101,9 @@ public class FixIncorrectOwnerIdRelationships : IAsyncMigrationRoutine { b.Id, b.Type, - HasChildren = context.BaseItems.Any(c => c.OwnerId.HasValue && c.OwnerId.Value.Equals(b.Id) && c.ExtraType != null && c.ExtraType != 0) + b.DateCreated, + HasOwnedExtras = context.BaseItems.Any(c => c.OwnerId.HasValue && c.OwnerId.Value.Equals(b.Id)), + HasDirectChildren = context.BaseItems.Any(c => c.ParentId.HasValue && c.ParentId.Value.Equals(b.Id)) }) .ToListAsync(cancellationToken) .ConfigureAwait(false); @@ -111,10 +113,13 @@ public class FixIncorrectOwnerIdRelationships : IAsyncMigrationRoutine continue; } - // Keep the item that has legitimate children (extras), then prefer Movie type over Video type, then lowest ID - var itemWithChildren = itemsWithPath.FirstOrDefault(i => i.HasChildren); - var movieTypeItem = itemsWithPath.FirstOrDefault(i => i.Type == "MediaBrowser.Controller.Entities.Movies.Movie"); - var itemToKeep = itemWithChildren ?? movieTypeItem ?? itemsWithPath.MinBy(i => i.Id); + // Keep the item that has direct children, then owned extras, then prefer non-Folder types, then newest + var itemToKeep = itemsWithPath + .OrderByDescending(i => i.HasDirectChildren) + .ThenByDescending(i => i.HasOwnedExtras) + .ThenByDescending(i => i.Type != "MediaBrowser.Controller.Entities.Folder") + .ThenByDescending(i => i.DateCreated) + .First(); if (itemToKeep is null) { continue; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 7c66dc6e36..ecf99accb6 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -420,6 +420,17 @@ namespace MediaBrowser.Controller.Entities // Create a list for our validated children var newItems = new List<BaseItem>(); + var actuallyRemoved = new List<BaseItem>(); + + // Build a reverse path→item lookup for detecting type changes + var currentChildrenByPath = new Dictionary<string, BaseItem>(StringComparer.OrdinalIgnoreCase); + foreach (var kvp in currentChildren) + { + if (!string.IsNullOrEmpty(kvp.Value.Path)) + { + currentChildrenByPath.TryAdd(kvp.Value.Path, kvp.Value); + } + } cancellationToken.ThrowIfCancellationRequested(); @@ -447,6 +458,24 @@ namespace MediaBrowser.Controller.Entities continue; } + // Check if an existing item occupies the same path with different type/ID + if (!string.IsNullOrEmpty(child.Path) + && currentChildrenByPath.TryGetValue(child.Path, out var staleItem) + && !staleItem.Id.Equals(child.Id)) + { + Logger.LogInformation( + "Item type changed at {Path}: {OldType} -> {NewType}, removing stale entry", + child.Path, + staleItem.GetType().Name, + child.GetType().Name); + + currentChildren.Remove(staleItem.Id); + currentChildrenByPath.Remove(child.Path); + staleItem.SetParent(null); + LibraryManager.DeleteItem(staleItem, new DeleteOptions { DeleteFileLocation = false }, this, false); + actuallyRemoved.Add(staleItem); + } + // Brand new item - needs to be added child.SetParent(this); newItems.Add(child); @@ -456,7 +485,6 @@ namespace MediaBrowser.Controller.Entities // That's all the new and changed ones - now see if any have been removed and need cleanup var itemsRemoved = currentChildren.Values.Except(validChildren).ToList(); var shouldRemove = !IsRoot || allowRemoveRoot; - var actuallyRemoved = new List<BaseItem>(); // If it's an AggregateFolder, don't remove if (shouldRemove && itemsRemoved.Count > 0) { |
