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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +17 -0
- data/.hound.yml +1055 -0
- data/.rubocop.yml +1 -0
- data/.travis.yml +17 -15
- data/Appraisals +4 -16
- data/CONTRIBUTING.md +19 -8
- data/Gemfile +5 -9
- data/LICENSE +1 -1
- data/NEWS +148 -31
- data/README.md +327 -191
- data/RELEASING.md +17 -0
- data/Rakefile +2 -2
- data/UPGRADING +12 -9
- data/features/basic_integration.feature +10 -6
- data/features/migration.feature +0 -24
- data/features/step_definitions/attachment_steps.rb +33 -27
- data/features/step_definitions/html_steps.rb +2 -2
- data/features/step_definitions/rails_steps.rb +39 -38
- data/features/step_definitions/s3_steps.rb +2 -2
- data/features/step_definitions/web_steps.rb +1 -103
- data/features/support/env.rb +1 -0
- data/features/support/file_helpers.rb +2 -2
- data/features/support/paths.rb +1 -1
- data/features/support/rails.rb +0 -24
- data/gemfiles/4.2.gemfile +6 -8
- data/gemfiles/5.0.gemfile +17 -0
- data/lib/paperclip/attachment.rb +32 -20
- data/lib/paperclip/attachment_registry.rb +3 -2
- data/lib/paperclip/callbacks.rb +8 -6
- data/lib/paperclip/content_type_detector.rb +27 -11
- data/lib/paperclip/errors.rb +3 -1
- data/lib/paperclip/file_command_content_type_detector.rb +6 -8
- data/lib/paperclip/geometry_parser_factory.rb +1 -1
- data/lib/paperclip/glue.rb +1 -1
- data/lib/paperclip/has_attached_file.rb +9 -2
- data/lib/paperclip/helpers.rb +14 -10
- data/lib/paperclip/interpolations/plural_cache.rb +6 -5
- data/lib/paperclip/interpolations.rb +19 -14
- data/lib/paperclip/io_adapters/abstract_adapter.rb +26 -3
- data/lib/paperclip/io_adapters/attachment_adapter.rb +10 -5
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +8 -8
- data/lib/paperclip/io_adapters/empty_string_adapter.rb +5 -4
- data/lib/paperclip/io_adapters/file_adapter.rb +12 -6
- data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +7 -7
- data/lib/paperclip/io_adapters/identity_adapter.rb +12 -6
- data/lib/paperclip/io_adapters/nil_adapter.rb +8 -5
- data/lib/paperclip/io_adapters/registry.rb +6 -2
- data/lib/paperclip/io_adapters/stringio_adapter.rb +9 -6
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +10 -6
- data/lib/paperclip/io_adapters/uri_adapter.rb +41 -19
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
- data/lib/paperclip/media_type_spoof_detector.rb +2 -2
- data/lib/paperclip/processor.rb +5 -4
- data/lib/paperclip/rails_environment.rb +25 -0
- data/lib/paperclip/schema.rb +3 -9
- data/lib/paperclip/storage/filesystem.rb +13 -2
- data/lib/paperclip/storage/fog.rb +30 -18
- data/lib/paperclip/storage/s3.rb +92 -65
- data/lib/paperclip/thumbnail.rb +16 -7
- data/lib/paperclip/url_generator.rb +16 -13
- data/lib/paperclip/validators/attachment_size_validator.rb +1 -7
- data/lib/paperclip/validators.rb +1 -1
- data/lib/paperclip/version.rb +3 -1
- data/lib/paperclip.rb +25 -12
- data/lib/tasks/paperclip.rake +33 -3
- data/paperclip.gemspec +18 -15
- data/spec/paperclip/attachment_definitions_spec.rb +1 -1
- data/spec/paperclip/attachment_processing_spec.rb +2 -4
- data/spec/paperclip/attachment_registry_spec.rb +84 -13
- data/spec/paperclip/attachment_spec.rb +130 -39
- data/spec/paperclip/content_type_detector_spec.rb +8 -1
- data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -1
- data/spec/paperclip/geometry_spec.rb +1 -1
- data/spec/paperclip/glue_spec.rb +44 -0
- data/spec/paperclip/has_attached_file_spec.rb +24 -8
- data/spec/paperclip/integration_spec.rb +4 -3
- data/spec/paperclip/interpolations_spec.rb +16 -13
- data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +47 -23
- data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +6 -3
- data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +7 -1
- data/spec/paperclip/io_adapters/file_adapter_spec.rb +6 -3
- data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +26 -6
- data/spec/paperclip/io_adapters/identity_adapter_spec.rb +1 -1
- data/spec/paperclip/io_adapters/registry_spec.rb +2 -2
- data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +5 -1
- data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +5 -5
- data/spec/paperclip/io_adapters/uri_adapter_spec.rb +77 -7
- data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +10 -0
- data/spec/paperclip/media_type_spoof_detector_spec.rb +34 -11
- data/spec/paperclip/paperclip_spec.rb +4 -29
- data/spec/paperclip/plural_cache_spec.rb +17 -16
- data/spec/paperclip/rails_environment_spec.rb +33 -0
- data/spec/paperclip/storage/fog_spec.rb +58 -3
- data/spec/paperclip/storage/s3_live_spec.rb +20 -14
- data/spec/paperclip/storage/s3_spec.rb +398 -213
- data/spec/paperclip/tempfile_factory_spec.rb +4 -0
- data/spec/paperclip/tempfile_spec.rb +35 -0
- data/spec/paperclip/thumbnail_spec.rb +51 -32
- data/spec/paperclip/url_generator_spec.rb +55 -44
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +26 -20
- data/spec/paperclip/validators_spec.rb +5 -5
- data/spec/spec_helper.rb +8 -1
- data/spec/support/assertions.rb +12 -1
- data/spec/support/conditional_filter_helper.rb +5 -0
- data/spec/support/fake_model.rb +4 -0
- data/spec/support/fixtures/empty.xlsx +0 -0
- data/spec/support/matchers/have_column.rb +11 -2
- data/spec/support/mock_attachment.rb +2 -0
- data/spec/support/mock_url_generator_builder.rb +2 -2
- data/spec/support/model_reconstruction.rb +9 -1
- data/spec/support/reporting.rb +11 -0
- metadata +109 -162
- data/RUNNING_TESTS.md +0 -4
- data/cucumber/paperclip_steps.rb +0 -6
- data/gemfiles/3.2.gemfile +0 -19
- data/gemfiles/4.0.gemfile +0 -19
- data/gemfiles/4.1.gemfile +0 -19
- data/lib/paperclip/locales/de.yml +0 -18
- data/lib/paperclip/locales/es.yml +0 -18
- data/lib/paperclip/locales/ja.yml +0 -18
- data/lib/paperclip/locales/pt-BR.yml +0 -18
- data/lib/paperclip/locales/zh-CN.yml +0 -18
- data/lib/paperclip/locales/zh-HK.yml +0 -18
- data/lib/paperclip/locales/zh-TW.yml +0 -18
- data/spec/support/mock_model.rb +0 -2
- data/spec/support/rails_helpers.rb +0 -7
data/lib/paperclip/storage/s3.rb
CHANGED
|
@@ -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
|
|
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 =>
|
|
48
|
+
# :original => "private"
|
|
49
49
|
# }
|
|
50
50
|
# Or globally:
|
|
51
|
-
# :s3_permissions =>
|
|
51
|
+
# :s3_permissions => "private"
|
|
52
52
|
#
|
|
53
|
-
# * +s3_protocol+: The protocol for the URLs generated to your S3 assets.
|
|
54
|
-
# 'http', 'https', or an empty string to generate
|
|
55
|
-
#
|
|
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
|
|
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>:
|
|
102
|
-
# Redundancy Storage.
|
|
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 => :
|
|
113
|
+
# :thumb => :REDUCED_REDUNDANCY
|
|
109
114
|
# }
|
|
115
|
+
#
|
|
110
116
|
# Or globally:
|
|
111
|
-
# :s3_storage_class => :
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 =
|
|
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\
|
|
161
|
-
@options[:path] = path_option.gsub(/:url/, @options[:url]).
|
|
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
|
-
|
|
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).
|
|
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).
|
|
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).
|
|
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 = { :
|
|
186
|
-
s3_object(style_name).
|
|
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 = { :
|
|
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
|
-
[:
|
|
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] ||=
|
|
269
|
+
instances[options] ||= ::Aws::S3::Resource.new(options)
|
|
248
270
|
end
|
|
249
271
|
|
|
250
272
|
def s3_bucket
|
|
251
|
-
@s3_bucket ||= s3_interface.
|
|
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.
|
|
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] || :
|
|
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?(
|
|
319
|
+
creds = creds.respond_to?(:call) ? creds.call(self) : creds
|
|
290
320
|
creds = find_credentials(creds).stringify_keys
|
|
291
|
-
|
|
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
|
|
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.
|
|
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 =>
|
|
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).
|
|
361
|
-
rescue
|
|
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
|
|
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.
|
|
387
|
-
rescue
|
|
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).
|
|
424
|
+
s3_object(style).get do |chunk|
|
|
398
425
|
local_file.write(chunk)
|
|
399
426
|
end
|
|
400
427
|
end
|
|
401
|
-
rescue
|
|
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
|
data/lib/paperclip/thumbnail.rb
CHANGED
|
@@ -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
|
-
|
|
68
|
-
dst.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
elsif
|
|
25
|
-
@attachment.instance.send(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
data/lib/paperclip/validators.rb
CHANGED
|
@@ -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\
|
|
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)
|
data/lib/paperclip/version.rb
CHANGED
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 '
|
|
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
|
-
:
|
|
84
|
-
:
|
|
85
|
-
:
|
|
86
|
-
:
|
|
87
|
-
:
|
|
88
|
-
:
|
|
89
|
-
:
|
|
90
|
-
:
|
|
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
|
|
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.
|
data/lib/tasks/paperclip.rake
CHANGED
|
@@ -18,7 +18,7 @@ module Paperclip
|
|
|
18
18
|
raise "Class #{klass.name} has no attachments specified"
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
if
|
|
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
|
|
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
|
-
|
|
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
|