Files
danbooru/app/logical/post_replacement_processor.rb
evazion e935f01358 uploads: fix temp files not being cleaned up quickly enough.
Fix temp files generated during the upload process not being cleaned up quickly enough. This included
downloaded files, generated preview images, and Ugoira video conversions.

Before we relied on `Tempfile` cleaning up files automatically. But this only happened when the
Tempfile object was garbage collected, which could take a long time. In the meantime we could have
hundreds of megabytes of temp files hanging around.

The fix is to explicitly close temp files when we're done with them. But the standard `Tempfile`
class doesn't immediately delete the file when it's closed. So we also have to introduce a
Danbooru::Tempfile wrapper that deletes the tempfile as soon as it's closed.
2022-11-15 18:50:50 -06:00

85 lines
2.7 KiB
Ruby

# frozen_string_literal: true
class PostReplacementProcessor
attr_reader :post, :replacement
def initialize(post:, replacement:)
@post = post
@replacement = replacement
end
def process!
media_file, image_url = get_file_for_upload(replacement.replacement_url, nil, replacement.replacement_file&.tempfile)
if Post.where.not(id: post.id).exists?(md5: media_file.md5)
raise "Duplicate of post ##{Post.find_by_md5(media_file.md5).id}"
end
if media_file.md5 == post.md5
media_asset = post.media_asset
else
MediaAsset.validate_media_file!(media_file, replacement.creator)
media_asset = MediaAsset.upload!(media_file)
end
if replacement.replacement_file.present?
canonical_url = "file://#{replacement.replacement_file.original_filename}"
elsif Source::URL.page_url(image_url).present?
canonical_url = image_url
else
canonical_url = replacement.replacement_url
end
replacement.replacement_url = canonical_url
replacement.file_ext = media_asset.file_ext
replacement.file_size = media_asset.file_size
replacement.image_height = media_asset.image_height
replacement.image_width = media_asset.image_width
replacement.md5 = media_asset.md5
replacement.media_asset = media_asset
post.lock!
post.md5 = media_asset.md5
post.file_ext = media_asset.file_ext
post.image_width = media_asset.image_width
post.image_height = media_asset.image_height
post.file_size = media_asset.file_size
post.source = replacement.final_source.presence || replacement.replacement_url
post.tag_string = "#{post.tag_string} #{replacement.tags}"
rescale_notes(post)
post.save!
post.update_iqdb
rescue Exception => exception
replacement.errors.add(:base, exception.message)
raise ActiveRecord::Rollback
ensure
media_file&.close
end
def rescale_notes(post)
x_scale = post.image_width.to_f / post.image_width_was.to_f
y_scale = post.image_height.to_f / post.image_height_was.to_f
post.notes.each do |note|
note.rescale!(x_scale, y_scale)
end
end
def get_file_for_upload(source_url, referer_url, file)
return MediaFile.open(file) if file.present?
raise "No file or source URL provided" if source_url.blank?
extractor = Source::Extractor.find(source_url, referer_url)
raise NotImplementedError, "No login credentials configured for #{extractor.site_name}." unless extractor.class.enabled?
image_urls = extractor.image_urls
raise "#{source_url} contains multiple images" if image_urls.size > 1
image_url = image_urls.first
file = extractor.download_file!(image_url)
[file, image_url]
end
end