mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
Refactored ItemValue structure
This commit is contained in:
parent
3e7ce5e1df
commit
ee0dad6f43
@ -1,10 +1,13 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
@ -13,11 +16,16 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger<CleanDatabaseScheduledTask> _logger;
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
|
||||
|
||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
|
||||
public CleanDatabaseScheduledTask(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<CleanDatabaseScheduledTask> logger,
|
||||
IDbContextFactory<JellyfinDbContext> dbProvider)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
_dbProvider = dbProvider;
|
||||
}
|
||||
|
||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
@ -34,7 +42,7 @@ namespace Emby.Server.Implementations.Data
|
||||
});
|
||||
|
||||
var numComplete = 0;
|
||||
var numItems = itemIds.Count;
|
||||
var numItems = itemIds.Count + 1;
|
||||
|
||||
_logger.LogDebug("Cleaning {0} items with dead parent links", numItems);
|
||||
|
||||
@ -60,6 +68,11 @@ namespace Emby.Server.Implementations.Data
|
||||
progress.Report(percent * 100);
|
||||
}
|
||||
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete();
|
||||
transaction.Commit();
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,22 @@ namespace Jellyfin.Data.Entities;
|
||||
public class AncestorId
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or Sets the AncestorId that may or may not be an database managed Item or an materialised local item.
|
||||
/// Gets or Sets the AncestorId.
|
||||
/// </summary>
|
||||
public required Guid ParentItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the related that may or may not be an database managed Item or an materialised local item.
|
||||
/// Gets or Sets the related BaseItem.
|
||||
/// </summary>
|
||||
public required Guid ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the ParentItem.
|
||||
/// </summary>
|
||||
public required BaseItemEntity ParentItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the Child item.
|
||||
/// </summary>
|
||||
public required BaseItemEntity Item { get; set; }
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ public class BaseItemEntity
|
||||
|
||||
public ICollection<UserData>? UserData { get; set; }
|
||||
|
||||
public ICollection<ItemValue>? ItemValues { get; set; }
|
||||
public ICollection<ItemValueMap>? ItemValues { get; set; }
|
||||
|
||||
public ICollection<MediaStreamInfo>? MediaStreams { get; set; }
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Jellyfin.Data.Entities;
|
||||
|
||||
@ -11,14 +9,9 @@ namespace Jellyfin.Data.Entities;
|
||||
public class ItemValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or Sets the reference ItemId.
|
||||
/// Gets or Sets the ItemValueId.
|
||||
/// </summary>
|
||||
public required Guid ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the referenced BaseItem.
|
||||
/// </summary>
|
||||
public required BaseItemEntity Item { get; set; }
|
||||
public required Guid ItemValueId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the Type.
|
||||
@ -34,4 +27,11 @@ public class ItemValue
|
||||
/// Gets or Sets the sanatised Value.
|
||||
/// </summary>
|
||||
public required string CleanValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets all associated BaseItems.
|
||||
/// </summary>
|
||||
#pragma warning disable CA2227 // Collection properties should be read only
|
||||
public ICollection<ItemValueMap>? BaseItemsMap { get; set; }
|
||||
#pragma warning restore CA2227 // Collection properties should be read only
|
||||
}
|
||||
|
30
Jellyfin.Data/Entities/ItemValueMap.cs
Normal file
30
Jellyfin.Data/Entities/ItemValueMap.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Jellyfin.Data.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Mapping table for the ItemValue BaseItem relation.
|
||||
/// </summary>
|
||||
public class ItemValueMap
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or Sets the ItemId.
|
||||
/// </summary>
|
||||
public required Guid ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the ItemValueId.
|
||||
/// </summary>
|
||||
public required Guid ItemValueId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the referenced <see cref="BaseItemEntity"/>.
|
||||
/// </summary>
|
||||
public required BaseItemEntity Item { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets the referenced <see cref="ItemValue"/>.
|
||||
/// </summary>
|
||||
public required ItemValue ItemValue { get; set; }
|
||||
}
|
@ -69,10 +69,11 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
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.ItemValuesMap.Where(e => e.ItemId == id).ExecuteDelete();
|
||||
context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete();
|
||||
context.BaseItemImageInfos.Where(e => e.ItemId == id).ExecuteDelete();
|
||||
context.BaseItemProviders.Where(e => e.ItemId == id).ExecuteDelete();
|
||||
context.BaseItems.Where(e => e.Id == id).ExecuteDelete();
|
||||
context.SaveChanges();
|
||||
transaction.Commit();
|
||||
}
|
||||
@ -83,25 +84,8 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
using var context = dbProvider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
|
||||
context.ItemValues.Where(e => e.Type == ItemValueType.InheritedTags).ExecuteDelete();
|
||||
context.ItemValues.AddRange(context.ItemValues.Where(e => e.Type == ItemValueType.Tags).Select(e => new ItemValue()
|
||||
{
|
||||
CleanValue = e.CleanValue,
|
||||
ItemId = e.ItemId,
|
||||
Type = ItemValueType.InheritedTags,
|
||||
Value = e.Value,
|
||||
Item = null!
|
||||
}));
|
||||
|
||||
context.ItemValues.AddRange(
|
||||
context.AncestorIds.Join(context.ItemValues.Where(e => e.Value != null && e.Type == ItemValueType.Tags), e => e.ParentItemId, e => e.ItemId, (e, f) => new ItemValue()
|
||||
{
|
||||
CleanValue = f.CleanValue,
|
||||
ItemId = e.ItemId,
|
||||
Item = null!,
|
||||
Type = ItemValueType.InheritedTags,
|
||||
Value = f.Value
|
||||
}));
|
||||
context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete();
|
||||
// ItemValue Inheritence is now correctly mapped via AncestorId on demand
|
||||
context.SaveChanges();
|
||||
|
||||
transaction.Commit();
|
||||
@ -717,24 +701,22 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
}
|
||||
}
|
||||
|
||||
var artistQuery = context.BaseItems.Where(w => filter.ArtistIds.Contains(w.Id));
|
||||
|
||||
if (filter.ArtistIds.Length > 0)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Any(f => f.Type <= ItemValueType.Artist && artistQuery.Any(w => w.CleanName == f.CleanValue)));
|
||||
.Where(e => e.ItemValues!.Any(f => f.ItemValue.Type <= ItemValueType.Artist && filter.ArtistIds.Contains(f.ItemId)));
|
||||
}
|
||||
|
||||
if (filter.AlbumArtistIds.Length > 0)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Any(f => f.Type == ItemValueType.Artist && artistQuery.Any(w => w.CleanName == f.CleanValue)));
|
||||
.Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.AlbumArtistIds.Contains(f.ItemId)));
|
||||
}
|
||||
|
||||
if (filter.ContributingArtistIds.Length > 0)
|
||||
{
|
||||
var contributingArtists = context.BaseItems.Where(e => filter.ContributingArtistIds.Contains(e.Id));
|
||||
baseQuery = baseQuery.Where(e => e.ItemValues!.Any(f => f.Type == 0 && contributingArtists.Any(w => w.CleanName == f.CleanValue)));
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.ContributingArtistIds.Contains(f.ItemId)));
|
||||
}
|
||||
|
||||
if (filter.AlbumIds.Length > 0)
|
||||
@ -744,42 +726,41 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
|
||||
if (filter.ExcludeArtistIds.Length > 0)
|
||||
{
|
||||
var excludeArtistQuery = context.BaseItems.Where(w => filter.ExcludeArtistIds.Contains(w.Id));
|
||||
baseQuery = baseQuery
|
||||
.Where(e => !e.ItemValues!.Any(f => f.Type <= ItemValueType.Artist && artistQuery.Any(w => w.CleanName == f.CleanValue)));
|
||||
.Where(e => !e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.ExcludeArtistIds.Contains(f.ItemId)));
|
||||
}
|
||||
|
||||
if (filter.GenreIds.Count > 0)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Any(f => f.Type == ItemValueType.Genre && context.BaseItems.Where(w => filter.GenreIds.Contains(w.Id)).Any(w => w.CleanName == f.CleanValue)));
|
||||
.Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Genre && filter.GenreIds.Contains(f.ItemId)));
|
||||
}
|
||||
|
||||
if (filter.Genres.Count > 0)
|
||||
{
|
||||
var cleanGenres = filter.Genres.Select(e => GetCleanValue(e)).ToArray();
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Any(f => f.Type == ItemValueType.Genre && cleanGenres.Contains(f.CleanValue)));
|
||||
.Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Genre && cleanGenres.Contains(f.ItemValue.CleanValue)));
|
||||
}
|
||||
|
||||
if (tags.Count > 0)
|
||||
{
|
||||
var cleanValues = tags.Select(e => GetCleanValue(e)).ToArray();
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Any(f => f.Type == ItemValueType.Tags && cleanValues.Contains(f.CleanValue)));
|
||||
.Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && cleanValues.Contains(f.ItemValue.CleanValue)));
|
||||
}
|
||||
|
||||
if (excludeTags.Count > 0)
|
||||
{
|
||||
var cleanValues = excludeTags.Select(e => GetCleanValue(e)).ToArray();
|
||||
baseQuery = baseQuery
|
||||
.Where(e => !e.ItemValues!.Any(f => f.Type == ItemValueType.Tags && cleanValues.Contains(f.CleanValue)));
|
||||
.Where(e => !e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && cleanValues.Contains(f.ItemValue.CleanValue)));
|
||||
}
|
||||
|
||||
if (filter.StudioIds.Length > 0)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Any(f => f.Type == ItemValueType.Studios && context.BaseItems.Where(w => filter.StudioIds.Contains(w.Id)).Any(w => w.CleanName == f.CleanValue)));
|
||||
.Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Studios && filter.StudioIds.Contains(f.ItemId)));
|
||||
}
|
||||
|
||||
if (filter.OfficialRatings.Length > 0)
|
||||
@ -936,13 +917,13 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => !e.ItemValues!.Any(f => (f.Type == ItemValueType.Artist || f.Type == ItemValueType.AlbumArtist) && f.CleanValue == e.CleanName));
|
||||
.Where(e => e.ItemValues!.Count(f => (f.ItemValue.Type == ItemValueType.Artist || f.ItemValue.Type == ItemValueType.AlbumArtist)) == 1);
|
||||
}
|
||||
|
||||
if (filter.IsDeadStudio.HasValue && filter.IsDeadStudio.Value)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => !e.ItemValues!.Any(f => f.Type == ItemValueType.Studios && f.CleanValue == e.CleanName));
|
||||
.Where(e => e.ItemValues!.Count(f => f.ItemValue.Type == ItemValueType.Studios) == 1);
|
||||
}
|
||||
|
||||
if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value)
|
||||
@ -1081,8 +1062,8 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
if (filter.ExcludeInheritedTags.Length > 0)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => !e.ItemValues!.Where(e => e.Type == ItemValueType.InheritedTags)
|
||||
.Any(f => filter.ExcludeInheritedTags.Contains(f.CleanValue)));
|
||||
.Where(e => !e.ItemValues!.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags)
|
||||
.Any(f => filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)));
|
||||
}
|
||||
|
||||
if (filter.IncludeInheritedTags.Length > 0)
|
||||
@ -1092,26 +1073,25 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Where(e => e.Type == ItemValueType.InheritedTags)
|
||||
.Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue))
|
||||
.Where(e => e.ItemValues!.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags)
|
||||
.Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
|
||||
||
|
||||
(e.ParentId.HasValue && context.ItemValues.Where(w => w.ItemId == e.ParentId.Value)!.Where(e => e.Type == ItemValueType.InheritedTags)
|
||||
.Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue))));
|
||||
(e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value)!.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags)
|
||||
.Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))));
|
||||
}
|
||||
|
||||
// A playlist should be accessible to its owner regardless of allowed tags.
|
||||
else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Where(e => e.Type == ItemValueType.InheritedTags)
|
||||
.Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue)) || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\""));
|
||||
// d ^^ this is stupid it hate this.
|
||||
.Where(e => e.AncestorIds!.Any(f => f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(w.ItemValue.CleanValue))
|
||||
|| e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")));
|
||||
// d ^^ this is stupid it hate this.
|
||||
}
|
||||
else
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
.Where(e => e.ItemValues!.Where(e => e.Type == ItemValueType.InheritedTags)
|
||||
.Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue)));
|
||||
.Where(e => e.AncestorIds!.Any(f => f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(w.ItemValue.CleanValue))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1277,25 +1257,48 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
entity.AncestorIds.Add(new AncestorId()
|
||||
{
|
||||
ParentItemId = ancestorId,
|
||||
ItemId = entity.Id
|
||||
ItemId = entity.Id,
|
||||
Item = null!,
|
||||
ParentItem = null!
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var itemValues = GetItemValuesToSave(item.Item, item.InheritedTags);
|
||||
context.ItemValues.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
||||
entity.ItemValues = new List<ItemValue>();
|
||||
var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags);
|
||||
var itemValues = itemValuesToSave.Select(e => e.Value).ToArray();
|
||||
context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
||||
entity.ItemValues = new List<ItemValueMap>();
|
||||
var referenceValues = context.ItemValues.Where(e => itemValues.Any(f => f == e.CleanValue)).ToArray();
|
||||
|
||||
foreach (var itemValue in itemValues)
|
||||
foreach (var itemValue in itemValuesToSave)
|
||||
{
|
||||
entity.ItemValues.Add(new()
|
||||
var refValue = referenceValues.FirstOrDefault(f => f.CleanValue == itemValue.Value && (int)f.Type == itemValue.MagicNumber);
|
||||
if (refValue is not null)
|
||||
{
|
||||
Item = entity,
|
||||
Type = (ItemValueType)itemValue.MagicNumber,
|
||||
Value = itemValue.Value,
|
||||
CleanValue = GetCleanValue(itemValue.Value),
|
||||
ItemId = entity.Id
|
||||
});
|
||||
entity.ItemValues.Add(new ItemValueMap()
|
||||
{
|
||||
Item = entity,
|
||||
ItemId = entity.Id,
|
||||
ItemValue = null!,
|
||||
ItemValueId = refValue.ItemValueId
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.ItemValues.Add(new ItemValueMap()
|
||||
{
|
||||
Item = entity,
|
||||
ItemId = entity.Id,
|
||||
ItemValue = new ItemValue()
|
||||
{
|
||||
CleanValue = GetCleanValue(itemValue.Value),
|
||||
Type = (ItemValueType)itemValue.MagicNumber,
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Value = itemValue.Value
|
||||
},
|
||||
ItemValueId = Guid.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1652,21 +1655,21 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
{
|
||||
using var context = dbProvider.CreateDbContext();
|
||||
|
||||
var query = context.ItemValues
|
||||
var query = context.ItemValuesMap
|
||||
.AsNoTracking()
|
||||
.Where(e => itemValueTypes.Any(w => (ItemValueType)w == e.Type));
|
||||
.Where(e => itemValueTypes.Any(w => (ItemValueType)w == e.ItemValue.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 == e.ItemId)));
|
||||
query = query.Where(e => withItemTypes.Contains(e.Item.Type));
|
||||
}
|
||||
|
||||
if (excludeItemTypes.Count > 0)
|
||||
{
|
||||
query = query.Where(e => !context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId == e.ItemId)));
|
||||
query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type));
|
||||
}
|
||||
|
||||
// query = query.DistinctBy(e => e.CleanValue);
|
||||
return query.Select(e => e.CleanValue).ToImmutableArray();
|
||||
return query.Select(e => e.ItemValue.CleanValue).ToImmutableArray();
|
||||
}
|
||||
|
||||
private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity)
|
||||
@ -1705,7 +1708,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
};
|
||||
var query = TranslateQuery(context.BaseItems.AsNoTracking(), context, innerQuery);
|
||||
|
||||
query = query.Where(e => e.Type == returnType && e.ItemValues!.Any(f => e.CleanName == f.CleanValue && itemValueTypes.Any(w => (ItemValueType)w == f.Type)));
|
||||
query = query.Where(e => e.Type == returnType && e.ItemValues!.Any(f => e.CleanName == f.ItemValue.CleanValue && itemValueTypes.Any(w => (ItemValueType)w == f.ItemValue.Type)));
|
||||
|
||||
if (filter.OrderBy.Count != 0
|
||||
|| !string.IsNullOrEmpty(filter.SearchTerm))
|
||||
@ -1745,13 +1748,13 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
// TODO: This is bad refactor!
|
||||
itemCount = new ItemCounts()
|
||||
{
|
||||
SeriesCount = context.ItemValues.Count(f => e.ItemValues!.Any(w => w.Type == f.Type && w.CleanValue == f.CleanValue && f.Item.Type == typeof(Series).FullName)),
|
||||
EpisodeCount = context.ItemValues.Count(f => e.ItemValues!.Any(w => w.Type == f.Type && w.CleanValue == f.CleanValue && f.Item.Type == typeof(Episode).FullName)),
|
||||
MovieCount = context.ItemValues.Count(f => e.ItemValues!.Any(w => w.Type == f.Type && w.CleanValue == f.CleanValue && f.Item.Type == typeof(Data.Entities.Libraries.Movie).FullName)),
|
||||
AlbumCount = context.ItemValues.Count(f => e.ItemValues!.Any(w => w.Type == f.Type && w.CleanValue == f.CleanValue && f.Item.Type == typeof(MusicAlbum).FullName)),
|
||||
ArtistCount = context.ItemValues.Count(f => e.ItemValues!.Any(w => w.Type == f.Type && w.CleanValue == f.CleanValue && f.Item.Type == typeof(MusicArtist).FullName)),
|
||||
SongCount = context.ItemValues.Count(f => e.ItemValues!.Any(w => w.Type == f.Type && w.CleanValue == f.CleanValue && f.Item.Type == typeof(Audio).FullName)),
|
||||
TrailerCount = context.ItemValues.Count(f => e.ItemValues!.Any(w => w.Type == f.Type && w.CleanValue == f.CleanValue && f.Item.Type == typeof(Trailer).FullName)),
|
||||
SeriesCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Series).FullName),
|
||||
EpisodeCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Data.Entities.Libraries.Movie).FullName),
|
||||
MovieCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Series).FullName),
|
||||
AlbumCount = e.ItemValues!.Count(f => f.Item.Type == typeof(MusicAlbum).FullName),
|
||||
ArtistCount = e.ItemValues!.Count(f => f.Item.Type == typeof(MusicArtist).FullName),
|
||||
SongCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Audio).FullName),
|
||||
TrailerCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Trailer).FullName),
|
||||
}
|
||||
});
|
||||
|
||||
@ -1981,9 +1984,9 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
|
||||
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 == ItemValueType.AlbumArtist).Select(f => f.CleanValue),
|
||||
ItemSortBy.Studio => e => e.ItemValues!.Where(f => f.Type == ItemValueType.Studios).Select(f => f.CleanValue),
|
||||
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),
|
||||
ItemSortBy.Studio => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).Select(f => f.ItemValue.CleanValue),
|
||||
ItemSortBy.OfficialRating => e => e.InheritedParentalRatingValue,
|
||||
// ItemSortBy.SeriesDatePlayed => "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)",
|
||||
ItemSortBy.SeriesSortName => e => e.SeriesName,
|
||||
|
@ -116,6 +116,11 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
|
||||
/// </summary>
|
||||
public DbSet<ItemValue> ItemValues => Set<ItemValue>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DbSet{TEntity}"/>.
|
||||
/// </summary>
|
||||
public DbSet<ItemValueMap> ItemValuesMap => Set<ItemValueMap>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DbSet{TEntity}"/> containing the user data.
|
||||
/// </summary>
|
||||
|
1582
Jellyfin.Server.Implementations/Migrations/20241010142722_FixedItemValueReferenceStyle.Designer.cs
generated
Normal file
1582
Jellyfin.Server.Implementations/Migrations/20241010142722_FixedItemValueReferenceStyle.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixedItemValueReferenceStyle : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ItemValues_BaseItems_ItemId",
|
||||
table: "ItemValues");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_ItemValues",
|
||||
table: "ItemValues");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ItemValues_ItemId_Type_CleanValue",
|
||||
table: "ItemValues");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ItemId",
|
||||
table: "ItemValues",
|
||||
newName: "ItemValueId");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_ItemValues",
|
||||
table: "ItemValues",
|
||||
column: "ItemValueId");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ItemValuesMap",
|
||||
columns: table => new
|
||||
{
|
||||
ItemId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
ItemValueId = table.Column<Guid>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ItemValuesMap", x => new { x.ItemValueId, x.ItemId });
|
||||
table.ForeignKey(
|
||||
name: "FK_ItemValuesMap_BaseItems_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ItemValuesMap_ItemValues_ItemValueId",
|
||||
column: x => x.ItemValueId,
|
||||
principalTable: "ItemValues",
|
||||
principalColumn: "ItemValueId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ItemValues_Type_CleanValue",
|
||||
table: "ItemValues",
|
||||
columns: new[] { "Type", "CleanValue" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ItemValuesMap_ItemId",
|
||||
table: "ItemValuesMap",
|
||||
column: "ItemId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_AncestorIds_BaseItems_ItemId",
|
||||
table: "AncestorIds",
|
||||
column: "ItemId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_AncestorIds_BaseItems_ParentItemId",
|
||||
table: "AncestorIds",
|
||||
column: "ParentItemId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_AncestorIds_BaseItems_ItemId",
|
||||
table: "AncestorIds");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_AncestorIds_BaseItems_ParentItemId",
|
||||
table: "AncestorIds");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ItemValuesMap");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_ItemValues",
|
||||
table: "ItemValues");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ItemValues_Type_CleanValue",
|
||||
table: "ItemValues");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ItemValueId",
|
||||
table: "ItemValues",
|
||||
newName: "ItemId");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_ItemValues",
|
||||
table: "ItemValues",
|
||||
columns: new[] { "ItemId", "Type", "Value" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ItemValues_ItemId_Type_CleanValue",
|
||||
table: "ItemValues",
|
||||
columns: new[] { "ItemId", "Type", "CleanValue" });
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ItemValues_BaseItems_ItemId",
|
||||
table: "ItemValues",
|
||||
column: "ItemId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
@ -683,26 +683,43 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ItemValue", b =>
|
||||
{
|
||||
b.Property<Guid>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Value")
|
||||
b.Property<Guid>("ItemValueId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CleanValue")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ItemId", "Type", "Value");
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasIndex("ItemId", "Type", "CleanValue");
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ItemValueId");
|
||||
|
||||
b.HasIndex("Type", "CleanValue");
|
||||
|
||||
b.ToTable("ItemValues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ItemValueMap", b =>
|
||||
{
|
||||
b.Property<Guid>("ItemValueId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ItemValueId", "ItemId");
|
||||
|
||||
b.HasIndex("ItemId");
|
||||
|
||||
b.ToTable("ItemValuesMap");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.MediaSegment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -1307,6 +1324,22 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", null)
|
||||
.WithMany("AncestorIds")
|
||||
.HasForeignKey("BaseItemEntityId");
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item")
|
||||
.WithMany()
|
||||
.HasForeignKey("ItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "ParentItem")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Item");
|
||||
|
||||
b.Navigation("ParentItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AttachmentStreamInfo", b =>
|
||||
@ -1410,7 +1443,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ItemValue", b =>
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ItemValueMap", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item")
|
||||
.WithMany("ItemValues")
|
||||
@ -1418,7 +1451,15 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.ItemValue", "ItemValue")
|
||||
.WithMany("BaseItemsMap")
|
||||
.HasForeignKey("ItemValueId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Item");
|
||||
|
||||
b.Navigation("ItemValue");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.MediaStreamInfo", b =>
|
||||
@ -1513,6 +1554,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Navigation("HomeSections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ItemValue", b =>
|
||||
{
|
||||
b.Navigation("BaseItemsMap");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("AccessSchedules");
|
||||
|
@ -15,5 +15,7 @@ public class AncestorIdConfiguration : IEntityTypeConfiguration<AncestorId>
|
||||
{
|
||||
builder.HasKey(e => new { e.ItemId, e.ParentItemId });
|
||||
builder.HasIndex(e => e.ParentItemId);
|
||||
builder.HasOne(e => e.ParentItem);
|
||||
builder.HasOne(e => e.Item);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ public class ItemValuesConfiguration : IEntityTypeConfiguration<ItemValue>
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<ItemValue> builder)
|
||||
{
|
||||
builder.HasKey(e => new { e.ItemId, e.Type, e.Value });
|
||||
builder.HasIndex(e => new { e.ItemId, e.Type, e.CleanValue });
|
||||
builder.HasKey(e => e.ItemValueId);
|
||||
builder.HasIndex(e => new { e.Type, e.CleanValue });
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.ModelConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// itemvalues Configuration.
|
||||
/// </summary>
|
||||
public class ItemValuesMapConfiguration : IEntityTypeConfiguration<ItemValueMap>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<ItemValueMap> builder)
|
||||
{
|
||||
builder.HasKey(e => new { e.ItemValueId, e.ItemId });
|
||||
builder.HasOne(e => e.Item);
|
||||
builder.HasOne(e => e.ItemValue);
|
||||
}
|
||||
}
|
@ -113,12 +113,31 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
|
||||
dbContext.SaveChanges();
|
||||
|
||||
var itemValueQuery = "select ItemId, Type, Value, CleanValue FROM 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();
|
||||
|
||||
foreach (SqliteDataReader dto in connection.Query(itemValueQuery))
|
||||
{
|
||||
dbContext.ItemValues.Add(GetItemValue(dto));
|
||||
var itemId = dto.GetGuid(0);
|
||||
var entity = GetItemValue(dto);
|
||||
var existingItemValue = dbContext.ItemValues.FirstOrDefault(f => f.Type == entity.Type && f.Value == entity.Value);
|
||||
if (existingItemValue is null)
|
||||
{
|
||||
dbContext.ItemValues.Add(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = existingItemValue;
|
||||
}
|
||||
|
||||
dbContext.ItemValuesMap.Add(new ItemValueMap()
|
||||
{
|
||||
Item = null!,
|
||||
ItemValue = null!,
|
||||
ItemId = itemId,
|
||||
ItemValueId = entity.ItemValueId
|
||||
});
|
||||
}
|
||||
|
||||
dbContext.SaveChanges();
|
||||
@ -185,7 +204,9 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
return new AncestorId()
|
||||
{
|
||||
ItemId = reader.GetGuid(0),
|
||||
ParentItemId = reader.GetGuid(1)
|
||||
ParentItemId = reader.GetGuid(1),
|
||||
Item = null!,
|
||||
ParentItem = null!
|
||||
};
|
||||
}
|
||||
|
||||
@ -226,11 +247,10 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
{
|
||||
return new ItemValue
|
||||
{
|
||||
ItemId = reader.GetGuid(0),
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Type = (ItemValueType)reader.GetInt32(1),
|
||||
Value = reader.GetString(2),
|
||||
CleanValue = reader.GetString(3),
|
||||
Item = null!
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user