RemoveDuplicateExtras.cs 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. using System;
  2. using System.Globalization;
  3. using System.IO;
  4. using System.Linq;
  5. using Emby.Server.Implementations.Data;
  6. using MediaBrowser.Controller;
  7. using Microsoft.Data.Sqlite;
  8. using Microsoft.Extensions.Logging;
  9. namespace Jellyfin.Server.Migrations.Routines;
  10. /// <summary>
  11. /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself.
  12. /// </summary>
  13. #pragma warning disable CS0618 // Type or member is obsolete
  14. [JellyfinMigration("2025-04-20T08:00:00", nameof(RemoveDuplicateExtras), "ACBE17B7-8435-4A83-8B64-6FCF162CB9BD")]
  15. internal class RemoveDuplicateExtras : IMigrationRoutine
  16. #pragma warning restore CS0618 // Type or member is obsolete
  17. {
  18. private const string DbFilename = "library.db";
  19. private readonly ILogger<RemoveDuplicateExtras> _logger;
  20. private readonly IServerApplicationPaths _paths;
  21. public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths)
  22. {
  23. _logger = logger;
  24. _paths = paths;
  25. }
  26. /// <inheritdoc/>
  27. public void Perform()
  28. {
  29. var dataPath = _paths.DataPath;
  30. var dbPath = Path.Combine(dataPath, DbFilename);
  31. using var connection = new SqliteConnection($"Filename={dbPath}");
  32. connection.Open();
  33. using (var transaction = connection.BeginTransaction())
  34. {
  35. // Query the database for the ids of duplicate extras
  36. var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
  37. var bads = string.Join(", ", queryResult.Select(x => x.GetString(0)));
  38. // Do nothing if no duplicate extras were detected
  39. if (bads.Length == 0)
  40. {
  41. _logger.LogInformation("No duplicate extras detected, skipping migration.");
  42. return;
  43. }
  44. // Back up the database before deleting any entries
  45. for (int i = 1; ; i++)
  46. {
  47. var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
  48. if (!File.Exists(bakPath))
  49. {
  50. try
  51. {
  52. File.Copy(dbPath, bakPath);
  53. _logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
  54. break;
  55. }
  56. catch (Exception ex)
  57. {
  58. _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
  59. throw;
  60. }
  61. }
  62. }
  63. // Delete all duplicate extras
  64. _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
  65. connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
  66. transaction.Commit();
  67. }
  68. }
  69. }