shrine 2.11.0 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

@@ -20,9 +20,13 @@ class Shrine
20
20
  #
21
21
  # This can be useful in combination with the `default_storage` plugin.
22
22
  module DynamicStorage
23
+ def self.configure(uploader, options = {})
24
+ uploader.opts[:dynamic_storages] ||= {}
25
+ end
26
+
23
27
  module ClassMethods
24
28
  def dynamic_storages
25
- @dynamic_storages ||= {}
29
+ opts[:dynamic_storages]
26
30
  end
27
31
 
28
32
  def storage(regex, &block)
@@ -68,7 +68,6 @@ class Shrine
68
68
 
69
69
  def spawn_thread
70
70
  Thread.new do
71
- Thread.current.abort_on_exception = true
72
71
  loop do
73
72
  task = @tasks.deq(true) rescue break
74
73
  task.call
@@ -37,7 +37,7 @@ class Shrine
37
37
  # uploads to the temporary (`:cache`) storage.
38
38
  #
39
39
  # The above will create a `GET /images/presign` endpoint, which calls
40
- # `#presign` on the storage and returns the HTTP verb, URL, fields, and
40
+ # `#presign` on the storage and returns the HTTP verb, URL, params, and
41
41
  # headers needed for a single upload directly to the storage service, in
42
42
  # JSON format.
43
43
  #
@@ -56,11 +56,6 @@ class Shrine
56
56
  # "headers": {}
57
57
  # }
58
58
  #
59
- # * `method` – HTTP verb
60
- # * `url` – request URL
61
- # * `fields` – POST parameters
62
- # * `headers` – request headers
63
- #
64
59
  # ## Location
65
60
  #
66
61
  # By default the generated location won't have any file extension, but you
@@ -34,15 +34,11 @@ class Shrine
34
34
  # require "image_processing/mini_magick"
35
35
  #
36
36
  # process(:store) do |io, context|
37
- # original = io.download
38
- #
39
- # resized = ImageProcessing::MiniMagick
40
- # .source(original)
41
- # .resize_to_limit!(800, 800)
42
- #
43
- # original.close!
44
- #
45
- # resized
37
+ # io.download do |original|
38
+ # ImageProcessing::MiniMagick
39
+ # .source(original)
40
+ # .resize_to_limit!(800, 800)
41
+ # end
46
42
  # end
47
43
  #
48
44
  # The declarations are additive and inheritable, so for the same action you
@@ -79,7 +79,7 @@ class Shrine
79
79
  # metadata. Also returns the correct "Content-Range" header on ranged
80
80
  # requests.
81
81
  def rack_headers(disposition:, range: false)
82
- length = range ? range.end - range.begin + 1 : size || io.size
82
+ length = range ? range.size : size || io.size
83
83
  type = mime_type || Rack::Mime.mime_type(".#{extension}")
84
84
  filename = original_filename || id.split("/").last
85
85
 
@@ -28,6 +28,13 @@ class Shrine
28
28
  # around the `open-uri` standard library. Note that Down expects the given
29
29
  # URL to be URI-encoded.
30
30
  #
31
+ # ## Dynamic options
32
+ #
33
+ # You can dynamically pass options to the downloader by using
34
+ # `Attacher#assign_remote_url`:
35
+ #
36
+ # attacher.assign_remote_url("http://example.com/cool-image.png", downloader: { 'Authorization' => 'Basic ...' })
37
+ #
31
38
  # ## Maximum size
32
39
  #
33
40
  # It's a good practice to limit the maximum filesize of the remote file:
@@ -50,8 +57,10 @@ class Shrine
50
57
  #
51
58
  # require "down/http"
52
59
  #
53
- # plugin :remote_url, max_size: 20*1024*1024, downloader: ->(url, max_size:) do
54
- # Down::Http.download(url, max_size: max_size, follow: { max_hops: 4 }, timeout: { read: 3 })
60
+ # plugin :remote_url, max_size: 20*1024*1024, downloader: -> (url, max_size:, **options) do
61
+ # Down::Http.download(url, max_size: max_size, **options) do |http|
62
+ # http.follow(max_hops: 2).timeout(connect: 2, read: 2)
63
+ # end
55
64
  # end
56
65
  #
57
66
  # ## Errors
@@ -60,7 +69,7 @@ class Shrine
60
69
  # equal to the error message. You can change the default error message:
61
70
  #
62
71
  # plugin :remote_url, error_message: "download failed"
63
- # plugin :remote_url, error_message: ->(url, error) { I18n.t("errors.download_failed") }
72
+ # plugin :remote_url, error_message: -> (url, error) { I18n.t("errors.download_failed") }
64
73
  #
65
74
  # ## Background
66
75
  #
@@ -109,11 +118,11 @@ class Shrine
109
118
  # Downloads the remote file and assigns it. If download failed, sets
