shrine 2.6.1 → 2.7.0

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.
@@ -120,7 +120,7 @@ class Shrine
120
120
  end
121
121
 
122
122
  extend Forwardable
123
- delegate Shrine::IO_METHODS.keys => :@tempfile
123
+ delegate [:read, :size, :rewind, :eof?, :close] => :@tempfile
124
124
  end
125
125
  end
126
126
 
@@ -0,0 +1,85 @@
1
+ require "rack"
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # The `rack_response` plugin allows you to convert an `UploadedFile` object
6
+ # into a triple consisting of status, headers, and body, suitable for
7
+ # returning as a response in a Rack-based application.
8
+ #
9
+ # plugin :rack_response
10
+ #
11
+ # To convert a `Shrine::UploadedFile` into a Rack response, simply call
12
+ # `#to_rack_response`:
13
+ #
14
+ # status, headers, body = uploaded_file.to_rack_response
15
+ # status #=> 200
16
+ # headers #=> {"Content-Length" => "100", "Content-Type" => "text/plain", "Content-Disposition" => "inline; filename=\"file.txt\""}
17
+ # body # object that responds to #each and #close
18
+ #
19
+ # An example how this can be used in a Rails controller:
20
+ #
21
+ # class FilesController < ActionController::Base
22
+ # def download
23
+ # # ...
24
+ # file_response = record.attachment.to_rack_response
25
+ #
26
+ # response.status = file_response[0]
27
+ # response.headers.merge!(file_response[1])
28
+ # self.response_body = file_response[2]
29
+ # end
30
+ # end
31
+ #
32
+ # By default the "Content-Disposition" header will use the `inline`
33
+ # disposition, but you can change it to `attachment` if you don't want the
34
+ # file to be rendered inside the browser:
35
+ #
36
+ # status, headers, body = uploaded_file.to_rack_response(disposition: "attachment")
37
+ # headers["Content-Disposition"] #=> "attachment; filename=\"file.txt\""
38
+ module RackResponse
39
+ module FileMethods
40
+ # Returns a Rack response triple for the uploaded file.
41
+ def to_rack_response(disposition: "inline")
42
+ status = 200
43
+ headers = rack_headers(disposition: disposition)
44
+ body = rack_body
45
+
46
+ [status, headers, body]
47
+ end
48
+
49
+ private
50
+
51
+ # Returns a hash of "Content-Length", "Content-Type", and
52
+ # "Content-Disposition" headers, whose values are extracted from
53
+ # metadata.
54
+ def rack_headers(disposition:)
55
+ length = size || io.size
56
+ type = mime_type || Rack::Mime.mime_type(".#{extension}")
57
+ filename = original_filename || id.split("/").last
58
+
59
+ headers = {}
60
+ headers["Content-Length"] = length.to_s if length
61
+ headers["Content-Type"] = type
62
+ headers["Content-Disposition"] = "#{disposition}; filename=\"#{filename}\""
63
+
64
+ headers
65
+ end
66
+
67
+ # Returns an object that responds to #each and #close, which yields
68
+ # contents of the file.
69
+ def rack_body
70
+ chunks = Enumerator.new do |yielder|
71
+ if io.respond_to?(:each_chunk) # Down::ChunkedIO
72
+ io.each_chunk { |chunk| yielder << chunk }
73
+ else
74
+ yielder << io.read(16*1024) until io.eof?
75
+ end
76
+ end
77
+
78
+ Rack::BodyProxy.new(chunks) { io.close }
79
+ end
80
+ end
81
+ end
82
+
83
+ register_plugin(:rack_response, RackResponse)
84
+ end
85
+ end
@@ -1,3 +1,5 @@
1
+ require "down"
2
+
1
3
  class Shrine
2
4
  module Plugins
3
5
  # The `remote_url` plugin allows you to attach files from a remote location.
@@ -42,15 +44,12 @@ class Shrine
42
44
  #
43
45
  # If you want to customize how the file is downloaded, you can override the
