< Summary - Jellyfin

Information
Class: Jellyfin.Server.Migrations.Routines.MigrateAuthenticationDb
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 67
Coverable lines: 67
Total lines: 162
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
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/58) Branch coverage: 0% (0/16) Total lines: 14311/4/2025 - 12:11:59 AM Line coverage: 0% (0/67) Branch coverage: 0% (0/22) Total lines: 162 8/14/2025 - 12:11:05 AM Line coverage: 0% (0/58) Branch coverage: 0% (0/16) Total lines: 14311/4/2025 - 12:11:59 AM Line coverage: 0% (0/67) Branch coverage: 0% (0/22) Total lines: 162

Metrics

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

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using Emby.Server.Implementations.Data;
 5using Jellyfin.Database.Implementations;
 6using Jellyfin.Database.Implementations.Entities.Security;
 7using MediaBrowser.Controller;
 8using MediaBrowser.Controller.Library;
 9using Microsoft.Data.Sqlite;
 10using Microsoft.EntityFrameworkCore;
 11using Microsoft.Extensions.Logging;
 12
 13namespace Jellyfin.Server.Migrations.Routines
 14{
 15    /// <summary>
 16    /// A migration that moves data from the authentication database into the new schema.
 17    /// </summary>
 18#pragma warning disable CS0618 // Type or member is obsolete
 19    [JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22")]
 20    public class MigrateAuthenticationDb : IMigrationRoutine
 21#pragma warning restore CS0618 // Type or member is obsolete
 22    {
 23        private const string DbFilename = "authentication.db";
 24
 25        private readonly ILogger<MigrateAuthenticationDb> _logger;
 26        private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 27        private readonly IServerApplicationPaths _appPaths;
 28        private readonly IUserManager _userManager;
 29
 30        /// <summary>
 31        /// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
 32        /// </summary>
 33        /// <param name="logger">The logger.</param>
 34        /// <param name="dbProvider">The database provider.</param>
 35        /// <param name="appPaths">The server application paths.</param>
 36        /// <param name="userManager">The user manager.</param>
 37        public MigrateAuthenticationDb(
 38            ILogger<MigrateAuthenticationDb> logger,
 39            IDbContextFactory<JellyfinDbContext> dbProvider,
 40            IServerApplicationPaths appPaths,
 41            IUserManager userManager)
 42        {
 043            _logger = logger;
 044            _dbProvider = dbProvider;
 045            _appPaths = appPaths;
 046            _userManager = userManager;
 047        }
 48
 49        /// <inheritdoc />
 50        public void Perform()
 51        {
 052            var dataPath = _appPaths.DataPath;
 053            var dbFilePath = Path.Combine(dataPath, DbFilename);
 54
 055            if (!File.Exists(dbFilePath))
 56            {
 057                _logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath);
 058                return;
 59            }
 60
 061            using (var connection = new SqliteConnection($"Filename={dbFilePath}"))
 62            {
 063                connection.Open();
 64
 065                var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Token
 066                foreach (var row in tableQuery)
 67                {
 068                    if (row.GetInt32(0) == 0)
 69                    {
 070                        _logger.LogWarning("Table 'Tokens' doesn't exist in {Path}, nothing to migrate", dbFilePath);
 071                        return;
 72                    }
 73                }
 74
 075                using var dbContext = _dbProvider.CreateDbContext();
 76
 077                var authenticatedDevices = connection.Query("SELECT * FROM Tokens");
 78
 079                foreach (var row in authenticatedDevices)
 80                {
 081                    var dateCreatedStr = row.GetString(9);
 082                    _ = DateTime.TryParse(dateCreatedStr, out var dateCreated);
 083                    var dateLastActivityStr = row.GetString(10);
 084                    _ = DateTime.TryParse(dateLastActivityStr, out var dateLastActivity);
 85
 086                    if (row.IsDBNull(6))
 87                    {
 088                        dbContext.ApiKeys.Add(new ApiKey(row.GetString(3))
 089                        {
 090                            AccessToken = row.GetString(1),
 091                            DateCreated = dateCreated,
 092                            DateLastActivity = dateLastActivity
 093                        });
 94                    }
 95                    else
 96                    {
 097                        var userId = row.GetGuid(6);
 098                        var user = _userManager.GetUserById(userId);
 099                        if (user is null)
 100                        {
 101                            // User doesn't exist, don't bring over the device.
 102                            continue;
 103                        }
 104
 0105                        dbContext.Devices.Add(new Device(
 0106                            userId,
 0107                            row.GetString(3),
 0108                            row.GetString(4),
 0109                            row.GetString(5),
 0110                            row.GetString(2))
 0111                        {
 0112                            AccessToken = row.GetString(1),
 0113                            IsActive = row.GetBoolean(8),
 0114                            DateCreated = dateCreated,
 0115                            DateLastActivity = dateLastActivity
 0116                        });
 117                    }
 118                }
 119
 0120                var deviceOptions = connection.Query("SELECT * FROM Devices");
 0121                var deviceIds = new HashSet<string>();
 0122                foreach (var row in deviceOptions)
 123                {
 0124                    if (row.IsDBNull(2))
 125                    {
 126                        continue;
 127                    }
 128
 0129                    var deviceId = row.GetString(2);
 0130                    if (deviceIds.Contains(deviceId))
 131                    {
 132                        continue;
 133                    }
 134
 0135                    deviceIds.Add(deviceId);
 136
 0137                    dbContext.DeviceOptions.Add(new DeviceOptions(deviceId)
 0138                    {
 0139                        CustomName = row.IsDBNull(1) ? null : row.GetString(1)
 0140                    });
 141                }
 142
 0143                dbContext.SaveChanges();
 144            }
 145
 146            try
 147            {
 0148                File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
 149
 0150                var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
 0151                if (File.Exists(journalPath))
 152                {
 0153                    File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
 154                }
 0155            }
 0156            catch (IOException e)
 157            {
 0158                _logger.LogError(e, "Error renaming legacy activity log database to 'authentication.db.old'");
 0159            }
 0160        }
 161    }
 162}