paperclip 4.2.2 → 5.2.1

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.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -0
  3. data/.hound.yml +1055 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +17 -15
  6. data/Appraisals +4 -16
  7. data/CONTRIBUTING.md +19 -8
  8. data/Gemfile +5 -9
  9. data/LICENSE +1 -1
  10. data/NEWS +148 -31
  11. data/README.md +327 -191
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +2 -2
  14. data/UPGRADING +12 -9
  15. data/features/basic_integration.feature +10 -6
  16. data/features/migration.feature +0 -24
  17. data/features/step_definitions/attachment_steps.rb +33 -27
  18. data/features/step_definitions/html_steps.rb +2 -2
  19. data/features/step_definitions/rails_steps.rb +39 -38
  20. data/features/step_definitions/s3_steps.rb +2 -2
  21. data/features/step_definitions/web_steps.rb +1 -103
  22. data/features/support/env.rb +1 -0
  23. data/features/support/file_helpers.rb +2 -2
  24. data/features/support/paths.rb +1 -1
  25. data/features/support/rails.rb +0 -24
  26. data/gemfiles/4.2.gemfile +6 -8
  27. data/gemfiles/5.0.gemfile +17 -0
  28. data/lib/paperclip/attachment.rb +32 -20
  29. data/lib/paperclip/attachment_registry.rb +3 -2
  30. data/lib/paperclip/callbacks.rb +8 -6
  31. data/lib/paperclip/content_type_detector.rb +27 -11
  32. data/lib/paperclip/errors.rb +3 -1
  33. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  34. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  35. data/lib/paperclip/glue.rb +1 -1
  36. data/lib/paperclip/has_attached_file.rb +9 -2
  37. data/lib/paperclip/helpers.rb +14 -10
  38. data/lib/paperclip/interpolations/plural_cache.rb +6 -5
  39. data/lib/paperclip/interpolations.rb +19 -14
  40. data/lib/paperclip/io_adapters/abstract_adapter.rb +26 -3
  41. data/lib/paperclip/io_adapters/attachment_adapter.rb +10 -5
  42. data/lib/paperclip/io_adapters/data_uri_adapter.rb +8 -8
  43. data/lib/paperclip/io_adapters/empty_string_adapter.rb +5 -4
  44. data/lib/paperclip/io_adapters/file_adapter.rb +12 -6
  45. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +7 -7
  46. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -6
  47. data/lib/paperclip/io_adapters/nil_adapter.rb +8 -5
  48. data/lib/paperclip/io_adapters/registry.rb +6 -2
  49. data/lib/paperclip/io_adapters/stringio_adapter.rb +9 -6
  50. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +10 -6
  51. data/lib/paperclip/io_adapters/uri_adapter.rb +41 -19
  52. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  53. data/lib/paperclip/media_type_spoof_detector.rb +2 -2
  54. data/lib/paperclip/processor.rb +5 -4
  55. data/lib/paperclip/rails_environment.rb +25 -0
  56. data/lib/paperclip/schema.rb +3 -9
  57. data/lib/paperclip/storage/filesystem.rb +13 -2
  58. data/lib/paperclip/storage/fog.rb +30 -18
  59. data/lib/paperclip/storage/s3.rb +92 -65
  60. data/lib/paperclip/thumbnail.rb +16 -7
  61. data/lib/paperclip/url_generator.rb +16 -13
  62. data/lib/paperclip/validators/attachment_size_validator.rb +1 -7
  63. data/lib/paperclip/validators.rb +1 -1
  64. data/lib/paperclip/version.rb +3 -1
  65. data/lib/paperclip.rb +25 -12
  66. data/lib/tasks/paperclip.rake +33 -3
  67. data/paperclip.gemspec +18 -15
  68. data/spec/paperclip/attachment_definitions_spec.rb +1 -1
  69. data/spec/paperclip/attachment_processing_spec.rb +2 -4
  70. data/spec/paperclip/attachment_registry_spec.rb +84 -13
  71. data/spec/paperclip/attachment_spec.rb +130 -39
  72. data/spec/paperclip/content_type_detector_spec.rb +8 -1
  73. data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -1
  74. data/spec/paperclip/geometry_spec.rb +1 -1
  75. data/spec/paperclip/glue_spec.rb +44 -0
  76. data/spec/paperclip/has_attached_file_spec.rb +24 -8
  77. data/spec/paperclip/integration_spec.rb +4 -3
  78. data/spec/paperclip/interpolations_spec.rb +16 -13
  79. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +47 -23
  80. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +6 -3
  81. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +7 -1
  82. data/spec/paperclip/io_adapters/file_adapter_spec.rb +6 -3
  83. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +26 -6
  84. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +1 -1
  85. data/spec/paperclip/io_adapters/registry_spec.rb +2 -2
  86. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +5 -1
  87. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +5 -5
  88. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +77 -7
  89. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +10 -0
  90. data/spec/paperclip/media_type_spoof_detector_spec.rb +34 -11
  91. data/spec/paperclip/paperclip_spec.rb +4 -29
  92. data/spec/paperclip/plural_cache_spec.rb +17 -16
  93. data/spec/paperclip/rails_environment_spec.rb +33 -0
  94. data/spec/paperclip/storage/fog_spec.rb +58 -3
  95. data/spec/paperclip/storage/s3_live_spec.rb +20 -14
  96. data/spec/paperclip/storage/s3_spec.rb +398 -213
  97. data/spec/paperclip/tempfile_factory_spec.rb +4 -0
  98. data/spec/paperclip/tempfile_spec.rb +35 -0
  99. data/spec/paperclip/thumbnail_spec.rb +51 -32
  100. data/spec/paperclip/url_generator_spec.rb +55 -44
  101. data/spec/paperclip/validators/attachment_size_validator_spec.rb +26 -20
  102. data/spec/paperclip/validators_spec.rb +5 -5
  103. data/spec/spec_helper.rb +8 -1
  104. data/spec/support/assertions.rb +12 -1
  105. data/spec/support/conditional_filter_helper.rb +5 -0
  106. data/spec/support/fake_model.rb +4 -0
  107. data/spec/support/fixtures/empty.xlsx +0 -0
  108. data/spec/support/matchers/have_column.rb +11 -2
  109. data/spec/support/mock_attachment.rb +2 -0
  110. data/spec/support/mock_url_generator_builder.rb +2 -2
  111. data/spec/support/model_reconstruction.rb +9 -1
  112. data/spec/support/reporting.rb +11 -0
  113. metadata +109 -162
  114. data/RUNNING_TESTS.md +0 -4
  115. data/cucumber/paperclip_steps.rb +0 -6
  116. data/gemfiles/3.2.gemfile +0 -19
  117. data/gemfiles/4.0.gemfile +0 -19
  118. data/gemfiles/4.1.gemfile +0 -19
  119. data/lib/paperclip/locales/de.yml +0 -18
  120. data/lib/paperclip/locales/es.yml +0 -18
  121. data/lib/paperclip/locales/ja.yml +0 -18
  122. data/lib/paperclip/locales/pt-BR.yml +0 -18
  123. data/lib/paperclip/locales/zh-CN.yml +0 -18
  124. data/lib/paperclip/locales/zh-HK.yml +0 -18
  125. data/lib/paperclip/locales/zh-TW.yml +0 -18
  126. data/spec/support/mock_model.rb +0 -2
  127. data/spec/support/rails_helpers.rb +0 -7
