aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2026-06-13 21:43:47 +0200
committerGitHub <noreply@github.com>2026-06-13 21:43:47 +0200
commit21efb55db6aedfa519247344a070cf50b6e3c167 (patch)
treeaa60dcf532e90aad451d8b37ed2ae5ecbc305988
parentdd42a121c43721c8984ba0026d6fbed4a526d01f (diff)
parent12f718e7bb1b3b247e90fe2769d2a6b22b9f37af (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);
+ }
}