Files
wjones ede6904433 Add Jellyfin.CodeAnalysis and analyzers to all projects
Integrated Jellyfin.CodeAnalysis as a project reference across core and test projects. Added centrally managed code analysis and style enforcement packages (IDisposableAnalyzers, Microsoft.CodeAnalysis.BannedApiAnalyzers, SerilogAnalyzer, SmartAnalyzers.MultithreadingAnalyzer, StyleCop.Analyzers) to all affected projects. Updated dependency graphs, asset files, and build scripts to ensure analyzers run during builds, enforcing consistent code quality and style rules throughout the codebase. Removed custom warning properties to rely on analyzer enforcement.
2026-02-23 07:03:54 -05:00

167 lines
6.3 KiB
C#

// <copyright file="MigrateAuthenticationDb.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace Jellyfin.Server.Migrations.Routines
{
using System;
using System.Collections.Generic;
using System.IO;
using Emby.Server.Implementations.Data;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities.Security;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
/// <summary>
/// A migration that moves data from the authentication database into the new schema.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
[JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22", RequiresSqlite = true)]
public class MigrateAuthenticationDb : IMigrationRoutine
#pragma warning restore CS0618 // Type or member is obsolete
{
private const string DbFilename = "authentication.db";
private readonly ILogger<MigrateAuthenticationDb> _logger;
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IServerApplicationPaths _appPaths;
private readonly IUserManager _userManager;
/// <summary>
/// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="dbProvider">The database provider.</param>
/// <param name="appPaths">The server application paths.</param>
/// <param name="userManager">The user manager.</param>
public MigrateAuthenticationDb(
ILogger<MigrateAuthenticationDb> logger,
IDbContextFactory<JellyfinDbContext> dbProvider,
IServerApplicationPaths appPaths,
IUserManager userManager)
{
_logger = logger;
_dbProvider = dbProvider;
_appPaths = appPaths;
_userManager = userManager;
}
/// <inheritdoc />
public void Perform()
{
var dataPath = _appPaths.DataPath;
var dbFilePath = Path.Combine(dataPath, DbFilename);
if (!File.Exists(dbFilePath))
{
_logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath);
return;
}
using (var connection = new SqliteConnection($"Filename={dbFilePath}"))
{
connection.Open();
var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Tokens';");
foreach (var row in tableQuery)
{
if (row.GetInt32(0) == 0)
{
_logger.LogWarning("Table 'Tokens' doesn't exist in {Path}, nothing to migrate", dbFilePath);
return;
}
}
using var dbContext = _dbProvider.CreateDbContext();
var authenticatedDevices = connection.Query("SELECT * FROM Tokens");
foreach (var row in authenticatedDevices)
{
var dateCreatedStr = row.GetString(9);
_ = DateTime.TryParse(dateCreatedStr, out var dateCreated);
var dateLastActivityStr = row.GetString(10);
_ = DateTime.TryParse(dateLastActivityStr, out var dateLastActivity);
if (row.IsDBNull(6))
{
dbContext.ApiKeys.Add(new ApiKey(row.GetString(3))
{
AccessToken = row.GetString(1),
DateCreated = dateCreated,
DateLastActivity = dateLastActivity
});
}
else
{
var userId = row.GetGuid(6);
var user = _userManager.GetUserById(userId);
if (user is null)
{
// User doesn't exist, don't bring over the device.
continue;
}
dbContext.Devices.Add(new Device(
userId,
row.GetString(3),
row.GetString(4),
row.GetString(5),
row.GetString(2))
{
AccessToken = row.GetString(1),
IsActive = row.GetBoolean(8),
DateCreated = dateCreated,
DateLastActivity = dateLastActivity
});
}
}
var deviceOptions = connection.Query("SELECT * FROM Devices");
var deviceIds = new HashSet<string>();
foreach (var row in deviceOptions)
{
if (row.IsDBNull(2))
{
continue;
}
var deviceId = row.GetString(2);
if (deviceIds.Contains(deviceId))
{
continue;
}
deviceIds.Add(deviceId);
dbContext.DeviceOptions.Add(new DeviceOptions(deviceId)
{
CustomName = row.IsDBNull(1) ? null : row.GetString(1)
});
}
dbContext.SaveChanges();
}
try
{
File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
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 activity log database to 'authentication.db.old'");
}
}
}
}