< Summary - Jellyfin

Information
Class: Jellyfin.Server.Migrations.Routines.RefreshCleanNames
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/RefreshCleanNames.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 43
Coverable lines: 43
Total lines: 102
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 12
Branch coverage: 0%
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: 0% (0/3) Total lines: 1054/19/2026 - 12:14:27 AM Line coverage: 0% (0/43) Branch coverage: 0% (0/12) Total lines: 1055/4/2026 - 12:15:16 AM Line coverage: 0% (0/43) Branch coverage: 0% (0/12) Total lines: 102 4/19/2026 - 12:14:27 AM Line coverage: 0% (0/43) Branch coverage: 0% (0/12) Total lines: 1055/4/2026 - 12:15:16 AM Line coverage: 0% (0/43) Branch coverage: 0% (0/12) Total lines: 102

Coverage delta

Coverage delta 1 -1

Metrics

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

File(s)

/srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/RefreshCleanNames.cs

#LineLine coverage
 1using System;
 2using System.Diagnostics;
 3using System.Linq;
 4using System.Threading;
 5using System.Threading.Tasks;
 6using Jellyfin.Database.Implementations;
 7using Jellyfin.Extensions;
 8using Jellyfin.Server.ServerSetupApp;
 9using Microsoft.EntityFrameworkCore;
 10using Microsoft.Extensions.Logging;
 11
 12namespace Jellyfin.Server.Migrations.Routines;
 13
 14/// <summary>
 15/// Migration to refresh CleanName values for all library items.
 16/// </summary>
 17[JellyfinMigration("2025-10-08T12:00:00", nameof(RefreshCleanNames))]
 18[JellyfinMigrationBackup(JellyfinDb = true)]
 19public class RefreshCleanNames : IAsyncMigrationRoutine
 20{
 21    private readonly IStartupLogger<RefreshCleanNames> _logger;
 22    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 23
 24    /// <summary>
 25    /// Initializes a new instance of the <see cref="RefreshCleanNames"/> class.
 26    /// </summary>
 27    /// <param name="logger">The logger.</param>
 28    /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
 29    public RefreshCleanNames(
 30        IStartupLogger<RefreshCleanNames> logger,
 31        IDbContextFactory<JellyfinDbContext> dbProvider)
 32    {
 033        _logger = logger;
 034        _dbProvider = dbProvider;
 035    }
 36
 37    /// <inheritdoc />
 38    public async Task PerformAsync(CancellationToken cancellationToken)
 39    {
 40        const int Limit = 10000;
 041        int itemCount = 0;
 42
 043        var sw = Stopwatch.StartNew();
 44
 045        using var context = _dbProvider.CreateDbContext();
 046        var records = context.BaseItems.Count(b => !string.IsNullOrEmpty(b.Name));
 047        _logger.LogInformation("Refreshing CleanName for {Count} library items", records);
 48
 049        var processedInPartition = 0;
 50
 051        await foreach (var item in context.BaseItems
 052                          .Where(b => !string.IsNullOrEmpty(b.Name))
 053                          .OrderBy(e => e.Id)
 054                          .WithPartitionProgress((partition) => _logger.LogInformation("Processed: {Offset}/{Total} - Up
 055                          .PartitionEagerAsync(Limit, cancellationToken)
 056                          .WithCancellation(cancellationToken)
 057                          .ConfigureAwait(false))
 58        {
 59            try
 60            {
 061                var newCleanName = string.IsNullOrWhiteSpace(item.Name) ? string.Empty : item.Name.GetCleanValue();
 062                if (!string.Equals(newCleanName, item.CleanName, StringComparison.Ordinal))
 63                {
 064                    _logger.LogDebug(
 065                        "Updating CleanName for item {Id}: '{OldValue}' -> '{NewValue}'",
 066                        item.Id,
 067                        item.CleanName,
 068                        newCleanName);
 069                    item.CleanName = newCleanName;
 070                    itemCount++;
 71                }
 072            }
 073            catch (Exception ex)
 74            {
 075                _logger.LogWarning(ex, "Failed to update CleanName for item {Id} ({Name})", item.Id, item.Name);
 076            }
 77
 078            processedInPartition++;
 79
 080            if (processedInPartition >= Limit)
 81            {
 082                await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 83                // Clear tracked entities to avoid memory growth across partitions
 084                context.ChangeTracker.Clear();
 085                processedInPartition = 0;
 86            }
 87        }
 88
 89        // Save any remaining changes after the loop
 090        if (processedInPartition > 0)
 91        {
 092            await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 093            context.ChangeTracker.Clear();
 94        }
 95
 096        _logger.LogInformation(
 097            "Refreshed CleanName for {UpdatedCount} out of {TotalCount} items in {Time}",
 098            itemCount,
 099            records,
 0100            sw.Elapsed);
 0101    }
 102}