@@ -41,19 +41,18 @@ module Paperclip
41
41
  # * +s3_permissions+: This is a String that should be one of the "canned" access
42
42
  # policies that S3 provides (more information can be found here:
43
43
  # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)
44
- # The default for Paperclip is :public_read.
44
+ # The default for Paperclip is public-read.
45
45
  #
46
46
  # You can set permission on a per style bases by doing the following:
47
47
  # :s3_permissions => {
48
- # :original => :private
48
+ # :original => "private"
49
49
  # }
50
50
  # Or globally:
51
- # :s3_permissions => :private
51
+ # :s3_permissions => "private"
52
52
  #
53
- # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
54
- # 'http', 'https', or an empty string to generate protocol-relative URLs. Defaults to 'http'
55
- # when your :s3_permissions are :public_read (the default), and 'https' when your
56
- # :s3_permissions are anything else.
53
+ # * +s3_protocol+: The protocol for the URLs generated to your S3 assets.
54
+ # Can be either 'http', 'https', or an empty string to generate
55
+ # protocol-relative URLs. Defaults to empty string.
57
56
  # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as
58
57
  # {'Expires' => 1.year.from_now.httpdate}. If you use a Proc, headers are determined at
59
58
  # runtime. Paperclip will call that Proc with attachment as the only argument.
@@ -61,11 +60,14 @@ module Paperclip
61
60
  # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
62
61
  # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
63
62
  # Paperclip will attempt to create it. The bucket name will not be interpolated.
