From 8ee4f951fee9e7be3614fd6c8444e528bcdfdeba Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 28 Feb 2026 11:31:52 +0100 Subject: Defer primary deletion if we replace primary until after item creation --- MediaBrowser.Controller/Entities/Folder.cs | 78 +++++++++++++++--------------- 1 file changed, 40 insertions(+), 38 deletions(-) (limited to 'MediaBrowser.Controller/Entities/Folder.cs') diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index ed41bdb6ba..3f88557571 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -486,8 +486,8 @@ namespace MediaBrowser.Controller.Entities var itemsRemoved = currentChildren.Values.Except(validChildren).ToList(); var shouldRemove = !IsRoot || allowRemoveRoot; // If it's an AggregateFolder, don't remove - // Collect old primaries that need demotion to alternates of newly created primaries - var oldPrimariesToDemote = new List<(Video OldPrimary, Video NewPrimary)>(); + // Collect replaced primaries for deferred deletion (after CreateItems) + var replacedPrimaries = new List<(Video OldPrimary, Video NewPrimary)>(); if (shouldRemove && itemsRemoved.Count > 0) { @@ -518,35 +518,37 @@ namespace MediaBrowser.Controller.Entities } } - if (item.IsFileProtocol) - { - Logger.LogDebug("Removed item: {Path}", item.Path); - - actuallyRemoved.Add(item); - item.SetParent(null); - LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false); - } - } - - // Detect items that need demotion AFTER all deletions have run. - // DeleteItem may promote an alternate to primary (clearing its OwnerId), - // so we must check OwnerId after the deletion loop to see the updated state. - foreach (var item in itemsRemoved.Except(actuallyRemoved)) - { - if (item is Video video - && video.OwnerId.IsEmpty() - && !string.IsNullOrEmpty(item.Path) - && alternateVersionPaths.Contains(item.Path)) + // Defer deletion if this primary video is being replaced by a new primary + // that takes over its alternates. Deleting now would trigger premature + // promotion inside DeleteItem and write stale paths to collection NFOs. + if (item is Video primaryVideo + && !primaryVideo.PrimaryVersionId.HasValue + && primaryVideo.OwnerId.IsEmpty() + && (primaryVideo.LocalAlternateVersions ?? []).Any(p => alternateVersionPaths.Contains(p))) { var newPrimary = newItems .OfType