| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 | 
							- using System;
 
- using System.Diagnostics;
 
- using System.Linq;
 
- using System.Threading;
 
- using System.Threading.Tasks;
 
- using Jellyfin.Database.Implementations;
 
- using Jellyfin.Server.ServerSetupApp;
 
- using Microsoft.EntityFrameworkCore;
 
- using Microsoft.Extensions.Logging;
 
- namespace Jellyfin.Server.Migrations.Routines;
 
- /// <summary>
 
- /// Migration to fix dates saved in the database to always be UTC.
 
- /// </summary>
 
- [JellyfinMigration("2025-06-20T18:00:00", nameof(FixDates))]
 
- public class FixDates : IAsyncMigrationRoutine
 
- {
 
-     private const int PageSize = 5000;
 
-     private readonly ILogger _logger;
 
-     private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 
-     /// <summary>
 
-     /// Initializes a new instance of the <see cref="FixDates"/> class.
 
-     /// </summary>
 
-     /// <param name="logger">The logger.</param>
 
-     /// <param name="startupLogger">The startup logger for Startup UI integration.</param>
 
-     /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
 
-     public FixDates(
 
-         ILogger<FixDates> logger,
 
-         IStartupLogger<FixDates> startupLogger,
 
-         IDbContextFactory<JellyfinDbContext> dbProvider)
 
-     {
 
-         _logger = startupLogger.With(logger);
 
-         _dbProvider = dbProvider;
 
-     }
 
-     /// <inheritdoc />
 
-     public async Task PerformAsync(CancellationToken cancellationToken)
 
-     {
 
-         if (!TimeZoneInfo.Local.Equals(TimeZoneInfo.Utc))
 
-         {
 
-             var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 
-             await using (context.ConfigureAwait(false))
 
-             {
 
-                 var sw = Stopwatch.StartNew();
 
-                 await FixBaseItemsAsync(context, sw, cancellationToken).ConfigureAwait(false);
 
-                 sw.Reset();
 
-                 await FixChaptersAsync(context, sw, cancellationToken).ConfigureAwait(false);
 
-                 sw.Reset();
 
-                 await FixBaseItemImageInfos(context, sw, cancellationToken).ConfigureAwait(false);
 
-             }
 
-         }
 
-     }
 
-     private async Task FixBaseItemsAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
 
-     {
 
-         int itemCount = 0;
 
-         var baseQuery = context.BaseItems.OrderBy(e => e.Id);
 
-         var records = baseQuery.Count();
 
-         _logger.LogInformation("Fixing dates for {Count} BaseItems.", records);
 
-         sw.Start();
 
-         await foreach (var result in context.BaseItems.OrderBy(e => e.Id)
 
-                         .WithPartitionProgress(
 
-                             (partition) =>
 
-                                 _logger.LogInformation(
 
-                                     "Processing BaseItems batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
 
-                                     partition + 1,
 
-                                     Math.Min((partition + 1) * PageSize, records),
 
-                                     records,
 
-                                     sw.Elapsed))
 
-                         .PartitionEagerAsync(PageSize, cancellationToken)
 
-                         .WithCancellation(cancellationToken)
 
-                         .ConfigureAwait(false))
 
-         {
 
-             result.DateCreated = ToUniversalTime(result.DateCreated);
 
-             result.DateLastMediaAdded = ToUniversalTime(result.DateLastMediaAdded);
 
-             result.DateLastRefreshed = ToUniversalTime(result.DateLastRefreshed);
 
-             result.DateLastSaved = ToUniversalTime(result.DateLastSaved);
 
-             result.DateModified = ToUniversalTime(result.DateModified);
 
-             itemCount++;
 
-         }
 
-         var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 
-         _logger.LogInformation("BaseItems: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
 
-     }
 
-     private async Task FixChaptersAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
 
-     {
 
-         int itemCount = 0;
 
-         var baseQuery = context.Chapters;
 
-         var records = baseQuery.Count();
 
-         _logger.LogInformation("Fixing dates for {Count} Chapters.", records);
 
-         sw.Start();
 
-         await foreach (var result in context.Chapters.OrderBy(e => e.ItemId)
 
-                         .WithPartitionProgress(
 
-                             (partition) =>
 
-                                 _logger.LogInformation(
 
-                                     "Processing Chapter batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
 
-                                     partition + 1,
 
-                                     Math.Min((partition + 1) * PageSize, records),
 
-                                     records,
 
-                                     sw.Elapsed))
 
-                         .PartitionEagerAsync(PageSize, cancellationToken)
 
-                         .WithCancellation(cancellationToken)
 
-                         .ConfigureAwait(false))
 
-         {
 
-             result.ImageDateModified = ToUniversalTime(result.ImageDateModified, true);
 
-             itemCount++;
 
-         }
 
-         var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 
-         _logger.LogInformation("Chapters: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
 
-     }
 
-     private async Task FixBaseItemImageInfos(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
 
-     {
 
-         int itemCount = 0;
 
-         var baseQuery = context.BaseItemImageInfos;
 
-         var records = baseQuery.Count();
 
-         _logger.LogInformation("Fixing dates for {Count} BaseItemImageInfos.", records);
 
-         sw.Start();
 
-         await foreach (var result in context.BaseItemImageInfos.OrderBy(e => e.Id)
 
-                         .WithPartitionProgress(
 
-                             (partition) =>
 
-                                 _logger.LogInformation(
 
-                                     "Processing BaseItemImageInfos batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {ElapsedTime}",
 
-                                     partition + 1,
 
-                                     Math.Min((partition + 1) * PageSize, records),
 
-                                     records,
 
-                                     sw.Elapsed))
 
-                         .PartitionEagerAsync(PageSize, cancellationToken)
 
-                         .WithCancellation(cancellationToken)
 
-                         .ConfigureAwait(false))
 
-         {
 
-             result.DateModified = ToUniversalTime(result.DateModified);
 
-             itemCount++;
 
-         }
 
-         var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 
-         _logger.LogInformation("BaseItemImageInfos: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", itemCount, saveCount, sw.Elapsed);
 
-     }
 
-     private DateTime? ToUniversalTime(DateTime? dateTime, bool isUTC = false)
 
-     {
 
-         if (dateTime is null)
 
-         {
 
-             return null;
 
-         }
 
-         if (dateTime.Value.Year == 1 && dateTime.Value.Month == 1 && dateTime.Value.Day == 1)
 
-         {
 
-             return null;
 
-         }
 
-         if (dateTime.Value.Kind == DateTimeKind.Utc || isUTC)
 
-         {
 
-             return dateTime.Value;
 
-         }
 
-         return dateTime.Value.ToUniversalTime();
 
-     }
 
- }
 
 
  |