163 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # 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
 |