< 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: 3
Coverable lines: 3
Total lines: 105
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 12/9/2025 - 12:12:43 AM Line coverage: 0% (0/3) Total lines: 105

Metrics

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

File(s)

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

#LineLine coverage
 1using System;
 2using System.Diagnostics;
 3using System.Linq;
 4using System.Text;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Jellyfin.Database.Implementations;
 8using Jellyfin.Database.Implementations.Entities;
 9using Jellyfin.Extensions;
 10using Jellyfin.Server.Implementations.Item;
 11using Jellyfin.Server.ServerSetupApp;
 12using Microsoft.EntityFrameworkCore;
 13using Microsoft.Extensions.Logging;
 14
 15namespace Jellyfin.Server.Migrations.Routines;
 16
 17/// <summary>
 18/// Migration to refresh CleanName values for all library items.
 19/// </summary>
 20[JellyfinMigration("2025-10-08T12:00:00", nameof(RefreshCleanNames))]
 21[JellyfinMigrationBackup(JellyfinDb = true)]
 22public class RefreshCleanNames : IAsyncMigrationRoutine
 23{
 24    private readonly IStartupLogger<RefreshCleanNames> _logger;
 25    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 26
 27    /// <summary>
 28    /// Initializes a new instance of the <see cref="RefreshCleanNames"/> class.
 29    /// </summary>
 30    /// <param name="logger">The logger.</param>
 31    /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
 32    public RefreshCleanNames(
 33        IStartupLogger<RefreshCleanNames> logger,
 34        IDbContextFactory<JellyfinDbContext> dbProvider)
 35    {
 036        _logger = logger;
 037        _dbProvider = dbProvider;
 038    }
 39
 40    /// <inheritdoc />
 41    public async Task PerformAsync(CancellationToken cancellationToken)
 42    {
 43        const int Limit = 1000;
 44        int itemCount = 0;
 45
 46        var sw = Stopwatch.StartNew();
 47
 48        using var context = _dbProvider.CreateDbContext();
 49        var records = context.BaseItems.Count(b => !string.IsNullOrEmpty(b.Name));
 50        _logger.LogInformation("Refreshing CleanName for {Count} library items", records);
 51
 52        var processedInPartition = 0;
 53
 54        await foreach (var item in context.BaseItems
 55                          .Where(b => !string.IsNullOrEmpty(b.Name))
 56                          .OrderBy(e => e.Id)
 57                          .WithPartitionProgress((partition) => _logger.LogInformation("Processed: {Offset}/{Total} - Up
 58                          .PartitionEagerAsync(Limit, cancellationToken)
 59                          .WithCancellation(cancellationToken)
 60                          .ConfigureAwait(false))
 61        {
 62            try
 63            {
 64                var newCleanName = string.IsNullOrWhiteSpace(item.Name) ? string.Empty : BaseItemRepository.GetCleanValu
 65                if (!string.Equals(newCleanName, item.CleanName, StringComparison.Ordinal))
 66                {
 67                    _logger.LogDebug(
 68                        "Updating CleanName for item {Id}: '{OldValue}' -> '{NewValue}'",
 69                        item.Id,
 70                        item.CleanName,
 71                        newCleanName);
 72                    item.CleanName = newCleanName;
 73                    itemCount++;
 74                }
 75            }
 76            catch (Exception ex)
 77            {
 78                _logger.LogWarning(ex, "Failed to update CleanName for item {Id} ({Name})", item.Id, item.Name);
 79            }
 80
 81            processedInPartition++;
 82
 83            if (processedInPartition >= Limit)
 84            {
 85                await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 86                // Clear tracked entities to avoid memory growth across partitions
 87                context.ChangeTracker.Clear();
 88                processedInPartition = 0;
 89            }
 90        }
 91
 92        // Save any remaining changes after the loop
 93        if (processedInPartition > 0)
 94        {
 95            await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 96            context.ChangeTracker.Clear();
 97        }
 98
 99        _logger.LogInformation(
 100            "Refreshed CleanName for {UpdatedCount} out of {TotalCount} items in {Time}",
 101            itemCount,
 102            records,
 103            sw.Elapsed);
 104    }
 105}