< Summary - Jellyfin

Information
Class: Jellyfin.Server.Migrations.Routines.MigrateUserDb
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 112
Coverable lines: 112
Total lines: 216
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 26
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Perform()0%702260%

File(s)

/srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs

#LineLine coverage
 1using System;
 2using System.IO;
 3using Emby.Server.Implementations.Data;
 4using Jellyfin.Data;
 5using Jellyfin.Database.Implementations;
 6using Jellyfin.Database.Implementations.Entities;
 7using Jellyfin.Database.Implementations.Enums;
 8using Jellyfin.Extensions.Json;
 9using Jellyfin.Server.Implementations.Users;
 10using MediaBrowser.Controller;
 11using MediaBrowser.Controller.Entities;
 12using MediaBrowser.Model.Configuration;
 13using MediaBrowser.Model.Serialization;
 14using MediaBrowser.Model.Users;
 15using Microsoft.Data.Sqlite;
 16using Microsoft.EntityFrameworkCore;
 17using Microsoft.Extensions.Logging;
 18using JsonSerializer = System.Text.Json.JsonSerializer;
 19
 20namespace Jellyfin.Server.Migrations.Routines;
 21
 22/// <summary>
 23/// The migration routine for migrating the user database to EF Core.
 24/// </summary>
 25#pragma warning disable CS0618 // Type or member is obsolete
 26[JellyfinMigration("2025-04-20T10:00:00", nameof(MigrateUserDb), "5C4B82A2-F053-4009-BD05-B6FCAD82F14C")]
 27public class MigrateUserDb : IMigrationRoutine
 28#pragma warning restore CS0618 // Type or member is obsolete
 29{
 30    private const string DbFilename = "users.db";
 31
 32    private readonly ILogger<MigrateUserDb> _logger;
 33    private readonly IServerApplicationPaths _paths;
 34    private readonly IDbContextFactory<JellyfinDbContext> _provider;
 35    private readonly IXmlSerializer _xmlSerializer;
 36
 37    /// <summary>
 38    /// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
 39    /// </summary>
 40    /// <param name="logger">The logger.</param>
 41    /// <param name="paths">The server application paths.</param>
 42    /// <param name="provider">The database provider.</param>
 43    /// <param name="xmlSerializer">The xml serializer.</param>
 44    public MigrateUserDb(
 45        ILogger<MigrateUserDb> logger,
 46        IServerApplicationPaths paths,
 47        IDbContextFactory<JellyfinDbContext> provider,
 48        IXmlSerializer xmlSerializer)
 49    {
 050        _logger = logger;
 051        _paths = paths;
 052        _provider = provider;
 053        _xmlSerializer = xmlSerializer;
 054    }
 55
 56    /// <inheritdoc/>
 57    public void Perform()
 58    {
 059        var dataPath = _paths.DataPath;
 060        _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
 61
 062        using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
 63        {
 064            connection.Open();
 065            using var dbContext = _provider.CreateDbContext();
 66
 067            var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
 68
 069            dbContext.RemoveRange(dbContext.Users);
 070            dbContext.SaveChanges();
 71
 072            foreach (var entry in queryResult)
 73            {
 074                UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options);
 075                if (mockup is null)
 76                {
 77                    continue;
 78                }
 79
 080                var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
 81
 082                var configPath = Path.Combine(userDataDir, "config.xml");
 083                var config = File.Exists(configPath)
 084                    ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? n
 085                    : new UserConfiguration();
 86
 087                var policyPath = Path.Combine(userDataDir, "policy.xml");
 088                var policy = File.Exists(policyPath)
 089                    ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy(
 090                    : new UserPolicy();
 091                policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
 092                    "Emby.Server.Implementations.Library",
 093                    "Jellyfin.Server.Implementations.Users",
 094                    StringComparison.Ordinal)
 095                    ?? typeof(DefaultAuthenticationProvider).FullName;
 96
 097                policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName;
 098                int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
 099                {
 0100                    -1 => null,
 0101                    0 => 3,
 0102                    _ => policy.LoginAttemptsBeforeLockout
 0103                };
 104
 0105                var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
 0106                {
 0107                    Id = entry.GetGuid(1),
 0108                    InternalId = entry.GetInt64(0),
 0109                    MaxParentalRatingScore = policy.MaxParentalRating,
 0110                    MaxParentalRatingSubScore = null,
 0111                    EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
 0112                    RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
 0113                    InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
 0114                    LoginAttemptsBeforeLockout = maxLoginAttempts,
 0115                    SubtitleMode = config.SubtitleMode,
 0116                    HidePlayedInLatest = config.HidePlayedInLatest,
 0117                    EnableLocalPassword = config.EnableLocalPassword,
 0118                    PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
 0119                    DisplayCollectionsView = config.DisplayCollectionsView,
 0120                    DisplayMissingEpisodes = config.DisplayMissingEpisodes,
 0121                    AudioLanguagePreference = config.AudioLanguagePreference,
 0122                    RememberAudioSelections = config.RememberAudioSelections,
 0123                    EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
 0124                    RememberSubtitleSelections = config.RememberSubtitleSelections,
 0125                    SubtitleLanguagePreference = config.SubtitleLanguagePreference,
 0126                    Password = mockup.Password,
 0127                    LastLoginDate = mockup.LastLoginDate,
 0128                    LastActivityDate = mockup.LastActivityDate
 0129                };
 130
 0131                if (mockup.ImageInfos.Length > 0)
 132                {
 0133                    ItemImageInfo info = mockup.ImageInfos[0];
 134
 0135                    user.ProfileImage = new ImageInfo(info.Path)
 0136                    {
 0137                        LastModified = info.DateModified
 0138                    };
 139                }
 140
 0141                user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
 0142                user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
 0143                user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
 0144                user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
 0145                user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
 0146                user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
 0147                user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
 0148                user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
 0149                user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding)
 0150                user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding)
 0151                user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
 0152                user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
 0153                user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
 0154                user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
 0155                user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
 0156                user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
 0157                user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
 0158                user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUser
 0159                user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
 0160                user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
 0161                user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
 0162                user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
 163
 0164                foreach (var policyAccessSchedule in policy.AccessSchedules)
 165                {
 0166                    user.AccessSchedules.Add(policyAccessSchedule);
 167                }
 168
 0169                user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
 0170                user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
 0171                user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
 0172                user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
 0173                user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFold
 0174                user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
 0175                user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
 0176                user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
 0177                user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
 178
 0179                dbContext.Users.Add(user);
 180            }
 181
 0182            dbContext.SaveChanges();
 183        }
 184
 185        try
 186        {
 0187            File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
 188
 0189            var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
 0190            if (File.Exists(journalPath))
 191            {
 0192                File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
 193            }
 0194        }
 0195        catch (IOException e)
 196        {
 0197            _logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
 0198        }
 0199    }
 200
 201#nullable disable
 202    internal class UserMockup
 203    {
 204        public string Password { get; set; }
 205
 206        public string EasyPassword { get; set; }
 207
 208        public DateTime? LastLoginDate { get; set; }
 209
 210        public DateTime? LastActivityDate { get; set; }
 211
 212        public string Name { get; set; }
 213
 214        public ItemImageInfo[] ImageInfos { get; set; }
 215    }
 216}