110
119
  # the error message and assigns the url to an instance variable so that
111
120
  # it shows up in the form.
112
- def remote_url=(url)
113
- return if url == ""
121
+ def assign_remote_url(url, downloader: {})
122
+ return if url == "" || url.nil?
114
123
 
115
124
  begin
116
- downloaded_file = download(url)
125
+ downloaded_file = download(url, downloader)
117
126
  rescue => error
118
127
  download_error = error
119
128
  end
@@ -127,6 +136,11 @@ class Shrine
127
136
  end
128
137
  end
129
138
 
139
+ # Alias for #assign_remote_url.
140
+ def remote_url=(url)
141
+ assign_remote_url(url)
142
+ end
143
+
130
144
  # Form builders require the reader as well.
131
145
  def remote_url
132
146
  @remote_url
@@ -137,18 +151,18 @@ class Shrine
137
151
  # Downloads the file using the "down" gem or a custom downloader.
138
152
  # Checks the file size and terminates the download early if the file
139
153
  # is too big.
140
- def download(url)
154
+ def download(url, options)
141
155
  downloader = shrine_class.opts[:remote_url_downloader]
142
156
  downloader = method(:"download_with_#{downloader}") if downloader.is_a?(Symbol)
143
157
  max_size = shrine_class.opts[:remote_url_max_size]
144
158
 
145
- downloader.call(url, max_size: max_size)
159
+ downloader.call(url, { max_size: max_size }.merge(options))
146
160
  end
147
161
 
148
162
  # We silence any download errors, because for the user's point of view
149
163
  # the download simply failed.
150
- def download_with_open_uri(url, max_size:)
151
- Down.download(url, max_size: max_size)
164
+ def download_with_open_uri(url, options)
165
+ Down.download(url, options)
152
166
  end
153
167
 
154
168
  def download_error_message(url, error)
@@ -75,8 +75,7 @@ class Shrine
75
75
  hash = send(:"calculate_#{algorithm}", io)
76
76
  io.rewind
77
77
 
78
- encoded_hash = send(:"encode_#{format}", hash)
79
- encoded_hash
78
+ send(:"encode_#{format}", hash)
80
79
  end
81
80
 
82
81
  private
@@ -44,8 +44,8 @@ class Shrine
44
44
  end
45
45
 
46
46
  DEFAULT_MESSAGES = {
47
- max_size: ->(max) { "is too large (max is #{max.to_f/1024/1024} MB)" },
48
- min_size: ->(min) { "is too small (min is #{min.to_f/1024/1024} MB)" },
47
+ max_size: ->(max) { "is too large (max is #{PRETTY_FILESIZE.call(max)})" },
48
+ min_size: ->(min) { "is too small (min is #{PRETTY_FILESIZE.call(min)})" },
49
49
  max_width: ->(max) { "is too wide (max is #{max} px)" },
50
50
  min_width: ->(min) { "is too narrow (min is #{min} px)" },
51
51
  max_height: ->(max) { "is too tall (max is #{max} px)" },
@@ -56,6 +56,19 @@ class Shrine
56
56
  extension_exclusion: ->(list) { "is of forbidden format" },
57
57
  }
58
58
 
59
+ FILESIZE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"].freeze
60
+
61
+ # Returns filesize in a human readable format with units.
62
+ # Uses the binary JEDEC unit system, i.e. 1.0 KB = 1024 bytes
63
+ PRETTY_FILESIZE = lambda do |bytes|
64
+ return "0.0 B" if bytes == 0
65
+
66
+ exp = Math.log(bytes, 1024).floor
67
+ max_exp = FILESIZE_UNITS.length - 1
68
+ exp = max_exp if exp > max_exp
69
+ "%.1f %s" % [bytes.to_f / 1024 ** exp, FILESIZE_UNITS[exp]]
70
+ end
71
+
59
72
  module AttacherClassMethods
60
73
  def default_validation_messages
