activestorage 6.0.3.4 → 6.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +144 -162
- data/MIT-LICENSE +1 -1
- data/README.md +35 -3
- data/app/controllers/active_storage/base_controller.rb +11 -0
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
- data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +2 -2
- data/app/controllers/active_storage/disk_controller.rb +8 -20
- data/app/controllers/active_storage/representations/proxy_controller.rb +19 -0
- data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +2 -2
- data/app/controllers/concerns/active_storage/file_server.rb +18 -0
- data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +2 -2
- data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
- data/app/jobs/active_storage/mirror_job.rb +15 -0
- data/app/models/active_storage/attachment.rb +18 -10
- data/app/models/active_storage/blob.rb +114 -59
- data/app/models/active_storage/blob/analyzable.rb +6 -2
- data/app/models/active_storage/blob/identifiable.rb +7 -6
- data/app/models/active_storage/blob/representable.rb +34 -4
- data/app/models/active_storage/preview.rb +31 -10
- data/app/models/active_storage/record.rb +7 -0
- data/app/models/active_storage/variant.rb +28 -41
- data/app/models/active_storage/variant_record.rb +8 -0
- data/app/models/active_storage/variant_with_record.rb +54 -0
- data/app/models/active_storage/variation.rb +25 -20
- data/config/routes.rb +58 -8
- data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
- data/lib/active_storage.rb +5 -2
- data/lib/active_storage/analyzer.rb +6 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +3 -0
- data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +14 -3
- data/lib/active_storage/attached/changes/create_many.rb +1 -0
- data/lib/active_storage/attached/changes/create_one.rb +17 -4
- data/lib/active_storage/attached/many.rb +4 -3
- data/lib/active_storage/attached/model.rb +59 -12
- data/lib/active_storage/attached/one.rb +4 -3
- data/lib/active_storage/engine.rb +25 -27
- data/lib/active_storage/gem_version.rb +3 -3
- data/lib/active_storage/log_subscriber.rb +6 -0
- data/lib/active_storage/previewer.rb +3 -2
- data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/video_previewer.rb +2 -2
- data/lib/active_storage/service.rb +36 -7
- data/lib/active_storage/service/azure_storage_service.rb +40 -35
- data/lib/active_storage/service/configurator.rb +3 -1
- data/lib/active_storage/service/disk_service.rb +36 -31
- data/lib/active_storage/service/gcs_service.rb +18 -16
- data/lib/active_storage/service/mirror_service.rb +31 -7
- data/lib/active_storage/service/registry.rb +32 -0
- data/lib/active_storage/service/s3_service.rb +53 -23
- data/lib/active_storage/transformers/image_processing_transformer.rb +13 -7
- data/lib/active_storage/transformers/transformer.rb +0 -3
- metadata +57 -21
- data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +0 -9
- data/lib/active_storage/downloading.rb +0 -47
- data/lib/active_storage/transformers/mini_magick_transformer.rb +0 -38
| @@ -3,7 +3,7 @@ | |
| 3 3 | 
             
            module ActiveStorage
         | 
| 4 4 | 
             
              # Representation of a single attachment to a model.
         | 
| 5 5 | 
             
              class Attached::One < Attached
         | 
| 6 | 
            -
                delegate_missing_to :attachment
         | 
| 6 | 
            +
                delegate_missing_to :attachment, allow_nil: true
         | 
| 7 7 |  | 
| 8 8 | 
             
                # Returns the associated attachment record.
         | 
| 9 9 | 
             
                #
         | 
| @@ -29,7 +29,8 @@ module ActiveStorage | |
| 29 29 | 
             
                #   person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
         | 
| 30 30 | 
             
                def attach(attachable)
         | 
| 31 31 | 
             
                  if record.persisted? && !record.changed?
         | 
| 32 | 
            -
                    record. | 
| 32 | 
            +
                    record.public_send("#{name}=", attachable)
         | 
| 33 | 
            +
                    record.save
         | 
| 33 34 | 
             
                  else
         | 
| 34 35 | 
             
                    record.public_send("#{name}=", attachable)
         | 
| 35 36 | 
             
                  end
         | 
| @@ -37,7 +38,7 @@ module ActiveStorage | |
| 37 38 |  | 
| 38 39 | 
             
                # Returns +true+ if an attachment has been made.
         | 
| 39 40 | 
             
                #
         | 
