|
|
|
# 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 = 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
|
|
|
|
|
|
|
|
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
|