aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-03-07 20:12:42 +0100
committerShadowghost <Ghost_of_Stone@web.de>2026-03-07 20:12:42 +0100
commit077fa89717957f871b172ca4b2dc4a178efd3bc5 (patch)
tree1c2be0089b3c33cda1ed96bde4b76a715a845df7 /Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs
parent268f23f39ac18e783156b91b575ee6a105b6937c (diff)
Split BaseItemRepository and IItemRepository
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs')
-rw-r--r--Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs160
1 files changed, 160 insertions, 0 deletions
diff --git a/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs b/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs
new file mode 100644
index 0000000000..19afe04f01
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs
@@ -0,0 +1,160 @@
+#pragma warning disable RS0030 // Do not use banned APIs
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Persistence;
+using Microsoft.EntityFrameworkCore;
+using DbLinkedChildType = Jellyfin.Database.Implementations.Entities.LinkedChildType;
+using LinkedChildType = MediaBrowser.Controller.Entities.LinkedChildType;
+
+namespace Jellyfin.Server.Implementations.Item;
+
+/// <summary>
+/// Provides linked children query and manipulation operations.
+/// </summary>
+public class LinkedChildrenService : ILinkedChildrenService
+{
+ private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
+ private readonly IItemTypeLookup _itemTypeLookup;
+ private readonly IItemQueryHelpers _queryHelpers;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LinkedChildrenService"/> class.
+ /// </summary>
+ /// <param name="dbProvider">The database context factory.</param>
+ /// <param name="itemTypeLookup">The item type lookup.</param>
+ /// <param name="queryHelpers">The shared query helpers.</param>
+ public LinkedChildrenService(
+ IDbContextFactory<JellyfinDbContext> dbProvider,
+ IItemTypeLookup itemTypeLookup,
+ IItemQueryHelpers queryHelpers)
+ {
+ _dbProvider = dbProvider;
+ _itemTypeLookup = itemTypeLookup;
+ _queryHelpers = queryHelpers;
+ }
+
+ /// <inheritdoc/>
+ public IReadOnlyList<Guid> GetLinkedChildrenIds(Guid parentId, int? childType = null)
+ {
+ using var dbContext = _dbProvider.CreateDbContext();
+
+ var query = dbContext.LinkedChildren
+ .Where(lc => lc.ParentId.Equals(parentId));
+
+ if (childType.HasValue)
+ {
+ query = query.Where(lc => (int)lc.ChildType == childType.Value);
+ }
+
+ return query
+ .OrderBy(lc => lc.SortOrder)
+ .Select(lc => lc.ChildId)
+ .ToList();
+ }
+
+ /// <inheritdoc/>
+ public IReadOnlyDictionary<string, MusicArtist[]> FindArtists(IReadOnlyList<string> artistNames)
+ {
+ using var dbContext = _dbProvider.CreateDbContext();
+
+ var artists = dbContext.BaseItems.Where(e => e.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!)
+ .Where(e => artistNames.Contains(e.Name))
+ .ToArray();
+
+ var lookup = artists
+ .GroupBy(e => e.Name!)
+ .ToDictionary(
+ g => g.Key,
+ g => g.Select(f => _queryHelpers.DeserializeBaseItem(f)).Where(dto => dto is not null).Cast<MusicArtist>().ToArray());
+
+ var result = new Dictionary<string, MusicArtist[]>(artistNames.Count);
+ foreach (var name in artistNames)
+ {
+ if (lookup.TryGetValue(name, out var artistArray))
+ {
+ result[name] = artistArray;
+ }
+ }
+
+ return result;
+ }
+
+ /// <inheritdoc/>
+ public IReadOnlyList<Guid> GetManualLinkedParentIds(Guid childId)
+ {
+ using var context = _dbProvider.CreateDbContext();
+ return context.LinkedChildren
+ .Where(lc => lc.ChildId == childId && lc.ChildType == DbLinkedChildType.Manual)
+ .Select(lc => lc.ParentId)
+ .Distinct()
+ .ToList();
+ }
+
+ /// <inheritdoc/>
+ public IReadOnlyList<Guid> RerouteLinkedChildren(Guid fromChildId, Guid toChildId)
+ {
+ using var context = _dbProvider.CreateDbContext();
+
+ var affectedParentIds = context.LinkedChildren
+ .Where(lc => lc.ChildId == fromChildId && lc.ChildType == DbLinkedChildType.Manual)
+ .Select(lc => lc.ParentId)
+ .Distinct()
+ .ToList();
+
+ if (affectedParentIds.Count == 0)
+ {
+ return affectedParentIds;
+ }
+
+ var parentsWithTarget = context.LinkedChildren
+ .Where(lc => lc.ChildId == toChildId && lc.ChildType == DbLinkedChildType.Manual)
+ .Select(lc => lc.ParentId)
+ .ToHashSet();
+
+ context.LinkedChildren
+ .Where(lc => lc.ChildId == fromChildId
+ && lc.ChildType == DbLinkedChildType.Manual
+ && !parentsWithTarget.Contains(lc.ParentId))
+ .ExecuteUpdate(s => s.SetProperty(e => e.ChildId, toChildId));
+
+ context.LinkedChildren
+ .Where(lc => lc.ChildId == fromChildId
+ && lc.ChildType == DbLinkedChildType.Manual
+ && parentsWithTarget.Contains(lc.ParentId))
+ .ExecuteDelete();
+
+ return affectedParentIds;
+ }
+
+ /// <inheritdoc/>
+ public void UpsertLinkedChild(Guid parentId, Guid childId, LinkedChildType childType)
+ {
+ using var context = _dbProvider.CreateDbContext();
+
+ var dbChildType = (DbLinkedChildType)childType;
+ var existingLink = context.LinkedChildren
+ .FirstOrDefault(lc => lc.ParentId == parentId && lc.ChildId == childId);
+
+ if (existingLink is null)
+ {
+ context.LinkedChildren.Add(new Jellyfin.Database.Implementations.Entities.LinkedChildEntity
+ {
+ ParentId = parentId,
+ ChildId = childId,
+ ChildType = dbChildType,
+ SortOrder = null
+ });
+ }
+ else
+ {
+ existingLink.ChildType = dbChildType;
+ }
+
+ context.SaveChanges();
+ }
+}