| 40 | 
            -
                #   class User <  | 
| 41 | 
            +
                #   class User < ApplicationRecord
         | 
| 41 42 | 
             
                #     has_one_attached :avatar
         | 
| 42 43 | 
             
                #   end
         | 
| 43 44 | 
             
                #
         | 
| @@ -14,6 +14,8 @@ require "active_storage/previewer/video_previewer" | |
| 14 14 | 
             
            require "active_storage/analyzer/image_analyzer"
         | 
| 15 15 | 
             
            require "active_storage/analyzer/video_analyzer"
         | 
| 16 16 |  | 
| 17 | 
            +
            require "active_storage/service/registry"
         | 
| 18 | 
            +
             | 
| 17 19 | 
             
            require "active_storage/reflection"
         | 
| 18 20 |  | 
| 19 21 | 
             
            module ActiveStorage
         | 
| @@ -24,7 +26,7 @@ module ActiveStorage | |
| 24 26 | 
             
                config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
         | 
| 25 27 | 
             
                config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
         | 
| 26 28 | 
             
                config.active_storage.paths = ActiveSupport::OrderedOptions.new
         | 
| 27 | 
            -
                config.active_storage.queues = ActiveSupport:: | 
| 29 | 
            +
                config.active_storage.queues = ActiveSupport::InheritableOptions.new(mirror: :active_storage_mirror)
         | 
| 28 30 |  | 
| 29 31 | 
             
                config.active_storage.variable_content_types = %w(
         | 
| 30 32 | 
             
                  image/png
         | 
| @@ -36,6 +38,14 @@ module ActiveStorage | |
| 36 38 | 
             
                  image/bmp
         | 
| 37 39 | 
             
                  image/vnd.adobe.photoshop
         | 
| 38 40 | 
             
                  image/vnd.microsoft.icon
         | 
| 41 | 
            +
                  image/webp
         | 
| 42 | 
            +
                )
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                config.active_storage.web_image_content_types = %w(
         | 
| 45 | 
            +
                  image/png
         | 
| 46 | 
            +
                  image/jpeg
         | 
| 47 | 
            +
                  image/jpg
         | 
| 48 | 
            +
                  image/gif
         | 
| 39 49 | 
             
                )
         | 
