mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-27 17:01:16 -04:00
168 lines
7.1 KiB
C#
168 lines
7.1 KiB
C#
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))
|
|
{
|
|
using var context = await _dbProvider.CreateDbContextAsync(cancellationToken).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();
|
|
}
|
|
}
|