From 7bf08daeec0448598782495c0cd176f3607a1a28 Mon Sep 17 00:00:00 2001 From: David Federman Date: Wed, 11 Feb 2026 23:43:09 -0800 Subject: [PATCH] Reattach user data after removing items during library scan When items are removed during a library scan, their user data is detached to a placeholder. If a replacement item already exists (e.g., a new version of the same episode was added before the old file was deleted), the user data would be stranded in the placeholder because the replacement item's initial ReattachUserDataAsync call happened before the old item was deleted. This fix checks for remaining valid children that share user data keys with removed items and reattaches any detached user data to them. Fixes #16149 --- MediaBrowser.Controller/Entities/Folder.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d2a3290c47..2ecb6cbdff 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -452,6 +452,7 @@ 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(); // If it's an AggregateFolder, don't remove if (shouldRemove && itemsRemoved.Count > 0) { @@ -467,6 +468,7 @@ namespace MediaBrowser.Controller.Entities { Logger.LogDebug("Removed item: {Path}", item.Path); + actuallyRemoved.Add(item); item.SetParent(null); LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false); } @@ -477,6 +479,20 @@ namespace MediaBrowser.Controller.Entities { LibraryManager.CreateItems(newItems, this, cancellationToken); } + + // After removing items, reattach any detached user data to remaining children + // that share the same user data keys (eg. same episode replaced with a new file). + if (actuallyRemoved.Count > 0) + { + var removedKeys = actuallyRemoved.SelectMany(i => i.GetUserDataKeys()).ToHashSet(); + foreach (var child in validChildren) + { + if (child.GetUserDataKeys().Any(removedKeys.Contains)) + { + await child.ReattachUserDataAsync(cancellationToken).ConfigureAwait(false); + } + } + } } else {