< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Library.Validators.GenresValidator
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
Line coverage
60%
Covered lines: 30
Uncovered lines: 20
Coverable lines: 50
Total lines: 123
Line coverage: 60%
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 1/23/2026 - 12:11:06 AM Line coverage: 100% (4/4) Total lines: 1034/19/2026 - 12:14:27 AM Line coverage: 43.5% (17/39) Branch coverage: 50% (2/4) Total lines: 1035/4/2026 - 12:15:16 AM Line coverage: 60% (30/50) Branch coverage: 20% (2/10) Total lines: 123 4/19/2026 - 12:14:27 AM Line coverage: 43.5% (17/39) Branch coverage: 50% (2/4) Total lines: 1035/4/2026 - 12:15:16 AM Line coverage: 60% (30/50) Branch coverage: 20% (2/10) Total lines: 123

Coverage delta

Coverage delta 57 -57

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Run()20%181056.52%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Library/Validators/GenresValidator.cs

#LineLine coverage
 1using System;
 2using System.Globalization;
 3using System.Linq;
 4using System.Threading;
 5using System.Threading.Tasks;
 6using Jellyfin.Data.Enums;
 7using MediaBrowser.Controller.Entities;
 8using MediaBrowser.Controller.Library;
 9using MediaBrowser.Controller.Persistence;
 10using Microsoft.Extensions.Logging;
 11
 12namespace Emby.Server.Implementations.Library.Validators;
 13
 14/// <summary>
 15/// Class GenresValidator.
 16/// </summary>
 17public class GenresValidator
 18{
 19    /// <summary>
 20    /// The library manager.
 21    /// </summary>
 22    private readonly ILibraryManager _libraryManager;
 23    private readonly IItemRepository _itemRepo;
 24
 25    /// <summary>
 26    /// The logger.
 27    /// </summary>
 28    private readonly ILogger<GenresValidator> _logger;
 29
 30    /// <summary>
 31    /// Initializes a new instance of the <see cref="GenresValidator"/> class.
 32    /// </summary>
 33    /// <param name="libraryManager">The library manager.</param>
 34    /// <param name="logger">The logger.</param>
 35    /// <param name="itemRepo">The item repository.</param>
 36    public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo)
 37    {
 1638        _libraryManager = libraryManager;
 1639        _logger = logger;
 1640        _itemRepo = itemRepo;
 1641    }
 42
 43    /// <summary>
 44    /// Runs the specified progress.
 45    /// </summary>
 46    /// <param name="progress">The progress.</param>
 47    /// <param name="cancellationToken">The cancellation token.</param>
 48    /// <returns>Task.</returns>
 49    public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
 50    {
 1651        var names = _itemRepo.GetGenreNames();
 1652        var existingGenreIds = _libraryManager.GetItemIds(new InternalItemsQuery
 1653        {
 1654            IncludeItemTypes = [BaseItemKind.Genre]
 1655        }).ToHashSet();
 56
 1657        var existingGenres = _libraryManager.GetItemList(new InternalItemsQuery
 1658        {
 1659            IncludeItemTypes = [BaseItemKind.Genre]
 1660        }).Cast<Genre>()
 1661        .GroupBy(g => g.Name, StringComparer.OrdinalIgnoreCase)
 1662        .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
 63
 1664        var numComplete = 0;
 1665        var count = names.Count;
 1666        var refreshed = 0;
 67
 3268        foreach (var name in names)
 69        {
 70            try
 71            {
 072                Genre? item = null;
 073                if (existingGenres.TryGetValue(name, out var existingGenre))
 74                {
 075                    item = existingGenre;
 76                }
 77
 78                // Fall back to GetGenre if not found (creates new item if needed)
 079                item ??= _libraryManager.GetGenre(name);
 80
 081                if (!existingGenreIds.Contains(item.Id))
 82                {
 083                    await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
 084                    refreshed++;
 85                }
 086            }
 087            catch (OperationCanceledException)
 88            {
 89                // Don't clutter the log
 090                throw;
 91            }
 092            catch (Exception ex)
 93            {
 094                _logger.LogError(ex, "Error refreshing {GenreName}", name);
 095            }
 96
 097            numComplete++;
 098            double percent = numComplete;
 099            percent /= count;
 0100            percent *= 100;
 101
 0102            progress.Report(percent);
 0103        }
 104
 16105        _logger.LogInformation("Refreshed metadata for {RefreshedCount} new genres out of {TotalCount} total", refreshed
 106
 16107        var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
 16108        {
 16109            IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre],
 16110            IsDeadGenre = true,
 16111            IsLocked = false
 16112        });
 113
 32114        foreach (var item in deadEntities)
 115        {
 0116            _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString
 117        }
 118
 16119        _libraryManager.DeleteItemsUnsafeFast(deadEntities, deleteSourceFiles: true);
 120
 16121        progress.Report(100);
 16122    }
 123}