From 96238da249d36358b060630c7e065eaf366b947d Mon Sep 17 00:00:00 2001 From: Lisa Durand Date: Mon, 22 Apr 2024 15:12:49 +0200 Subject: [PATCH 1/6] add service to rotate jpg attachment automatically --- .../attachment_auto_rotate_concern.rb | 22 +++++++++++++ app/services/auto_rotate_service.rb | 30 ++++++++++++++++++ config/initializers/active_storage.rb | 1 + spec/fixtures/files/image-rotated.jpg | Bin 0 -> 15562 bytes spec/services/auto_rotate_service_spec.rb | 15 +++++++++ 5 files changed, 68 insertions(+) create mode 100644 app/models/concerns/attachment_auto_rotate_concern.rb create mode 100644 app/services/auto_rotate_service.rb create mode 100644 spec/fixtures/files/image-rotated.jpg create mode 100644 spec/services/auto_rotate_service_spec.rb diff --git a/app/models/concerns/attachment_auto_rotate_concern.rb b/app/models/concerns/attachment_auto_rotate_concern.rb new file mode 100644 index 000000000..9fba8a9ab --- /dev/null +++ b/app/models/concerns/attachment_auto_rotate_concern.rb @@ -0,0 +1,22 @@ +module AttachmentAutoRotateConcern + extend ActiveSupport::Concern + + included do + after_create_commit :auto_rotate + end + + private + + def auto_rotate + return if blob.nil? + return if ["image/jpeg", "image/jpg"].exclude?(blob.content_type) + + blob.open do |file| + Tempfile.create(["rotated", File.extname(file)]) do |output| + processed = AutoRotateService.new.process(file, output) + blob.upload(processed) # also update checksum & byte_size accordingly + blob.save! + end + end + end +end diff --git a/app/services/auto_rotate_service.rb b/app/services/auto_rotate_service.rb new file mode 100644 index 000000000..39813c8c5 --- /dev/null +++ b/app/services/auto_rotate_service.rb @@ -0,0 +1,30 @@ +class AutoRotateService + def process(file, output) + auto_rotate_image(file, output) + output + end + + private + + def auto_rotate_image(file, output) + image = MiniMagick::Image.new(file.to_path) + + case image["%[orientation]"] + when 'LeftBottom' + rotate_image(file, output, 90) + when 'BottomRight' + rotate_image(file, output, 180) + when 'RightTop' + rotate_image(file, output, 270) + end + end + + def rotate_image(file, output, degree) + MiniMagick::Tool::Convert.new do |convert| + convert << file.to_path + convert.rotate(degree) + convert.auto_orient + convert << output.to_path + end + end +end diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index 042333ae3..c20df224f 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -17,6 +17,7 @@ end ActiveSupport.on_load(:active_storage_attachment) do include AttachmentTitreIdentiteWatermarkConcern include AttachmentVirusScannerConcern + include AttachmentAutoRotateConcern end Rails.application.reloader.to_prepare do diff --git a/spec/fixtures/files/image-rotated.jpg b/spec/fixtures/files/image-rotated.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2f58be2c8b76259a0a96ae033cc28e8e524d841d GIT binary patch literal 15562 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 L|C^VGHUB>VinQH` literal 0 HcmV?d00001 diff --git a/spec/services/auto_rotate_service_spec.rb b/spec/services/auto_rotate_service_spec.rb new file mode 100644 index 000000000..9895be7d6 --- /dev/null +++ b/spec/services/auto_rotate_service_spec.rb @@ -0,0 +1,15 @@ +RSpec.describe AutoRotateService do + let(:image) { file_fixture("image-rotated.jpg") } + let(:auto_rotate_service) { AutoRotateService.new } + + describe '#process' do + it 'returns a tempfile if auto_rotate succeeds' do + Tempfile.create do |output| + auto_rotate_service.process(image, output) + expect(MiniMagick::Image.new(image.to_path)["%[orientation]"]).to eq('LeftBottom') + expect(MiniMagick::Image.new(output.to_path)["%[orientation]"]).to eq('TopLeft') + expect(output.size).to be_between(image.size / 1.2, image.size) + end + end + end +end From fce2c03015990b845c5c2193094b675af085a3be Mon Sep 17 00:00:00 2001 From: Lisa Durand Date: Mon, 22 Apr 2024 16:05:26 +0200 Subject: [PATCH 2/6] fix spec changing fake image file extension --- spec/components/attachment/multiple_component_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/components/attachment/multiple_component_spec.rb b/spec/components/attachment/multiple_component_spec.rb index 9f4d02549..26dfacd2c 100644 --- a/spec/components/attachment/multiple_component_spec.rb +++ b/spec/components/attachment/multiple_component_spec.rb @@ -105,7 +105,7 @@ RSpec.describe Attachment::MultipleComponent, type: :component do attached_file.attach( io: StringIO.new("x" * 2), filename: "me.jpg", - content_type: "image/jpeg", + content_type: "image/png", metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } ) champ.save! From b4a7b4bfbdc7293562708b21a6ffdda56d1b5e4f Mon Sep 17 00:00:00 2001 From: Lisa Durand Date: Mon, 29 Apr 2024 16:53:21 +0200 Subject: [PATCH 3/6] create a job for all image treatments --- app/jobs/image_processor_job.rb | 59 +++++++++++++++++++ app/jobs/titre_identite_watermark_job.rb | 28 --------- .../attachment_auto_rotate_concern.rb | 22 ------- .../attachment_image_processor_concern.rb | 21 +++++++ ...ern.rb => blob_image_processor_concern.rb} | 8 +-- app/services/auto_rotate_service.rb | 6 +- config/initializers/active_storage.rb | 5 +- ...ob_spec.rb => image_processor_job_spec.rb} | 2 +- 8 files changed, 91 insertions(+), 60 deletions(-) create mode 100644 app/jobs/image_processor_job.rb delete mode 100644 app/jobs/titre_identite_watermark_job.rb delete mode 100644 app/models/concerns/attachment_auto_rotate_concern.rb create mode 100644 app/models/concerns/attachment_image_processor_concern.rb rename app/models/concerns/{blob_titre_identite_watermark_concern.rb => blob_image_processor_concern.rb} (58%) rename spec/jobs/{titre_identite_watermark_job_spec.rb => image_processor_job_spec.rb} (97%) diff --git a/app/jobs/image_processor_job.rb b/app/jobs/image_processor_job.rb new file mode 100644 index 000000000..6fa027e93 --- /dev/null +++ b/app/jobs/image_processor_job.rb @@ -0,0 +1,59 @@ +class ImageProcessorJob < ApplicationJob + class FileNotScannedYetError < StandardError + end + + # If by the time the job runs the blob has been deleted, ignore the error + discard_on ActiveRecord::RecordNotFound + # If the file is deleted during the scan, ignore the error + discard_on ActiveStorage::FileNotFoundError + # If the file is not analyzed or scanned for viruses yet, retry later + # (to avoid modifying the file while it is being scanned). + retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10 + + def perform(blob) + return if blob.nil? + raise FileNotScannedYetError if blob.virus_scanner.pending? + return if ActiveStorage::Attachment.find_by(blob_id: blob.id).record_type == "ActiveStorage::VariantRecord" + + auto_rotate(blob) if ["image/jpeg", "image/jpg"].include?(blob.content_type) + create_variants(blob) if blob.variant_required? + add_watermark(blob) if blob.watermark_pending? + end + + private + + def auto_rotate(blob) + blob.open do |file| + Tempfile.create(["rotated", File.extname(file)]) do |output| + processed = AutoRotateService.new.process(file, output) + return if processed.blank? + + blob.upload(processed) # also update checksum & byte_size accordingly + blob.save! + end + end + end + + def create_variants(blob) + blob.attachments.each do |attachment| + next unless attachment&.representable? + attachment.representation(resize_to_limit: [300, 300]).processed + attachment.representation(resize_to_limit: [400, 400]).processed + end + end + + def add_watermark(blob) + return if blob.watermark_done? + + blob.open do |file| + Tempfile.create(["watermarked", File.extname(file)]) do |output| + processed = WatermarkService.new.process(file, output) + return if processed.blank? + + blob.upload(processed) # also update checksum & byte_size accordingly + blob.watermarked_at = Time.current + blob.save! + end + end + end +end diff --git a/app/jobs/titre_identite_watermark_job.rb b/app/jobs/titre_identite_watermark_job.rb deleted file mode 100644 index 1b261e291..000000000 --- a/app/jobs/titre_identite_watermark_job.rb +++ /dev/null @@ -1,28 +0,0 @@ -class TitreIdentiteWatermarkJob < ApplicationJob - class FileNotScannedYetError < StandardError - end - - # If by the time the job runs the blob has been deleted, ignore the error - discard_on ActiveRecord::RecordNotFound - # If the file is deleted during the scan, ignore the error - discard_on ActiveStorage::FileNotFoundError - # If the file is not analyzed or scanned for viruses yet, retry later - # (to avoid modifying the file while it is being scanned). - retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10 - - def perform(blob) - return if blob.watermark_done? - raise FileNotScannedYetError if blob.virus_scanner.pending? - - blob.open do |file| - Tempfile.create(["watermarked", File.extname(file)]) do |output| - processed = WatermarkService.new.process(file, output) - return if processed.blank? - - blob.upload(processed) # also update checksum & byte_size accordingly - blob.watermarked_at = Time.current - blob.save! - end - end - end -end diff --git a/app/models/concerns/attachment_auto_rotate_concern.rb b/app/models/concerns/attachment_auto_rotate_concern.rb deleted file mode 100644 index 9fba8a9ab..000000000 --- a/app/models/concerns/attachment_auto_rotate_concern.rb +++ /dev/null @@ -1,22 +0,0 @@ -module AttachmentAutoRotateConcern - extend ActiveSupport::Concern - - included do - after_create_commit :auto_rotate - end - - private - - def auto_rotate - return if blob.nil? - return if ["image/jpeg", "image/jpg"].exclude?(blob.content_type) - - blob.open do |file| - Tempfile.create(["rotated", File.extname(file)]) do |output| - processed = AutoRotateService.new.process(file, output) - blob.upload(processed) # also update checksum & byte_size accordingly - blob.save! - end - end - end -end diff --git a/app/models/concerns/attachment_image_processor_concern.rb b/app/models/concerns/attachment_image_processor_concern.rb new file mode 100644 index 000000000..d1c44609c --- /dev/null +++ b/app/models/concerns/attachment_image_processor_concern.rb @@ -0,0 +1,21 @@ +# Run a virus scan on all attachments after they are analyzed. +# +# We're using a class extension to ensure that all attachments get scanned, +# regardless on how they were created. This could be an ActiveStorage::Analyzer, +# but as of Rails 6.1 only the first matching analyzer is ever run on +# a blob (and we may want to analyze the dimension of a picture as well +# as scanning it). +module AttachmentImageProcessorConcern + extend ActiveSupport::Concern + + included do + after_create_commit :process_image + end + + private + + def process_image + return if blob.nil? + ImageProcessorJob.perform_later(blob) + end +end diff --git a/app/models/concerns/blob_titre_identite_watermark_concern.rb b/app/models/concerns/blob_image_processor_concern.rb similarity index 58% rename from app/models/concerns/blob_titre_identite_watermark_concern.rb rename to app/models/concerns/blob_image_processor_concern.rb index 152c512f9..46b56b955 100644 --- a/app/models/concerns/blob_titre_identite_watermark_concern.rb +++ b/app/models/concerns/blob_image_processor_concern.rb @@ -1,4 +1,4 @@ -module BlobTitreIdentiteWatermarkConcern +module BlobImageProcessorConcern def watermark_pending? watermark_required? && !watermark_done? end @@ -7,10 +7,8 @@ module BlobTitreIdentiteWatermarkConcern watermarked_at.present? end - def watermark_later - if watermark_pending? - TitreIdentiteWatermarkJob.perform_later(self) - end + def variant_required? + attachments.any? { _1.record.class == Champs::TitreIdentiteChamp || _1.record.class == Champs::PieceJustificativeChamp } end private diff --git a/app/services/auto_rotate_service.rb b/app/services/auto_rotate_service.rb index 39813c8c5..9cec29a95 100644 --- a/app/services/auto_rotate_service.rb +++ b/app/services/auto_rotate_service.rb @@ -1,7 +1,6 @@ class AutoRotateService def process(file, output) auto_rotate_image(file, output) - output end private @@ -9,6 +8,8 @@ class AutoRotateService def auto_rotate_image(file, output) image = MiniMagick::Image.new(file.to_path) + return nil if !image.valid? + case image["%[orientation]"] when 'LeftBottom' rotate_image(file, output, 90) @@ -16,6 +17,8 @@ class AutoRotateService rotate_image(file, output, 180) when 'RightTop' rotate_image(file, output, 270) + else + nil end end @@ -26,5 +29,6 @@ class AutoRotateService convert.auto_orient convert << output.to_path end + output end end diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index c20df224f..5598525ad 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -4,7 +4,7 @@ Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer ActiveSupport.on_load(:active_storage_blob) do - include BlobTitreIdentiteWatermarkConcern + include BlobImageProcessorConcern include BlobVirusScannerConcern include BlobSignedIdConcern @@ -15,9 +15,8 @@ ActiveSupport.on_load(:active_storage_blob) do end ActiveSupport.on_load(:active_storage_attachment) do - include AttachmentTitreIdentiteWatermarkConcern + include AttachmentImageProcessorConcern include AttachmentVirusScannerConcern - include AttachmentAutoRotateConcern end Rails.application.reloader.to_prepare do diff --git a/spec/jobs/titre_identite_watermark_job_spec.rb b/spec/jobs/image_processor_job_spec.rb similarity index 97% rename from spec/jobs/titre_identite_watermark_job_spec.rb rename to spec/jobs/image_processor_job_spec.rb index ebb9497cc..e337a825f 100644 --- a/spec/jobs/titre_identite_watermark_job_spec.rb +++ b/spec/jobs/image_processor_job_spec.rb @@ -1,4 +1,4 @@ -describe TitreIdentiteWatermarkJob, type: :job do +describe ImageProcessorJob, type: :job do let(:blob) do ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png") end From 93c37611079c3d72a3494e7929cecf4abd41644c Mon Sep 17 00:00:00 2001 From: Lisa Durand Date: Tue, 7 May 2024 16:27:08 +0200 Subject: [PATCH 4/6] add specs --- app/jobs/image_processor_job.rb | 2 +- .../attachment_image_processor_concern.rb | 2 + .../instructeurs/dossiers_controller_spec.rb | 10 +- spec/fixtures/files/image-no-exif.jpg | Bin 0 -> 13240 bytes spec/fixtures/files/image-no-rotation.jpg | Bin 0 -> 15562 bytes spec/jobs/image_processor_job_spec.rb | 123 ++++++++++++++---- spec/services/auto_rotate_service_spec.rb | 22 +++- 7 files changed, 129 insertions(+), 30 deletions(-) create mode 100644 spec/fixtures/files/image-no-exif.jpg create mode 100644 spec/fixtures/files/image-no-rotation.jpg diff --git a/app/jobs/image_processor_job.rb b/app/jobs/image_processor_job.rb index 6fa027e93..1c37040fe 100644 --- a/app/jobs/image_processor_job.rb +++ b/app/jobs/image_processor_job.rb @@ -13,7 +13,7 @@ class ImageProcessorJob < ApplicationJob def perform(blob) return if blob.nil? raise FileNotScannedYetError if blob.virus_scanner.pending? - return if ActiveStorage::Attachment.find_by(blob_id: blob.id).record_type == "ActiveStorage::VariantRecord" + return if ActiveStorage::Attachment.find_by(blob_id: blob.id)&.record_type == "ActiveStorage::VariantRecord" auto_rotate(blob) if ["image/jpeg", "image/jpg"].include?(blob.content_type) create_variants(blob) if blob.variant_required? diff --git a/app/models/concerns/attachment_image_processor_concern.rb b/app/models/concerns/attachment_image_processor_concern.rb index d1c44609c..459e54d76 100644 --- a/app/models/concerns/attachment_image_processor_concern.rb +++ b/app/models/concerns/attachment_image_processor_concern.rb @@ -16,6 +16,8 @@ module AttachmentImageProcessorConcern def process_image return if blob.nil? + return if blob.attachments.any? { _1.record_type == "Export" } + ImageProcessorJob.perform_later(blob) end end diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index f4f065948..27a02ae7f 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -1429,13 +1429,13 @@ describe Instructeurs::DossiersController, type: :controller do describe '#pieces_jointes' do let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :piece_justificative }], instructeurs:) } let(:dossier) { create(:dossier, :en_construction, :with_populated_champs, procedure: procedure) } + let(:path) { 'spec/fixtures/files/logo_test_procedure.png' } before do dossier.champs.first.piece_justificative_file.attach( - io: StringIO.new("image file"), - filename: "image.jpeg", - content_type: "image/jpeg", - # we don't want to run virus scanner on this file + io: File.open(path), + filename: "logo_test_procedure.png", + content_type: "image/png", metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } ) get :pieces_jointes, params: { @@ -1446,7 +1446,7 @@ describe Instructeurs::DossiersController, type: :controller do it do expect(response.body).to include('Télécharger le fichier toto.txt') - expect(response.body).to include('Télécharger le fichier image.jpeg') + expect(response.body).to include('Télécharger le fichier logo_test_procedure.png') expect(response.body).to include('Visualiser') end end diff --git a/spec/fixtures/files/image-no-exif.jpg b/spec/fixtures/files/image-no-exif.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ec032c2b42702a40367058d6f4c15e33a6201e9 GIT binary patch literal 13240 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 G{(k`CXWfAS literal 0 HcmV?d00001 diff --git a/spec/jobs/image_processor_job_spec.rb b/spec/jobs/image_processor_job_spec.rb index e337a825f..9c520babd 100644 --- a/spec/jobs/image_processor_job_spec.rb +++ b/spec/jobs/image_processor_job_spec.rb @@ -3,26 +3,27 @@ describe ImageProcessorJob, type: :job do ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png") end + let(:blob_jpg) do + ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.jpg") + end + + let(:attachment) { ActiveStorage::Attachment.new(name: "test", blob: blob) } let(:antivirus_pending) { false } let(:watermark_service) { instance_double("WatermarkService") } + let(:auto_rotate_service) { instance_double("AutoRotateService") } before do virus_scanner_mock = instance_double("ActiveStorage::VirusScanner", pending?: antivirus_pending) + allow(blob).to receive(:attachments).and_return([attachment]) + allow(blob).to receive(:virus_scanner).and_return(virus_scanner_mock) + allow(blob_jpg).to receive(:virus_scanner).and_return(virus_scanner_mock) allow(WatermarkService).to receive(:new).and_return(watermark_service) allow(watermark_service).to receive(:process).and_return(true) - end - context "when watermark is already done" do - before do - allow(blob).to receive(:watermark_done?).and_return(true) - end - - it "does not process the blob" do - expect(watermark_service).not_to receive(:process) - described_class.perform_now(blob) - end + allow(AutoRotateService).to receive(:new).and_return(auto_rotate_service) + allow(auto_rotate_service).to receive(:process).and_return(true) end context "when the blob is not scanned yet" do @@ -33,24 +34,102 @@ describe ImageProcessorJob, type: :job do end end - context "when the blob is ready to be watermarked" do - let(:watermarked_file) { Tempfile.new("watermarked.png") } + describe 'autorotate' do + context "when image is not a jpg" do + let(:rotated_file) { Tempfile.new("rotated.png") } - before do - allow(watermarked_file).to receive(:size).and_return(100) + before do + allow(rotated_file).to receive(:size).and_return(100) + end + + it "it does not process autorotate" do + expect(auto_rotate_service).not_to receive(:process) + described_class.perform_now(blob) + end end - it "processes the blob with watermark" do - expect(watermark_service).to receive(:process).and_return(watermarked_file) + context "when image is a jpg " do + let(:rotated_file) { Tempfile.new("rotated.jpg") } - expect { - described_class.perform_now(blob) - }.to change { - blob.reload.checksum + before do + allow(rotated_file).to receive(:size).and_return(100) + end + + it "it processes autorotate" do + expect(auto_rotate_service).to receive(:process).and_return(rotated_file) + described_class.perform_now(blob_jpg) + end + end + end + + describe 'create representation' do + let(:file) { fixture_file_upload('spec/fixtures/files/logo_test_procedure.png', 'image/png') } + let(:blob_info) do + { + filename: file.original_filename, + byte_size: file.size, + checksum: Digest::SHA256.file(file.path), + content_type: file.content_type, + # we don't want to run virus scanner on this file + metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } } + end - expect(blob.byte_size).to eq(100) - expect(blob.watermarked_at).to be_present + let(:blob) do + blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_info) + blob.upload(file) + blob + end + + context "when representation is not required" do + it "it does not create blob representation" do + expect { described_class.perform_now(blob) }.not_to change { ActiveStorage::VariantRecord.count } + end + end + + context "when representation is required" do + before do + allow(blob).to receive(:variant_required?).and_return(true) + end + + it "it creates blob representation" do + expect { described_class.perform_now(blob) }.to change { ActiveStorage::VariantRecord.count }.by(2) + end + end + end + + describe 'watermark' do + context "when watermark is already done" do + before do + allow(blob).to receive(:watermark_done?).and_return(true) + end + + it "does not process the watermark" do + expect(watermark_service).not_to receive(:process) + described_class.perform_now(blob) + end + end + + context "when the blob is ready to be watermarked" do + let(:watermarked_file) { Tempfile.new("watermarked.png") } + + before do + allow(watermarked_file).to receive(:size).and_return(100) + allow(blob).to receive(:watermark_pending?).and_return(true) + end + + it "processes the blob with watermark" do + expect(watermark_service).to receive(:process).and_return(watermarked_file) + + expect { + described_class.perform_now(blob) + }.to change { + blob.reload.checksum + } + + expect(blob.byte_size).to eq(100) + expect(blob.watermarked_at).to be_present + end end end end diff --git a/spec/services/auto_rotate_service_spec.rb b/spec/services/auto_rotate_service_spec.rb index 9895be7d6..f8a007f76 100644 --- a/spec/services/auto_rotate_service_spec.rb +++ b/spec/services/auto_rotate_service_spec.rb @@ -1,14 +1,32 @@ RSpec.describe AutoRotateService do let(:image) { file_fixture("image-rotated.jpg") } + let(:image_no_exif) { file_fixture("image-no-exif.jpg") } + let(:image_no_rotation) { file_fixture("image-no-rotation.jpg") } let(:auto_rotate_service) { AutoRotateService.new } describe '#process' do it 'returns a tempfile if auto_rotate succeeds' do Tempfile.create do |output| - auto_rotate_service.process(image, output) + result = auto_rotate_service.process(image, output) expect(MiniMagick::Image.new(image.to_path)["%[orientation]"]).to eq('LeftBottom') expect(MiniMagick::Image.new(output.to_path)["%[orientation]"]).to eq('TopLeft') - expect(output.size).to be_between(image.size / 1.2, image.size) + expect(result.size).to be_between(image.size / 1.2, image.size) + end + end + + it 'returns nil if image does not need to be return' do + Tempfile.create do |output| + result = auto_rotate_service.process(image_no_rotation, output) + expect(MiniMagick::Image.new(image_no_rotation.to_path)["%[orientation]"]).to eq('TopLeft') + expect(result).to eq nil + end + end + + it 'returns nil if no exif info on image' do + Tempfile.create do |output| + result = auto_rotate_service.process(image_no_exif, output) + expect(MiniMagick::Image.new(image_no_exif.to_path)["%[orientation]"]).to eq('Undefined') + expect(result).to eq nil end end end From affb1820d801ed74cfae13e5f3ef5b6935de8779 Mon Sep 17 00:00:00 2001 From: Lisa Durand Date: Tue, 7 May 2024 16:41:20 +0200 Subject: [PATCH 5/6] add PDF preview in view for gallery --- app/assets/images/apercu-indisponible.png | Bin 5467 -> 5193 bytes app/assets/images/pdf-placeholder.png | Bin 3581 -> 0 bytes app/jobs/image_processor_job.rb | 5 ++--- .../champs/piece_justificative_champ.rb | 4 +--- app/models/champs/titre_identite_champ.rb | 4 +--- .../concerns/blob_image_processor_concern.rb | 2 +- .../dossiers/pieces_jointes.html.haml | 6 +++--- .../piece_justificative/_show.html.haml | 6 +++--- spec/jobs/image_processor_job_spec.rb | 4 ++-- 9 files changed, 13 insertions(+), 18 deletions(-) delete mode 100644 app/assets/images/pdf-placeholder.png diff --git a/app/assets/images/apercu-indisponible.png b/app/assets/images/apercu-indisponible.png index 6bebd9f59de194d5d5eab6847ac31e31c5d5a4d4..e6c56638a09003ae21f4e970de493cdce4097c94 100644 GIT binary patch literal 5193 zcmeHLX*64HyN;=Eq!Se#)YJi0L#tZzT>4T|OHnGcMbnz6m$$FL->J83 z{yYql0097i!xrYIb^rih*8Tt<;6>uQG`{jK!hz<_AppQ}x&6UsVRwF=7vu}E`_lw~ z#Y-;nZUmvm*2VxpE%M;b10euFY{tUW_)Y{Lby^>Jesto%?2i}SUykQyeMptMf6iIs zNII%ftVSS3HW7F>E&-kn=Q|~uDSIVBD78a0wbsi=nIBbS4irg2#+K{<>ECsDFe#AjK ztBV`UCU)&Lg0k(yxw|aeF77U^ELkQlBGChLFzsnuBxB*C5u%qn5VtkpzE{^O@zg`! zNb4Po42Ls6Q4v650$}d#-YyMpt1Na_QkUVjKFf&JFu1;7)>!Ar^fcNYdVNumIK!T4^O5iEp8pKQxY4S$15~62a z&-3vU(?#Zrfg;U+T{NA|;?3F%i)!)0{{`nCy5HyGkI?mLP5#rb47fWCVO2CBL7+8= z(i2)e-5zNlA1L?hBme&|=#%qjL0kLK+DF90wpFEO5O4QB@(|R_=F3c0RsJ8CA@G*r z&fHN2)#O-0k+8^-<=S3pbhG59e)2xDz-mdWKXzB4hV^7^ExwQ4)6GF1?D{pNk)NuojimuQILij;=Ktmv}iI~I&N#uA?1VF8Vq~~my*`F0*1}>Nd3S9Y3eoM_ z`h3Z;i}nZ-#~v=UK^m}sO%h_*OJ$X025ft^o`Cs7I^urR4!t+r#nAxm^`5lmg2z#v zT?!#122y!y?!8s_G6%7otA(6i!#%a>R@ll;7j?2HMt388taNf&(jA+}!Q=6r1zHx0 zHUS;XvB;vA(LYiZFlBF|yla7b+XT3qI0jtITTb}!>45gy+Je)X{=UjrWnzEdZp$FL z!(-XAPmem%A~*UJXpa|kDZTut_ zzC)35>Vh-S$SCGk#+aY?1{1WjNikymyTz7E6^dojbDKf^qJ!jw z#3jARs`9Uf`HlE^=x$HxOehGy+~z8Xe^hhJ4%89+B5cVwJ_5_G$8WJ$s)?Nn5F-+P zs5jBzX#7PT#dP$T*J|RaKM;#F=kU^;*^2s&Dgwr9bavfq@+#&Q=j;MSYbp1c?}Zfe>+d|UZJM@lnisdy^Di`@UAf{M5NuO4|w?j{8 z(KGIPB3)s<(W1BIXm%|jXTEe$*~isp@69Czc-gl{yBnzW_e@FJ{eifFc1WHaN8P^F zvmiKzF|4gxv%f>yXl!j3_=Va9@8=!XsMERN(kWvmUYAydJ+y%0j@BPQ_c+@Hs`olr z?`gUhdMgw)T^;X2wu1U`4&MteRm!KhE+^g$!m}NlredNQ6Hx@OzP-rp`8>j>2+5U` zWc%UQMtKtvwyuNQIh83>gu)-KmI=~O%KONiA$-Jf_a-5lZfPVorKkRB+8hdgQk5I`C#I|MsFGSG!wHR94coQv!z%W;oO;55!&Eo^;caZ>`Zv`8veeXJV|$ z+NO_xbv?o-mfV(~p{>D>cxqt`mL{D-&fZ<(d8p6gKp(IBn&HC#Az&KAuD44Dyz=T7 zBuw3j`X~+cl-HXpu}`Ao6o%S6TsDUm)iLW^6r?T3*TbNYTIFZOc>e3I zPZi)+*y&B{Yjw7Z11VLDS_`2~Lt#*OV3Z&*Z6=mAJvwdASQN9XA;)p3qbj8o!1{CR zCVFPmHa?H@4>4OSTYct;UlEEmpF0*Ymvq8WNRx_+jV-QE!66Ve*kl^wQTpO!Pb2`6Tum_HGDsi5yFqq zJ+IYsFQB)c1kMM)Y~TBG4rv7umwuut&?lVsdK+`HlQk1PoaY{=tInvIYUvv@poH3j)v|`$5AxBVR)P76OO2f{GZC1cH<~B< z7+#vlNvFw3Y9kR}{jRR#TAV|#9AB;$@9P>zHyD7pxv!e=6(Fie|GE}H+67BwnD?3h#HaT7=Et3~(`Axo|0CUpZw+W~M6shqN} z+`IU?+#WUD_GoonBb`}0?x1ORD$VN|2je$2f`3c`gg{zCtH;ke@sTon$01IXvu);i z4}5hOMl+l=rQ=nV@flCcNC)0h68?~Wsk4kNQm*b%KL%oLm$Y5!JZb!?Hf9I5!u(L4^9=y0KRUSv4@7Go~zI9 zlvu9v{>qp(NwMhde`J>OdlJ{0Y70Dm;rfCq6F87`8VTw++xqNhgRqFI87QRZ@G)76 zlmYDY(vHSV^nCXpGOoLw*x0R>XcZlfKWX~WW#}P92xVf^j2Q$q(i8X)d7${26ZVFV z$GN|Qc6%$!bwXwu4?gl;JoyIxjyieGsLq<{Mv+*xj1TmWxqR74@PuNLub#lnRp1Vj z=x$3&=eru_G@9PT1al0izR`gjSz#>?`wzP-1S$t#Ejz zq>vj+>B-QYu|L{9D)z(D>7H}1w*o}ZlImI_iL6?&Bf4!~AA=D|sKsLyFfR?b2|ULO zntqWpGYWbjQb6)-mleV-z;1S%8~2SHgO;|epTd;ZI+qV=P%$EFw~qmIdeQ(T>; zH+|9ex9l(YOm9rk`#qN|nC9J9+3%II^c=dd1v?#UMW54h{4r7e?U?L7ozVuR(l-)h zr%eZw3x1`$2(!$945YF(7RWpPcfzfczU<(>fnR{j!L_dhyk#85E8~&S_R&pDTf)Xy z^IXVLA$&E$JNT3JQ5LVL6Xw1oPOdJB6P9>wNdI~L5YJI}8q@qs%`>3in1N(F5@RAl?1*!jOBXvP(Tn_~ zcad~=xZi!W4O`dg5iz1ct}HenR|j1}>JpOya~G6_fPpQCiUs~#*Jr9{;JemeE$?P? pnhzd~KMuM3?`F{dc@u2gy&!ly!(Lq|g4g*2SeV(EVomNR{u8#TD=Pp1 literal 5467 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 diff --git a/app/assets/images/pdf-placeholder.png b/app/assets/images/pdf-placeholder.png deleted file mode 100644 index 58da75f9f728e70eb30af89f205a263ddc1aa1bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3581 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 { validate_champ_value? || validation_context == :prefill } validates :piece_justificative_file, diff --git a/app/models/champs/titre_identite_champ.rb b/app/models/champs/titre_identite_champ.rb index 667feca7e..6d95f4d43 100644 --- a/app/models/champs/titre_identite_champ.rb +++ b/app/models/champs/titre_identite_champ.rb @@ -2,9 +2,7 @@ class Champs::TitreIdentiteChamp < Champ FILE_MAX_SIZE = 20.megabytes ACCEPTED_FORMATS = ['image/png', 'image/jpeg'] - has_many_attached :piece_justificative_file do |attachable| - attachable.variant :medium, resize: '400x400' - end + has_many_attached :piece_justificative_file # TODO: if: -> { validate_champ_value? || validation_context == :prefill } validates :piece_justificative_file, content_type: ACCEPTED_FORMATS, size: { less_than: FILE_MAX_SIZE } diff --git a/app/models/concerns/blob_image_processor_concern.rb b/app/models/concerns/blob_image_processor_concern.rb index 46b56b955..3b92ad4e4 100644 --- a/app/models/concerns/blob_image_processor_concern.rb +++ b/app/models/concerns/blob_image_processor_concern.rb @@ -7,7 +7,7 @@ module BlobImageProcessorConcern watermarked_at.present? end - def variant_required? + def representation_required? attachments.any? { _1.record.class == Champs::TitreIdentiteChamp || _1.record.class == Champs::PieceJustificativeChamp } end diff --git a/app/views/instructeurs/dossiers/pieces_jointes.html.haml b/app/views/instructeurs/dossiers/pieces_jointes.html.haml index a0b1816a1..1d08707a3 100644 --- a/app/views/instructeurs/dossiers/pieces_jointes.html.haml +++ b/app/views/instructeurs/dossiers/pieces_jointes.html.haml @@ -5,13 +5,13 @@ .fr-container .gallery.gallery-pieces-jointes{ "data-controller": "lightbox" } - @champs_with_pieces_jointes.each do |champ| - - champ.piece_justificative_file.each do |attachment| + - champ.piece_justificative_file.with_all_variant_records.each do |attachment| .gallery-item - blob = attachment.blob - if blob.content_type.in?(AUTHORIZED_PDF_TYPES) = link_to blob.url, id: blob.id, data: { iframe: true, src: blob.url }, class: 'gallery-link', type: blob.content_type, title: "#{champ.libelle} -- #{blob.filename}" do .thumbnail - = image_tag("pdf-placeholder.png") + = image_tag(attachment.representation(resize_to_limit: [400, 400]).processed.url, loading: :lazy) .fr-btn.fr-btn--tertiary.fr-btn--icon-left.fr-icon-eye{ role: :button } Visualiser .champ-libelle @@ -21,7 +21,7 @@ - elsif blob.content_type.in?(AUTHORIZED_IMAGE_TYPES) = link_to image_url(blob.url), title: "#{champ.libelle} -- #{blob.filename}", data: { src: blob.url }, class: 'gallery-link' do .thumbnail - = image_tag(attachment.variant(:medium), loading: :lazy) + = image_tag(attachment.representation(resize_to_limit: [400, 400]).processed.url, loading: :lazy) .fr-btn.fr-btn--tertiary.fr-btn--icon-left.fr-icon-eye{ role: :button } Visualiser .champ-libelle diff --git a/app/views/shared/champs/piece_justificative/_show.html.haml b/app/views/shared/champs/piece_justificative/_show.html.haml index 50218e54b..670e9e011 100644 --- a/app/views/shared/champs/piece_justificative/_show.html.haml +++ b/app/views/shared/champs/piece_justificative/_show.html.haml @@ -5,20 +5,20 @@ %li= render Attachment::ShowComponent.new(attachment:, new_tab: true) - else .gallery-items-list - - champ.piece_justificative_file.attachments.each do |attachment| + - champ.piece_justificative_file.attachments.with_all_variant_records.each do |attachment| .gallery-item - blob = attachment.blob - if blob.content_type.in?(AUTHORIZED_PDF_TYPES) = link_to blob.url, id: blob.id, data: { iframe: true, src: blob.url }, class: 'gallery-link', type: blob.content_type, title: "#{champ.libelle} -- #{blob.filename}" do .thumbnail - = image_tag("pdf-placeholder.png") + = image_tag(attachment.representation(resize_to_limit: [400, 400]).processed.url, loading: :lazy) .fr-btn.fr-btn--tertiary.fr-btn--icon-left.fr-icon-eye{ role: :button } = 'Visualiser' - elsif blob.content_type.in?(AUTHORIZED_IMAGE_TYPES) = link_to image_url(blob.url), title: "#{champ.libelle} -- #{blob.filename}", data: { src: blob.url }, class: 'gallery-link' do .thumbnail - = image_tag(attachment.variant(:medium), loading: :lazy) + = image_tag(attachment.representation(resize_to_limit: [400, 400]).processed.url, loading: :lazy) .fr-btn.fr-btn--tertiary.fr-btn--icon-left.fr-icon-eye{ role: :button } = 'Visualiser' - else diff --git a/spec/jobs/image_processor_job_spec.rb b/spec/jobs/image_processor_job_spec.rb index 9c520babd..e32999273 100644 --- a/spec/jobs/image_processor_job_spec.rb +++ b/spec/jobs/image_processor_job_spec.rb @@ -89,11 +89,11 @@ describe ImageProcessorJob, type: :job do context "when representation is required" do before do - allow(blob).to receive(:variant_required?).and_return(true) + allow(blob).to receive(:representation_required?).and_return(true) end it "it creates blob representation" do - expect { described_class.perform_now(blob) }.to change { ActiveStorage::VariantRecord.count }.by(2) + expect { described_class.perform_now(blob) }.to change { ActiveStorage::VariantRecord.count }.by(1) end end end From 7cfd9becc8817cb8183e10d1649e594c09210e23 Mon Sep 17 00:00:00 2001 From: Lisa Durand Date: Thu, 16 May 2024 12:54:09 +0200 Subject: [PATCH 6/6] avoid performing job if attachment is created on existed blob --- app/models/concerns/attachment_image_processor_concern.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/attachment_image_processor_concern.rb b/app/models/concerns/attachment_image_processor_concern.rb index 459e54d76..25d4a3a09 100644 --- a/app/models/concerns/attachment_image_processor_concern.rb +++ b/app/models/concerns/attachment_image_processor_concern.rb @@ -16,7 +16,8 @@ module AttachmentImageProcessorConcern def process_image return if blob.nil? - return if blob.attachments.any? { _1.record_type == "Export" } + return if blob.attachments.size > 1 + return if blob.attachments.last.record_type == "Export" ImageProcessorJob.perform_later(blob) end