# frozen_string_literal: true require_relative '../../config/boot' require_relative '../../config/environment' require_relative 'cli_helper' module Mastodon class UpgradeCLI < Thor include CLIHelper def self.exit_on_failure? true end CURRENT_STORAGE_SCHEMA_VERSION = 1 option :dry_run, type: :boolean, default: false option :verbose, type: :boolean, default: false, aliases: [:v] desc 'storage-schema', 'Upgrade storage schema of various file attachments to the latest version' long_desc <<~LONG_DESC Iterates over every file attachment of every record and, if its storage schema is outdated, performs the necessary upgrade to the latest one. In practice this means e.g. moving files to different directories. Will most likely take a long time. LONG_DESC def storage_schema progress = create_progress_bar(nil) dry_run = dry_run? ? ' (DRY RUN)' : '' records = 0 klasses = [ Account, CustomEmoji, MediaAttachment, PreviewCard, ] klasses.each do |klass| attachment_names = klass.attachment_definitions.keys klass.find_each do |record| attachment_names.each do |attachment_name| attachment = record.public_send(attachment_name) upgraded = false next if attachment.blank? || attachment.storage_schema_version >= CURRENT_STORAGE_SCHEMA_VERSION styles = attachment.styles.keys styles << :original unless styles.include?(:original) styles.each do |style| success = begin case Paperclip::Attachment.default_options[:storage] when :s3 upgrade_storage_s3(progress, attachment, style) when :fog upgrade_storage_fog(progress, attachment, style) when :filesystem upgrade_storage_filesystem(progress, attachment, style) end end upgraded = true if style == :original && success progress.increment end attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION) if upgraded end if record.changed? record.save unless dry_run? records += 1 end end end progress.total = progress.progress progress.finish say("Upgraded storage schema of #{records} records#{dry_run}", :green, true) end private def upgrade_storage_s3(progress, attachment, style) previous_storage_schema_version = attachment.storage_schema_version object = attachment.s3_object(style) success = true attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION) new_object = attachment.s3_object(style) if new_object.key != object.key && object.exists? progress.log("Moving #{object.key} to #{new_object.key}") if options[:verbose] begin object.move_to(new_object, acl: attachment.s3_permissions(style)) unless dry_run? rescue => e progress.log(pastel.red("Error processing #{object.key}: #{e}")) success = false end end # Because we move files style-by-style, it's important to restore # previous version at the end. The upgrade will be recorded after # all styles are updated attachment.instance_write(:storage_schema_version, previous_storage_schema_version) success end def upgrade_storage_fog(_progress, _attachment, _style) say('The fog storage driver is not supported for this operation at this time', :red) exit(1) end def upgrade_storage_filesystem(progress, attachment, style) previous_storage_schema_version = attachment.storage_schema_version previous_path = attachment.path(style) success = true attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION) upgraded_path = attachment.path(style) if upgraded_path != previous_path && File.exist?(previous_path) progress.log("Moving #{previous_path} to #{upgraded_path}") if options[:verbose] begin unless dry_run? FileUtils.mkdir_p(File.dirname(upgraded_path)) FileUtils.mv(previous_path, upgraded_path) begin FileUtils.rmdir(File.dirname(previous_path), parents: true) rescue Errno::ENOTEMPTY # OK end end rescue => e progress.log(pastel.red("Error processing #{previous_path}: #{e}")) success = false unless dry_run? begin FileUtils.rmdir(File.dirname(upgraded_path), parents: true) rescue Errno::ENOTEMPTY # OK end end end end # Because we move files style-by-style, it's important to restore # previous version at the end. The upgrade will be recorded after # all styles are updated attachment.instance_write(:storage_schema_version, previous_storage_schema_version) success end end end