Expanded People architecture and fixed migration

pull/12798/head
JPVenson 6 months ago
parent f397fc5b98
commit b73985e04f

@ -154,7 +154,7 @@ public class BaseItemEntity
public Guid? SeriesId { get; set; }
public ICollection<People>? Peoples { get; set; }
public ICollection<PeopleBaseItemMap>? Peoples { get; set; }
public ICollection<UserData>? UserData { get; set; }

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities;
#pragma warning disable CA2227 // Collection properties should be read only
/// <summary>
/// People entity.
@ -11,37 +10,22 @@ namespace Jellyfin.Data.Entities;
public class People
{
/// <summary>
/// Gets or Sets The ItemId.
/// Gets or Sets the PeopleId.
/// </summary>
public required Guid ItemId { get; set; }
/// <summary>
/// Gets or Sets Reference Item.
/// </summary>
public required BaseItemEntity Item { get; set; }
public required Guid Id { get; set; }
/// <summary>
/// Gets or Sets the Persons Name.
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Gets or Sets the Role.
/// </summary>
public string? Role { get; set; }
/// <summary>
/// Gets or Sets the Type.
/// </summary>
public string? PersonType { get; set; }
/// <summary>
/// Gets or Sets the SortOrder.
/// </summary>
public int? SortOrder { get; set; }
/// <summary>
/// Gets or Sets the ListOrder.
/// Gets or Sets the mapping of People to BaseItems.
/// </summary>
public int? ListOrder { get; set; }
public ICollection<PeopleBaseItemMap>? BaseItems { get; set; }
}

@ -0,0 +1,44 @@
using System;
namespace Jellyfin.Data.Entities;
/// <summary>
/// Mapping table for People to BaseItems.
/// </summary>
public class PeopleBaseItemMap
{
/// <summary>
/// Gets or Sets the SortOrder.
/// </summary>
public int? SortOrder { get; set; }
/// <summary>
/// Gets or Sets the ListOrder.
/// </summary>
public int? ListOrder { get; set; }
/// <summary>
/// Gets or Sets the Role name the assosiated actor played in the <see cref="BaseItemEntity"/>.
/// </summary>
public string? Role { get; set; }
/// <summary>
/// Gets or Sets The ItemId.
/// </summary>
public required Guid ItemId { get; set; }
/// <summary>
/// Gets or Sets Reference Item.
/// </summary>
public required BaseItemEntity Item { get; set; }
/// <summary>
/// Gets or Sets The PeopleId.
/// </summary>
public required Guid PeopleId { get; set; }
/// <summary>
/// Gets or Sets Reference People.
/// </summary>
public required People People { get; set; }
}

