Fixed (most) tests

pull/12798/head
JPVenson 5 months ago
parent c2844bda3b
commit 01d834f21a

@ -156,28 +156,12 @@ public class BaseItemEntity
public Guid? ParentId { get; set; }
public BaseItemEntity? Parent { get; set; }
public ICollection<BaseItemEntity>? DirectChildren { get; set; }
public Guid? TopParentId { get; set; }
public BaseItemEntity? TopParent { get; set; }
public ICollection<BaseItemEntity>? AllChildren { get; set; }
public Guid? SeasonId { get; set; }
public BaseItemEntity? Season { get; set; }
public ICollection<BaseItemEntity>? SeasonEpisodes { get; set; }
public Guid? SeriesId { get; set; }
public ICollection<BaseItemEntity>? SeriesEpisodes { get; set; }
public BaseItemEntity? Series { get; set; }
public ICollection<People>? Peoples { get; set; }
public ICollection<UserData>? UserData { get; set; }
@ -191,4 +175,14 @@ public class BaseItemEntity
public ICollection<BaseItemProvider>? Provider { get; set; }
public ICollection<AncestorId>? AncestorIds { get; set; }
// those are references to __LOCAL__ ids not DB ids ... TODO: Bring the whole folder structure into the DB
// public ICollection<BaseItemEntity>? SeriesEpisodes { get; set; }
// public BaseItemEntity? Series { get; set; }
// public BaseItemEntity? Season { get; set; }
// public BaseItemEntity? Parent { get; set; }
// public ICollection<BaseItemEntity>? DirectChildren { get; set; }
// public BaseItemEntity? TopParent { get; set; }
// public ICollection<BaseItemEntity>? AllChildren { get; set; }
// public ICollection<BaseItemEntity>? SeasonEpisodes { get; set; }
}