64
- # You can define the bucket as a Proc if you want to determine it's name at runtime.
63
+ # You can define the bucket as a Proc if you want to determine its name at runtime.
65
64
  # Paperclip will call that Proc with attachment as the only argument.
66
65
  # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
67
66
  # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
68
67
  # link in the +url+ entry for more information about S3 domains and buckets.
68
+ # * +s3_prefixes_in_alias+: The number of prefixes that is prepended by
69
+ # s3_host_alias. This will remove the prefixes from the path in
70
+ # :s3_alias_url url interpolation
69
71
  # * +url+: There are four options for the S3 url. You can choose to have the bucket's name
70
72
  # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
71
73
  # You can also specify a CNAME (which requires the CNAME to be specified as
@@ -93,22 +95,29 @@ module Paperclip
93
95
  # S3 (strictly speaking) does not support directories, you can still use a / to
94
96
  # separate parts of your file name.
95
97
  # * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
98
+ # * +s3_region+: For aws-sdk v2, s3_region is required.
96
99
  # * +s3_metadata+: These key/value pairs will be stored with the
97
100
  # object. This option works by prefixing each key with
98
101
  # "x-amz-meta-" before sending it as a header on the object
99
102
  # upload request. Can be defined both globally and within a style-specific hash.
100
103
  # * +s3_storage_class+: If this option is set to
101
- # <tt>:reduced_redundancy</tt>, the object will be stored using Reduced
102
- # Redundancy Storage. RRS enables customers to reduce their
104
+ # <tt>:REDUCED_REDUNDANCY</tt>, the object will be stored using Reduced
105
+ # Redundancy Storage. RRS enables customers to reduce their
103
106
  # costs by storing non-critical, reproducible data at lower
104
107
  # levels of redundancy than Amazon S3's standard storage.
108
+ # * +use_accelerate_endpoint+: Use accelerate endpoint
109
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
105
110
  #
106
111
  # You can set storage class on a per style bases by doing the following:
107
112
  # :s3_storage_class => {
108
- # :thumb => :reduced_reduncancy
113
+ # :thumb => :REDUCED_REDUNDANCY
109
114
  # }
115
+ #
110
116
  # Or globally:
111
- # :s3_storage_class => :reduced_redundancy
117
+ # :s3_storage_class => :REDUCED_REDUNDANCY
118
+ #
119
+ # Other storage classes, such as <tt>:STANDARD_IA</tt>, are also available—see the
120
+ # documentation for the <tt>aws-sdk</tt> gem for the full list.
112
121
 
113
122
  module S3
114
123
  def self.extended base
@@ -117,39 +126,23 @@ module Paperclip
117
126
  rescue LoadError => e
118
127
  e.message << " (You may need to install the aws-sdk gem)"
119
128
  raise e
120
- end unless defined?(AWS::Core)
121
-
122
- # Overriding log formatter to make sure it return a UTF-8 string
123
- if defined?(AWS::Core::LogFormatter)
124
- AWS::Core::LogFormatter.class_eval do
125
- def summarize_hash(hash)
126
- hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
127
- end
128
- end
129
- elsif defined?(AWS::Core::ClientLogging)
130
- AWS::Core::ClientLogging.class_eval do
131
- def sanitize_hash(hash)
132
- hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
133
- end
134
- end
129
+ end
130
+ if Gem::Version.new(Aws::VERSION) >= Gem::Version.new(2) &&
131
+ Gem::Version.new(Aws::VERSION) <= Gem::Version.new("2.0.33")
132
+ raise LoadError, "paperclip does not support aws-sdk versions 2.0.0 - 2.0.33. Please upgrade aws-sdk to a newer version."
135
133
  end
136
134
 
137
135
  base.instance_eval do
138
136
  @s3_options = @options[:s3_options] || {}
139
137
  @s3_permissions = set_permissions(@options[:s3_permissions])
140
- @s3_protocol = @options[:s3_protocol] ||
141
- Proc.new do |style, attachment|
142
- permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
143
- permission = permission.call(attachment, style) if permission.respond_to?(:call)
144
- (permission == :public_read) ? 'http' : 'https'
145
- end
138
+ @s3_protocol = @options[:s3_protocol] || "".freeze
146
139
  @s3_metadata = @options[:s3_metadata] || {}
147
140
  @s3_headers = {}
148
141
  merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)
149
142
 
150
143
  @s3_storage_class = set_storage_class(@options[:s3_storage_class])
151
144
 
152
- @s3_server_side_encryption = :aes256
145
+ @s3_server_side_encryption = "AES256"
153
146
  if @options[:s3_server_side_encryption].blank?
