mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-03 23:36:38 +01:00
Migrate PrimaryVersionId to GUID and fix assignment
This commit is contained in:
@@ -408,7 +408,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
// If deleting a primary version video, clear PrimaryVersionId from alternate versions
|
||||
// OwnerId check: items with OwnerId set are alternate versions or extras, not primaries
|
||||
if (item is Video video && string.IsNullOrEmpty(video.PrimaryVersionId) && video.OwnerId.IsEmpty())
|
||||
if (item is Video video && !video.PrimaryVersionId.HasValue && video.OwnerId.IsEmpty())
|
||||
{
|
||||
var alternateVersions = GetLocalAlternateVersionIds(video)
|
||||
.Concat(GetLinkedAlternateVersions(video).Select(v => v.Id))
|
||||
@@ -435,16 +435,15 @@ namespace Emby.Server.Implementations.Library
|
||||
// Update remaining alternates to point to new primary
|
||||
foreach (var alternate in alternateVersions.Skip(1))
|
||||
{
|
||||
alternate.SetPrimaryVersionId(newPrimary.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
alternate.SetPrimaryVersionId(newPrimary.Id);
|
||||
alternate.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item is Video alternateVideo && !string.IsNullOrEmpty(alternateVideo.PrimaryVersionId)
|
||||
&& Guid.TryParse(alternateVideo.PrimaryVersionId, out var primaryId))
|
||||
else if (item is Video alternateVideo && alternateVideo.PrimaryVersionId.HasValue)
|
||||
{
|
||||
// If deleting an alternate version, re-route references to its primary
|
||||
_itemRepository.RerouteLinkedChildren(alternateVideo.Id, primaryId);
|
||||
_itemRepository.RerouteLinkedChildren(alternateVideo.Id, alternateVideo.PrimaryVersionId.Value);
|
||||
}
|
||||
|
||||
var children = item.IsFolder
|
||||
@@ -2181,6 +2180,7 @@ namespace Emby.Server.Implementations.Library
|
||||
if (altVideo is not null)
|
||||
{
|
||||
altVideo.OwnerId = video.Id;
|
||||
altVideo.SetPrimaryVersionId(video.Id);
|
||||
allItems.Add(altVideo);
|
||||
}
|
||||
}
|
||||
@@ -2383,6 +2383,7 @@ namespace Emby.Server.Implementations.Library
|
||||
if (altVideo is not null)
|
||||
{
|
||||
altVideo.OwnerId = video.Id;
|
||||
altVideo.SetPrimaryVersionId(video.Id);
|
||||
allItems.Add(altVideo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,9 +147,9 @@ public class VideosController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (item.LinkedAlternateVersions.Length == 0)
|
||||
if (item.LinkedAlternateVersions.Length == 0 && item.PrimaryVersionId.HasValue)
|
||||
{
|
||||
item = _libraryManager.GetItemById<Video>(Guid.Parse(item.PrimaryVersionId));
|
||||
item = _libraryManager.GetItemById<Video>(item.PrimaryVersionId.Value);
|
||||
}
|
||||
|
||||
if (item is null)
|
||||
@@ -197,7 +197,7 @@ public class VideosController : BaseJellyfinApiController
|
||||
return BadRequest("Please supply at least two videos to merge.");
|
||||
}
|
||||
|
||||
var primaryVersion = items.FirstOrDefault(i => i.MediaSourceCount > 1 && string.IsNullOrEmpty(i.PrimaryVersionId));
|
||||
var primaryVersion = items.FirstOrDefault(i => i.MediaSourceCount > 1 && !i.PrimaryVersionId.HasValue);
|
||||
if (primaryVersion is null)
|
||||
{
|
||||
primaryVersion = items
|
||||
@@ -218,7 +218,7 @@ public class VideosController : BaseJellyfinApiController
|
||||
|
||||
foreach (var item in items.Where(i => !i.Id.Equals(primaryVersion.Id)))
|
||||
{
|
||||
item.SetPrimaryVersionId(primaryVersion.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
item.SetPrimaryVersionId(primaryVersion.Id);
|
||||
|
||||
await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ public class FixIncorrectOwnerIdRelationships : IAsyncMigrationRoutine
|
||||
lc => lc.ChildId,
|
||||
item => item.Id,
|
||||
(lc, item) => new { lc.ParentId, lc.ChildId, item.PrimaryVersionId })
|
||||
.Where(x => x.PrimaryVersionId == null || x.PrimaryVersionId != x.ParentId.ToString())
|
||||
.Where(x => !x.PrimaryVersionId.HasValue || !x.PrimaryVersionId.Value.Equals(x.ParentId))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -307,7 +307,7 @@ public class FixIncorrectOwnerIdRelationships : IAsyncMigrationRoutine
|
||||
|
||||
if (childItem is not null)
|
||||
{
|
||||
childItem.PrimaryVersionId = link.ParentId.ToString();
|
||||
childItem.PrimaryVersionId = link.ParentId;
|
||||
updatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1074,9 +1074,9 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
entity.OriginalTitle = originalTitle;
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var primaryVersionId))
|
||||
if (reader.TryGetString(index++, out var primaryVersionId) && Guid.TryParse(primaryVersionId, out var primaryVersionGuid))
|
||||
{
|
||||
entity.PrimaryVersionId = primaryVersionId;
|
||||
entity.PrimaryVersionId = primaryVersionGuid;
|
||||
}
|
||||
|
||||
if (reader.TryReadDateTime(index++, out var dateLastMediaAdded))
|
||||
|
||||
@@ -480,7 +480,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
if (item is Video video)
|
||||
{
|
||||
// Check via PrimaryVersionId
|
||||
if (!string.IsNullOrEmpty(video.PrimaryVersionId))
|
||||
if (video.PrimaryVersionId.HasValue)
|
||||
{
|
||||
Logger.LogDebug("Item is an alternate version (via PrimaryVersionId), skipping deletion: {Path}", item.Path ?? item.Name);
|
||||
continue;
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string PrimaryVersionId { get; set; }
|
||||
public Guid? PrimaryVersionId { get; set; }
|
||||
|
||||
public string[] AdditionalParts { get; set; }
|
||||
|
||||
@@ -254,9 +254,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
private int GetMediaSourceCount(HashSet<Guid> callstack = null)
|
||||
{
|
||||
callstack ??= new();
|
||||
if (!string.IsNullOrEmpty(PrimaryVersionId))
|
||||
if (PrimaryVersionId.HasValue)
|
||||
{
|
||||
var item = LibraryManager.GetItemById(PrimaryVersionId);
|
||||
var item = LibraryManager.GetItemById(PrimaryVersionId.Value);
|
||||
if (item is Video video)
|
||||
{
|
||||
if (callstack.Contains(video.Id))
|
||||
@@ -317,25 +317,17 @@ namespace MediaBrowser.Controller.Entities
|
||||
return list;
|
||||
}
|
||||
|
||||
public void SetPrimaryVersionId(string id)
|
||||
public void SetPrimaryVersionId(Guid? id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
PrimaryVersionId = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrimaryVersionId = id;
|
||||
}
|
||||
|
||||
PrimaryVersionId = id;
|
||||
PresentationUniqueKey = CreatePresentationUniqueKey();
|
||||
}
|
||||
|
||||
public override string CreatePresentationUniqueKey()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PrimaryVersionId))
|
||||
if (PrimaryVersionId.HasValue)
|
||||
{
|
||||
return PrimaryVersionId;
|
||||
return PrimaryVersionId.Value.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return base.CreatePresentationUniqueKey();
|
||||
@@ -483,6 +475,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
if (altVideo is not null)
|
||||
{
|
||||
altVideo.OwnerId = Id;
|
||||
altVideo.SetPrimaryVersionId(Id);
|
||||
LibraryManager.CreateItem(altVideo, GetParent());
|
||||
}
|
||||
}
|
||||
@@ -495,6 +488,13 @@ namespace MediaBrowser.Controller.Entities
|
||||
if (LibraryManager.GetItemById(id) is Video video)
|
||||
{
|
||||
LibraryManager.UpsertLinkedChild(Id, video.Id, LinkedChildType.LocalAlternateVersion);
|
||||
|
||||
// Ensure PrimaryVersionId is set for existing alternate versions that may not have it
|
||||
if (!video.PrimaryVersionId.HasValue)
|
||||
{
|
||||
video.SetPrimaryVersionId(Id);
|
||||
await video.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,9 +612,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
list.AddRange(LibraryManager.GetLinkedAlternateVersions(this).Select(i => ((BaseItem)i, MediaSourceType.Grouping)));
|
||||
|
||||
if (!string.IsNullOrEmpty(PrimaryVersionId))
|
||||
if (PrimaryVersionId.HasValue)
|
||||
{
|
||||
if (LibraryManager.GetItemById(PrimaryVersionId) is Video primary)
|
||||
if (LibraryManager.GetItemById(PrimaryVersionId.Value) is Video primary)
|
||||
{
|
||||
var existingIds = list.Select(i => i.Item1.Id).ToList();
|
||||
list.Add((primary, MediaSourceType.Grouping));
|
||||
|
||||
@@ -96,7 +96,7 @@ public class BaseItemEntity
|
||||
|
||||
public string? OriginalTitle { get; set; }
|
||||
|
||||
public string? PrimaryVersionId { get; set; }
|
||||
public Guid? PrimaryVersionId { get; set; }
|
||||
|
||||
public DateTime? DateLastMediaAdded { get; set; }
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChangePrimaryVersionIdToGuid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Convert "N" format (32 chars, no hyphens) to standard GUID format (36 chars with hyphens)
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = UPPER(
|
||||
SUBSTR(PrimaryVersionId,1,8)||'-'||
|
||||
SUBSTR(PrimaryVersionId,9,4)||'-'||
|
||||
SUBSTR(PrimaryVersionId,13,4)||'-'||
|
||||
SUBSTR(PrimaryVersionId,17,4)||'-'||
|
||||
SUBSTR(PrimaryVersionId,21,12))
|
||||
WHERE PrimaryVersionId IS NOT NULL AND LENGTH(PrimaryVersionId) = 32");
|
||||
|
||||
// Normalize existing standard-format values to uppercase
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = UPPER(PrimaryVersionId)
|
||||
WHERE PrimaryVersionId IS NOT NULL");
|
||||
|
||||
// Clear invalid values (not 36 characters = not a valid GUID)
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = NULL
|
||||
WHERE PrimaryVersionId IS NOT NULL AND LENGTH(PrimaryVersionId) != 36");
|
||||
|
||||
// Clear placeholder/empty GUIDs
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = NULL
|
||||
WHERE PrimaryVersionId = '00000000-0000-0000-0000-000000000000'");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Convert standard GUID format back to "N" format (remove hyphens, lowercase)
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = LOWER(REPLACE(PrimaryVersionId, '-', ''))
|
||||
WHERE PrimaryVersionId IS NOT NULL");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.2");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.3");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.AccessSchedule", b =>
|
||||
{
|
||||
@@ -294,7 +294,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Property<string>("PresentationUniqueKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PrimaryVersionId")
|
||||
b.Property<Guid?>("PrimaryVersionId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProductionLocations")
|
||||
|
||||
Reference in New Issue
Block a user