44
46
  # `:downloader` parameter and provide your own implementation. For example,
45
- # you can ensure the URL is URI-encoded (using the [Addressable] gem) before
46
- # passing it to Down:
47
+ # you can use the HTTP.rb Down backend for downloading:
47
48
  #
48
- # require "down"
49
- # require "addressable/uri"
49
+ # require "down/http"
50
50
  #
51
51
  # plugin :remote_url, max_size: 20*1024*1024, downloader: ->(url, max_size:) do
52
- # url = Addressable::URI.encode(Addressable::URI.decode(url))
53
- # Down.download(url, max_size: max_size, max_redirects: 4, read_timeout: 3)
52
+ # Down::Http.download(url, max_size: max_size, follow: { max_hops: 4 }, timeout: { read: 3 })
54
53
  # end
55
54
  #
56
55
  # ## Errors
@@ -138,7 +137,6 @@ class Shrine
138
137
  # We silence any download errors, because for the user's point of view
139
138
  # the download simply failed.
140
139
  def download_with_open_uri(url, max_size:)
141
- require "down"
142
140
  Down.download(url, max_size: max_size)
143
141
  end
144
142
 
@@ -74,7 +74,7 @@ class Shrine
74
74
  def validate
75
75
  super
76
76
  #{@name}_attacher.errors.each do |message|
