< 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: 121
Coverable lines: 121
Total lines: 233
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 32
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 8/14/2025 - 12:11:05 AM Line coverage: 0% (0/112) Branch coverage: 0% (0/26) Total lines: 21610/28/2025 - 12:11:27 AM Line coverage: 0% (0/121) Branch coverage: 0% (0/32) Total lines: 233 8/14/2025 - 12:11:05 AM Line coverage: 0% (0/112) Branch coverage: 0% (0/26) Total lines: 21610/28/2025 - 12:11:27 AM Line coverage: 0% (0/121) Branch coverage: 0% (0/32) Total lines: 233

Metrics

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

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        var userDbPath = Path.Combine(dataPath, DbFilename);
 061        if (!File.Exists(userDbPath))
 62        {
 063            _logger.LogWarning("{UserDbPath} doesn't exist, nothing to migrate", userDbPath);
 064            return;
 65        }
 66
 067        _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
 68
 069        using (var connection = new SqliteConnection($"Filename={userDbPath}"))
 70        {
 071            connection.Open();
 072            var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='LocalUser
 073            foreach (var row in tableQuery)
 74            {
 075                if (row.GetInt32(0) == 0)
 76                {
 077                    _logger.LogWarning("Table 'LocalUsersv2' doesn't exist in {UserDbPath}, nothing to migrate", userDbP
 078                    return;
 79                }
 80            }
 81
 082            using var dbContext = _provider.CreateDbContext();
 83
 084            var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
 85
 086            dbContext.RemoveRange(dbContext.Users);
 087            dbContext.SaveChanges();
 88
 089            foreach (var entry in queryResult)
 90            {
 091                UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options);
 092                if (mockup is null)
 93                {
 94                    continue;
 95                }
 96
 097                var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
 98
 099                var configPath = Path.Combine(userDataDir, "config.xml");
 0100                var config = File.Exists(configPath)
 0101                    ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? n
 0102                    : new UserConfiguration();
 103
 0104                var policyPath = Path.Combine(userDataDir, "policy.xml");
 0105                var policy = File.Exists(policyPath)
 0106                    ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy(
 0107                    : new UserPolicy();
 0108                policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
 0109                    "Emby.Server.Implementations.Library",
 0110                    "Jellyfin.Server.Implementations.Users",
 0111                    StringComparison.Ordinal)
 0112                    ?? typeof(DefaultAuthenticationProvider).FullName;
 113
 0114                policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName;
 0115                int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
 0116                {
 0117                    -1 => null,
 0118                    0 => 3,
 0119                    _ => policy.LoginAttemptsBeforeLockout
 0120                };
 121
 0122                var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
 0123                {
 0124                    Id = entry.GetGuid(1),
 0125                    InternalId = entry.GetInt64(0),
 0126                    MaxParentalRatingScore = policy.MaxParentalRating,
 0127                    MaxParentalRatingSubScore = null,
 0128                    EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
 0129                    RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
 0130                    InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
 0131                    LoginAttemptsBeforeLockout = maxLoginAttempts,
 0132                    SubtitleMode = config.SubtitleMode,
 0133                    HidePlayedInLatest = config.HidePlayedInLatest,
 0134                    EnableLocalPassword = config.EnableLocalPassword,
 0135                    PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
 0136                    DisplayCollectionsView = config.DisplayCollectionsView,
 0137                    DisplayMissingEpisodes = config.DisplayMissingEpisodes,
 0138                    AudioLanguagePreference = config.AudioLanguagePreference,
 0139                    RememberAudioSelections = config.RememberAudioSelections,
 0140                    EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
 0141                    RememberSubtitleSelections = config.RememberSubtitleSelections,
 0142                    SubtitleLanguagePreference = config.SubtitleLanguagePreference,
 0143                    Password = mockup.Password,
 0144                    LastLoginDate = mockup.LastLoginDate,
 0145                    LastActivityDate = mockup.LastActivityDate
 0146                };
 147
 0148                if (mockup.ImageInfos.Length > 0)
 149                {
 0150                    ItemImageInfo info = mockup.ImageInfos[0];
 151
 0152                    user.ProfileImage = new ImageInfo(info.Path)
 0153                    {
 0154                        LastModified = info.DateModified
 0155                    };
 156                }
 157
 0158                user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
 0159                user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
 0160                user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
 0161                user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
 0162                user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
 0163                user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
 0164                user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
 0165                user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
 0166                user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding)
 0167                user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding)
 0168                user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
 0169                user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
 0170                user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
 0171                user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
 0172                user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
 0173                user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
 0174                user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
 0175                user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUser
 0176                user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
 0177                user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
 0178                user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
 0179                user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
 180
 0181                foreach (var policyAccessSchedule in policy.AccessSchedules)
 182                {
 0183                    user.AccessSchedules.Add(policyAccessSchedule);
 184                }
 185
 0186                user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
 0187                user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
 0188                user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
 0189                user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
 0190                user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFold
 0191                user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
 0192                user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
 0193                user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
 0194                user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
 195
 0196                dbContext.Users.Add(user);
 197            }
 198
 0199            dbContext.SaveChanges();
 200        }
 201
 202        try
 203        {
 0204            File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
 205
 0206            var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
 0207            if (File.Exists(journalPath))
 208            {
 0209                File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
 210            }
 0211        }
 0212        catch (IOException e)
 213        {
 0214            _logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
 0215        }
 0216    }
 217
 218#nullable disable
 219    internal class UserMockup
 220    {
 221        public string Password { get; set; }
 222
 223        public string EasyPassword { get; set; }
 224
 225        public DateTime? LastLoginDate { get; set; }
 226
 227        public DateTime? LastActivityDate { get; set; }
 228
 229        public string Name { get; set; }
 230
 231        public ItemImageInfo[] ImageInfos { get; set; }
 232    }
 233}