aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-02-28 17:08:32 +0100
committerShadowghost <Ghost_of_Stone@web.de>2026-02-28 17:08:32 +0100
commit116a036d566ff5b40a3670daaea0720392da97fd (patch)
treeefdfd8b9d437d48b589f1358ab8f6f372eb8ee07
parent8ee4f951fee9e7be3614fd6c8444e528bcdfdeba (diff)
Fix promotion
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs77
1 files changed, 69 insertions, 8 deletions
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 3f88557571..4506bafced 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -489,16 +489,16 @@ namespace MediaBrowser.Controller.Entities
// Collect replaced primaries for deferred deletion (after CreateItems)
var replacedPrimaries = new List<(Video OldPrimary, Video NewPrimary)>();
+ // Build a set of paths that are alternate versions of valid children
+ // These items should not be deleted - they're managed by their primary video
+ var alternateVersionPaths = validChildren
+ .OfType<Video>()
+ .SelectMany(v => v.LocalAlternateVersions ?? [])
+ .Where(p => !string.IsNullOrEmpty(p))
+ .ToHashSet(StringComparer.OrdinalIgnoreCase);
+
if (shouldRemove && itemsRemoved.Count > 0)
{
- // Build a set of paths that are alternate versions of valid children
- // These items should not be deleted - they're managed by their primary video
- var alternateVersionPaths = validChildren
- .OfType<Video>()
- .SelectMany(v => v.LocalAlternateVersions ?? [])
- .Where(p => !string.IsNullOrEmpty(p))
- .ToHashSet(StringComparer.OrdinalIgnoreCase);
-
foreach (var item in itemsRemoved)
{
if (!item.CanDelete())
@@ -596,6 +596,67 @@ namespace MediaBrowser.Controller.Entities
LibraryManager.DeleteItem(oldPrimary, new DeleteOptions { DeleteFileLocation = false }, this, false);
}
+ // Demote old primaries that are now alternate versions of newly created primaries.
+ // This handles the case where a new file is added that becomes the new primary
+ // (e.g. movie-2 added, movie-3 was primary → movie-3 needs demotion).
+ // Items in replacedPrimaries are excluded (already in actuallyRemoved).
+ var oldPrimariesToDemote = new List<(Video OldPrimary, Video NewPrimary)>();
+ foreach (var item in itemsRemoved.Except(actuallyRemoved))
+ {
+ if (item is Video video
+ && video.OwnerId.IsEmpty()
+ && !string.IsNullOrEmpty(item.Path)
+ && alternateVersionPaths.Contains(item.Path))
+ {
+ var newPrimary = newItems
+ .OfType<Video>()
+ .FirstOrDefault(v => (v.LocalAlternateVersions ?? [])
+ .Any(p => string.Equals(p, item.Path, StringComparison.OrdinalIgnoreCase)));
+ if (newPrimary is not null)
+ {
+ oldPrimariesToDemote.Add((video, newPrimary));
+ }
+ }
+ }
+
+ foreach (var (oldPrimary, newPrimary) in oldPrimariesToDemote)
+ {
+ Logger.LogInformation(
+ "Demoting old primary {OldName} ({OldId}) to alternate of new primary {NewName} ({NewId})",
+ oldPrimary.Name,
+ oldPrimary.Id,
+ newPrimary.Name,
+ newPrimary.Id);
+
+ // First: update old primary's alternate items to point to new primary.
+ // Order matters — update alternates FIRST so they don't get orphan-deleted
+ // when old primary's arrays are cleared.
+ var oldAlternateIds = LibraryManager.GetLocalAlternateVersionIds(oldPrimary)
+ .Concat(LibraryManager.GetLinkedAlternateVersions(oldPrimary).Select(v => v.Id))
+ .Distinct()
+ .ToList();
+
+ foreach (var altId in oldAlternateIds)
+ {
+ if (LibraryManager.GetItemById(altId) is Video altVideo && !altVideo.Id.Equals(newPrimary.Id))
+ {
+ altVideo.SetPrimaryVersionId(newPrimary.Id);
+ altVideo.OwnerId = newPrimary.Id;
+ await altVideo.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ // Then: demote old primary — clear its arrays and set it as alternate of new primary
+ oldPrimary.LocalAlternateVersions = [];
+ oldPrimary.LinkedAlternateVersions = [];
+ oldPrimary.SetPrimaryVersionId(newPrimary.Id);
+ oldPrimary.OwnerId = newPrimary.Id;
+ await oldPrimary.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
+
+ // Re-route playlist/collection references from old primary to new primary
+ await LibraryManager.RerouteLinkedChildReferencesAsync(oldPrimary.Id, newPrimary.Id).ConfigureAwait(false);
+ }
+
// 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)