activestorage 6.1.4 → 7.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +143 -200
- data/MIT-LICENSE +1 -1
- data/README.md +25 -11
- data/app/assets/javascripts/activestorage.esm.js +856 -0
- data/app/assets/javascripts/activestorage.js +270 -377
- data/app/controllers/active_storage/base_controller.rb +1 -10
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -4
- data/app/controllers/active_storage/blobs/redirect_controller.rb +6 -4
- data/app/controllers/active_storage/direct_uploads_controller.rb +7 -1
- data/app/controllers/active_storage/disk_controller.rb +1 -0
- data/app/controllers/active_storage/representations/base_controller.rb +5 -1
- data/app/controllers/active_storage/representations/proxy_controller.rb +6 -4
- data/app/controllers/active_storage/representations/redirect_controller.rb +6 -4
- data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
- data/app/controllers/concerns/active_storage/set_current.rb +3 -3
- data/app/controllers/concerns/active_storage/streaming.rb +65 -0
- data/app/javascript/activestorage/blob_record.js +10 -3
- data/app/javascript/activestorage/direct_upload.js +4 -2
- data/app/javascript/activestorage/direct_upload_controller.js +9 -1
- data/app/javascript/activestorage/ujs.js +1 -1
- data/app/models/active_storage/attachment.rb +36 -3
- data/app/models/active_storage/blob/representable.rb +7 -5
- data/app/models/active_storage/blob.rb +92 -36
- data/app/models/active_storage/current.rb +12 -2
- data/app/models/active_storage/preview.rb +6 -4
- data/app/models/active_storage/record.rb +1 -1
- data/app/models/active_storage/variant.rb +3 -6
- data/app/models/active_storage/variant_record.rb +2 -0
- data/app/models/active_storage/variant_with_record.rb +9 -5
- data/app/models/active_storage/variation.rb +2 -2
- data/config/routes.rb +10 -10
- data/db/migrate/20170806125915_create_active_storage_tables.rb +32 -11
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +15 -2
- data/db/update_migrate/20211119233751_remove_not_null_on_active_storage_blobs_checksum.rb +5 -0
- data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
- data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
- data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
- data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
- data/lib/active_storage/analyzer.rb +8 -4
- data/lib/active_storage/attached/changes/create_many.rb +7 -3
- data/lib/active_storage/attached/changes/create_one.rb +1 -1
- data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
- data/lib/active_storage/attached/changes/delete_many.rb +1 -1
- data/lib/active_storage/attached/changes/delete_one.rb +1 -1
- data/lib/active_storage/attached/changes/detach_many.rb +18 -0
- data/lib/active_storage/attached/changes/detach_one.rb +24 -0
- data/lib/active_storage/attached/changes/purge_many.rb +27 -0
- data/lib/active_storage/attached/changes/purge_one.rb +27 -0
- data/lib/active_storage/attached/changes.rb +7 -1
- data/lib/active_storage/attached/many.rb +27 -15
- data/lib/active_storage/attached/model.rb +31 -5
- data/lib/active_storage/attached/one.rb +32 -27
- data/lib/active_storage/direct_upload_token.rb +59 -0
- data/lib/active_storage/downloader.rb +4 -4
- data/lib/active_storage/engine.rb +30 -1
- data/lib/active_storage/errors.rb +3 -0
- data/lib/active_storage/fixture_set.rb +76 -0
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/previewer.rb +4 -4
- data/lib/active_storage/reflection.rb +12 -2
- data/lib/active_storage/service/azure_storage_service.rb +28 -6
- data/lib/active_storage/service/configurator.rb +1 -1
- data/lib/active_storage/service/disk_service.rb +24 -19
- data/lib/active_storage/service/gcs_service.rb +109 -11
- data/lib/active_storage/service/mirror_service.rb +2 -2
- data/lib/active_storage/service/registry.rb +1 -1
- data/lib/active_storage/service/s3_service.rb +37 -15
- data/lib/active_storage/service.rb +13 -5
- data/lib/active_storage/transformers/image_processing_transformer.rb +1 -1
- data/lib/active_storage/transformers/transformer.rb +1 -1
- data/lib/active_storage.rb +6 -1
- metadata +32 -20
- data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -3,6 +3,25 @@
|
|
3
3
|
module ActiveStorage
|
4
4
|
# Representation of a single attachment to a model.
|
5
5
|
class Attached::One < Attached
|
6
|
+
##
|
7
|
+
# :method: purge
|
8
|
+
#
|
9
|
+
# Directly purges the attachment (i.e. destroys the blob and
|
10
|
+
# attachment and deletes the file on the service).
|
11
|
+
delegate :purge, to: :purge_one
|
12
|
+
|
13
|
+
##
|
14
|
+
# :method: purge_later
|
15
|
+
#
|
16
|
+
# Purges the attachment through the queuing system.
|
17
|
+
delegate :purge_later, to: :purge_one
|
18
|
+
|
19
|
+
##
|
20
|
+
# :method: detach
|
21
|
+
#
|
22
|
+
# Deletes the attachment without purging it, leaving its blob in place.
|
23
|
+
delegate :detach, to: :detach_one
|
24
|
+
|
6
25
|
delegate_missing_to :attachment, allow_nil: true
|
7
26
|
|
8
27
|
# Returns the associated attachment record.
|
@@ -13,6 +32,13 @@ module ActiveStorage
|
|
13
32
|
change.present? ? change.attachment : record.public_send("#{name}_attachment")
|
14
33
|
end
|
15
34
|
|
35
|
+
# Returns +true+ if an attachment is not attached.
|
36
|
+
#
|
37
|
+
# class User < ApplicationRecord
|
38
|
+
# has_one_attached :avatar
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# User.new.avatar.blank? # => true
|
16
42
|
def blank?
|
17
43
|
!attached?
|
18
44
|
end
|
@@ -25,7 +51,7 @@ module ActiveStorage
|
|
25
51
|
#
|
26
52
|
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
|
27
53
|
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
|
28
|
-
# person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/
|
54
|
+
# person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpeg")
|
29
55
|
# person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
|
30
56
|
def attach(attachable)
|
31
57
|
if record.persisted? && !record.changed?
|
@@ -47,34 +73,13 @@ module ActiveStorage
|
|
47
73
|
attachment.present?
|
48
74
|
end
|
49
75
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
attachment.delete
|
54
|
-
write_attachment nil
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# Directly purges the attachment (i.e. destroys the blob and
|
59
|
-
# attachment and deletes the file on the service).
|
60
|
-
def purge
|
61
|
-
if attached?
|
62
|
-
attachment.purge
|
63
|
-
write_attachment nil
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Purges the attachment through the queuing system.
|
68
|
-
def purge_later
|
69
|
-
if attached?
|
70
|
-
attachment.purge_later
|
71
|
-
write_attachment nil
|
76
|
+
private
|
77
|
+
def purge_one
|
78
|
+
Attached::Changes::PurgeOne.new(name, record, attachment)
|
72
79
|
end
|
73
|
-
end
|
74
80
|
|
75
|
-
|
76
|
-
|
77
|
-
record.public_send("#{name}_attachment=", attachment)
|
81
|
+
def detach_one
|
82
|
+
Attached::Changes::DetachOne.new(name, record, attachment)
|
78
83
|
end
|
79
84
|
end
|
80
85
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
module DirectUploadToken
|
5
|
+
extend self
|
6
|
+
|
7
|
+
SEPARATOR = "."
|
8
|
+
DIRECT_UPLOAD_TOKEN_LENGTH = 32
|
9
|
+
|
10
|
+
def generate_direct_upload_token(attachment_name, service_name, session)
|
11
|
+
token = direct_upload_token(session, attachment_name)
|
12
|
+
encode_direct_upload_token([service_name, token].join(SEPARATOR))
|
13
|
+
end
|
14
|
+
|
15
|
+
def verify_direct_upload_token(token, attachment_name, session)
|
16
|
+
raise ActiveStorage::InvalidDirectUploadTokenError if token.nil?
|
17
|
+
|
18
|
+
service_name, *token_components = decode_token(token).split(SEPARATOR)
|
19
|
+
decoded_token = token_components.join(SEPARATOR)
|
20
|
+
|
21
|
+
return service_name if valid_direct_upload_token?(decoded_token, attachment_name, session)
|
22
|
+
|
23
|
+
raise ActiveStorage::InvalidDirectUploadTokenError
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def direct_upload_token(session, attachment_name) # :doc:
|
28
|
+
direct_upload_token_hmac(session, "direct_upload##{attachment_name}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid_direct_upload_token?(token, attachment_name, session) # :doc:
|
32
|
+
correct_token = direct_upload_token(session, attachment_name)
|
33
|
+
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
|
34
|
+
rescue ArgumentError
|
35
|
+
raise ActiveStorage::InvalidDirectUploadTokenError
|
36
|
+
end
|
37
|
+
|
38
|
+
def direct_upload_token_hmac(session, identifier) # :doc:
|
39
|
+
OpenSSL::HMAC.digest(
|
40
|
+
OpenSSL::Digest::SHA256.new,
|
41
|
+
real_direct_upload_token(session),
|
42
|
+
identifier
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def real_direct_upload_token(session) # :doc:
|
47
|
+
session[:_direct_upload_token] ||= SecureRandom.urlsafe_base64(DIRECT_UPLOAD_TOKEN_LENGTH, padding: false)
|
48
|
+
encode_direct_upload_token(session[:_direct_upload_token])
|
49
|
+
end
|
50
|
+
|
51
|
+
def decode_token(encoded_token) # :nodoc:
|
52
|
+
Base64.urlsafe_decode64(encoded_token)
|
53
|
+
end
|
54
|
+
|
55
|
+
def encode_direct_upload_token(raw_token) # :nodoc:
|
56
|
+
Base64.urlsafe_encode64(raw_token)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
|
-
class Downloader
|
4
|
+
class Downloader # :nodoc:
|
5
5
|
attr_reader :service
|
6
6
|
|
7
7
|
def initialize(service)
|
8
8
|
@service = service
|
9
9
|
end
|
10
10
|
|
11
|
-
def open(key, checksum
|
11
|
+
def open(key, checksum: nil, verify: true, name: "ActiveStorage-", tmpdir: nil)
|
12
12
|
open_tempfile(name, tmpdir) do |file|
|
13
13
|
download key, file
|
14
|
-
verify_integrity_of
|
14
|
+
verify_integrity_of(file, checksum: checksum) if verify
|
15
15
|
yield file
|
16
16
|
end
|
17
17
|
end
|
@@ -35,7 +35,7 @@ module ActiveStorage
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def verify_integrity_of(file, checksum:)
|
38
|
-
unless Digest::MD5.file(file).base64digest == checksum
|
38
|
+
unless OpenSSL::Digest::MD5.file(file).base64digest == checksum
|
39
39
|
raise ActiveStorage::IntegrityError
|
40
40
|
end
|
41
41
|
end
|
@@ -12,7 +12,10 @@ require "active_storage/previewer/mupdf_previewer"
|
|
12
12
|
require "active_storage/previewer/video_previewer"
|
13
13
|
|
14
14
|
require "active_storage/analyzer/image_analyzer"
|
15
|
+
require "active_storage/analyzer/image_analyzer/image_magick"
|
16
|
+
require "active_storage/analyzer/image_analyzer/vips"
|
15
17
|
require "active_storage/analyzer/video_analyzer"
|
18
|
+
require "active_storage/analyzer/audio_analyzer"
|
16
19
|
|
17
20
|
require "active_storage/service/registry"
|
18
21
|
|
@@ -24,7 +27,7 @@ module ActiveStorage
|
|
24
27
|
|
25
28
|
config.active_storage = ActiveSupport::OrderedOptions.new
|
26
29
|
config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
|
27
|
-
config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
|
30
|
+
config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer::Vips, ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick, ActiveStorage::Analyzer::VideoAnalyzer, ActiveStorage::Analyzer::AudioAnalyzer ]
|
28
31
|
config.active_storage.paths = ActiveSupport::OrderedOptions.new
|
29
32
|
config.active_storage.queues = ActiveSupport::InheritableOptions.new
|
30
33
|
|
@@ -39,6 +42,9 @@ module ActiveStorage
|
|
39
42
|
image/vnd.adobe.photoshop
|
40
43
|
image/vnd.microsoft.icon
|
41
44
|
image/webp
|
45
|
+
image/avif
|
46
|
+
image/heic
|
47
|
+
image/heif
|
42
48
|
)
|
43
49
|
|
44
50
|
config.active_storage.web_image_content_types = %w(
|
@@ -90,10 +96,13 @@ module ActiveStorage
|
|
90
96
|
ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
|
91
97
|
ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
|
92
98
|
ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
|
99
|
+
ActiveStorage.urls_expire_in = app.config.active_storage.urls_expire_in
|
93
100
|
ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
|
94
101
|
ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
|
95
102
|
ActiveStorage.video_preview_arguments = app.config.active_storage.video_preview_arguments || "-y -vframes 1 -f image2"
|
96
103
|
|
104
|
+
ActiveStorage.silence_invalid_content_types_warning = app.config.active_storage.silence_invalid_content_types_warning || false
|
105
|
+
|
97
106
|
ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
|
98
107
|
ActiveStorage.track_variants = app.config.active_storage.track_variants || false
|
99
108
|
end
|
@@ -144,5 +153,25 @@ module ActiveStorage
|
|
144
153
|
ActiveRecord::Reflection.singleton_class.prepend(Reflection::ReflectionExtension)
|
145
154
|
end
|
146
155
|
end
|
156
|
+
|
157
|
+
initializer "active_storage.asset" do
|
158
|
+
if Rails.application.config.respond_to?(:assets)
|
159
|
+
Rails.application.config.assets.precompile += %w( activestorage activestorage.esm )
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
initializer "active_storage.fixture_set" do
|
164
|
+
ActiveSupport.on_load(:active_record_fixture_set) do
|
165
|
+
ActiveStorage::FixtureSet.file_fixture_path ||= Rails.root.join(*[
|
166
|
+
ENV.fetch("FIXTURES_PATH") { File.join("test", "fixtures") },
|
167
|
+
ENV["FIXTURES_DIR"],
|
168
|
+
"files"
|
169
|
+
].compact_blank)
|
170
|
+
end
|
171
|
+
|
172
|
+
ActiveSupport.on_load(:active_support_test_case) do
|
173
|
+
ActiveStorage::FixtureSet.file_fixture_path = ActiveSupport::TestCase.file_fixture_path
|
174
|
+
end
|
175
|
+
end
|
147
176
|
end
|
148
177
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/testing/file_fixtures"
|
4
|
+
require "active_record/secure_token"
|
5
|
+
|
6
|
+
module ActiveStorage
|
7
|
+
# Fixtures are a way of organizing data that you want to test against; in
|
8
|
+
# short, sample data.
|
9
|
+
#
|
10
|
+
# To learn more about fixtures, read the
|
11
|
+
# {ActiveRecord::FixtureSet}[rdoc-ref:ActiveRecord::FixtureSet] documentation.
|
12
|
+
#
|
13
|
+
# === YAML
|
14
|
+
#
|
15
|
+
# Like other Active Record-backed models,
|
16
|
+
# {ActiveStorage::Attachment}[rdoc-ref:ActiveStorage::Attachment] and
|
17
|
+
# {ActiveStorage::Blob}[rdoc-ref:ActiveStorage::Blob] records inherit from
|
18
|
+
# {ActiveRecord::Base}[rdoc-ref:ActiveRecord::Base] instances and therefore
|
19
|
+
# can be populated by fixtures.
|
20
|
+
#
|
21
|
+
# Consider a hypothetical <tt>Article</tt> model class, its related
|
22
|
+
# fixture data, as well as fixture data for related ActiveStorage::Attachment
|
23
|
+
# and ActiveStorage::Blob records:
|
24
|
+
#
|
25
|
+
# # app/models/article.rb
|
26
|
+
# class Article < ApplicationRecord
|
27
|
+
# has_one_attached :thumbnail
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # fixtures/active_storage/blobs.yml
|
31
|
+
# first_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob filename: "first.png" %>
|
32
|
+
#
|
33
|
+
# # fixtures/active_storage/attachments.yml
|
34
|
+
# first_thumbnail_attachment:
|
35
|
+
# name: thumbnail
|
36
|
+
# record: first (Article)
|
37
|
+
# blob: first_thumbnail_blob
|
38
|
+
#
|
39
|
+
# When processed, Active Record will insert database records for each fixture
|
40
|
+
# entry and will ensure the Active Storage relationship is intact.
|
41
|
+
class FixtureSet
|
42
|
+
include ActiveSupport::Testing::FileFixtures
|
43
|
+
include ActiveRecord::SecureToken
|
44
|
+
|
45
|
+
# Generate a YAML-encoded representation of an ActiveStorage::Blob
|
46
|
+
# instance's attributes, resolve the file relative to the directory mentioned
|
47
|
+
# by <tt>ActiveSupport::Testing::FileFixtures.file_fixture</tt>, and upload
|
48
|
+
# the file to the Service
|
49
|
+
#
|
50
|
+
# === Examples
|
51
|
+
#
|
52
|
+
# # tests/fixtures/action_text/blobs.yml
|
53
|
+
# second_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob(
|
54
|
+
# filename: "second.svg",
|
55
|
+
# ) %>
|
56
|
+
#
|
57
|
+
# third_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob(
|
58
|
+
# filename: "third.svg",
|
59
|
+
# content_type: "image/svg+xml",
|
60
|
+
# service_name: "public"
|
61
|
+
# ) %>
|
62
|
+
#
|
63
|
+
def self.blob(filename:, **attributes)
|
64
|
+
new.prepare Blob.new(filename: filename, key: generate_unique_secure_token), **attributes
|
65
|
+
end
|
66
|
+
|
67
|
+
def prepare(instance, **attributes)
|
68
|
+
io = file_fixture(instance.filename.to_s).open
|
69
|
+
instance.unfurl(io)
|
70
|
+
instance.assign_attributes(attributes)
|
71
|
+
instance.upload_without_unfurling(io)
|
72
|
+
|
73
|
+
instance.attributes.transform_values { |value| value.is_a?(Hash) ? value.to_json : value }.compact.to_json
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -26,7 +26,7 @@ module ActiveStorage
|
|
26
26
|
|
27
27
|
private
|
28
28
|
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
29
|
-
def download_blob_to_tempfile(&block)
|
29
|
+
def download_blob_to_tempfile(&block) # :doc:
|
30
30
|
blob.open tmpdir: tmpdir, &block
|
31
31
|
end
|
32
32
|
|
@@ -44,7 +44,7 @@ module ActiveStorage
|
|
44
44
|
# end
|
45
45
|
#
|
46
46
|
# The output tempfile is opened in the directory returned by #tmpdir.
|
47
|
-
def draw(*argv)
|
47
|
+
def draw(*argv) # :doc:
|
48
48
|
open_tempfile do |file|
|
49
49
|
instrument :preview, key: blob.key do
|
50
50
|
capture(*argv, to: file)
|
@@ -83,11 +83,11 @@ module ActiveStorage
|
|
83
83
|
to.rewind
|
84
84
|
end
|
85
85
|
|
86
|
-
def logger
|
86
|
+
def logger # :doc:
|
87
87
|
ActiveStorage.logger
|
88
88
|
end
|
89
89
|
|
90
|
-
def tmpdir
|
90
|
+
def tmpdir # :doc:
|
91
91
|
Dir.tmpdir
|
92
92
|
end
|
93
93
|
end
|
@@ -2,9 +2,19 @@
|
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
4
|
module Reflection
|
5
|
+
class HasAttachedReflection < ActiveRecord::Reflection::MacroReflection # :nodoc:
|
6
|
+
def variant(name, transformations)
|
7
|
+
variants[name] = transformations
|
8
|
+
end
|
9
|
+
|
10
|
+
def variants
|
11
|
+
@variants ||= {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
5
15
|
# Holds all the metadata about a has_one_attached attachment as it was
|
6
16
|
# specified in the Active Record class.
|
7
|
-
class HasOneAttachedReflection <
|
17
|
+
class HasOneAttachedReflection < HasAttachedReflection # :nodoc:
|
8
18
|
def macro
|
9
19
|
:has_one_attached
|
10
20
|
end
|
@@ -12,7 +22,7 @@ module ActiveStorage
|
|
12
22
|
|
13
23
|
# Holds all the metadata about a has_many_attached attachment as it was
|
14
24
|
# specified in the Active Record class.
|
15
|
-
class HasManyAttachedReflection <
|
25
|
+
class HasManyAttachedReflection < HasAttachedReflection # :nodoc:
|
16
26
|
def macro
|
17
27
|
:has_many_attached
|
18
28
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
gem "azure-storage-blob", ">=
|
3
|
+
gem "azure-storage-blob", ">= 2.0"
|
4
4
|
|
5
5
|
require "active_support/core_ext/numeric/bytes"
|
6
6
|
require "azure/storage/blob"
|
@@ -19,12 +19,12 @@ module ActiveStorage
|
|
19
19
|
@public = public
|
20
20
|
end
|
21
21
|
|
22
|
-
def upload(key, io, checksum: nil, filename: nil, content_type: nil, disposition: nil, **)
|
22
|
+
def upload(key, io, checksum: nil, filename: nil, content_type: nil, disposition: nil, custom_metadata: {}, **)
|
23
23
|
instrument :upload, key: key, checksum: checksum do
|
24
24
|
handle_errors do
|
25
25
|
content_disposition = content_disposition_with(filename: filename, type: disposition) if disposition && filename
|
26
26
|
|
27
|
-
client.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type, content_disposition: content_disposition)
|
27
|
+
client.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type, content_disposition: content_disposition, metadata: custom_metadata)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -86,7 +86,7 @@ module ActiveStorage
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
89
|
+
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, custom_metadata: {})
|
90
90
|
instrument :url, key: key do |payload|
|
91
91
|
generated_url = signer.signed_uri(
|
92
92
|
uri_for(key), false,
|
@@ -101,10 +101,28 @@ module ActiveStorage
|
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
104
|
-
def headers_for_direct_upload(key, content_type:, checksum:, filename: nil, disposition: nil, **)
|
104
|
+
def headers_for_direct_upload(key, content_type:, checksum:, filename: nil, disposition: nil, custom_metadata: {}, **)
|
105
105
|
content_disposition = content_disposition_with(type: disposition, filename: filename) if filename
|
106
106
|
|
107
|
-
{ "Content-Type" => content_type, "Content-MD5" => checksum, "x-ms-blob-content-disposition" => content_disposition, "x-ms-blob-type" => "BlockBlob" }
|
107
|
+
{ "Content-Type" => content_type, "Content-MD5" => checksum, "x-ms-blob-content-disposition" => content_disposition, "x-ms-blob-type" => "BlockBlob", **custom_metadata_headers(custom_metadata) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
|
111
|
+
content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
|
112
|
+
|
113
|
+
client.create_append_blob(
|
114
|
+
container,
|
115
|
+
destination_key,
|
116
|
+
content_type: content_type,
|
117
|
+
content_disposition: content_disposition,
|
118
|
+
metadata: custom_metadata,
|
119
|
+
).tap do |blob|
|
120
|
+
source_keys.each do |source_key|
|
121
|
+
stream(source_key) do |chunk|
|
122
|
+
client.append_blob_block(container, blob.name, chunk)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
108
126
|
end
|
109
127
|
|
110
128
|
private
|
@@ -166,5 +184,9 @@ module ActiveStorage
|
|
166
184
|
raise
|
167
185
|
end
|
168
186
|
end
|
187
|
+
|
188
|
+
def custom_metadata_headers(metadata)
|
189
|
+
metadata.transform_keys { |key| "x-ms-meta-#{key}" }
|
190
|
+
end
|
169
191
|
end
|
170
192
|
end
|
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
require "fileutils"
|
4
4
|
require "pathname"
|
5
|
-
require "
|
5
|
+
require "openssl"
|
6
6
|
require "active_support/core_ext/numeric/bytes"
|
7
7
|
|
8
8
|
module ActiveStorage
|
9
9
|
# Wraps a local disk path as an Active Storage service. See ActiveStorage::Service for the generic API
|
10
10
|
# documentation that applies to all services.
|
11
11
|
class Service::DiskService < Service
|
12
|
-
|
12
|
+
attr_accessor :root
|
13
13
|
|
14
14
|
def initialize(root:, public: false, **options)
|
15
15
|
@root = root
|
@@ -72,7 +72,7 @@ module ActiveStorage
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
75
|
+
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, custom_metadata: {})
|
76
76
|
instrument :url, key: key do |payload|
|
77
77
|
verified_token_with_expiration = ActiveStorage.verifier.generate(
|
78
78
|
{
|
@@ -86,11 +86,9 @@ module ActiveStorage
|
|
86
86
|
purpose: :blob_token
|
87
87
|
)
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
generated_url
|
89
|
+
url_helpers.update_rails_disk_service_url(verified_token_with_expiration, url_options).tap do |generated_url|
|
90
|
+
payload[:url] = generated_url
|
91
|
+
end
|
94
92
|
end
|
95
93
|
end
|
96
94
|
|
@@ -98,10 +96,20 @@ module ActiveStorage
|
|
98
96
|
{ "Content-Type" => content_type }
|
99
97
|
end
|
100
98
|
|
101
|
-
def path_for(key)
|
99
|
+
def path_for(key) # :nodoc:
|
102
100
|
File.join root, folder_for(key), key
|
103
101
|
end
|
104
102
|
|
103
|
+
def compose(source_keys, destination_key, **)
|
104
|
+
File.open(make_path_for(destination_key), "w") do |destination_file|
|
105
|
+
source_keys.each do |source_key|
|
106
|
+
File.open(path_for(source_key), "rb") do |source_file|
|
107
|
+
IO.copy_stream(source_file, destination_file)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
105
113
|
private
|
106
114
|
def private_url(key, expires_in:, filename:, content_type:, disposition:, **)
|
107
115
|
generate_url(key, expires_in: expires_in, filename: filename, content_type: content_type, disposition: disposition)
|
@@ -124,14 +132,11 @@ module ActiveStorage
|
|
124
132
|
purpose: :blob_key
|
125
133
|
)
|
126
134
|
|
127
|
-
|
135
|
+
if url_options.blank?
|
136
|
+
raise ArgumentError, "Cannot generate URL for #{filename} using Disk service, please set ActiveStorage::Current.url_options."
|
137
|
+
end
|
128
138
|
|
129
|
-
url_helpers.rails_disk_service_url(verified_key_with_expiration,
|
130
|
-
protocol: current_uri.scheme,
|
131
|
-
host: current_uri.host,
|
132
|
-
port: current_uri.port,
|
133
|
-
filename: filename
|
134
|
-
)
|
139
|
+
url_helpers.rails_disk_service_url(verified_key_with_expiration, filename: filename, **url_options)
|
135
140
|
end
|
136
141
|
|
137
142
|
|
@@ -154,7 +159,7 @@ module ActiveStorage
|
|
154
159
|
end
|
155
160
|
|
156
161
|
def ensure_integrity_of(key, checksum)
|
157
|
-
unless Digest::MD5.file(path_for(key)).base64digest == checksum
|
162
|
+
unless OpenSSL::Digest::MD5.file(path_for(key)).base64digest == checksum
|
158
163
|
delete key
|
159
164
|
raise ActiveStorage::IntegrityError
|
160
165
|
end
|
@@ -164,8 +169,8 @@ module ActiveStorage
|
|
164
169
|
@url_helpers ||= Rails.application.routes.url_helpers
|
165
170
|
end
|
166
171
|
|
167
|
-
def
|
168
|
-
ActiveStorage::Current.
|
172
|
+
def url_options
|
173
|
+
ActiveStorage::Current.url_options
|
169
174
|
end
|
170
175
|
end
|
171
176
|
end
|