@ -5,20 +5,16 @@ using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
@ -26,6 +22,7 @@ using MediaBrowser.Model.Querying;
using Microsoft.EntityFrameworkCore;
using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
using BaseItemEntity = Jellyfin.Data.Entities.BaseItemEntity;
#pragma warning disable RS0030 // Do not use banned APIs
namespace Jellyfin.Server.Implementations.Item;
@ -66,12 +63,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
using var context = dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
context.Peoples.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
context.Chapters.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
context.MediaStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
context.AncestorIds.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
context.ItemValues.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
context.BaseItems.Where(e => e.Id.Equals(id)).ExecuteDelete();
context.Peoples.Where(e => e.ItemId == id).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();
context.ItemValues.Where(e => e.ItemId == id).ExecuteDelete();
context.BaseItems.Where(e => e.Id == id).ExecuteDelete();
context.SaveChanges();
transaction.Commit();
}
@ -113,8 +110,8 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
PrepareFilterQuery(filter);
using var context = dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter)
.DistinctBy(e => e.Id);
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
// .DistinctBy(e => e.Id);
var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
@ -266,8 +263,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
PrepareFilterQuery(filter);
using var context = dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems, context, filter)
.DistinctBy(e => e.Id);
var dbQuery = TranslateQuery(context.BaseItems, context, filter);
if (filter.Limit.HasValue || filter.StartIndex.HasValue)
{
var offset = filter.StartIndex ?? 0;
@ -299,6 +295,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
return dbQuery.Count();
}
#pragma warning disable CA1307 // Specify StringComparison for clarity
private IQueryable<BaseItemEntity> TranslateQuery(
IQueryable<BaseItemEntity> baseQuery,
JellyfinDbContext context,
@ -419,7 +416,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (!string.IsNullOrEmpty(filter.SearchTerm))
{
baseQuery = baseQuery.Where(e => e.CleanName!.Contains(filter.SearchTerm, StringComparison.InvariantCultureIgnoreCase) || (e.OriginalTitle != null && e.OriginalTitle.Contains(filter.SearchTerm, StringComparison.InvariantCultureIgnoreCase)));
baseQuery = baseQuery.Where(e => e.CleanName!.Contains(filter.SearchTerm) || (e.OriginalTitle != null && e.OriginalTitle.Contains(filter.SearchTerm)));
}
if (filter.IsFolder.HasValue)
@ -474,18 +471,15 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
baseQuery = baseQuery.Where(e => includeTypeName.Contains(e.Type));
}
if (filter.ChannelIds.Count == 1)
if (filter.ChannelIds.Count > 0)
{
baseQuery = baseQuery.Where(e => e.ChannelId == filter.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
}
else if (filter.ChannelIds.Count > 1)
{
baseQuery = baseQuery.Where(e => filter.ChannelIds.Select(f => f.ToString("N", CultureInfo.InvariantCulture)).Contains(e.ChannelId));
var channelIds = filter.ChannelIds.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).ToArray();
baseQuery = baseQuery.Where(e => channelIds.Contains(e.ChannelId));
}
if (!filter.ParentId.IsEmpty())
{
baseQuery = baseQuery.Where(e => e.ParentId.Equals(filter.ParentId));
baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId);
}
if (!string.IsNullOrWhiteSpace(filter.Path))
@ -591,7 +585,8 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (filter.TrailerTypes.Length > 0)
{
baseQuery = baseQuery.Where(e => filter.TrailerTypes.Any(f => e.TrailerTypes!.Contains(f.ToString(), StringComparison.OrdinalIgnoreCase)));
var trailerTypes = filter.TrailerTypes.Select(e => e.ToString()).ToArray();
baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Contains(f)));
}
if (filter.IsAiring.HasValue)
@ -611,7 +606,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
baseQuery = baseQuery
.Where(e =>
context.Peoples.Where(w => context.BaseItems.Where(w => filter.PersonIds.Contains(w.Id)).Any(f => f.Name == w.Name))
.Any(f => f.ItemId.Equals(e.Id)));
.Any(f => f.ItemId == e.Id));
}
if (!string.IsNullOrWhiteSpace(filter.Person))
@ -649,12 +644,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
{
baseQuery = baseQuery.Where(e =>
e.CleanName == filter.NameContains
|| e.OriginalTitle!.Contains(filter.NameContains!, StringComparison.Ordinal));
|| e.OriginalTitle!.Contains(filter.NameContains!));
}
if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
{
baseQuery = baseQuery.Where(e => e.SortName!.Contains(filter.NameStartsWith, StringComparison.OrdinalIgnoreCase));
baseQuery = baseQuery.Where(e => e.SortName!.Contains(filter.NameStartsWith));
}
if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
@ -671,31 +666,32 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (filter.ImageTypes.Length > 0)
{
baseQuery = baseQuery.Where(e => filter.ImageTypes.Any(f => e.Images!.Contains(f.ToString(), StringComparison.InvariantCulture)));
var imgTypes = filter.ImageTypes.Select(e => e.ToString()).ToArray();
baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Contains(f)));
}
if (filter.IsLiked.HasValue)
{
baseQuery = baseQuery
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.Rating >= UserItemData.MinLikeValue);
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Rating >= UserItemData.MinLikeValue);
}
if (filter.IsFavoriteOrLiked.HasValue)
{
baseQuery = baseQuery
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavoriteOrLiked);
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavoriteOrLiked);
}
if (filter.IsFavorite.HasValue)
{
baseQuery = baseQuery
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavorite);
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavorite);
}
if (filter.IsPlayed.HasValue)
{
baseQuery = baseQuery
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.Played == filter.IsPlayed.Value);
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Played == filter.IsPlayed.Value);
}
if (filter.IsResumable.HasValue)
@ -703,12 +699,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (filter.IsResumable.Value)
{
baseQuery = baseQuery
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.PlaybackPositionTicks > 0);
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks > 0);
}
else
{
baseQuery = baseQuery
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.PlaybackPositionTicks == 0);
.Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks == 0);
}
}
@ -925,7 +921,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value)
{
baseQuery = baseQuery
.Where(e => e.ParentId.HasValue && context.BaseItems.Any(f => f.Id.Equals(e.ParentId.Value)));
.Where(e => e.ParentId.HasValue && context.BaseItems.Any(f => f.Id == e.ParentId.Value));
}
if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value)
@ -1048,11 +1044,11 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
if (enableItemsByName && includedItemByNameTypes.Count > 0)
{
baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w.Equals(e.TopParentId!.Value)));
baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w == e.TopParentId!.Value));
}
else
{
baseQuery = baseQuery.Where(e => queryTopParentIds.Any(w => w.Equals(e.TopParentId!.Value)));
baseQuery = baseQuery.Where(e => queryTopParentIds.Any(w => w == e.TopParentId!.Value));
}
}
@ -1064,7 +1060,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
{
baseQuery = baseQuery
.Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.AncestorIds!.Any(w => w.ItemId.Equals(f.Id))));
.Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.AncestorIds!.Any(w => w.ItemId == f.Id)));
}
if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
@ -1090,7 +1086,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
.Where(e => e.ItemValues!.Where(e => e.Type == 6)
.Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue))
||
(e.ParentId.HasValue && context.ItemValues.Where(w => w.ItemId.Equals(e.ParentId.Value))!.Where(e => e.Type == 6)
(e.ParentId.HasValue && context.ItemValues.Where(w => w.ItemId == e.ParentId.Value)!.Where(e => e.Type == 6)
.Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue))));
}
@ -1112,21 +1108,23 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (filter.SeriesStatuses.Length > 0)
{
var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray();
baseQuery = baseQuery
.Where(e => filter.SeriesStatuses.Any(f => e.Data!.Contains(f.ToString(), StringComparison.InvariantCultureIgnoreCase)));
.Where(e => seriesStatus.Any(f => e.Data!.Contains(f)));
}
if (filter.BoxSetLibraryFolders.Length > 0)
{
var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).ToArray();
baseQuery = baseQuery
.Where(e => filter.BoxSetLibraryFolders.Any(f => e.Data!.Contains(f.ToString("N", CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase)));
.Where(e => boxsetFolders.Any(f => e.Data!.Contains(f)));
}
if (filter.VideoTypes.Length > 0)
{
var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"" + e + "\"");
baseQuery = baseQuery
.Where(e => videoTypeBs.Any(f => e.Data!.Contains(f, StringComparison.InvariantCultureIgnoreCase)));
.Where(e => videoTypeBs.Any(f => e.Data!.Contains(f)));
}
if (filter.Is3D.HasValue)
@ -1134,12 +1132,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (filter.Is3D.Value)
{
baseQuery = baseQuery
.Where(e => e.Data!.Contains("Video3DFormat", StringComparison.InvariantCultureIgnoreCase));
.Where(e => e.Data!.Contains("Video3DFormat"));
}
else
{
baseQuery = baseQuery
.Where(e => !e.Data!.Contains("Video3DFormat", StringComparison.InvariantCultureIgnoreCase));
.Where(e => !e.Data!.Contains("Video3DFormat"));
}
}
@ -1148,12 +1146,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
if (filter.IsPlaceHolder.Value)
{
baseQuery = baseQuery
.Where(e => e.Data!.Contains("IsPlaceHolder\":true", StringComparison.InvariantCultureIgnoreCase));
.Where(e => e.Data!.Contains("IsPlaceHolder\":true"));
}
else
{
baseQuery = baseQuery
.Where(e => !e.Data!.Contains("IsPlaceHolder\":true", StringComparison.InvariantCultureIgnoreCase));
.Where(e => !e.Data!.Contains("IsPlaceHolder\":true"));
}
}
@ -1212,7 +1210,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
using var db = dbProvider.CreateDbContext();
db.BaseItems
.Where(e => e.Id.Equals(item.Id))
.Where(e => e.Id == item.Id)
.ExecuteUpdate(e => e.SetProperty(f => f.Images, images));
}
@ -1246,11 +1244,20 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
}
using var context = dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
foreach (var item in tuples)
{
var entity = Map(item.Item);
context.BaseItems.Add(entity);
if (!context.BaseItems.Any(e => e.Id == entity.Id))
{
context.BaseItems.Add(entity);
}
else
{
context.BaseItems.Attach(entity).State = EntityState.Modified;
}
context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete();
if (item.Item.SupportsAncestors && item.AncestorIds != null)
{
foreach (var ancestorId in item.AncestorIds)
@ -1260,13 +1267,13 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
Item = entity,
AncestorIdText = ancestorId.ToString(),
Id = ancestorId,
ItemId = entity.Id
ItemId = Guid.Empty
});
}
}
var itemValues = GetItemValuesToSave(item.Item, item.InheritedTags);
context.ItemValues.Where(e => e.ItemId.Equals(entity.Id)).ExecuteDelete();
context.ItemValues.Where(e => e.ItemId == entity.Id).ExecuteDelete();
foreach (var itemValue in itemValues)
{
context.ItemValues.Add(new()
@ -1275,12 +1282,13 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
Type = itemValue.MagicNumber,
Value = itemValue.Value,
CleanValue = GetCleanValue(itemValue.Value),
ItemId = entity.Id
ItemId = Guid.Empty
});
}
}
context.SaveChanges(true);
context.SaveChanges();
transaction.Commit();
}
/// <inheritdoc cref="IItemRepository" />
@ -1292,7 +1300,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
}
using var context = dbProvider.CreateDbContext();
var item = context.BaseItems.FirstOrDefault(e => e.Id.Equals(id));
var item = context.BaseItems.FirstOrDefault(e => e.Id == id);
if (item is null)
{
return null;
@ -1380,7 +1388,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
dto.Audio = Enum.Parse<ProgramAudio>(entity.Audio);
}
dto.ExtraIds = entity.ExtraIds?.Split('|').Select(e => Guid.Parse(e)).ToArray();
dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? null : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray();
dto.ProductionLocations = entity.ProductionLocations?.Split('|');
dto.Studios = entity.Studios?.Split('|');
dto.Tags = entity.Tags?.Split('|');
@ -1535,8 +1543,8 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
entity.Audio = dto.Audio?.ToString();
entity.ExtraType = dto.ExtraType?.ToString();
entity.ExtraIds = string.Join('|', dto.ExtraIds);
entity.ProductionLocations = string.Join('|', dto.ProductionLocations);
entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : null;
entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
entity.LockedFields = dto.LockedFields is not null ? string.Join('|', dto.LockedFields) : null;
@ -1628,15 +1636,15 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
.Where(e => itemValueTypes.Contains(e.Type));
if (withItemTypes.Count > 0)
{
query = query.Where(e => context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId.Equals(e.ItemId))));
query = query.Where(e => context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId == e.ItemId)));
}
if (excludeItemTypes.Count > 0)
{
query = query.Where(e => !context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId.Equals(e.ItemId))));
query = query.Where(e => !context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId == e.ItemId)));
}
query = query.DistinctBy(e => e.CleanValue);
// query = query.DistinctBy(e => e.CleanValue);
return query.Select(e => e.CleanValue).ToImmutableArray();
}
@ -2131,12 +2139,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
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.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.LastPlayedDate,
ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.PlayCount,
ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite,
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.IsFolder => e => e.IsFolder,
ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played,
ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played,
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.DateLastContentAdded => e => e.DateLastMediaAdded,
ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.Type == 0).Select(f => f.CleanValue),
ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.Type == 1).Select(f => f.CleanValue),

