MigrateAuthenticationDb.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using Emby.Server.Implementations.Data;
  5. using Jellyfin.Database.Implementations;
  6. using Jellyfin.Database.Implementations.Entities.Security;
  7. using MediaBrowser.Controller;
  8. using MediaBrowser.Controller.Library;
  9. using Microsoft.Data.Sqlite;
  10. using Microsoft.EntityFrameworkCore;
  11. using Microsoft.Extensions.Logging;
  12. namespace Jellyfin.Server.Migrations.Routines
  13. {
  14. /// <summary>
  15. /// A migration that moves data from the authentication database into the new schema.
  16. /// </summary>
  17. #pragma warning disable CS0618 // Type or member is obsolete
  18. [JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22")]
  19. public class MigrateAuthenticationDb : IMigrationRoutine
  20. #pragma warning restore CS0618 // Type or member is obsolete
  21. {
  22. private const string DbFilename = "authentication.db";
  23. private readonly ILogger<MigrateAuthenticationDb> _logger;
  24. private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
  25. private readonly IServerApplicationPaths _appPaths;
  26. private readonly IUserManager _userManager;
  27. /// <summary>
  28. /// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
  29. /// </summary>
  30. /// <param name="logger">The logger.</param>
  31. /// <param name="dbProvider">The database provider.</param>
  32. /// <param name="appPaths">The server application paths.</param>
  33. /// <param name="userManager">The user manager.</param>
  34. public MigrateAuthenticationDb(
  35. ILogger<MigrateAuthenticationDb> logger,
  36. IDbContextFactory<JellyfinDbContext> dbProvider,
  37. IServerApplicationPaths appPaths,
  38. IUserManager userManager)
  39. {
  40. _logger = logger;
  41. _dbProvider = dbProvider;
  42. _appPaths = appPaths;
  43. _userManager = userManager;
  44. }
  45. /// <inheritdoc />
  46. public void Perform()
  47. {
  48. var dataPath = _appPaths.DataPath;
  49. var dbFilePath = Path.Combine(dataPath, DbFilename);
  50. if (!File.Exists(dbFilePath))
  51. {
  52. _logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath);
  53. return;
  54. }
  55. using (var connection = new SqliteConnection($"Filename={dbFilePath}"))
  56. {
  57. connection.Open();
  58. var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Tokens';");
  59. foreach (var row in tableQuery)
  60. {
  61. if (row.GetInt32(0) == 0)
  62. {
  63. _logger.LogWarning("Table 'Tokens' doesn't exist in {Path}, nothing to migrate", dbFilePath);
  64. return;
  65. }
  66. }
  67. using var dbContext = _dbProvider.CreateDbContext();
  68. var authenticatedDevices = connection.Query("SELECT * FROM Tokens");
  69. foreach (var row in authenticatedDevices)
  70. {
  71. var dateCreatedStr = row.GetString(9);
  72. _ = DateTime.TryParse(dateCreatedStr, out var dateCreated);
  73. var dateLastActivityStr = row.GetString(10);
  74. _ = DateTime.TryParse(dateLastActivityStr, out var dateLastActivity);
  75. if (row.IsDBNull(6))
  76. {
  77. dbContext.ApiKeys.Add(new ApiKey(row.GetString(3))
  78. {
  79. AccessToken = row.GetString(1),
  80. DateCreated = dateCreated,
  81. DateLastActivity = dateLastActivity
  82. });
  83. }
  84. else
  85. {
  86. var userId = row.GetGuid(6);
  87. var user = _userManager.GetUserById(userId);
  88. if (user is null)
  89. {
  90. // User doesn't exist, don't bring over the device.
  91. continue;
  92. }
  93. dbContext.Devices.Add(new Device(
  94. userId,
  95. row.GetString(3),
  96. row.GetString(4),
  97. row.GetString(5),
  98. row.GetString(2))
  99. {
  100. AccessToken = row.GetString(1),
  101. IsActive = row.GetBoolean(8),
  102. DateCreated = dateCreated,
  103. DateLastActivity = dateLastActivity
  104. });
  105. }
  106. }
  107. var deviceOptions = connection.Query("SELECT * FROM Devices");
  108. var deviceIds = new HashSet<string>();
  109. foreach (var row in deviceOptions)
  110. {
  111. if (row.IsDBNull(2))
  112. {
  113. continue;
  114. }
  115. var deviceId = row.GetString(2);
  116. if (deviceIds.Contains(deviceId))
  117. {
  118. continue;
  119. }
  120. deviceIds.Add(deviceId);
  121. dbContext.DeviceOptions.Add(new DeviceOptions(deviceId)
  122. {
  123. CustomName = row.IsDBNull(1) ? null : row.GetString(1)
  124. });
  125. }
  126. dbContext.SaveChanges();
  127. }
  128. try
  129. {
  130. File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
  131. var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
  132. if (File.Exists(journalPath))
  133. {
  134. File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
  135. }
  136. }
  137. catch (IOException e)
  138. {
  139. _logger.LogError(e, "Error renaming legacy activity log database to 'authentication.db.old'");
  140. }
  141. }
  142. }
  143. }