< Summary - Jellyfin

Information
Class: Jellyfin.Server.Migrations.Routines.MigrateAuthenticationDb
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/20250420140000_MigrateAuthenticationDb.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 67
Coverable lines: 67
Total lines: 163
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 4/8/2026 - 12:11:47 AM Line coverage: 0% (0/67) Branch coverage: 0% (0/22) Total lines: 1627/6/2026 - 12:16:28 AM Line coverage: 0% (0/67) Branch coverage: 0% (0/22) Total lines: 163 4/8/2026 - 12:11:47 AM Line coverage: 0% (0/67) Branch coverage: 0% (0/22) Total lines: 1627/6/2026 - 12:16:28 AM Line coverage: 0% (0/67) Branch coverage: 0% (0/22) Total lines: 163

Coverage delta

Coverage delta 1 -1

Metrics

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

File(s)

/srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/20250420140000_MigrateAuthenticationDb.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.IO;
 5using Emby.Server.Implementations.Data;
 6using Jellyfin.Database.Implementations;
 7using Jellyfin.Database.Implementations.Entities.Security;
 8using MediaBrowser.Controller;
 9using MediaBrowser.Controller.Library;
 10using Microsoft.Data.Sqlite;
 11using Microsoft.EntityFrameworkCore;
 12using Microsoft.Extensions.Logging;
 13
 14namespace Jellyfin.Server.Migrations.Routines
 15{
 16    /// <summary>
 17    /// A migration that moves data from the authentication database into the new schema.
 18    /// </summary>
 19#pragma warning disable CS0618 // Type or member is obsolete
 20    [JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22")]
 21    public class MigrateAuthenticationDb : IMigrationRoutine
 22#pragma warning restore CS0618 // Type or member is obsolete
 23    {
 24        private const string DbFilename = "authentication.db";
 25
 26        private readonly ILogger<MigrateAuthenticationDb> _logger;
 27        private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 28        private readonly IServerApplicationPaths _appPaths;
 29        private readonly IUserManager _userManager;
 30
 31        /// <summary>
 32        /// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
 33        /// </summary>
 34        /// <param name="logger">The logger.</param>
 35        /// <param name="dbProvider">The database provider.</param>
 36        /// <param name="appPaths">The server application paths.</param>
 37        /// <param name="userManager">The user manager.</param>
 38        public MigrateAuthenticationDb(
 39            ILogger<MigrateAuthenticationDb> logger,
 40            IDbContextFactory<JellyfinDbContext> dbProvider,
 41            IServerApplicationPaths appPaths,
 42            IUserManager userManager)
 43        {
 044            _logger = logger;
 045            _dbProvider = dbProvider;
 046            _appPaths = appPaths;
 047            _userManager = userManager;
 048        }
 49
 50        /// <inheritdoc />
 51        public void Perform()
 52        {
 053            var dataPath = _appPaths.DataPath;
 054            var dbFilePath = Path.Combine(dataPath, DbFilename);
 55
 056            if (!File.Exists(dbFilePath))
 57            {
 058                _logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath);
 059                return;
 60            }
 61
 062            using (var connection = new SqliteConnection($"Filename={dbFilePath}"))
 63            {
 064                connection.Open();
 65
 066                var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Token
 067                foreach (var row in tableQuery)
 68                {
 069                    if (row.GetInt32(0) == 0)
 70                    {
 071                        _logger.LogWarning("Table 'Tokens' doesn't exist in {Path}, nothing to migrate", dbFilePath);
 072                        return;
 73                    }
 74                }
 75
 076                using var dbContext = _dbProvider.CreateDbContext();
 77
 078                var authenticatedDevices = connection.Query("SELECT * FROM Tokens");
 79
 080                foreach (var row in authenticatedDevices)
 81                {
 082                    var dateCreatedStr = row.GetString(9);
 083                    _ = DateTime.TryParse(dateCreatedStr, CultureInfo.InvariantCulture, out var dateCreated);
 084                    var dateLastActivityStr = row.GetString(10);
 085                    _ = DateTime.TryParse(dateLastActivityStr, CultureInfo.InvariantCulture, out var dateLastActivity);
 86
 087                    if (row.IsDBNull(6))
 88                    {
 089                        dbContext.ApiKeys.Add(new ApiKey(row.GetString(3))
 090                        {
 091                            AccessToken = row.GetString(1),
 092                            DateCreated = dateCreated,
 093                            DateLastActivity = dateLastActivity
 094                        });
 95                    }
 96                    else
 97                    {
 098                        var userId = row.GetGuid(6);
 099                        var user = _userManager.GetUserById(userId);
 0100                        if (user is null)
 101                        {
 102                            // User doesn't exist, don't bring over the device.
 103                            continue;
 104                        }
 105
 0106                        dbContext.Devices.Add(new Device(
 0107                            userId,
 0108                            row.GetString(3),
 0109                            row.GetString(4),
 0110                            row.GetString(5),
 0111                            row.GetString(2))
 0112                        {
 0113                            AccessToken = row.GetString(1),
 0114                            IsActive = row.GetBoolean(8),
 0115                            DateCreated = dateCreated,
 0116                            DateLastActivity = dateLastActivity
 0117                        });
 118                    }
 119                }
 120
 0121                var deviceOptions = connection.Query("SELECT * FROM Devices");
 0122                var deviceIds = new HashSet<string>();
 0123                foreach (var row in deviceOptions)
 124                {
 0125                    if (row.IsDBNull(2))
 126                    {
 127                        continue;
 128                    }
 129
 0130                    var deviceId = row.GetString(2);
 0131                    if (deviceIds.Contains(deviceId))
 132                    {
 133                        continue;
 134                    }
 135
 0136                    deviceIds.Add(deviceId);
 137
 0138                    dbContext.DeviceOptions.Add(new DeviceOptions(deviceId)
 0139                    {
 0140                        CustomName = row.IsDBNull(1) ? null : row.GetString(1)
 0141                    });
 142                }
 143
 0144                dbContext.SaveChanges();
 145            }
 146
 147            try
 148            {
 0149                File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
 150
 0151                var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
 0152                if (File.Exists(journalPath))
 153                {
 0154                    File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
 155                }
 0156            }
 0157            catch (IOException e)
 158            {
 0159                _logger.LogError(e, "Error renaming legacy activity log database to 'authentication.db.old'");
 0160            }
 0161        }
 162    }
 163}