upgrade.rb 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. # frozen_string_literal: true
  2. require_relative 'base'
  3. module Mastodon::CLI
  4. class Upgrade < Base
  5. CURRENT_STORAGE_SCHEMA_VERSION = 1
  6. option :dry_run, type: :boolean, default: false
  7. option :verbose, type: :boolean, default: false, aliases: [:v]
  8. desc 'storage-schema', 'Upgrade storage schema of various file attachments to the latest version'
  9. long_desc <<~LONG_DESC
  10. Iterates over every file attachment of every record and, if its storage schema is outdated, performs the
  11. necessary upgrade to the latest one. In practice this means e.g. moving files to different directories.
  12. Will most likely take a long time.
  13. LONG_DESC
  14. def storage_schema
  15. progress = create_progress_bar(nil)
  16. records = 0
  17. klasses = [
  18. Account,
  19. CustomEmoji,
  20. MediaAttachment,
  21. PreviewCard,
  22. ]
  23. klasses.each do |klass|
  24. attachment_names = klass.attachment_definitions.keys
  25. klass.find_each do |record|
  26. attachment_names.each do |attachment_name|
  27. attachment = record.public_send(attachment_name)
  28. upgraded = false
  29. next if attachment.blank? || attachment.storage_schema_version >= CURRENT_STORAGE_SCHEMA_VERSION
  30. styles = attachment.styles.keys
  31. styles << :original unless styles.include?(:original)
  32. styles.each do |style|
  33. success = case Paperclip::Attachment.default_options[:storage]
  34. when :s3
  35. upgrade_storage_s3(progress, attachment, style)
  36. when :fog
  37. upgrade_storage_fog(progress, attachment, style)
  38. when :azure
  39. upgrade_storage_azure(progress, attachment, style)
  40. when :filesystem
  41. upgrade_storage_filesystem(progress, attachment, style)
  42. end
  43. upgraded = true if style == :original && success
  44. progress.increment
  45. end
  46. attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION) if upgraded
  47. end
  48. if record.changed?
  49. record.save unless dry_run?
  50. records += 1
  51. end
  52. end
  53. end
  54. progress.total = progress.progress
  55. progress.finish
  56. say("Upgraded storage schema of #{records} records#{dry_run_mode_suffix}", :green, true)
  57. end
  58. private
  59. def upgrade_storage_s3(progress, attachment, style)
  60. previous_storage_schema_version = attachment.storage_schema_version
  61. object = attachment.s3_object(style)
  62. success = true
  63. attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION)
  64. new_object = attachment.s3_object(style)
  65. if new_object.key != object.key && object.exists?
  66. progress.log("Moving #{object.key} to #{new_object.key}") if options[:verbose]
  67. begin
  68. object.move_to(new_object, acl: attachment.s3_permissions(style)) unless dry_run?
  69. rescue => e
  70. progress.log(pastel.red("Error processing #{object.key}: #{e}"))
  71. success = false
  72. end
  73. end
  74. # Because we move files style-by-style, it's important to restore
  75. # previous version at the end. The upgrade will be recorded after
  76. # all styles are updated
  77. attachment.instance_write(:storage_schema_version, previous_storage_schema_version)
  78. success
  79. end
  80. def upgrade_storage_fog(_progress, _attachment, _style)
  81. say('The fog storage driver is not supported for this operation at this time', :red)
  82. exit(1)
  83. end
  84. def upgrade_storage_azure(_progress, _attachment, _style)
  85. say('The azure storage driver is not supported for this operation at this time', :red)
  86. exit(1)
  87. end
  88. def upgrade_storage_filesystem(progress, attachment, style)
  89. previous_storage_schema_version = attachment.storage_schema_version
  90. previous_path = attachment.path(style)
  91. success = true
  92. attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION)
  93. upgraded_path = attachment.path(style)
  94. if upgraded_path != previous_path && File.exist?(previous_path)
  95. progress.log("Moving #{previous_path} to #{upgraded_path}") if options[:verbose]
  96. begin
  97. unless dry_run?
  98. FileUtils.mkdir_p(File.dirname(upgraded_path))
  99. FileUtils.mv(previous_path, upgraded_path)
  100. begin
  101. FileUtils.rmdir(File.dirname(previous_path), parents: true)
  102. rescue Errno::ENOTEMPTY
  103. # OK
  104. end
  105. end
  106. rescue => e
  107. progress.log(pastel.red("Error processing #{previous_path}: #{e}"))
  108. success = false
  109. unless dry_run?
  110. begin
  111. FileUtils.rmdir(File.dirname(upgraded_path), parents: true)
  112. rescue Errno::ENOTEMPTY
  113. # OK
  114. end
  115. end
  116. end
  117. end
  118. # Because we move files style-by-style, it's important to restore
  119. # previous version at the end. The upgrade will be recorded after
  120. # all styles are updated
  121. attachment.instance_write(:storage_schema_version, previous_storage_schema_version)
  122. success
  123. end
  124. end
  125. end