154
147
  @s3_server_side_encryption = false
155
148
  end
@@ -157,33 +150,49 @@ module Paperclip
157
150
  @s3_server_side_encryption = @options[:s3_server_side_encryption]
158
151
  end
159
152
 
160
- unless @options[:url].to_s.match(/\A:s3.*url\Z/) || @options[:url] == ":asset_host"
161
- @options[:path] = path_option.gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system/, '')
162
- @options[:url] = ":s3_path_url"
153
+ unless @options[:url].to_s.match(/\A:s3.*url\z/) || @options[:url] == ":asset_host".freeze
154
+ @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "".freeze)
155
+ @options[:url] = ":s3_path_url".freeze
163
156
  end
164
157
  @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
165
158
 
166
159
  @http_proxy = @options[:http_proxy] || nil
160
+
161
+ if @options.has_key?(:use_accelerate_endpoint) &&
162
+ Gem::Version.new(Aws::VERSION) < Gem::Version.new("2.3.0")
163
+ raise LoadError, ":use_accelerate_endpoint is only available from aws-sdk version 2.3.0. Please upgrade aws-sdk to a newer version."
164
+ end
165
+
166
+ @use_accelerate_endpoint = @options[:use_accelerate_endpoint]
167
167
  end
168
168
 
169
169
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
170
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{\A/}, "")}"
170
+ protocol = attachment.s3_protocol(style, true)
171
+ host = attachment.s3_host_alias
172
+ path = attachment.path(style).
173
+ split("/")[attachment.s3_prefixes_in_alias..-1].
174
+ join("/").
175
+ sub(%r{\A/}, "".freeze)
176
+ "#{protocol}//#{host}/#{path}"
171
177
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
172
178
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
173
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{\A/}, "")}"
179
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
174
180
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
175
181
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
176
- "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{\A/}, "")}"
182
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
177
183
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
178
184
  Paperclip.interpolates(:asset_host) do |attachment, style|
179
- "#{attachment.path(style).gsub(%r{\A/}, "")}"
185
+ "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
180
186
  end unless Paperclip::Interpolations.respond_to? :asset_host
181
187
  end
182
188
 
183
189
  def expiring_url(time = 3600, style_name = default_style)
184
190
  if path(style_name)
185
- base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
186
- s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
191
+ base_options = { expires_in: time }
192
+ s3_object(style_name).presigned_url(
193
+ :get,
194
+ base_options.merge(s3_url_options),
195
+ ).to_s
187
196
  else
188
197
  url(style_name)
189
198
  end
@@ -197,7 +206,14 @@ module Paperclip
197
206
  host_name = @options[:s3_host_name]
198
207
  host_name = host_name.call(self) if host_name.is_a?(Proc)
199
208
 
200
- host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
209
+ host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com".freeze
210
+ end
211
+
212
+ def s3_region
213
+ region = @options[:s3_region]
214
+ region = region.call(self) if region.is_a?(Proc)
215
+
216
+ region || s3_credentials[:s3_region]
201
217
  end
202
218
 
203
219
  def s3_host_alias
@@ -206,6 +222,10 @@ module Paperclip
206
222
  @s3_host_alias
207
223
  end
208
224
 
225
+ def s3_prefixes_in_alias
226
+ @s3_prefixes_in_alias ||= @options[:s3_prefixes_in_alias].to_i
227
+ end
228
+
209
229
  def s3_url_options
210
230
  s3_url_options = @options[:s3_url_options] || {}
211
231
  s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call)
@@ -220,7 +240,7 @@ module Paperclip
220
240
 
221
241
  def s3_interface
222
242
  @s3_interface ||= begin
223
- config = { :s3_endpoint => s3_host_name }
243
+ config = { region: s3_region }
224
244
 
225
245
  if using_http_proxy?
226
246
 
@@ -234,7 +254,9 @@ module Paperclip
234
254
  config[:proxy_uri] = URI::HTTP.build(proxy_opts)
235
255
  end
236
256
 
