From 11faeef8b82fecdc39863afed81d57c0d35b07d2 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Sun, 26 Feb 2023 14:00:01 +0100 Subject: [PATCH] Add PictureThumb.storage_class Instead of configuring a `PictureThumb.generator_class` that needs to implement the creation of the `Alchemy::PictureThumb` records and make sure do that in a multi-concurrency safe way, we keep the implementation in core and abstract the storage class instead. --- app/models/alchemy/picture/url.rb | 2 +- app/models/alchemy/picture_thumb.rb | 20 +++++------ app/models/alchemy/picture_thumb/create.rb | 25 ++++---------- .../alchemy/picture_thumb/file_store.rb | 33 +++++++++++++++++++ .../alchemy/picture_thumb/file_store_spec.rb | 27 +++++++++++++++ 5 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 app/models/alchemy/picture_thumb/file_store.rb create mode 100644 spec/models/alchemy/picture_thumb/file_store_spec.rb diff --git a/app/models/alchemy/picture/url.rb b/app/models/alchemy/picture/url.rb index c819c74d1a..4fd2a7aa3e 100644 --- a/app/models/alchemy/picture/url.rb +++ b/app/models/alchemy/picture/url.rb @@ -36,7 +36,7 @@ def uid else uid = PictureThumb::Uid.call(signature, variant) ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do - PictureThumb.generator_class.call(variant, signature, uid) + PictureThumb::Create.call(variant, signature, uid) end uid end diff --git a/app/models/alchemy/picture_thumb.rb b/app/models/alchemy/picture_thumb.rb index 3d8088f9ad..982b122290 100644 --- a/app/models/alchemy/picture_thumb.rb +++ b/app/models/alchemy/picture_thumb.rb @@ -7,7 +7,7 @@ module Alchemy # different thumbnail store (ie. a remote file storage). # # config/initializers/alchemy.rb - # Alchemy::PictureThumb.generator_class = My::ThumbnailGenerator + # Alchemy::PictureThumb.storage_class = My::ThumbnailStore # class PictureThumb < BaseRecord belongs_to :picture, class_name: "Alchemy::Picture" @@ -16,18 +16,18 @@ class PictureThumb < BaseRecord validates :uid, presence: true class << self - # Thumbnail generator class + # Thumbnail storage class # - # @see Alchemy::PictureThumb::Create - def generator_class - @_generator_class ||= Alchemy::PictureThumb::Create + # @see Alchemy::PictureThumb::FileStore + def storage_class + @_storage_class ||= Alchemy::PictureThumb::FileStore end - # Set a thumbnail generator class + # Set a thumbnail storage class # - # @see Alchemy::PictureThumb::Create - def generator_class=(klass) - @_generator_class = klass + # @see Alchemy::PictureThumb::FileStore + def storage_class=(klass) + @_storage_class = klass end # Upfront generation of picture thumbnails @@ -49,7 +49,7 @@ def generate_thumbs!(picture) next if thumb uid = Alchemy::PictureThumb::Uid.call(signature, variant) - generator_class.call(variant, signature, uid) + Alchemy::PictureThumb::Create.call(variant, signature, uid) end end end diff --git a/app/models/alchemy/picture_thumb/create.rb b/app/models/alchemy/picture_thumb/create.rb index 0334f4e4c3..6461b43f55 100644 --- a/app/models/alchemy/picture_thumb/create.rb +++ b/app/models/alchemy/picture_thumb/create.rb @@ -2,9 +2,11 @@ module Alchemy class PictureThumb < BaseRecord - # Stores the render result of a Alchemy::PictureVariant - # in the configured Dragonfly datastore - # (Default: Dragonfly::FileDataStore) + # Creates a Alchemy::PictureThumb + # + # Stores the processes result of a Alchemy::PictureVariant + # in the configured +Alchemy::PictureThumb.storage_class+ + # (Default: {Alchemy::PictureThumb::FileStore}) # class Create class << self @@ -24,26 +26,13 @@ def call(variant, signature, uid) thumb.uid = uid end begin - # process the image - image = variant.image - # store the processed image - image.to_file(server_path(uid)).close - rescue RuntimeError => e + Alchemy::PictureThumb.storage_class.call(variant, uid) + rescue StandardError => e ErrorTracking.notification_handler.call(e) # destroy the thumb if processing or storing fails @thumb&.destroy end end - - private - - # Alchemys dragonfly datastore config seperates the storage path from the public server - # path for security reasons. The Dragonfly FileDataStorage does not support that, - # so we need to build the path on our own. - def server_path(uid) - dragonfly_app = ::Dragonfly.app(:alchemy_pictures) - "#{dragonfly_app.datastore.server_root}/#{uid}" - end end end end diff --git a/app/models/alchemy/picture_thumb/file_store.rb b/app/models/alchemy/picture_thumb/file_store.rb new file mode 100644 index 0000000000..cfe4d313f1 --- /dev/null +++ b/app/models/alchemy/picture_thumb/file_store.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Alchemy + class PictureThumb < BaseRecord + # Stores the render result of a Alchemy::PictureVariant + # in the configured Dragonfly datastore + # (Default: Dragonfly::FileDataStore) + # + class FileStore + class << self + # @param [Alchemy::PictureVariant] variant the to be rendered image + # @param [String] uid The Unique Image Identifier the image is stored at + # + def call(variant, uid) + # process the image + image = variant.image + # store the processed image + image.to_file(server_path(uid)).close + end + + private + + # Alchemys dragonfly datastore config seperates the storage path from the public server + # path for security reasons. The Dragonfly FileDataStorage does not support that, + # so we need to build the path on our own. + def server_path(uid) + dragonfly_app = ::Dragonfly.app(:alchemy_pictures) + "#{dragonfly_app.datastore.server_root}/#{uid}" + end + end + end + end +end diff --git a/spec/models/alchemy/picture_thumb/file_store_spec.rb b/spec/models/alchemy/picture_thumb/file_store_spec.rb new file mode 100644 index 0000000000..fd5f331284 --- /dev/null +++ b/spec/models/alchemy/picture_thumb/file_store_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Alchemy::PictureThumb::FileStore do + let(:image) { File.new(File.expand_path("../../../fixtures/image.png", __dir__)) } + let(:picture) { FactoryBot.create(:alchemy_picture, image_file: image) } + let!(:variant) { Alchemy::PictureVariant.new(picture, { size: "1x1" }) } + let(:uid_path) { "pictures/#{picture.id}/1234" } + + let(:root_path) do + datastore = Dragonfly.app(:alchemy_pictures).datastore + datastore.server_root + end + + subject(:store) do + Alchemy::PictureThumb::FileStore.call(variant, "/#{uid_path}/image.png") + end + + before do + FileUtils.rm_rf("#{root_path}/#{uid_path}") + end + + it "stores thumb on the disk" do + expect { store }.to change { Dir.glob("#{root_path}/#{uid_path}").length }.by(1) + end +end