@ -4,20 +4,18 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations;
/// <inheritdoc/>
public class JellyfinDbContext : DbContext
/// <summary>
/// Initializes a new instance of the <see cref="JellyfinDbContext"/> class.
/// </summary>
/// <param name="options">The database context options.</param>
/// <param name="logger">Logger.</param>
public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILogger<JellyfinDbContext> logger) : DbContext(options)
{
/// <summary>
/// Initializes a new instance of the <see cref="JellyfinDbContext"/> class.
/// </summary>
/// <param name="options">The database context options.</param>
public JellyfinDbContext(DbContextOptions<JellyfinDbContext> options) : base(options)
{
}
/// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing the access schedules.
/// </summary>
@ -228,7 +226,15 @@ public class JellyfinDbContext : DbContext
saveEntity.OnSavingChanges();
}
return base.SaveChanges();
try
{
return base.SaveChanges();
}
catch (Exception e)
{
logger.LogError(e, "Error trying to save changes.");
throw;
}
}
/// <inheritdoc />

@ -1,775 +0,0 @@
// <auto-generated />
using System;
using Jellyfin.Server.Implementations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations
{
[DbContext(typeof(JellyfinDbContext))]
[Migration("20240907123425_UserDataInJfLib")]
partial class UserDataInJfLib
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("DayOfWeek")
.HasColumnType("INTEGER");
b.Property<double>("EndHour")
.HasColumnType("REAL");
b.Property<double>("StartHour")
.HasColumnType("REAL");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AccessSchedules");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<int>("LogSeverity")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("Overview")
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("ShortOverview")
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("DateCreated");
b.ToTable("ActivityLogs");
});
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId", "ItemId", "Client", "Key")
.IsUnique();
b.ToTable("CustomItemDisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ChromecastVersion")
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<string>("DashboardTheme")
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<bool>("EnableNextVideoInfoOverlay")
.HasColumnType("INTEGER");
b.Property<int?>("IndexBy")
.HasColumnType("INTEGER");
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<int>("ScrollDirection")
.HasColumnType("INTEGER");
b.Property<bool>("ShowBackdrop")
.HasColumnType("INTEGER");
b.Property<bool>("ShowSidebar")
.HasColumnType("INTEGER");
b.Property<int>("SkipBackwardLength")
.HasColumnType("INTEGER");
b.Property<int>("SkipForwardLength")
.HasColumnType("INTEGER");
b.Property<string>("TvHome")
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId", "ItemId", "Client")
.IsUnique();
b.ToTable("DisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("DisplayPreferencesId")
.HasColumnType("INTEGER");
b.Property<int>("Order")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("DisplayPreferencesId");
b.ToTable("HomeSection");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Path")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("ImageInfos");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<int?>("IndexBy")
.HasColumnType("INTEGER");
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<bool>("RememberIndexing")
.HasColumnType("INTEGER");
b.Property<bool>("RememberSorting")
.HasColumnType("INTEGER");
b.Property<string>("SortBy")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<int>("SortOrder")
.HasColumnType("INTEGER");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<int>("ViewType")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("ItemDisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.MediaSegment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<long>("EndTicks")
.HasColumnType("INTEGER");
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<string>("SegmentProviderId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("StartTicks")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("MediaSegments");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Kind")
.HasColumnType("INTEGER");
b.Property<Guid?>("Permission_Permissions_Guid")
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
b.Property<bool>("Value")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId", "Kind")
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
b.ToTable("Permissions");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Kind")
.HasColumnType("INTEGER");
b.Property<Guid?>("Preference_Preferences_Guid")
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId", "Kind")
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
b.ToTable("Preferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateLastActivity")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("AccessToken")
.IsUnique();
b.ToTable("ApiKeys");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("AppName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<string>("AppVersion")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateLastActivity")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<string>("DeviceId")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("DeviceName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("DeviceId");
b.HasIndex("AccessToken", "DateLastActivity");
b.HasIndex("DeviceId", "DateLastActivity");
b.HasIndex("UserId", "DeviceId");
b.ToTable("Devices");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CustomName")
.HasColumnType("TEXT");
b.Property<string>("DeviceId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("DeviceId")
.IsUnique();
b.ToTable("DeviceOptions");
});
modelBuilder.Entity("Jellyfin.Data.Entities.TrickplayInfo", b =>
{
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<int>("Width")
.HasColumnType("INTEGER");
b.Property<int>("Bandwidth")
.HasColumnType("INTEGER");
b.Property<int>("Height")
.HasColumnType("INTEGER");
b.Property<int>("Interval")
.HasColumnType("INTEGER");
b.Property<int>("ThumbnailCount")
.HasColumnType("INTEGER");
b.Property<int>("TileHeight")
.HasColumnType("INTEGER");
b.Property<int>("TileWidth")
.HasColumnType("INTEGER");
b.HasKey("ItemId", "Width");
b.ToTable("TrickplayInfos");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AudioLanguagePreference")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("AuthenticationProviderId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("CastReceiverId")
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<bool>("DisplayCollectionsView")
.HasColumnType("INTEGER");
b.Property<bool>("DisplayMissingEpisodes")
.HasColumnType("INTEGER");
b.Property<bool>("EnableAutoLogin")
.HasColumnType("INTEGER");
b.Property<bool>("EnableLocalPassword")
.HasColumnType("INTEGER");
b.Property<bool>("EnableNextEpisodeAutoPlay")
.HasColumnType("INTEGER");
b.Property<bool>("EnableUserPreferenceAccess")
.HasColumnType("INTEGER");
b.Property<bool>("HidePlayedInLatest")
.HasColumnType("INTEGER");
b.Property<long>("InternalId")
.HasColumnType("INTEGER");
b.Property<int>("InvalidLoginAttemptCount")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastActivityDate")
.HasColumnType("TEXT");
b.Property<DateTime?>("LastLoginDate")
.HasColumnType("TEXT");
b.Property<int?>("LoginAttemptsBeforeLockout")
.HasColumnType("INTEGER");
b.Property<int>("MaxActiveSessions")
.HasColumnType("INTEGER");
b.Property<int?>("MaxParentalAgeRating")
.HasColumnType("INTEGER");
b.Property<bool>("MustUpdatePassword")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.Property<string>("PasswordResetProviderId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("PlayDefaultAudioTrack")
.HasColumnType("INTEGER");
b.Property<bool>("RememberAudioSelections")
.HasColumnType("INTEGER");
b.Property<bool>("RememberSubtitleSelections")
.HasColumnType("INTEGER");
b.Property<int?>("RemoteClientBitrateLimit")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("SubtitleLanguagePreference")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<int>("SubtitleMode")
.HasColumnType("INTEGER");
b.Property<int>("SyncPlayAccess")
.HasColumnType("INTEGER");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT")
.UseCollation("NOCASE");
b.HasKey("Id");
b.HasIndex("Username")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("Jellyfin.Data.Entities.UserData", b =>
{
b.Property<int?>("AudioStreamIndex")
.HasColumnType("INTEGER");
b.Property<bool>("IsFavorite")
.HasColumnType("INTEGER");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime?>("LastPlayedDate")
.HasColumnType("TEXT");
b.Property<bool?>("Likes")
.HasColumnType("INTEGER");
b.Property<int>("PlayCount")
.HasColumnType("INTEGER");
b.Property<long>("PlaybackPositionTicks")
.HasColumnType("INTEGER");
b.Property<bool>("Played")
.HasColumnType("INTEGER");
b.Property<double?>("Rating")
.HasColumnType("REAL");
b.Property<int?>("SubtitleStreamIndex")
.HasColumnType("INTEGER");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasIndex("UserId");
b.HasIndex("Key", "UserId")
.IsUnique();
b.HasIndex("Key", "UserId", "IsFavorite");
b.HasIndex("Key", "UserId", "LastPlayedDate");
b.HasIndex("Key", "UserId", "PlaybackPositionTicks");
b.HasIndex("Key", "UserId", "Played");
b.ToTable("UserData");
});
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("AccessSchedules")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("DisplayPreferences")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
{
b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
.WithMany("HomeSections")
.HasForeignKey("DisplayPreferencesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithOne("ProfileImage")
.HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("ItemDisplayPreferences")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Permissions")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Preferences")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Jellyfin.Data.Entities.UserData", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.Navigation("HomeSections");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
b.Navigation("AccessSchedules");
b.Navigation("DisplayPreferences");
b.Navigation("ItemDisplayPreferences");
b.Navigation("Permissions");
b.Navigation("Preferences");
b.Navigation("ProfileImage");
});
#pragma warning restore 612, 618
}
}
}

@ -1,79 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations
{
/// <inheritdoc />
public partial class UserDataInJfLib : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "UserData",
columns: table => new
{
Key = table.Column<string>(type: "TEXT", nullable: false),
Rating = table.Column<double>(type: "REAL", nullable: true),
PlaybackPositionTicks = table.Column<long>(type: "INTEGER", nullable: false),
PlayCount = table.Column<int>(type: "INTEGER", nullable: false),
IsFavorite = table.Column<bool>(type: "INTEGER", nullable: false),
LastPlayedDate = table.Column<DateTime>(type: "TEXT", nullable: true),
Played = table.Column<bool>(type: "INTEGER", nullable: false),
AudioStreamIndex = table.Column<int>(type: "INTEGER", nullable: true),
SubtitleStreamIndex = table.Column<int>(type: "INTEGER", nullable: true),
Likes = table.Column<bool>(type: "INTEGER", nullable: true),
UserId = table.Column<Guid>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.ForeignKey(
name: "FK_UserData_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId",
table: "UserData",
columns: new[] { "Key", "UserId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId_IsFavorite",
table: "UserData",
columns: new[] { "Key", "UserId", "IsFavorite" });
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId_LastPlayedDate",
table: "UserData",
columns: new[] { "Key", "UserId", "LastPlayedDate" });
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId_PlaybackPositionTicks",
table: "UserData",
columns: new[] { "Key", "UserId", "PlaybackPositionTicks" });
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId_Played",
table: "UserData",
columns: new[] { "Key", "UserId", "Played" });
migrationBuilder.CreateIndex(
name: "IX_UserData_UserId",
table: "UserData",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserData");
}
}
}