237
- [:access_key_id, :secret_access_key, :credential_provider].each do |opt|
257
+ config[:use_accelerate_endpoint] = use_accelerate_endpoint?
258
+
259
+ [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt|
238
260
  config[opt] = s3_credentials[opt] if s3_credentials[opt]
239
261
  end
240
262
 
@@ -244,15 +266,23 @@ module Paperclip
244
266
 
245
267
  def obtain_s3_instance_for(options)
246
268
  instances = (Thread.current[:paperclip_s3_instances] ||= {})
247
- instances[options] ||= AWS::S3.new(options)
269
+ instances[options] ||= ::Aws::S3::Resource.new(options)
248
270
  end
249
271
 
250
272
  def s3_bucket
251
- @s3_bucket ||= s3_interface.buckets[bucket_name]
273
+ @s3_bucket ||= s3_interface.bucket(bucket_name)
274
+ end
275
+
276
+ def style_name_as_path(style_name)
277
+ path(style_name).sub(%r{\A/},'')
252
278
  end
253
279
 
254
280
  def s3_object style_name = default_style
255
- s3_bucket.objects[path(style_name).sub(%r{\A/},'')]
281
+ s3_bucket.object style_name_as_path(style_name)
282
+ end
283
+
284
+ def use_accelerate_endpoint?
285
+ !!@use_accelerate_endpoint
256
286
  end
257
287
 
258
288
  def using_http_proxy?
@@ -277,7 +307,7 @@ module Paperclip
277
307
 
278
308
  def set_permissions permissions
279
309
  permissions = { :default => permissions } unless permissions.respond_to?(:merge)
280
- permissions.merge :default => (permissions[:default] || :public_read)
310
+ permissions.merge :default => (permissions[:default] || :"public-read")
281
311
  end
282
312
 
283
313
  def set_storage_class(storage_class)
@@ -286,10 +316,9 @@ module Paperclip
286
316
  end
287
317
 
288
318
  def parse_credentials creds
289
- creds = creds.respond_to?('call') ? creds.call(self) : creds
319
+ creds = creds.respond_to?(:call) ? creds.call(self) : creds
290
320
  creds = find_credentials(creds).stringify_keys
291
- env = Object.const_defined?(:Rails) ? Rails.env : nil
292
- (creds[env] || creds).symbolize_keys
321
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
293
322
  end
294
323
 
295
324
  def exists?(style = default_style)
@@ -298,7 +327,7 @@ module Paperclip
298
327
  else
299
328
  false
300
329
  end
301
- rescue AWS::Errors::Base => e
330
+ rescue Aws::Errors::ServiceError => e
302
331
  false
303
332
  end
304
333
 
@@ -324,7 +353,7 @@ module Paperclip
324
353
  end
325
354
 
326
355
  def create_bucket
327
- s3_interface.buckets.create(bucket_name)
356
+ s3_interface.bucket(bucket_name).create
328
357
  end
329
358
 
330
359
  def flush_writes #:nodoc:
@@ -332,11 +361,9 @@ module Paperclip
332
361
  retries = 0
333
362
  begin
334
363
  log("saving #{path(style)}")
335
- acl = @s3_permissions[style] || @s3_permissions[:default]
336
- acl = acl.call(self, style) if acl.respond_to?(:call)
337
364
  write_options = {
338
365
  :content_type => file.content_type,
339
- :acl => acl
366
+ :acl => s3_permissions(style)
340
367
  }
341
368
 
342
369
  # add storage class for this style if defined
@@ -357,11 +384,11 @@ module Paperclip
357
384
  write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
358
385
  write_options.merge!(@s3_headers)
359
386
 
360
- s3_object(style).write(file, write_options)
361
- rescue AWS::S3::Errors::NoSuchBucket
387
+ s3_object(style).upload_file(file.path, write_options)
388
+ rescue ::Aws::S3::Errors::NoSuchBucket
362
389
  create_bucket
363
390
  retry
364
- rescue AWS::S3::Errors::SlowDown
391
+ rescue ::Aws::S3::Errors::SlowDown
365
392
  retries += 1
366
393
  if retries <= 5
367
394
  sleep((2 ** retries) * 0.5)
@@ -383,8 +410,8 @@ module Paperclip
383
410
  @queued_for_delete.each do |path|
384
411
  begin
385
412
  log("deleting #{path}")
386
- s3_bucket.objects[path.sub(%r{\A/},'')].delete
387
- rescue AWS::Errors::Base => e
413
+ s3_bucket.object(path.sub(%r{\A/}, "")).delete
414
+ rescue Aws::Errors::ServiceError => e
388
415
  # Ignore this.
389
416
  end
390
417
  end
@@ -394,11 +421,11 @@ module Paperclip
394
421
  def copy_to_local_file(style, local_dest_path)
395
422
  log("copying #{path(style)} to local file #{local_dest_path}")
396
423
  ::File.open(local_dest_path, 'wb') do |local_file|
397
- s3_object(style).read do |chunk|
424
+ s3_object(style).get do |chunk|
398
425
  local_file.write(chunk)
399
426
  end
400
427
  end
401
- rescue AWS::Errors::Base => e
428
+ rescue Aws::Errors::ServiceError => e
402
429
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
403
430
  false
404
431
  end
@@ -3,10 +3,11 @@ module Paperclip
3
3
  class Thumbnail < Processor
4
4
 
5
5
  attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options,
6
- :source_file_options, :animated, :auto_orient
6
+ :source_file_options, :animated, :auto_orient, :frame_index
7
7
 
8
8
  # List of formats that we need to preserve animation
9
9
  ANIMATED_FORMATS = %w(gif)
10
+ MULTI_FRAME_FORMATS = %w(.mkv .avi .mp4 .mov .mpg .mpeg .gif)
10
11
 
11
12
  # Creates a Thumbnail object set to work on the +file+ given. It
12
13
  # will attempt to transform the image into one defined by +target_geometry+
@@ -25,11 +26,11 @@ module Paperclip
25
26
  # +whiny+ - whether to raise an error when processing fails. Defaults to true
26
27
  # +format+ - the desired filename extension
27
28
  # +animated+ - whether to merge all the layers in the image. Defaults to true
29
+ # +frame_index+ - the frame index of the source file to render as the thumbnail
28
30
  def initialize(file, options = {}, attachment = nil)
29
31
  super
30
32
 
31
33
  geometry = options[:geometry].to_s
32
- @file = file
33
34
  @crop = geometry[-1,1] == '#'
34
35
  @target_geometry = options.fetch(:string_geometry_parser, Geometry).parse(geometry)
35
36
  @current_geometry = options.fetch(:file_geometry_parser, Geometry).from_file(@file)
@@ -42,12 +43,12 @@ module Paperclip
42
43
  if @auto_orient && @current_geometry.respond_to?(:auto_orient)
43
44
  @current_geometry.auto_orient
44
45
  end
45
-
46
46
  @source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
47
47
  @convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
48
48
 
49
49
  @current_format = File.extname(@file.path)
50
50
  @basename = File.basename(@file.path, @current_format)
51
+ @frame_index = multi_frame_format? ? options.fetch(:frame_index, 0) : 0
51
52
  end
52
53
 
53
54
  # Returns true if the +target_geometry+ is meant to crop.
@@ -64,8 +65,8 @@ module Paperclip
64
65
  # that contains the new image.
65
66
  def make
66
67
  src = @file
67
- dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
68
- dst.binmode
68
+ filename = [@basename, @format ? ".#{@format}" : ""].join
69
+ dst = TempfileFactory.new.generate(filename)
69
70
 
70
71
  begin
71
72
  parameters = []
@@ -77,7 +78,12 @@ module Paperclip
77
78
 
78
79
  parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
79
80
 
80
- success = convert(parameters, :source => "#{File.expand_path(src.path)}#{'[0]' unless animated?}", :dest => File.expand_path(dst.path))
81
+ frame = animated? ? "" : "[#{@frame_index}]"
82
+ convert(
83
+ parameters,
84
+ source: "#{File.expand_path(src.path)}#{frame}",
85
+ dest: File.expand_path(dst.path),
86
+ )
81
87
  rescue Cocaine::ExitStatusError => e
82
88
  raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny
83
89
  rescue Cocaine::CommandNotFoundError => e
@@ -102,7 +108,10 @@ module Paperclip
102
108
 
103
109
  protected
104
110
 
105
- # Return true if the format is animated
111
+ def multi_frame_format?
112
+ MULTI_FRAME_FORMATS.include? @current_format
113
+ end
114
+
106
115
  def animated?
107
116
  @animated && (ANIMATED_FORMATS.include?(@format.to_s) || @format.blank?) && identified_as_animated?
108
117
  end
@@ -2,29 +2,32 @@ require 'uri'
2
2
 
3
3
  module Paperclip
4
4
  class UrlGenerator
5
- def initialize(attachment, attachment_options)
5
+ def initialize(attachment)
6
6
  @attachment = attachment
7
- @attachment_options = attachment_options
8
7
  end
9
8
 
10
9
  def for(style_name, options)
11
- timestamp_as_needed(
12
- escape_url_as_needed(
13
- @attachment_options[:interpolator].interpolate(most_appropriate_url, @attachment, style_name),
14
- options
15
- ), options)
10
+ interpolated = attachment_options[:interpolator].interpolate(
11
+ most_appropriate_url, @attachment, style_name
12
+ )
13
+
14
+ escaped = escape_url_as_needed(interpolated, options)
15
+ timestamp_as_needed(escaped, options)
16
16
  end
17
17
 
18
18
  private
19
19
 
20
+ attr_reader :attachment
21
+ delegate :options, to: :attachment, prefix: true
22
+
20
23
  # This method is all over the place.
21
24
  def default_url
22
- if @attachment_options[:default_url].respond_to?(:call)
23
- @attachment_options[:default_url].call(@attachment)
24
- elsif @attachment_options[:default_url].is_a?(Symbol)
25
- @attachment.instance.send(@attachment_options[:default_url])
25
+ if attachment_options[:default_url].respond_to?(:call)
26
+ attachment_options[:default_url].call(@attachment)
27
+ elsif attachment_options[:default_url].is_a?(Symbol)
28
+ @attachment.instance.send(attachment_options[:default_url])
26
29
  else
27
- @attachment_options[:default_url]
30
+ attachment_options[:default_url]
28
31
  end
29
32
  end
30
33
 
@@ -32,7 +35,7 @@ module Paperclip
32
35
  if @attachment.original_filename.nil?
33
36
  default_url
34
37
  else
35
- @attachment_options[:url]
38
+ attachment_options[:url]
36
39
  end
37
40
  end
38
41
 
@@ -71,13 +71,7 @@ module Paperclip
71
71
  end
72
72
 
73
73
  def human_size(size)
74
- if defined?(ActiveSupport::NumberHelper) # Rails 4.0+
75
- ActiveSupport::NumberHelper.number_to_human_size(size)
76
- else
77
- storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
78
- unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => size.to_i, :raise => true)
79
- storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe
80
- end
74
+ ActiveSupport::NumberHelper.number_to_human_size(size)
81
75
  end
