mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
i have too much time.
Refactored BaseItem and UserData relation
This commit is contained in:
parent
cd2e043472
commit
10a2a316a4
@ -16,6 +16,7 @@ using MediaBrowser.Model.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
|
||||
using Book = MediaBrowser.Controller.Entities.Book;
|
||||
#pragma warning disable RS0030 // Do not use banned APIs
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
@ -134,7 +135,9 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return new UserData()
|
||||
{
|
||||
Key = dto.Key,
|
||||
ItemId = Guid.Parse(dto.Key),
|
||||
Item = null!,
|
||||
User = null!,
|
||||
AudioStreamIndex = dto.AudioStreamIndex,
|
||||
IsFavorite = dto.IsFavorite,
|
||||
LastPlayedDate = dto.LastPlayedDate,
|
||||
@ -152,7 +155,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return new UserItemData()
|
||||
{
|
||||
Key = dto.Key,
|
||||
Key = dto.ItemId.ToString("D"),
|
||||
AudioStreamIndex = dto.AudioStreamIndex,
|
||||
IsFavorite = dto.IsFavorite,
|
||||
LastPlayedDate = dto.LastPlayedDate,
|
||||
@ -182,12 +185,12 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
using var context = _repository.CreateDbContext();
|
||||
var key = keys.FirstOrDefault();
|
||||
if (key is null)
|
||||
if (key is null || Guid.TryParse(key, out var itemId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var userData = context.UserData.AsNoTracking().FirstOrDefault(e => e.Key == key && e.UserId.Equals(userId));
|
||||
var userData = context.UserData.AsNoTracking().FirstOrDefault(e => e.ItemId == itemId && e.UserId.Equals(userId));
|
||||
|
||||
if (userData is not null)
|
||||
{
|
||||
|
@ -110,8 +110,6 @@ public class BaseItemEntity
|
||||
|
||||
public string? SeriesName { get; set; }
|
||||
|
||||
public string? UserDataKey { get; set; }
|
||||
|
||||
public string? SeasonName { get; set; }
|
||||
|
||||
public string? ExternalSeriesId { get; set; }
|
||||
|
@ -8,12 +8,6 @@ namespace Jellyfin.Data.Entities;
|
||||
/// </summary>
|
||||
public class UserData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the key.
|
||||
/// </summary>
|
||||
/// <value>The key.</value>
|
||||
public required string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the users 0-10 rating.
|
||||
/// </summary>
|
||||
@ -69,13 +63,24 @@ public class UserData
|
||||
/// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value>
|
||||
public bool? Likes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key.
|
||||
/// </summary>
|
||||
/// <value>The key.</value>
|
||||
public required Guid ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the BaseItem.
|
||||
/// </summary>
|
||||
public required BaseItemEntity? Item { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the UserId.
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
public required Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the User.
|
||||
/// </summary>
|
||||
public User? User { get; set; }
|
||||
public required User? User { get; set; }
|
||||
}
|
||||
|
@ -671,25 +671,25 @@ public sealed class BaseItemRepository(
|
||||
if (filter.IsLiked.HasValue)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Rating >= UserItemData.MinLikeValue);
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Rating >= UserItemData.MinLikeValue);
|
||||
}
|
||||
|
||||
if (filter.IsFavoriteOrLiked.HasValue)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavoriteOrLiked);
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavoriteOrLiked);
|
||||
}
|
||||
|
||||
if (filter.IsFavorite.HasValue)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavorite);
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorite);
|
||||
}
|
||||
|
||||
if (filter.IsPlayed.HasValue)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Played == filter.IsPlayed.Value);
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Played == filter.IsPlayed.Value);
|
||||
}
|
||||
|
||||
if (filter.IsResumable.HasValue)
|
||||
@ -697,12 +697,12 @@ public sealed class BaseItemRepository(
|
||||
if (filter.IsResumable.Value)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks > 0);
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks == 0);
|
||||
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2019,12 +2019,12 @@ public sealed class BaseItemRepository(
|
||||
ItemSortBy.AirTime => e => e.SortName, // TODO
|
||||
ItemSortBy.Runtime => e => e.RunTimeTicks,
|
||||
ItemSortBy.Random => e => EF.Functions.Random(),
|
||||
ItemSortBy.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.LastPlayedDate,
|
||||
ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.PlayCount,
|
||||
ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.IsFavorite,
|
||||
ItemSortBy.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.LastPlayedDate,
|
||||
ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.PlayCount,
|
||||
ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.IsFavorite,
|
||||
ItemSortBy.IsFolder => e => e.IsFolder,
|
||||
ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.Played,
|
||||
ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.Played,
|
||||
ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.Played,
|
||||
ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.Played,
|
||||
ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded,
|
||||
ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).Select(f => f.ItemValue.CleanValue),
|
||||
ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue),
|
||||
|
@ -121,7 +121,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
{
|
||||
var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person];
|
||||
query = query.Where(e => e.PersonType == personType)
|
||||
.Where(e => context.BaseItems.Where(d => context.UserData.Where(w => w.IsFavorite == filter.IsFavorite && w.UserId.Equals(filter.User.Id)).Any(f => f.Key == d.UserDataKey))
|
||||
.Where(e => context.BaseItems.Where(d => d.UserData!.Any(w => w.IsFavorite == filter.IsFavorite && w.UserId.Equals(filter.User.Id)))
|
||||
.Select(f => f.Name).Contains(e.Name));
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,6 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
|
||||
builder.HasIndex(e => e.ParentId);
|
||||
builder.HasIndex(e => e.PresentationUniqueKey);
|
||||
builder.HasIndex(e => new { e.Id, e.Type, e.IsFolder, e.IsVirtualItem });
|
||||
builder.HasIndex(e => new { e.UserDataKey, e.Type });
|
||||
|
||||
// covering index
|
||||
builder.HasIndex(e => new { e.TopParentId, e.Id });
|
||||
|
@ -13,10 +13,11 @@ public class UserDataConfiguration : IEntityTypeConfiguration<UserData>
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<UserData> builder)
|
||||
{
|
||||
builder.HasKey(d => new { d.Key, d.UserId });
|
||||
builder.HasIndex(d => new { d.Key, d.UserId, d.Played });
|
||||
builder.HasIndex(d => new { d.Key, d.UserId, d.PlaybackPositionTicks });
|
||||
builder.HasIndex(d => new { d.Key, d.UserId, d.IsFavorite });
|
||||
builder.HasIndex(d => new { d.Key, d.UserId, d.LastPlayedDate });
|
||||
builder.HasKey(d => new { d.ItemId, d.UserId });
|
||||
builder.HasIndex(d => new { d.ItemId, d.UserId, d.Played });
|
||||
builder.HasIndex(d => new { d.ItemId, d.UserId, d.PlaybackPositionTicks });
|
||||
builder.HasIndex(d => new { d.ItemId, d.UserId, d.IsFavorite });
|
||||
builder.HasIndex(d => new { d.ItemId, d.UserId, d.LastPlayedDate });
|
||||
builder.HasOne(e => e.Item);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
@ -71,37 +72,19 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
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 (var entity in queryResult)
|
||||
{
|
||||
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);
|
||||
|
||||
_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";
|
||||
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, UserDataKey, SeasonName, SeasonId, SeriesId, PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId FROM TypedBaseItems";
|
||||
dbContext.BaseItems.ExecuteDelete();
|
||||
|
||||
var legacyBaseItemWithUserKeys = new Dictionary<string, BaseItemEntity>();
|
||||
foreach (SqliteDataReader dto in connection.Query(typedBaseItemsQuery))
|
||||
{
|
||||
dbContext.BaseItems.Add(GetItem(dto));
|
||||
var baseItem = GetItem(dto);
|
||||
dbContext.BaseItems.Add(baseItem.BaseItem);
|
||||
legacyBaseItemWithUserKeys[baseItem.LegacyUserDataKey] = baseItem.BaseItem;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Try saving {0} BaseItem entries.", dbContext.BaseItems.Local.Count);
|
||||
@ -109,6 +92,36 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
stepElapsed = stopwatch.Elapsed - stepElapsed;
|
||||
_logger.LogInformation("Saving BaseItems entries took {0}.", stepElapsed);
|
||||
|
||||
_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();
|
||||
var oldUserdata = new Dictionary<string, UserData>();
|
||||
|
||||
foreach (var entity in queryResult)
|
||||
{
|
||||
var userData = GetUserData(users, entity);
|
||||
if (userData.Data is null)
|
||||
{
|
||||
_logger.LogError("Was not able to migrate user data with key {0}", entity.GetString(0));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!legacyBaseItemWithUserKeys.TryGetValue(userData.LegacyUserDataKey!, out var refItem))
|
||||
{
|
||||
_logger.LogError("Was not able to migrate user data with key {0} because it does not reference a valid BaseItem.", entity.GetString(0));
|
||||
continue;
|
||||
}
|
||||
|
||||
userData.Data.ItemId = refItem.Id;
|
||||
dbContext.UserData.Add(userData.Data);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Try saving {0} UserData entries.", dbContext.UserData.Local.Count);
|
||||
dbContext.SaveChanges();
|
||||
|
||||
_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();
|
||||
@ -266,17 +279,19 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
}
|
||||
}
|
||||
|
||||
private static UserData? GetUserData(ImmutableArray<User> users, SqliteDataReader dto)
|
||||
private static (UserData? Data, string? LegacyUserDataKey) GetUserData(ImmutableArray<User> users, SqliteDataReader dto)
|
||||
{
|
||||
var indexOfUser = dto.GetInt32(1);
|
||||
if (users.Length < indexOfUser)
|
||||
{
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
return new UserData()
|
||||
var oldKey = dto.GetString(0);
|
||||
|
||||
return (new UserData()
|
||||
{
|
||||
Key = dto.GetString(0),
|
||||
ItemId = Guid.NewGuid(),
|
||||
UserId = users.ElementAt(indexOfUser).Id,
|
||||
Rating = dto.IsDBNull(2) ? null : dto.GetDouble(2),
|
||||
Played = dto.GetBoolean(3),
|
||||
@ -288,7 +303,8 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
SubtitleStreamIndex = dto.IsDBNull(9) ? null : dto.GetInt32(9),
|
||||
Likes = null,
|
||||
User = null!,
|
||||
};
|
||||
Item = null!
|
||||
}, oldKey);
|
||||
}
|
||||
|
||||
private AncestorId GetAncestorId(SqliteDataReader reader)
|
||||
@ -604,7 +620,7 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
return item;
|
||||
}
|
||||
|
||||
private BaseItemEntity GetItem(SqliteDataReader reader)
|
||||
private (BaseItemEntity BaseItem, string LegacyUserDataKey) GetItem(SqliteDataReader reader)
|
||||
{
|
||||
var entity = new BaseItemEntity()
|
||||
{
|
||||
@ -870,6 +886,10 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
entity.SeriesName = seriesName;
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var userDataKey))
|
||||
{
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var seasonName))
|
||||
{
|
||||
entity.SeasonName = seasonName;
|
||||
@ -971,7 +991,7 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
entity.OwnerId = ownerId.ToString("N");
|
||||
}
|
||||
|
||||
return entity;
|
||||
return (entity, userDataKey);
|
||||
}
|
||||
|
||||
private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)
|
||||
|
Loading…
x
Reference in New Issue
Block a user