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.

Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +180 -69
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +43 -8
  5. data/app/assets/javascripts/activestorage.js +5 -2
  6. data/app/controllers/active_storage/base_controller.rb +13 -4
  7. data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
  8. data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +3 -3
  9. data/app/controllers/active_storage/direct_uploads_controller.rb +2 -2
  10. data/app/controllers/active_storage/disk_controller.rb +13 -22
  11. data/app/controllers/active_storage/representations/proxy_controller.rb +19 -0
  12. data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +3 -3
  13. data/app/controllers/concerns/active_storage/file_server.rb +18 -0
  14. data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
  15. data/app/controllers/concerns/active_storage/set_current.rb +15 -0
  16. data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
  17. data/app/javascript/activestorage/blob_record.js +7 -2
  18. data/app/jobs/active_storage/analyze_job.rb +5 -0
  19. data/app/jobs/active_storage/base_job.rb +0 -1
  20. data/app/jobs/active_storage/mirror_job.rb +15 -0
  21. data/app/jobs/active_storage/purge_job.rb +3 -0
  22. data/app/models/active_storage/attachment.rb +35 -16
  23. data/app/models/active_storage/blob.rb +178 -68
  24. data/app/models/active_storage/blob/analyzable.rb +6 -2
  25. data/app/models/active_storage/blob/identifiable.rb +7 -6
  26. data/app/models/active_storage/blob/representable.rb +36 -6
  27. data/app/models/active_storage/filename.rb +0 -6
  28. data/app/models/active_storage/preview.rb +37 -12
  29. data/app/models/active_storage/record.rb +7 -0
  30. data/app/models/active_storage/variant.rb +53 -67
  31. data/app/models/active_storage/variant_record.rb +8 -0
  32. data/app/models/active_storage/variant_with_record.rb +54 -0
  33. data/app/models/active_storage/variation.rb +30 -34
  34. data/config/routes.rb +66 -15
  35. data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
  36. data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
  37. data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
  38. data/lib/active_storage.rb +29 -6
  39. data/lib/active_storage/analyzer.rb +15 -4
  40. data/lib/active_storage/analyzer/image_analyzer.rb +14 -4
  41. data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
  42. data/lib/active_storage/analyzer/video_analyzer.rb +17 -8
  43. data/lib/active_storage/attached.rb +7 -22
  44. data/lib/active_storage/attached/changes.rb +16 -0
  45. data/lib/active_storage/attached/changes/create_many.rb +47 -0
  46. data/lib/active_storage/attached/changes/create_one.rb +82 -0
  47. data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
  48. data/lib/active_storage/attached/changes/delete_many.rb +27 -0
  49. data/lib/active_storage/attached/changes/delete_one.rb +19 -0
  50. data/lib/active_storage/attached/many.rb +19 -12
  51. data/lib/active_storage/attached/model.rb +212 -0
  52. data/lib/active_storage/attached/one.rb +19 -21
  53. data/lib/active_storage/downloader.rb +43 -0
  54. data/lib/active_storage/engine.rb +58 -23
  55. data/lib/active_storage/errors.rb +22 -3
  56. data/lib/active_storage/gem_version.rb +4 -4
  57. data/lib/active_storage/log_subscriber.rb +6 -0
  58. data/lib/active_storage/previewer.rb +24 -13
  59. data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
  60. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +5 -5
  61. data/lib/active_storage/previewer/video_previewer.rb +17 -10
  62. data/lib/active_storage/reflection.rb +64 -0
  63. data/lib/active_storage/service.rb +44 -12
  64. data/lib/active_storage/service/azure_storage_service.rb +65 -44
  65. data/lib/active_storage/service/configurator.rb +6 -2
  66. data/lib/active_storage/service/disk_service.rb +57 -44
  67. data/lib/active_storage/service/gcs_service.rb +68 -64
  68. data/lib/active_storage/service/mirror_service.rb +31 -7
  69. data/lib/active_storage/service/registry.rb +32 -0
  70. data/lib/active_storage/service/s3_service.rb +58 -24
  71. data/lib/active_storage/transformers/image_processing_transformer.rb +45 -0
  72. data/lib/active_storage/transformers/transformer.rb +39 -0
  73. data/lib/tasks/activestorage.rake +7 -0
  74. metadata +84 -19
  75. data/app/models/active_storage/filename/parameters.rb +0 -36
  76. data/lib/active_storage/attached/macros.rb +0 -110
  77. 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
- def self.accept?(blob)
6
- blob.video?
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}.png", content_type: "image/png"
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", "-vcodec", "png",
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 a signed, temporary URL for the file at the +key+. The URL will be valid for the amount
103
- # of seconds specified in +expires_in+. You most also provide the +disposition+ (+:inline+ or +:attachment+),
104
- # +filename+, and +content_type+ that you wish the file to be served with on request.
105
- def url(key, expires_in:, disposition:, filename:, content_type:)
106
- raise NotImplementedError
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") + "; #{filename.parameters}"
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, :blobs, :container, :signer
13
+ attr_reader :client, :container, :signer
12
14
 
13
- def initialize(storage_account_name:, storage_access_key:, container:)
14
- @client = Azure::Storage::Client.create(storage_account_name: storage_account_name, storage_access_key: storage_access_key)
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
- begin
23
- blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum)
24
- rescue Azure::Core::Http::HTTPError
25
- raise ActiveStorage::IntegrityError
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
- _, io = blobs.get_blob(container, key)
38
- io.force_encoding(Encoding::BINARY)
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
- _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
46
- io.force_encoding(Encoding::BINARY)
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
- begin
53
- blobs.delete_blob(container, key)
54
- rescue Azure::Core::Http::HTTPError
55
- # Ignore files already deleted
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 = blobs.list_blobs(container, prefix: prefix, marker: marker)
70
+ results = client.list_blobs(container, prefix: prefix, marker: marker)
66
71
 
67
72
  results.each do |blob|
68
- blobs.delete_blob(container, blob.name)
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 url(key, expires_in:, filename:, disposition:, content_type:)
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: "r",
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 url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
102
- instrument :url, key: key do |payload|
103
- generated_url = signer.signed_uri(
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: "rw",
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
- payload[:url] = generated_url
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
- blobs.generate_uri("#{container}/#{key}")
128
+ client.generate_uri("#{container}/#{key}")
123
129
  end
124
130
 
125
131
  def blob_for(key)
126
- blobs.get_blob_properties(container, key)
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 = blobs.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1)
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(**config, configurator: self)
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
- File.open(path_for(key), "rb") do |file|
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
- begin
53
- File.delete path_for(key)
54
- rescue Errno::ENOENT
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
- { expires_in: expires_in,
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