82
76
 
83
77
  def min_value_in_human_size(record)
@@ -36,7 +36,7 @@ module Paperclip
36
36
  options = attributes.extract_options!.dup
37
37
 
38
38
  Paperclip::Validators.constants.each do |constant|
39
- if constant.to_s =~ /\AAttachment(.+)Validator\Z/
39
+ if constant.to_s =~ /\AAttachment(.+)Validator\z/
40
40
  validator_kind = $1.underscore.to_sym
41
41
 
42
42
  if options.has_key?(validator_kind)
@@ -1,3 +1,5 @@
1
1
  module Paperclip
2
- VERSION = "4.2.2" unless defined? Paperclip::VERSION
2
+ unless defined?(Paperclip::VERSION)
3
+ VERSION = "5.2.1".freeze
4
+ end
3
5
  end
data/lib/paperclip.rb CHANGED
@@ -55,11 +55,21 @@ require 'paperclip/helpers'
55
55
  require 'paperclip/has_attached_file'
56
56
  require 'paperclip/attachment_registry'
57
57
  require 'paperclip/filename_cleaner'
58
- require 'mime/types'
58
+ require 'paperclip/rails_environment'
59
+
60
+ begin
61
+ # Use mime/types/columnar if available, for reduced memory usage
62
+ require "mime/types/columnar"
63
+ rescue LoadError
64
+ require "mime/types"
65
+ end
66
+
67
+ require 'mimemagic'
68
+ require 'mimemagic/overlay'
59
69
  require 'logger'
