MigrateActivityLogDb.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using Emby.Server.Implementations.Data;
  5. using Jellyfin.Data.Entities;
  6. using Jellyfin.Server.Implementations;
  7. using MediaBrowser.Controller;
  8. using Microsoft.EntityFrameworkCore;
  9. using Microsoft.Extensions.Logging;
  10. using SQLitePCL.pretty;
  11. namespace Jellyfin.Server.Migrations.Routines
  12. {
  13. /// <summary>
  14. /// The migration routine for migrating the activity log database to EF Core.
  15. /// </summary>
  16. public class MigrateActivityLogDb : IMigrationRoutine
  17. {
  18. private const string DbFilename = "activitylog.db";
  19. private readonly ILogger<MigrateActivityLogDb> _logger;
  20. private readonly JellyfinDbProvider _provider;
  21. private readonly IServerApplicationPaths _paths;
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="MigrateActivityLogDb"/> class.
  24. /// </summary>
  25. /// <param name="logger">The logger.</param>
  26. /// <param name="paths">The server application paths.</param>
  27. /// <param name="provider">The database provider.</param>
  28. public MigrateActivityLogDb(ILogger<MigrateActivityLogDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider)
  29. {
  30. _logger = logger;
  31. _provider = provider;
  32. _paths = paths;
  33. }
  34. /// <inheritdoc/>
  35. public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978");
  36. /// <inheritdoc/>
  37. public string Name => "MigrateActivityLogDatabase";
  38. /// <inheritdoc/>
  39. public bool PerformOnNewInstall => false;
  40. /// <inheritdoc/>
  41. public void Perform()
  42. {
  43. var logLevelDictionary = new Dictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase)
  44. {
  45. { "None", LogLevel.None },
  46. { "Trace", LogLevel.Trace },
  47. { "Debug", LogLevel.Debug },
  48. { "Information", LogLevel.Information },
  49. { "Info", LogLevel.Information },
  50. { "Warn", LogLevel.Warning },
  51. { "Warning", LogLevel.Warning },
  52. { "Error", LogLevel.Error },
  53. { "Critical", LogLevel.Critical }
  54. };
  55. var dataPath = _paths.DataPath;
  56. using (var connection = SQLite3.Open(
  57. Path.Combine(dataPath, DbFilename),
  58. ConnectionFlags.ReadOnly,
  59. null))
  60. {
  61. using var userDbConnection = SQLite3.Open(Path.Combine(dataPath, "users.db"), ConnectionFlags.ReadOnly, null);
  62. _logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin.");
  63. using var dbContext = _provider.CreateContext();
  64. var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id");
  65. // Make sure that the database is empty in case of failed migration due to power outages, etc.
  66. dbContext.ActivityLogs.RemoveRange(dbContext.ActivityLogs);
  67. dbContext.SaveChanges();
  68. // Reset the autoincrement counter
  69. dbContext.Database.ExecuteSqlRaw("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'ActivityLog';");
  70. dbContext.SaveChanges();
  71. var newEntries = new List<ActivityLog>();
  72. foreach (var entry in queryResult)
  73. {
  74. if (!logLevelDictionary.TryGetValue(entry[8].ToString(), out var severity))
  75. {
  76. severity = LogLevel.Trace;
  77. }
  78. var guid = Guid.Empty;
  79. if (entry[6].SQLiteType != SQLiteType.Null && !Guid.TryParse(entry[6].ToString(), out guid))
  80. {
  81. // This is not a valid Guid, see if it is an internal ID from an old Emby schema
  82. _logger.LogWarning("Invalid Guid in UserId column: {Guid}", entry[6].ToString());
  83. using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id");
  84. statement.TryBind("@Id", entry[6].ToString());
  85. foreach (var row in statement.Query())
  86. {
  87. if (row.Count > 0 && Guid.TryParse(row[0].ToString(), out guid))
  88. {
  89. // Successfully parsed a Guid from the user table.
  90. break;
  91. }
  92. }
  93. }
  94. var newEntry = new ActivityLog(entry[1].ToString(), entry[4].ToString(), guid)
  95. {
  96. DateCreated = entry[7].ReadDateTime(),
  97. LogSeverity = severity
  98. };
  99. if (entry[2].SQLiteType != SQLiteType.Null)
  100. {
  101. newEntry.Overview = entry[2].ToString();
  102. }
  103. if (entry[3].SQLiteType != SQLiteType.Null)
  104. {
  105. newEntry.ShortOverview = entry[3].ToString();
  106. }
  107. if (entry[5].SQLiteType != SQLiteType.Null)
  108. {
  109. newEntry.ItemId = entry[5].ToString();
  110. }
  111. newEntries.Add(newEntry);
  112. }
  113. dbContext.ActivityLogs.AddRange(newEntries);
  114. dbContext.SaveChanges();
  115. }
  116. try
  117. {
  118. File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
  119. var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
  120. if (File.Exists(journalPath))
  121. {
  122. File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
  123. }
  124. }
  125. catch (IOException e)
  126. {
  127. _logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'");
  128. }
  129. }
  130. }
  131. }