Class: Shrine::Storage::S3
- Inherits:
-
Object
- Object
- Shrine::Storage::S3
- Includes:
- ClientSideEncryption
- Defined in:
- lib/shrine/storage/s3.rb
Defined Under Namespace
Modules: ClientSideEncryption
Constant Summary collapse
- MAX_MULTIPART_PARTS =
10_000- MIN_PART_SIZE =
5*1024*1024
- MULTIPART_THRESHOLD =
{ upload: 15*1024*1024, copy: 100*1024*1024 }
- COPY_OPTIONS =
{ tagging_directive: "REPLACE" }
Instance Attribute Summary collapse
-
#bucket ⇒ Object
readonly
Returns the value of attribute bucket.
-
#client ⇒ Object
readonly
Returns the value of attribute client.
-
#copy_options ⇒ Object
readonly
Returns the value of attribute copy_options.
-
#prefix ⇒ Object
readonly
Returns the value of attribute prefix.
-
#public ⇒ Object
readonly
Returns the value of attribute public.
-
#signer ⇒ Object
readonly
Returns the value of attribute signer.
-
#upload_options ⇒ Object
readonly
Returns the value of attribute upload_options.
Attributes included from ClientSideEncryption
Instance Method Summary collapse
-
#clear!(&block) ⇒ Object
If block is given, deletes all objects from the storage for which the block evaluates to true.
-
#delete(id) ⇒ Object
Deletes the file from the storage.
-
#delete_prefixed(delete_prefix) ⇒ Object
Deletes objects at keys starting with the specified prefix.
-
#exists?(id) ⇒ Boolean
Returns true file exists on S3.
-
#initialize(bucket:, client: nil, prefix: nil, upload_options: {}, multipart_threshold: {}, max_multipart_parts: nil, signer: nil, public: nil, copy_options: COPY_OPTIONS) ⇒ S3
constructor
Initializes a storage for uploading to S3.
-
#object(id) ⇒ Object
Returns an
Aws::S3::Objectfor the given id. -
#open(id, rewindable: true, encoding: nil) ⇒ Object
Returns a
Down::ChunkedIOobject that downloads S3 object content on-demand. -
#presign(id, method: :post, **presign_options) ⇒ Object
Returns URL, params, headers, and verb for direct uploads.
-
#upload(io, id, shrine_metadata: {}, **upload_options) ⇒ Object
If the file is an UploadedFile from S3, issues a COPY command, otherwise uploads the file.
-
#url(id, public: self.public, host: nil) ⇒ Object
Returns the presigned URL to the file.
Constructor Details
#initialize(bucket:, client: nil, prefix: nil, upload_options: {}, multipart_threshold: {}, max_multipart_parts: nil, signer: nil, public: nil, copy_options: COPY_OPTIONS) ⇒ S3
Initializes a storage for uploading to S3. All options are forwarded to
[Aws::S3::Client#initialize], except the following:
:bucket : (Required). Name of the S3 bucket.
:client
: By default an Aws::S3::Client instance is created internally from
additional options, but you can use this option to provide your own
client. This can be an Aws::S3::Client or an
Aws::S3::Encryption::Client object.
:prefix : "Directory" inside the bucket to store files into.
:upload_options
: Additional options that will be used for uploading files, they will
be passed to [Aws::S3::Object#put], [Aws::S3::Object#copy_from]
and [Aws::S3::Bucket#presigned_post].
:copy_options
: Additional options that will be used for copying files, they will
be passed to [Aws::S3::Object#copy_from].
:multipart_threshold
: If the input file is larger than the specified size, a parallelized
multipart will be used for the upload/copy. Defaults to
{upload: 15*1024*1024, copy: 100*1024*1024} (15MB for upload
requests, 100MB for copy requests).
:max_multipart_parts : Limits the number of parts if parellized multipart upload/copy is used. Defaults to 10_000.
In addition to specifying the :bucket, you'll also need to provide
AWS credentials. The most common way is to provide them directly via
:access_key_id, :secret_access_key, and :region options. But you
can also use any other way of authentication specified in the AWS SDK
documentation.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/shrine/storage/s3.rb', line 71 def initialize(bucket:, client: nil, prefix: nil, upload_options: {}, multipart_threshold: {}, max_multipart_parts: nil, signer: nil, public: nil, copy_options: COPY_OPTIONS, **) raise ArgumentError, "the :bucket option is nil" unless bucket @client = client || Aws::S3::Client.new(**) @transfer_manager = Aws::S3::TransferManager.new(client: @client) if defined?(Aws::S3::TransferManager) @bucket = Aws::S3::Bucket.new(name: bucket, client: @client) @prefix = prefix @upload_options = @copy_options = @multipart_threshold = MULTIPART_THRESHOLD.merge(multipart_threshold) @max_multipart_parts = max_multipart_parts || MAX_MULTIPART_PARTS @signer = signer @public = public end |
Instance Attribute Details
#bucket ⇒ Object (readonly)
Returns the value of attribute bucket.
17 18 19 |
# File 'lib/shrine/storage/s3.rb', line 17 def bucket @bucket end |
#client ⇒ Object (readonly)
Returns the value of attribute client.
17 18 19 |
# File 'lib/shrine/storage/s3.rb', line 17 def client @client end |
#copy_options ⇒ Object (readonly)
Returns the value of attribute copy_options.
17 18 19 |
# File 'lib/shrine/storage/s3.rb', line 17 def @copy_options end |
#prefix ⇒ Object (readonly)
Returns the value of attribute prefix.
17 18 19 |
# File 'lib/shrine/storage/s3.rb', line 17 def prefix @prefix end |
#public ⇒ Object (readonly)
Returns the value of attribute public.
17 18 19 |
# File 'lib/shrine/storage/s3.rb', line 17 def public @public end |
#signer ⇒ Object (readonly)
Returns the value of attribute signer.
17 18 19 |
# File 'lib/shrine/storage/s3.rb', line 17 def signer @signer end |
#upload_options ⇒ Object (readonly)
Returns the value of attribute upload_options.
17 18 19 |
# File 'lib/shrine/storage/s3.rb', line 17 def @upload_options end |
Instance Method Details
#clear!(&block) ⇒ Object
If block is given, deletes all objects from the storage for which the block evaluates to true. Otherwise deletes all objects from the storage.
s3.clear!
# or
s3.clear! { |object| object.last_modified < Time.now - 7*24*60*60 }
219 220 221 222 223 224 |
# File 'lib/shrine/storage/s3.rb', line 219 def clear!(&block) objects_to_delete = bucket.objects(prefix: prefix) objects_to_delete = objects_to_delete.lazy.select(&block) if block delete_objects(objects_to_delete) end |
#delete(id) ⇒ Object
Deletes the file from the storage.
199 200 201 |
# File 'lib/shrine/storage/s3.rb', line 199 def delete(id) object(id).delete end |
#delete_prefixed(delete_prefix) ⇒ Object
Deletes objects at keys starting with the specified prefix.
s3.delete_prefixed("somekey/derivatives/")
206 207 208 209 210 211 |
# File 'lib/shrine/storage/s3.rb', line 206 def delete_prefixed(delete_prefix) # We need to make sure to combine with storage prefix, and # that it ends in '/' cause S3 can be squirrely about matching interior. delete_prefix = delete_prefix.chomp("/") + "/" bucket.objects(prefix: [*prefix, delete_prefix].join("/")).batch_delete! end |
#exists?(id) ⇒ Boolean
Returns true file exists on S3.
128 129 130 |
# File 'lib/shrine/storage/s3.rb', line 128 def exists?(id) object(id).exists? end |
#object(id) ⇒ Object
Returns an Aws::S3::Object for the given id.
227 228 229 |
# File 'lib/shrine/storage/s3.rb', line 227 def object(id) bucket.object(object_key(id)) end |
#open(id, rewindable: true, encoding: nil) ⇒ Object
Returns a Down::ChunkedIO object that downloads S3 object content
on-demand. By default, read content will be cached onto disk so that
it can be rewinded, but if you don't need that you can pass
rewindable: false. A required character encoding can be passed in
encoding; the default is Encoding::BINARY via Down::ChunkedIO.
Any additional options are forwarded to [Aws::S3::Object#get].
119 120 121 122 123 124 125 |
# File 'lib/shrine/storage/s3.rb', line 119 def open(id, rewindable: true, encoding: nil, **) chunks, length = get(id, **) Down::ChunkedIO.new(chunks: chunks, rewindable: rewindable, size: length, encoding: encoding) rescue Aws::S3::Errors::NoSuchKey raise Shrine::FileNotFound, "file #{id.inspect} not found on storage" end |
#presign(id, method: :post, **presign_options) ⇒ Object
Returns URL, params, headers, and verb for direct uploads.
s3.presign("key") #=>
# {
# url: "https://my-bucket.s3.amazonaws.com/...",
# fields: { ... }, # blank for PUT presigns
# headers: { ... }, # blank for POST presigns
# method: "post",
# }
By default it calls [Aws::S3::Object#presigned_post] which generates
data for a POST request, but you can also specify method: :put for
PUT uploads which calls [Aws::S3::Object#presigned_url].
s3.presign("key", method: :post) # for POST upload (default)
s3.presign("key", method: :put) # for PUT upload
Any additional options are forwarded to the underlying AWS SDK method.
189 190 191 192 193 194 195 196 |
# File 'lib/shrine/storage/s3.rb', line 189 def presign(id, method: :post, **) = {} [:acl] = "public-read" if public .merge!(@upload_options, ) send(:"presign_#{method}", id, ) end |
#upload(io, id, shrine_metadata: {}, **upload_options) ⇒ Object
If the file is an UploadedFile from S3, issues a COPY command, otherwise
uploads the file. For files larger than :multipart_threshold a
multipart upload/copy will be used for better performance and more
resilient uploads.
It assigns the correct "Content-Type" taken from the MIME type, because by default S3 sets everything to "application/octet-stream".
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/shrine/storage/s3.rb', line 93 def upload(io, id, shrine_metadata: {}, **) content_type, filename = .values_at("mime_type", "filename") = {} [:content_type] = content_type if content_type [:content_disposition] = ContentDisposition.inline(filename) if filename [:acl] = "public-read" if public .merge!(@upload_options, ) if copyable?(io) copy(io, id, **) else put(io, id, **) end end |
#url(id, public: self.public, host: nil) ⇒ Object
Returns the presigned URL to the file.
:host
: This option replaces the host part of the returned URL, and is
typically useful for setting CDN hosts (e.g.
http://abc123.cloudfront.net)
:public : Returns the unsigned URL to the S3 object. This requires the S3 object to be public.
All other options are forwarded to [Aws::S3::Object#presigned_url] or
[Aws::S3::Object#public_url].
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/shrine/storage/s3.rb', line 148 def url(id, public: self.public, host: nil, **) if public || signer url = object(id).public_url(**) else url = object(id).presigned_url(:get, **) end if host uri = URI.parse(url) uri.path = uri.path.match(/^\/#{bucket.name}/).post_match unless uri.host.include?(bucket.name) url = URI.join(host, uri.request_uri[1..-1]).to_s end if signer url = signer.call(url, **) end url end |