shrine 3.6.0 → 3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/doc/changing_derivatives.md +2 -1
- data/doc/changing_location.md +17 -5
- data/doc/getting_started.md +4 -2
- data/doc/plugins/derivation_endpoint.md +2 -1
- data/doc/plugins/derivatives.md +2 -1
- data/doc/plugins/download_endpoint.md +16 -4
- data/doc/plugins/refresh_metadata.md +20 -0
- data/doc/plugins/signature.md +8 -6
- data/doc/processing.md +5 -3
- data/doc/release_notes/3.7.0.md +75 -0
- data/lib/shrine/attacher.rb +21 -21
- data/lib/shrine/attachment.rb +2 -2
- data/lib/shrine/plugins/_urlsafe_serialization.rb +4 -4
- data/lib/shrine/plugins/add_metadata.rb +2 -4
- data/lib/shrine/plugins/atomic_helpers.rb +7 -7
- data/lib/shrine/plugins/backgrounding.rb +9 -9
- data/lib/shrine/plugins/column.rb +6 -4
- data/lib/shrine/plugins/default_url.rb +4 -4
- data/lib/shrine/plugins/delete_raw.rb +2 -2
- data/lib/shrine/plugins/derivation_endpoint.rb +25 -25
- data/lib/shrine/plugins/derivatives.rb +5 -1
- data/lib/shrine/plugins/download_endpoint.rb +62 -10
- data/lib/shrine/plugins/entity.rb +7 -7
- data/lib/shrine/plugins/infer_extension.rb +1 -1
- data/lib/shrine/plugins/instrumentation.rb +8 -8
- data/lib/shrine/plugins/mirroring.rb +10 -10
- data/lib/shrine/plugins/model.rb +9 -9
- data/lib/shrine/plugins/presign_endpoint.rb +4 -4
- data/lib/shrine/plugins/pretty_location.rb +2 -2
- data/lib/shrine/plugins/processing.rb +3 -3
- data/lib/shrine/plugins/rack_file.rb +2 -2
- data/lib/shrine/plugins/rack_response.rb +4 -4
- data/lib/shrine/plugins/refresh_metadata.rb +6 -6
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/restore_cached_data.rb +3 -3
- data/lib/shrine/plugins/signature.rb +2 -2
- data/lib/shrine/plugins/store_dimensions.rb +2 -2
- data/lib/shrine/plugins/upload_endpoint.rb +4 -4
- data/lib/shrine/plugins/upload_options.rb +1 -1
- data/lib/shrine/plugins/validation.rb +8 -8
- data/lib/shrine/plugins/versions.rb +10 -10
- data/lib/shrine/plugins.rb +6 -14
- data/lib/shrine/storage/file_system.rb +4 -17
- data/lib/shrine/storage/linter.rb +8 -8
- data/lib/shrine/storage/memory.rb +1 -3
- data/lib/shrine/storage/s3.rb +45 -37
- data/lib/shrine/uploaded_file.rb +20 -18
- data/lib/shrine/version.rb +1 -1
- data/lib/shrine.rb +18 -18
- data/shrine.gemspec +6 -6
- metadata +15 -17
|
@@ -11,14 +11,14 @@ class Shrine
|
|
|
11
11
|
module AttacherMethods
|
|
12
12
|
private
|
|
13
13
|
|
|
14
|
-
def cached(value, **
|
|
14
|
+
def cached(value, **)
|
|
15
15
|
cached_file = super
|
|
16
16
|
|
|
17
17
|
# TODO: Remove this conditional when we remove the versions plugin
|
|
18
18
|
if cached_file.is_a?(Hash) || cached_file.is_a?(Array)
|
|
19
|
-
uploaded_file(cached_file) { |file| file.refresh_metadata!(**context, **
|
|
19
|
+
uploaded_file(cached_file) { |file| file.refresh_metadata!(**context, **) }
|
|
20
20
|
else
|
|
21
|
-
cached_file.refresh_metadata!(**context, **
|
|
21
|
+
cached_file.refresh_metadata!(**context, **)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
cached_file
|
|
@@ -37,7 +37,7 @@ class Shrine
|
|
|
37
37
|
def instrument_signature(io, algorithm, format, &block)
|
|
38
38
|
return yield unless respond_to?(:instrument)
|
|
39
39
|
|
|
40
|
-
instrument(:signature, io
|
|
40
|
+
instrument(:signature, io:, algorithm:, format:, &block)
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -45,7 +45,7 @@ class Shrine
|
|
|
45
45
|
# Calculates `algorithm` hash of the contents of the IO object, and
|
|
46
46
|
# encodes it into `format`.
|
|
47
47
|
def calculate_signature(io, algorithm, format: :hex)
|
|
48
|
-
self.class.calculate_signature(io, algorithm, format:
|
|
48
|
+
self.class.calculate_signature(io, algorithm, format:)
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -57,7 +57,7 @@ class Shrine
|
|
|
57
57
|
def dimensions_analyzer(name)
|
|
58
58
|
on_error = opts[:store_dimensions][:on_error]
|
|
59
59
|
|
|
60
|
-
DimensionsAnalyzer.new(name, on_error:
|
|
60
|
+
DimensionsAnalyzer.new(name, on_error:).method(:call)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
private
|
|
@@ -66,7 +66,7 @@ class Shrine
|
|
|
66
66
|
def instrument_dimensions(io, &block)
|
|
67
67
|
return yield unless respond_to?(:instrument)
|
|
68
68
|
|
|
69
|
-
instrument(:image_dimensions, io
|
|
69
|
+
instrument(:image_dimensions, io:, &block)
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
|
@@ -26,12 +26,12 @@ class Shrine
|
|
|
26
26
|
# The `storage_key` needs to be one of the registered Shrine storages.
|
|
27
27
|
# Additional options can be given to override the options given on
|
|
28
28
|
# plugin initialization.
|
|
29
|
-
def upload_endpoint(storage_key, **
|
|
29
|
+
def upload_endpoint(storage_key, **)
|
|
30
30
|
Shrine::UploadEndpoint.new(
|
|
31
31
|
shrine_class: self,
|
|
32
32
|
storage_key: storage_key,
|
|
33
33
|
**opts[:upload_endpoint],
|
|
34
|
-
|
|
34
|
+
**,
|
|
35
35
|
)
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -41,7 +41,7 @@ class Shrine
|
|
|
41
41
|
# It performs the same mounting logic that Rack and other web
|
|
42
42
|
# frameworks use, and is meant for cases where statically mounting the
|
|
43
43
|
# endpoint in the router isn't enough.
|
|
44
|
-
def upload_response(storage_key, env, **
|
|
44
|
+
def upload_response(storage_key, env, **)
|
|
45
45
|
script_name = env["SCRIPT_NAME"]
|
|
46
46
|
path_info = env["PATH_INFO"]
|
|
47
47
|
|
|
@@ -49,7 +49,7 @@ class Shrine
|
|
|
49
49
|
env["SCRIPT_NAME"] += path_info
|
|
50
50
|
env["PATH_INFO"] = ""
|
|
51
51
|
|
|
52
|
-
upload_endpoint(storage_key, **
|
|
52
|
+
upload_endpoint(storage_key, **).call(env)
|
|
53
53
|
ensure
|
|
54
54
|
env["SCRIPT_NAME"] = script_name
|
|
55
55
|
env["PATH_INFO"] = path_info
|
|
@@ -24,21 +24,21 @@ class Shrine
|
|
|
24
24
|
attr_reader :errors
|
|
25
25
|
|
|
26
26
|
# Initializes validation errors to an empty array.
|
|
27
|
-
def initialize(**
|
|
27
|
+
def initialize(**)
|
|
28
28
|
super
|
|
29
29
|
@errors = []
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Performs validations after attaching cached file.
|
|
33
|
-
def attach_cached(value, validate: nil, **
|
|
34
|
-
result = super(value, validate: false, **
|
|
33
|
+
def attach_cached(value, validate: nil, **)
|
|
34
|
+
result = super(value, validate: false, **)
|
|
35
35
|
validation(validate)
|
|
36
36
|
result
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# Performs validations after attaching file.
|
|
40
|
-
def attach(io, validate: nil, **
|
|
41
|
-
result = super(io, **
|
|
40
|
+
def attach(io, validate: nil, **)
|
|
41
|
+
result = super(io, **)
|
|
42
42
|
validation(validate)
|
|
43
43
|
result
|
|
44
44
|
end
|
|
@@ -61,16 +61,16 @@ class Shrine
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Calls #validate_block, passing it accepted parameters.
|
|
64
|
-
def _validate(**
|
|
64
|
+
def _validate(**)
|
|
65
65
|
if method(:validate_block).arity.zero?
|
|
66
66
|
validate_block
|
|
67
67
|
else
|
|
68
|
-
validate_block(**
|
|
68
|
+
validate_block(**)
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
# Overridden by the `Attacher.validate` block.
|
|
73
|
-
def validate_block(**
|
|
73
|
+
def validate_block(**)
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -59,16 +59,16 @@ class Shrine
|
|
|
59
59
|
|
|
60
60
|
# Smart versioned URLs, which include the version name in the default
|
|
61
61
|
# URL, and properly forwards any options to the underlying storage.
|
|
62
|
-
def url(version = nil, **
|
|
62
|
+
def url(version = nil, **)
|
|
63
63
|
if file.is_a?(Hash)
|
|
64
64
|
if version
|
|
65
65
|
version = version.to_sym
|
|
66
66
|
if file.key?(version)
|
|
67
|
-
file[version].url(**
|
|
67
|
+
file[version].url(**)
|
|
68
68
|
elsif fallback = shrine_class.version_fallbacks[version]
|
|
69
|
-
url(fallback, **
|
|
69
|
+
url(fallback, **)
|
|
70
70
|
else
|
|
71
|
-
default_url(
|
|
71
|
+
default_url(**, version:)
|
|
72
72
|
end
|
|
73
73
|
else
|
|
74
74
|
raise Error, "must call Shrine::Attacher#url with the name of the version"
|
|
@@ -76,12 +76,12 @@ class Shrine
|
|
|
76
76
|
else
|
|
77
77
|
if version
|
|
78
78
|
if file && shrine_class.opts[:versions][:fallback_to_original]
|
|
79
|
-
file.url(**
|
|
79
|
+
file.url(**)
|
|
80
80
|
else
|
|
81
|
-
default_url(
|
|
81
|
+
default_url(**, version:)
|
|
82
82
|
end
|
|
83
83
|
else
|
|
84
|
-
super(**
|
|
84
|
+
super(**)
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
end
|
|
@@ -130,7 +130,7 @@ class Shrine
|
|
|
130
130
|
|
|
131
131
|
def map_file(object, transform_keys: :to_sym)
|
|
132
132
|
if object.is_a?(Hash) || object.is_a?(Array)
|
|
133
|
-
deep_map(object, transform_keys:
|
|
133
|
+
deep_map(object, transform_keys:) do |path, value|
|
|
134
134
|
yield path, value unless value.is_a?(Hash) || value.is_a?(Array)
|
|
135
135
|
end
|
|
136
136
|
elsif object
|
|
@@ -150,7 +150,7 @@ class Shrine
|
|
|
150
150
|
key = key.send(transform_keys)
|
|
151
151
|
result = yield [*path, key], value
|
|
152
152
|
|
|
153
|
-
hash.merge! key => (result || deep_map(value, [*path, key], transform_keys
|
|
153
|
+
hash.merge! key => (result || deep_map(value, [*path, key], transform_keys:, &block))
|
|
154
154
|
end
|
|
155
155
|
elsif object.is_a?(Array)
|
|
156
156
|
result = yield path, object
|
|
@@ -160,7 +160,7 @@ class Shrine
|
|
|
160
160
|
object.map.with_index do |value, idx|
|
|
161
161
|
result = yield [*path, idx], value
|
|
162
162
|
|
|
163
|
-
result || deep_map(value, [*path, idx], transform_keys
|
|
163
|
+
result || deep_map(value, [*path, idx], transform_keys:, &block)
|
|
164
164
|
end
|
|
165
165
|
else
|
|
166
166
|
result = yield path, object
|
data/lib/shrine/plugins.rb
CHANGED
|
@@ -18,26 +18,18 @@ class Shrine
|
|
|
18
18
|
plugin
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
# Delegate
|
|
22
|
-
def self.load_dependencies(plugin, uploader,
|
|
21
|
+
# Delegate to the plugin's `load_dependencies` method.
|
|
22
|
+
def self.load_dependencies(plugin, uploader, ...)
|
|
23
23
|
return unless plugin.respond_to?(:load_dependencies)
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
plugin.load_dependencies(uploader, *args, **kwargs, &block)
|
|
27
|
-
else
|
|
28
|
-
plugin.load_dependencies(uploader, *args, &block)
|
|
29
|
-
end
|
|
25
|
+
plugin.load_dependencies(uploader, ...)
|
|
30
26
|
end
|
|
31
27
|
|
|
32
|
-
# Delegate
|
|
33
|
-
def self.configure(plugin, uploader,
|
|
28
|
+
# Delegate to the plugin's `load_dependencies` method.
|
|
29
|
+
def self.configure(plugin, uploader, ...)
|
|
34
30
|
return unless plugin.respond_to?(:configure)
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
plugin.configure(uploader, *args, **kwargs, &block)
|
|
38
|
-
else
|
|
39
|
-
plugin.configure(uploader, *args, &block)
|
|
40
|
-
end
|
|
32
|
+
plugin.configure(uploader, ...)
|
|
41
33
|
end
|
|
42
34
|
|
|
43
35
|
# Register the given plugin with Shrine, so that it can be loaded using
|
|
@@ -59,8 +59,8 @@ class Shrine
|
|
|
59
59
|
|
|
60
60
|
# Opens the file on the given location in read mode. Accepts additional
|
|
61
61
|
# `File.open` arguments.
|
|
62
|
-
def open(id, **
|
|
63
|
-
path(id).open(binmode: true, **
|
|
62
|
+
def open(id, **)
|
|
63
|
+
path(id).open(binmode: true, **)
|
|
64
64
|
rescue Errno::ENOENT
|
|
65
65
|
raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
|
|
66
66
|
end
|
|
@@ -75,7 +75,7 @@ class Shrine
|
|
|
75
75
|
# from the returned path (e.g. #directory can be set to "public" folder).
|
|
76
76
|
# Both cases accept a `:host` value which will be prefixed to the
|
|
77
77
|
# generated path.
|
|
78
|
-
def url(id, host: nil, **
|
|
78
|
+
def url(id, host: nil, **)
|
|
79
79
|
path = (prefix ? relative_path(id) : path(id)).to_s
|
|
80
80
|
host ? host + path : path
|
|
81
81
|
end
|
|
@@ -123,7 +123,7 @@ class Shrine
|
|
|
123
123
|
# Cleans all empty subdirectories up the hierarchy.
|
|
124
124
|
def clean(path)
|
|
125
125
|
path.dirname.ascend do |pathname|
|
|
126
|
-
if
|
|
126
|
+
if Dir.empty?(pathname) && pathname != directory
|
|
127
127
|
pathname.rmdir
|
|
128
128
|
else
|
|
129
129
|
break
|
|
@@ -175,19 +175,6 @@ class Shrine
|
|
|
175
175
|
.find
|
|
176
176
|
.each { |path| yield path if path.file? }
|
|
177
177
|
end
|
|
178
|
-
|
|
179
|
-
if RUBY_VERSION >= "2.4"
|
|
180
|
-
def dir_empty?(path)
|
|
181
|
-
Dir.empty?(path)
|
|
182
|
-
end
|
|
183
|
-
else
|
|
184
|
-
# :nocov:
|
|
185
|
-
def dir_empty?(path)
|
|
186
|
-
Dir.foreach(path) { |x| return false unless [".", ".."].include?(x) }
|
|
187
|
-
true
|
|
188
|
-
end
|
|
189
|
-
# :nocov:
|
|
190
|
-
end
|
|
191
178
|
end
|
|
192
179
|
end
|
|
193
180
|
end
|
|
@@ -24,18 +24,18 @@ class Shrine
|
|
|
24
24
|
#
|
|
25
25
|
# Shrine::Storage::Linter.new(storage).call(->{File.open("test/fixtures/image.jpg")})
|
|
26
26
|
class Linter
|
|
27
|
-
def self.call(*
|
|
28
|
-
new(*
|
|
27
|
+
def self.call(*)
|
|
28
|
+
new(*).call
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def initialize(storage, action: :error, nonexisting: "nonexisting")
|
|
31
|
+
def initialize(storage, action: :error, nonexisting: String.new("nonexisting"))
|
|
32
32
|
@storage = storage
|
|
33
33
|
@action = action
|
|
34
34
|
@nonexisting = nonexisting
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def call(io_factory = default_io_factory)
|
|
38
|
-
storage.upload(io_factory.call, id = "foo", shrine_metadata: { "foo" => "bar" })
|
|
38
|
+
storage.upload(io_factory.call, id = String.new("foo"), shrine_metadata: { "foo" => "bar" })
|
|
39
39
|
|
|
40
40
|
lint_open(id)
|
|
41
41
|
lint_exists(id)
|
|
@@ -43,9 +43,9 @@ class Shrine
|
|
|
43
43
|
lint_delete(id)
|
|
44
44
|
|
|
45
45
|
if storage.respond_to?(:delete_prefixed)
|
|
46
|
-
storage.upload(io_factory.call, id1 = "a/a/a")
|
|
47
|
-
storage.upload(io_factory.call, id2 = "a/a/b")
|
|
48
|
-
storage.upload(io_factory.call, id3 = "a/aaa/a")
|
|
46
|
+
storage.upload(io_factory.call, id1 = String.new("a/a/a"))
|
|
47
|
+
storage.upload(io_factory.call, id2 = String.new("a/a/b"))
|
|
48
|
+
storage.upload(io_factory.call, id3 = String.new("a/aaa/a"))
|
|
49
49
|
|
|
50
50
|
lint_delete_prefixed(prefix: "a/a/",
|
|
51
51
|
expect_deleted: [id1, id2],
|
|
@@ -55,7 +55,7 @@ class Shrine
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
if storage.respond_to?(:clear!)
|
|
58
|
-
storage.upload(io_factory.call, id = "quux"
|
|
58
|
+
storage.upload(io_factory.call, id = String.new("quux"))
|
|
59
59
|
lint_clear(id)
|
|
60
60
|
end
|
|
61
61
|
|
|
@@ -17,9 +17,7 @@ class Shrine
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def open(id, **)
|
|
20
|
-
|
|
21
|
-
io.set_encoding(io.string.encoding) # Ruby 2.7.0 – https://bugs.ruby-lang.org/issues/16497
|
|
22
|
-
io
|
|
20
|
+
StringIO.new(store.fetch(id))
|
|
23
21
|
rescue KeyError
|
|
24
22
|
raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
|
|
25
23
|
end
|
data/lib/shrine/storage/s3.rb
CHANGED
|
@@ -68,10 +68,11 @@ class Shrine
|
|
|
68
68
|
# [`Aws::S3::Bucket#presigned_post`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
|
|
69
69
|
# [`Aws::S3::Client#initialize`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#initialize-instance_method
|
|
70
70
|
# [configuring AWS SDK]: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html
|
|
71
|
-
def initialize(bucket:, client: nil, prefix: nil, upload_options: {}, multipart_threshold: {}, max_multipart_parts: nil, signer: nil, public: nil, copy_options: COPY_OPTIONS, **
|
|
71
|
+
def initialize(bucket:, client: nil, prefix: nil, upload_options: {}, multipart_threshold: {}, max_multipart_parts: nil, signer: nil, public: nil, copy_options: COPY_OPTIONS, **)
|
|
72
72
|
raise ArgumentError, "the :bucket option is nil" unless bucket
|
|
73
73
|
|
|
74
|
-
@client = client || Aws::S3::Client.new(**
|
|
74
|
+
@client = client || Aws::S3::Client.new(**)
|
|
75
|
+
@transfer_manager = Aws::S3::TransferManager.new(client: @client) if defined?(Aws::S3::TransferManager)
|
|
75
76
|
@bucket = Aws::S3::Bucket.new(name: bucket, client: @client)
|
|
76
77
|
@prefix = prefix
|
|
77
78
|
@upload_options = upload_options
|
|
@@ -97,8 +98,7 @@ class Shrine
|
|
|
97
98
|
options[:content_disposition] = ContentDisposition.inline(filename) if filename
|
|
98
99
|
options[:acl] = "public-read" if public
|
|
99
100
|
|
|
100
|
-
options.merge!(@upload_options)
|
|
101
|
-
options.merge!(upload_options)
|
|
101
|
+
options.merge!(@upload_options, upload_options)
|
|
102
102
|
|
|
103
103
|
if copyable?(io)
|
|
104
104
|
copy(io, id, **options)
|
|
@@ -116,8 +116,8 @@ class Shrine
|
|
|
116
116
|
# Any additional options are forwarded to [`Aws::S3::Object#get`].
|
|
117
117
|
#
|
|
118
118
|
# [`Aws::S3::Object#get`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method
|
|
119
|
-
def open(id, rewindable: true, encoding: nil, **
|
|
120
|
-
chunks, length = get(id, **
|
|
119
|
+
def open(id, rewindable: true, encoding: nil, **)
|
|
120
|
+
chunks, length = get(id, **)
|
|
121
121
|
|
|
122
122
|
Down::ChunkedIO.new(chunks: chunks, rewindable: rewindable, size: length, encoding: encoding)
|
|
123
123
|
rescue Aws::S3::Errors::NoSuchKey
|
|
@@ -145,11 +145,11 @@ class Shrine
|
|
|
145
145
|
#
|
|
146
146
|
# [`Aws::S3::Object#presigned_url`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
|
|
147
147
|
# [`Aws::S3::Object#public_url`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#public_url-instance_method
|
|
148
|
-
def url(id, public: self.public, host: nil, **
|
|
148
|
+
def url(id, public: self.public, host: nil, **)
|
|
149
149
|
if public || signer
|
|
150
|
-
url = object(id).public_url(**
|
|
150
|
+
url = object(id).public_url(**)
|
|
151
151
|
else
|
|
152
|
-
url = object(id).presigned_url(:get, **
|
|
152
|
+
url = object(id).presigned_url(:get, **)
|
|
153
153
|
end
|
|
154
154
|
|
|
155
155
|
if host
|
|
@@ -159,7 +159,7 @@ class Shrine
|
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
if signer
|
|
162
|
-
url = signer.call(url, **
|
|
162
|
+
url = signer.call(url, **)
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
url
|
|
@@ -190,8 +190,7 @@ class Shrine
|
|
|
190
190
|
options = {}
|
|
191
191
|
options[:acl] = "public-read" if public
|
|
192
192
|
|
|
193
|
-
options.merge!(@upload_options)
|
|
194
|
-
options.merge!(presign_options)
|
|
193
|
+
options.merge!(@upload_options, presign_options)
|
|
195
194
|
|
|
196
195
|
send(:"presign_#{method}", id, options)
|
|
197
196
|
end
|
|
@@ -231,12 +230,21 @@ class Shrine
|
|
|
231
230
|
|
|
232
231
|
private
|
|
233
232
|
|
|
234
|
-
#
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
233
|
+
# Upload the file to S3.
|
|
234
|
+
# Uses @transfer_manager, if defined, for any size upload.
|
|
235
|
+
# Falls back to the original code using the older, now depricated
|
|
236
|
+
# AWS APIs for users of older version of the AWS Gem.
|
|
237
|
+
# for multipart uploads of large files.
|
|
238
|
+
def put(io, id, **)
|
|
239
|
+
if @transfer_manager
|
|
240
|
+
@transfer_manager.upload_stream(bucket: bucket.name, key: object_key(id), part_size: part_size(io), **) do |write_stream|
|
|
241
|
+
IO.copy_stream(io, write_stream)
|
|
242
|
+
end
|
|
243
|
+
elsif io.respond_to?(:size) && io.size && io.size <= @multipart_threshold[:upload]
|
|
244
|
+
object(id).put(body: io, **)
|
|
245
|
+
else
|
|
246
|
+
# multipart upload old API
|
|
247
|
+
object(id).upload_stream(part_size: part_size(io), **) do |write_stream|
|
|
240
248
|
IO.copy_stream(io, write_stream)
|
|
241
249
|
end
|
|
242
250
|
end
|
|
@@ -255,8 +263,7 @@ class Shrine
|
|
|
255
263
|
options.merge!(multipart_copy: true, content_length: io.size)
|
|
256
264
|
end
|
|
257
265
|
|
|
258
|
-
options.merge!(@copy_options)
|
|
259
|
-
options.merge!(copy_options)
|
|
266
|
+
options.merge!(@copy_options, copy_options)
|
|
260
267
|
|
|
261
268
|
object(id).copy_from(io.storage.object(io.id), **options)
|
|
262
269
|
end
|
|
@@ -274,13 +281,14 @@ class Shrine
|
|
|
274
281
|
|
|
275
282
|
# When any of these options are specified, the corresponding request
|
|
276
283
|
# headers must be included in the upload request.
|
|
277
|
-
headers = {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
+
headers = {
|
|
285
|
+
"Content-Length" => options[:content_length],
|
|
286
|
+
"Content-Type" => options[:content_type],
|
|
287
|
+
"Content-Disposition" => options[:content_disposition],
|
|
288
|
+
"Content-Encoding" => options[:content_encoding],
|
|
289
|
+
"Content-Language" => options[:content_language],
|
|
290
|
+
"Content-MD5" => options[:content_md5],
|
|
291
|
+
}.compact
|
|
284
292
|
|
|
285
293
|
{ method: :put, url: url, headers: headers }
|
|
286
294
|
end
|
|
@@ -301,8 +309,8 @@ class Shrine
|
|
|
301
309
|
# object before all content is downloaded, so we hack our way around it.
|
|
302
310
|
# This way get the content length without an additional HEAD request.
|
|
303
311
|
if Gem::Version.new(Aws::CORE_GEM_VERSION) >= Gem::Version.new("3.104.0")
|
|
304
|
-
def get(id, **
|
|
305
|
-
enum = object(id).enum_for(:get, **
|
|
312
|
+
def get(id, **)
|
|
313
|
+
enum = object(id).enum_for(:get, **)
|
|
306
314
|
|
|
307
315
|
begin
|
|
308
316
|
content_length = Integer(enum.peek.last["content-length"])
|
|
@@ -315,8 +323,8 @@ class Shrine
|
|
|
315
323
|
[chunks, content_length]
|
|
316
324
|
end
|
|
317
325
|
else
|
|
318
|
-
def get(id, **
|
|
319
|
-
req = client.build_request(:get_object, bucket: bucket.name, key: object_key(id), **
|
|
326
|
+
def get(id, **)
|
|
327
|
+
req = client.build_request(:get_object, bucket: bucket.name, key: object_key(id), **)
|
|
320
328
|
|
|
321
329
|
body = req.enum_for(:send_request)
|
|
322
330
|
begin
|
|
@@ -359,10 +367,10 @@ class Shrine
|
|
|
359
367
|
|
|
360
368
|
# Save the encryption client and continue initialization with normal
|
|
361
369
|
# client.
|
|
362
|
-
def initialize(client: nil, **
|
|
370
|
+
def initialize(client: nil, **)
|
|
363
371
|
return super unless client.class.name.start_with?("Aws::S3::Encryption")
|
|
364
372
|
|
|
365
|
-
super(client: client.client, **
|
|
373
|
+
super(client: client.client, **)
|
|
366
374
|
@encryption_client = client
|
|
367
375
|
end
|
|
368
376
|
|
|
@@ -370,19 +378,19 @@ class Shrine
|
|
|
370
378
|
|
|
371
379
|
# Encryption client doesn't support multipart uploads, so we always use
|
|
372
380
|
# #put_object.
|
|
373
|
-
def put(io, id, **
|
|
381
|
+
def put(io, id, **)
|
|
374
382
|
return super unless encryption_client
|
|
375
383
|
|
|
376
|
-
encryption_client.put_object(body: io, bucket: bucket.name, key: object_key(id), **
|
|
384
|
+
encryption_client.put_object(body: io, bucket: bucket.name, key: object_key(id), **)
|
|
377
385
|
end
|
|
378
386
|
|
|
379
|
-
def get(id, **
|
|
387
|
+
def get(id, **)
|
|
380
388
|
return super unless encryption_client
|
|
381
389
|
|
|
382
390
|
# Encryption client v2 warns against streaming download, so we first
|
|
383
391
|
# download all content into a file.
|
|
384
392
|
tempfile = Tempfile.new("shrine-s3", binmode: true)
|
|
385
|
-
response = encryption_client.get_object(response_target: tempfile, bucket: bucket.name, key: object_key(id), **
|
|
393
|
+
response = encryption_client.get_object(response_target: tempfile, bucket: bucket.name, key: object_key(id), **)
|
|
386
394
|
tempfile.rewind
|
|
387
395
|
|
|
388
396
|
chunks = Enumerator.new do |yielder|
|
data/lib/shrine/uploaded_file.rb
CHANGED
|
@@ -22,6 +22,8 @@ class Shrine
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
module InstanceMethods
|
|
25
|
+
RFC2396_PARSER = URI::RFC2396_Parser.new
|
|
26
|
+
|
|
25
27
|
# The location where the file was uploaded to the storage.
|
|
26
28
|
attr_reader :id
|
|
27
29
|
|
|
@@ -50,7 +52,7 @@ class Shrine
|
|
|
50
52
|
# The extension derived from #id if present, otherwise it's derived
|
|
51
53
|
# from #original_filename.
|
|
52
54
|
def extension
|
|
53
|
-
identifier = id =~
|
|
55
|
+
identifier = id =~ RFC2396_PARSER.make_regexp ? id.sub(/\?.+$/, "") : id # strip query params for shrine-url
|
|
54
56
|
result = File.extname(identifier)[1..-1]
|
|
55
57
|
result ||= File.extname(original_filename.to_s)[1..-1]
|
|
56
58
|
result.downcase if result
|
|
@@ -89,9 +91,9 @@ class Shrine
|
|
|
89
91
|
# # or
|
|
90
92
|
#
|
|
91
93
|
# uploaded_file.open { |io| io.read } # the IO is automatically closed
|
|
92
|
-
def open(**
|
|
94
|
+
def open(**)
|
|
93
95
|
@io.close if @io
|
|
94
|
-
@io = _open(**
|
|
96
|
+
@io = _open(**)
|
|
95
97
|
|
|
96
98
|
return @io unless block_given?
|
|
97
99
|
|
|
@@ -118,9 +120,9 @@ class Shrine
|
|
|
118
120
|
# # or
|
|
119
121
|
#
|
|
120
122
|
# uploaded_file.download { |tempfile| tempfile.read } # tempfile is deleted
|
|
121
|
-
def download(**
|
|
123
|
+
def download(**)
|
|
122
124
|
tempfile = Tempfile.new(["shrine", ".#{extension}"], binmode: true)
|
|
123
|
-
stream(tempfile, **
|
|
125
|
+
stream(tempfile, **)
|
|
124
126
|
tempfile.open
|
|
125
127
|
|
|
126
128
|
block_given? ? yield(tempfile) : tempfile
|
|
@@ -139,19 +141,19 @@ class Shrine
|
|
|
139
141
|
# uploaded_file.stream(StringIO.new)
|
|
140
142
|
# # or
|
|
141
143
|
# uploaded_file.stream("/path/to/destination")
|
|
142
|
-
def stream(destination, **
|
|
144
|
+
def stream(destination, **)
|
|
143
145
|
if opened?
|
|
144
146
|
IO.copy_stream(io, destination)
|
|
145
147
|
io.rewind
|
|
146
148
|
else
|
|
147
|
-
open(**
|
|
149
|
+
open(**) { |io| IO.copy_stream(io, destination) }
|
|
148
150
|
end
|
|
149
151
|
end
|
|
150
152
|
|
|
151
153
|
# Part of complying to the IO interface. It delegates to the internally
|
|
152
154
|
# opened IO object.
|
|
153
|
-
def read(*
|
|
154
|
-
io.read(*
|
|
155
|
+
def read(*)
|
|
156
|
+
io.read(*)
|
|
155
157
|
end
|
|
156
158
|
|
|
157
159
|
# Part of complying to the IO interface. It delegates to the internally
|
|
@@ -179,8 +181,8 @@ class Shrine
|
|
|
179
181
|
end
|
|
180
182
|
|
|
181
183
|
# Calls `#url` on the storage, forwarding any given URL options.
|
|
182
|
-
def url(**
|
|
183
|
-
storage.url(id, **
|
|
184
|
+
def url(**)
|
|
185
|
+
storage.url(id, **)
|
|
184
186
|
end
|
|
185
187
|
|
|
186
188
|
# Calls `#exists?` on the storage, which checks whether the file exists
|
|
@@ -190,8 +192,8 @@ class Shrine
|
|
|
190
192
|
end
|
|
191
193
|
|
|
192
194
|
# Uploads a new file to this file's location and returns it.
|
|
193
|
-
def replace(io, **
|
|
194
|
-
uploader.upload(io,
|
|
195
|
+
def replace(io, **)
|
|
196
|
+
uploader.upload(io, **, location: id)
|
|
195
197
|
end
|
|
196
198
|
|
|
197
199
|
# Calls `#delete` on the storage, which deletes the file from the
|
|
@@ -207,12 +209,12 @@ class Shrine
|
|
|
207
209
|
|
|
208
210
|
# Returns the data hash in the JSON format. Suitable for storing in a
|
|
209
211
|
# database column or passing to a background job.
|
|
210
|
-
def to_json(*
|
|
211
|
-
data.to_json(*
|
|
212
|
+
def to_json(*)
|
|
213
|
+
data.to_json(*)
|
|
212
214
|
end
|
|
213
215
|
|
|
214
216
|
# Conform to ActiveSupport's JSON interface.
|
|
215
|
-
def as_json(*
|
|
217
|
+
def as_json(*)
|
|
216
218
|
data
|
|
217
219
|
end
|
|
218
220
|
|
|
@@ -263,8 +265,8 @@ class Shrine
|
|
|
263
265
|
@io ||= _open
|
|
264
266
|
end
|
|
265
267
|
|
|
266
|
-
def _open(**
|
|
267
|
-
storage.open(id, **
|
|
268
|
+
def _open(**)
|
|
269
|
+
storage.open(id, **)
|
|
268
270
|
end
|
|
269
271
|
end
|
|
270
272
|
|