From 4e524218d3086e4f930e2005f1ce2d82f29e1a20 Mon Sep 17 00:00:00 2001 From: tateisu Date: Mon, 9 Mar 2020 00:01:07 +0900 Subject: Add `--skip-media-remove` option to `tootctl statuses remove` (#13080) * Add skip_media_remove option to tootctl statuses remove * Add skip_media_remove option to tootctl statuses remove Co-authored-by: tateisu --- lib/mastodon/statuses_cli.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/mastodon/statuses_cli.rb b/lib/mastodon/statuses_cli.rb index ecaac17e3..b9dccdd8a 100644 --- a/lib/mastodon/statuses_cli.rb +++ b/lib/mastodon/statuses_cli.rb @@ -14,6 +14,7 @@ module Mastodon option :days, type: :numeric, default: 90 option :clean_followed, type: :boolean + option :skip_media_remove, type: :boolean desc 'remove', 'Remove unreferenced statuses' long_desc <<~LONG_DESC Remove statuses that are not referenced by local user activity, such as @@ -58,9 +59,10 @@ module Mastodon scope.in_batches.delete_all - say('Beginning removal of now-orphaned media attachments to free up disk space...') - - Scheduler::MediaCleanupScheduler.new.perform + unless options[:skip_media_remove] + say('Beginning removal of now-orphaned media attachments to free up disk space...') + Scheduler::MediaCleanupScheduler.new.perform + end say("Done after #{Time.now.to_f - start_at}s", :green) ensure -- cgit From 9660aa4543deff41c60d131e081137f84e771499 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 8 Mar 2020 23:56:18 +0100 Subject: Change local media attachments to perform heavy processing asynchronously (#13210) Fix #9106 --- app/controllers/api/v1/media_controller.rb | 29 ++++++++++++++---- app/controllers/api/v2/media_controller.rb | 12 ++++++++ app/javascript/mastodon/actions/compose.js | 23 +++++++++++++-- app/models/concerns/attachmentable.rb | 2 +- app/models/media_attachment.rb | 26 +++++++++++++++-- .../rest/media_attachment_serializer.rb | 4 ++- app/services/post_status_service.rb | 1 + app/workers/backup_worker.rb | 8 +++-- app/workers/post_process_media_worker.rb | 34 ++++++++++++++++++++++ config/application.rb | 1 + config/locales/en.yml | 1 + config/routes.rb | 3 +- ...06035625_add_processing_to_media_attachments.rb | 5 ++++ db/schema.rb | 3 +- lib/paperclip/attachment_extensions.rb | 30 +++++++++++++++++++ 15 files changed, 165 insertions(+), 17 deletions(-) create mode 100644 app/controllers/api/v2/media_controller.rb create mode 100644 app/workers/post_process_media_worker.rb create mode 100644 db/migrate/20200306035625_add_processing_to_media_attachments.rb create mode 100644 lib/paperclip/attachment_extensions.rb (limited to 'lib') diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb index d87d7b946..0bb3d0d27 100644 --- a/app/controllers/api/v1/media_controller.rb +++ b/app/controllers/api/v1/media_controller.rb @@ -3,25 +3,42 @@ class Api::V1::MediaController < Api::BaseController before_action -> { doorkeeper_authorize! :write, :'write:media' } before_action :require_user! + before_action :set_media_attachment, except: [:create] + before_action :check_processing, except: [:create] def create - @media = current_account.media_attachments.create!(media_params) - render json: @media, serializer: REST::MediaAttachmentSerializer + @media_attachment = current_account.media_attachments.create!(media_attachment_params) + render json: @media_attachment, serializer: REST::MediaAttachmentSerializer rescue Paperclip::Errors::NotIdentifiedByImageMagickError render json: file_type_error, status: 422 rescue Paperclip::Error render json: processing_error, status: 500 end + def show + render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment + end + def update - @media = current_account.media_attachments.where(status_id: nil).find(params[:id]) - @media.update!(media_params) - render json: @media, serializer: REST::MediaAttachmentSerializer + @media_attachment.update!(media_attachment_params) + render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment end private - def media_params + def status_code_for_media_attachment + @media_attachment.not_processed? ? 206 : 200 + end + + def set_media_attachment + @media_attachment = current_account.media_attachments.unattached.find(params[:id]) + end + + def check_processing + render json: processing_error, status: 422 if @media_attachment.processing_failed? + end + + def media_attachment_params params.permit(:file, :description, :focus) end diff --git a/app/controllers/api/v2/media_controller.rb b/app/controllers/api/v2/media_controller.rb new file mode 100644 index 000000000..0c1baf01d --- /dev/null +++ b/app/controllers/api/v2/media_controller.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Api::V2::MediaController < Api::V1::MediaController + def create + @media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params)) + render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: 202 + rescue Paperclip::Errors::NotIdentifiedByImageMagickError + render json: file_type_error, status: 422 + rescue Paperclip::Error + render json: processing_error, status: 500 + end +end diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index c3c6ff1a1..68ef8fbd5 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -230,12 +230,31 @@ export function uploadCompose(files) { // Account for disparity in size of original image and resized data total += file.size - f.size; - return api(getState).post('/api/v1/media', data, { + return api(getState).post('/api/v2/media', data, { onUploadProgress: function({ loaded }){ progress[i] = loaded; dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); }, - }).then(({ data }) => dispatch(uploadComposeSuccess(data, f))); + }).then(({ status, data }) => { + // If server-side processing of the media attachment has not completed yet, + // poll the server until it is, before showing the media attachment as uploaded + + if (status === 200) { + dispatch(uploadComposeSuccess(data, f)); + } else if (status === 202) { + const poll = () => { + api(getState).get(`/api/v1/media/${data.id}`).then(response => { + if (response.status === 200) { + dispatch(uploadComposeSuccess(data, f)); + } else if (response.status === 206) { + setTimeout(() => poll(), 1000); + } + }).catch(error => dispatch(uploadComposeFail(error))); + }; + + poll(); + } + }); }).catch(error => dispatch(uploadComposeFail(error))); }; }; diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb index 43ff8ac12..18b872c1e 100644 --- a/app/models/concerns/attachmentable.rb +++ b/app/models/concerns/attachmentable.rb @@ -74,7 +74,7 @@ module Attachmentable self.class.attachment_definitions.each_key do |attachment_name| attachment = send(attachment_name) - next if attachment.blank? || attachment.queued_for_write[:original].blank? + next if attachment.blank? || attachment.queued_for_write[:original].blank? || attachment.options[:preserve_files] attachment.instance_write :file_name, SecureRandom.hex(8) + File.extname(attachment.instance_read(:file_name)) end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 1e36625ca..3fd4f231c 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -19,12 +19,14 @@ # description :text # scheduled_status_id :bigint(8) # blurhash :string +# processing :integer # class MediaAttachment < ApplicationRecord self.inheritance_column = nil enum type: [:image, :gifv, :video, :unknown, :audio] + enum processing: [:queued, :in_progress, :complete, :failed], _prefix: true MAX_DESCRIPTION_LENGTH = 1_500 @@ -156,6 +158,10 @@ class MediaAttachment < ApplicationRecord remote_url.blank? end + def not_processed? + processing.present? && !processing_complete? + end + def needs_redownload? file.blank? && remote_url.present? end @@ -203,10 +209,18 @@ class MediaAttachment < ApplicationRecord "#{x},#{y}" end + attr_writer :delay_processing + + def delay_processing? + @delay_processing + end + + after_commit :enqueue_processing, on: :create after_commit :reset_parent_cache, on: :update before_create :prepare_description, unless: :local? before_create :set_shortcode + before_create :set_processing before_post_process :set_type_and_extension @@ -277,6 +291,10 @@ class MediaAttachment < ApplicationRecord end end + def set_processing + self.processing = delay_processing? ? :queued : :complete + end + def set_meta meta = populate_meta @@ -322,9 +340,11 @@ class MediaAttachment < ApplicationRecord }.compact end - def reset_parent_cache - return if status_id.nil? + def enqueue_processing + PostProcessMediaWorker.perform_async(id) if delay_processing? + end - Rails.cache.delete("statuses/#{status_id}") + def reset_parent_cache + Rails.cache.delete("statuses/#{status_id}") if status_id.present? end end diff --git a/app/serializers/rest/media_attachment_serializer.rb b/app/serializers/rest/media_attachment_serializer.rb index 1b3498ea4..cc10e3001 100644 --- a/app/serializers/rest/media_attachment_serializer.rb +++ b/app/serializers/rest/media_attachment_serializer.rb @@ -12,7 +12,9 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer end def url - if object.needs_redownload? + if object.not_processed? + nil + elsif object.needs_redownload? media_proxy_url(object.id, :original) else full_asset_url(object.file.url(:original)) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 2301621ab..c61b3baa2 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -101,6 +101,7 @@ class PostStatusService < BaseService @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)) raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:audio_or_video?) + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if @media.any?(&:not_processed?) end def language_from_option(str) diff --git a/app/workers/backup_worker.rb b/app/workers/backup_worker.rb index e4c609d70..7b0b52844 100644 --- a/app/workers/backup_worker.rb +++ b/app/workers/backup_worker.rb @@ -9,8 +9,12 @@ class BackupWorker backup_id = msg['args'].first ActiveRecord::Base.connection_pool.with_connection do - backup = Backup.find(backup_id) - backup&.destroy + begin + backup = Backup.find(backup_id) + backup.destroy + rescue ActiveRecord::RecordNotFound + true + end end end diff --git a/app/workers/post_process_media_worker.rb b/app/workers/post_process_media_worker.rb new file mode 100644 index 000000000..d3ebda194 --- /dev/null +++ b/app/workers/post_process_media_worker.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class PostProcessMediaWorker + include Sidekiq::Worker + + sidekiq_options retry: 1, dead: false + + sidekiq_retries_exhausted do |msg| + media_attachment_id = msg['args'].first + + ActiveRecord::Base.connection_pool.with_connection do + begin + media_attachment = MediaAttachment.find(media_attachment_id) + media_attachment.processing = :failed + media_attachment.save + rescue ActiveRecord::RecordNotFound + true + end + end + + Sidekiq.logger.error("Processing media attachment #{media_attachment_id} failed with #{msg['error_message']}") + end + + def perform(media_attachment_id) + media_attachment = MediaAttachment.find(media_attachment_id) + media_attachment.processing = :in_progress + media_attachment.save + media_attachment.file.reprocess_original! + media_attachment.processing = :complete + media_attachment.save + rescue ActiveRecord::RecordNotFound + true + end +end diff --git a/config/application.rb b/config/application.rb index 1baa166ce..cd599eefc 100644 --- a/config/application.rb +++ b/config/application.rb @@ -7,6 +7,7 @@ require 'rails/all' Bundler.require(*Rails.groups) require_relative '../app/lib/exceptions' +require_relative '../lib/paperclip/attachment_extensions' require_relative '../lib/paperclip/lazy_thumbnail' require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/video_transcoder' diff --git a/config/locales/en.yml b/config/locales/en.yml index 8440b471c..a031f9e9d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -853,6 +853,7 @@ en: media_attachments: validations: images_and_video: Cannot attach a video to a status that already contains images + not_ready: Cannot attach files that have not finished processing. Try again in a moment! too_many: Cannot attach more than 4 files migrations: acct: Moved to diff --git a/config/routes.rb b/config/routes.rb index 2de31e2db..89d446d10 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -343,7 +343,7 @@ Rails.application.routes.draw do end end - resources :media, only: [:create, :update] + resources :media, only: [:create, :update, :show] resources :blocks, only: [:index] resources :mutes, only: [:index] resources :favourites, only: [:index] @@ -455,6 +455,7 @@ Rails.application.routes.draw do end namespace :v2 do + resources :media, only: [:create] get '/search', to: 'search#index', as: :search end diff --git a/db/migrate/20200306035625_add_processing_to_media_attachments.rb b/db/migrate/20200306035625_add_processing_to_media_attachments.rb new file mode 100644 index 000000000..131ffa52a --- /dev/null +++ b/db/migrate/20200306035625_add_processing_to_media_attachments.rb @@ -0,0 +1,5 @@ +class AddProcessingToMediaAttachments < ActiveRecord::Migration[5.2] + def change + add_column :media_attachments, :processing, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index b09ee0e76..ddb9a5d8c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_01_26_203551) do +ActiveRecord::Schema.define(version: 2020_03_06_035625) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -459,6 +459,7 @@ ActiveRecord::Schema.define(version: 2020_01_26_203551) do t.text "description" t.bigint "scheduled_status_id" t.string "blurhash" + t.integer "processing" t.index ["account_id"], name: "index_media_attachments_on_account_id" t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id" t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true diff --git a/lib/paperclip/attachment_extensions.rb b/lib/paperclip/attachment_extensions.rb new file mode 100644 index 000000000..3b308af5f --- /dev/null +++ b/lib/paperclip/attachment_extensions.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Paperclip + module AttachmentExtensions + # We overwrite this method to support delayed processing in + # Sidekiq. Since we process the original file to reduce disk + # usage, and we still want to generate thumbnails straight + # away, it's the only style we need to exclude + def process_style?(style_name, style_args) + if style_name == :original && instance.respond_to?(:delay_processing?) && instance.delay_processing? + false + else + style_args.empty? || style_args.include?(style_name) + end + end + + def reprocess_original! + old_original_path = path(:original) + reprocess!(:original) + new_original_path = path(:original) + + if new_original_path != old_original_path + @queued_for_delete << old_original_path + flush_deletes + end + end + end +end + +Paperclip::Attachment.prepend(Paperclip::AttachmentExtensions) -- cgit From abd839488054b6de2dec9e7ec095d79e4a106573 Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 9 Mar 2020 23:15:59 +0100 Subject: Fix MP4 (H264 + AAC) video files being needlessly re-encoded (#13239) --- app/models/media_attachment.rb | 18 +++++++++++++++++- lib/paperclip/video_transcoder.rb | 16 ++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 7c9b4b909..9ca0a6cda 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -77,6 +77,22 @@ class MediaAttachment < ApplicationRecord }, }.freeze + VIDEO_PASSTHROUGH_OPTIONS = { + video_codec_whitelist: ['h264'], + audio_codec_whitelist: ['aac', nil], + options: { + format: 'mp4', + convert_options: { + output: { + 'loglevel' => 'fatal', + 'map_metadata' => '-1', + 'c:v' => 'copy', + 'c:a' => 'copy', + }, + }, + }, + }.freeze + VIDEO_STYLES = { small: { convert_options: { @@ -91,7 +107,7 @@ class MediaAttachment < ApplicationRecord blurhash: BLURHASH_OPTIONS, }, - original: VIDEO_FORMAT, + original: VIDEO_FORMAT.merge(passthrough_options: VIDEO_PASSTHROUGH_OPTIONS), }.freeze AUDIO_STYLES = { diff --git a/lib/paperclip/video_transcoder.rb b/lib/paperclip/video_transcoder.rb index 66f7feda5..0de548964 100644 --- a/lib/paperclip/video_transcoder.rb +++ b/lib/paperclip/video_transcoder.rb @@ -5,12 +5,20 @@ module Paperclip # to check when uploaded videos are actually gifv's class VideoTranscoder < Paperclip::Processor def make - meta = ::Av.cli.identify(@file.path) + movie = FFMPEG::Movie.new(@file.path) + actual_options = options + passthrough_options = actual_options[:passthrough_options] + actual_options = passthrough_options[:options] if passthrough?(movie, passthrough_options) - attachment.instance.type = MediaAttachment.types[:gifv] unless meta[:audio_encode] - options[:format] = File.extname(attachment.instance.file_file_name)[1..-1] if options[:keep_same_format] + attachment.instance.type = MediaAttachment.types[:gifv] unless movie.audio_codec - Paperclip::Transcoder.make(file, options, attachment) + Paperclip::Transcoder.make(file, actual_options, attachment) + end + + private + + def passthrough?(movie, options) + options && options[:video_codec_whitelist].include?(movie.video_codec) && options[:audio_codec_whitelist].include?(movie.audio_codec) end end end -- cgit From 2c6099125d9ed5c2add62e84d5ba7b93de658910 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 10 Mar 2020 11:58:40 +0100 Subject: Fix videos with unsupported colorspace not being transcoded (#13242) --- app/models/media_attachment.rb | 5 +++-- lib/paperclip/video_transcoder.rb | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 9ca0a6cda..31aa918b7 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -78,8 +78,9 @@ class MediaAttachment < ApplicationRecord }.freeze VIDEO_PASSTHROUGH_OPTIONS = { - video_codec_whitelist: ['h264'], - audio_codec_whitelist: ['aac', nil], + video_codecs: ['h264'], + audio_codecs: ['aac', nil], + colorspaces: ['yuv420p'], options: { format: 'mp4', convert_options: { diff --git a/lib/paperclip/video_transcoder.rb b/lib/paperclip/video_transcoder.rb index 0de548964..4d9544231 100644 --- a/lib/paperclip/video_transcoder.rb +++ b/lib/paperclip/video_transcoder.rb @@ -6,19 +6,21 @@ module Paperclip class VideoTranscoder < Paperclip::Processor def make movie = FFMPEG::Movie.new(@file.path) - actual_options = options - passthrough_options = actual_options[:passthrough_options] - actual_options = passthrough_options[:options] if passthrough?(movie, passthrough_options) attachment.instance.type = MediaAttachment.types[:gifv] unless movie.audio_codec - Paperclip::Transcoder.make(file, actual_options, attachment) + Paperclip::Transcoder.make(file, actual_options(movie), attachment) end private - def passthrough?(movie, options) - options && options[:video_codec_whitelist].include?(movie.video_codec) && options[:audio_codec_whitelist].include?(movie.audio_codec) + def actual_options(movie) + opts = options[:passthrough_options] + if opts && opts[:video_codecs].include?(movie.video_codec) && opts[:audio_codecs].include?(movie.audio_codec) && opts[:colorspaces].include?(movie.colorspace) + opts[:options] + else + options + end end end end -- cgit From 0c8945e5ff9e75dd322ee2b2c4ea9cc1dc728911 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 26 Mar 2020 01:56:41 +0100 Subject: Change `tootctl media remove-orphans` to work for all classes (#13316) Change `tootctl media lookup` to not use an interactive prompt --- app/lib/activitypub/tag_manager.rb | 2 + app/models/media_attachment.rb | 13 ---- lib/mastodon/media_cli.rb | 111 ++++++++++++++++++++++++--------- lib/paperclip/attachment_extensions.rb | 13 ++++ 4 files changed, 98 insertions(+), 41 deletions(-) (limited to 'lib') diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index ed680d762..1523f86d4 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -15,6 +15,8 @@ class ActivityPub::TagManager def url_for(target) return target.url if target.respond_to?(:local?) && !target.local? + return unless target.respond_to?(:object_type) + case target.object_type when :person target.instance_actor? ? about_more_url(instance_actor: true) : short_account_url(target) diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 31aa918b7..f45e2c9f7 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -184,19 +184,6 @@ class MediaAttachment < ApplicationRecord audio? || video? end - def variant?(other_file_name) - return true if file_file_name == other_file_name - return false if file_file_name.nil? - - formats = file.styles.values.map(&:format).compact - - return false if formats.empty? - - extension = File.extname(other_file_name) - - formats.include?(extension.delete('.')) && File.basename(other_file_name, extension) == File.basename(file_file_name, File.extname(file_file_name)) - end - def to_param shortcode end diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb index d842b986f..b4ad78fe5 100644 --- a/lib/mastodon/media_cli.rb +++ b/lib/mastodon/media_cli.rb @@ -45,6 +45,7 @@ module Mastodon end option :start_after + option :prefix option :dry_run, type: :boolean, default: false desc 'remove-orphans', 'Scan storage and check for files that do not belong to existing media attachments' long_desc <<~LONG_DESC @@ -58,6 +59,7 @@ module Mastodon reclaimed_bytes = 0 removed = 0 dry_run = options[:dry_run] ? ' (DRY RUN)' : '' + prefix = options[:prefix] case Paperclip::Attachment.default_options[:storage] when :s3 @@ -69,7 +71,7 @@ module Mastodon loop do objects = begin begin - bucket.objects(start_after: last_key, prefix: 'media_attachments/files/').limit(1000).map { |x| x } + bucket.objects(start_after: last_key, prefix: prefix).limit(1000).map { |x| x } rescue => e progress.log(pastel.red("Error fetching list of files: #{e}")) progress.log("If you want to continue from this point, add --start-after=#{last_key} to your command") if last_key @@ -79,16 +81,21 @@ module Mastodon break if objects.empty? - last_key = objects.last.key - attachments_map = MediaAttachment.where(id: objects.map { |object| object.key.split('/')[2..-2].join.to_i }).each_with_object({}) { |attachment, map| map[attachment.id] = attachment } + last_key = objects.last.key + record_map = preload_records_from_mixed_objects(objects) objects.each do |object| - attachment_id = object.key.split('/')[2..-2].join.to_i - filename = object.key.split('/').last + path_segments = object.key.split('/') + model_name = path_segments.first.classify + attachment_name = path_segments[1].singularize + record_id = path_segments[2..-2].join.to_i + file_name = path_segments.last + record = record_map.dig(model_name, record_id) + attachment = record&.public_send(attachment_name) progress.increment - next unless attachments_map[attachment_id].nil? || !attachments_map[attachment_id].variant?(filename) + next unless attachment.blank? || !attachment.variant?(file_name) begin object.delete unless options[:dry_run] @@ -110,17 +117,24 @@ module Mastodon root_path = ENV.fetch('RAILS_ROOT_PATH', File.join(':rails_root', 'public', 'system')).gsub(':rails_root', Rails.root.to_s) - Find.find(File.join(root_path, 'media_attachments', 'files')) do |path| + Find.find(File.join(*[root_path, prefix].compact)) do |path| next if File.directory?(path) - key = path.gsub("#{root_path}#{File::SEPARATOR}", '') - attachment_id = key.split(File::SEPARATOR)[2..-2].join.to_i - filename = key.split(File::SEPARATOR).last - attachment = MediaAttachment.find_by(id: attachment_id) + key = path.gsub("#{root_path}#{File::SEPARATOR}", '') + path_segments = key.split(File::SEPARATOR) + model_name = path_segments.first.classify + record_id = path_segments[2..-2].join.to_i + attachment_name = path_segments[1].singularize + file_name = path_segments.last + + next unless PRELOAD_MODEL_WHITELIST.include?(model_name) + + record = model_name.constantize.find_by(id: record_id) + attachment = record&.public_send(attachment_name) progress.increment - next unless attachment.nil? || !attachment.variant?(filename) + next unless attachment.blank? || !attachment.variant?(file_name) begin size = File.size(path) @@ -213,25 +227,66 @@ module Mastodon say("Settings:\t#{number_to_human_size(SiteUpload.sum(:file_file_size))}") end - desc 'lookup', 'Lookup where media is displayed by passing a media URL' - def lookup - prompt = TTY::Prompt.new + desc 'lookup URL', 'Lookup where media is displayed by passing a media URL' + def lookup(url) + path = Addressable::URI.parse(url).path + path_segments = path.split('/')[2..-1] + model_name = path_segments.first.classify + record_id = path_segments[2..-2].join.to_i - url = prompt.ask('Please enter a URL to the media to lookup:', required: true) + unless PRELOAD_MODEL_WHITELIST.include?(model_name) + say("Cannot find corresponding model: #{model_name}", :red) + exit(1) + end - attachment_id = url - .split('/')[0..-2] - .grep(/\A\d+\z/) - .join('') + record = model_name.constantize.find_by(id: record_id) + record = record.status if record.respond_to?(:status) - if url.split('/')[0..-2].include? 'media_attachments' - model = MediaAttachment.find(attachment_id).status - prompt.say(ActivityPub::TagManager.instance.url_for(model)) - elsif url.split('/')[0..-2].include? 'accounts' - model = Account.find(attachment_id) - prompt.say(ActivityPub::TagManager.instance.url_for(model)) - else - prompt.say('Not found') + unless record + say('Cannot find corresponding record', :red) + exit(1) + end + + display_url = ActivityPub::TagManager.instance.url_for(record) + + if display_url.blank? + say('No public URL for this type of record', :red) + exit(1) + end + + say(display_url, :blue) + rescue Addressable::URI::InvalidURIError + say('Invalid URL', :red) + exit(1) + end + + private + + PRELOAD_MODEL_WHITELIST = %w( + Account + Backup + CustomEmoji + Import + MediaAttachment + PreviewCard + SiteUpload + ).freeze + + def preload_records_from_mixed_objects(objects) + preload_map = Hash.new { |hash, key| hash[key] = [] } + + objects.map do |object| + segments = object.key.split('/').first + model_name = segments.first.classify + record_id = segments[2..-2].join.to_i + + next unless PRELOAD_MODEL_WHITELIST.include?(model_name) + + preload_map[model_name] << record_id + end + + preload_map.each_with_object({}) do |(model_name, record_ids), model_map| + model_map[model_name] = model_name.constantize.where(id: record_ids).each_with_object({}) { |record, record_map| record_map[record.id] = record } end end end diff --git a/lib/paperclip/attachment_extensions.rb b/lib/paperclip/attachment_extensions.rb index 3b308af5f..d9ec0159a 100644 --- a/lib/paperclip/attachment_extensions.rb +++ b/lib/paperclip/attachment_extensions.rb @@ -24,6 +24,19 @@ module Paperclip flush_deletes end end + + def variant?(other_filename) + return true if original_filename == other_filename + return false if original_filename.nil? + + formats = styles.values.map(&:format).compact + + return false if formats.empty? + + other_extension = File.extname(other_filename) + + formats.include?(other_extension.delete('.')) && File.basename(other_filename, other_extension) == File.basename(original_filename, File.extname(original_filename)) + end end end -- cgit From 6c79b7237e31eb510af7df3f4f2cb133dea39845 Mon Sep 17 00:00:00 2001 From: ThibG Date: Thu, 26 Mar 2020 15:09:16 +0100 Subject: Fix Paperclip using deprecated URI.escape function (#13320) Monkey-patch Paperclip to perform URL escaping in a slightly more appropriate way, and get rid of runtime deprecation warnings. --- config/application.rb | 1 + lib/paperclip/url_generator_extensions.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 lib/paperclip/url_generator_extensions.rb (limited to 'lib') diff --git a/config/application.rb b/config/application.rb index cd599eefc..4c34efa15 100644 --- a/config/application.rb +++ b/config/application.rb @@ -7,6 +7,7 @@ require 'rails/all' Bundler.require(*Rails.groups) require_relative '../app/lib/exceptions' +require_relative '../lib/paperclip/url_generator_extensions' require_relative '../lib/paperclip/attachment_extensions' require_relative '../lib/paperclip/lazy_thumbnail' require_relative '../lib/paperclip/gif_transcoder' diff --git a/lib/paperclip/url_generator_extensions.rb b/lib/paperclip/url_generator_extensions.rb new file mode 100644 index 000000000..1079efdbc --- /dev/null +++ b/lib/paperclip/url_generator_extensions.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Paperclip + module UrlGeneratorExtensions + # Monkey-patch Paperclip to use Addressable::URI's normalization instead + # of the long-deprecated URI.esacpe + def escape_url(url) + if url.respond_to?(:escape) + url.escape + else + Addressable::URI.parse(url).normalize.to_str.gsub(escape_regex) { |m| "%#{m.ord.to_s(16).upcase}" } + end + end + end +end + +Paperclip::UrlGenerator.prepend(Paperclip::UrlGeneratorExtensions) -- cgit From a9a063c0e983c5643178428327ca9907558c1f68 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 2 Apr 2020 05:28:51 +0200 Subject: Fix `tootctl media remove-orphans` ignoring `PAPERCLIP_ROOT_PATH` (#13375) Fix #13371 --- lib/mastodon/media_cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb index b4ad78fe5..08a8f1093 100644 --- a/lib/mastodon/media_cli.rb +++ b/lib/mastodon/media_cli.rb @@ -115,7 +115,7 @@ module Mastodon when :filesystem require 'find' - root_path = ENV.fetch('RAILS_ROOT_PATH', File.join(':rails_root', 'public', 'system')).gsub(':rails_root', Rails.root.to_s) + root_path = ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')).gsub(':rails_root', Rails.root.to_s) Find.find(File.join(*[root_path, prefix].compact)) do |path| next if File.directory?(path) -- cgit From a889756dd5f72a1423a613d8ce141f5347da48bc Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 5 Apr 2020 06:23:46 +0200 Subject: Bump version to 3.1.3 (#13389) --- CHANGELOG.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/mastodon/version.rb | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/CHANGELOG.md b/CHANGELOG.md index 33663c2ad..24a952a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,69 @@ Changelog All notable changes to this project will be documented in this file. +## [v3.1.3] - 2020-04-05 +### Added + +- Add ability to filter audit log in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13381)) +- Add titles to warning presets in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13252)) +- Add option to include resolved DNS records when blacklisting e-mail domains in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13254)) +- Add ability to delete files uploaded for settings in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13192)) +- Add sorting by username, creation and last activity in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13076)) +- Add explanation as to why unlocked accounts may have follow requests in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13385)) +- Add link to bookmarks to dropdown in web UI ([mayaeh](https://github.com/tootsuite/mastodon/pull/13273)) +- Add support for links to statuses in announcements to be opened in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13212)) +- Add tooltips to audio/video player buttons in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13203)) +- Add submit button to the top of preferences pages ([guigeekz](https://github.com/tootsuite/mastodon/pull/13068)) +- Add specific rate limits for posting and following ([Gargron](https://github.com/tootsuite/mastodon/pull/13172)) +- Add federation support for the "hide network" preference ([ThibG](https://github.com/tootsuite/mastodon/pull/11673)) +- Add `--skip-media-remove` option to `tootctl statuses remove` ([tateisu](https://github.com/tootsuite/mastodon/pull/13080)) + +### Changed + +- **Change design of polls in web UI** ([Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/13257), [ThibG](https://github.com/tootsuite/mastodon/pull/13313)) +- Change status click areas in web UI to be bigger ([ariasuni](https://github.com/tootsuite/mastodon/pull/13327)) +- **Change `tootctl media remove-orphans` to work for all classes** ([Gargron](https://github.com/tootsuite/mastodon/pull/13316)) +- **Change local media attachments to perform heavy processing asynchronously** ([Gargron](https://github.com/tootsuite/mastodon/pull/13210)) +- Change video uploads to always be converted to H264/MP4 ([Gargron](https://github.com/tootsuite/mastodon/pull/13220), [ThibG](https://github.com/tootsuite/mastodon/pull/13239), [ThibG](https://github.com/tootsuite/mastodon/pull/13242)) +- Change video uploads to enforce certain limits ([Gargron](https://github.com/tootsuite/mastodon/pull/13218)) +- Change the tooltip "Toggle visibility" to "Hide media" in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13199)) +- Change description of privacy levels to be more intuitive in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13197)) +- Change GIF label to be displayed even when autoplay is enabled in web UI ([koyuawsmbrtn](https://github.com/tootsuite/mastodon/pull/13209)) +- Change the string "Hide everything from …" to "Block domain …" in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13178), [mayaeh](https://github.com/tootsuite/mastodon/pull/13221)) +- Change wording of media display preferences to be more intuitive ([ariasuni](https://github.com/tootsuite/mastodon/pull/13198)) + +### Fixed + +- Fix `tootctl media remove-orphans` ignoring `PAPERCLIP_ROOT_PATH` ([Gargron](https://github.com/tootsuite/mastodon/pull/13375)) +- Fix returning results when searching for URL with non-zero offset ([Gargron](https://github.com/tootsuite/mastodon/pull/13377)) +- Fix pinning a column in web UI sometimes redirecting out of web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13376)) +- Fix background jobs not using locks like they are supposed to ([Gargron](https://github.com/tootsuite/mastodon/pull/13361)) +- Fix content warning being unnecessarily cleared when hiding content warning input in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13348)) +- Fix import overwrite option not being selectable ([noellabo](https://github.com/tootsuite/mastodon/pull/13347)) +- Fix wrong color for ellipsis in boost confirmation dialog in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13355)) +- Fix unnecessary unfollowing when importing follows with overwrite option ([noellabo](https://github.com/tootsuite/mastodon/pull/13350)) +- Fix 404 and 410 API errors being silently discarded in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13279)) +- Fix OCR not working on Safari because of unsupported worker-src CSP ([ThibG](https://github.com/tootsuite/mastodon/pull/13323)) +- Fix media not being marked sensitive when a content warning is set with no text ([ThibG](https://github.com/tootsuite/mastodon/pull/13277)) +- Fix crash after deleting announcements in web UI ([codesections](https://github.com/tootsuite/mastodon/pull/13283), [ThibG](https://github.com/tootsuite/mastodon/pull/13312)) +- Fix bookmarks not being searchable ([Kjwon15](https://github.com/tootsuite/mastodon/pull/13271), [noellabo](https://github.com/tootsuite/mastodon/pull/13293)) +- Fix reported accounts not being whitelisted from further spam checks when resolving a spam check report ([ThibG](https://github.com/tootsuite/mastodon/pull/13289)) +- Fix web UI crash in single-column mode on prehistoric browsers ([ThibG](https://github.com/tootsuite/mastodon/pull/13267)) +- Fix some timeouts when searching for URLs ([ThibG](https://github.com/tootsuite/mastodon/pull/13253)) +- Fix detailed view of direct messages displaying a 0 boost count in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13244)) +- Fix regression in “Edit media” modal in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13243)) +- Fix public posts from silenced accounts not being changed to unlisted visibility ([ThibG](https://github.com/tootsuite/mastodon/pull/13096)) +- Fix error when searching for URLs that contain the mention syntax ([ThibG](https://github.com/tootsuite/mastodon/pull/13151)) +- Fix text area above/right of emoji picker being accidentally clickable in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/13148)) +- Fix too large announcements not being scrollable in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13211)) +- Fix `tootctl media remove-orphans` crashing when encountering invalid media ([ThibG](https://github.com/tootsuite/mastodon/pull/13170)) +- Fix installation failing when Redis password contains special characters ([ThibG](https://github.com/tootsuite/mastodon/pull/13156)) +- Fix announcements with fully-qualified mentions to local users crashing web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13164)) + +### Security + +- Fix re-sending of e-mail confirmation not being rate limited ([Gargron](https://github.com/tootsuite/mastodon/pull/13360)) + ## [v3.1.2] - 2020-02-27 ### Added diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index ed8bc96cd..00ac7753f 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 2 + 3 end def flags -- cgit