< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Item.LinkedChildrenService
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs
Line coverage
26%
Covered lines: 21
Uncovered lines: 58
Coverable lines: 79
Total lines: 166
Line coverage: 26.5%
Branch coverage
20%
Covered branches: 2
Total branches: 10
Branch coverage: 20%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/4/2026 - 12:15:16 AM Line coverage: 24.6% (19/77) Branch coverage: 20% (2/10) Total lines: 1625/11/2026 - 12:15:59 AM Line coverage: 26.5% (21/79) Branch coverage: 20% (2/10) Total lines: 166 5/4/2026 - 12:15:16 AM Line coverage: 24.6% (19/77) Branch coverage: 20% (2/10) Total lines: 1625/11/2026 - 12:15:59 AM Line coverage: 26.5% (21/79) Branch coverage: 20% (2/10) Total lines: 166

Coverage delta

Coverage delta 2 -2

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetLinkedChildrenIds(...)0%620%
FindArtists(...)50%4489.47%
GetManualLinkedParentIds(...)100%210%
RerouteLinkedChildren(...)0%620%
UpsertLinkedChild(...)0%620%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Item/LinkedChildrenService.cs

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2#pragma warning disable CA1304 // Specify CultureInfo
 3#pragma warning disable CA1311 // Specify a culture or use an invariant version
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Linq;
 8using Jellyfin.Data.Enums;
 9using Jellyfin.Database.Implementations;
 10using MediaBrowser.Controller.Entities.Audio;
 11using MediaBrowser.Controller.Persistence;
 12using Microsoft.EntityFrameworkCore;
 13using DbLinkedChildType = Jellyfin.Database.Implementations.Entities.LinkedChildType;
 14using LinkedChildType = MediaBrowser.Controller.Entities.LinkedChildType;
 15
 16namespace Jellyfin.Server.Implementations.Item;
 17
 18/// <summary>
 19/// Provides linked children query and manipulation operations.
 20/// </summary>
 21public class LinkedChildrenService : ILinkedChildrenService
 22{
 23    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 24    private readonly IItemTypeLookup _itemTypeLookup;
 25    private readonly IItemQueryHelpers _queryHelpers;
 26
 27    /// <summary>
 28    /// Initializes a new instance of the <see cref="LinkedChildrenService"/> class.
 29    /// </summary>
 30    /// <param name="dbProvider">The database context factory.</param>
 31    /// <param name="itemTypeLookup">The item type lookup.</param>
 32    /// <param name="queryHelpers">The shared query helpers.</param>
 33    public LinkedChildrenService(
 34        IDbContextFactory<JellyfinDbContext> dbProvider,
 35        IItemTypeLookup itemTypeLookup,
 36        IItemQueryHelpers queryHelpers)
 37    {
 2138        _dbProvider = dbProvider;
 2139        _itemTypeLookup = itemTypeLookup;
 2140        _queryHelpers = queryHelpers;
 2141    }
 42
 43    /// <inheritdoc/>
 44    public IReadOnlyList<Guid> GetLinkedChildrenIds(Guid parentId, int? childType = null)
 45    {
 046        using var dbContext = _dbProvider.CreateDbContext();
 47
 048        var query = dbContext.LinkedChildren
 049            .Where(lc => lc.ParentId.Equals(parentId));
 50
 051        if (childType.HasValue)
 52        {
 053            query = query.Where(lc => (int)lc.ChildType == childType.Value);
 54        }
 55
 056        return query
 057            .OrderBy(lc => lc.SortOrder)
 058            .Select(lc => lc.ChildId)
 059            .ToArray();
 060    }
 61
 62    /// <inheritdoc/>
 63    public IReadOnlyDictionary<string, MusicArtist[]> FindArtists(IReadOnlyList<string> artistNames)
 64    {
 1765        using var dbContext = _dbProvider.CreateDbContext();
 66
 1767        var lowerNames = artistNames.Select(n => n.ToLowerInvariant()).ToArray();
 1768        var artists = dbContext.BaseItems
 1769            .AsNoTracking()
 1770            .Where(e => e.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!)
 1771            .Where(e => lowerNames.Contains(e.Name!.ToLower()))
 1772            .ToArray();
 73
 1774        var lookup = artists
 1775            .GroupBy(e => e.Name!, StringComparer.OrdinalIgnoreCase)
 1776            .ToDictionary(
 1777                g => g.Key,
 1778                g => g.Select(f => _queryHelpers.DeserializeBaseItem(f)).Where(dto => dto is not null).Cast<MusicArtist>
 1779                StringComparer.OrdinalIgnoreCase);
 80
 1781        var result = new Dictionary<string, MusicArtist[]>(artistNames.Count);
 3482        foreach (var name in artistNames)
 83        {
 084            if (lookup.TryGetValue(name, out var artistArray))
 85            {
 086                result[name] = artistArray;
 87            }
 88        }
 89
 1790        return result;
 1791    }
 92
 93    /// <inheritdoc/>
 94    public IReadOnlyList<Guid> GetManualLinkedParentIds(Guid childId)
 95    {
 096        using var context = _dbProvider.CreateDbContext();
 097        return context.LinkedChildren
 098            .Where(lc => lc.ChildId == childId && lc.ChildType == DbLinkedChildType.Manual)
 099            .Select(lc => lc.ParentId)
 0100            .Distinct()
 0101            .ToList();
 0102    }
 103
 104    /// <inheritdoc/>
 105    public IReadOnlyList<Guid> RerouteLinkedChildren(Guid fromChildId, Guid toChildId)
 106    {
 0107        using var context = _dbProvider.CreateDbContext();
 108
 0109        var affectedParentIds = context.LinkedChildren
 0110            .Where(lc => lc.ChildId == fromChildId && lc.ChildType == DbLinkedChildType.Manual)
 0111            .Select(lc => lc.ParentId)
 0112            .Distinct()
 0113            .ToList();
 114
 0115        if (affectedParentIds.Count == 0)
 116        {
 0117            return affectedParentIds;
 118        }
 119
 0120        var parentsWithTarget = context.LinkedChildren
 0121            .Where(lc => lc.ChildId == toChildId && lc.ChildType == DbLinkedChildType.Manual)
 0122            .Select(lc => lc.ParentId)
 0123            .ToHashSet();
 124
 0125        context.LinkedChildren
 0126            .Where(lc => lc.ChildId == fromChildId
 0127                && lc.ChildType == DbLinkedChildType.Manual
 0128                && !parentsWithTarget.Contains(lc.ParentId))
 0129            .ExecuteUpdate(s => s.SetProperty(e => e.ChildId, toChildId));
 130
 0131        context.LinkedChildren
 0132            .Where(lc => lc.ChildId == fromChildId
 0133                && lc.ChildType == DbLinkedChildType.Manual
 0134                && parentsWithTarget.Contains(lc.ParentId))
 0135            .ExecuteDelete();
 136
 0137        return affectedParentIds;
 0138    }
 139
 140    /// <inheritdoc/>
 141    public void UpsertLinkedChild(Guid parentId, Guid childId, LinkedChildType childType)
 142    {
 0143        using var context = _dbProvider.CreateDbContext();
 144
 0145        var dbChildType = (DbLinkedChildType)childType;
 0146        var existingLink = context.LinkedChildren
 0147            .FirstOrDefault(lc => lc.ParentId == parentId && lc.ChildId == childId);
 148
 0149        if (existingLink is null)
 150        {
 0151            context.LinkedChildren.Add(new Jellyfin.Database.Implementations.Entities.LinkedChildEntity
 0152            {
 0153                ParentId = parentId,
 0154                ChildId = childId,
 0155                ChildType = dbChildType,
 0156                SortOrder = null
 0157            });
 158        }
 159        else
 160        {
 0161            existingLink.ChildType = dbChildType;
 162        }
 163
 0164        context.SaveChanges();
 0165    }
 166}