diff options
Diffstat (limited to 'Jellyfin.Server/Migrations/Routines')
9 files changed, 197 insertions, 9 deletions
diff --git a/Jellyfin.Server/Migrations/Routines/CleanMusicArtist.cs b/Jellyfin.Server/Migrations/Routines/CleanMusicArtist.cs new file mode 100644 index 0000000000..d5c5f3d929 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/CleanMusicArtist.cs @@ -0,0 +1,47 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations; +using Jellyfin.Server.ServerSetupApp; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Cleans up all Music artists that have been migrated in the 10.11 RC migrations. +/// </summary> +[JellyfinMigration("2025-10-09T20:00:00", nameof(CleanMusicArtist))] +[JellyfinMigrationBackup(JellyfinDb = true)] +public class CleanMusicArtist : IAsyncMigrationRoutine +{ + private readonly IStartupLogger<CleanMusicArtist> _startupLogger; + private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory; + + /// <summary> + /// Initializes a new instance of the <see cref="CleanMusicArtist"/> class. + /// </summary> + /// <param name="startupLogger">The startup logger.</param> + /// <param name="dbContextFactory">The Db context factory.</param> + public CleanMusicArtist(IStartupLogger<CleanMusicArtist> startupLogger, IDbContextFactory<JellyfinDbContext> dbContextFactory) + { + _startupLogger = startupLogger; + _dbContextFactory = dbContextFactory; + } + + /// <inheritdoc/> + public async Task PerformAsync(CancellationToken cancellationToken) + { + var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (context.ConfigureAwait(false)) + { + var peoples = context.Peoples.Where(e => e.PersonType == nameof(PersonKind.Artist) || e.PersonType == nameof(PersonKind.AlbumArtist)); + _startupLogger.LogInformation("Delete {Number} Artist and Album Artist person types from db", await peoples.CountAsync(cancellationToken).ConfigureAwait(false)); + + await peoples + .ExecuteDeleteAsync(cancellationToken) + .ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs b/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs new file mode 100644 index 0000000000..6edfcbcfd5 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/DisableLegacyAuthorization.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to disable legacy authorization in the system config. +/// </summary> +[JellyfinMigration("2025-11-18T16:00:00", nameof(DisableLegacyAuthorization))] +public class DisableLegacyAuthorization : IAsyncMigrationRoutine +{ + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="DisableLegacyAuthorization"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public DisableLegacyAuthorization(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc /> + public Task PerformAsync(CancellationToken cancellationToken) + { + _serverConfigurationManager.Configuration.EnableLegacyAuthorization = false; + _serverConfigurationManager.SaveConfiguration(); + + return Task.CompletedTask; + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index a954d307e1..8c8563190d 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -55,9 +55,25 @@ namespace Jellyfin.Server.Migrations.Routines }; var dataPath = _paths.DataPath; - using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}")) + var activityLogPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(activityLogPath)) + { + _logger.LogWarning("{ActivityLogDb} doesn't exist, nothing to migrate", activityLogPath); + return; + } + + using (var connection = new SqliteConnection($"Filename={activityLogPath}")) { connection.Open(); + var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='ActivityLog';"); + foreach (var row in tableQuery) + { + if (row.GetInt32(0) == 0) + { + _logger.LogWarning("Table 'ActivityLog' doesn't exist in {ActivityLogPath}, nothing to migrate", activityLogPath); + return; + } + } using var userDbConnection = new SqliteConnection($"Filename={Path.Combine(dataPath, "users.db")}"); userDbConnection.Open(); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs index c6699c21df..0de775e03a 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs @@ -50,9 +50,28 @@ namespace Jellyfin.Server.Migrations.Routines public void Perform() { var dataPath = _appPaths.DataPath; - using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}")) + var dbFilePath = Path.Combine(dataPath, DbFilename); + + if (!File.Exists(dbFilePath)) + { + _logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath); + return; + } + + using (var connection = new SqliteConnection($"Filename={dbFilePath}")) { connection.Open(); + + var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Tokens';"); + foreach (var row in tableQuery) + { + if (row.GetInt32(0) == 0) + { + _logger.LogWarning("Table 'Tokens' doesn't exist in {Path}, nothing to migrate", dbFilePath); + return; + } + } + using var dbContext = _dbProvider.CreateDbContext(); var authenticatedDevices = connection.Query("SELECT * FROM Tokens"); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 0d9952ce97..ffd06fea0d 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -78,9 +78,27 @@ namespace Jellyfin.Server.Migrations.Routines var displayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var customDisplayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var dbFilePath = Path.Combine(_paths.DataPath, DbFilename); + + if (!File.Exists(dbFilePath)) + { + _logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath); + return; + } + using (var connection = new SqliteConnection($"Filename={dbFilePath}")) { connection.Open(); + + var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='userdisplaypreferences';"); + foreach (var row in tableQuery) + { + if (row.GetInt32(0) == 0) + { + _logger.LogWarning("Table 'userdisplaypreferences' doesn't exist in {Path}, nothing to migrate", dbFilePath); + return; + } + } + using var dbContext = _provider.CreateDbContext(); var results = connection.Query("SELECT * FROM userdisplaypreferences"); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs index c199ee4d6b..aa55309264 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs @@ -122,6 +122,16 @@ public class MigrateKeyframeData : IDatabaseMigrationRoutine { lastWriteTimeUtc = File.GetLastWriteTimeUtc(filePath); } + catch (ArgumentOutOfRangeException e) + { + _logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message); + return null; + } + catch (UnauthorizedAccessException e) + { + _logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message); + return null; + } catch (IOException e) { _logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message); @@ -135,14 +145,21 @@ public class MigrateKeyframeData : IDatabaseMigrationRoutine return Path.Join(keyframeCachePath, prefix, filename); } - private static bool TryReadFromCache(string? cachePath, [NotNullWhen(true)] out MediaEncoding.Keyframes.KeyframeData? cachedResult) + private bool TryReadFromCache(string? cachePath, [NotNullWhen(true)] out MediaEncoding.Keyframes.KeyframeData? cachedResult) { if (File.Exists(cachePath)) { - var bytes = File.ReadAllBytes(cachePath); - cachedResult = JsonSerializer.Deserialize<MediaEncoding.Keyframes.KeyframeData>(bytes, _jsonOptions); + try + { + var bytes = File.ReadAllBytes(cachePath); + cachedResult = JsonSerializer.Deserialize<MediaEncoding.Keyframes.KeyframeData>(bytes, _jsonOptions); - return cachedResult is not null; + return cachedResult is not null; + } + catch (JsonException jsonException) + { + _logger.LogWarning(jsonException, "Failed to read {Path}", cachePath); + } } cachedResult = null; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs index b90da9f7d3..d221d18531 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs @@ -383,8 +383,6 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine }); } - baseItemIds.Clear(); - foreach (var item in peopleCache) { operation.JellyfinDbContext.Peoples.Add(item.Value.Person); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index e5584fb947..8c3361ee16 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -57,11 +57,28 @@ public class MigrateUserDb : IMigrationRoutine public void Perform() { var dataPath = _paths.DataPath; + var userDbPath = Path.Combine(dataPath, DbFilename); + if (!File.Exists(userDbPath)) + { + _logger.LogWarning("{UserDbPath} doesn't exist, nothing to migrate", userDbPath); + return; + } + _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin."); - using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}")) + using (var connection = new SqliteConnection($"Filename={userDbPath}")) { connection.Open(); + var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='LocalUsersv2';"); + foreach (var row in tableQuery) + { + if (row.GetInt32(0) == 0) + { + _logger.LogWarning("Table 'LocalUsersv2' doesn't exist in {UserDbPath}, nothing to migrate", userDbPath); + return; + } + } + using var dbContext = _provider.CreateDbContext(); var queryResult = connection.Query("SELECT * FROM LocalUsersv2"); diff --git a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs index 8b394dd7aa..fbf9c16377 100644 --- a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs +++ b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs @@ -224,6 +224,18 @@ public class MoveExtractedFiles : IAsyncMigrationRoutine return null; } + catch (UnauthorizedAccessException e) + { + _logger.LogDebug("Skipping subtitle at index {Index} for {Path}: {Exception}", attachmentStreamIndex, mediaPath, e.Message); + + return null; + } + catch (ArgumentOutOfRangeException e) + { + _logger.LogDebug("Skipping attachment at index {Index} for {Path}: {Exception}", attachmentStreamIndex, mediaPath, e.Message); + + return null; + } filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Value.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D", CultureInfo.InvariantCulture); } @@ -263,6 +275,18 @@ public class MoveExtractedFiles : IAsyncMigrationRoutine { date = File.GetLastWriteTimeUtc(path); } + catch (ArgumentOutOfRangeException e) + { + _logger.LogDebug("Skipping subtitle at index {Index} for {Path}: {Exception}", streamIndex, path, e.Message); + + return null; + } + catch (UnauthorizedAccessException e) + { + _logger.LogDebug("Skipping subtitle at index {Index} for {Path}: {Exception}", streamIndex, path, e.Message); + + return null; + } catch (IOException e) { _logger.LogDebug("Skipping subtitle at index {Index} for {Path}: {Exception}", streamIndex, path, e.Message); |
