diff options
| author | Bond-009 <bond.009@outlook.com> | 2026-06-13 21:43:47 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-13 21:43:47 +0200 |
| commit | 21efb55db6aedfa519247344a070cf50b6e3c167 (patch) | |
| tree | aa60dcf532e90aad451d8b37ed2ae5ecbc305988 | |
| parent | dd42a121c43721c8984ba0026d6fbed4a526d01f (diff) | |
| parent | 12f718e7bb1b3b247e90fe2769d2a6b22b9f37af (diff) | |
Merge pull request #17064 from Shadowghost/fix-clean-names-values
Fix CleanName and CleanValue refresh
| -rw-r--r-- | Jellyfin.Server/Migrations/Routines/20260610120000_RefreshCleanNamesAndValues.cs (renamed from Jellyfin.Server/Migrations/Routines/20251008120000_RefreshCleanNames.cs) | 85 |
1 files changed, 78 insertions, 7 deletions
diff --git a/Jellyfin.Server/Migrations/Routines/20251008120000_RefreshCleanNames.cs b/Jellyfin.Server/Migrations/Routines/20260610120000_RefreshCleanNamesAndValues.cs index eca50ac100..7ade727d9b 100644 --- a/Jellyfin.Server/Migrations/Routines/20251008120000_RefreshCleanNames.cs +++ b/Jellyfin.Server/Migrations/Routines/20260610120000_RefreshCleanNamesAndValues.cs @@ -12,22 +12,22 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.Routines; /// <summary> -/// Migration to refresh CleanName values for all library items. +/// Migration to refresh CleanName values for all library items and CleanValue values for all item values. /// </summary> -[JellyfinMigration("2025-10-08T12:00:00", nameof(RefreshCleanNames))] +[JellyfinMigration("2026-06-10T12:00:00", nameof(RefreshCleanNamesAndValues))] [JellyfinMigrationBackup(JellyfinDb = true)] -public class RefreshCleanNames : IAsyncMigrationRoutine +public class RefreshCleanNamesAndValues : IAsyncMigrationRoutine { - private readonly IStartupLogger<RefreshCleanNames> _logger; + private readonly IStartupLogger<RefreshCleanNamesAndValues> _logger; private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; /// <summary> - /// Initializes a new instance of the <see cref="RefreshCleanNames"/> class. + /// Initializes a new instance of the <see cref="RefreshCleanNamesAndValues"/> class. /// </summary> /// <param name="logger">The logger.</param> /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param> - public RefreshCleanNames( - IStartupLogger<RefreshCleanNames> logger, + public RefreshCleanNamesAndValues( + IStartupLogger<RefreshCleanNamesAndValues> logger, IDbContextFactory<JellyfinDbContext> dbProvider) { _logger = logger; @@ -37,6 +37,12 @@ public class RefreshCleanNames : IAsyncMigrationRoutine /// <inheritdoc /> public async Task PerformAsync(CancellationToken cancellationToken) { + await RefreshCleanNamesAsync(cancellationToken).ConfigureAwait(false); + await RefreshCleanValuesAsync(cancellationToken).ConfigureAwait(false); + } + + private async Task RefreshCleanNamesAsync(CancellationToken cancellationToken) + { const int Limit = 10000; int itemCount = 0; @@ -99,4 +105,69 @@ public class RefreshCleanNames : IAsyncMigrationRoutine records, sw.Elapsed); } + + private async Task RefreshCleanValuesAsync(CancellationToken cancellationToken) + { + const int Limit = 10000; + int itemCount = 0; + + var sw = Stopwatch.StartNew(); + + using var context = _dbProvider.CreateDbContext(); + var records = context.ItemValues.Count(b => !string.IsNullOrEmpty(b.Value)); + _logger.LogInformation("Refreshing CleanValue for {Count} item values", records); + + var processedInPartition = 0; + + await foreach (var item in context.ItemValues + .Where(b => !string.IsNullOrEmpty(b.Value)) + .OrderBy(e => e.ItemValueId) + .WithPartitionProgress((partition) => _logger.LogInformation("Processed: {Offset}/{Total} - Updated: {UpdatedCount} - Time: {Elapsed}", partition * Limit, records, itemCount, sw.Elapsed)) + .PartitionEagerAsync(Limit, cancellationToken) + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + try + { + var newCleanValue = string.IsNullOrWhiteSpace(item.Value) ? string.Empty : item.Value.GetCleanValue(); + if (!string.Equals(newCleanValue, item.CleanValue, StringComparison.Ordinal)) + { + _logger.LogDebug( + "Updating CleanValue for item value {Id}: '{OldValue}' -> '{NewValue}'", + item.ItemValueId, + item.CleanValue, + newCleanValue); + item.CleanValue = newCleanValue; + itemCount++; + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to update CleanValue for item value {Id} ({Value})", item.ItemValueId, item.Value); + } + + processedInPartition++; + + if (processedInPartition >= Limit) + { + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + // Clear tracked entities to avoid memory growth across partitions + context.ChangeTracker.Clear(); + processedInPartition = 0; + } + } + + // Save any remaining changes after the loop + if (processedInPartition > 0) + { + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + context.ChangeTracker.Clear(); + } + + _logger.LogInformation( + "Refreshed CleanValue for {UpdatedCount} out of {TotalCount} item values in {Time}", + itemCount, + records, + sw.Elapsed); + } } |
