mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-21 08:06:58 +01:00
Merge remote-tracking branch 'upstream/master' into epg-fixes
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Database.Implementations.MatchCriteria;
|
||||
|
||||
namespace Jellyfin.Database.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for querying item hierarchies using iterative traversal.
|
||||
/// Uses AncestorIds and LinkedChildren tables for parent-child traversal.
|
||||
/// </summary>
|
||||
public static class DescendantQueryHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a queryable of all descendant IDs for a parent item.
|
||||
/// Traverses AncestorIds and LinkedChildren to find all descendants.
|
||||
/// </summary>
|
||||
/// <param name="context">Database context.</param>
|
||||
/// <param name="parentId">Parent item ID.</param>
|
||||
/// <returns>Queryable of descendant item IDs.</returns>
|
||||
public static IQueryable<Guid> GetAllDescendantIds(JellyfinDbContext context, Guid parentId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var descendants = TraverseHierarchyDown(context, [parentId]);
|
||||
|
||||
descendants.Remove(parentId);
|
||||
|
||||
return descendants.AsQueryable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a queryable of all owned descendant IDs for a parent item.
|
||||
/// Traverses only AncestorIds (hierarchical ownership), NOT LinkedChildren (associations).
|
||||
/// Use this for deletion to avoid destroying items that are merely linked (e.g. movies in a BoxSet).
|
||||
/// </summary>
|
||||
/// <param name="context">Database context.</param>
|
||||
/// <param name="parentId">Parent item ID.</param>
|
||||
/// <returns>Queryable of owned descendant item IDs.</returns>
|
||||
public static IQueryable<Guid> GetOwnedDescendantIds(JellyfinDbContext context, Guid parentId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var descendants = TraverseHierarchyDownOwned(context, [parentId]);
|
||||
|
||||
descendants.Remove(parentId);
|
||||
|
||||
return descendants.AsQueryable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all owned descendant IDs for multiple parent items in a single traversal.
|
||||
/// More efficient than calling <see cref="GetOwnedDescendantIds"/> per parent because
|
||||
/// it performs one traversal for all seeds instead of N separate traversals.
|
||||
/// </summary>
|
||||
/// <param name="context">Database context.</param>
|
||||
/// <param name="parentIds">Parent item IDs.</param>
|
||||
/// <returns>Set of all owned descendant item IDs (excluding the parent IDs themselves).</returns>
|
||||
public static HashSet<Guid> GetOwnedDescendantIdsBatch(JellyfinDbContext context, IReadOnlyList<Guid> parentIds)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
ArgumentNullException.ThrowIfNull(parentIds);
|
||||
|
||||
if (parentIds.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var seedSet = new HashSet<Guid>(parentIds);
|
||||
var descendants = TraverseHierarchyDownOwned(context, seedSet);
|
||||
|
||||
// Remove the seed IDs — callers want only descendants
|
||||
descendants.ExceptWith(seedSet);
|
||||
|
||||
return descendants;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a queryable of all folder IDs that have any descendant matching the specified criteria.
|
||||
/// Can be used in LINQ .Contains() expressions.
|
||||
/// </summary>
|
||||
/// <param name="context">Database context.</param>
|
||||
/// <param name="criteria">The matching criteria to apply.</param>
|
||||
/// <returns>Queryable of folder IDs.</returns>
|
||||
public static IQueryable<Guid> GetFolderIdsMatching(JellyfinDbContext context, FolderMatchCriteria criteria)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
ArgumentNullException.ThrowIfNull(criteria);
|
||||
var matchingItemIds = criteria switch
|
||||
{
|
||||
HasSubtitles => context.MediaStreamInfos
|
||||
.Where(ms => ms.StreamType == MediaStreamTypeEntity.Subtitle)
|
||||
.Select(ms => ms.ItemId)
|
||||
.Distinct()
|
||||
.ToHashSet(),
|
||||
HasChapterImages => context.Chapters
|
||||
.Where(c => c.ImagePath != null)
|
||||
.Select(c => c.ItemId)
|
||||
.Distinct()
|
||||
.ToHashSet(),
|
||||
HasMediaStreamType m => GetMatchingMediaStreamItemIds(context, m),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(criteria), $"Unknown criteria type: {criteria.GetType().Name}")
|
||||
};
|
||||
|
||||
var ancestors = TraverseHierarchyUp(context, matchingItemIds);
|
||||
|
||||
return ancestors.AsQueryable();
|
||||
}
|
||||
|
||||
private static HashSet<Guid> GetMatchingMediaStreamItemIds(JellyfinDbContext context, HasMediaStreamType criteria)
|
||||
{
|
||||
var query = context.MediaStreamInfos
|
||||
.Where(ms => ms.StreamType == criteria.StreamType && ms.Language == criteria.Language);
|
||||
|
||||
if (criteria.IsExternal.HasValue)
|
||||
{
|
||||
var isExternal = criteria.IsExternal.Value;
|
||||
query = query.Where(ms => ms.IsExternal == isExternal);
|
||||
}
|
||||
|
||||
return query.Select(ms => ms.ItemId).Distinct().ToHashSet();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traverses DOWN the hierarchy from parent folders to find all descendants.
|
||||
/// </summary>
|
||||
private static HashSet<Guid> TraverseHierarchyDown(JellyfinDbContext context, ICollection<Guid> startIds)
|
||||
{
|
||||
var visited = new HashSet<Guid>(startIds);
|
||||
var folderStack = new HashSet<Guid>(startIds);
|
||||
|
||||
while (folderStack.Count != 0)
|
||||
{
|
||||
var currentFolders = folderStack.ToArray();
|
||||
folderStack.Clear();
|
||||
|
||||
var directChildren = context.AncestorIds
|
||||
.WhereOneOrMany(currentFolders, e => e.ParentItemId)
|
||||
.Select(e => e.ItemId)
|
||||
.ToArray();
|
||||
|
||||
var linkedChildren = context.LinkedChildren
|
||||
.WhereOneOrMany(currentFolders, e => e.ParentId)
|
||||
.Select(e => e.ChildId)
|
||||
.ToArray();
|
||||
|
||||
var allChildren = directChildren.Concat(linkedChildren).Distinct().ToArray();
|
||||
|
||||
if (allChildren.Length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var childFolders = context.BaseItems
|
||||
.WhereOneOrMany(allChildren, e => e.Id)
|
||||
.Where(e => e.IsFolder)
|
||||
.Select(e => e.Id)
|
||||
.ToHashSet();
|
||||
|
||||
foreach (var childId in allChildren)
|
||||
{
|
||||
if (visited.Add(childId) && childFolders.Contains(childId))
|
||||
{
|
||||
folderStack.Add(childId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return visited;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traverses DOWN the hierarchy using only AncestorIds (ownership), not LinkedChildren.
|
||||
/// </summary>
|
||||
private static HashSet<Guid> TraverseHierarchyDownOwned(JellyfinDbContext context, ICollection<Guid> startIds)
|
||||
{
|
||||
var visited = new HashSet<Guid>(startIds);
|
||||
var folderStack = new HashSet<Guid>(startIds);
|
||||
|
||||
while (folderStack.Count != 0)
|
||||
{
|
||||
var currentFolders = folderStack.ToArray();
|
||||
folderStack.Clear();
|
||||
|
||||
var directChildren = context.AncestorIds
|
||||
.WhereOneOrMany(currentFolders, e => e.ParentItemId)
|
||||
.Select(e => e.ItemId)
|
||||
.ToArray();
|
||||
|
||||
if (directChildren.Length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var childFolders = context.BaseItems
|
||||
.WhereOneOrMany(directChildren, e => e.Id)
|
||||
.Where(e => e.IsFolder)
|
||||
.Select(e => e.Id)
|
||||
.ToHashSet();
|
||||
|
||||
foreach (var childId in directChildren)
|
||||
{
|
||||
if (visited.Add(childId) && childFolders.Contains(childId))
|
||||
{
|
||||
folderStack.Add(childId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return visited;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traverses UP the hierarchy from items to find all ancestor folders.
|
||||
/// </summary>
|
||||
private static HashSet<Guid> TraverseHierarchyUp(JellyfinDbContext context, ICollection<Guid> startIds)
|
||||
{
|
||||
var ancestors = new HashSet<Guid>();
|
||||
var itemStack = new HashSet<Guid>(startIds);
|
||||
|
||||
while (itemStack.Count != 0)
|
||||
{
|
||||
var currentItems = itemStack.ToArray();
|
||||
itemStack.Clear();
|
||||
|
||||
var ancestorParents = context.AncestorIds
|
||||
.WhereOneOrMany(currentItems, e => e.ItemId)
|
||||
.Select(e => e.ParentItemId)
|
||||
.ToArray();
|
||||
|
||||
var linkedParents = context.LinkedChildren
|
||||
.WhereOneOrMany(currentItems, e => e.ChildId)
|
||||
.Select(e => e.ParentId)
|
||||
.ToArray();
|
||||
|
||||
foreach (var parentId in ancestorParents.Concat(linkedParents))
|
||||
{
|
||||
if (ancestors.Add(parentId))
|
||||
{
|
||||
itemStack.Add(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ancestors;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ public class BaseItemEntity
|
||||
|
||||
public string? OriginalTitle { get; set; }
|
||||
|
||||
public string? PrimaryVersionId { get; set; }
|
||||
public Guid? PrimaryVersionId { get; set; }
|
||||
|
||||
public DateTime? DateLastMediaAdded { get; set; }
|
||||
|
||||
@@ -118,8 +118,6 @@ public class BaseItemEntity
|
||||
|
||||
public string? ProductionLocations { get; set; }
|
||||
|
||||
public string? ExtraIds { get; set; }
|
||||
|
||||
public int? TotalBitrate { get; set; }
|
||||
|
||||
public BaseItemExtraType? ExtraType { get; set; }
|
||||
@@ -134,7 +132,17 @@ public class BaseItemEntity
|
||||
|
||||
public string? ShowId { get; set; }
|
||||
|
||||
public string? OwnerId { get; set; }
|
||||
public Guid? OwnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the owner item (for extras like trailers, theme songs, etc.).
|
||||
/// </summary>
|
||||
public BaseItemEntity? Owner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extras owned by this item (trailers, theme songs, behind the scenes, etc.).
|
||||
/// </summary>
|
||||
public ICollection<BaseItemEntity>? Extras { get; set; }
|
||||
|
||||
public int? Width { get; set; }
|
||||
|
||||
@@ -178,6 +186,16 @@ public class BaseItemEntity
|
||||
|
||||
public ICollection<BaseItemImageInfo>? Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the linked children (for BoxSets, Playlists, etc.).
|
||||
/// </summary>
|
||||
public ICollection<LinkedChildEntity>? LinkedChildEntities { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the items this entity is linked to as a child.
|
||||
/// </summary>
|
||||
public ICollection<LinkedChildEntity>? LinkedChildOfEntities { 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; }
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace Jellyfin.Database.Implementations.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a linked child relationship between items (e.g., BoxSet to Movies, Playlist to tracks).
|
||||
/// </summary>
|
||||
public class LinkedChildEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the parent item ID (BoxSet, Playlist, etc.).
|
||||
/// </summary>
|
||||
public required Guid ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the child item ID.
|
||||
/// </summary>
|
||||
public required Guid ChildId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of linked child (Manual or Shortcut).
|
||||
/// </summary>
|
||||
public required LinkedChildType ChildType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sort order.
|
||||
/// </summary>
|
||||
public int? SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent item navigation property.
|
||||
/// </summary>
|
||||
public BaseItemEntity? Parent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the child item navigation property.
|
||||
/// </summary>
|
||||
public BaseItemEntity? Child { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Jellyfin.Database.Implementations.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// The linked child type.
|
||||
/// </summary>
|
||||
public enum LinkedChildType
|
||||
{
|
||||
/// <summary>
|
||||
/// Manually linked child.
|
||||
/// </summary>
|
||||
Manual = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut linked child.
|
||||
/// </summary>
|
||||
Shortcut = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Local alternate version (same item, different file path).
|
||||
/// </summary>
|
||||
LocalAlternateVersion = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Linked alternate version (different item ID).
|
||||
/// </summary>
|
||||
LinkedAlternateVersion = 3
|
||||
}
|
||||
@@ -143,6 +143,11 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
|
||||
/// </summary>
|
||||
public DbSet<PeopleBaseItemMap> PeopleBaseItemMap => Set<PeopleBaseItemMap>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DbSet{TEntity}"/> containing linked children relationships.
|
||||
/// </summary>
|
||||
public DbSet<LinkedChildEntity> LinkedChildren => Set<LinkedChildEntity>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DbSet{TEntity}"/> containing the referenced Providers with ids.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Jellyfin.Database.Implementations.MatchCriteria;
|
||||
|
||||
/// <summary>
|
||||
/// Base type for folder matching criteria using discriminated union pattern.
|
||||
/// </summary>
|
||||
public abstract record FolderMatchCriteria;
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Jellyfin.Database.Implementations.MatchCriteria;
|
||||
|
||||
/// <summary>
|
||||
/// Matches folders containing descendants with chapter images.
|
||||
/// </summary>
|
||||
public sealed record HasChapterImages : FolderMatchCriteria;
|
||||
@@ -0,0 +1,14 @@
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
|
||||
namespace Jellyfin.Database.Implementations.MatchCriteria;
|
||||
|
||||
/// <summary>
|
||||
/// Matches folders containing descendants with a specific media stream type and language.
|
||||
/// </summary>
|
||||
/// <param name="StreamType">The type of media stream to match (Audio, Subtitle, etc.).</param>
|
||||
/// <param name="Language">The language to match.</param>
|
||||
/// <param name="IsExternal">If not null, filters by internal (false) or external (true) streams. Only applicable to subtitles.</param>
|
||||
public sealed record HasMediaStreamType(
|
||||
MediaStreamTypeEntity StreamType,
|
||||
string Language,
|
||||
bool? IsExternal = null) : FolderMatchCriteria;
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Jellyfin.Database.Implementations.MatchCriteria;
|
||||
|
||||
/// <summary>
|
||||
/// Matches folders containing descendants with subtitles.
|
||||
/// </summary>
|
||||
public sealed record HasSubtitles : FolderMatchCriteria;
|
||||
@@ -28,15 +28,17 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
|
||||
builder.HasMany(e => e.Parents);
|
||||
builder.HasMany(e => e.Children);
|
||||
builder.HasMany(e => e.DirectChildren).WithOne(e => e.DirectParent).HasForeignKey(e => e.ParentId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasMany(e => e.Extras).WithOne(e => e.Owner).HasForeignKey(e => e.OwnerId).OnDelete(DeleteBehavior.NoAction);
|
||||
builder.HasMany(e => e.LockedFields);
|
||||
builder.HasMany(e => e.TrailerTypes);
|
||||
builder.HasMany(e => e.Images);
|
||||
|
||||
builder.HasIndex(e => e.Path);
|
||||
builder.HasIndex(e => e.ParentId);
|
||||
builder.HasIndex(e => e.OwnerId);
|
||||
builder.HasIndex(e => e.Name);
|
||||
builder.HasIndex(e => new { e.ExtraType, e.OwnerId });
|
||||
builder.HasIndex(e => e.PresentationUniqueKey);
|
||||
builder.HasIndex(e => new { e.Id, e.Type, e.IsFolder, e.IsVirtualItem });
|
||||
|
||||
// covering index
|
||||
builder.HasIndex(e => new { e.TopParentId, e.Id });
|
||||
// series
|
||||
@@ -53,14 +55,33 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
|
||||
// latest items
|
||||
builder.HasIndex(e => new { e.Type, e.TopParentId, e.IsVirtualItem, e.PresentationUniqueKey, e.DateCreated });
|
||||
builder.HasIndex(e => new { e.IsFolder, e.TopParentId, e.IsVirtualItem, e.PresentationUniqueKey, e.DateCreated });
|
||||
// latest items - optimized for sorting by DateCreated (no PresentationUniqueKey breaking the sort)
|
||||
builder.HasIndex(e => new { e.TopParentId, e.Type, e.IsVirtualItem, e.DateCreated });
|
||||
builder.HasIndex(e => new { e.TopParentId, e.IsFolder, e.IsVirtualItem, e.DateCreated });
|
||||
builder.HasIndex(e => new { e.TopParentId, e.MediaType, e.IsVirtualItem, e.DateCreated });
|
||||
// resume
|
||||
builder.HasIndex(e => new { e.MediaType, e.TopParentId, e.IsVirtualItem, e.PresentationUniqueKey });
|
||||
// sorted library queries (e.g., Series sorted by SortName)
|
||||
builder.HasIndex(e => new { e.Type, e.TopParentId, e.SortName });
|
||||
// NextUp: per-series episode ordering (index seek + range scan on season/episode)
|
||||
builder.HasIndex(e => new { e.Type, e.SeriesPresentationUniqueKey, e.ParentIndexNumber, e.IndexNumber });
|
||||
// ByName queries: WHERE Type = X AND CleanName IN (...)
|
||||
builder.HasIndex(e => new { e.Type, e.CleanName });
|
||||
// Latest TV: GROUP BY SeriesName
|
||||
builder.HasIndex(e => e.SeriesName);
|
||||
// Latest TV: episode count per season, season count per series
|
||||
builder.HasIndex(e => e.SeasonId);
|
||||
builder.HasIndex(e => e.SeriesId);
|
||||
|
||||
// Items/Counts: SELECT Type, COUNT(*) GROUP BY Type filtered by TopParentId.
|
||||
builder.HasIndex(e => new { e.TopParentId, e.Type, e.IsVirtualItem })
|
||||
.HasFilter("\"PrimaryVersionId\" IS NULL AND (\"OwnerId\" IS NULL OR \"ExtraType\" IS NOT NULL)");
|
||||
|
||||
builder.HasData(new BaseItemEntity()
|
||||
{
|
||||
Id = Guid.Parse("00000000-0000-0000-0000-000000000001"),
|
||||
Type = "PLACEHOLDER",
|
||||
Name = "This is a placeholder item for UserData that has been detacted from its original item",
|
||||
Name = "This is a placeholder item for UserData that has been detached from its original item",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Jellyfin.Database.Implementations.ModelConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// FluentAPI configuration for the BaseItemImageInfo entity.
|
||||
/// </summary>
|
||||
public class BaseItemImageInfoConfiguration : IEntityTypeConfiguration<BaseItemImageInfo>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<BaseItemImageInfo> builder)
|
||||
{
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.HasOne(e => e.Item).WithMany(e => e.Images).HasForeignKey(e => e.ItemId);
|
||||
|
||||
// Composite index for filtering by item and image type (also covers ItemId-only lookups)
|
||||
builder.HasIndex(e => new { e.ItemId, e.ImageType });
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,6 @@ public class BaseItemProviderConfiguration : IEntityTypeConfiguration<BaseItemPr
|
||||
{
|
||||
builder.HasKey(e => new { e.ItemId, e.ProviderId });
|
||||
builder.HasOne(e => e.Item);
|
||||
builder.HasIndex(e => new { e.ProviderId, e.ProviderValue, e.ItemId });
|
||||
builder.HasIndex(e => new { e.ProviderId, e.ItemId, e.ProviderValue });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ namespace Jellyfin.Database.Implementations.ModelConfiguration
|
||||
|
||||
builder
|
||||
.HasIndex(entity => new { entity.UserId, entity.DeviceId });
|
||||
|
||||
builder
|
||||
.HasIndex(entity => entity.DeviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Jellyfin.Database.Implementations.ModelConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// LinkedChildEntity configuration.
|
||||
/// </summary>
|
||||
public class LinkedChildConfiguration : IEntityTypeConfiguration<LinkedChildEntity>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Configure(EntityTypeBuilder<LinkedChildEntity> builder)
|
||||
{
|
||||
builder.ToTable("LinkedChildren");
|
||||
builder.HasKey(e => new { e.ParentId, e.ChildId });
|
||||
builder.HasIndex(e => new { e.ParentId, e.SortOrder });
|
||||
builder.HasIndex(e => new { e.ParentId, e.ChildType });
|
||||
builder.HasIndex(e => new { e.ChildId, e.ChildType });
|
||||
|
||||
builder.HasOne(e => e.Parent)
|
||||
.WithMany(e => e.LinkedChildEntities)
|
||||
.HasForeignKey(e => e.ParentId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
builder.HasOne(e => e.Child)
|
||||
.WithMany(e => e.LinkedChildOfEntities)
|
||||
.HasForeignKey(e => e.ChildId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,5 @@ public class MediaStreamInfoConfiguration : IEntityTypeConfiguration<MediaStream
|
||||
public void Configure(EntityTypeBuilder<MediaStreamInfo> builder)
|
||||
{
|
||||
builder.HasKey(e => new { e.ItemId, e.StreamIndex });
|
||||
builder.HasIndex(e => e.StreamIndex);
|
||||
builder.HasIndex(e => e.StreamType);
|
||||
builder.HasIndex(e => new { e.StreamIndex, e.StreamType });
|
||||
builder.HasIndex(e => new { e.StreamIndex, e.StreamType, e.Language });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class PeopleBaseItemMapConfiguration : IEntityTypeConfiguration<PeopleBas
|
||||
builder.HasKey(e => new { e.ItemId, e.PeopleId, e.Role });
|
||||
builder.HasIndex(e => new { e.ItemId, e.SortOrder });
|
||||
builder.HasIndex(e => new { e.ItemId, e.ListOrder });
|
||||
builder.HasIndex(e => e.PeopleId);
|
||||
builder.HasOne(e => e.Item);
|
||||
builder.HasOne(e => e.People);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ public class UserDataConfiguration : IEntityTypeConfiguration<UserData>
|
||||
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.HasIndex(d => new { d.UserId, d.ItemId, d.LastPlayedDate });
|
||||
builder.HasIndex(d => new { d.UserId, d.Played, d.ItemId });
|
||||
builder.HasIndex(d => new { d.UserId, d.IsFavorite, d.ItemId });
|
||||
builder.HasOne(e => e.Item).WithMany(e => e.UserData);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddLinkedChildrenTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "LinkedChildren",
|
||||
columns: table => new
|
||||
{
|
||||
ParentId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
ChildId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
ChildType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
SortOrder = table.Column<int>(type: "INTEGER", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_LinkedChildren", x => new { x.ParentId, x.ChildId });
|
||||
table.ForeignKey(
|
||||
name: "FK_LinkedChildren_BaseItems_ChildId",
|
||||
column: x => x.ChildId,
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_LinkedChildren_BaseItems_ParentId",
|
||||
column: x => x.ParentId,
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LinkedChildren_ChildId",
|
||||
table: "LinkedChildren",
|
||||
column: "ChildId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LinkedChildren_ChildId_ChildType",
|
||||
table: "LinkedChildren",
|
||||
columns: new[] { "ChildId", "ChildType" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LinkedChildren_ParentId",
|
||||
table: "LinkedChildren",
|
||||
column: "ParentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LinkedChildren_ParentId_ChildType",
|
||||
table: "LinkedChildren",
|
||||
columns: new[] { "ParentId", "ChildType" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LinkedChildren_ParentId_SortOrder",
|
||||
table: "LinkedChildren",
|
||||
columns: new[] { "ParentId", "SortOrder" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Re-populate LinkedChildren data back into the JSON Data column before dropping the table
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET Data = CASE
|
||||
WHEN Data IS NULL OR Data = '' THEN
|
||||
json_object('LinkedChildren', (
|
||||
SELECT json_group_array(
|
||||
json_object(
|
||||
'Path', Child.Path,
|
||||
'Type', CASE LC.ChildType
|
||||
WHEN 0 THEN 'Manual'
|
||||
WHEN 1 THEN 'Shortcut'
|
||||
ELSE 'Manual'
|
||||
END,
|
||||
'ItemId', LOWER(REPLACE(LC.ChildId, '-', ''))
|
||||
)
|
||||
)
|
||||
FROM LinkedChildren LC
|
||||
INNER JOIN BaseItems Child ON LC.ChildId = Child.Id
|
||||
WHERE LC.ParentId = BaseItems.Id
|
||||
ORDER BY LC.SortOrder
|
||||
))
|
||||
ELSE
|
||||
json_set(
|
||||
Data,
|
||||
'$.LinkedChildren',
|
||||
(
|
||||
SELECT json_group_array(
|
||||
json_object(
|
||||
'Path', Child.Path,
|
||||
'Type', CASE LC.ChildType
|
||||
WHEN 0 THEN 'Manual'
|
||||
WHEN 1 THEN 'Shortcut'
|
||||
ELSE 'Manual'
|
||||
END,
|
||||
'ItemId', LOWER(REPLACE(LC.ChildId, '-', ''))
|
||||
)
|
||||
)
|
||||
FROM LinkedChildren LC
|
||||
INNER JOIN BaseItems Child ON LC.ChildId = Child.Id
|
||||
WHERE LC.ParentId = BaseItems.Id
|
||||
ORDER BY LC.SortOrder
|
||||
)
|
||||
)
|
||||
END
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM LinkedChildren LC
|
||||
WHERE LC.ParentId = BaseItems.Id
|
||||
)");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "LinkedChildren");
|
||||
|
||||
migrationBuilder.Sql(
|
||||
@"DELETE FROM __EFMigrationsHistory
|
||||
WHERE MigrationId = '20260113120000_MigrateLinkedChildren'");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChangeOwnerIdToGuid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Normalize OwnerId to uppercase GUID format
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET OwnerId = UPPER(OwnerId)
|
||||
WHERE OwnerId IS NOT NULL");
|
||||
|
||||
// Clear invalid OwnerId values (not 36 characters = not a valid GUID)
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET OwnerId = null
|
||||
WHERE OwnerId IS NOT NULL AND length(OwnerId) != 36");
|
||||
|
||||
// Clear placeholder/empty GUIDs
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "OwnerId",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
column: "OwnerId",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "OwnerId",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
column: "OwnerId",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
columns: new[] { "BaseItemEntityId", "Name", "OwnerId" },
|
||||
values: new object[] { null, "This is a placeholder item for UserData that has been detached from its original item", null });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
column: "BaseItemEntityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_ExtraType",
|
||||
table: "BaseItems",
|
||||
column: "ExtraType");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_ExtraType_OwnerId",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "ExtraType", "OwnerId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_OwnerId",
|
||||
table: "BaseItems",
|
||||
column: "OwnerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_TopParentId_IsFolder_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "TopParentId", "IsFolder", "IsVirtualItem", "DateCreated" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_TopParentId_MediaType_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "TopParentId", "MediaType", "IsVirtualItem", "DateCreated" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_TopParentId_Type_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "TopParentId", "Type", "IsVirtualItem", "DateCreated" });
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
column: "BaseItemEntityId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
column: "OwnerId",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET OwnerId = LOWER(OwnerId)
|
||||
WHERE OwnerId IS NOT NULL");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_ExtraType",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_ExtraType_OwnerId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_OwnerId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_TopParentId_IsFolder_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_TopParentId_MediaType_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_TopParentId_Type_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
columns: new[] { "Name", "OwnerId" },
|
||||
values: new object[] { "This is a placeholder item for UserData that has been detacted from its original item", null });
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddForeignKeyToOwnerId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_OwnerId",
|
||||
table: "BaseItems",
|
||||
column: "OwnerId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_OwnerId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
column: "BaseItemEntityId",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
column: "BaseItemEntityId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
column: "BaseItemEntityId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DropExtraIdsColumn : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ExtraIds",
|
||||
table: "BaseItems");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ExtraIds",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
column: "ExtraIds",
|
||||
value: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddLatestItemsDateCreatedIndexes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserData_UserId",
|
||||
table: "UserData");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserData_UserId_ItemId_LastPlayedDate",
|
||||
table: "UserData",
|
||||
columns: new[] { "UserId", "ItemId", "LastPlayedDate" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserData_UserId_ItemId_LastPlayedDate",
|
||||
table: "UserData");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserData_UserId",
|
||||
table: "UserData",
|
||||
column: "UserId");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddIndicesToImageInfo : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItemImageInfos_ItemId_ImageType",
|
||||
table: "BaseItemImageInfos",
|
||||
columns: new[] { "ItemId", "ImageType" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItemImageInfos_ItemId_ImageType",
|
||||
table: "BaseItemImageInfos");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddBaseItemNameIndex : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserData_UserId_IsFavorite_ItemId",
|
||||
table: "UserData",
|
||||
columns: new[] { "UserId", "IsFavorite", "ItemId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserData_UserId_Played_ItemId",
|
||||
table: "UserData",
|
||||
columns: new[] { "UserId", "Played", "ItemId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_Name",
|
||||
table: "BaseItems",
|
||||
column: "Name");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserData_UserId_IsFavorite_ItemId",
|
||||
table: "UserData");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_UserData_UserId_Played_ItemId",
|
||||
table: "UserData");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_Name",
|
||||
table: "BaseItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,171 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class IndexOptimizations : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MediaStreamInfos_StreamIndex",
|
||||
table: "MediaStreamInfos");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MediaStreamInfos_StreamIndex_StreamType",
|
||||
table: "MediaStreamInfos");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MediaStreamInfos_StreamIndex_StreamType_Language",
|
||||
table: "MediaStreamInfos");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MediaStreamInfos_StreamType",
|
||||
table: "MediaStreamInfos");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_LinkedChildren_ChildId",
|
||||
table: "LinkedChildren");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_LinkedChildren_ParentId",
|
||||
table: "LinkedChildren");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Devices_DeviceId",
|
||||
table: "Devices");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_ExtraType",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_Id_Type_IsFolder_IsVirtualItem",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItemProviders_ProviderId_ProviderValue_ItemId",
|
||||
table: "BaseItemProviders");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItemImageInfos_ItemId",
|
||||
table: "BaseItemImageInfos");
|
||||
|
||||
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_SeriesName",
|
||||
table: "BaseItems",
|
||||
column: "SeriesName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_Type_SeriesPresentationUniqueKey_ParentIndexNumber_IndexNumber",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "Type", "SeriesPresentationUniqueKey", "ParentIndexNumber", "IndexNumber" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_Type_TopParentId_SortName",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "Type", "TopParentId", "SortName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItemProviders_ProviderId_ItemId_ProviderValue",
|
||||
table: "BaseItemProviders",
|
||||
columns: new[] { "ProviderId", "ItemId", "ProviderValue" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_SeasonId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_SeriesId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_SeriesName",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_Type_SeriesPresentationUniqueKey_ParentIndexNumber_IndexNumber",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_Type_TopParentId_SortName",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItemProviders_ProviderId_ItemId_ProviderValue",
|
||||
table: "BaseItemProviders");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MediaStreamInfos_StreamIndex",
|
||||
table: "MediaStreamInfos",
|
||||
column: "StreamIndex");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MediaStreamInfos_StreamIndex_StreamType",
|
||||
table: "MediaStreamInfos",
|
||||
columns: new[] { "StreamIndex", "StreamType" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MediaStreamInfos_StreamIndex_StreamType_Language",
|
||||
table: "MediaStreamInfos",
|
||||
columns: new[] { "StreamIndex", "StreamType", "Language" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MediaStreamInfos_StreamType",
|
||||
table: "MediaStreamInfos",
|
||||
column: "StreamType");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LinkedChildren_ChildId",
|
||||
table: "LinkedChildren",
|
||||
column: "ChildId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LinkedChildren_ParentId",
|
||||
table: "LinkedChildren",
|
||||
column: "ParentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Devices_DeviceId",
|
||||
table: "Devices",
|
||||
column: "DeviceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_ExtraType",
|
||||
table: "BaseItems",
|
||||
column: "ExtraType");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_Id_Type_IsFolder_IsVirtualItem",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "Id", "Type", "IsFolder", "IsVirtualItem" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItemProviders_ProviderId_ProviderValue_ItemId",
|
||||
table: "BaseItemProviders",
|
||||
columns: new[] { "ProviderId", "ProviderValue", "ItemId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItemImageInfos_ItemId",
|
||||
table: "BaseItemImageInfos",
|
||||
column: "ItemId");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChangePrimaryVersionIdToGuid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Convert "N" format (32 chars, no hyphens) to standard GUID format (36 chars with hyphens)
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = UPPER(
|
||||
SUBSTR(PrimaryVersionId,1,8)||'-'||
|
||||
SUBSTR(PrimaryVersionId,9,4)||'-'||
|
||||
SUBSTR(PrimaryVersionId,13,4)||'-'||
|
||||
SUBSTR(PrimaryVersionId,17,4)||'-'||
|
||||
SUBSTR(PrimaryVersionId,21,12))
|
||||
WHERE PrimaryVersionId IS NOT NULL AND LENGTH(PrimaryVersionId) = 32");
|
||||
|
||||
// Normalize existing standard-format values to uppercase
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = UPPER(PrimaryVersionId)
|
||||
WHERE PrimaryVersionId IS NOT NULL");
|
||||
|
||||
// Clear invalid values (not 36 characters = not a valid GUID)
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = NULL
|
||||
WHERE PrimaryVersionId IS NOT NULL AND LENGTH(PrimaryVersionId) != 36");
|
||||
|
||||
// Clear placeholder/empty GUIDs
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = NULL
|
||||
WHERE PrimaryVersionId = '00000000-0000-0000-0000-000000000000'");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Convert standard GUID format back to "N" format (remove hyphens, lowercase)
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET PrimaryVersionId = LOWER(REPLACE(PrimaryVersionId, '-', ''))
|
||||
WHERE PrimaryVersionId IS NOT NULL");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddTypeCleanNameIndex : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_Type_CleanName",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "Type", "CleanName" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_Type_CleanName",
|
||||
table: "BaseItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPartialIndexForItemCounts : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_TopParentId_Type_IsVirtualItem",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "TopParentId", "Type", "IsVirtualItem" },
|
||||
filter: "\"PrimaryVersionId\" IS NULL AND (\"OwnerId\" IS NULL OR \"ExtraType\" IS NOT NULL)");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_TopParentId_Type_IsVirtualItem",
|
||||
table: "BaseItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.AccessSchedule", b =>
|
||||
{
|
||||
@@ -207,9 +207,6 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Property<string>("ExternalServiceId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ExtraIds")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ExtraType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@@ -273,7 +270,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("OwnerId")
|
||||
b.Property<Guid?>("OwnerId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("ParentId")
|
||||
@@ -297,7 +294,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Property<string>("PresentationUniqueKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PrimaryVersionId")
|
||||
b.Property<Guid?>("PrimaryVersionId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProductionLocations")
|
||||
@@ -363,26 +360,51 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.HasIndex("Path");
|
||||
|
||||
b.HasIndex("PresentationUniqueKey");
|
||||
|
||||
b.HasIndex("SeasonId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.HasIndex("SeriesName");
|
||||
|
||||
b.HasIndex("ExtraType", "OwnerId");
|
||||
|
||||
b.HasIndex("TopParentId", "Id");
|
||||
|
||||
b.HasIndex("Type", "CleanName");
|
||||
|
||||
b.HasIndex("TopParentId", "Type", "IsVirtualItem")
|
||||
.HasFilter("\"PrimaryVersionId\" IS NULL AND (\"OwnerId\" IS NULL OR \"ExtraType\" IS NOT NULL)");
|
||||
|
||||
b.HasIndex("Type", "TopParentId", "Id");
|
||||
|
||||
b.HasIndex("Type", "TopParentId", "PresentationUniqueKey");
|
||||
|
||||
b.HasIndex("Type", "TopParentId", "StartDate");
|
||||
b.HasIndex("Type", "TopParentId", "SortName");
|
||||
|
||||
b.HasIndex("Id", "Type", "IsFolder", "IsVirtualItem");
|
||||
b.HasIndex("Type", "TopParentId", "StartDate");
|
||||
|
||||
b.HasIndex("MediaType", "TopParentId", "IsVirtualItem", "PresentationUniqueKey");
|
||||
|
||||
b.HasIndex("TopParentId", "IsFolder", "IsVirtualItem", "DateCreated");
|
||||
|
||||
b.HasIndex("TopParentId", "MediaType", "IsVirtualItem", "DateCreated");
|
||||
|
||||
b.HasIndex("TopParentId", "Type", "IsVirtualItem", "DateCreated");
|
||||
|
||||
b.HasIndex("Type", "SeriesPresentationUniqueKey", "IsFolder", "IsVirtualItem");
|
||||
|
||||
b.HasIndex("Type", "SeriesPresentationUniqueKey", "ParentIndexNumber", "IndexNumber");
|
||||
|
||||
b.HasIndex("Type", "SeriesPresentationUniqueKey", "PresentationUniqueKey", "SortName");
|
||||
|
||||
b.HasIndex("IsFolder", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated");
|
||||
@@ -404,7 +426,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
IsRepeat = false,
|
||||
IsSeries = false,
|
||||
IsVirtualItem = false,
|
||||
Name = "This is a placeholder item for UserData that has been detacted from its original item",
|
||||
Name = "This is a placeholder item for UserData that has been detached from its original item",
|
||||
Type = "PLACEHOLDER"
|
||||
});
|
||||
});
|
||||
@@ -439,7 +461,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ItemId");
|
||||
b.HasIndex("ItemId", "ImageType");
|
||||
|
||||
b.ToTable("BaseItemImageInfos");
|
||||
|
||||
@@ -477,7 +499,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasKey("ItemId", "ProviderId");
|
||||
|
||||
b.HasIndex("ProviderId", "ProviderValue", "ItemId");
|
||||
b.HasIndex("ProviderId", "ItemId", "ProviderValue");
|
||||
|
||||
b.ToTable("BaseItemProviders");
|
||||
|
||||
@@ -782,6 +804,33 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.HasAnnotation("Sqlite:UseSqlReturningClause", false);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.LinkedChildEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("ParentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ChildId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ChildType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ParentId", "ChildId");
|
||||
|
||||
b.HasIndex("ChildId", "ChildType");
|
||||
|
||||
b.HasIndex("ParentId", "ChildType");
|
||||
|
||||
b.HasIndex("ParentId", "SortOrder");
|
||||
|
||||
b.ToTable("LinkedChildren", (string)null);
|
||||
|
||||
b.HasAnnotation("Sqlite:UseSqlReturningClause", false);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.MediaSegment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -956,14 +1005,6 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasKey("ItemId", "StreamIndex");
|
||||
|
||||
b.HasIndex("StreamIndex");
|
||||
|
||||
b.HasIndex("StreamType");
|
||||
|
||||
b.HasIndex("StreamIndex", "StreamType");
|
||||
|
||||
b.HasIndex("StreamIndex", "StreamType", "Language");
|
||||
|
||||
b.ToTable("MediaStreamInfos");
|
||||
|
||||
b.HasAnnotation("Sqlite:UseSqlReturningClause", false);
|
||||
@@ -1167,8 +1208,6 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.HasIndex("AccessToken", "DateLastActivity");
|
||||
|
||||
b.HasIndex("DeviceId", "DateLastActivity");
|
||||
@@ -1396,8 +1435,6 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasKey("ItemId", "UserId", "CustomDataKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("ItemId", "UserId", "IsFavorite");
|
||||
|
||||
b.HasIndex("ItemId", "UserId", "LastPlayedDate");
|
||||
@@ -1406,6 +1443,12 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasIndex("ItemId", "UserId", "Played");
|
||||
|
||||
b.HasIndex("UserId", "IsFavorite", "ItemId");
|
||||
|
||||
b.HasIndex("UserId", "ItemId", "LastPlayedDate");
|
||||
|
||||
b.HasIndex("UserId", "Played", "ItemId");
|
||||
|
||||
b.ToTable("UserData");
|
||||
|
||||
b.HasAnnotation("Sqlite:UseSqlReturningClause", false);
|
||||
@@ -1452,12 +1495,19 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemEntity", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Database.Implementations.Entities.BaseItemEntity", "Owner")
|
||||
.WithMany("Extras")
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
b.HasOne("Jellyfin.Database.Implementations.Entities.BaseItemEntity", "DirectParent")
|
||||
.WithMany("DirectChildren")
|
||||
.HasForeignKey("ParentId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("DirectParent");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemImageInfo", b =>
|
||||
@@ -1580,6 +1630,25 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Navigation("Item");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.LinkedChildEntity", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Database.Implementations.Entities.BaseItemEntity", "Child")
|
||||
.WithMany("LinkedChildOfEntities")
|
||||
.HasForeignKey("ChildId")
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jellyfin.Database.Implementations.Entities.BaseItemEntity", "Parent")
|
||||
.WithMany("LinkedChildEntities")
|
||||
.HasForeignKey("ParentId")
|
||||
.OnDelete(DeleteBehavior.NoAction)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Child");
|
||||
|
||||
b.Navigation("Parent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.MediaStreamInfo", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Database.Implementations.Entities.BaseItemEntity", "Item")
|
||||
@@ -1664,10 +1733,16 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.Navigation("DirectChildren");
|
||||
|
||||
b.Navigation("Extras");
|
||||
|
||||
b.Navigation("Images");
|
||||
|
||||
b.Navigation("ItemValues");
|
||||
|
||||
b.Navigation("LinkedChildEntities");
|
||||
|
||||
b.Navigation("LinkedChildOfEntities");
|
||||
|
||||
b.Navigation("LockedFields");
|
||||
|
||||
b.Navigation("MediaStreams");
|
||||
|
||||
@@ -60,11 +60,13 @@ public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
|
||||
|
||||
var customOptions = databaseConfiguration.CustomProviderOptions?.Options;
|
||||
|
||||
var sqliteConnectionBuilder = new SqliteConnectionStringBuilder();
|
||||
sqliteConnectionBuilder.DataSource = GetOption(customOptions, "path", e => e, () => Path.Combine(_applicationPaths.DataPath, "jellyfin.db"));
|
||||
sqliteConnectionBuilder.Cache = GetOption(customOptions, "cache", Enum.Parse<SqliteCacheMode>, () => SqliteCacheMode.Default);
|
||||
sqliteConnectionBuilder.Pooling = GetOption(customOptions, "pooling", e => e.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase), () => true);
|
||||
sqliteConnectionBuilder.DefaultTimeout = GetOption(customOptions, "command-timeout", int.Parse, () => 30);
|
||||
var sqliteConnectionBuilder = new SqliteConnectionStringBuilder
|
||||
{
|
||||
DataSource = GetOption(customOptions, "path", e => e, () => Path.Combine(_applicationPaths.DataPath, "jellyfin.db")),
|
||||
Cache = GetOption(customOptions, "cache", Enum.Parse<SqliteCacheMode>, () => SqliteCacheMode.Default),
|
||||
Pooling = GetOption(customOptions, "pooling", e => e.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase), () => true),
|
||||
DefaultTimeout = GetOption(customOptions, "command-timeout", int.Parse, () => 60)
|
||||
};
|
||||
|
||||
var connectionString = sqliteConnectionBuilder.ToString();
|
||||
|
||||
@@ -77,7 +79,8 @@ public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
|
||||
sqLiteOptions => sqLiteOptions.MigrationsAssembly(GetType().Assembly))
|
||||
// TODO: Remove when https://github.com/dotnet/efcore/pull/35873 is merged & released
|
||||
.ConfigureWarnings(warnings =>
|
||||
warnings.Ignore(RelationalEventId.NonTransactionalMigrationOperationWarning))
|
||||
warnings.Ignore(RelationalEventId.NonTransactionalMigrationOperationWarning)
|
||||
.Ignore(RelationalEventId.MultipleCollectionIncludeWarning))
|
||||
.AddInterceptors(new PragmaConnectionInterceptor(
|
||||
_logger,
|
||||
GetOption<int?>(customOptions, "cacheSize", e => int.Parse(e, CultureInfo.InvariantCulture)),
|
||||
@@ -155,7 +158,7 @@ public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
|
||||
/// <inheritdoc />
|
||||
public Task RestoreBackupFast(string key, CancellationToken cancellationToken)
|
||||
{
|
||||
// ensure there are absolutly no dangling Sqlite connections.
|
||||
// ensure there are absolutely no dangling Sqlite connections.
|
||||
SqliteConnection.ClearAllPools();
|
||||
var path = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
|
||||
var backupFile = Path.Combine(_applicationPaths.DataPath, BackupFolderName, $"{key}_jellyfin.db");
|
||||
|
||||
@@ -17,7 +17,7 @@ with SQLite currently being the only supported provider, you need to run the Ent
|
||||
The example is made from the root folder of the project e.g for codespaces `/workspaces/jellyfin`
|
||||
|
||||
```cmd
|
||||
dotnet ef migrations add {MIGRATION_NAME} --project "src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite" -- --migration-provider Jellyfin-SQLite
|
||||
dotnet ef migrations add {MIGRATION_NAME} --project "src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite" --output-dir Migrations -- --migration-provider Jellyfin-SQLite
|
||||
```
|
||||
|
||||
If you get the error: `Run "dotnet tool restore" to make the "dotnet-ef" command available.` Run `dotnet restore`.
|
||||
|
||||
@@ -148,5 +148,30 @@ namespace Jellyfin.Extensions
|
||||
{
|
||||
return string.IsNullOrEmpty(text) ? text : text.AsSpan().LeftPart('\0').ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a string for comparison by removing diacritics, converting to lowercase,
|
||||
/// replacing punctuation/special characters with spaces, and collapsing whitespace.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to normalize.</param>
|
||||
/// <returns>The normalized string, or the original if null/whitespace.</returns>
|
||||
public static string GetCleanValue(this string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
// Remove diacritics and convert to lowercase
|
||||
var cleaned = value.RemoveDiacritics().ToLowerInvariant();
|
||||
|
||||
// Replace all punctuation and special characters with spaces
|
||||
cleaned = Regex.Replace(cleaned, @"[^\p{L}\p{N}\s]", " ");
|
||||
|
||||
// Collapse multiple spaces into single space and trim
|
||||
cleaned = Regex.Replace(cleaned, @"\s+", " ").Trim();
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator
|
||||
{
|
||||
if (keyframeData.KeyframeTicks.Count > 0 && keyframeData.TotalDuration < keyframeData.KeyframeTicks[^1])
|
||||
{
|
||||
throw new ArgumentException("Invalid duration in keyframe data", nameof(keyframeData));
|
||||
keyframeData = new KeyframeData(keyframeData.KeyframeTicks[^1], keyframeData.KeyframeTicks);
|
||||
}
|
||||
|
||||
long lastKeyframe = 0;
|
||||
@@ -176,7 +176,12 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(TimeSpan.FromTicks(keyframeData.TotalDuration - lastKeyframe).TotalSeconds);
|
||||
var remaining = keyframeData.TotalDuration - lastKeyframe;
|
||||
if (remaining > 0)
|
||||
{
|
||||
result.Add(TimeSpan.FromTicks(remaining).TotalSeconds);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user