@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
[DbContext(typeof(JellyfinDbContext))]
[Migration("20241009112234_BaseItemRefactor")]
[Migration("20241009132112_BaseItemRefactor")]
partial class BaseItemRefactor
{
/// <inheritdoc />
@ -379,10 +379,6 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("PresentationUniqueKey");
b.HasIndex("SeasonId");
b.HasIndex("SeriesId");
b.HasIndex("TopParentId", "Id");
b.HasIndex("UserDataKey", "Type");
@ -1275,33 +1271,6 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Navigation("Item");
});
modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
{
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Parent")
.WithMany("DirectChildren")
.HasForeignKey("ParentId");
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Season")
.WithMany("SeasonEpisodes")
.HasForeignKey("SeasonId");
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Series")
.WithMany("SeriesEpisodes")
.HasForeignKey("SeriesId");
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "TopParent")
.WithMany("AllChildren")
.HasForeignKey("TopParentId");
b.Navigation("Parent");
b.Navigation("Season");
b.Navigation("Series");
b.Navigation("TopParent");
});
modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b =>
{
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item")
@ -1436,14 +1405,10 @@ namespace Jellyfin.Server.Implementations.Migrations
modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
{
b.Navigation("AllChildren");
b.Navigation("AncestorIds");
b.Navigation("Chapters");
b.Navigation("DirectChildren");
b.Navigation("ItemValues");
b.Navigation("MediaStreams");
@ -1452,10 +1417,6 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Navigation("Provider");
b.Navigation("SeasonEpisodes");
b.Navigation("SeriesEpisodes");
b.Navigation("UserData");
});