@ -81,7 +81,8 @@ public sealed class BaseItemRepository(
using var context = dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
context.Peoples.Where(e => e.ItemId == id).ExecuteDelete();
context.PeopleBaseItemMap.Where(e => e.ItemId == id).ExecuteDelete();
context.Peoples.Where(e => e.BaseItems!.Count == 0).ExecuteDelete();
context.Chapters.Where(e => e.ItemId == id).ExecuteDelete();
context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
context.AncestorIds.Where(e => e.ItemId == id).ExecuteDelete();
@ -602,13 +603,13 @@ public sealed class BaseItemRepository(
{
baseQuery = baseQuery
.Where(e =>
context.Peoples.Where(w => context.BaseItems.Where(r => filter.PersonIds.Contains(r.Id)).Any(f => f.Name == w.Name))
context.PeopleBaseItemMap.Where(w => context.BaseItems.Where(r => filter.PersonIds.Contains(r.Id)).Any(f => f.Name == w.People.Name))
.Any(f => f.ItemId == e.Id));
}
if (!string.IsNullOrWhiteSpace(filter.Person))
{
baseQuery = baseQuery.Where(e => e.Peoples!.Any(f => f.Name == filter.Person));
baseQuery = baseQuery.Where(e => e.Peoples!.Any(f => f.People.Name == filter.Person));
}
if (!string.IsNullOrWhiteSpace(filter.MinSortName))
@ -934,7 +935,7 @@ public sealed class BaseItemRepository(
if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value)
{
baseQuery = baseQuery
.Where(e => !e.Peoples!.Any(f => f.Name == e.Name));
.Where(e => !e.Peoples!.Any(f => f.People.Name == e.Name));
}
if (filter.Years.Length == 1)

@ -10,6 +10,7 @@ using MediaBrowser.Controller.Persistence;
using Microsoft.EntityFrameworkCore;
namespace Jellyfin.Server.Implementations.Item;
#pragma warning disable RS0030 // Do not use banned APIs
/// <summary>
/// Manager for handling people.
@ -28,7 +29,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) :
using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter);
dbQuery = dbQuery.OrderBy(e => e.ListOrder);
// dbQuery = dbQuery.OrderBy(e => e.ListOrder);
if (filter.Limit > 0)
{
dbQuery = dbQuery.Take(filter.Limit);
@ -43,7 +44,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) :
using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter);
dbQuery = dbQuery.OrderBy(e => e.ListOrder);
// dbQuery = dbQuery.OrderBy(e => e.ListOrder);
if (filter.Limit > 0)
{
dbQuery = dbQuery.Take(filter.Limit);
@ -58,7 +59,29 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) :
using var context = _dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
context.Peoples.Where(e => e.ItemId.Equals(itemId)).ExecuteDelete();
context.PeopleBaseItemMap.Where(e => e.ItemId == itemId).ExecuteDelete();
foreach (var item in people)
{
var personEntity = Map(item);
var existingEntity = context.Peoples.FirstOrDefault(e => e.Id == personEntity.Id);
if (existingEntity is null)
{
context.Peoples.Add(personEntity);
existingEntity = personEntity;
}
context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
{
Item = null!,
ItemId = itemId,
People = existingEntity,
PeopleId = existingEntity.Id,
ListOrder = item.SortOrder,
SortOrder = item.SortOrder,
Role = item.Role
});
}
context.Peoples.AddRange(people.Select(Map));
context.SaveChanges();
transaction.Commit();
@ -68,10 +91,8 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) :
{
var personInfo = new PersonInfo()
{
ItemId = people.ItemId,
Id = people.Id,
Name = people.Name,
Role = people.Role,
SortOrder = people.SortOrder,
};
if (Enum.TryParse<PersonKind>(people.PersonType, out var kind))
{
@ -85,13 +106,9 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) :
{
var personInfo = new People()
{
ItemId = people.ItemId,
Name = people.Name,
Role = people.Role,
SortOrder = people.SortOrder,
PersonType = people.Type.ToString(),
Item = null!,
ListOrder = people.SortOrder
Id = people.Id,
};
return personInfo;
@ -108,12 +125,12 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) :
if (!filter.ItemId.IsEmpty())
{
query = query.Where(e => e.ItemId.Equals(filter.ItemId));
query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.ItemId)));
}
if (!filter.AppearsInItemId.IsEmpty())
{
query = query.Where(e => context.Peoples.Where(f => f.ItemId.Equals(filter.AppearsInItemId)).Select(e => e.Name).Contains(e.Name));
query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.AppearsInItemId)));
}
var queryPersonTypes = filter.PersonTypes.Where(IsValidPersonType).ToList();
@ -129,9 +146,9 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) :
query = query.Where(e => !queryPersonTypes.Contains(e.PersonType));
}
if (filter.MaxListOrder.HasValue)
if (filter.MaxListOrder.HasValue && !filter.ItemId.IsEmpty())
{
query = query.Where(e => e.ListOrder <= filter.MaxListOrder.Value);
query = query.Where(e => e.BaseItems!.First(w => w.ItemId == filter.ItemId).ListOrder <= filter.MaxListOrder.Value);
}
if (!string.IsNullOrWhiteSpace(filter.NameContains))

@ -112,7 +112,7 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
public DbSet<Chapter> Chapters => Set<Chapter>();
/// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing the user data.
/// Gets the <see cref="DbSet{TEntity}"/>.
/// </summary>
public DbSet<ItemValue> ItemValues => Set<ItemValue>();
@ -122,15 +122,20 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
public DbSet<ItemValueMap> ItemValuesMap => Set<ItemValueMap>();
/// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing the user data.
/// Gets the <see cref="DbSet{TEntity}"/>.
/// </summary>
public DbSet<MediaStreamInfo> MediaStreamInfos => Set<MediaStreamInfo>();
/// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing the user data.
/// Gets the <see cref="DbSet{TEntity}"/>.
/// </summary>
public DbSet<People> Peoples => Set<People>();
/// <summary>
/// Gets the <see cref="DbSet{TEntity}"/>.
/// </summary>
public DbSet<PeopleBaseItemMap> PeopleBaseItemMap => Set<PeopleBaseItemMap>();
/// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing the referenced Providers with ids.
/// </summary>

