diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-21 21:02:16 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-21 21:02:16 +0200 |
| commit | 5b951f012e04e6ef97e4ef645d53bdf433a9eb7f (patch) | |
| tree | 207addbdbb48df480f491d2b4b860c18171b3549 | |
| parent | 1791d8067d00dc1381b9cdc3cc4ad44c1f6c7197 (diff) | |
Implement model side of PDF raster preview generation
| -rw-r--r-- | app/models/concerns/file_attachment.rb | 24 | ||||
| -rw-r--r-- | lib/tasks/assets.rake | 6 | ||||
| -rw-r--r-- | test/controllers/assets_controller_test.rb | 16 |
3 files changed, 28 insertions, 18 deletions
diff --git a/app/models/concerns/file_attachment.rb b/app/models/concerns/file_attachment.rb index 548531f..6221312 100644 --- a/app/models/concerns/file_attachment.rb +++ b/app/models/concerns/file_attachment.rb | |||
| @@ -31,6 +31,7 @@ module FileAttachment | |||
| 31 | 31 | ||
| 32 | IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze | 32 | IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze |
| 33 | VECTOR_CONTENT_TYPES = %w[image/svg+xml].freeze | 33 | VECTOR_CONTENT_TYPES = %w[image/svg+xml].freeze |
| 34 | RASTERIZED_CONTENT_TYPES = %w[application/pdf].freeze | ||
| 34 | DISPLAYABLE_AS_IMAGE = IMAGE_CONTENT_TYPES + VECTOR_CONTENT_TYPES | 35 | DISPLAYABLE_AS_IMAGE = IMAGE_CONTENT_TYPES + VECTOR_CONTENT_TYPES |
| 35 | 36 | ||
| 36 | included do | 37 | included do |
| @@ -60,6 +61,11 @@ module FileAttachment | |||
| 60 | has_variant?(:medium) | 61 | has_variant?(:medium) |
| 61 | end | 62 | end |
| 62 | 63 | ||
| 64 | def variant_filename(style) | ||
| 65 | return upload_file_name if style == :original || !RASTERIZED_CONTENT_TYPES.include?(upload_content_type) | ||
| 66 | File.basename(upload_file_name, ".*") + ".png" | ||
| 67 | end | ||
| 68 | |||
| 63 | private | 69 | private |
| 64 | 70 | ||
| 65 | def build_upload_proxy | 71 | def build_upload_proxy |
| @@ -89,11 +95,7 @@ module FileAttachment | |||
| 89 | FileUtils.mkdir_p(File.dirname(original_path)) | 95 | FileUtils.mkdir_p(File.dirname(original_path)) |
| 90 | FileUtils.cp(uploaded_file.tempfile.path, original_path) | 96 | FileUtils.cp(uploaded_file.tempfile.path, original_path) |
| 91 | 97 | ||
| 92 | if IMAGE_CONTENT_TYPES.include?(upload_content_type) | 98 | generate_all_variants(original_path) |
| 93 | generate_variants(original_path) | ||
| 94 | elsif VECTOR_CONTENT_TYPES.include?(upload_content_type) | ||
| 95 | generate_svg_variants(original_path) | ||
| 96 | end | ||
| 97 | end | 99 | end |
| 98 | 100 | ||
| 99 | def generate_variants(original_path) | 101 | def generate_variants(original_path) |
| @@ -112,6 +114,16 @@ module FileAttachment | |||
| 112 | end | 114 | end |
| 113 | end | 115 | end |
| 114 | 116 | ||
| 117 | def generate_all_variants(original_path) | ||
| 118 | if IMAGE_CONTENT_TYPES.include?(upload_content_type) | ||
| 119 | generate_variants(original_path) | ||
| 120 | elsif VECTOR_CONTENT_TYPES.include?(upload_content_type) | ||
| 121 | generate_svg_variants(original_path) | ||
| 122 | elsif RASTERIZED_CONTENT_TYPES.include?(upload_content_type) | ||
| 123 | generate_variants("#{original_path}[0]") | ||
| 124 | end | ||
| 125 | end | ||
| 126 | |||
| 115 | def delete_upload_files | 127 | def delete_upload_files |
| 116 | dir = upload_root.join(id.to_s) | 128 | dir = upload_root.join(id.to_s) |
| 117 | FileUtils.rm_rf(dir) if Dir.exist?(dir) | 129 | FileUtils.rm_rf(dir) if Dir.exist?(dir) |
| @@ -135,7 +147,7 @@ module FileAttachment | |||
| 135 | 147 | ||
| 136 | def url(style = :original) | 148 | def url(style = :original) |
| 137 | return "" if @record.upload_file_name.blank? | 149 | return "" if @record.upload_file_name.blank? |
| 138 | "/system/uploads/#{@record.id}/#{style}/#{@record.upload_file_name}" | 150 | "/system/uploads/#{@record.id}/#{style}/#{@record.variant_filename(style)}" |
| 139 | end | 151 | end |
| 140 | 152 | ||
| 141 | def content_type | 153 | def content_type |
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index 9d4b048..e778021 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake | |||
| @@ -1,7 +1,9 @@ | |||
| 1 | namespace :assets do | 1 | namespace :assets do |
| 2 | desc "Regenerate ImageMagick variants for every image asset from its stored original -- rerun whenever a new style is added to FileAttachment::STYLES after assets already exist. Scope to one asset first with ASSET_ID=123." | 2 | desc "Regenerate ImageMagick variants for every image asset from its stored original -- rerun whenever a new style is added to FileAttachment::STYLES after assets already exist. Scope to one asset first with ASSET_ID=123." |
| 3 | task :regenerate_variants => :environment do | 3 | task :regenerate_variants => :environment do |
| 4 | scope = Asset.images | 4 | scope = Asset.where(upload_content_type: FileAttachment::IMAGE_CONTENT_TYPES + |
| 5 | FileAttachment::VECTOR_CONTENT_TYPES + | ||
| 6 | FileAttachment::RASTERIZED_CONTENT_TYPES) | ||
| 5 | scope = scope.where(id: ENV["ASSET_ID"]) if ENV["ASSET_ID"].present? | 7 | scope = scope.where(id: ENV["ASSET_ID"]) if ENV["ASSET_ID"].present? |
| 6 | 8 | ||
| 7 | scope.find_each do |asset| | 9 | scope.find_each do |asset| |
| @@ -12,7 +14,7 @@ namespace :assets do | |||
| 12 | next | 14 | next |
| 13 | end | 15 | end |
| 14 | 16 | ||
| 15 | asset.send(:generate_variants, original_path) | 17 | asset.send(:generate_all_variants, original_path) |
| 16 | puts "Regenerated variants for Asset##{asset.id} (#{asset.name})" | 18 | puts "Regenerated variants for Asset##{asset.id} (#{asset.name})" |
| 17 | end | 19 | end |
| 18 | end | 20 | end |
diff --git a/test/controllers/assets_controller_test.rb b/test/controllers/assets_controller_test.rb index 59ebab5..f834541 100644 --- a/test/controllers/assets_controller_test.rb +++ b/test/controllers/assets_controller_test.rb | |||
| @@ -70,7 +70,7 @@ class AssetsControllerTest < ActionController::TestCase | |||
| 70 | 70 | ||
| 71 | # --- create with PDF --- | 71 | # --- create with PDF --- |
| 72 | 72 | ||
| 73 | test "create asset with PDF upload generates only original" do | 73 | test "create asset with PDF upload generates rasterized variants" do |
| 74 | uploaded = Rack::Test::UploadedFile.new( | 74 | uploaded = Rack::Test::UploadedFile.new( |
| 75 | Rails.root.join('test', 'fixtures', 'files', 'test_document.pdf'), | 75 | Rails.root.join('test', 'fixtures', 'files', 'test_document.pdf'), |
| 76 | 'application/pdf' | 76 | 'application/pdf' |
| @@ -81,18 +81,14 @@ class AssetsControllerTest < ActionController::TestCase | |||
| 81 | assert_response :redirect | 81 | assert_response :redirect |
| 82 | 82 | ||
| 83 | asset = Asset.last | 83 | asset = Asset.last |
| 84 | assert_equal 'test_document.pdf', asset.upload_file_name | 84 | original_path = asset.send(:file_path, :original) |
| 85 | assert_equal 'application/pdf', asset.upload_content_type | ||
| 86 | |||
| 87 | # only original should exist, no image variants | ||
| 88 | original_path = Rails.root.join('public', 'system', 'uploads', | ||
| 89 | asset.id.to_s, 'original', 'test_document.pdf') | ||
| 90 | assert File.exist?(original_path), "Expected original at #{original_path}" | 85 | assert File.exist?(original_path), "Expected original at #{original_path}" |
| 86 | assert_equal 'test_document.pdf', File.basename(original_path) | ||
| 91 | 87 | ||
| 92 | %w[medium thumb headline large].each do |style| | 88 | %w[medium thumb headline large].each do |style| |
| 93 | path = Rails.root.join('public', 'system', 'uploads', | 89 | path = asset.send(:file_path, style) |
| 94 | asset.id.to_s, style, 'test_document.pdf') | 90 | assert File.exist?(path), "Expected a #{style} variant at #{path}" |
| 95 | assert !File.exist?(path), "Expected no #{style} variant for PDF" | 91 | assert_equal '.png', File.extname(path), "Expected #{style} variant to be a PNG, not a PDF" |
| 96 | end | 92 | end |
| 97 | end | 93 | end |
| 98 | 94 | ||
