mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
Only consider migrations that have key set for migration.xml migration (#14061)
This commit is contained in:
parent
28e2f5bb08
commit
a7bb3ea214
@ -17,7 +17,9 @@ public sealed class JellyfinMigrationAttribute : Attribute
|
||||
/// </summary>
|
||||
/// <param name="order">The ordering this migration should be applied to. Must be a valid DateTime ISO8601 formatted string.</param>
|
||||
/// <param name="name">The name of this Migration.</param>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public JellyfinMigrationAttribute(string order, string name) : this(order, name, null)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
}
|
||||
|
||||
@ -27,6 +29,7 @@ public sealed class JellyfinMigrationAttribute : Attribute
|
||||
/// <param name="order">The ordering this migration should be applied to. Must be a valid DateTime ISO8601 formatted string.</param>
|
||||
/// <param name="name">The name of this Migration.</param>
|
||||
/// <param name="key">[ONLY FOR LEGACY MIGRATIONS]The unique key of this migration. Must be a valid Guid formatted string.</param>
|
||||
[Obsolete("This Constructor should only be used for Legacy migrations. Use the (Order,Name) one for all new ones instead.")]
|
||||
public JellyfinMigrationAttribute(string order, string name, string? key)
|
||||
{
|
||||
Order = DateTime.Parse(order, CultureInfo.InvariantCulture);
|
||||
|
@ -108,8 +108,9 @@ internal class JellyfinMigrationService
|
||||
{
|
||||
var historyRepository = dbContext.GetService<IHistoryRepository>();
|
||||
var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false);
|
||||
var oldMigrations = Migrations.SelectMany(e => e)
|
||||
.Where(e => migrationOptions.Applied.Any(f => f.Id.Equals(e.Metadata.Key!.Value))) // this is a legacy migration that will always have its own ID.
|
||||
var oldMigrations = Migrations
|
||||
.SelectMany(e => e.Where(e => e.Metadata.Key is not null)) // only consider migrations that have the key set as its the reference marker for legacy migrations.
|
||||
.Where(e => migrationOptions.Applied.Any(f => f.Id.Equals(e.Metadata.Key!.Value)))
|
||||
.Where(e => !appliedMigrations.Contains(e.BuildCodeMigrationId()))
|
||||
.ToArray();
|
||||
var startupScripts = oldMigrations.Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.BuildCodeMigrationId(), GetJellyfinVersion()))));
|
||||
|
@ -8,8 +8,8 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Jellyfin.Server.Migrations.PreStartupRoutines;
|
||||
|
||||
/// <inheritdoc />
|
||||
[JellyfinMigration("2025-04-20T00:00:00", nameof(CreateNetworkConfiguration), "9B354818-94D5-4B68-AC49-E35CB85F9D84", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T00:00:00", nameof(CreateNetworkConfiguration), "9B354818-94D5-4B68-AC49-E35CB85F9D84", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
|
||||
public class CreateNetworkConfiguration : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -10,8 +10,8 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Jellyfin.Server.Migrations.PreStartupRoutines;
|
||||
|
||||
/// <inheritdoc />
|
||||
[JellyfinMigration("2025-04-20T03:00:00", nameof(MigrateEncodingOptions), "A8E61960-7726-4450-8F3D-82C12DAABBCB", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T03:00:00", nameof(MigrateEncodingOptions), "A8E61960-7726-4450-8F3D-82C12DAABBCB", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
|
||||
public class MigrateEncodingOptions : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -9,8 +9,8 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Jellyfin.Server.Migrations.PreStartupRoutines;
|
||||
|
||||
/// <inheritdoc />
|
||||
[JellyfinMigration("2025-04-20T02:00:00", nameof(MigrateMusicBrainzTimeout), "A6DCACF4-C057-4Ef9-80D3-61CEF9DDB4F0", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T02:00:00", nameof(MigrateMusicBrainzTimeout), "A6DCACF4-C057-4Ef9-80D3-61CEF9DDB4F0", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
|
||||
public class MigrateMusicBrainzTimeout : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -9,8 +9,8 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Jellyfin.Server.Migrations.PreStartupRoutines;
|
||||
|
||||
/// <inheritdoc />
|
||||
[JellyfinMigration("2025-04-20T04:00:00", nameof(RenameEnableGroupingIntoCollections), "E73B777D-CD5C-4E71-957A-B86B3660B7CF", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T04:00:00", nameof(RenameEnableGroupingIntoCollections), "E73B777D-CD5C-4E71-957A-B86B3660B7CF", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
|
||||
public class RenameEnableGroupingIntoCollections : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// <summary>
|
||||
/// Migration to add the default cast receivers to the system config.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T16:00:00", nameof(AddDefaultCastReceivers), "34A1A1C4-5572-418E-A2F8-32CDFE2668E8", RunMigrationOnSetup = true)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T16:00:00", nameof(AddDefaultCastReceivers), "34A1A1C4-5572-418E-A2F8-32CDFE2668E8", RunMigrationOnSetup = true)]
|
||||
public class AddDefaultCastReceivers : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
/// <summary>
|
||||
/// Migration to initialize system configuration with the default plugin repository.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T09:00:00", nameof(AddDefaultPluginRepository), "EB58EBEE-9514-4B9B-8225-12E1A40020DF", RunMigrationOnSetup = true)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T09:00:00", nameof(AddDefaultPluginRepository), "EB58EBEE-9514-4B9B-8225-12E1A40020DF", RunMigrationOnSetup = true)]
|
||||
public class AddDefaultPluginRepository : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -12,8 +12,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
/// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json,
|
||||
/// otherwise a blank file will be created.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T06:00:00", nameof(CreateUserLoggingConfigFile), "EF103419-8451-40D8-9F34-D1A8E93A1679")]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T06:00:00", nameof(CreateUserLoggingConfigFile), "EF103419-8451-40D8-9F34-D1A8E93A1679")]
|
||||
internal class CreateUserLoggingConfigFile : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
/// <summary>
|
||||
/// Disable transcode throttling for all installations since it is currently broken for certain video formats.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T05:00:00", nameof(DisableTranscodingThrottling), "4124C2CD-E939-4FFB-9BE9-9B311C413638")]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T05:00:00", nameof(DisableTranscodingThrottling), "4124C2CD-E939-4FFB-9BE9-9B311C413638")]
|
||||
internal class DisableTranscodingThrottling : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -16,8 +16,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
/// <summary>
|
||||
/// Fixes the data column of audio types to be deserializable.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T18:00:00", nameof(FixAudioData), "CF6FABC2-9FBE-4933-84A5-FFE52EF22A58")]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T18:00:00", nameof(FixAudioData), "CF6FABC2-9FBE-4933-84A5-FFE52EF22A58")]
|
||||
internal class FixAudioData : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -13,8 +13,8 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// <summary>
|
||||
/// Properly set playlist owner.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T15:00:00", nameof(FixPlaylistOwner), "615DFA9E-2497-4DBB-A472-61938B752C5B")]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T15:00:00", nameof(FixPlaylistOwner), "615DFA9E-2497-4DBB-A472-61938B752C5B")]
|
||||
internal class FixPlaylistOwner : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -14,8 +14,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
/// <summary>
|
||||
/// The migration routine for migrating the activity log database to EF Core.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T07:00:00", nameof(MigrateActivityLogDb), "3793eb59-bc8c-456c-8b9f-bd5a62a42978")]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T07:00:00", nameof(MigrateActivityLogDb), "3793eb59-bc8c-456c-8b9f-bd5a62a42978")]
|
||||
public class MigrateActivityLogDb : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -15,8 +15,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
/// <summary>
|
||||
/// A migration that moves data from the authentication database into the new schema.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22")]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22")]
|
||||
public class MigrateAuthenticationDb : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -20,8 +20,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
/// <summary>
|
||||
/// The migration routine for migrating the display preferences database to EF Core.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T12:00:00", nameof(MigrateDisplayPreferencesDb), "06387815-C3CC-421F-A888-FB5F9992BEA8")]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T12:00:00", nameof(MigrateDisplayPreferencesDb), "06387815-C3CC-421F-A888-FB5F9992BEA8")]
|
||||
public class MigrateDisplayPreferencesDb : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// <summary>
|
||||
/// Migration to move extracted files to the new directories.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-21T00:00:00", nameof(MigrateKeyframeData), "EA4bCAE1-09A4-428E-9B90-4B4FD2EA1B24")]
|
||||
[JellyfinMigration("2025-04-21T00:00:00", nameof(MigrateKeyframeData))]
|
||||
public class MigrateKeyframeData : IDatabaseMigrationRoutine
|
||||
{
|
||||
private readonly ILogger<MigrateKeyframeData> _logger;
|
||||
|
@ -9,26 +9,16 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.Server.Implementations.Item;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Chapters;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
|
||||
using Chapter = Jellyfin.Database.Implementations.Entities.Chapter;
|
||||
@ -38,7 +28,7 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// <summary>
|
||||
/// The migration routine for migrating the userdata database to EF Core.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T20:00:00", nameof(MigrateLibraryDb), "36445464-849f-429f-9ad0-bb130efa0664")]
|
||||
[JellyfinMigration("2025-04-20T20:00:00", nameof(MigrateLibraryDb))]
|
||||
internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
{
|
||||
private const string DbFilename = "library.db";
|
||||
|
@ -5,62 +5,63 @@ using MediaBrowser.Model.Globalization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
/// <summary>
|
||||
/// Migrate rating levels.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T22:00:00", nameof(MigrateRatingLevels))]
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
internal class MigrateRatingLevels : IDatabaseMigrationRoutine
|
||||
{
|
||||
/// <summary>
|
||||
/// Migrate rating levels.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T22:00:00", nameof(MigrateRatingLevels), "98724538-EB11-40E3-931A-252C55BDDE7A")]
|
||||
internal class MigrateRatingLevels : IDatabaseMigrationRoutine
|
||||
private readonly ILogger<MigrateRatingLevels> _logger;
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _provider;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
|
||||
public MigrateRatingLevels(
|
||||
IDbContextFactory<JellyfinDbContext> provider,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILocalizationManager localizationManager)
|
||||
{
|
||||
private readonly ILogger<MigrateRatingLevels> _logger;
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _provider;
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
_provider = provider;
|
||||
_localizationManager = localizationManager;
|
||||
_logger = loggerFactory.CreateLogger<MigrateRatingLevels>();
|
||||
}
|
||||
|
||||
public MigrateRatingLevels(
|
||||
IDbContextFactory<JellyfinDbContext> provider,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILocalizationManager localizationManager)
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
_logger.LogInformation("Recalculating parental rating levels based on rating string.");
|
||||
using var context = _provider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
var ratings = context.BaseItems.AsNoTracking().Select(e => e.OfficialRating).Distinct();
|
||||
foreach (var rating in ratings)
|
||||
{
|
||||
_provider = provider;
|
||||
_localizationManager = localizationManager;
|
||||
_logger = loggerFactory.CreateLogger<MigrateRatingLevels>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
_logger.LogInformation("Recalculating parental rating levels based on rating string.");
|
||||
using var context = _provider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
var ratings = context.BaseItems.AsNoTracking().Select(e => e.OfficialRating).Distinct();
|
||||
foreach (var rating in ratings)
|
||||
if (string.IsNullOrEmpty(rating))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rating))
|
||||
{
|
||||
int? value = null;
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, value));
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ratingValue = _localizationManager.GetRatingScore(rating);
|
||||
var score = ratingValue?.Score;
|
||||
var subScore = ratingValue?.SubScore;
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == rating)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, score));
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == rating)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, subScore));
|
||||
}
|
||||
int? value = null;
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, value));
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ratingValue = _localizationManager.GetRatingScore(rating);
|
||||
var score = ratingValue?.Score;
|
||||
var subScore = ratingValue?.SubScore;
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == rating)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, score));
|
||||
context.BaseItems
|
||||
.Where(e => e.OfficialRating == rating)
|
||||
.ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, subScore));
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
|
@ -17,201 +17,200 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// The migration routine for migrating the user database to EF Core.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T10:00:00", nameof(MigrateUserDb), "5C4B82A2-F053-4009-BD05-B6FCAD82F14C")]
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
/// <summary>
|
||||
/// The migration routine for migrating the user database to EF Core.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public class MigrateUserDb : IMigrationRoutine
|
||||
[JellyfinMigration("2025-04-20T10:00:00", nameof(MigrateUserDb), "5C4B82A2-F053-4009-BD05-B6FCAD82F14C")]
|
||||
public class MigrateUserDb : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
private const string DbFilename = "users.db";
|
||||
|
||||
private readonly ILogger<MigrateUserDb> _logger;
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _provider;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="paths">The server application paths.</param>
|
||||
/// <param name="provider">The database provider.</param>
|
||||
/// <param name="xmlSerializer">The xml serializer.</param>
|
||||
public MigrateUserDb(
|
||||
ILogger<MigrateUserDb> logger,
|
||||
IServerApplicationPaths paths,
|
||||
IDbContextFactory<JellyfinDbContext> provider,
|
||||
IXmlSerializer xmlSerializer)
|
||||
{
|
||||
private const string DbFilename = "users.db";
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
_provider = provider;
|
||||
_xmlSerializer = xmlSerializer;
|
||||
}
|
||||
|
||||
private readonly ILogger<MigrateUserDb> _logger;
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
private readonly IDbContextFactory<JellyfinDbContext> _provider;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _paths.DataPath;
|
||||
_logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="paths">The server application paths.</param>
|
||||
/// <param name="provider">The database provider.</param>
|
||||
/// <param name="xmlSerializer">The xml serializer.</param>
|
||||
public MigrateUserDb(
|
||||
ILogger<MigrateUserDb> logger,
|
||||
IServerApplicationPaths paths,
|
||||
IDbContextFactory<JellyfinDbContext> provider,
|
||||
IXmlSerializer xmlSerializer)
|
||||
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
|
||||
{
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
_provider = provider;
|
||||
_xmlSerializer = xmlSerializer;
|
||||
}
|
||||
connection.Open();
|
||||
using var dbContext = _provider.CreateDbContext();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _paths.DataPath;
|
||||
_logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
|
||||
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
|
||||
|
||||
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
|
||||
dbContext.RemoveRange(dbContext.Users);
|
||||
dbContext.SaveChanges();
|
||||
|
||||
foreach (var entry in queryResult)
|
||||
{
|
||||
connection.Open();
|
||||
using var dbContext = _provider.CreateDbContext();
|
||||
|
||||
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
|
||||
|
||||
dbContext.RemoveRange(dbContext.Users);
|
||||
dbContext.SaveChanges();
|
||||
|
||||
foreach (var entry in queryResult)
|
||||
UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options);
|
||||
if (mockup is null)
|
||||
{
|
||||
UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options);
|
||||
if (mockup is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
|
||||
|
||||
var configPath = Path.Combine(userDataDir, "config.xml");
|
||||
var config = File.Exists(configPath)
|
||||
? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration()
|
||||
: new UserConfiguration();
|
||||
|
||||
var policyPath = Path.Combine(userDataDir, "policy.xml");
|
||||
var policy = File.Exists(policyPath)
|
||||
? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy()
|
||||
: new UserPolicy();
|
||||
policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
|
||||
"Emby.Server.Implementations.Library",
|
||||
"Jellyfin.Server.Implementations.Users",
|
||||
StringComparison.Ordinal)
|
||||
?? typeof(DefaultAuthenticationProvider).FullName;
|
||||
|
||||
policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName;
|
||||
int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
|
||||
{
|
||||
-1 => null,
|
||||
0 => 3,
|
||||
_ => policy.LoginAttemptsBeforeLockout
|
||||
};
|
||||
|
||||
var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
|
||||
{
|
||||
Id = entry.GetGuid(1),
|
||||
InternalId = entry.GetInt64(0),
|
||||
MaxParentalRatingScore = policy.MaxParentalRating,
|
||||
MaxParentalRatingSubScore = null,
|
||||
EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
|
||||
RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
|
||||
InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
|
||||
LoginAttemptsBeforeLockout = maxLoginAttempts,
|
||||
SubtitleMode = config.SubtitleMode,
|
||||
HidePlayedInLatest = config.HidePlayedInLatest,
|
||||
EnableLocalPassword = config.EnableLocalPassword,
|
||||
PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
|
||||
DisplayCollectionsView = config.DisplayCollectionsView,
|
||||
DisplayMissingEpisodes = config.DisplayMissingEpisodes,
|
||||
AudioLanguagePreference = config.AudioLanguagePreference,
|
||||
RememberAudioSelections = config.RememberAudioSelections,
|
||||
EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
|
||||
RememberSubtitleSelections = config.RememberSubtitleSelections,
|
||||
SubtitleLanguagePreference = config.SubtitleLanguagePreference,
|
||||
Password = mockup.Password,
|
||||
LastLoginDate = mockup.LastLoginDate,
|
||||
LastActivityDate = mockup.LastActivityDate
|
||||
};
|
||||
|
||||
if (mockup.ImageInfos.Length > 0)
|
||||
{
|
||||
ItemImageInfo info = mockup.ImageInfos[0];
|
||||
|
||||
user.ProfileImage = new ImageInfo(info.Path)
|
||||
{
|
||||
LastModified = info.DateModified
|
||||
};
|
||||
}
|
||||
|
||||
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
|
||||
user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
|
||||
user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
|
||||
user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
|
||||
user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
|
||||
user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
|
||||
user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
|
||||
user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
|
||||
user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
|
||||
user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
|
||||
user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
|
||||
user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
|
||||
user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
|
||||
user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
|
||||
user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
|
||||
user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
|
||||
user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
|
||||
user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
|
||||
user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
|
||||
|
||||
foreach (var policyAccessSchedule in policy.AccessSchedules)
|
||||
{
|
||||
user.AccessSchedules.Add(policyAccessSchedule);
|
||||
}
|
||||
|
||||
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
|
||||
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
|
||||
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
|
||||
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
|
||||
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
||||
user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
|
||||
user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
|
||||
user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
|
||||
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
|
||||
|
||||
dbContext.Users.Add(user);
|
||||
continue;
|
||||
}
|
||||
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
|
||||
var configPath = Path.Combine(userDataDir, "config.xml");
|
||||
var config = File.Exists(configPath)
|
||||
? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration()
|
||||
: new UserConfiguration();
|
||||
|
||||
var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
|
||||
if (File.Exists(journalPath))
|
||||
var policyPath = Path.Combine(userDataDir, "policy.xml");
|
||||
var policy = File.Exists(policyPath)
|
||||
? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy()
|
||||
: new UserPolicy();
|
||||
policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
|
||||
"Emby.Server.Implementations.Library",
|
||||
"Jellyfin.Server.Implementations.Users",
|
||||
StringComparison.Ordinal)
|
||||
?? typeof(DefaultAuthenticationProvider).FullName;
|
||||
|
||||
policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName;
|
||||
int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
|
||||
{
|
||||
File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
|
||||
-1 => null,
|
||||
0 => 3,
|
||||
_ => policy.LoginAttemptsBeforeLockout
|
||||
};
|
||||
|
||||
var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
|
||||
{
|
||||
Id = entry.GetGuid(1),
|
||||
InternalId = entry.GetInt64(0),
|
||||
MaxParentalRatingScore = policy.MaxParentalRating,
|
||||
MaxParentalRatingSubScore = null,
|
||||
EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
|
||||
RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
|
||||
InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
|
||||
LoginAttemptsBeforeLockout = maxLoginAttempts,
|
||||
SubtitleMode = config.SubtitleMode,
|
||||
HidePlayedInLatest = config.HidePlayedInLatest,
|
||||
EnableLocalPassword = config.EnableLocalPassword,
|
||||
PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
|
||||
DisplayCollectionsView = config.DisplayCollectionsView,
|
||||
DisplayMissingEpisodes = config.DisplayMissingEpisodes,
|
||||
AudioLanguagePreference = config.AudioLanguagePreference,
|
||||
RememberAudioSelections = config.RememberAudioSelections,
|
||||
EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
|
||||
RememberSubtitleSelections = config.RememberSubtitleSelections,
|
||||
SubtitleLanguagePreference = config.SubtitleLanguagePreference,
|
||||
Password = mockup.Password,
|
||||
LastLoginDate = mockup.LastLoginDate,
|
||||
LastActivityDate = mockup.LastActivityDate
|
||||
};
|
||||
|
||||
if (mockup.ImageInfos.Length > 0)
|
||||
{
|
||||
ItemImageInfo info = mockup.ImageInfos[0];
|
||||
|
||||
user.ProfileImage = new ImageInfo(info.Path)
|
||||
{
|
||||
LastModified = info.DateModified
|
||||
};
|
||||
}
|
||||
|
||||
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
|
||||
user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
|
||||
user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
|
||||
user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
|
||||
user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
|
||||
user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
|
||||
user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
|
||||
user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
|
||||
user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
|
||||
user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
|
||||
user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
|
||||
user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
|
||||
user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
|
||||
user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
|
||||
user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
|
||||
user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
|
||||
user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
|
||||
user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
|
||||
user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
|
||||
user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
|
||||
|
||||
foreach (var policyAccessSchedule in policy.AccessSchedules)
|
||||
{
|
||||
user.AccessSchedules.Add(policyAccessSchedule);
|
||||
}
|
||||
|
||||
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
|
||||
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
|
||||
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
|
||||
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
|
||||
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
||||
user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
|
||||
user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
|
||||
user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
|
||||
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
|
||||
|
||||
dbContext.Users.Add(user);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
|
||||
}
|
||||
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
internal class UserMockup
|
||||
try
|
||||
{
|
||||
public string Password { get; set; }
|
||||
File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
|
||||
|
||||
public string EasyPassword { get; set; }
|
||||
|
||||
public DateTime? LastLoginDate { get; set; }
|
||||
|
||||
public DateTime? LastActivityDate { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public ItemImageInfo[] ImageInfos { get; set; }
|
||||
var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
|
||||
if (File.Exists(journalPath))
|
||||
{
|
||||
File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
|
||||
}
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
internal class UserMockup
|
||||
{
|
||||
public string Password { get; set; }
|
||||
|
||||
public string EasyPassword { get; set; }
|
||||
|
||||
public DateTime? LastLoginDate { get; set; }
|
||||
|
||||
public DateTime? LastActivityDate { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public ItemImageInfo[] ImageInfos { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// <summary>
|
||||
/// Migration to move extracted files to the new directories.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T21:00:00", nameof(MoveExtractedFiles), "9063b0Ef-CFF1-4EDC-9A13-74093681A89B")]
|
||||
[JellyfinMigration("2025-04-20T21:00:00", nameof(MoveExtractedFiles))]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public class MoveExtractedFiles : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
@ -15,8 +15,8 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// <summary>
|
||||
/// Migration to move trickplay files to the new directory.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T23:00:00", nameof(MoveTrickplayFiles), "9540D44A-D8DC-11EF-9CBB-B77274F77C52", RunMigrationOnSetup = true)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T23:00:00", nameof(MoveTrickplayFiles), RunMigrationOnSetup = true)]
|
||||
public class MoveTrickplayFiles : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -2,42 +2,41 @@ using System;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Updates;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// Migration to initialize system configuration with the default plugin repository.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T11:00:00", nameof(ReaddDefaultPluginRepository), "5F86E7F6-D966-4C77-849D-7A7B40B68C4E", RunMigrationOnSetup = true)]
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
/// <summary>
|
||||
/// Migration to initialize system configuration with the default plugin repository.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public class ReaddDefaultPluginRepository : IMigrationRoutine
|
||||
[JellyfinMigration("2025-04-20T11:00:00", nameof(ReaddDefaultPluginRepository), "5F86E7F6-D966-4C77-849D-7A7B40B68C4E", RunMigrationOnSetup = true)]
|
||||
public class ReaddDefaultPluginRepository : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo
|
||||
{
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
Name = "Jellyfin Stable",
|
||||
Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"
|
||||
};
|
||||
|
||||
private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo
|
||||
{
|
||||
Name = "Jellyfin Stable",
|
||||
Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"
|
||||
};
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReaddDefaultPluginRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||
public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReaddDefaultPluginRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||
public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
// Only add if repository list is empty
|
||||
if (_serverConfigurationManager.Configuration.PluginRepositories.Length == 0)
|
||||
{
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
// Only add if repository list is empty
|
||||
if (_serverConfigurationManager.Configuration.PluginRepositories.Length == 0)
|
||||
{
|
||||
_serverConfigurationManager.Configuration.PluginRepositories = new[] { _defaultRepositoryInfo };
|
||||
_serverConfigurationManager.SaveConfiguration();
|
||||
}
|
||||
_serverConfigurationManager.Configuration.PluginRepositories = new[] { _defaultRepositoryInfo };
|
||||
_serverConfigurationManager.SaveConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ 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)]
|
||||
[JellyfinMigration("2025-04-20T23:00:00", nameof(RefreshInternalDateModified))]
|
||||
public class RefreshInternalDateModified : IDatabaseMigrationRoutine
|
||||
{
|
||||
private readonly ILogger<RefreshInternalDateModified> _logger;
|
||||
|
@ -3,44 +3,43 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// Removes the old 'RemoveDownloadImagesInAdvance' from library options.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T13:00:00", nameof(RemoveDownloadImagesInAdvance), "A81F75E0-8F43-416F-A5E8-516CCAB4D8CC")]
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
/// <summary>
|
||||
/// Removes the old 'RemoveDownloadImagesInAdvance' from library options.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
internal class RemoveDownloadImagesInAdvance : IMigrationRoutine
|
||||
[JellyfinMigration("2025-04-20T13:00:00", nameof(RemoveDownloadImagesInAdvance), "A81F75E0-8F43-416F-A5E8-516CCAB4D8CC")]
|
||||
internal class RemoveDownloadImagesInAdvance : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
private readonly ILogger<RemoveDownloadImagesInAdvance> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public RemoveDownloadImagesInAdvance(ILogger<RemoveDownloadImagesInAdvance> logger, ILibraryManager libraryManager)
|
||||
{
|
||||
private readonly ILogger<RemoveDownloadImagesInAdvance> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public RemoveDownloadImagesInAdvance(ILogger<RemoveDownloadImagesInAdvance> logger, ILibraryManager libraryManager)
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var virtualFolders = _libraryManager.GetVirtualFolders(false);
|
||||
_logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries");
|
||||
foreach (var virtualFolder in virtualFolders)
|
||||
{
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var virtualFolders = _libraryManager.GetVirtualFolders(false);
|
||||
_logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries");
|
||||
foreach (var virtualFolder in virtualFolders)
|
||||
// Some virtual folders don't have a proper item id.
|
||||
if (!Guid.TryParse(virtualFolder.ItemId, out var folderId))
|
||||
{
|
||||
// Some virtual folders don't have a proper item id.
|
||||
if (!Guid.TryParse(virtualFolder.ItemId, out var folderId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var libraryOptions = virtualFolder.LibraryOptions;
|
||||
var collectionFolder = _libraryManager.GetItemById<CollectionFolder>(folderId) ?? throw new InvalidOperationException("Failed to find CollectionFolder");
|
||||
// The property no longer exists in LibraryOptions, so we just re-save the options to get old data removed.
|
||||
collectionFolder.UpdateLibraryOptions(libraryOptions);
|
||||
_logger.LogInformation("Removed from '{VirtualFolder}'", virtualFolder.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var libraryOptions = virtualFolder.LibraryOptions;
|
||||
var collectionFolder = _libraryManager.GetItemById<CollectionFolder>(folderId) ?? throw new InvalidOperationException("Failed to find CollectionFolder");
|
||||
// The property no longer exists in LibraryOptions, so we just re-save the options to get old data removed.
|
||||
collectionFolder.UpdateLibraryOptions(libraryOptions);
|
||||
_logger.LogInformation("Removed from '{VirtualFolder}'", virtualFolder.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,71 +7,70 @@ using MediaBrowser.Controller;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T08:00:00", nameof(RemoveDuplicateExtras), "ACBE17B7-8435-4A83-8B64-6FCF162CB9BD")]
|
||||
namespace Jellyfin.Server.Migrations.Routines;
|
||||
|
||||
/// <summary>
|
||||
/// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
internal class RemoveDuplicateExtras : IMigrationRoutine
|
||||
[JellyfinMigration("2025-04-20T08:00:00", nameof(RemoveDuplicateExtras), "ACBE17B7-8435-4A83-8B64-6FCF162CB9BD")]
|
||||
internal class RemoveDuplicateExtras : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
private const string DbFilename = "library.db";
|
||||
private readonly ILogger<RemoveDuplicateExtras> _logger;
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
|
||||
public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths)
|
||||
{
|
||||
private const string DbFilename = "library.db";
|
||||
private readonly ILogger<RemoveDuplicateExtras> _logger;
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
}
|
||||
|
||||
public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths)
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _paths.DataPath;
|
||||
var dbPath = Path.Combine(dataPath, DbFilename);
|
||||
using var connection = new SqliteConnection($"Filename={dbPath}");
|
||||
connection.Open();
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
{
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
}
|
||||
// Query the database for the ids of duplicate extras
|
||||
var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
|
||||
var bads = string.Join(", ", queryResult.Select(x => x.GetString(0)));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _paths.DataPath;
|
||||
var dbPath = Path.Combine(dataPath, DbFilename);
|
||||
using var connection = new SqliteConnection($"Filename={dbPath}");
|
||||
connection.Open();
|
||||
using (var transaction = connection.BeginTransaction())
|
||||
// Do nothing if no duplicate extras were detected
|
||||
if (bads.Length == 0)
|
||||
{
|
||||
// Query the database for the ids of duplicate extras
|
||||
var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
|
||||
var bads = string.Join(", ", queryResult.Select(x => x.GetString(0)));
|
||||
_logger.LogInformation("No duplicate extras detected, skipping migration.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Do nothing if no duplicate extras were detected
|
||||
if (bads.Length == 0)
|
||||
// Back up the database before deleting any entries
|
||||
for (int i = 1; ; i++)
|
||||
{
|
||||
var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
|
||||
if (!File.Exists(bakPath))
|
||||
{
|
||||
_logger.LogInformation("No duplicate extras detected, skipping migration.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Back up the database before deleting any entries
|
||||
for (int i = 1; ; i++)
|
||||
{
|
||||
var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
|
||||
if (!File.Exists(bakPath))
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(dbPath, bakPath);
|
||||
_logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
|
||||
throw;
|
||||
}
|
||||
File.Copy(dbPath, bakPath);
|
||||
_logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all duplicate extras
|
||||
_logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
|
||||
connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
// Delete all duplicate extras
|
||||
_logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
|
||||
connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// <summary>
|
||||
/// Remove duplicate playlist entries.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T19:00:00", nameof(RemoveDuplicatePlaylistChildren), "96C156A2-7A13-4B3B-A8B8-FB80C94D20C0")]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T19:00:00", nameof(RemoveDuplicatePlaylistChildren), "96C156A2-7A13-4B3B-A8B8-FB80C94D20C0")]
|
||||
internal class RemoveDuplicatePlaylistChildren : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
@ -6,8 +6,8 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// <summary>
|
||||
/// Migration to update the default Jellyfin plugin repository.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2025-04-20T17:00:00", nameof(UpdateDefaultPluginRepository), "852816E0-2712-49A9-9240-C6FC5FCAD1A8", RunMigrationOnSetup = true)]
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
[JellyfinMigration("2025-04-20T17:00:00", nameof(UpdateDefaultPluginRepository), "852816E0-2712-49A9-9240-C6FC5FCAD1A8", RunMigrationOnSetup = true)]
|
||||
public class UpdateDefaultPluginRepository : IMigrationRoutine
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user