@ -0,0 +1,152 @@
using System;
using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations
{
/// <inheritdoc />
public partial class LibraryPeopleMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Peoples_BaseItems_ItemId",
table: "Peoples");
migrationBuilder.DropPrimaryKey(
name: "PK_Peoples",
table: "Peoples");
migrationBuilder.DropIndex(
name: "IX_Peoples_ItemId_ListOrder",
table: "Peoples");
migrationBuilder.DropColumn(
name: "ListOrder",
table: "Peoples");
migrationBuilder.DropColumn(
name: "SortOrder",
table: "Peoples");
migrationBuilder.RenameColumn(
name: "ItemId",
table: "Peoples",
newName: "Id");
migrationBuilder.AlterColumn<string>(
name: "Role",
table: "Peoples",
type: "TEXT",
nullable: true,
oldClrType: typeof(string),
oldType: "TEXT");
migrationBuilder.AddPrimaryKey(
name: "PK_Peoples",
table: "Peoples",
column: "Id");
migrationBuilder.CreateTable(
name: "PeopleBaseItemMap",
columns: table => new
{
ItemId = table.Column<Guid>(type: "TEXT", nullable: false),
PeopleId = table.Column<Guid>(type: "TEXT", nullable: false),
SortOrder = table.Column<int>(type: "INTEGER", nullable: true),
ListOrder = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PeopleBaseItemMap", x => new { x.ItemId, x.PeopleId });
table.ForeignKey(
name: "FK_PeopleBaseItemMap_BaseItems_ItemId",
column: x => x.ItemId,
principalTable: "BaseItems",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_PeopleBaseItemMap_Peoples_PeopleId",
column: x => x.PeopleId,
principalTable: "Peoples",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_PeopleBaseItemMap_ItemId_ListOrder",
table: "PeopleBaseItemMap",
columns: new[] { "ItemId", "ListOrder" });
migrationBuilder.CreateIndex(
name: "IX_PeopleBaseItemMap_ItemId_SortOrder",
table: "PeopleBaseItemMap",
columns: new[] { "ItemId", "SortOrder" });
migrationBuilder.CreateIndex(
name: "IX_PeopleBaseItemMap_PeopleId",
table: "PeopleBaseItemMap",
column: "PeopleId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PeopleBaseItemMap");
migrationBuilder.DropPrimaryKey(
name: "PK_Peoples",
table: "Peoples");
migrationBuilder.RenameColumn(
name: "Id",
table: "Peoples",
newName: "ItemId");
migrationBuilder.AlterColumn<string>(
name: "Role",
table: "Peoples",
type: "TEXT",
nullable: false,
defaultValue: string.Empty,
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AddColumn<int>(
name: "ListOrder",
table: "Peoples",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "SortOrder",
table: "Peoples",
type: "INTEGER",
nullable: true);
migrationBuilder.AddPrimaryKey(
name: "PK_Peoples",
table: "Peoples",
columns: new[] { "ItemId", "Role", "ListOrder" });
migrationBuilder.CreateIndex(
name: "IX_Peoples_ItemId_ListOrder",
table: "Peoples",
columns: new[] { "ItemId", "ListOrder" });
migrationBuilder.AddForeignKey(
name: "FK_Peoples_BaseItems_ItemId",
table: "Peoples",
column: "ItemId",
principalTable: "BaseItems",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations
{
/// <inheritdoc />
public partial class LibraryPeopleRoleMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Role",
table: "Peoples");
migrationBuilder.AddColumn<string>(
name: "Role",
table: "PeopleBaseItemMap",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Role",
table: "PeopleBaseItemMap");
migrationBuilder.AddColumn<string>(
name: "Role",
table: "Peoples",
type: "TEXT",
nullable: true);
}
}
}

@ -910,33 +910,51 @@ namespace Jellyfin.Server.Implementations.Migrations
});
modelBuilder.Entity("Jellyfin.Data.Entities.People", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("PersonType")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Name");
b.ToTable("Peoples");
});
modelBuilder.Entity("Jellyfin.Data.Entities.PeopleBaseItemMap", b =>
{
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<string>("Role")
b.Property<Guid>("PeopleId")
.HasColumnType("TEXT");
b.Property<int?>("ListOrder")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("PersonType")
b.Property<string>("Role")
.HasColumnType("TEXT");
b.Property<int?>("SortOrder")
.HasColumnType("INTEGER");
b.HasKey("ItemId", "Role", "ListOrder");
b.HasKey("ItemId", "PeopleId");
b.HasIndex("Name");
b.HasIndex("PeopleId");
b.HasIndex("ItemId", "ListOrder");
b.ToTable("Peoples");
b.HasIndex("ItemId", "SortOrder");
b.ToTable("PeopleBaseItemMap");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
@ -1473,7 +1491,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Navigation("Item");
});
modelBuilder.Entity("Jellyfin.Data.Entities.People", b =>
modelBuilder.Entity("Jellyfin.Data.Entities.PeopleBaseItemMap", b =>
{
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item")
.WithMany("Peoples")
@ -1481,7 +1499,15 @@ namespace Jellyfin.Server.Implementations.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Jellyfin.Data.Entities.People", "People")
.WithMany("BaseItems")
.HasForeignKey("PeopleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Item");
b.Navigation("People");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
@ -1559,6 +1585,11 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Navigation("BaseItemsMap");
});
modelBuilder.Entity("Jellyfin.Data.Entities.People", b =>
{
b.Navigation("BaseItems");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
b.Navigation("AccessSchedules");

@ -0,0 +1,22 @@
using System;
using Jellyfin.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Jellyfin.Server.Implementations.ModelConfiguration;
/// <summary>
/// People configuration.
/// </summary>
public class PeopleBaseItemMapConfiguration : IEntityTypeConfiguration<PeopleBaseItemMap>
{
/// <inheritdoc/>
public void Configure(EntityTypeBuilder<PeopleBaseItemMap> builder)
{
builder.HasKey(e => new { e.ItemId, e.PeopleId });
builder.HasIndex(e => new { e.ItemId, e.SortOrder });
builder.HasIndex(e => new { e.ItemId, e.ListOrder });
builder.HasOne(e => e.Item);
builder.HasOne(e => e.People);
}
}

@ -13,8 +13,8 @@ public class PeopleConfiguration : IEntityTypeConfiguration<People>
/// <inheritdoc/>
public void Configure(EntityTypeBuilder<People> builder)
{
builder.HasKey(e => new { e.ItemId, e.Role, e.ListOrder });
builder.HasIndex(e => new { e.ItemId, e.ListOrder });
builder.HasKey(e => e.Id);
builder.HasIndex(e => e.Name);
builder.HasMany(e => e.BaseItems);
}
}

@ -47,7 +47,8 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.AddDefaultCastReceivers),
typeof(Routines.UpdateDefaultPluginRepository),
typeof(Routines.FixAudioData),
typeof(Routines.MoveTrickplayFiles)
typeof(Routines.MoveTrickplayFiles),
typeof(Routines.MigrateLibraryDb),
};
/// <summary>