| 40 50 |  | 
| 41 51 | 
             
                config.active_storage.content_types_to_serve_as_binary = %w(
         | 
| @@ -73,14 +83,18 @@ module ActiveStorage | |
| 73 83 | 
             
                    ActiveStorage.analyzers         = app.config.active_storage.analyzers || []
         | 
| 74 84 | 
             
                    ActiveStorage.paths             = app.config.active_storage.paths || {}
         | 
| 75 85 | 
             
                    ActiveStorage.routes_prefix     = app.config.active_storage.routes_prefix || "/rails/active_storage"
         | 
| 86 | 
            +
                    ActiveStorage.draw_routes       = app.config.active_storage.draw_routes != false
         | 
| 87 | 
            +
                    ActiveStorage.resolve_model_to_route = app.config.active_storage.resolve_model_to_route || :rails_storage_redirect
         | 
| 76 88 |  | 
| 77 89 | 
             
                    ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
         | 
| 90 | 
            +
                    ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
         | 
| 78 91 | 
             
                    ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
         | 
| 79 92 | 
             
                    ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
         | 
| 80 93 | 
             
                    ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
         | 
| 81 94 | 
             
                    ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
         | 
| 82 95 |  | 
| 83 96 | 
             
                    ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
         | 
| 97 | 
            +
                    ActiveStorage.track_variants = app.config.active_storage.track_variants || false
         | 
| 84 98 | 
             
                  end
         | 
| 85 99 | 
             
                end
         | 
| 86 100 |  | 
| @@ -100,42 +114,26 @@ module ActiveStorage | |
| 100 114 |  | 
| 101 115 | 
             
                initializer "active_storage.services" do
         | 
| 102 116 | 
             
                  ActiveSupport.on_load(:active_storage_blob) do
         | 
| 103 | 
            -
                     | 
| 104 | 
            -
                       | 
| 105 | 
            -
                        config_file =  | 
| 117 | 
            +
                    configs = Rails.configuration.active_storage.service_configurations ||=
         | 
| 118 | 
            +
                      begin
         | 
| 119 | 
            +
                        config_file = Rails.root.join("config/storage/#{Rails.env}.yml")
         | 
| 120 | 
            +
                        config_file = Rails.root.join("config/storage.yml") unless config_file.exist?
         | 
| 106 121 | 
             
                        raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist?
         | 
| 107 122 |  | 
| 108 | 
            -
                         | 
| 109 | 
            -
                        require "erb"
         | 
| 110 | 
            -
             | 
| 111 | 
            -
                        YAML.load(ERB.new(config_file.read).result) || {}
         | 
| 112 | 
            -
                      rescue Psych::SyntaxError => e
         | 
| 113 | 
            -
                        raise "YAML syntax error occurred while parsing #{config_file}. " \
         | 
| 114 | 
            -
                              "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
         | 
| 115 | 
            -
                              "Error: #{e.message}"
         | 
| 123 | 
            +
                        ActiveSupport::ConfigurationFile.parse(config_file)
         | 
| 116 124 | 
             
                      end
         | 
| 117 125 |  | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
                          raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace
         | 
| 123 | 
            -
                        end
         | 
| 126 | 
            +
                    ActiveStorage::Blob.services = ActiveStorage::Service::Registry.new(configs)
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    if config_choice = Rails.configuration.active_storage.service
         | 
| 129 | 
            +
                      ActiveStorage::Blob.service = ActiveStorage::Blob.services.fetch(config_choice)
         | 
| 124 130 | 
             
                    end
         | 
| 125 131 | 
             
                  end
         | 
| 126 132 | 
             
                end
         | 
| 127 133 |  | 
| 128 134 | 
             
                initializer "active_storage.queues" do
         | 
| 129 135 | 
             
                  config.after_initialize do |app|
         | 
| 130 | 
            -
                     | 
| 131 | 
            -
                      ActiveSupport::Deprecation.warn \
         | 
| 132 | 
            -
                        "config.active_storage.queue is deprecated and will be removed in Rails 6.1. " \
         | 
| 133 | 
            -
                        "Set config.active_storage.queues.purge and config.active_storage.queues.analysis instead."
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                      ActiveStorage.queues = { purge: queue, analysis: queue }
         | 
| 136 | 
            -
                    else
         | 
| 137 | 
            -
                      ActiveStorage.queues = app.config.active_storage.queues || {}
         | 
| 138 | 
            -
                    end
         | 
| 136 | 
            +
                    ActiveStorage.queues = app.config.active_storage.queues || {}
         | 
| 139 137 | 
             
                  end
         | 
| 140 138 | 
             
                end
         | 
| 141 139 |  | 
| @@ -32,6 +32,12 @@ module ActiveStorage | |
| 32 32 | 
             
                  debug event, color("Generated URL for file at key: #{key_in(event)} (#{event.payload[:url]})", BLUE)
         | 
| 33 33 | 
             
                end
         | 
| 34 34 |  | 
| 35 | 
            +
                def service_mirror(event)
         | 
| 36 | 
            +
                  message = "Mirrored file at key: #{key_in(event)}"
         | 
| 37 | 
            +
                  message += " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum]
         | 
| 38 | 
            +
                  debug event, color(message, GREEN)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 35 41 | 
             
                def logger
         | 
| 36 42 | 
             
                  ActiveStorage.logger
         | 
| 37 43 | 
             
                end
         | 
| @@ -18,8 +18,9 @@ module ActiveStorage | |
| 18 18 | 
             
                end
         | 
| 19 19 |  | 
| 20 20 | 
             
                # Override this method in a concrete subclass. Have it yield an attachable preview image (i.e.
         | 
| 21 | 
            -
                # anything accepted by ActiveStorage::Attached::One#attach).
         | 
| 22 | 
            -
                 | 
| 21 | 
            +
                # anything accepted by ActiveStorage::Attached::One#attach). Pass the additional options to
         | 
| 22 | 
            +
                # the underlying blob that is created.
         | 
| 23 | 
            +
                def preview(**options)
         | 
| 23 24 | 
             
                  raise NotImplementedError
         | 
| 24 25 | 
             
                end
         | 
| 25 26 |  | 
| @@ -12,7 +12,7 @@ module ActiveStorage | |
| 12 12 | 
             
                  end
         | 
| 13 13 |  | 
| 14 14 | 
             
                  def mutool_exists?
         | 
| 15 | 
            -
                    return @mutool_exists  | 
| 15 | 
            +
                    return @mutool_exists if defined?(@mutool_exists) && !@mutool_exists.nil?
         | 
| 16 16 |  | 
| 17 17 | 
             
                    system mutool_path, out: File::NULL, err: File::NULL
         | 
| 18 18 |  | 
| @@ -20,10 +20,10 @@ module ActiveStorage | |
| 20 20 | 
             
                  end
         | 
| 21 21 | 
             
                end
         | 
| 22 22 |  | 
| 23 | 
            -
                def preview
         | 
| 23 | 
            +
                def preview(**options)
         | 
| 24 24 | 
             
                  download_blob_to_tempfile do |input|
         | 
| 25 25 | 
             
                    draw_first_page_from input do |output|
         | 
| 26 | 
            -
                      yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
         | 
| 26 | 
            +
                      yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png", **options
         | 
| 27 27 | 
             
                    end
         | 
| 28 28 | 
             
                  end
         | 
| 29 29 | 
             
                end
         | 
| @@ -18,10 +18,10 @@ module ActiveStorage | |
| 18 18 | 
             
                  end
         | 
| 19 19 | 
             
                end
         | 
| 20 20 |  | 
| 21 | 
            -
                def preview
         | 
| 21 | 
            +
                def preview(**options)
         | 
| 22 22 | 
             
                  download_blob_to_tempfile do |input|
         | 
| 23 23 | 
             
                    draw_first_page_from input do |output|
         | 
| 24 | 
            -
                      yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
         | 
| 24 | 
            +
                      yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png", **options
         | 
| 25 25 | 
             
                    end
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 | 
             
                end
         | 
| @@ -29,7 +29,7 @@ module ActiveStorage | |
| 29 29 | 
             
                private
         | 
| 30 30 | 
             
                  def draw_first_page_from(file, &block)
         | 
| 31 31 | 
             
                    # use 72 dpi to match thumbnail dimensions of the PDF
         | 
| 32 | 
            -
                    draw self.class.pdftoppm_path, "-singlefile", "-r", "72", "-png", file.path, &block
         | 
| 32 | 
            +
                    draw self.class.pdftoppm_path, "-singlefile", "-cropbox", "-r", "72", "-png", file.path, &block
         | 
| 33 33 | 
             
                  end
         | 
| 34 34 | 
             
              end
         | 
| 35 35 | 
             
            end
         | 
| @@ -18,10 +18,10 @@ module ActiveStorage | |
| 18 18 | 
             
                  end
         | 
| 19 19 | 
             
                end
         | 
| 20 20 |  | 
| 21 | 
            -
                def preview
         | 
| 21 | 
            +
                def preview(**options)
         | 
| 22 22 | 
             
                  download_blob_to_tempfile do |input|
         | 
| 23 23 | 
             
                    draw_relevant_frame_from input do |output|
         | 
| 24 | 
            -
                      yield io: output, filename: "#{blob.filename.base}.jpg", content_type: "image/jpeg"
         | 
| 24 | 
            +
                      yield io: output, filename: "#{blob.filename.base}.jpg", content_type: "image/jpeg", **options
         | 
| 25 25 | 
             
                    end
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 | 
             
                end
         | 
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require "active_storage/log_subscriber"
         | 
| 4 | 
            +
            require "active_storage/downloader"
         | 
| 4 5 | 
             
            require "action_dispatch"
         | 
| 5 6 | 
             
            require "action_dispatch/http/content_disposition"
         | 
| 6 7 |  | 
| @@ -40,6 +41,7 @@ module ActiveStorage | |
| 40 41 | 
             
              class Service
         | 
| 41 42 | 
             
                extend ActiveSupport::Autoload
         | 
| 42 43 | 
             
                autoload :Configurator
         | 
| 44 | 
            +
                attr_accessor :name
         | 
| 43 45 |  | 
| 44 46 | 
             
                class << self
         | 
| 45 47 | 
             
                  # Configure an Active Storage service by name from a set of configurations,
         | 
| @@ -55,8 +57,10 @@ module ActiveStorage | |
| 55 57 | 
             
                  # Passes the configurator and all of the service's config as keyword args.
         | 
| 56 58 | 
             
                  #
         | 
| 57 59 | 
             
                  # See MirrorService for an example.
         | 
| 58 | 
            -
                  def build(configurator:, service: nil, **service_config) #:nodoc:
         | 
| 59 | 
            -
                    new(**service_config)
         | 
| 60 | 
            +
                  def build(configurator:, name:, service: nil, **service_config) #:nodoc:
         | 
| 61 | 
            +
                    new(**service_config).tap do |service_instance|
         | 
| 62 | 
            +
                      service_instance.name = name
         | 
| 63 | 
            +
                    end
         | 
| 60 64 | 
             
                  end
         | 
| 61 65 | 
             
                end
         | 
| 62 66 |  | 
| @@ -101,11 +105,23 @@ module ActiveStorage | |
| 101 105 | 
             
                  raise NotImplementedError
         | 
| 102 106 | 
             
                end
         | 
| 103 107 |  | 
| 104 | 
            -
                # Returns  | 
| 105 | 
            -
                #  | 
| 106 | 
            -
                # +filename+, and +content_type+ that you wish the file to be served with on request.
         | 
| 107 | 
            -
                 | 
| 108 | 
            -
             | 
| 108 | 
            +
                # Returns the URL for the file at the +key+. This returns a permanent URL for public files, and returns a
         | 
| 109 | 
            +
                # short-lived URL for private files. For private files you can provide the +disposition+ (+:inline+ or +:attachment+),
         | 
| 110 | 
            +
                # +filename+, and +content_type+ that you wish the file to be served with on request. Additionally, you can also provide
         | 
| 111 | 
            +
                # the amount of seconds the URL will be valid for, specified in +expires_in+.
         | 
| 112 | 
            +
                def url(key, **options)
         | 
| 113 | 
            +
                  instrument :url, key: key do |payload|
         | 
| 114 | 
            +
                    generated_url =
         | 
| 115 | 
            +
                      if public?
         | 
| 116 | 
            +
                        public_url(key, **options)
         | 
| 117 | 
            +
                      else
         | 
| 118 | 
            +
                        private_url(key, **options)
         | 
| 119 | 
            +
                      end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    payload[:url] = generated_url
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    generated_url
         | 
| 124 | 
            +
                  end
         | 
| 109 125 | 
             
                end
         | 
| 110 126 |  | 
| 111 127 | 
             
                # Returns a signed, temporary URL that a direct upload file can be PUT to on the +key+.
         | 
| @@ -121,7 +137,20 @@ module ActiveStorage | |
| 121 137 | 
             
                  {}
         | 
| 122 138 | 
             
                end
         | 
| 123 139 |  | 
| 140 | 
            +
                def public?
         | 
| 141 | 
            +
                  @public
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 124 144 | 
             
                private
         | 
| 145 | 
            +
                  def private_url(key, expires_in:, filename:, disposition:, content_type:, **)
         | 
| 146 | 
            +
                    raise NotImplementedError
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  def public_url(key, **)
         | 
| 150 | 
            +
                    raise NotImplementedError
         | 
| 151 | 
            +
                  end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
             | 
| 125 154 | 
             
                  def instrument(operation, payload = {}, &block)
         | 
| 126 155 | 
             
                    ActiveSupport::Notifications.instrument(
         | 
| 127 156 | 
             
                      "service_#{operation}.active_storage",
         | 
| @@ -1,26 +1,30 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            gem "azure-storage-blob", ">= 1.1"
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            require "active_support/core_ext/numeric/bytes"
         | 
| 4 | 
            -
            require "azure/storage"
         | 
| 5 | 
            -
            require "azure/storage/core/auth/shared_access_signature"
         | 
| 6 | 
            +
            require "azure/storage/blob"
         | 
| 7 | 
            +
            require "azure/storage/common/core/auth/shared_access_signature"
         | 
| 6 8 |  | 
| 7 9 | 
             
            module ActiveStorage
         | 
| 8 10 | 
             
              # Wraps the Microsoft Azure Storage Blob Service as an Active Storage service.
         | 
| 9 11 | 
             
              # See ActiveStorage::Service for the generic API documentation that applies to all services.
         | 
| 10 12 | 
             
              class Service::AzureStorageService < Service
         | 
| 11 | 
            -
                attr_reader :client, : | 
| 13 | 
            +
                attr_reader :client, :container, :signer
         | 
| 12 14 |  | 
| 13 | 
            -
                def initialize(storage_account_name:, storage_access_key:, container:, **options)
         | 
| 14 | 
            -
                  @client = Azure::Storage:: | 
| 15 | 
            -
                  @signer = Azure::Storage::Core::Auth::SharedAccessSignature.new(storage_account_name, storage_access_key)
         | 
| 16 | 
            -
                  @blobs = client.blob_client
         | 
| 15 | 
            +
                def initialize(storage_account_name:, storage_access_key:, container:, public: false, **options)
         | 
| 16 | 
            +
                  @client = Azure::Storage::Blob::BlobService.create(storage_account_name: storage_account_name, storage_access_key: storage_access_key, **options)
         | 
| 17 | 
            +
                  @signer = Azure::Storage::Common::Core::Auth::SharedAccessSignature.new(storage_account_name, storage_access_key)
         | 
| 17 18 | 
             
                  @container = container
         | 
| 19 | 
            +
                  @public = public
         | 
| 18 20 | 
             
                end
         | 
| 19 21 |  | 
| 20 | 
            -
                def upload(key, io, checksum: nil, **)
         | 
| 22 | 
            +
                def upload(key, io, checksum: nil, filename: nil, content_type: nil, disposition: nil, **)
         | 
| 21 23 | 
             
                  instrument :upload, key: key, checksum: checksum do
         | 
| 22 24 | 
             
                    handle_errors do
         | 
| 23 | 
            -
                       | 
| 25 | 
            +
                      content_disposition = content_disposition_with(filename: filename, type: disposition) if disposition && filename
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                      client.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type, content_disposition: content_disposition)
         | 
| 24 28 | 
             
                    end
         | 
| 25 29 | 
             
                  end
         | 
| 26 30 | 
             
                end
         | 
| @@ -33,7 +37,7 @@ module ActiveStorage | |
| 33 37 | 
             
                  else
         | 
| 34 38 | 
             
                    instrument :download, key: key do
         | 
| 35 39 | 
             
                      handle_errors do
         | 
| 36 | 
            -
                        _, io =  | 
| 40 | 
            +
                        _, io = client.get_blob(container, key)
         | 
| 37 41 | 
             
                        io.force_encoding(Encoding::BINARY)
         | 
| 38 42 | 
             
                      end
         | 
| 39 43 | 
             
                    end
         | 
| @@ -43,7 +47,7 @@ module ActiveStorage | |
| 43 47 | 
             
                def download_chunk(key, range)
         | 
| 44 48 | 
             
                  instrument :download_chunk, key: key, range: range do
         | 
| 45 49 | 
             
                    handle_errors do
         | 
| 46 | 
            -
                      _, io =  | 
| 50 | 
            +
                      _, io = client.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
         | 
| 47 51 | 
             
                      io.force_encoding(Encoding::BINARY)
         | 
| 48 52 | 
             
                    end
         | 
| 49 53 | 
             
                  end
         | 
| @@ -51,7 +55,7 @@ module ActiveStorage | |
| 51 55 |  | 
| 52 56 | 
             
                def delete(key)
         | 
| 53 57 | 
             
                  instrument :delete, key: key do
         | 
| 54 | 
            -
                     | 
| 58 | 
            +
                    client.delete_blob(container, key)
         | 
| 55 59 | 
             
                  rescue Azure::Core::Http::HTTPError => e
         | 
| 56 60 | 
             
                    raise unless e.type == "BlobNotFound"
         | 
| 57 61 | 
             
                    # Ignore files already deleted
         | 
| @@ -63,10 +67,10 @@ module ActiveStorage | |
| 63 67 | 
             
                    marker = nil
         | 
| 64 68 |  | 
| 65 69 | 
             
                    loop do
         | 
| 66 | 
            -
                      results =  | 
| 70 | 
            +
                      results = client.list_blobs(container, prefix: prefix, marker: marker)
         | 
| 67 71 |  | 
| 68 72 | 
             
                      results.each do |blob|
         | 
| 69 | 
            -
                         | 
| 73 | 
            +
                        client.delete_blob(container, blob.name)
         | 
| 70 74 | 
             
                      end
         | 
| 71 75 |  | 
| 72 76 | 
             
                      break unless marker = results.continuation_token.presence
         | 
| @@ -82,15 +86,13 @@ module ActiveStorage | |
| 82 86 | 
             
                  end
         | 
| 83 87 | 
             
                end
         | 
| 84 88 |  | 
| 85 | 
            -
                def  | 
| 89 | 
            +
                def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
         | 
| 86 90 | 
             
                  instrument :url, key: key do |payload|
         | 
| 87 91 | 
             
                    generated_url = signer.signed_uri(
         | 
| 88 92 | 
             
                      uri_for(key), false,
         | 
| 89 93 | 
             
                      service: "b",
         | 
| 90 | 
            -
                      permissions: " | 
| 91 | 
            -
                      expiry: format_expiry(expires_in) | 
| 92 | 
            -
                      content_disposition: content_disposition_with(type: disposition, filename: filename),
         | 
| 93 | 
            -
                      content_type: content_type
         | 
| 94 | 
            +
                      permissions: "rw",
         | 
| 95 | 
            +
                      expiry: format_expiry(expires_in)
         | 
| 94 96 | 
             
                    ).to_s
         | 
| 95 97 |  | 
| 96 98 | 
             
                    payload[:url] = generated_url
         | 
| @@ -99,32 +101,35 @@ module ActiveStorage | |
| 99 101 | 
             
                  end
         | 
| 100 102 | 
             
                end
         | 
| 101 103 |  | 
| 102 | 
            -
                def  | 
| 103 | 
            -
                   | 
| 104 | 
            -
             | 
| 104 | 
            +
                def headers_for_direct_upload(key, content_type:, checksum:, filename: nil, disposition: nil, **)
         | 
| 105 | 
            +
                  content_disposition = content_disposition_with(type: disposition, filename: filename) if filename
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  { "Content-Type" => content_type, "Content-MD5" => checksum, "x-ms-blob-content-disposition" => content_disposition, "x-ms-blob-type" => "BlockBlob" }
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                private
         | 
| 111 | 
            +
                  def private_url(key, expires_in:, filename:, disposition:, content_type:, **)
         | 
| 112 | 
            +
                    signer.signed_uri(
         | 
| 105 113 | 
             
                      uri_for(key), false,
         | 
| 106 114 | 
             
                      service: "b",
         | 
| 107 | 
            -
                      permissions: " | 
| 108 | 
            -
                      expiry: format_expiry(expires_in)
         | 
| 115 | 
            +
                      permissions: "r",
         | 
| 116 | 
            +
                      expiry: format_expiry(expires_in),
         | 
| 117 | 
            +
                      content_disposition: content_disposition_with(type: disposition, filename: filename),
         | 
| 118 | 
            +
                      content_type: content_type
         | 
| 109 119 | 
             
                    ).to_s
         | 
| 120 | 
            +
                  end
         | 
| 110 121 |  | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
                    generated_url
         | 
| 122 | 
            +
                  def public_url(key, **)
         | 
| 123 | 
            +
                    uri_for(key).to_s
         | 
| 114 124 | 
             
                  end
         | 
| 115 | 
            -
                end
         | 
| 116 125 |  | 
| 117 | 
            -
                def headers_for_direct_upload(key, content_type:, checksum:, **)
         | 
| 118 | 
            -
                  { "Content-Type" => content_type, "Content-MD5" => checksum, "x-ms-blob-type" => "BlockBlob" }
         | 
| 119 | 
            -
                end
         | 
| 120 126 |  | 
| 121 | 
            -
                private
         | 
| 122 127 | 
             
                  def uri_for(key)
         | 
| 123 | 
            -
                     | 
| 128 | 
            +
                    client.generate_uri("#{container}/#{key}")
         | 
| 124 129 | 
             
                  end
         | 
| 125 130 |  | 
| 126 131 | 
             
                  def blob_for(key)
         | 
| 127 | 
            -
                     | 
| 132 | 
            +
                    client.get_blob_properties(container, key)
         | 
| 128 133 | 
             
                  rescue Azure::Core::Http::HTTPError
         | 
| 129 134 | 
             
                    false
         | 
| 130 135 | 
             
                  end
         | 
| @@ -143,7 +148,7 @@ module ActiveStorage | |
| 143 148 | 
             
                    raise ActiveStorage::FileNotFoundError unless blob.present?
         | 
| 144 149 |  | 
| 145 150 | 
             
                    while offset < blob.properties[:content_length]
         | 
| 146 | 
            -
                      _, chunk =  | 
| 151 | 
            +
                      _, chunk = client.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1)
         | 
| 147 152 | 
             
                      yield chunk.force_encoding(Encoding::BINARY)
         | 
| 148 153 | 
             
                      offset += chunk_size
         | 
| 149 154 | 
             
                    end
         |