60
70
  require 'cocaine'
61
71
 
62
- require 'paperclip/railtie' if defined?(Rails)
72
+ require 'paperclip/railtie' if defined?(Rails::Railtie)
63
73
 
64
74
  # The base module that gets included in ActiveRecord::Base. See the
65
75
  # documentation for Paperclip::ClassMethods for more useful information.
@@ -80,14 +90,14 @@ module Paperclip
80
90
  # image's orientation. Defaults to true.
81
91
  def self.options
82
92
  @options ||= {
83
- :whiny => true,
84
- :image_magick_path => nil,
85
- :command_path => nil,
86
- :log => true,
87
- :log_command => true,
88
- :swallow_stderr => true,
89
- :content_type_mappings => {},
90
- :use_exif_orientation => true
93
+ command_path: nil,
94
+ content_type_mappings: {},
95
+ log: true,
96
+ log_command: true,
97
+ read_timeout: nil,
98
+ swallow_stderr: true,
99
+ use_exif_orientation: true,
100
+ whiny: true,
91
101
  }
92
102
  end
93
103
 
@@ -109,7 +119,7 @@ module Paperclip
109
119
  # called on it, the attachment will *not* be deleted until +save+ is called. See the
110
120
  # Paperclip::Attachment documentation for more specifics. There are a number of options