@ -11,21 +11,6 @@ namespace Jellyfin.Server.Implementations.Migrations
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_UserData_Key_UserId",
table: "UserData");
migrationBuilder.AddColumn<Guid>(
name: "BaseItemEntityId",
table: "UserData",
type: "TEXT",
nullable: true);
migrationBuilder.AddPrimaryKey(
name: "PK_UserData",
table: "UserData",
columns: new[] { "Key", "UserId" });
migrationBuilder.CreateTable(
name: "BaseItems",
columns: table => new
@ -109,26 +94,6 @@ namespace Jellyfin.Server.Implementations.Migrations
constraints: table =>
{
table.PrimaryKey("PK_BaseItems", x => x.Id);
table.ForeignKey(
name: "FK_BaseItems_BaseItems_ParentId",
column: x => x.ParentId,
principalTable: "BaseItems",
principalColumn: "Id");
table.ForeignKey(
name: "FK_BaseItems_BaseItems_SeasonId",
column: x => x.SeasonId,
principalTable: "BaseItems",
principalColumn: "Id");
table.ForeignKey(
name: "FK_BaseItems_BaseItems_SeriesId",
column: x => x.SeriesId,
principalTable: "BaseItems",
principalColumn: "Id");
table.ForeignKey(
name: "FK_BaseItems_BaseItems_TopParentId",
column: x => x.TopParentId,
principalTable: "BaseItems",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
@ -318,10 +283,38 @@ namespace Jellyfin.Server.Implementations.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_UserData_BaseItemEntityId",
table: "UserData",
column: "BaseItemEntityId");
migrationBuilder.CreateTable(
name: "UserData",
columns: table => new
{
Key = table.Column<string>(type: "TEXT", nullable: false),
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
Rating = table.Column<double>(type: "REAL", nullable: true),
PlaybackPositionTicks = table.Column<long>(type: "INTEGER", nullable: false),
PlayCount = table.Column<int>(type: "INTEGER", nullable: false),
IsFavorite = table.Column<bool>(type: "INTEGER", nullable: false),
LastPlayedDate = table.Column<DateTime>(type: "TEXT", nullable: true),
Played = table.Column<bool>(type: "INTEGER", nullable: false),
AudioStreamIndex = table.Column<int>(type: "INTEGER", nullable: true),
SubtitleStreamIndex = table.Column<int>(type: "INTEGER", nullable: true),
Likes = table.Column<bool>(type: "INTEGER", nullable: true),
BaseItemEntityId = table.Column<Guid>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UserData", x => new { x.Key, x.UserId });
table.ForeignKey(
name: "FK_UserData_BaseItems_BaseItemEntityId",
column: x => x.BaseItemEntityId,
principalTable: "BaseItems",
principalColumn: "Id");
table.ForeignKey(
name: "FK_UserData_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AncestorIds_Id",
@ -368,16 +361,6 @@ namespace Jellyfin.Server.Implementations.Migrations
table: "BaseItems",
column: "PresentationUniqueKey");
migrationBuilder.CreateIndex(
name: "IX_BaseItems_SeasonId",
table: "BaseItems",
column: "SeasonId");
migrationBuilder.CreateIndex(
name: "IX_BaseItems_SeriesId",
table: "BaseItems",
column: "SeriesId");
migrationBuilder.CreateIndex(
name: "IX_BaseItems_TopParentId_Id",
table: "BaseItems",
@ -453,21 +436,40 @@ namespace Jellyfin.Server.Implementations.Migrations
table: "Peoples",
column: "Name");
migrationBuilder.AddForeignKey(
name: "FK_UserData_BaseItems_BaseItemEntityId",
migrationBuilder.CreateIndex(
name: "IX_UserData_BaseItemEntityId",
table: "UserData",
column: "BaseItemEntityId");
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId_IsFavorite",
table: "UserData",
columns: new[] { "Key", "UserId", "IsFavorite" });
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId_LastPlayedDate",
table: "UserData",
columns: new[] { "Key", "UserId", "LastPlayedDate" });
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId_PlaybackPositionTicks",
table: "UserData",
column: "BaseItemEntityId",
principalTable: "BaseItems",
principalColumn: "Id");
columns: new[] { "Key", "UserId", "PlaybackPositionTicks" });
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId_Played",
table: "UserData",
columns: new[] { "Key", "UserId", "Played" });
migrationBuilder.CreateIndex(
name: "IX_UserData_UserId",
table: "UserData",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_UserData_BaseItems_BaseItemEntityId",
table: "UserData");
migrationBuilder.DropTable(
name: "AncestorIds");
@ -490,25 +492,10 @@ namespace Jellyfin.Server.Implementations.Migrations
name: "Peoples");
migrationBuilder.DropTable(
name: "BaseItems");
name: "UserData");
migrationBuilder.DropPrimaryKey(
name: "PK_UserData",
table: "UserData");
migrationBuilder.DropIndex(
name: "IX_UserData_BaseItemEntityId",
table: "UserData");
migrationBuilder.DropColumn(
name: "BaseItemEntityId",
table: "UserData");
migrationBuilder.CreateIndex(
name: "IX_UserData_Key_UserId",
table: "UserData",
columns: new[] { "Key", "UserId" },
unique: true);
migrationBuilder.DropTable(
name: "BaseItems");
}
}
}