61
74
  @default_validation_messages ||= DEFAULT_MESSAGES.merge(
@@ -15,16 +15,17 @@ class Shrine
15
15
  # plugin :processing
16
16
  #
17
17
  # process(:store) do |io, context|
18
- # original = io.download
19
- # pipeline = ImageProcessing::MiniMagick.source(original)
18
+ # versions = { original: io } # retain original
20
19
  #
21
- # size_800 = pipeline.resize_to_limit!(800, 800)
22
- # size_500 = pipeline.resize_to_limit!(500, 500)
23
- # size_300 = pipeline.resize_to_limit!(300, 300)
20
+ # io.download do |original|
21
+ # pipeline = ImageProcessing::MiniMagick.source(original)
24
22
  #
25
- # original.close!
23
+ # versions[:large] = pipeline.resize_to_limit!(800, 800)
24
+ # versions[:medium] = pipeline.resize_to_limit!(500, 500)
25
+ # versions[:small] = pipeline.resize_to_limit!(300, 300)
26
+ # end
26
27
  #
27
- # { original: io, large: size_800, medium: size_500, small: size_300}
28
+ # versions # return the hash of processed files
28
29
  # end
29
30
  #
30
31
  # You probably want to load the `delete_raw` plugin to automatically
@@ -105,17 +106,18 @@ class Shrine
105
106
  # example, you might want to split a PDf into pages:
106
107
  #
107
108
  # process(:store) do |io, context|
108
- # pdf = io.download
109
- # page_count = MiniMagick::Image.new(pdf.path).pages.count
110
- # pipeline = ImageProcessing::MiniMagick.source(pdf).convert("jpg")
109
+ # versions = { pages: [] }
111
110
  #
112
- # pages = page_count.times.map do |page_number|
113
- # pipeline.loader(page: page_number).call
114
- # end
111
+ # io.download do |pdf|
112
+ # page_count = MiniMagick::Image.new(pdf.path).pages.count
113
+ # pipeline = ImageProcessing::MiniMagick.source(pdf).convert("jpg")
115
114
  #
116
- # pdf.close!
115
+ # page_count.times do |page_number|
116
+ # versions[:pages] << pipeline.loader(page: page_number).call
117
+ # end
118
+ # end
117
119
  #
118
- # { pages: pages } # array of pages
120
+ # versions
119
121
  # end
120
122
  #
121
123
  # You can also combine Hashes and Arrays, there is no limit to the level of
@@ -286,13 +288,7 @@ class Shrine
286
288
  shrine_class.opts[:versions_fallback_to_original]
287
289
  end
288
290
 
289
- def assign_cached(value)
290
- cached_file = uploaded_file(value)
291
- Shrine.deprecation("Assigning cached hash of files is deprecated for security reasons and will be removed in Shrine 3.") if cached_file.is_a?(Hash) || cached_file.is_a?(Array)
292
- super(cached_file)
293
- end
294
-
295
- # Converts the Hash of UploadedFile objects into a Hash of data.
291
+ # Converts the Hash/Array of UploadedFile objects into a Hash/Array of data.
296
292
  def convert_to_data(object)
297
293
  if object.is_a?(Hash)
298
294
  object.inject({}) do |hash, (name, value)|
@@ -151,9 +151,10 @@ class Shrine
151
151
  (io.is_a?(UploadedFile) && io.storage.is_a?(Storage::FileSystem))
152
152
  end
153
153
 
154
- # Opens the file on the given location in read mode.
155
- def open(id, &block)
156
- path(id).open("rb", &block)
154
+ # Opens the file on the given location in read mode. Accepts additional
155
+ # `File.open` arguments.
156
+ def open(id, *args, &block)
157
+ path(id).open("rb", *args, &block)
157
158
  end
158
159
 
159
160
  # Returns true if the file exists on the filesystem.
@@ -31,10 +31,10 @@ class Shrine
31
31
  # It can be initialized by providing the bucket name and credentials:
32
32
  #
33
33
  # s3 = Shrine::Storage::S3.new(
34
+ # bucket: "my-app", # required
34
35
  # access_key_id: "abc",
35
36
  # secret_access_key: "xyz",
36
37
  # region: "eu-west-1",
37
- # bucket: "my-app",
38
38
  # )
39
39
  #
40
40
  # The core features of this storage requires the following AWS permissions:
@@ -264,8 +264,9 @@ class Shrine
264
264
  end
265
265
  end
266
266
 
267
- # Downloads the file from S3, and returns a `Tempfile`. Any additional
268
- # options are forwarded to [`Aws::S3::Object#get`].
267
+ # Downloads the file from S3 and returns a `Tempfile`. The download will
268
+ # be automatically retried up to 3 times. Any additional options are
269
+ # forwarded to [`Aws::S3::Object#get`].
269
270
  #
270
271
  # [`Aws::S3::Object#get`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method
271
272
  def download(id, **options)
@@ -279,13 +280,21 @@ class Shrine
279
280
  raise
280
281
  end
281
282
 
282
- # Returns a `Down::ChunkedIO` object representing the S3 object. Any
283
- # additional options are forwarded to [`Aws::S3::Object#get`].
283
+ # Returns a `Down::ChunkedIO` object that downloads S3 object content
284
+ # on-demand. By default, read content will be cached onto disk so that
285
+ # it can be rewinded, but if you don't need that you can pass
286
+ # `rewindable: false`.
287
+ #
288
+ # Any additional options are forwarded to [`Aws::S3::Object#get`].
284
289
  #
285
290
  # [`Aws::S3::Object#get`]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#get-instance_method
286
- def open(id, **options)
291
+ def open(id, rewindable: true, **options)
287
292
  object = object(id)
288
- io = Down::ChunkedIO.new(chunks: object.enum_for(:get, **options), data: { object: object })
293
+ io = Down::ChunkedIO.new(
294
+ chunks: object.enum_for(:get, **options),
295
+ rewindable: rewindable,
296
+ data: { object: object },
297
+ )
289
298
  io.size = object.content_length
290
299
  io
291
300
  end
@@ -430,7 +439,7 @@ class Shrine
430
439
  object(id).upload_file(path, **options)
431
440
  File.size(path)
432
441
  else
433
- io.open if io.is_a?(UploadedFile)
442
+ io.to_io if io.is_a?(UploadedFile) # open if not already opened
434
443
 
435
444
  if io.respond_to?(:size) && io.size
436
445
  object(id).put(body: io, **options)
@@ -506,13 +515,12 @@ class Shrine
506
515
 
507
516
  # Uploads at most 5MB of IO content into a single multipart part.
508
517
  def upload_part(multipart_upload, io, part_number)
509
- Tempfile.create("shrine-s3-part-#{part_number}") do |body|
510
- multipart_part = multipart_upload.part(part_number)
511
-
518
+ Tempfile.create("shrine-s3-part-#{part_number}", binmode: true) do |body|
512
519
  IO.copy_stream(io, body, MIN_PART_SIZE)
513
520
  body.rewind
514
521
 
515
- response = multipart_part.upload(body: body)
522
+ multipart_part = multipart_upload.part(part_number)
523
+ response = multipart_part.upload(body: body)
516
524
 
517
525
  { part_number: part_number, size: body.size, etag: response.etag }
518
526
  end
@@ -7,7 +7,7 @@ class Shrine
7
7
 
8
8
  module VERSION
9
9
  MAJOR = 2
10
- MINOR = 11
10
+ MINOR = 12
11
11
  TINY = 0
12
12
  PRE = nil
13
13
 
@@ -45,16 +45,22 @@ direct uploads for fully asynchronous user experience.
45
45
  gem.add_development_dependency "mini_magick", "~> 4.0" unless ENV["CI"]
46
46
  gem.add_development_dependency "ruby-vips", "~> 2.0" unless ENV["CI"]
47
47
  gem.add_development_dependency "aws-sdk-s3", "~> 1.2"
48
+ gem.add_development_dependency "aws-sdk-core", "~> 3.23"
48
49
 
49
50
  unless RUBY_ENGINE == "jruby" || ENV["CI"]
50
51
  gem.add_development_dependency "ruby-filemagic", "~> 0.7"
51
52
  end
52
53
 
53
54
  gem.add_development_dependency "sequel"
54
- gem.add_development_dependency "activerecord", "~> 4.2"
55
+
56
+ if RUBY_VERSION >= "2.2.2"
57
+ gem.add_development_dependency "activerecord", "~> 5.0"
58
+ else
59
+ gem.add_development_dependency "activerecord", "~> 4.2"
60
+ end
55
61
 
56
62
  if RUBY_ENGINE == "jruby"
57
- gem.add_development_dependency "activerecord-jdbcsqlite3-adapter", "1.3.24"
63
+ gem.add_development_dependency "activerecord-jdbcsqlite3-adapter", "51"
58
64
  else
59
65
  gem.add_development_dependency "sqlite3"
60
66
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shrine
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.0
4
+ version: 2.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-28 00:00:00.000000000 Z
11
+ date: 2018-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -248,6 +248,20 @@ dependencies:
248
248
  - - "~>"
249
249
  - !ruby/object:Gem::Version
250
250
  version: '1.2'
251
+ - !ruby/object:Gem::Dependency
252
+ name: aws-sdk-core
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - "~>"
256
+ - !ruby/object:Gem::Version
257
+ version: '3.23'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - "~>"
263
+ - !ruby/object:Gem::Version
264
+ version: '3.23'
251
265
  - !ruby/object:Gem::Dependency
252
266
  name: ruby-filemagic
253
267
  requirement: !ruby/object:Gem::Requirement
@@ -282,14 +296,14 @@ dependencies:
282
296
  requirements:
283
297
  - - "~>"
284
298
  - !ruby/object:Gem::Version
285
- version: '4.2'
299
+ version: '5.0'
286
300
  type: :development
287
301
  prerelease: false
288
302
  version_requirements: !ruby/object:Gem::Requirement
289
303
  requirements:
290
304
  - - "~>"
291
305
  - !ruby/object:Gem::Version
292
- version: '4.2'
306
+ version: '5.0'
293
307
  - !ruby/object:Gem::Dependency
294
308
  name: sqlite3
295
309
  requirement: !ruby/object:Gem::Requirement