< Summary - Jellyfin

Information
Class: Jellyfin.Server.Migrations.Routines.EnableLocalSimilarityProviders
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/20260831100000_EnableLocalSimilarityProviders.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 47
Coverable lines: 47
Total lines: 146
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 34
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 10/29/2026 - 12:17:11 AM Line coverage: 0% (0/47) Branch coverage: 0% (0/34) Total lines: 146 10/29/2026 - 12:17:11 AM Line coverage: 0% (0/47) Branch coverage: 0% (0/34) Total lines: 146

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
PerformAsync(...)0%2040%
EnableLocalProviders(...)0%210140%
EnableLocalProviders(...)0%156120%
GetLocalProvidersByItemType()0%2040%

File(s)

/srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/20260831100000_EnableLocalSimilarityProviders.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Threading;
 5using System.Threading.Tasks;
 6using Jellyfin.Server.Migrations.Stages;
 7using Jellyfin.Server.ServerSetupApp;
 8using MediaBrowser.Controller.Entities;
 9using MediaBrowser.Controller.Library;
 10using MediaBrowser.Controller.Providers;
 11using MediaBrowser.Model.Configuration;
 12using MediaBrowser.Model.Entities;
 13using Microsoft.Extensions.Logging;
 14
 15namespace Jellyfin.Server.Migrations.Routines;
 16
 17/// <summary>
 18/// Enables the local similarity providers on libraries that predate the similar items settings.
 19/// </summary>
 20[JellyfinMigration("2026-08-31T10:00:00", nameof(EnableLocalSimilarityProviders), Stage = JellyfinMigrationStageTypes.Ap
 21internal class EnableLocalSimilarityProviders : IAsyncMigrationRoutine
 22{
 23    private readonly ILibraryManager _libraryManager;
 24    private readonly IProviderManager _providerManager;
 25    private readonly ILogger _logger;
 26
 27    /// <summary>
 28    /// Initializes a new instance of the <see cref="EnableLocalSimilarityProviders"/> class.
 29    /// </summary>
 30    /// <param name="libraryManager">The library manager.</param>
 31    /// <param name="providerManager">The provider manager.</param>
 32    /// <param name="startupLogger">The startup logger for Startup UI integration.</param>
 33    /// <param name="logger">The logger.</param>
 34    public EnableLocalSimilarityProviders(
 35        ILibraryManager libraryManager,
 36        IProviderManager providerManager,
 37        IStartupLogger<EnableLocalSimilarityProviders> startupLogger,
 38        ILogger<EnableLocalSimilarityProviders> logger)
 39    {
 040        _libraryManager = libraryManager;
 041        _providerManager = providerManager;
 042        _logger = startupLogger.With(logger);
 043    }
 44
 45    /// <inheritdoc />
 46    public Task PerformAsync(CancellationToken cancellationToken)
 47    {
 48        // Libraries created before similar items became configurable have an empty provider list,
 49        // which the library editor renders as "everything unchecked" instead of falling back to the
 50        // defaults it uses for new libraries. Seed the local providers so they stay enabled.
 051        var localProvidersByType = GetLocalProvidersByItemType();
 052        if (localProvidersByType.Count == 0)
 53        {
 054            return Task.CompletedTask;
 55        }
 56
 057        foreach (var virtualFolder in _libraryManager.GetVirtualFolders(false))
 58        {
 059            cancellationToken.ThrowIfCancellationRequested();
 60
 061            EnableLocalProviders(virtualFolder, localProvidersByType);
 62        }
 63
 064        return Task.CompletedTask;
 65    }
 66
 67    private void EnableLocalProviders(VirtualFolderInfo virtualFolder, Dictionary<string, string[]> localProvidersByType
 68    {
 069        var options = virtualFolder.LibraryOptions;
 070        if (options?.TypeOptions is null || options.TypeOptions.Length == 0)
 71        {
 072            return;
 73        }
 74
 75        // Some virtual folders don't have a proper item id.
 076        if (!Guid.TryParse(virtualFolder.ItemId, out var folderId))
 77        {
 078            return;
 79        }
 80
 081        var collectionFolder = _libraryManager.GetItemById<CollectionFolder>(folderId);
 082        if (collectionFolder is null)
 83        {
 084            _logger.LogWarning("Could not find collection folder for virtual folder '{LibraryName}' with id '{FolderId}'
 085            return;
 86        }
 87
 088        var changed = false;
 089        foreach (var typeOptions in options.TypeOptions)
 90        {
 091            changed |= EnableLocalProviders(typeOptions, localProvidersByType, virtualFolder.Name);
 92        }
 93
 094        if (changed)
 95        {
 096            collectionFolder.UpdateLibraryOptions(options);
 97        }
 098    }
 99
 100    private bool EnableLocalProviders(TypeOptions typeOptions, Dictionary<string, string[]> localProvidersByType, string
 101    {
 0102        if (typeOptions.Type is null || !localProvidersByType.TryGetValue(typeOptions.Type, out var localProviders))
 103        {
 0104            return false;
 105        }
 106
 0107        var enabled = typeOptions.SimilarItemProviders ?? [];
 0108        var missing = localProviders.Where(name => !enabled.Contains(name, StringComparer.OrdinalIgnoreCase)).ToArray();
 0109        if (missing.Length == 0)
 110        {
 0111            return false;
 112        }
 113
 114        // Local providers rank ahead of remote ones, and the enabled list doubles as the
 115        // priority order when no explicit order was saved.
 0116        typeOptions.SimilarItemProviders = [.. missing, .. enabled];
 0117        if (typeOptions.SimilarItemProviderOrder is { Length: > 0 } order)
 118        {
 0119            typeOptions.SimilarItemProviderOrder = [.. missing, .. order];
 120        }
 121
 0122        _logger.LogInformation("Enabled local similarity providers {Providers} for '{ItemType}' in library '{LibraryName
 0123        return true;
 124    }
 125
 126    private Dictionary<string, string[]> GetLocalProvidersByItemType()
 127    {
 0128        var result = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
 129
 0130        foreach (var summary in _providerManager.GetAllMetadataPlugins())
 131        {
 0132            var names = summary.Plugins
 0133                .Where(p => p.Type == MetadataPluginType.LocalSimilarityProvider)
 0134                .Select(p => p.Name)
 0135                .Distinct(StringComparer.OrdinalIgnoreCase)
 0136                .ToArray();
 137
 0138            if (names.Length > 0)
 139            {
 0140                result[summary.ItemType] = names;
 141            }
 142        }
 143
 0144        return result;
 145    }
 146}