diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-02-05 00:17:44 +0100 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-02-05 01:41:07 +0100 |
| commit | a0346fe5b70a434860f973086be176ecc2018a52 (patch) | |
| tree | 9e005d17c7d7712ccf65527fae7c009ef5d0967c /MediaBrowser.Controller | |
| parent | aedd2b04a2687adfcc52db96aa3fb7b2ad94fdcc (diff) | |
Fix multiple version handling
Diffstat (limited to 'MediaBrowser.Controller')
| -rw-r--r-- | MediaBrowser.Controller/Entities/BaseItem.cs | 11 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Entities/Movies/Movie.cs | 19 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Entities/Video.cs | 33 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Library/ILibraryManager.cs | 9 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Persistence/IItemRepository.cs | 10 |
5 files changed, 78 insertions, 4 deletions
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f1c1555842..3f04b1ffae 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -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 + }); + } } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 797f44e2d5..5ab149a49d 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -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 /> diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 1ddc193359..21aa50b49d 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -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() diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index adb590ddbe..9b46dec3fe 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -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 @@ -230,6 +231,14 @@ namespace MediaBrowser.Controller.Library 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> /// <param name="rules">The rules.</param> diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index a063debbb5..c35e8e50a3 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -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); } |