111
121
  # you can set to change the behavior of a Paperclip attachment:
112
- # * +url+: The full URL of where the attachment is publically accessible. This can just
122
+ # * +url+: The full URL of where the attachment is publicly accessible. This can just
113
123
  # as easily point to a directory served directly through Apache as it can to an action
114
124
  # that can control permissions. You can specify the full domain and path, but usually
115
125
  # just an absolute path is sufficient. The leading slash *must* be included manually for
@@ -118,6 +128,9 @@ module Paperclip
118
128
  # Paperclip::Attachment#interpolate for more information on variable interpolaton.
119
129
  # :url => "/:class/:attachment/:id/:style_:filename"
120
130
  # :url => "http://some.other.host/stuff/:class/:id_:extension"
131
+ # Note: When using the +s3+ storage option, the +url+ option expects
132
+ # particular values. See the Paperclip::Storage::S3#url documentation for
133
+ # specifics.
121
134
  # * +default_url+: The URL that will be returned if there is no attachment assigned.
122
135
  # This field is interpolated just as the url is. The default value is
123
136
  # "/:attachment/:style/missing.png"
@@ -136,7 +149,7 @@ module Paperclip
136
149
  # user.avatar.url # => "/avatars/23/normal_me.png"
137
150
  # * +keep_old_files+: Keep the existing attachment files (original + resized) from
138
151
  # being automatically deleted when an attachment is cleared or updated. Defaults to +false+.
139
- # * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
152
+ # * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
140
153
  # record is destroyed. Defaults to +false+.
141
154
  # * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
142
155
  # to a command line error. This will override the global setting for this attachment.
@@ -18,7 +18,7 @@ module Paperclip
18
18
  raise "Class #{klass.name} has no attachments specified"
19
19
  end
20
20
 
21
- if !name.blank? && attachment_names.map(&:to_s).include?(name.to_s)
21
+ if name.present? && attachment_names.map(&:to_s).include?(name.to_s)
22
22
  [ name ]
23
23
  else
24
24
  attachment_names
@@ -46,7 +46,7 @@ namespace :paperclip do
46
46
  attachment = instance.send(name)
47
47
  begin
48
48
  attachment.reprocess!(*styles)
49
- rescue Exception => e
49
+ rescue StandardError => e
50
50
  Paperclip::Task.log_error("exception while processing #{klass} ID #{instance.id}:")
51
51
  Paperclip::Task.log_error(" " + e.message + "\n")
52
52
  end
@@ -64,7 +64,8 @@ namespace :paperclip do
64
64
  names = Paperclip::Task.obtain_attachments(klass)
65
65
  names.each do |name|
66
66
  Paperclip.each_instance_with_attachment(klass, name) do |instance|
67
- if file = Paperclip.io_adapters.for(instance.send(name))
67
+ attachment = instance.send(name)
68
+ if file = Paperclip.io_adapters.for(attachment, attachment.options[:adapter_options])
68
69
  instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
69
70
  instance.send("#{name}_content_type=", file.content_type.to_s.strip)
70
71
  instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
@@ -90,6 +91,19 @@ namespace :paperclip do
90
91
  end
91
92
  Paperclip.save_current_attachments_styles!
92
93
  end
94
+
95
+ desc "Regenerates fingerprints for a given CLASS (and optional ATTACHMENT). Useful when changing digest."
96
+ task :fingerprints => :environment do
97
+ klass = Paperclip::Task.obtain_class
98
+ names = Paperclip::Task.obtain_attachments(klass)
99
+ names.each do |name|
100
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
101
+ attachment = instance.send(name)
102
+ attachment.assign(attachment)
103
+ instance.save(:validate => false)
104
+ end
105
+ end
106
+ end
93
107
  end
94
108
 
95
109
  desc "Cleans out invalid attachments. Useful after you've added new validations."
@@ -108,4 +122,20 @@ namespace :paperclip do
108
122
  end
109
123
  end
110
124
  end
125
+
126
+ desc "find missing attachments. Useful to know which attachments are broken"
127
+ task :find_broken_attachments => :environment do
128
+ klass = Paperclip::Task.obtain_class
129
+ names = Paperclip::Task.obtain_attachments(klass)
130
+ names.each do |name|
131
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
132
+ attachment = instance.send(name)
133
+ if attachment.exists?
134
+ print "."
135
+ else
136
+ Paperclip::Task.log_error("#{instance.class}##{attachment.name}, #{instance.id}, #{attachment.url}")
137
+ end
138
+ end
139
+ end
140
+ end
111
141
  end