mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-30 19:32:57 +01:00
Fix promotion
This commit is contained in:
@@ -489,16 +489,16 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
// Collect replaced primaries for deferred deletion (after CreateItems)
|
// Collect replaced primaries for deferred deletion (after CreateItems)
|
||||||
var replacedPrimaries = new List<(Video OldPrimary, Video NewPrimary)>();
|
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)
|
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)
|
foreach (var item in itemsRemoved)
|
||||||
{
|
{
|
||||||
if (!item.CanDelete())
|
if (!item.CanDelete())
|
||||||
@@ -596,6 +596,67 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
LibraryManager.DeleteItem(oldPrimary, new DeleteOptions { DeleteFileLocation = false }, this, false);
|
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
|
// 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).
|
// that share the same user data keys (eg. same episode replaced with a new file).
|
||||||
if (actuallyRemoved.Count > 0)
|
if (actuallyRemoved.Count > 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user