@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Logging.Abstractions;
namespace Jellyfin.Server.Implementations.Migrations
{
@ -14,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
var optionsBuilder = new DbContextOptionsBuilder<JellyfinDbContext>();
optionsBuilder.UseSqlite("Data Source=jellyfin.db");
return new JellyfinDbContext(optionsBuilder.Options);
return new JellyfinDbContext(optionsBuilder.Options, NullLogger<JellyfinDbContext>.Instance);
}
}
}

@ -376,10 +376,6 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("PresentationUniqueKey");
b.HasIndex("SeasonId");
b.HasIndex("SeriesId");
b.HasIndex("TopParentId", "Id");
b.HasIndex("UserDataKey", "Type");
@ -1272,33 +1268,6 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Navigation("Item");
});
modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
{
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Parent")
.WithMany("DirectChildren")
.HasForeignKey("ParentId");
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Season")
.WithMany("SeasonEpisodes")
.HasForeignKey("SeasonId");
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Series")
.WithMany("SeriesEpisodes")
.HasForeignKey("SeriesId");
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "TopParent")
.WithMany("AllChildren")
.HasForeignKey("TopParentId");
b.Navigation("Parent");
b.Navigation("Season");
b.Navigation("Series");
b.Navigation("TopParent");
});
modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b =>
{
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item")
@ -1433,14 +1402,10 @@ namespace Jellyfin.Server.Implementations.Migrations
modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
{
b.Navigation("AllChildren");
b.Navigation("AncestorIds");
b.Navigation("Chapters");
b.Navigation("DirectChildren");
b.Navigation("ItemValues");
b.Navigation("MediaStreams");
@ -1449,10 +1414,6 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Navigation("Provider");
b.Navigation("SeasonEpisodes");
b.Navigation("SeriesEpisodes");
b.Navigation("UserData");
});

