1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Migrations.Routines;
/// <summary>
/// Migration to re-read creation dates for library items with internal metadata paths.
/// </summary>
[JellyfinMigration("2025-04-20T23:00:00", nameof(RefreshInternalDateModified), "32E762EB-4918-45CE-A44C-C801F66B877D", RunMigrationOnSetup = false)]
public class RefreshInternalDateModified : IDatabaseMigrationRoutine
{
private readonly ILogger<RefreshInternalDateModified> _logger;
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationHost _applicationHost;
private readonly bool _useFileCreationTimeForDateAdded;
private IReadOnlyList<string> _internalTypes = [
typeof(Genre).FullName!,
typeof(MusicGenre).FullName!,
typeof(MusicArtist).FullName!,
typeof(People).FullName!,
typeof(Studio).FullName!
];
private IReadOnlyList<string> _internalPaths;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshInternalDateModified"/> class.
/// </summary>
/// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
/// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
/// <param name="logger">The logger.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
public RefreshInternalDateModified(
IServerApplicationHost applicationHost,
IServerApplicationPaths applicationPaths,
IServerConfigurationManager configurationManager,
IDbContextFactory<JellyfinDbContext> dbProvider,
ILogger<RefreshInternalDateModified> logger,
IFileSystem fileSystem)
{
_dbProvider = dbProvider;
_logger = logger;
_fileSystem = fileSystem;
_applicationHost = applicationHost;
_internalPaths = [
applicationPaths.ArtistsPath,
applicationPaths.GenrePath,
applicationPaths.MusicGenrePath,
applicationPaths.StudioPath,
applicationPaths.PeoplePath
];
_useFileCreationTimeForDateAdded = configurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded;
}
/// <inheritdoc />
public void Perform()
{
const int Limit = 5000;
int itemCount = 0, offset = 0;
var sw = Stopwatch.StartNew();
using var context = _dbProvider.CreateDbContext();
var records = context.BaseItems.Count(b => _internalTypes.Contains(b.Type));
_logger.LogInformation("Checking if {Count} potentially internal items require refreshed DateModified", records);
do
{
var results = context.BaseItems
.Where(b => _internalTypes.Contains(b.Type))
.OrderBy(e => e.Id)
.Skip(offset)
.Take(Limit)
.ToList();
foreach (var item in results)
{
var itemPath = item.Path;
if (itemPath is not null)
{
var realPath = _applicationHost.ExpandVirtualPath(item.Path);
if (_internalPaths.Any(path => realPath.StartsWith(path, StringComparison.Ordinal)))
{
var writeTime = _fileSystem.GetLastWriteTimeUtc(realPath);
var itemModificationTime = item.DateModified;
if (writeTime != itemModificationTime)
{
_logger.LogDebug("Reset file modification date: Old: {Old} - New: {New} - Path: {Path}", itemModificationTime, writeTime, realPath);
item.DateModified = writeTime;
if (_useFileCreationTimeForDateAdded)
{
item.DateCreated = _fileSystem.GetCreationTimeUtc(realPath);
}
itemCount++;
}
}
}
}
offset += Limit;
if (offset > records)
{
offset = records;
}
_logger.LogInformation("Checked: {Count} - Refreshed: {Items} - Time: {Time}", offset, itemCount, sw.Elapsed);
} while (offset < records);
context.SaveChanges();
_logger.LogInformation("Refreshed DateModified for {Count} items in {Time}", itemCount, sw.Elapsed);
}
}
|