@ -1,26 +1,25 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Entities.Libraries;
using Jellyfin.Extensions;
using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Chapter = Jellyfin.Data.Entities.Chapter;
namespace Jellyfin.Server.Migrations.Routines;
#pragma warning disable RS0030 // Do not use banned APIs
/// <summary>
/// The migration routine for migrating the userdata database to EF Core.
@ -50,13 +49,13 @@ public class MigrateLibraryDb : IMigrationRoutine
}
/// <inheritdoc/>
public Guid Id => Guid.Parse("5bcb4197-e7c0-45aa-9902-963bceab5798");
public Guid Id => Guid.Parse("36445464-849f-429f-9ad0-bb130efa0664");
/// <inheritdoc/>
public string Name => "MigrateUserData";
public string Name => "MigrateLibraryDbData";
/// <inheritdoc/>
public bool PerformOnNewInstall => false;
public bool PerformOnNewInstall => false; // TODO Change back after testing
/// <inheritdoc/>
public void Perform()
@ -66,24 +65,38 @@ public class MigrateLibraryDb : IMigrationRoutine
var dataPath = _paths.DataPath;
var libraryDbPath = Path.Combine(dataPath, DbFilename);
using var connection = new SqliteConnection($"Filename={libraryDbPath}");
var stopwatch = new Stopwatch();
stopwatch.Start();
connection.Open();
using var dbContext = _provider.CreateDbContext();
_logger.LogInformation("Start moving UserData.");
var queryResult = connection.Query("SELECT key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex FROM UserDatas");
dbContext.UserData.ExecuteDelete();
var users = dbContext.Users.AsNoTracking().ToImmutableArray();
foreach (SqliteDataReader dto in queryResult)
foreach (var entity in queryResult)
{
dbContext.UserData.Add(GetUserData(users, dto));
var userData = GetUserData(users, entity);
if (userData is null)
{
_logger.LogError("Was not able to migrate user data with key {0}", entity.GetString(0));
continue;
}
dbContext.UserData.Add(userData);
}
_logger.LogInformation("Try saving {0} UserData entries.", dbContext.UserData.Local.Count);
dbContext.SaveChanges();
var stepElapsed = stopwatch.Elapsed;
_logger.LogInformation("Saving UserData entries took {0}.", stepElapsed);
var typedBaseItemsQuery = "SELECT type, data, StartDate, EndDate, ChannelId, IsMovie, IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage, PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber, ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, guid, Genres, ParentId, Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, SeasonName, SeasonId, SeriesId, PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId FROM TypeBaseItems";
_logger.LogInformation("Start moving TypedBaseItem.");
var typedBaseItemsQuery = "SELECT type, data, StartDate, EndDate, ChannelId, IsMovie, IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage, PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber, ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, guid, Genres, ParentId, Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, SeasonName, SeasonId, SeriesId, PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId FROM TypedBaseItems";
dbContext.BaseItems.ExecuteDelete();
foreach (SqliteDataReader dto in connection.Query(typedBaseItemsQuery))
@ -91,9 +104,13 @@ public class MigrateLibraryDb : IMigrationRoutine
dbContext.BaseItems.Add(GetItem(dto));
}
_logger.LogInformation("Try saving {0} BaseItem entries.", dbContext.BaseItems.Local.Count);
dbContext.SaveChanges();
stepElapsed = stopwatch.Elapsed - stepElapsed;
_logger.LogInformation("Saving BaseItems entries took {0}.", stepElapsed);
var mediaStreamQuery = "SELECT ItemId, StreamIndex, StreamType, Codec, Language, ChannelLayout, Profile, AspectRatio, Path, IsInterlaced, BitRate, Channels, SampleRate, IsDefault, IsForced, IsExternal, Height, Width, AverageFrameRate, RealFrameRate, Level, PixelFormat, BitDepth, IsAnamorphic, RefFrames, CodecTag, Comment, NalLengthSize, IsAvc, Title, TimeBase, CodecTimeBase, ColorPrimaries, ColorSpace, ColorTransfer, DvVersionMajor, DvVersionMinor, DvProfile, DvLevel, RpuPresentFlag, ElPresentFlag, BlPresentFlag, DvBlSignalCompatibilityId, IsHearingImpaired, Rotation FROM MediaStreams";
_logger.LogInformation("Start moving MediaStreamInfos.");
var mediaStreamQuery = "SELECT ItemId, StreamIndex, StreamType, Codec, Language, ChannelLayout, Profile, AspectRatio, Path, IsInterlaced, BitRate, Channels, SampleRate, IsDefault, IsForced, IsExternal, Height, Width, AverageFrameRate, RealFrameRate, Level, PixelFormat, BitDepth, IsAnamorphic, RefFrames, CodecTag, Comment, NalLengthSize, IsAvc, Title, TimeBase, CodecTimeBase, ColorPrimaries, ColorSpace, ColorTransfer, DvVersionMajor, DvVersionMinor, DvProfile, DvLevel, RpuPresentFlag, ElPresentFlag, BlPresentFlag, DvBlSignalCompatibilityId, IsHearingImpaired FROM MediaStreams";
dbContext.MediaStreamInfos.ExecuteDelete();
foreach (SqliteDataReader dto in connection.Query(mediaStreamQuery))
@ -101,18 +118,59 @@ public class MigrateLibraryDb : IMigrationRoutine
dbContext.MediaStreamInfos.Add(GetMediaStream(dto));
}
_logger.LogInformation("Try saving {0} MediaStreamInfos entries.", dbContext.MediaStreamInfos.Local.Count);
dbContext.SaveChanges();
stepElapsed = stopwatch.Elapsed - stepElapsed;
_logger.LogInformation("Saving MediaStreamInfos entries took {0}.", stepElapsed);
_logger.LogInformation("Start moving People.");
var personsQuery = "select ItemId, Name, Role, PersonType, SortOrder from People p";
dbContext.Peoples.ExecuteDelete();
dbContext.PeopleBaseItemMap.ExecuteDelete();
foreach (SqliteDataReader dto in connection.Query(personsQuery))
foreach (SqliteDataReader reader in connection.Query(personsQuery))
{
dbContext.Peoples.Add(GetPerson(dto));
var itemId = reader.GetGuid(0);
if (!dbContext.BaseItems.Any(f => f.Id == itemId))
{
_logger.LogError("Dont save person {0} because its not in use by any BaseItem", reader.GetString(1));
continue;
}
var entity = GetPerson(reader);
var existingPerson = dbContext.Peoples.FirstOrDefault(e => e.Name == entity.Name);
if (existingPerson is null)
{
dbContext.Peoples.Add(entity);
existingPerson = entity;
}
if (reader.TryGetString(2, out var role))
{
}
if (reader.TryGetInt32(4, out var sortOrder))
{
}
dbContext.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
{
Item = null!,
ItemId = itemId,
People = existingPerson,
PeopleId = existingPerson.Id,
ListOrder = sortOrder,
SortOrder = sortOrder,
Role = role
});
}
_logger.LogInformation("Try saving {0} People entries.", dbContext.MediaStreamInfos.Local.Count);
dbContext.SaveChanges();
stepElapsed = stopwatch.Elapsed - stepElapsed;
_logger.LogInformation("Saving People entries took {0}.", stepElapsed);
_logger.LogInformation("Start moving ItemValues.");
// do not migrate inherited types as they are now properly mapped in search and lookup.
var itemValueQuery = "select ItemId, Type, Value, CleanValue FROM ItemValues WHERE Type <> 6";
dbContext.ItemValues.ExecuteDelete();
@ -140,32 +198,60 @@ public class MigrateLibraryDb : IMigrationRoutine
});
}
_logger.LogInformation("Try saving {0} ItemValues entries.", dbContext.ItemValues.Local.Count);
dbContext.SaveChanges();
stepElapsed = stopwatch.Elapsed - stepElapsed;
_logger.LogInformation("Saving People ItemValues took {0}.", stepElapsed);
var chapterQuery = "select StartPositionTicks,Name,ImagePath,ImageDateModified from Chapters2";
_logger.LogInformation("Start moving Chapters.");
var chapterQuery = "select ItemId,StartPositionTicks,Name,ImagePath,ImageDateModified,ChapterIndex from Chapters2";
dbContext.Chapters.ExecuteDelete();
foreach (SqliteDataReader dto in connection.Query(chapterQuery))
{
dbContext.Chapters.Add(GetChapter(dto));
var chapter = GetChapter(dto);
dbContext.Chapters.Add(chapter);
}
_logger.LogInformation("Try saving {0} Chapters entries.", dbContext.Chapters.Local.Count);
dbContext.SaveChanges();
stepElapsed = stopwatch.Elapsed - stepElapsed;
_logger.LogInformation("Saving Chapters took {0}.", stepElapsed);
_logger.LogInformation("Start moving AncestorIds.");
var ancestorIdsQuery = "select ItemId, AncestorId, AncestorIdText from AncestorIds";
dbContext.Chapters.ExecuteDelete();
foreach (SqliteDataReader dto in connection.Query(ancestorIdsQuery))
{
dbContext.AncestorIds.Add(GetAncestorId(dto));
var ancestorId = GetAncestorId(dto);
if (!dbContext.BaseItems.Any(e => e.Id == ancestorId.ItemId))
{
_logger.LogInformation("Dont move AncestorId ({0}, {1}) because no Item found.", ancestorId.ItemId, ancestorId.ParentItemId);
continue;
}
if (!dbContext.BaseItems.Any(e => e.Id == ancestorId.ParentItemId))
{
_logger.LogInformation("Dont move AncestorId ({0}, {1}) because no parent Item found.", ancestorId.ItemId, ancestorId.ParentItemId);
continue;
}
dbContext.AncestorIds.Add(ancestorId);
}
_logger.LogInformation("Try saving {0} AncestorIds entries.", dbContext.Chapters.Local.Count);
dbContext.SaveChanges();
stepElapsed = stopwatch.Elapsed - stepElapsed;
_logger.LogInformation("Saving AncestorIds took {0}.", stepElapsed);
connection.Close();
_logger.LogInformation("Migration of the Library.db done.");
_logger.LogInformation("Move {0} to {1}.", libraryDbPath, libraryDbPath + ".old");
File.Move(libraryDbPath, libraryDbPath + ".old");
// _logger.LogInformation("Migration of the Library.db done.");
// _logger.LogInformation("Move {0} to {1}.", libraryDbPath, libraryDbPath + ".old");
// File.Move(libraryDbPath, libraryDbPath + ".old");
_logger.LogInformation("Migrating Library db took {0}.", stopwatch.Elapsed);
if (dbContext.Database.IsSqlite())
{
@ -180,12 +266,18 @@ public class MigrateLibraryDb : IMigrationRoutine
}
}
private static UserData GetUserData(ImmutableArray<User> users, SqliteDataReader dto)
private static UserData? GetUserData(ImmutableArray<User> users, SqliteDataReader dto)
{
var indexOfUser = dto.GetInt32(1);
if (users.Length < indexOfUser)
{
return null;
}
return new UserData()
{
Key = dto.GetString(0),
UserId = users.ElementAt(dto.GetInt32(1)).Id,
UserId = users.ElementAt(indexOfUser).Id,
Rating = dto.IsDBNull(2) ? null : dto.GetDouble(2),
Played = dto.GetBoolean(3),
PlayCount = dto.GetInt32(4),
@ -219,23 +311,23 @@ public class MigrateLibraryDb : IMigrationRoutine
{
var chapter = new Chapter
{
StartPositionTicks = reader.GetInt64(0),
ChapterIndex = 0,
StartPositionTicks = reader.GetInt64(1),
ChapterIndex = reader.GetInt32(5),
Item = null!,
ItemId = Guid.Empty
ItemId = reader.GetGuid(0),
};
if (reader.TryGetString(1, out var chapterName))
if (reader.TryGetString(2, out var chapterName))
{
chapter.Name = chapterName;
}
if (reader.TryGetString(2, out var imagePath))
if (reader.TryGetString(3, out var imagePath))
{
chapter.ImagePath = imagePath;
}
if (reader.TryReadDateTime(3, out var imageDateModified))
if (reader.TryReadDateTime(4, out var imageDateModified))
{
chapter.ImageDateModified = imageDateModified;
}
@ -258,26 +350,15 @@ public class MigrateLibraryDb : IMigrationRoutine
{
var item = new People
{
ItemId = reader.GetGuid(0),
Id = Guid.NewGuid(),
Name = reader.GetString(1),
Item = null!
};
if (reader.TryGetString(2, out var role))
{
item.Role = role;
}
if (reader.TryGetString(3, out var type))
{
item.PersonType = type;
}
if (reader.TryGetInt32(4, out var sortOrder))
{
item.SortOrder = sortOrder;
}
return item;
}
@ -515,10 +596,10 @@ public class MigrateLibraryDb : IMigrationRoutine
item.IsHearingImpaired = reader.TryGetBoolean(43, out var result) && result;
if (reader.TryGetInt32(44, out var rotation))
{
item.Rotation = rotation;
}
// if (reader.TryGetInt32(44, out var rotation))
// {
// item.Rotation = rotation;
// }
return item;
}

@ -17,8 +17,14 @@ namespace MediaBrowser.Controller.Entities
public PersonInfo()
{
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
Id = Guid.NewGuid();
}
/// <summary>
/// Gets or Sets the PersonId.
/// </summary>
public Guid Id { get; set; }
public Guid ItemId { get; set; }
/// <summary>

Loading…
Cancel
Save