@ -15,10 +15,11 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
public void Configure(EntityTypeBuilder<BaseItemEntity> builder)
{
builder.HasKey(e => e.Id);
builder.HasOne(e => e.Parent).WithMany(e => e.DirectChildren).HasForeignKey(e => e.ParentId);
builder.HasOne(e => e.TopParent).WithMany(e => e.AllChildren).HasForeignKey(e => e.TopParentId);
builder.HasOne(e => e.Season).WithMany(e => e.SeasonEpisodes).HasForeignKey(e => e.SeasonId);
builder.HasOne(e => e.Series).WithMany(e => e.SeriesEpisodes).HasForeignKey(e => e.SeriesId);
// TODO: See rant in entity file.
// builder.HasOne(e => e.Parent).WithMany(e => e.DirectChildren).HasForeignKey(e => e.ParentId);
// builder.HasOne(e => e.TopParent).WithMany(e => e.AllChildren).HasForeignKey(e => e.TopParentId);
// builder.HasOne(e => e.Season).WithMany(e => e.SeasonEpisodes).HasForeignKey(e => e.SeasonId);
// builder.HasOne(e => e.Series).WithMany(e => e.SeriesEpisodes).HasForeignKey(e => e.SeriesId);
builder.HasMany(e => e.Peoples);
builder.HasMany(e => e.UserData);
builder.HasMany(e => e.ItemValues);

@ -36,7 +36,7 @@ namespace MediaBrowser.Controller.Providers
public IReadOnlyList<PersonInfo> People
{
get => _people;
set => _people = value.ToList();
set => _people = value?.ToList();
}
public bool HasMetadata { get; set; }

@ -330,7 +330,7 @@ namespace Jellyfin.Providers.Tests.Manager
MetadataService<Movie, MovieInfo>.MergeBaseItemData(source, target, lockedFields, replaceData, false);
actualValue = target.People;
return newValue?.Equals(actualValue) ?? actualValue is null;
return newValue?.SequenceEqual((IEnumerable<PersonInfo>)actualValue!) ?? actualValue is null;
}
/// <summary>

@ -13,7 +13,7 @@ using Xunit.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers;
[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
// [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
@ -62,12 +62,23 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
}
[Fact]
[Priority(0)]
[Priority(-2)]
public async Task UpdateLibraryOptions_Valid_Success()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
var createBody = new AddVirtualFolderDto()
{
LibraryOptions = new LibraryOptions()
{
Enabled = false
}
};
using var createResponse = await client.PostAsJsonAsync("Library/VirtualFolders?name=test&refreshLibrary=true", createBody, _jsonOptions);
Assert.Equal(HttpStatusCode.NoContent, createResponse.StatusCode);
using var response = await client.GetAsync("Library/VirtualFolders");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@ -80,13 +91,13 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
Assert.False(options.Enabled);
options.Enabled = true;
var body = new UpdateLibraryOptionsDto()
var existBody = new UpdateLibraryOptionsDto()
{
Id = Guid.Parse(library.ItemId),
LibraryOptions = options
};
using var response2 = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", body, _jsonOptions);
using var response2 = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", existBody, _jsonOptions);
Assert.Equal(HttpStatusCode.NoContent, response2.StatusCode);
}

Loading…
Cancel
Save