Fix multiple version handling

This commit is contained in:
Shadowghost
2026-02-05 00:17:44 +01:00
parent aedd2b04a2
commit a0346fe5b7
8 changed files with 260 additions and 8 deletions

View File

@@ -1430,10 +1430,15 @@ namespace MediaBrowser.Controller.Entities
});
foreach (var removedExtra in removedExtras)
{
LibraryManager.DeleteItem(removedExtra, new DeleteOptions()
// Only delete items that are actual extras (have ExtraType set)
// Items with OwnerId but no ExtraType might be alternate versions, not extras
if (removedExtra.ExtraType.HasValue)
{
DeleteFileLocation = false
});
LibraryManager.DeleteItem(removedExtra, new DeleteOptions()
{
DeleteFileLocation = false
});
}
}
}

View File

@@ -9,8 +9,10 @@ using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.Movies
{
@@ -91,6 +93,20 @@ namespace MediaBrowser.Controller.Entities.Movies
};
var id = LibraryManager.GetNewItemId(path, typeof(Movie));
// Check if the file still exists
if (!FileSystem.FileExists(path))
{
// File was removed - clean up any orphaned database entry
if (LibraryManager.GetItemById(id) is Movie orphanedMovie && orphanedMovie.OwnerId.Equals(Id))
{
Logger.LogInformation("Alternate version file no longer exists, removing orphaned item: {Path}", path);
LibraryManager.DeleteItem(orphanedMovie, new DeleteOptions { DeleteFileLocation = false });
}
return;
}
if (LibraryManager.GetItemById(id) is not Movie movie)
{
movie = LibraryManager.ResolvePath(FileSystem.GetFileSystemInfo(path)) as Movie;
@@ -109,6 +125,9 @@ namespace MediaBrowser.Controller.Entities.Movies
}
await RefreshMetadataForOwnedItem(movie, copyTitleMetadata, newOptions, cancellationToken).ConfigureAwait(false);
// Create LinkedChild entry for this local alternate version
LibraryManager.UpsertLinkedChild(Id, movie.Id, LinkedChildType.LocalAlternateVersion);
}
/// <inheritdoc />

View File

@@ -19,6 +19,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
{
@@ -428,6 +429,17 @@ namespace MediaBrowser.Controller.Entities
{
var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
// Clean up LocalAlternateVersions - remove paths that no longer exist
if (LocalAlternateVersions.Length > 0)
{
var validPaths = LocalAlternateVersions.Where(FileSystem.FileExists).ToArray();
if (validPaths.Length != LocalAlternateVersions.Length)
{
LocalAlternateVersions = validPaths;
hasChanges = true;
}
}
if (IsStacked)
{
var tasks = AdditionalParts
@@ -467,7 +479,21 @@ namespace MediaBrowser.Controller.Entities
SearchResult = null
};
var id = LibraryManager.GetNewItemId(path, typeof(Video));
var id = LibraryManager.GetNewItemId(path, GetType());
// Check if the file still exists
if (!FileSystem.FileExists(path))
{
// File was removed - clean up any orphaned database entry
if (LibraryManager.GetItemById(id) is Video orphanedVideo && orphanedVideo.OwnerId.Equals(Id))
{
Logger.LogInformation("Owned video file no longer exists, removing orphaned item: {Path}", path);
LibraryManager.DeleteItem(orphanedVideo, new DeleteOptions { DeleteFileLocation = false });
}
return;
}
if (LibraryManager.GetItemById(id) is not Video video)
{
video = LibraryManager.ResolvePath(FileSystem.GetFileSystemInfo(path)) as Video;
@@ -486,6 +512,11 @@ namespace MediaBrowser.Controller.Entities
}
await RefreshMetadataForOwnedItem(video, copyTitleMetadata, newOptions, cancellationToken).ConfigureAwait(false);
// Create LinkedChild entry for this local alternate version
// This ensures the relationship exists in the database even if the alternate version
// was created after the primary video was first saved
LibraryManager.UpsertLinkedChild(Id, video.Id, LinkedChildType.LocalAlternateVersion);
}
private void RefreshLinkedAlternateVersions()

View File

@@ -20,6 +20,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using LinkedChildType = MediaBrowser.Controller.Entities.LinkedChildType;
using Person = MediaBrowser.Controller.Entities.Person;
namespace MediaBrowser.Controller.Library
@@ -229,6 +230,14 @@ namespace MediaBrowser.Controller.Library
/// <returns>Enumerable of linked Video items.</returns>
IEnumerable<Video> GetLinkedAlternateVersions(Video video);
/// <summary>
/// Creates or updates a LinkedChild entry linking a parent to a child item.
/// </summary>
/// <param name="parentId">The parent item ID.</param>
/// <param name="childId">The child item ID.</param>
/// <param name="childType">The type of linked child relationship.</param>
void UpsertLinkedChild(Guid parentId, Guid childId, LinkedChildType childType);
/// <summary>
/// Adds the parts.
/// </summary>

View File

@@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using LinkedChildType = MediaBrowser.Controller.Entities.LinkedChildType;
namespace MediaBrowser.Controller.Persistence;
@@ -234,4 +235,13 @@ public interface IItemRepository
/// <param name="toChildId">The child ID to re-route to.</param>
/// <returns>Number of references updated.</returns>
int RerouteLinkedChildren(Guid fromChildId, Guid toChildId);
/// <summary>
/// Creates or updates a LinkedChild entry linking a parent to a child item.
/// If the link already exists, updates the child type.
/// </summary>
/// <param name="parentId">The parent item ID.</param>
/// <param name="childId">The child item ID.</param>
/// <param name="childType">The type of linked child relationship.</param>
void UpsertLinkedChild(Guid parentId, Guid childId, LinkedChildType childType);
}