activestorage 5.2.4.4 → 6.1.1
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 +180 -69
- data/MIT-LICENSE +1 -1
- data/README.md +43 -8
- data/app/assets/javascripts/activestorage.js +5 -2
- data/app/controllers/active_storage/base_controller.rb +13 -4
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
- data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +3 -3
- data/app/controllers/active_storage/direct_uploads_controller.rb +2 -2
- data/app/controllers/active_storage/disk_controller.rb +13 -22
- data/app/controllers/active_storage/representations/proxy_controller.rb +19 -0
- data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +3 -3
- 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 +15 -0
- data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
- data/app/javascript/activestorage/blob_record.js +7 -2
- data/app/jobs/active_storage/analyze_job.rb +5 -0
- data/app/jobs/active_storage/base_job.rb +0 -1
- data/app/jobs/active_storage/mirror_job.rb +15 -0
- data/app/jobs/active_storage/purge_job.rb +3 -0
- data/app/models/active_storage/attachment.rb +35 -16
- data/app/models/active_storage/blob.rb +178 -68
- 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 +36 -6
- data/app/models/active_storage/filename.rb +0 -6
- data/app/models/active_storage/preview.rb +37 -12
- data/app/models/active_storage/record.rb +7 -0
- data/app/models/active_storage/variant.rb +53 -67
- 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 +30 -34
- data/config/routes.rb +66 -15
- 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 +29 -6
- data/lib/active_storage/analyzer.rb +15 -4
- data/lib/active_storage/analyzer/image_analyzer.rb +14 -4
- data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +17 -8
- data/lib/active_storage/attached.rb +7 -22
- data/lib/active_storage/attached/changes.rb +16 -0
- data/lib/active_storage/attached/changes/create_many.rb +47 -0
- data/lib/active_storage/attached/changes/create_one.rb +82 -0
- data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
- data/lib/active_storage/attached/changes/delete_many.rb +27 -0
- data/lib/active_storage/attached/changes/delete_one.rb +19 -0
- data/lib/active_storage/attached/many.rb +19 -12
- data/lib/active_storage/attached/model.rb +212 -0
- data/lib/active_storage/attached/one.rb +19 -21
- data/lib/active_storage/downloader.rb +43 -0
- data/lib/active_storage/engine.rb +58 -23
- data/lib/active_storage/errors.rb +22 -3
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/log_subscriber.rb +6 -0
- data/lib/active_storage/previewer.rb +24 -13
- data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +5 -5
- data/lib/active_storage/previewer/video_previewer.rb +17 -10
- data/lib/active_storage/reflection.rb +64 -0
- data/lib/active_storage/service.rb +44 -12
- data/lib/active_storage/service/azure_storage_service.rb +65 -44
- data/lib/active_storage/service/configurator.rb +6 -2
- data/lib/active_storage/service/disk_service.rb +57 -44
- data/lib/active_storage/service/gcs_service.rb +68 -64
- 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 +58 -24
- data/lib/active_storage/transformers/image_processing_transformer.rb +45 -0
- data/lib/active_storage/transformers/transformer.rb +39 -0
- data/lib/tasks/activestorage.rake +7 -0
- metadata +84 -19
- data/app/models/active_storage/filename/parameters.rb +0 -36
- data/lib/active_storage/attached/macros.rb +0 -110
- data/lib/active_storage/downloading.rb +0 -39
@@ -2,26 +2,33 @@
|
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
4
|
class Previewer::VideoPreviewer < Previewer
|
5
|
-
|
6
|
-
blob
|
5
|
+
class << self
|
6
|
+
def accept?(blob)
|
7
|
+
blob.video? && ffmpeg_exists?
|
8
|
+
end
|
9
|
+
|
10
|
+
def ffmpeg_exists?
|
11
|
+
return @ffmpeg_exists if defined?(@ffmpeg_exists)
|
12
|
+
|
13
|
+
@ffmpeg_exists = system(ffmpeg_path, "-version", out: File::NULL, err: File::NULL)
|
14
|
+
end
|
15
|
+
|
16
|
+
def ffmpeg_path
|
17
|
+
ActiveStorage.paths[:ffmpeg] || "ffmpeg"
|
18
|
+
end
|
7
19
|
end
|
8
20
|
|
9
|
-
def preview
|
21
|
+
def preview(**options)
|
10
22
|
download_blob_to_tempfile do |input|
|
11
23
|
draw_relevant_frame_from input do |output|
|
12
|
-
yield io: output, filename: "#{blob.filename.base}.
|
24
|
+
yield io: output, filename: "#{blob.filename.base}.jpg", content_type: "image/jpeg", **options
|
13
25
|
end
|
14
26
|
end
|
15
27
|
end
|
16
28
|
|
17
29
|
private
|
18
30
|
def draw_relevant_frame_from(file, &block)
|
19
|
-
draw ffmpeg_path, "-i", file.path, "-y", "-
|
20
|
-
"-vf", "thumbnail", "-vframes", "1", "-f", "image2", "-", &block
|
21
|
-
end
|
22
|
-
|
23
|
-
def ffmpeg_path
|
24
|
-
ActiveStorage.paths[:ffmpeg] || "ffmpeg"
|
31
|
+
draw self.class.ffmpeg_path, "-i", file.path, "-y", "-vframes", "1", "-f", "image2", "-", &block
|
25
32
|
end
|
26
33
|
end
|
27
34
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
module Reflection
|
5
|
+
# Holds all the metadata about a has_one_attached attachment as it was
|
6
|
+
# specified in the Active Record class.
|
7
|
+
class HasOneAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
|
8
|
+
def macro
|
9
|
+
:has_one_attached
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Holds all the metadata about a has_many_attached attachment as it was
|
14
|
+
# specified in the Active Record class.
|
15
|
+
class HasManyAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
|
16
|
+
def macro
|
17
|
+
:has_many_attached
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ReflectionExtension # :nodoc:
|
22
|
+
def add_attachment_reflection(model, name, reflection)
|
23
|
+
model.attachment_reflections = model.attachment_reflections.merge(name.to_s => reflection)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def reflection_class_for(macro)
|
28
|
+
case macro
|
29
|
+
when :has_one_attached
|
30
|
+
HasOneAttachedReflection
|
31
|
+
when :has_many_attached
|
32
|
+
HasManyAttachedReflection
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module ActiveRecordExtensions
|
40
|
+
extend ActiveSupport::Concern
|
41
|
+
|
42
|
+
included do
|
43
|
+
class_attribute :attachment_reflections, instance_writer: false, default: {}
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
# Returns an array of reflection objects for all the attachments in the
|
48
|
+
# class.
|
49
|
+
def reflect_on_all_attachments
|
50
|
+
attachment_reflections.values
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the reflection object for the named +attachment+.
|
54
|
+
#
|
55
|
+
# User.reflect_on_attachment(:avatar)
|
56
|
+
# # => the avatar reflection
|
57
|
+
#
|
58
|
+
def reflect_on_attachment(attachment)
|
59
|
+
attachment_reflections[attachment.to_s]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_storage/log_subscriber"
|
4
|
+
require "active_storage/downloader"
|
5
|
+
require "action_dispatch"
|
6
|
+
require "action_dispatch/http/content_disposition"
|
4
7
|
|
5
8
|
module ActiveStorage
|
6
|
-
class IntegrityError < StandardError; end
|
7
|
-
|
8
9
|
# Abstract class serving as an interface for concrete services.
|
9
10
|
#
|
10
11
|
# The available services are:
|
@@ -40,8 +41,7 @@ module ActiveStorage
|
|
40
41
|
class Service
|
41
42
|
extend ActiveSupport::Autoload
|
42
43
|
autoload :Configurator
|
43
|
-
|
44
|
-
class_attribute :url_expires_in, default: 5.minutes
|
44
|
+
attr_accessor :name
|
45
45
|
|
46
46
|
class << self
|
47
47
|
# Configure an Active Storage service by name from a set of configurations,
|
@@ -57,8 +57,10 @@ module ActiveStorage
|
|
57
57
|
# Passes the configurator and all of the service's config as keyword args.
|
58
58
|
#
|
59
59
|
# See MirrorService for an example.
|
60
|
-
def build(configurator:, service: nil, **service_config) #:nodoc:
|
61
|
-
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
|
62
64
|
end
|
63
65
|
end
|
64
66
|
|
@@ -84,6 +86,10 @@ module ActiveStorage
|
|
84
86
|
raise NotImplementedError
|
85
87
|
end
|
86
88
|
|
89
|
+
def open(*args, **options, &block)
|
90
|
+
ActiveStorage::Downloader.new(self).open(*args, **options, &block)
|
91
|
+
end
|
92
|
+
|
87
93
|
# Delete the file at the +key+.
|
88
94
|
def delete(key)
|
89
95
|
raise NotImplementedError
|
@@ -99,11 +105,23 @@ module ActiveStorage
|
|
99
105
|
raise NotImplementedError
|
100
106
|
end
|
101
107
|
|
102
|
-
# Returns
|
103
|
-
#
|
104
|
-
# +filename+, and +content_type+ that you wish the file to be served with on request.
|
105
|
-
|
106
|
-
|
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
|
107
125
|
end
|
108
126
|
|
109
127
|
# Returns a signed, temporary URL that a direct upload file can be PUT to on the +key+.
|
@@ -119,7 +137,20 @@ module ActiveStorage
|
|
119
137
|
{}
|
120
138
|
end
|
121
139
|
|
140
|
+
def public?
|
141
|
+
@public
|
142
|
+
end
|
143
|
+
|
122
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
|
+
|
123
154
|
def instrument(operation, payload = {}, &block)
|
124
155
|
ActiveSupport::Notifications.instrument(
|
125
156
|
"service_#{operation}.active_storage",
|
@@ -132,7 +163,8 @@ module ActiveStorage
|
|
132
163
|
end
|
133
164
|
|
134
165
|
def content_disposition_with(type: "inline", filename:)
|
135
|
-
(type.to_s.presence_in(%w( attachment inline )) || "inline")
|
166
|
+
disposition = (type.to_s.presence_in(%w( attachment inline )) || "inline")
|
167
|
+
ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename.sanitized)
|
136
168
|
end
|
137
169
|
end
|
138
170
|
end
|
@@ -1,28 +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:)
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
handle_errors do
|
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)
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -34,26 +36,29 @@ module ActiveStorage
|
|
34
36
|
end
|
35
37
|
else
|
36
38
|
instrument :download, key: key do
|
37
|
-
|
38
|
-
|
39
|
+
handle_errors do
|
40
|
+
_, io = client.get_blob(container, key)
|
41
|
+
io.force_encoding(Encoding::BINARY)
|
42
|
+
end
|
39
43
|
end
|
40
44
|
end
|
41
45
|
end
|
42
46
|
|
43
47
|
def download_chunk(key, range)
|
44
48
|
instrument :download_chunk, key: key, range: range do
|
45
|
-
|
46
|
-
|
49
|
+
handle_errors do
|
50
|
+
_, io = client.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
|
51
|
+
io.force_encoding(Encoding::BINARY)
|
52
|
+
end
|
47
53
|
end
|
48
54
|
end
|
49
55
|
|
50
56
|
def delete(key)
|
51
57
|
instrument :delete, key: key do
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
58
|
+
client.delete_blob(container, key)
|
59
|
+
rescue Azure::Core::Http::HTTPError => e
|
60
|
+
raise unless e.type == "BlobNotFound"
|
61
|
+
# Ignore files already deleted
|
57
62
|
end
|
58
63
|
end
|
59
64
|
|
@@ -62,10 +67,10 @@ module ActiveStorage
|
|
62
67
|
marker = nil
|
63
68
|
|
64
69
|
loop do
|
65
|
-
results =
|
70
|
+
results = client.list_blobs(container, prefix: prefix, marker: marker)
|
66
71
|
|
67
72
|
results.each do |blob|
|
68
|
-
|
73
|
+
client.delete_blob(container, blob.name)
|
69
74
|
end
|
70
75
|
|
71
76
|
break unless marker = results.continuation_token.presence
|
@@ -81,15 +86,13 @@ module ActiveStorage
|
|
81
86
|
end
|
82
87
|
end
|
83
88
|
|
84
|
-
def
|
89
|
+
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
85
90
|
instrument :url, key: key do |payload|
|
86
91
|
generated_url = signer.signed_uri(
|
87
92
|
uri_for(key), false,
|
88
93
|
service: "b",
|
89
|
-
permissions: "
|
90
|
-
expiry: format_expiry(expires_in)
|
91
|
-
content_disposition: content_disposition_with(type: disposition, filename: filename),
|
92
|
-
content_type: content_type
|
94
|
+
permissions: "rw",
|
95
|
+
expiry: format_expiry(expires_in)
|
93
96
|
).to_s
|
94
97
|
|
95
98
|
payload[:url] = generated_url
|
@@ -98,32 +101,35 @@ module ActiveStorage
|
|
98
101
|
end
|
99
102
|
end
|
100
103
|
|
101
|
-
def
|
102
|
-
|
103
|
-
|
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(
|
104
113
|
uri_for(key), false,
|
105
114
|
service: "b",
|
106
|
-
permissions: "
|
107
|
-
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
|
108
119
|
).to_s
|
120
|
+
end
|
109
121
|
|
110
|
-
|
111
|
-
|
112
|
-
generated_url
|
122
|
+
def public_url(key, **)
|
123
|
+
uri_for(key).to_s
|
113
124
|
end
|
114
|
-
end
|
115
125
|
|
116
|
-
def headers_for_direct_upload(key, content_type:, checksum:, **)
|
117
|
-
{ "Content-Type" => content_type, "Content-MD5" => checksum, "x-ms-blob-type" => "BlockBlob" }
|
118
|
-
end
|
119
126
|
|
120
|
-
private
|
121
127
|
def uri_for(key)
|
122
|
-
|
128
|
+
client.generate_uri("#{container}/#{key}")
|
123
129
|
end
|
124
130
|
|
125
131
|
def blob_for(key)
|
126
|
-
|
132
|
+
client.get_blob_properties(container, key)
|
127
133
|
rescue Azure::Core::Http::HTTPError
|
128
134
|
false
|
129
135
|
end
|
@@ -139,11 +145,26 @@ module ActiveStorage
|
|
139
145
|
chunk_size = 5.megabytes
|
140
146
|
offset = 0
|
141
147
|
|
148
|
+
raise ActiveStorage::FileNotFoundError unless blob.present?
|
149
|
+
|
142
150
|
while offset < blob.properties[:content_length]
|
143
|
-
_, chunk =
|
151
|
+
_, chunk = client.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1)
|
144
152
|
yield chunk.force_encoding(Encoding::BINARY)
|
145
153
|
offset += chunk_size
|
146
154
|
end
|
147
155
|
end
|
156
|
+
|
157
|
+
def handle_errors
|
158
|
+
yield
|
159
|
+
rescue Azure::Core::Http::HTTPError => e
|
160
|
+
case e.type
|
161
|
+
when "BlobNotFound"
|
162
|
+
raise ActiveStorage::FileNotFoundError
|
163
|
+
when "Md5Mismatch"
|
164
|
+
raise ActiveStorage::IntegrityError
|
165
|
+
else
|
166
|
+
raise
|
167
|
+
end
|
168
|
+
end
|
148
169
|
end
|
149
170
|
end
|
@@ -14,7 +14,9 @@ module ActiveStorage
|
|
14
14
|
|
15
15
|
def build(service_name)
|
16
16
|
config = config_for(service_name.to_sym)
|
17
|
-
resolve(config.fetch(:service)).build(
|
17
|
+
resolve(config.fetch(:service)).build(
|
18
|
+
**config, configurator: self, name: service_name
|
19
|
+
)
|
18
20
|
end
|
19
21
|
|
20
22
|
private
|
@@ -26,7 +28,9 @@ module ActiveStorage
|
|
26
28
|
|
27
29
|
def resolve(class_name)
|
28
30
|
require "active_storage/service/#{class_name.to_s.underscore}_service"
|
29
|
-
ActiveStorage::Service.const_get(:"#{class_name}Service")
|
31
|
+
ActiveStorage::Service.const_get(:"#{class_name.camelize}Service")
|
32
|
+
rescue LoadError
|
33
|
+
raise "Missing service adapter for #{class_name.inspect}"
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -11,8 +11,9 @@ module ActiveStorage
|
|
11
11
|
class Service::DiskService < Service
|
12
12
|
attr_reader :root
|
13
13
|
|
14
|
-
def initialize(root:)
|
14
|
+
def initialize(root:, public: false, **options)
|
15
15
|
@root = root
|
16
|
+
@public = public
|
16
17
|
end
|
17
18
|
|
18
19
|
def upload(key, io, checksum: nil, **)
|
@@ -22,18 +23,16 @@ module ActiveStorage
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
def download(key)
|
26
|
+
def download(key, &block)
|
26
27
|
if block_given?
|
27
28
|
instrument :streaming_download, key: key do
|
28
|
-
|
29
|
-
while data = file.read(5.megabytes)
|
30
|
-
yield data
|
31
|
-
end
|
32
|
-
end
|
29
|
+
stream key, &block
|
33
30
|
end
|
34
31
|
else
|
35
32
|
instrument :download, key: key do
|
36
33
|
File.binread path_for(key)
|
34
|
+
rescue Errno::ENOENT
|
35
|
+
raise ActiveStorage::FileNotFoundError
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
@@ -44,16 +43,16 @@ module ActiveStorage
|
|
44
43
|
file.seek range.begin
|
45
44
|
file.read range.size
|
46
45
|
end
|
46
|
+
rescue Errno::ENOENT
|
47
|
+
raise ActiveStorage::FileNotFoundError
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
51
|
def delete(key)
|
51
52
|
instrument :delete, key: key do
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
# Ignore files already deleted
|
56
|
-
end
|
53
|
+
File.delete path_for(key)
|
54
|
+
rescue Errno::ENOENT
|
55
|
+
# Ignore files already deleted
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
@@ -73,35 +72,6 @@ module ActiveStorage
|
|
73
72
|
end
|
74
73
|
end
|
75
74
|
|
76
|
-
def url(key, expires_in:, filename:, disposition:, content_type:)
|
77
|
-
instrument :url, key: key do |payload|
|
78
|
-
content_disposition = content_disposition_with(type: disposition, filename: filename)
|
79
|
-
verified_key_with_expiration = ActiveStorage.verifier.generate(
|
80
|
-
{
|
81
|
-
key: key,
|
82
|
-
disposition: content_disposition,
|
83
|
-
content_type: content_type
|
84
|
-
},
|
85
|
-
{ expires_in: expires_in,
|
86
|
-
purpose: :blob_key }
|
87
|
-
)
|
88
|
-
|
89
|
-
current_uri = URI.parse(current_host)
|
90
|
-
|
91
|
-
generated_url = url_helpers.rails_disk_service_url(verified_key_with_expiration,
|
92
|
-
protocol: current_uri.scheme,
|
93
|
-
host: current_uri.host,
|
94
|
-
port: current_uri.port,
|
95
|
-
disposition: content_disposition,
|
96
|
-
content_type: content_type,
|
97
|
-
filename: filename
|
98
|
-
)
|
99
|
-
payload[:url] = generated_url
|
100
|
-
|
101
|
-
generated_url
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
75
|
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
106
76
|
instrument :url, key: key do |payload|
|
107
77
|
verified_token_with_expiration = ActiveStorage.verifier.generate(
|
@@ -109,10 +79,11 @@ module ActiveStorage
|
|
109
79
|
key: key,
|
110
80
|
content_type: content_type,
|
111
81
|
content_length: content_length,
|
112
|
-
checksum: checksum
|
82
|
+
checksum: checksum,
|
83
|
+
service_name: name
|
113
84
|
},
|
114
|
-
|
115
|
-
purpose: :blob_token
|
85
|
+
expires_in: expires_in,
|
86
|
+
purpose: :blob_token
|
116
87
|
)
|
117
88
|
|
118
89
|
generated_url = url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: current_host)
|
@@ -132,6 +103,48 @@ module ActiveStorage
|
|
132
103
|
end
|
133
104
|
|
134
105
|
private
|
106
|
+
def private_url(key, expires_in:, filename:, content_type:, disposition:, **)
|
107
|
+
generate_url(key, expires_in: expires_in, filename: filename, content_type: content_type, disposition: disposition)
|
108
|
+
end
|
109
|
+
|
110
|
+
def public_url(key, filename:, content_type: nil, disposition: :attachment, **)
|
111
|
+
generate_url(key, expires_in: nil, filename: filename, content_type: content_type, disposition: disposition)
|
112
|
+
end
|
113
|
+
|
114
|
+
def generate_url(key, expires_in:, filename:, content_type:, disposition:)
|
115
|
+
content_disposition = content_disposition_with(type: disposition, filename: filename)
|
116
|
+
verified_key_with_expiration = ActiveStorage.verifier.generate(
|
117
|
+
{
|
118
|
+
key: key,
|
119
|
+
disposition: content_disposition,
|
120
|
+
content_type: content_type,
|
121
|
+
service_name: name
|
122
|
+
},
|
123
|
+
expires_in: expires_in,
|
124
|
+
purpose: :blob_key
|
125
|
+
)
|
126
|
+
|
127
|
+
current_uri = URI.parse(current_host)
|
128
|
+
|
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
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def stream(key)
|
139
|
+
File.open(path_for(key), "rb") do |file|
|
140
|
+
while data = file.read(5.megabytes)
|
141
|
+
yield data
|
142
|
+
end
|
143
|
+
end
|
144
|
+
rescue Errno::ENOENT
|
145
|
+
raise ActiveStorage::FileNotFoundError
|
146
|
+
end
|
147
|
+
|
135
148
|
def folder_for(key)
|
136
149
|
[ key[0..1], key[2..3] ].join("/")
|
137
150
|
end
|