77
- errors.add(:#{@name}, message)
77
+ errors.add(:#{@name}, *message)
78
78
  end
79
79
  end
80
80
  RUBY
@@ -133,7 +133,7 @@ class Shrine
133
133
 
134
134
  def encode_base64(hash)
135
135
  require "base64"
136
- Base64.encode64(hash)
136
+ Base64.strict_encode64(hash)
137
137
  end
138
138
  end
139
139
 
@@ -0,0 +1,238 @@
1
+ require "rack"
2
+
3
+ require "json"
4
+
5
+ class Shrine
6
+ module Plugins
7
+ # The `upload_endpoint` plugin provides a Rack endpoint which accepts file
8
+ # uploads and forwards them to specified storage. It can be used with
9
+ # client-side file upload libraries like [FineUploader], [Dropzone] or
10
+ # [jQuery-File-Upload] for asynchronous uploads.
11
+ #
12
+ # plugin :upload_endpoint
13
+ #
14
+ # The plugin adds a `Shrine.upload_endpoint` method which accepts a storage
15
+ # identifier and returns a Rack application that accepts multipart POST
16
+ # requests, and uploads received files to the specified storage.
17
+ #
18
+ # Shrine.upload_endpoint(:cache) # rack app
19
+ #
20
+ # Asynchronous upload is typically meant to replace the caching phase in
21
+ # the default synchronous workflow, so we want the uploads to go to
22
+ # temporary (`:cache`) storage.
23
+ #
24
+ # When we want to mount the Rack application to our app, it's recommended
25
+ # to generate endpoints for specific uploaders. This is because different
26
+ # uploaders may have different uploading logic, and this also allows
27
+ # customizing the upload endpoint per uploader.
28
+ #
29
+ # Rails.application.routes.draw do
30
+ # mount ImageUploader.upload_endpoint(:cache) => "/images/upload"
31
+ # end
32
+ #
33
+ # The above will create a `POST /images/upload` endpoint, which uploads the
34
+ # file received in the `file` param using `ImageUploader`, and returns a
35
+ # JSON representation of the uploaded file.
36
+ #
37
+ # # POST /images/upload
38
+ # {
39
+ # "id": "43kewit94.jpg",
40
+ # "storage": "cache",
41
+ # "metadata": {
42
+ # "size": 384393,
43
+ # "filename": "nature.jpg",
44
+ # "mime_type": "image/jpeg"
45
+ # }
46
+ # }
47
+ #
48
+ # This JSON string can now be assigned to an attachment attribute instead
49
+ # of a raw file. In a form it can be written to a hidden attachment field,
50
+ # and then it can be assigned as the attachment.
51
+ #
52
+ # ## Limiting filesize
53
+ #
54
+ # It's good practice to limit the accepted filesize of uploaded files. You
55
+ # can do that with the `:max_size` option:
56
+ #
57
+ # plugin :upload_endpoint, max_size: 20*1024*1024 # 20 MB
58
+ #
59
+ # If the uploaded file is larger than the specified value, a `413 Payload
60
+ # Too Large` response will be returned.
61
+ #
62
+ # ## Context
63
+ #
64
+ # The upload context will *not* contain `:record` and `:name` values, as
65
+ # the upload happens independently of a database record. The endpoint will
66
+ # sent the following upload context:
67
+ #
68
+ # * `:action` - holds the value `:upload`
69
+ # * `:request` - holds an instance of `Rack::Request`
70
+ #
71
+ # You can update the upload context via `:upload_context`:
72
+ #
73
+ # plugin :upload_endpoint, upload_context: -> (request) do
74
+ # { location: "my-location" }
75
+ # end
76
+ #
77
+ # ## Upload
78
+ #
79
+ # You can also customize the upload itself via the `:upload` option:
80
+ #
81
+ # plugin :upload_endpoint, upload: -> (io, context, request) do
82
+ # # perform uploading and return the Shrine::UploadedFile
83
+ # end
84
+ #
85
+ # ## Response
86
+ #
87
+ # The response returned by the endpoint can be customized via the
88
+ # `:rack_response` option:
89
+ #
90
+ # plugin :upload_endpoint, rack_response: -> (uploaded_file, request) do
91
+ # body = { data: uploaded_file.data, url: uploaded_file.url }.to_json
92
+ # [201, { "Content-Type" => "application/json" }, [body]]
93
+ # end
94
+ #
95
+ # ## Ad-hoc options
96
+ #
97
+ # You can override any of the options above when creating the endpoint:
98
+ #
99
+ # Shrine.upload_endpoint(:cache, max_size: 20*1024*1024)
100
+ #
101
+ # [FineUploader]: https://github.com/FineUploader/fine-uploader
102
+ # [Dropzone]: https://github.com/enyo/dropzone
103
+ # [jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
104
+ module UploadEndpoint
105
+ def self.load_dependencies(uploader, opts = {})
106
+ uploader.plugin :rack_file
107
+ end
108
+
109
+ def self.configure(uploader, opts = {})
110
+ uploader.opts[:upload_endpoint_max_size] = opts.fetch(:max_size, uploader.opts[:upload_endpoint_max_size])
111
+ uploader.opts[:upload_endpoint_upload_context] = opts.fetch(:upload_context, uploader.opts[:upload_endpoint_upload_context])
112
+ uploader.opts[:upload_endpoint_upload] = opts.fetch(:upload, uploader.opts[:upload_endpoint_upload])
113
+ uploader.opts[:upload_endpoint_rack_response] = opts.fetch(:rack_response, uploader.opts[:upload_endpoint_rack_response])
114
+ end
115
+
116
+ module ClassMethods
117
+ # Returns a Rack application (object that responds to `#call`) which
118
+ # accepts multipart POST requests to the root URL, uploads given file
119
+ # to the specified storage, and returns that information in JSON format.
120
+ #
121
+ # The `storage_key` needs to be one of the registered Shrine storages.
122
+ # Additional options can be given to override the options given on
123
+ # plugin initialization.
124
+ def upload_endpoint(storage_key, **options)
125
+ App.new({
126
+ shrine_class: self,
127
+ storage_key: storage_key,
128
+ max_size: opts[:upload_endpoint_max_size],
129
+ upload_context: opts[:upload_endpoint_upload_context],
130
+ upload: opts[:upload_endpoint_upload],
131
+ rack_response: opts[:upload_endpoint_rack_response],
132
+ }.merge(options))
133
+ end
134
+ end
135
+
136
+ # Rack application that accepts multipart POSt request to the root URL,
137
+ # calls `#upload` with the uploaded file, and returns the uploaded file
138
+ # information in JSON format.
139
+ class App
140
+ # Writes given options to instance variables.
141
+ def initialize(options)
142
+ options.each do |name, value|
143
+ instance_variable_set("@#{name}", value)
144
+ end
145
+ end
146
+
147
+ # Accepts a Rack env hash, routes POST requests to the root URL, and
148
+ # returns a Rack response triple.
149
+ #
150
+ # If request isn't to the root URL, a `404 Not Found` response is
151
+ # returned. If request verb isn't GET, a `405 Method Not Allowed`
152
+ # response is returned.
153
+ def call(env)
154
+ request = Rack::Request.new(env)
155
+
156
+ status, headers, body = catch(:halt) do
157
+ error!(404, "Not Found") unless ["", "/"].include?(request.path_info)
158
+ error!(405, "Method Not Allowed") unless request.post?
159
+
160
+ handle_request(request)
161
+ end
162
+
163
+ headers["Content-Length"] = body.map(&:bytesize).inject(0, :+).to_s
164
+
165
+ [status, headers, body]
166
+ end
167
+
168
+ private
169
+
170
+ # Accepts a `Rack::Request` object, uploads the file, and returns a Rack
171
+ # response.
172
+ def handle_request(request)
173
+ io = get_io(request)
174
+ context = get_context(request)
175
+
176
+ uploaded_file = upload(io, context, request)
177
+
178
+ make_response(uploaded_file, request)
179
+ end
180
+
181
+ # Retrieves the "file" multipart request parameter, and returns an
182
+ # IO-like object that can be passed to `Shrine#upload`.
183
+ def get_io(request)
184
+ file = request.params["file"]
185
+
186
+ error!(400, "Upload Not Found") unless file.is_a?(Hash) && file[:tempfile]
187
+ error!(413, "Upload Too Large") if @max_size && file[:tempfile].size > @max_size
188
+
189
+ @shrine_class.rack_file(file)
190
+ end
191
+
192
+ # Returns a hash of information containing `:action` and `:request`
193
+ # keys, which is to be passed to `Shrine#upload`. Calls
194
+ # `:upload_context` option if given.
195
+ def get_context(request)
196
+ context = { action: :upload, phase: :upload, request: request }
197
+ context.merge! @upload_context.call(request) if @upload_context
198
+ context
199
+ end
200
+
201
+ # Calls `Shrine#upload` with the given IO and context, and returns a
202
+ # `Shrine::UploadedFile` object. If `:upload` option is given, calls
203
+ # that instead.
204
+ def upload(io, context, request)
205
+ if @upload
206
+ @upload.call(io, context, request)
207
+ else
208
+ uploader.upload(io, context)
209
+ end
210
+ end
211
+
212
+ # Transforms the uploaded file object into a JSON response. It returns
213
+ # a Rack response triple - an array consisting of a status number, hash
214
+ # of headers, and a body enumerable. If a `:rack_response` option is
215
+ # given, calls that instead.
216
+ def make_response(object, request)
217
+ if @rack_response
218
+ @rack_response.call(object, request)
219
+ else
220
+ [200, {"Content-Type" => "application/json"}, [object.to_json]]
221
+ end
222
+ end
223
+
224
+ # Used for early returning an error response.
225
+ def error!(status, message)
226
+ throw :halt, [status, {"Content-Type" => "text/plain"}, [message]]
227
+ end
228
+
229
+ # Returns the uploader around the specified storage.
230
+ def uploader
231
+ @shrine_class.new(@storage_key)
232
+ end
233
+ end
234
+ end
235
+
236
+ register_plugin(:upload_endpoint, UploadEndpoint)
237
+ end
238
+ end
@@ -202,8 +202,9 @@ class Shrine
202
202
  def method_missing(name, *args)
203
203
  if name == :download
204
204
  Shrine.deprecation("Shrine::Storage::FileSystem#download is deprecated and will be removed in Shrine 3.")
205
- require "down"
206
- open(*args) { |file| Down.copy_to_tempfile(*args, file) }
205
+ tempfile = Tempfile.new(["shrine-filesystem", File.extname(args[0])], binmode: true)
206
+ open(*args) { |file| IO.copy_stream(file, tempfile) }
207
+ tempfile.tap(&:open)
207
208
  else
208
209
  super
209
210
  end
@@ -1,16 +1,22 @@
1
- require "aws-sdk"
2
- require "down"
1
+ begin
2
+ require "aws-sdk-s3"
3
+ if Gem::Version.new(Aws::S3::GEM_VERSION) < Gem::Version.new("1.2.0")
4
+ raise "Shrine::Storage::S3 requires aws-sdk-s3 version 1.2.0 or above"
5
+ end
6
+ rescue LoadError
7
+ require "aws-sdk"
8
+ Aws.eager_autoload!(services: ["S3"])
9
+ end
10
+ require "down/chunked_io"
3
11
  require "uri"
4
- require "cgi/util"
5
-
6
- Aws.eager_autoload!(services: ["S3"])
12
+ require "cgi"
7
13
 
8
14
  class Shrine
9
15
  module Storage
10
- # The S3 storage handles uploads to Amazon S3 service, using the [aws-sdk]
11
- # gem:
16
+ # The S3 storage handles uploads to Amazon S3 service, using the
17
+ # [aws-sdk-s3] gem:
12
18
  #
13
- # gem "aws-sdk", "~> 2.1"
19
+ # gem "aws-sdk-s3", "~> 1.2"
14
20
  #
15
21
  # It is initialized with the following 4 required options:
16
22
  #
@@ -49,7 +55,7 @@ class Shrine
49
55
  #
50
56
  # Shrine::Storage::S3.new(upload_options: {acl: "private"}, **s3_options)
51
57
  #
52
- # These options will be passed to aws-sdk's methods for [uploading],
58
+ # These options will be passed to aws-sdk-s3's methods for [uploading],
53
59
  # [copying] and [presigning].
54
60
  #
55
61
  # You can also generate upload options per upload with the `upload_options`
@@ -81,7 +87,7 @@ class Shrine
81
87
  # uploaded_file.url(public: true) # public URL without signed parameters
82
88
  # uploaded_file.url(download: true) # forced download URL
83
89
  #
84
- # All other options are forwarded to the [aws-sdk] gem:
90
+ # All other options are forwarded to the aws-sdk-s3 gem:
85
91
  #
86
92
  # uploaded_file.url(expires_in: 15)
87
93
  # uploaded_file.url(virtual_host: true)
@@ -106,16 +112,16 @@ class Shrine
106
112
  # ## Presigns
107
113
  #
108
114
  # This storage can generate presigns for direct uploads to Amazon S3, and
109
- # it accepts additional options which are passed to [aws-sdk]. There are
115
+ # it accepts additional options which are passed to aws-sdk-s3. There are
110
116
  # three places in which you can specify presign options:
111
117
  #
112
118
  # * in `:upload_options` option on this storage
113
- # * in `direct_upload` plugin through `:presign_options`
119
+ # * in `presign_endpoint` plugin through `:presign_options`
114
120
  # * in `Storage::S3#presign` by forwarding options
115
121
  #
116
122
  # ## Large files
117
123
  #
118
- # The [aws-sdk] gem has the ability to automatically use multipart
124
+ # The aws-sdk-s3 gem has the ability to automatically use multipart
119
125
  # upload/copy for larger files, splitting the file into multiple chunks
120
126
  # and uploading/copying them in parallel.
121
127
  #
@@ -127,7 +133,7 @@ class Shrine
127
133
  # thresholds = {upload: 30*1024*1024, copy: 200*1024*1024}
128
134
  # Shrine::Storage::S3.new(multipart_threshold: thresholds, **s3_options)
129
135
  #
130
- # If you want to change how many threads [aws-sdk] will use for multipart
136
+ # If you want to change how many threads aws-sdk-s3 will use for multipart
131
137
  # upload/copy, you can use the `upload_options` plugin to specify
132
138
  # `:thread_count`.
133
139
  #
@@ -139,14 +145,14 @@ class Shrine
139
145
  #
140
146
  # If you're using S3 as a cache, you will probably want to periodically
141
147
  # delete old files which aren't used anymore. S3 has a built-in way to do
142
- # this, read [this article](http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html)
143
- # for instructions.
148
+ # this, read [this article][object lifecycle] for instructions.
144
149
  #
145
- # [uploading]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#put-instance_method
146
- # [copying]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#copy_from-instance_method
147
- # [presigning]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#presigned_post-instance_method
148
- # [aws-sdk]: https://github.com/aws/aws-sdk-ruby
150
+ # [uploading]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#put-instance_method
151
+ # [copying]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#copy_from-instance_method
152
+ # [presigning]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
153
+ # [aws-sdk-s3]: https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-s3
149
154
  # [Transfer Acceleration]: http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
155
+ # [object lifecycle]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#put-instance_method
150
156
  class S3
151
157
  attr_reader :client, :bucket, :prefix, :host, :upload_options
152
158
 
@@ -156,7 +162,7 @@ class Shrine
156
162
  # :secret_access_key
157
163
  # :region
158
164
  # :bucket
159
- # : Credentials required by the `aws-sdk` gem.
165
+ # : Credentials required by the `aws-sdk-s3` gem.
160
166
  #
161
167
  # :prefix
162
168
  # : "Folder" name inside the bucket to store files into.
@@ -174,22 +180,21 @@ class Shrine
174
180
  #
175
181
  # All other options are forwarded to [`Aws::S3::Client#initialize`].
176
182
  #
177
- # [`Aws::S3::Object#put`]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#put-instance_method
178
- # [`Aws::S3::Object#copy_from`]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#copy_from-instance_method
179
- # [`Aws::S3::Bucket#presigned_post`]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#presigned_post-instance_method
180
- # [`Aws::S3::Client#initialize`]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#initialize-instance_method
183
+ # [`Aws::S3::Object#put`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#put-instance_method
184
+ # [`Aws::S3::Object#copy_from`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#copy_from-instance_method
185
+ # [`Aws::S3::Bucket#presigned_post`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
186
+ # [`Aws::S3::Client#initialize`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#initialize-instance_method
181
187
  def initialize(bucket:, prefix: nil, host: nil, upload_options: {}, multipart_threshold: {}, **s3_options)
182
188
  Shrine.deprecation("The :host option to Shrine::Storage::S3#initialize is deprecated and will be removed in Shrine 3. Pass :host to S3#url instead, you can also use default_url_options plugin.") if host
183
189
  resource = Aws::S3::Resource.new(**s3_options)
184
190
 
185
191
  if multipart_threshold.is_a?(Integer)
186
192
  Shrine.deprecation("Accepting the :multipart_threshold S3 option as an integer is deprecated, use a hash with :upload and :copy keys instead, e.g. {upload: 15*1024*1024, copy: 150*1024*1024}")
187
- multipart_threshold = {upload: multipart_threshold}
193
+ multipart_threshold = { upload: multipart_threshold }
188
194
  end
189
- multipart_threshold[:upload] ||= 15*1024*1024
190
- multipart_threshold[:copy] ||= 100*1024*1024
195
+ multipart_threshold = { upload: 15*1024*1024, copy: 100*1024*1024 }.merge(multipart_threshold)
191
196
 
192
- @bucket = resource.bucket(bucket)
197
+ @bucket = resource.bucket(bucket) or fail(ArgumentError, "the :bucket option was nil")
193
198
  @client = resource.client
194
199
  @prefix = prefix
195
200
  @host = host
@@ -240,7 +245,10 @@ class Shrine
240
245
 
241
246
  # Returns a `Down::ChunkedIO` object representing the S3 object.
242
247
  def open(id)
243
- Down.open(url(id), ssl_ca_cert: Aws.config[:ssl_ca_bundle])
248
+ object = object(id)
249
+ io = Down::ChunkedIO.new(chunks: object.enum_for(:get), data: {object: object})
250
+ io.size = object.content_length
251
+ io
244
252
  end
245
253
 
246
254
  # Returns true file exists on S3.
@@ -248,20 +256,6 @@ class Shrine
248
256
  object(id).exists?
249
257
  end
250
258
 
251
- # Deletes the file from S3.
252
- def delete(id)
253
- object(id).delete
254
- end
255
-
256
- # This is called when multiple files are being deleted at once. Issues a
257
- # single MULTI DELETE command for each 1000 objects (S3 delete limit).
258
- def multi_delete(ids)
259
- ids.each_slice(1000) do |ids_batch|
260
- delete_params = {objects: ids_batch.map { |id| {key: object(id).key} }}
261
- bucket.delete_objects(delete: delete_params)
262
- end
263
- end
264
-
265
259
  # Returns the presigned URL to the file.
266
260
  #
267
261
  # :public
@@ -282,8 +276,8 @@ class Shrine
282
276
  # All other options are forwarded to [`Aws::S3::Object#presigned_url`] or
283
277
  # [`Aws::S3::Object#public_url`].
284
278
  #
285
- # [`Aws::S3::Object#presigned_url`]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#presigned_url-instance_method
286
- # [`Aws::S3::Object#public_url`]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#public_url-instance_method
279
+ # [`Aws::S3::Object#presigned_url`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
280
+ # [`Aws::S3::Object#public_url`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#public_url-instance_method
287
281
  def url(id, download: nil, public: nil, host: self.host, **options)
288
282
  options[:response_content_disposition] ||= "attachment" if download
289
283
  options[:response_content_disposition] = encode_content_disposition(options[:response_content_disposition]) if options[:response_content_disposition]
@@ -303,24 +297,38 @@ class Shrine
303
297
  url
304
298
  end
305
299
 
306
- # Deletes all files from the storage.
307
- def clear!
308
- objects = bucket.object_versions(prefix: prefix)
309
- objects.respond_to?(:batch_delete!) ? objects.batch_delete! : objects.delete
310
- end
311
-
312
300
  # Returns a signature for direct uploads. Internally it calls
313
301
  # [`Aws::S3::Bucket#presigned_post`], and forwards any additional options
314
302
  # to it.
315
303
  #
316
- # [`Aws::S3::Bucket#presigned_post`]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Bucket.html#presigned_post-instance_method
304
+ # [`Aws::S3::Bucket#presigned_post`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#presigned_post-instance_method
317
305
  def presign(id, **options)
318
- options = upload_options.merge(options)
306
+ options = @upload_options.merge(options)
319
307
  options[:content_disposition] = encode_content_disposition(options[:content_disposition]) if options[:content_disposition]
320
308
 
321
309
  object(id).presigned_post(options)
322
310
  end
323
311
 
312
+ # Deletes the file from S3.
313
+ def delete(id)
314
+ object(id).delete
315
+ end
316
+
317
+ # This is called when multiple files are being deleted at once. Issues a
318
+ # single MULTI DELETE command for each 1000 objects (S3 delete limit).
319
+ def multi_delete(ids)
320
+ ids.each_slice(1000) do |ids_batch|
321
+ delete_params = {objects: ids_batch.map { |id| {key: object(id).key} }}
322
+ bucket.delete_objects(delete: delete_params)
323
+ end
324
+ end
325
+
326
+ # Deletes all files from the storage.
327
+ def clear!
328
+ objects = bucket.object_versions(prefix: prefix)
329
+ objects.respond_to?(:batch_delete!) ? objects.batch_delete! : objects.delete
330
+ end
331
+
324
332
  # Returns an `Aws::S3::Object` for the given id.
325
333
  def object(id)
326
334
  bucket.object([*prefix, id].join("/"))